From 155a5a8c65df2fadf048aac3638bf8d9cee20c42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Tue, 19 Mar 2024 23:03:17 +0800 Subject: [PATCH 1/6] feat: +software develop tool lib --- metagpt/actions/action.py | 5 + metagpt/actions/prepare_documents.py | 5 +- metagpt/context.py | 13 +- metagpt/tools/libs/__init__.py | 16 ++ metagpt/tools/libs/software_development.py | 268 +++++++++++++++++++++ metagpt/utils/git_repository.py | 8 +- 6 files changed, 304 insertions(+), 11 deletions(-) create mode 100644 metagpt/tools/libs/software_development.py diff --git a/metagpt/actions/action.py b/metagpt/actions/action.py index 1b93213f7..5fd538720 100644 --- a/metagpt/actions/action.py +++ b/metagpt/actions/action.py @@ -104,3 +104,8 @@ class Action(SerializationMixin, ContextMixin, BaseModel): if self.node: return await self._run_action_node(*args, **kwargs) raise NotImplementedError("The run method should be implemented in a subclass.") + + def override_context(self): + """Set `private_context` and `context` to the same `Context` object.""" + if not self.private_context: + self.private_context = self.context diff --git a/metagpt/actions/prepare_documents.py b/metagpt/actions/prepare_documents.py index ab069dc11..08f2c2fcb 100644 --- a/metagpt/actions/prepare_documents.py +++ b/metagpt/actions/prepare_documents.py @@ -14,8 +14,6 @@ from typing import Optional from metagpt.actions import Action, ActionOutput from metagpt.const import REQUIREMENT_FILENAME from metagpt.utils.file_repository import FileRepository -from metagpt.utils.git_repository import GitRepository -from metagpt.utils.project_repo import ProjectRepo class PrepareDocuments(Action): @@ -38,8 +36,7 @@ class PrepareDocuments(Action): if path.exists() and not self.config.inc: shutil.rmtree(path) self.config.project_path = path - self.context.git_repo = GitRepository(local_path=path, auto_init=True) - self.context.repo = ProjectRepo(self.context.git_repo) + self.context.set_repo_dir(path) async def run(self, with_messages, **kwargs): """Create and initialize the workspace folder, initialize the Git environment.""" diff --git a/metagpt/context.py b/metagpt/context.py index 0add4c71a..4596e7847 100644 --- a/metagpt/context.py +++ b/metagpt/context.py @@ -5,6 +5,8 @@ @Author : alexanderwu @File : context.py """ +from __future__ import annotations + import os from pathlib import Path from typing import Any, Optional @@ -78,11 +80,12 @@ class Context(BaseModel): # env.update({k: v for k, v in i.items() if isinstance(v, str)}) return env - # def use_llm(self, name: Optional[str] = None, provider: LLMType = LLMType.OPENAI) -> BaseLLM: - # """Use a LLM instance""" - # self._llm_config = self.config.get_llm_config(name, provider) - # self._llm = None - # return self._llm + def set_repo_dir(self, path: str | Path): + repo_path = Path(path) + if not repo_path.exists(): + return + self.git_repo = GitRepository(local_path=repo_path, auto_init=True) + self.repo = ProjectRepo(self.git_repo) def _select_costmanager(self, llm_config: LLMConfig) -> CostManager: """Return a CostManager instance""" diff --git a/metagpt/tools/libs/__init__.py b/metagpt/tools/libs/__init__.py index 91596fd3d..eb5ffbc5c 100644 --- a/metagpt/tools/libs/__init__.py +++ b/metagpt/tools/libs/__init__.py @@ -12,6 +12,15 @@ from metagpt.tools.libs import ( web_scraping, email_login, ) +from metagpt.tools.libs.software_development import ( + write_prd, + write_design, + write_project_plan, + write_codes, + run_qa_test, + fix_bug, + git_archive, +) _ = ( data_preprocess, @@ -20,4 +29,11 @@ _ = ( gpt_v_generator, web_scraping, email_login, + write_prd, + write_design, + write_project_plan, + write_codes, + run_qa_test, + fix_bug, + git_archive, ) # Avoid pre-commit error diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py new file mode 100644 index 000000000..15fc6ad28 --- /dev/null +++ b/metagpt/tools/libs/software_development.py @@ -0,0 +1,268 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import annotations + +from pathlib import Path +from typing import Optional + +from metagpt.const import BUGFIX_FILENAME, REQUIREMENT_FILENAME +from metagpt.schema import BugFixContext, Message +from metagpt.tools.tool_registry import register_tool +from metagpt.utils.common import any_to_str + + +@register_tool(tags=["software company", "ProductManager"]) +async def write_prd(idea: str, project_path: Optional[str | Path]) -> Path: + """Writes a PRD based on user requirements. + + Args: + idea (str): The idea or concept for the PRD. + project_path (Optional[str|Path], optional): The path to an existing project directory created by `write_prd`. + If it's None, a new project path will be created. Defaults to None. + + Returns: + Path: The path to the project directory + + Example: + >>> # Create a new project: + >>> from metagpt.tools.libs.software_development import write_prd + >>> project_path = await write_prd("Create a new feature for the application") + >>> print(project_path) + '/path/to/project_path' + + >>> # Add user requirements to the exists project: + >>> from metagpt.tools.libs.software_development import write_prd + >>> project_path = '/path/to/exists_project_path' + >>> await write_prd("Create a new feature for the application", project_path=project_path) + >>> print(project_path) + '/path/to/exists_project_path' + """ + from metagpt.actions import UserRequirement + from metagpt.context import Context + from metagpt.roles import ProductManager + + ctx = Context() + if project_path: + ctx.config.project_path = Path(project_path) + ctx.config.inc = True + role = ProductManager(context=ctx) + msg = await role.run(with_message=Message(content=idea, cause_by=UserRequirement)) + await role.run(with_message=msg) + return ctx.repo.workdir + + +@register_tool(tags=["software company", "Architect"]) +async def write_design(project_path: str | Path) -> Path: + """Writes a design to the project repository, based on the PRD of the project. + + Args: + project_path (str|Path): The path to the project repository. + + Returns: + Path: The path to the project directory + + Example: + >>> from metagpt.tools.libs.software_development import write_design + >>> project_path = '/path/to/project_path' # Returned by `write_prd` + >>> project_path = await write_desgin(project_path) + >>> print(project_path) + '/path/to/project_path' + + """ + from metagpt.actions import WritePRD + from metagpt.context import Context + from metagpt.roles import Architect + + ctx = Context() + ctx.set_repo_dir(project_path) + + role = Architect(context=ctx) + await role.run(with_message=Message(content="", cause_by=WritePRD)) + return project_path + + +@register_tool(tags=["software company", "Architect"]) +async def write_project_plan(project_path: str | Path) -> Path: + """Writes a project plan to the project repository, based on the design of the project. + + Args: + project_path (str|Path): The path to the project repository. + + Returns: + Path: The path to the project directory + + Example: + >>> from metagpt.tools.libs.software_development import write_project_plan + >>> project_path = '/path/to/project_path' # Returned by `write_prd` + >>> project_path = await write_project_plan(project_path) + >>> print(project_path) + '/path/to/project_path' + + """ + from metagpt.actions import WriteDesign + from metagpt.context import Context + from metagpt.roles import ProjectManager + + ctx = Context() + ctx.set_repo_dir(project_path) + + role = ProjectManager(context=ctx) + await role.run(with_message=Message(content="", cause_by=WriteDesign)) + return project_path + + +@register_tool(tags=["software company", "Engineer"]) +async def write_codes(project_path: str | Path, inc: bool = False) -> Path: + """Writes codes to the project repository, based on the project plan of the project. + + Args: + project_path (str|Path): The path to the project repository. + inc (bool, optional): Whether to write incremental codes. Defaults to False. + + Returns: + Path: The path to the project directory + + Example: + # Write codes to a new project + >>> from metagpt.tools.libs.software_development import write_codes + >>> project_path = '/path/to/project_path' # Returned by `write_prd` + >>> project_path = await write_codes(project_path) + >>> print(project_path) + '/path/to/project_path' + + # Write increment codes to the exists project + >>> from metagpt.tools.libs.software_development import write_codes + >>> project_path = '/path/to/project_path' # Returned by `write_prd` + >>> project_path = await write_codes(project_path, inc=True) + >>> print(project_path) + '/path/to/project_path' + """ + from metagpt.actions import WriteTasks + from metagpt.context import Context + from metagpt.roles import Engineer + + ctx = Context() + ctx.config.inc = inc + ctx.set_repo_dir(project_path) + + role = Engineer(context=ctx) + msg = Message(content="", cause_by=WriteTasks, send_to=role) + me = {any_to_str(role), role.name} + while me.intersection(msg.send_to): + msg = await role.run(with_message=msg) + return project_path + + +@register_tool(tags=["software company", "QaEngineer"]) +async def run_qa_test(project_path: str | Path) -> Path: + """Run QA test on the project repository. + + Args: + project_path (str|Path): The path to the project repository. + + Returns: + Path: The path to the project directory + + Example: + >>> from metagpt.tools.libs.software_development import run_qa_test + >>> project_path = '/path/to/project_path' # Returned by `write_codes` + >>> project_path = await run_qa_test(project_path) + >>> print(project_path) + '/path/to/project_path' + """ + from metagpt.actions.summarize_code import SummarizeCode + from metagpt.context import Context + from metagpt.environment import Environment + from metagpt.roles import QaEngineer + + ctx = Context() + ctx.set_repo_dir(project_path) + ctx.src_workspace = ctx.git_repo.workdir / ctx.git_repo.workdir.name + + env = Environment(context=ctx) + role = QaEngineer(context=ctx) + env.add_role(role) + + msg = Message(content="", cause_by=SummarizeCode, send_to=role) + env.publish_message(msg) + + while not env.is_idle: + await env.run() + return project_path + + +@register_tool(tags=["software company", "Engineer"]) +async def fix_bug(project_path: str | Path, issue: str) -> Path: + """Fix bugs in the project repository. + + Args: + project_path (str|Path): The path to the project repository. + + + Returns: + Path: The path to the project directory + + Example: + >>> from metagpt.tools.libs.software_development import fix_bug + >>> project_path = '/path/to/project_path' # Returned by `write_codes` + >>> issue = 'The exception description about the issue' + >>> project_path = await fix_bug(project_path=project_path, issue=issue) + >>> print(project_path) + '/path/to/project_path' + """ + from metagpt.actions.fix_bug import FixBug + from metagpt.context import Context + from metagpt.roles import Engineer + + ctx = Context() + ctx.set_repo_dir(project_path) + ctx.src_workspace = ctx.git_repo.workdir / ctx.git_repo.workdir.name + await ctx.repo.docs.save(filename=BUGFIX_FILENAME, content=issue) + await ctx.repo.docs.save(filename=REQUIREMENT_FILENAME, content="") + + role = Engineer(context=ctx) + bug_fix = BugFixContext(filename=BUGFIX_FILENAME) + msg = Message( + content=bug_fix.model_dump_json(), + instruct_content=bug_fix, + role="", + cause_by=FixBug, + sent_from=role, + send_to=role, + ) + me = {any_to_str(role), role.name} + while me.intersection(msg.send_to): + msg = await role.run(with_message=msg) + return project_path + + +@register_tool(tags=["software company", "git"]) +async def git_archive(project_path: str | Path) -> str: + """Stage and commit changes for the project repository using Git. + + Args: + project_path (str|Path): The path to the project repository. + + + Returns: + git log + + Example: + >>> from metagpt.tools.libs.software_development import git_archive + >>> project_path = '/path/to/project_path' # Returned by `write_prd` + >>> git_log = await git_archive(project_path=project_path) + >>> print(git_log) + commit a221d1c418c07f2b4fc07001e486285ead1a520a (HEAD -> feature/toollib/software_company, geekan/main) + Merge: e01afd09 4a72f398 + Author: Sirui Hong + Date: Tue Mar 19 15:16:03 2024 +0800 + Merge pull request #1037 from iorisa/fixbug/issues/1018 + fixbug: #1018 + + """ + from metagpt.context import Context + + ctx = Context() + ctx.set_repo_dir(project_path) + ctx.git_repo.archive() + return ctx.git_repo.log() diff --git a/metagpt/utils/git_repository.py b/metagpt/utils/git_repository.py index 16f675175..02ec63eeb 100644 --- a/metagpt/utils/git_repository.py +++ b/metagpt/utils/git_repository.py @@ -52,7 +52,7 @@ class GitRepository: self._dependency = None self._gitignore_rules = None if local_path: - self.open(local_path=local_path, auto_init=auto_init) + self.open(local_path=Path(local_path), auto_init=auto_init) def open(self, local_path: Path, auto_init=False): """Open an existing Git repository or initialize a new one if auto_init is True. @@ -68,7 +68,7 @@ class GitRepository: if not auto_init: return local_path.mkdir(parents=True, exist_ok=True) - return self._init(local_path) + self._init(local_path) def _init(self, local_path: Path): """Initialize a new Git repository at the specified path. @@ -283,3 +283,7 @@ class GitRepository: continue files.append(filename) return files + + def log(self) -> str: + """Return git log""" + return self._repository.git.log() From a994716eb2e9efbeccb7e700f0bac2a092c6d721 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 11:20:04 +0800 Subject: [PATCH 2/6] feat: add software team tool lib --- metagpt/context.py | 2 - metagpt/tools/libs/software_development.py | 2 +- .../tools/libs/test_software_development.py | 57 +++++++++++++++++++ 3 files changed, 58 insertions(+), 3 deletions(-) create mode 100644 tests/metagpt/tools/libs/test_software_development.py diff --git a/metagpt/context.py b/metagpt/context.py index 4596e7847..09e6ec2f9 100644 --- a/metagpt/context.py +++ b/metagpt/context.py @@ -82,8 +82,6 @@ class Context(BaseModel): def set_repo_dir(self, path: str | Path): repo_path = Path(path) - if not repo_path.exists(): - return self.git_repo = GitRepository(local_path=repo_path, auto_init=True) self.repo = ProjectRepo(self.git_repo) diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index 15fc6ad28..0db899f49 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -12,7 +12,7 @@ from metagpt.utils.common import any_to_str @register_tool(tags=["software company", "ProductManager"]) -async def write_prd(idea: str, project_path: Optional[str | Path]) -> Path: +async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Path: """Writes a PRD based on user requirements. Args: diff --git a/tests/metagpt/tools/libs/test_software_development.py b/tests/metagpt/tools/libs/test_software_development.py new file mode 100644 index 000000000..d51fc3dc1 --- /dev/null +++ b/tests/metagpt/tools/libs/test_software_development.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import pytest + +from metagpt.tools.libs import ( + fix_bug, + git_archive, + run_qa_test, + write_codes, + write_design, + write_prd, + write_project_plan, +) + + +@pytest.mark.asyncio +async def test_software_team(): + path = await write_prd("snake game") + assert path + + path = await write_design(path) + assert path + + path = await write_project_plan(path) + assert path + + path = await write_codes(path) + assert path + + path = await run_qa_test(path) + assert path + + issue = """ +pygame 2.0.1 (SDL 2.0.14, Python 3.9.17) +Hello from the pygame community. https://www.pygame.org/contribute.html +Traceback (most recent call last): + File "/Users/ix/github/bak/MetaGPT/workspace/snake_game/snake_game/main.py", line 10, in + main() + File "/Users/ix/github/bak/MetaGPT/workspace/snake_game/snake_game/main.py", line 7, in main + game.start_game() + File "/Users/ix/github/bak/MetaGPT/workspace/snake_game/snake_game/game.py", line 81, in start_game + x +NameError: name 'x' is not defined + """ + path = await fix_bug(path, issue) + assert path + + new_path = await write_prd("snake game with moving enemy", path) + assert new_path == path + + git_log = await git_archive(new_path) + assert git_log + + +if __name__ == "__main__": + pytest.main([__file__, "-s"]) From 0fc536e3ee158a997914626b71143f763e1d0549 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 16:58:31 +0800 Subject: [PATCH 3/6] feat: +examples/di/software_company.py --- examples/di/software_company.py | 45 +++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 examples/di/software_company.py diff --git a/examples/di/software_company.py b/examples/di/software_company.py new file mode 100644 index 000000000..2a11d7981 --- /dev/null +++ b/examples/di/software_company.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import fire + +from metagpt.roles.di.data_interpreter import DataInterpreter + + +async def main(): + # prompt = f"""This is a software requirement:\n```text\nwrite a snake game\n```\n . + # 1. Append user requirement to project path `/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game` and write a PRD.\n + # 2. Write a design to project path `/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game`.\n + # Note: All required dependencies and environments have been fully installed and configured.""" + # prompt = f"""1. fix bug:\n```text\n{issue}\n````\nin project path `/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game`.\n + # Note: All required dependencies and environments have been fully installed and configured.""" + prompt = """ +This is a software requirement: +```text +write a snake game +``` +--- +1. Writes a PRD based on software requirements. +2. Writes a design to the project repository, based on the PRD of the project. +3. Writes a project plan to the project repository, based on the design of the project. +4. Writes codes to the project repository, based on the project plan of the project. +5. Run QA test on the project repository. +6. Stage and commit changes for the project repository using Git. +Note: All required dependencies and environments have been fully installed and configured. +""" + di = DataInterpreter( + tools=[ + "write_prd", + "write_design", + "write_project_plan", + "write_codes", + "run_qa_test", + "fix_bug", + "git_archive", + ] + ) + + await di.run(prompt) + + +if __name__ == "__main__": + fire.Fire(main) From a02c213b325a768c29da0fa7642eaecfdc3b5c92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 18:56:09 +0800 Subject: [PATCH 4/6] refactor: parameters --- examples/di/software_company.py | 6 -- metagpt/tools/libs/software_development.py | 96 +++++++++++----------- 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/examples/di/software_company.py b/examples/di/software_company.py index 2a11d7981..ac9999ca9 100644 --- a/examples/di/software_company.py +++ b/examples/di/software_company.py @@ -6,12 +6,6 @@ from metagpt.roles.di.data_interpreter import DataInterpreter async def main(): - # prompt = f"""This is a software requirement:\n```text\nwrite a snake game\n```\n . - # 1. Append user requirement to project path `/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game` and write a PRD.\n - # 2. Write a design to project path `/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game`.\n - # Note: All required dependencies and environments have been fully installed and configured.""" - # prompt = f"""1. fix bug:\n```text\n{issue}\n````\nin project path `/Users/iorishinier/github/bak/MetaGPT/workspace/snake_game`.\n - # Note: All required dependencies and environments have been fully installed and configured.""" prompt = """ This is a software requirement: ```text diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index 0db899f49..59c71d492 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -21,21 +21,21 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat If it's None, a new project path will be created. Defaults to None. Returns: - Path: The path to the project directory + Path: The path to the PRD files under the project directory Example: >>> # Create a new project: >>> from metagpt.tools.libs.software_development import write_prd - >>> project_path = await write_prd("Create a new feature for the application") - >>> print(project_path) - '/path/to/project_path' + >>> prd_path = await write_prd("Create a new feature for the application") + >>> print(prd_path) + '/path/to/project_path/docs/prd/' >>> # Add user requirements to the exists project: >>> from metagpt.tools.libs.software_development import write_prd >>> project_path = '/path/to/exists_project_path' - >>> await write_prd("Create a new feature for the application", project_path=project_path) - >>> print(project_path) - '/path/to/exists_project_path' + >>> prd_path = await write_prd("Create a new feature for the application", project_path=project_path) + >>> print(prd_path = ) + '/path/to/project_path/docs/prd/' """ from metagpt.actions import UserRequirement from metagpt.context import Context @@ -48,25 +48,25 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat role = ProductManager(context=ctx) msg = await role.run(with_message=Message(content=idea, cause_by=UserRequirement)) await role.run(with_message=msg) - return ctx.repo.workdir + return ctx.repo.docs.prd.workdir @register_tool(tags=["software company", "Architect"]) -async def write_design(project_path: str | Path) -> Path: +async def write_design(prd_path: str | Path) -> Path: """Writes a design to the project repository, based on the PRD of the project. Args: - project_path (str|Path): The path to the project repository. + prd_path (str|Path): The path to the PRD files under the project directory. Returns: - Path: The path to the project directory + Path: The path to the system design files under the project directory. Example: >>> from metagpt.tools.libs.software_development import write_design - >>> project_path = '/path/to/project_path' # Returned by `write_prd` - >>> project_path = await write_desgin(project_path) - >>> print(project_path) - '/path/to/project_path' + >>> prd_path = '/path/to/project_path/docs/prd' # Returned by `write_prd` + >>> system_design_path = await write_desgin(prd_path) + >>> print(system_design_path) + '/path/to/project_path/docs/system_design/' """ from metagpt.actions import WritePRD @@ -74,29 +74,30 @@ async def write_design(project_path: str | Path) -> Path: from metagpt.roles import Architect ctx = Context() + project_path = Path(prd_path).parent.parent ctx.set_repo_dir(project_path) role = Architect(context=ctx) await role.run(with_message=Message(content="", cause_by=WritePRD)) - return project_path + return ctx.repo.docs.system_design.workdir @register_tool(tags=["software company", "Architect"]) -async def write_project_plan(project_path: str | Path) -> Path: +async def write_project_plan(system_design_path: str | Path) -> Path: """Writes a project plan to the project repository, based on the design of the project. Args: - project_path (str|Path): The path to the project repository. + system_design_path (str|Path): The path to the system design files under the project directory. Returns: - Path: The path to the project directory + Path: The path to task files under the project directory. Example: >>> from metagpt.tools.libs.software_development import write_project_plan - >>> project_path = '/path/to/project_path' # Returned by `write_prd` - >>> project_path = await write_project_plan(project_path) - >>> print(project_path) - '/path/to/project_path' + >>> system_design_path = '/path/to/project_path/docs/system_design/' # Returned by `write_design` + >>> task_path = await write_project_plan(system_design_path) + >>> print(task_path) + '/path/to/project_path/docs/task' """ from metagpt.actions import WriteDesign @@ -104,38 +105,39 @@ async def write_project_plan(project_path: str | Path) -> Path: from metagpt.roles import ProjectManager ctx = Context() + project_path = Path(system_design_path).parent.parent ctx.set_repo_dir(project_path) role = ProjectManager(context=ctx) await role.run(with_message=Message(content="", cause_by=WriteDesign)) - return project_path + return ctx.repo.docs.task.workdir @register_tool(tags=["software company", "Engineer"]) -async def write_codes(project_path: str | Path, inc: bool = False) -> Path: +async def write_codes(task_path: str | Path, inc: bool = False) -> Path: """Writes codes to the project repository, based on the project plan of the project. Args: - project_path (str|Path): The path to the project repository. + task_path (str|Path): The path to task files under the project directory. inc (bool, optional): Whether to write incremental codes. Defaults to False. Returns: - Path: The path to the project directory + Path: The path to the source code files under the project directory. Example: # Write codes to a new project >>> from metagpt.tools.libs.software_development import write_codes - >>> project_path = '/path/to/project_path' # Returned by `write_prd` - >>> project_path = await write_codes(project_path) - >>> print(project_path) - '/path/to/project_path' + >>> task_path = '/path/to/project_path/docs/task' # Returned by `write_project_plan` + >>> src_path = await write_codes(task_path) + >>> print(src_path) + '/path/to/project_path/src/' # Write increment codes to the exists project >>> from metagpt.tools.libs.software_development import write_codes - >>> project_path = '/path/to/project_path' # Returned by `write_prd` - >>> project_path = await write_codes(project_path, inc=True) - >>> print(project_path) - '/path/to/project_path' + >>> task_path = '/path/to/project_path/docs/task' # Returned by `write_prd` + >>> src_path = await write_codes(task_path, inc=True) + >>> print(src_path) + '/path/to/project_path/src/' """ from metagpt.actions import WriteTasks from metagpt.context import Context @@ -143,6 +145,7 @@ async def write_codes(project_path: str | Path, inc: bool = False) -> Path: ctx = Context() ctx.config.inc = inc + project_path = Path(task_path).parent.parent ctx.set_repo_dir(project_path) role = Engineer(context=ctx) @@ -150,25 +153,25 @@ async def write_codes(project_path: str | Path, inc: bool = False) -> Path: me = {any_to_str(role), role.name} while me.intersection(msg.send_to): msg = await role.run(with_message=msg) - return project_path + return ctx.repo.srcs.workdir @register_tool(tags=["software company", "QaEngineer"]) -async def run_qa_test(project_path: str | Path) -> Path: +async def run_qa_test(src_path: str | Path) -> Path: """Run QA test on the project repository. Args: - project_path (str|Path): The path to the project repository. + src_path (str|Path): The path to the source code files under the project directory. Returns: - Path: The path to the project directory + Path: The path to the unit tests under the project directory Example: >>> from metagpt.tools.libs.software_development import run_qa_test - >>> project_path = '/path/to/project_path' # Returned by `write_codes` - >>> project_path = await run_qa_test(project_path) - >>> print(project_path) - '/path/to/project_path' + >>> src_path = '/path/to/project_path/src/' # Returned by `write_codes` + >>> test_path = await run_qa_test(src_path) + >>> print(test_path) + '/path/to/project_path/tests' """ from metagpt.actions.summarize_code import SummarizeCode from metagpt.context import Context @@ -176,6 +179,7 @@ async def run_qa_test(project_path: str | Path) -> Path: from metagpt.roles import QaEngineer ctx = Context() + project_path = Path(src_path).parent ctx.set_repo_dir(project_path) ctx.src_workspace = ctx.git_repo.workdir / ctx.git_repo.workdir.name @@ -188,7 +192,7 @@ async def run_qa_test(project_path: str | Path) -> Path: while not env.is_idle: await env.run() - return project_path + return ctx.repo.tests.workdir @register_tool(tags=["software company", "Engineer"]) @@ -197,7 +201,7 @@ async def fix_bug(project_path: str | Path, issue: str) -> Path: Args: project_path (str|Path): The path to the project repository. - + issue (str): Description of the bug or issue. Returns: Path: The path to the project directory @@ -205,7 +209,7 @@ async def fix_bug(project_path: str | Path, issue: str) -> Path: Example: >>> from metagpt.tools.libs.software_development import fix_bug >>> project_path = '/path/to/project_path' # Returned by `write_codes` - >>> issue = 'The exception description about the issue' + >>> issue = 'Exception: exception about ...; Bug: bug about ...; Issue: issue about ...' >>> project_path = await fix_bug(project_path=project_path, issue=issue) >>> print(project_path) '/path/to/project_path' From 31d0011a15c73b99f33ab77226d6b160c3abff3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 19:04:41 +0800 Subject: [PATCH 5/6] refactor: comments --- metagpt/tools/libs/software_development.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index 59c71d492..225657fc1 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -17,7 +17,7 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat Args: idea (str): The idea or concept for the PRD. - project_path (Optional[str|Path], optional): The path to an existing project directory created by `write_prd`. + project_path (Optional[str|Path], optional): The path to an existing project directory. If it's None, a new project path will be created. Defaults to None. Returns: From b6da03df3ae41adfa95f7288a23d269ef1924b9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=8E=98=E6=9D=83=20=E9=A9=AC?= Date: Wed, 20 Mar 2024 19:42:50 +0800 Subject: [PATCH 6/6] refactor: comments --- metagpt/tools/libs/software_development.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/metagpt/tools/libs/software_development.py b/metagpt/tools/libs/software_development.py index 225657fc1..41d6fec57 100644 --- a/metagpt/tools/libs/software_development.py +++ b/metagpt/tools/libs/software_development.py @@ -11,7 +11,7 @@ from metagpt.tools.tool_registry import register_tool from metagpt.utils.common import any_to_str -@register_tool(tags=["software company", "ProductManager"]) +@register_tool(tags=["software development", "ProductManager"]) async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Path: """Writes a PRD based on user requirements. @@ -51,7 +51,7 @@ async def write_prd(idea: str, project_path: Optional[str | Path] = None) -> Pat return ctx.repo.docs.prd.workdir -@register_tool(tags=["software company", "Architect"]) +@register_tool(tags=["software development", "Architect"]) async def write_design(prd_path: str | Path) -> Path: """Writes a design to the project repository, based on the PRD of the project. @@ -82,7 +82,7 @@ async def write_design(prd_path: str | Path) -> Path: return ctx.repo.docs.system_design.workdir -@register_tool(tags=["software company", "Architect"]) +@register_tool(tags=["software development", "Architect"]) async def write_project_plan(system_design_path: str | Path) -> Path: """Writes a project plan to the project repository, based on the design of the project. @@ -113,7 +113,7 @@ async def write_project_plan(system_design_path: str | Path) -> Path: return ctx.repo.docs.task.workdir -@register_tool(tags=["software company", "Engineer"]) +@register_tool(tags=["software development", "Engineer"]) async def write_codes(task_path: str | Path, inc: bool = False) -> Path: """Writes codes to the project repository, based on the project plan of the project. @@ -156,7 +156,7 @@ async def write_codes(task_path: str | Path, inc: bool = False) -> Path: return ctx.repo.srcs.workdir -@register_tool(tags=["software company", "QaEngineer"]) +@register_tool(tags=["software development", "QaEngineer"]) async def run_qa_test(src_path: str | Path) -> Path: """Run QA test on the project repository. @@ -195,7 +195,7 @@ async def run_qa_test(src_path: str | Path) -> Path: return ctx.repo.tests.workdir -@register_tool(tags=["software company", "Engineer"]) +@register_tool(tags=["software development", "Engineer"]) async def fix_bug(project_path: str | Path, issue: str) -> Path: """Fix bugs in the project repository. @@ -240,7 +240,7 @@ async def fix_bug(project_path: str | Path, issue: str) -> Path: return project_path -@register_tool(tags=["software company", "git"]) +@register_tool(tags=["software development", "git"]) async def git_archive(project_path: str | Path) -> str: """Stage and commit changes for the project repository using Git.