diff --git a/.gitignore b/.gitignore
index 05158cca2..1613a638d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -160,6 +160,7 @@ tmp
metagpt/roles/idea_agent.py
.aider*
*.bak
+*.bk
# output folder
output
@@ -168,3 +169,5 @@ tmp.png
tests/metagpt/utils/file_repo_git
*.tmp
*.png
+htmlcov
+htmlcov.*
diff --git a/metagpt/actions/design_api.py b/metagpt/actions/design_api.py
index 03f3d7704..2574550e4 100644
--- a/metagpt/actions/design_api.py
+++ b/metagpt/actions/design_api.py
@@ -47,10 +47,10 @@ class WriteDesign(Action):
)
async def run(self, with_messages: Message, schema: str = CONFIG.prompt_schema):
- # Use `git diff` to identify which PRD documents have been modified in the `docs/prds` directory.
+ # Use `git status` to identify which PRD documents have been modified in the `docs/prds` directory.
prds_file_repo = CONFIG.git_repo.new_file_repository(PRDS_FILE_REPO)
changed_prds = prds_file_repo.changed_files
- # Use `git diff` to identify which design documents in the `docs/system_designs` directory have undergone
+ # Use `git status` to identify which design documents in the `docs/system_designs` directory have undergone
# changes.
system_design_file_repo = CONFIG.git_repo.new_file_repository(SYSTEM_DESIGN_FILE_REPO)
changed_system_designs = system_design_file_repo.changed_files
diff --git a/metagpt/memory/brain_memory.py b/metagpt/memory/brain_memory.py
index fe6bf991d..ff29eaddb 100644
--- a/metagpt/memory/brain_memory.py
+++ b/metagpt/memory/brain_memory.py
@@ -157,7 +157,7 @@ class BrainMemory(BaseModel):
if left == 0:
break
m.content = m.content[0:left]
- msgs.append(m.model_dump())
+ msgs.append(m)
break
msgs.append(m)
total_length += delta
@@ -171,8 +171,8 @@ class BrainMemory(BaseModel):
@staticmethod
def to_metagpt_history_format(history) -> str:
- mmsg = [SimpleMessage(role=m.role, content=m.content) for m in history]
- return json.dumps(mmsg)
+ mmsg = [SimpleMessage(role=m.role, content=m.content).model_dump() for m in history]
+ return json.dumps(mmsg, ensure_ascii=False)
async def get_title(self, llm, max_words=5, **kwargs) -> str:
"""Generate text title"""
diff --git a/metagpt/roles/assistant.py b/metagpt/roles/assistant.py
index 89965f3bd..227578a63 100644
--- a/metagpt/roles/assistant.py
+++ b/metagpt/roles/assistant.py
@@ -132,8 +132,8 @@ class Assistant(Role):
def get_memory(self) -> str:
return self.memory.model_dump_json()
- def load_memory(self, jsn):
+ def load_memory(self, m):
try:
- self.memory = BrainMemory(**jsn)
+ self.memory = BrainMemory(**m)
except Exception as e:
logger.exception(f"load error:{e}, data:{jsn}")
diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py
index b8866e055..e05e69cbb 100644
--- a/metagpt/roles/engineer.py
+++ b/metagpt/roles/engineer.py
@@ -235,7 +235,9 @@ class Engineer(Role):
task_doc = await task_file_repo.get(i.name)
elif str(i.parent) == SYSTEM_DESIGN_FILE_REPO:
design_doc = await design_file_repo.get(i.name)
- # FIXME: design doc没有加载进来,是None
+ if not task_doc or not design_doc:
+ logger.error(f'Detected source code "{filename}" from an unknown origin.')
+ raise ValueError(f'Detected source code "{filename}" from an unknown origin.')
context = CodingContext(filename=filename, design_doc=design_doc, task_doc=task_doc, code_doc=old_code_doc)
return context
diff --git a/metagpt/schema.py b/metagpt/schema.py
index 5dde0ee46..91158ffeb 100644
--- a/metagpt/schema.py
+++ b/metagpt/schema.py
@@ -343,16 +343,21 @@ class MessageQueue(BaseModel):
return "[]"
lst = []
+ msgs = []
try:
while True:
item = await wait_for(self._queue.get(), timeout=1.0)
if item is None:
break
- lst.append(item.dict(exclude_none=True))
+ msgs.append(item)
+ lst.append(item.dump())
self._queue.task_done()
except asyncio.TimeoutError:
logger.debug("Queue is empty, exiting...")
- return json.dumps(lst)
+ finally:
+ for m in msgs:
+ self._queue.put_nowait(m)
+ return json.dumps(lst, ensure_ascii=False)
@staticmethod
def load(data) -> "MessageQueue":
@@ -361,7 +366,7 @@ class MessageQueue(BaseModel):
try:
lst = json.loads(data)
for i in lst:
- msg = Message(**i)
+ msg = Message.load(i)
queue.push(msg)
except JSONDecodeError as e:
logger.warning(f"JSON load failed: {data}, error:{e}")
diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py
index 30c318fd5..5999b2e11 100644
--- a/metagpt/utils/common.py
+++ b/metagpt/utils/common.py
@@ -528,18 +528,18 @@ def role_raise_decorator(func):
@handle_exception
-async def aread(file_path: str) -> str:
+async def aread(filename: str | Path, encoding=None) -> str:
"""Read file asynchronously."""
- async with aiofiles.open(str(file_path), mode="r") as reader:
+ async with aiofiles.open(str(filename), mode="r", encoding=encoding) as reader:
content = await reader.read()
return content
-async def awrite(filename: str | Path, data: str):
+async def awrite(filename: str | Path, data: str, encoding=None):
"""Write file asynchronously."""
pathname = Path(filename)
pathname.parent.mkdir(parents=True, exist_ok=True)
- async with aiofiles.open(str(pathname), mode="w", encoding="utf-8") as writer:
+ async with aiofiles.open(str(pathname), mode="w", encoding=encoding) as writer:
await writer.write(data)
diff --git a/metagpt/utils/pycst.py b/metagpt/utils/pycst.py
index 1edfed81c..a26ba70ff 100644
--- a/metagpt/utils/pycst.py
+++ b/metagpt/utils/pycst.py
@@ -49,6 +49,14 @@ def get_docstring_statement(body: DocstringNode) -> cst.SimpleStatementLine:
return statement
+def has_decorator(node: DocstringNode, name: str) -> bool:
+ return hasattr(node, "decorators") and any(
+ (hasattr(i.decorator, "value") and i.decorator.value == name)
+ or (hasattr(i.decorator, "func") and hasattr(i.decorator.func, "value") and i.decorator.func.value == name)
+ for i in node.decorators
+ )
+
+
class DocstringCollector(cst.CSTVisitor):
"""A visitor class for collecting docstrings from a CST.
@@ -82,7 +90,7 @@ class DocstringCollector(cst.CSTVisitor):
def _leave(self, node: DocstringNode) -> None:
key = tuple(self.stack)
self.stack.pop()
- if hasattr(node, "decorators") and any(i.decorator.value == "overload" for i in node.decorators):
+ if has_decorator(node, "overload"):
return
statement = get_docstring_statement(node)
@@ -127,9 +135,7 @@ class DocstringTransformer(cst.CSTTransformer):
key = tuple(self.stack)
self.stack.pop()
- if hasattr(updated_node, "decorators") and any(
- (i.decorator.value == "overload") for i in updated_node.decorators
- ):
+ if has_decorator(updated_node, "overload"):
return updated_node
statement = self.docstrings.get(key)
diff --git a/tests/data/code/js/1.js b/tests/data/code/js/1.js
new file mode 100644
index 000000000..042f922b3
--- /dev/null
+++ b/tests/data/code/js/1.js
@@ -0,0 +1,6 @@
+WRMCB=function(e){var c=console;if(c&&c.log&&c.error){c.log('Error running batched script.');c.error(e);}}
+;
+try {
+/* module-key = 'jira.webresources:bigpipe-js', location = '/includes/jira/common/bigpipe.js' */
+define("jira/bigpipe/element",["jquery","wrm/data","jira/skate","jira/util/logger"],function(e,r,t,n){return t("big-pipe",{attached:function(i){function a(){var e=new CustomEvent("success");i.dispatchEvent(e)}function o(e,r){var t=new CustomEvent("error");t.data={event:e,signature:r},i.dispatchEvent(t)}function d(e,r){p("error"),o(e,r)}function p(e){"performance"in window&&performance.mark&&performance.mark(c+e)}var s=i.getAttribute("data-id");if(null===s)return n.error("No data-id attribute provided for tag for element:",i),void d({name:"NoPipeIdError",message:"Unable to render element. Element does not contain a pipe id.",element:i},"no.pipe.id");var c="bigPipe."+s+".";p("start");var u=r.claim(s);u?function(r){try{var o=e(r);e(i).replaceWith(o).each(function(){t.init(this)}),p("end"),a()}catch(e){n.error("Error while parsing html: "+e),d(e,"parsing")}}(u):d({name:"NoDataError",message:"BigPipe response is empty."},"no.data")},detached:function(){},type:t.type.ELEMENT,resolvedAttribute:"resolved",unresolvedAttribute:"unresolved"})});
+}catch(e){WRMCB(e)};
\ No newline at end of file
diff --git a/tests/data/code/python/1.py b/tests/data/code/python/1.py
new file mode 100644
index 000000000..e9aeaeeee
--- /dev/null
+++ b/tests/data/code/python/1.py
@@ -0,0 +1,83 @@
+"""
+===============
+Degree Analysis
+===============
+
+This example shows several ways to visualize the distribution of the degree of
+nodes with two common techniques: a *degree-rank plot* and a
+*degree histogram*.
+
+In this example, a random Graph is generated with 100 nodes. The degree of
+each node is determined, and a figure is generated showing three things:
+1. The subgraph of connected components
+2. The degree-rank plot for the Graph, and
+3. The degree histogram
+"""
+import matplotlib.pyplot as plt
+import networkx as nx
+import numpy as np
+
+G = nx.gnp_random_graph(100, 0.02, seed=10374196)
+
+degree_sequence = sorted((d for n, d in G.degree()), reverse=True)
+dmax = max(degree_sequence)
+
+fig = plt.figure("Degree of a random graph", figsize=(8, 8))
+# Create a gridspec for adding subplots of different sizes
+axgrid = fig.add_gridspec(5, 4)
+
+ax0 = fig.add_subplot(axgrid[0:3, :])
+Gcc = G.subgraph(sorted(nx.connected_components(G), key=len, reverse=True)[0])
+pos = nx.spring_layout(Gcc, seed=10396953)
+nx.draw_networkx_nodes(Gcc, pos, ax=ax0, node_size=20)
+nx.draw_networkx_edges(Gcc, pos, ax=ax0, alpha=0.4)
+ax0.set_title("Connected components of G")
+ax0.set_axis_off()
+
+print("aa")
+
+ax1 = fig.add_subplot(axgrid[3:, :2])
+ax1.plot(degree_sequence, "b-", marker="o")
+ax1.set_title("Degree Rank Plot")
+ax1.set_ylabel("Degree")
+ax1.set_xlabel("Rank")
+
+ax2 = fig.add_subplot(axgrid[3:, 2:])
+ax2.bar(*np.unique(degree_sequence, return_counts=True))
+ax2.set_title("Degree histogram")
+ax2.set_xlabel("Degree")
+ax2.set_ylabel("# of Nodes")
+
+fig.tight_layout()
+plt.show()
+
+
+class Game:
+ def __init__(self):
+ self.snake = Snake(400, 300, 5, 0)
+ self.enemy = Enemy(100, 100, 3, 1)
+ self.power_up = PowerUp(200, 200)
+
+ def handle_events(self):
+ for event in pygame.event.get():
+ if event.type == pygame.QUIT:
+ return False
+ elif event.type == pygame.KEYDOWN:
+ if event.key == pygame.K_UP:
+ self.snake.change_direction(0)
+ elif event.key == pygame.K_DOWN:
+ self.snake.change_direction(1)
+ elif event.key == pygame.K_LEFT:
+ self.snake.change_direction(2)
+ elif event.key == pygame.K_RIGHT:
+ self.snake.change_direction(3)
+ return True
+
+ def update(self):
+ self.snake.move()
+ self.enemy.move()
+
+ def draw(self, screen):
+ self.snake.draw(screen)
+ self.enemy.draw(screen)
+ self.power_up.draw(screen)
diff --git a/tests/data/demo_project/dependencies.json b/tests/data/demo_project/dependencies.json
new file mode 100644
index 000000000..cfcf6c165
--- /dev/null
+++ b/tests/data/demo_project/dependencies.json
@@ -0,0 +1 @@
+{"docs/system_design/20231221155954.json": ["docs/prds/20231221155954.json"], "docs/tasks/20231221155954.json": ["docs/system_design/20231221155954.json"], "game_2048/game.py": ["docs/tasks/20231221155954.json", "docs/system_design/20231221155954.json"], "game_2048/main.py": ["docs/tasks/20231221155954.json", "docs/system_design/20231221155954.json"], "resources/code_summaries/20231221155954.md": ["docs/tasks/20231221155954.json", "game_2048/game.py", "docs/system_design/20231221155954.json", "game_2048/main.py"], "docs/code_summaries/20231221155954.json": ["docs/tasks/20231221155954.json", "game_2048/game.py", "docs/system_design/20231221155954.json", "game_2048/main.py"], "tests/test_main.py": ["game_2048/main.py"], "tests/test_game.py": ["game_2048/game.py"], "test_outputs/test_main.py.json": ["game_2048/main.py", "tests/test_main.py"], "test_outputs/test_game.py.json": ["game_2048/game.py", "tests/test_game.py"]}
\ No newline at end of file
diff --git a/tests/metagpt/actions/test_ui_design.py b/tests/metagpt/actions/test_ui_design.py
deleted file mode 100644
index 83590ec7d..000000000
--- a/tests/metagpt/actions/test_ui_design.py
+++ /dev/null
@@ -1,189 +0,0 @@
-# -*- coding: utf-8 -*-
-# @Date : 2023/7/22 02:40
-# @Author : stellahong (stellahong@deepwisdom.ai)
-#
-from tests.metagpt.roles.ui_role import UIDesign
-
-llm_resp = """
- # UI Design Description
-```The user interface for the snake game will be designed in a way that is simple, clean, and intuitive. The main elements of the game such as the game grid, snake, food, score, and game over message will be clearly defined and easy to understand. The game grid will be centered on the screen with the score displayed at the top. The game controls will be intuitive and easy to use. The design will be modern and minimalist with a pleasing color scheme.```
-
-## Selected Elements
-
-Game Grid: The game grid will be a rectangular area in the center of the screen where the game will take place. It will be defined by a border and will have a darker background color.
-
-Snake: The snake will be represented by a series of connected blocks that move across the grid. The color of the snake will be different from the background color to make it stand out.
-
-Food: The food will be represented by small objects that are a different color from the snake and the background. The food will be randomly placed on the grid.
-
-Score: The score will be displayed at the top of the screen. The score will increase each time the snake eats a piece of food.
-
-Game Over: When the game is over, a message will be displayed in the center of the screen. The player will be given the option to restart the game.
-
-## HTML Layout
-```html
-
-
-