diff --git a/metagpt/actions/design_api_an.py b/metagpt/actions/design_api_an.py index ee1941350..3890e656a 100644 --- a/metagpt/actions/design_api_an.py +++ b/metagpt/actions/design_api_an.py @@ -18,14 +18,6 @@ IMPLEMENTATION_APPROACH = ActionNode( example="We will ...", ) -INCREMENTAL_IMPLEMENTATION_APPROACH = ActionNode( - key="Incremental Implementation approach", - expected_type=str, - instruction="Analyze the challenging aspects of the requirements and select a suitable open-source framework. " - "Outline the incremental steps involved in the implementation process with the detailed strategies.", - example="we will ...", -) - REFINED_IMPLEMENTATION_APPROACH = ActionNode( key="Refined Implementation Approach", expected_type=str, @@ -63,16 +55,6 @@ DATA_STRUCTURES_AND_INTERFACES = ActionNode( example=MMC1, ) -INCREMENTAL_DATA_STRUCTURES_AND_INTERFACES = ActionNode( - key="Incremental Data structures and interfaces", - expected_type=str, - instruction="Extend the existing mermaid classDiagram code syntax to incorporate new classes, " - "methods (including __init__), and functions with precise type annotations. Clearly delineate additional " - "relationships between classes, maintaining adherence to PEP8 standards. Enhance the level of detail in data " - "structures, ensuring a comprehensive API design that seamlessly integrates with the existing structure.", - example=MMC1_REFINE, -) - REFINED_DATA_STRUCTURES_AND_INTERFACES = ActionNode( key="Refined Data Structures and Interfaces", expected_type=str, @@ -108,32 +90,6 @@ ANYTHING_UNCLEAR = ActionNode( example="Clarification needed on third-party API integration, ...", ) -INC_DESIGN_CONTEXT = """ -## Legacy Content -{old_design} - -## New Requirements -{requirements} - -## PRD Increment Content -{prd_increment} -""" - -MERGE_DESIGN_CONTEXT = """ -Role: You are a professional Architect tasked with overseeing incremental development. -Based on new requirements, review and refine the system design. Integrate existing architecture with incremental design changes, ensuring the refined design encompasses all architectural elements, enhancements, and adjustments. Retain content unrelated to incremental development needs for coherence and clarity. - -# Context -## New Requirements -{requirements} - -## Legacy Content -{old_design} - -## Design Increment Content -{design_increment} -""" - NODES = [ IMPLEMENTATION_APPROACH, # PROJECT_NAME, @@ -143,8 +99,6 @@ NODES = [ ANYTHING_UNCLEAR, ] -INC_NODES = [INCREMENTAL_IMPLEMENTATION_APPROACH, INCREMENTAL_DATA_STRUCTURES_AND_INTERFACES, REFINED_PROGRAM_CALL_FLOW] - REFINE_NODES = [ REFINED_IMPLEMENTATION_APPROACH, REFINED_FILE_LIST, @@ -154,7 +108,6 @@ REFINE_NODES = [ ] DESIGN_API_NODE = ActionNode.from_children("DesignAPI", NODES) -INCREMENTAL_DESIGN_NODES = ActionNode.from_children("Incremental_Design_API", INC_NODES) REFINED_DESIGN_NODES = ActionNode.from_children("Refined_Design_API", REFINE_NODES) diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py index 559c5ef8e..8b0196707 100644 --- a/metagpt/actions/project_management_an.py +++ b/metagpt/actions/project_management_an.py @@ -35,24 +35,12 @@ LOGIC_ANALYSIS = ActionNode( ], ) -INCREMENTAL_LOGIC_ANALYSIS = ActionNode( - key="Incremental Logic Analysis", - expected_type=List[List[str]], - instruction="Provide a list of files with the classes/methods/functions to be implemented or modified " - "incrementally. Include thorough dependency analysis, consider potential impacts on existing code, and document" - " necessary imports.", - example=[ - ["new_feature.py", "Introduces NewFeature class and related functions"], - ["utils.py", "Modifies existing utility functions to support incremental changes"], - ], -) - REFINED_LOGIC_ANALYSIS = ActionNode( key="Refined Logic Analysis", expected_type=List[List[str]], instruction="Review and refine the logic analysis by merging the Legacy Content and Incremental Content. " "Provide a comprehensive list of files with classes/methods/functions to be implemented or modified incrementally. " - "Include thorough dependency analysis, consider potential impacts on existing code, and document necessary imports.", + "Include dependency analysis, consider potential impacts on existing code, and document necessary imports.", example=[ ["game.py", "Contains Game class and ... functions"], ["main.py", "Contains main function, from game import Game"], @@ -68,15 +56,6 @@ TASK_LIST = ActionNode( example=["game.py", "main.py"], ) -INCREMENTAL_TASK_LIST = ActionNode( - key="Incremental Task list", - expected_type=List[str], - instruction="Break down the incremental development tasks into a prioritized list of filenames." - "Organize the tasks based on dependency order, ensuring a systematic and efficient implementation." - "Only output filename! Do not include comments in the list ", - example=["new_feature.py", "main.py"], -) - REFINED_TASK_LIST = ActionNode( key="Refined Task list", expected_type=List[str], @@ -101,14 +80,6 @@ SHARED_KNOWLEDGE = ActionNode( example="`game.py` contains functions shared across the project.", ) -INCREMENTAL_SHARED_KNOWLEDGE = ActionNode( - key="Incremental Shared Knowledge", - expected_type=str, - instruction="Document any new shared knowledge generated during incremental development. This includes common " - "utility functions, configuration variables, or any information vital for team collaboration.", - example="`new_module.py` introduces shared utility functions for improved code reusability.", -) - REFINED_SHARED_KNOWLEDGE = ActionNode( key="Refined Shared Knowledge", expected_type=str, @@ -126,32 +97,6 @@ ANYTHING_UNCLEAR_PM = ActionNode( example="Clarification needed on how to start and initialize third-party libraries.", ) -INC_PM_CONTEXT = """ -### Legacy Content -{old_tasks} - -### New Requirements -{requirements} - -### Design Increment Content -{design_increment} -""" - -MERGE_PM_CONTEXT = """ -Role: You are a professional Project Manager tasked with overseeing incremental development. -Based on New Requirements, refine the project context to account for incremental development. Ensure the context offers a comprehensive overview of the project's evolving scope, covering both legacy content and incremental content. Retain any content unrelated to incremental development. - -# Context -## New Requirements -{requirements} - -## Legacy Content -{old_tasks} - -## Increment Content -{tasks_increment} -""" - NODES = [ REQUIRED_PYTHON_PACKAGES, REQUIRED_OTHER_LANGUAGE_PACKAGES, @@ -162,8 +107,6 @@ NODES = [ ANYTHING_UNCLEAR_PM, ] -INC_NODES = [INCREMENTAL_LOGIC_ANALYSIS, INCREMENTAL_TASK_LIST, INCREMENTAL_SHARED_KNOWLEDGE] - REFINE_NODES = [ REQUIRED_PYTHON_PACKAGES, REQUIRED_OTHER_LANGUAGE_PACKAGES, @@ -175,7 +118,6 @@ REFINE_NODES = [ ] PM_NODE = ActionNode.from_children("PM_NODE", NODES) -INCREMENTAL_PM_NODES = ActionNode.from_children("Incremental_PM_NODES", INC_NODES) REFINED_PM_NODES = ActionNode.from_children("Refined_PM_NODES", REFINE_NODES) diff --git a/metagpt/actions/write_code.py b/metagpt/actions/write_code.py index f92b72f7f..ce0e2fe3b 100644 --- a/metagpt/actions/write_code.py +++ b/metagpt/actions/write_code.py @@ -157,6 +157,24 @@ class WriteCode(Action): @staticmethod async def get_codes(task_doc, exclude, mode="normal") -> str: + """ + Get code snippets based on different modes. + + Attributes: + task_doc (Document): Document object of the task file. + exclude (str): Specifies the filename to be excluded from the code snippets. + mode (str): Specifies the mode, either "normal" or "guide" (default is "normal"). + + Returns: + str: Code snippets. + + Description: + If mode is set to "normal", it returns code snippets for the regular coding phase, + i.e., all the code generated before writing the current file. + + If mode is set to "guide", it returns code snippets for incremental development, + building upon the existing code in the "normal" mode and adding code for the current file's older versions. + """ if not task_doc: return "" if not task_doc.content: @@ -173,6 +191,8 @@ class WriteCode(Action): union_files_list = list(set(src_files) | set(old_files)) for filename in union_files_list: if filename == exclude: + # Exclude unnecessary code to maintain a clean and focused main.py file, ensuring only relevant and + # essential functionality is included for the project’s requirements if filename in old_files and filename != "main.py": # Use legacy code doc = await old_file_repo.get(filename=filename) diff --git a/metagpt/actions/write_code_guideline_an.py b/metagpt/actions/write_code_guideline_an.py index 528b4e8f3..e4afb393d 100644 --- a/metagpt/actions/write_code_guideline_an.py +++ b/metagpt/actions/write_code_guideline_an.py @@ -9,6 +9,7 @@ import asyncio from metagpt.actions.action import Action from metagpt.actions.action_node import ActionNode +from metagpt.logs import logger GUIDELINES_AND_INCREMENTAL_CHANGE = ActionNode( key="Guidelines and Incremental Change", @@ -455,7 +456,7 @@ async def main(): write_code_guideline = WriteCodeGuideline() node = await write_code_guideline.run(CODE_GUIDELINE_CONTEXT_EXAMPLE) guideline = node.instruct_content.model_dump_json() - print(guideline) + logger.info(guideline) if __name__ == "__main__": diff --git a/metagpt/actions/write_prd_an.py b/metagpt/actions/write_prd_an.py index 7004bfffc..4830076e3 100644 --- a/metagpt/actions/write_prd_an.py +++ b/metagpt/actions/write_prd_an.py @@ -130,14 +130,6 @@ REQUIREMENT_ANALYSIS = ActionNode( example="", ) -INCREMENTAL_REQUIREMENT_ANALYSIS = ActionNode( - key="Incremental Requirement Analysis", - expected_type=List[str], - instruction="Propose the comprehensive incremental development requirement analysis on new features and enhanced " - "features for New Requirements.", - example=["Require add/update/modify ..."], -) - REFINED_REQUIREMENT_ANALYSIS = ActionNode( key="Refined Requirement Analysis", expected_type=List[str], @@ -194,22 +186,6 @@ REASON = ActionNode( key="reason", expected_type=str, instruction="Explain the reasoning process from question to answer", example="..." ) - -INCREMENTAL_PRD_CONTEXT = """ -Role: You are a professional Product Manager tasked with overseeing incremental development. -Based on New Requirements, output a New PRD that seamlessly integrates both the Legacy Content and the Incremental Content. Ensure the resulting document captures the complete scope of features, enhancements, and retain content unrelated to incremental development needs for coherence and clarity. - -# Context -## New Requirements -{requirements} - -## Legacy Content -{old_prd} - -## PRD Incremental Content -{prd_increment} -""" - REFINE_PRD_TEMPLATE = """ ### Project Name {project_name} @@ -255,11 +231,8 @@ REFINE_NODES = [ ANYTHING_UNCLEAR, ] -INCREMENT_PRD_NODES = [INCREMENTAL_REQUIREMENT_ANALYSIS, REQUIREMENT_POOL] - WRITE_PRD_NODE = ActionNode.from_children("WritePRD", NODES) REFINE_PRD_NODE = ActionNode.from_children("RefinePRD", REFINE_NODES) -INCREMENTAL_PRD_NODE = ActionNode.from_children("IncrementalPRD", INCREMENT_PRD_NODES) WP_ISSUE_TYPE_NODE = ActionNode.from_children("WP_ISSUE_TYPE", [ISSUE_TYPE, REASON]) WP_IS_RELATIVE_NODE = ActionNode.from_children("WP_IS_RELATIVE", [IS_RELATIVE, REASON]) diff --git a/metagpt/roles/engineer.py b/metagpt/roles/engineer.py index 772cf0944..a6b51bd00 100644 --- a/metagpt/roles/engineer.py +++ b/metagpt/roles/engineer.py @@ -119,18 +119,9 @@ class Engineer(Role): self._init_action_system_message(action) coding_context = await action.run(guideline=guideline) - # Get dependencies + dependencies = {coding_context.design_doc.root_relative_path, coding_context.task_doc.root_relative_path} if guideline: - dependencies = { - coding_context.design_doc.root_relative_path, - coding_context.task_doc.root_relative_path, - "code_guideline.json", - } - else: - dependencies = { - coding_context.design_doc.root_relative_path, - coding_context.task_doc.root_relative_path, - } + dependencies.add("code_guideline.json") await src_file_repo.save( coding_context.filename, dependencies=dependencies, @@ -344,6 +335,7 @@ class Engineer(Role): return self.next_todo_action async def _write_code_guideline(self): + """Write some guidelines that guides subsequent WriteCode and WriteCodeReview""" logger.info("Writing code guideline..") user_requirement = str(self.rc.memory.get_by_role("Human")[0]) diff --git a/data/Gomoku.zip b/tests/data/incremental_dev_project/Gomoku.zip similarity index 100% rename from data/Gomoku.zip rename to tests/data/incremental_dev_project/Gomoku.zip diff --git a/data/dice_simulator_new.zip b/tests/data/incremental_dev_project/dice_simulator_new.zip similarity index 100% rename from data/dice_simulator_new.zip rename to tests/data/incremental_dev_project/dice_simulator_new.zip diff --git a/data/number_guessing_game.zip b/tests/data/incremental_dev_project/number_guessing_game.zip similarity index 100% rename from data/number_guessing_game.zip rename to tests/data/incremental_dev_project/number_guessing_game.zip diff --git a/data/pygame_2048.zip b/tests/data/incremental_dev_project/pygame_2048.zip similarity index 100% rename from data/pygame_2048.zip rename to tests/data/incremental_dev_project/pygame_2048.zip diff --git a/tests/data/incremental_dev_project/readme.md b/tests/data/incremental_dev_project/readme.md new file mode 100644 index 000000000..231589028 --- /dev/null +++ b/tests/data/incremental_dev_project/readme.md @@ -0,0 +1,3 @@ +# Code archive + +This folder contains a compressed package for the test_incremental_dev.py file, which is used to demonstrate the process of incremental development. diff --git a/data/simple_add_calculator.zip b/tests/data/incremental_dev_project/simple_add_calculator.zip similarity index 100% rename from data/simple_add_calculator.zip rename to tests/data/incremental_dev_project/simple_add_calculator.zip diff --git a/data/snake_game.zip b/tests/data/incremental_dev_project/snake_game.zip similarity index 100% rename from data/snake_game.zip rename to tests/data/incremental_dev_project/snake_game.zip diff --git a/data/word_cloud.zip b/tests/data/incremental_dev_project/word_cloud.zip similarity index 100% rename from data/word_cloud.zip rename to tests/data/incremental_dev_project/word_cloud.zip diff --git a/tests/metagpt/actions/test_design_api_an.py b/tests/metagpt/actions/test_design_api_an.py index 5b016ecce..c729c047c 100644 --- a/tests/metagpt/actions/test_design_api_an.py +++ b/tests/metagpt/actions/test_design_api_an.py @@ -101,4 +101,7 @@ def llm(): async def test_write_design_an(): node = await REFINED_DESIGN_NODES.fill(CONTEXT, llm) assert node.instruct_content + assert "Refined Implementation Approach" in node.instruct_content.model_dump_json() + assert "Refined File List" in node.instruct_content.model_dump_json() assert "Refined Data Structures and Interfaces" in node.instruct_content.model_dump_json() + assert "Refined Program call flow" in node.instruct_content.model_dump_json() diff --git a/tests/metagpt/actions/test_project_management_an.py b/tests/metagpt/actions/test_project_management_an.py index e0c1381ec..68d73a3d9 100644 --- a/tests/metagpt/actions/test_project_management_an.py +++ b/tests/metagpt/actions/test_project_management_an.py @@ -141,4 +141,8 @@ def llm(): async def test_project_management_an(llm): node = await REFINED_PM_NODES.fill(CONTEXT, llm) assert node.instruct_content + assert "Required Python Packages" in node.instruct_content.model_dump_json() + assert "Required Other Language Packages" in node.instruct_content.model_dump_json() assert "Refined Logic Analysis" in node.instruct_content.model_dump_json() + assert "Refined Task List" in node.instruct_content.model_dump_json() + assert "Refined Shared Knowledge" in node.instruct_content.model_dump_json() diff --git a/tests/metagpt/actions/test_write_code_guideline_an.py b/tests/metagpt/actions/test_write_code_guideline_an.py index f8a1ae626..f3fba26cc 100644 --- a/tests/metagpt/actions/test_write_code_guideline_an.py +++ b/tests/metagpt/actions/test_write_code_guideline_an.py @@ -185,9 +185,94 @@ if __name__ == "__main__": CalculatorApp.main() ```""" -INCREMENTAL_CHANGE_EXAMPLE = """ +GUIDELINES_AND_INCREMENTAL_CHANGE_EXAMPLE = """ { - "Incremental Change": "- operations.py: Implement the Operations class with a method to perform the requested arithmetic operation. This class will be used by the Calculator class to execute the operations.\n```python\n## operations.py\nclass Operations:\n @staticmethod\n def perform_operation(operation: str, number1: float, number2: float) -> float:\n if operation == 'add':\n return number1 + number2\n elif operation == 'subtract':\n return number1 - number2\n elif operation == 'multiply':\n return number1 * number2\n elif operation == 'divide':\n if number2 == 0:\n raise ValueError('Cannot divide by zero')\n return number1 / number2\n else:\n raise ValueError('Invalid operation')\n```\n\n- calculator.py: Extend the Calculator class to include methods for subtraction, multiplication, and division. These methods will utilize the Operations class to perform the actual calculations.\n```python\n## calculator.py\nfrom operations import Operations\nclass Calculator:\n ...\n def subtract(self, number1: float, number2: float) -> float:\n return Operations.perform_operation('subtract', number1, number2)\n\n def multiply(self, number1: float, number2: float) -> float:\n return Operations.perform_operation('multiply', number1, number2)\n\n def divide(self, number1: float, number2: float) -> float:\n return Operations.perform_operation('divide', number1, number2)\n```\n\n- interface.py: Update the Interface class to include buttons for subtraction, multiplication, and division, and link them to the corresponding methods in the Calculator class. Also, handle the display of errors such as division by zero.\n```python\n## interface.py\nimport tkinter as tk\nfrom tkinter import messagebox\nfrom calculator import Calculator\n...\nclass Interface:\n ...\n def create_widgets(self):\n ...\n self.subtract_button = tk.Button(self.root, text='-', command=self.subtract, font=('Arial', 18))\n self.subtract_button.grid(row=3, column=0, sticky='nsew')\n\n self.multiply_button = tk.Button(self.root, text='*', command=self.multiply, font=('Arial', 18))\n self.multiply_button.grid(row=3, column=1, sticky='nsew')\n\n self.divide_button = tk.Button(self.root, text='/', command=self.divide, font=('Arial', 18))\n self.divide_button.grid(row=3, column=2, sticky='nsew')\n ...\n\n def subtract(self):\n number1, number2 = self.get_input()\n if number1 is not None and number2 is not None:\n result = self.calculator.subtract(number1, number2)\n self.display_result(result)\n\n def multiply(self):\n number1, number2 = self.get_input()\n if number1 is not None and number2 is not None:\n result = self.calculator.multiply(number1, number2)\n self.display_result(result)\n\n def divide(self):\n number1, number2 = self.get_input()\n if number1 is not None and number2 is not None:\n try:\n result = self.calculator.divide(number1, number2)\n except ValueError as e:\n self.show_error(str(e))\n return\n self.display_result(result)\n```\n\n- main.py: No changes needed in main.py as it serves as the entry point and will run the updated Interface class.\n```python\n## main.py\nfrom interface import Interface\n...\n```\n\nNote: Ensure that the new operations buttons in the Interface class are properly arranged and that the grid layout is adjusted accordingly. Also, make sure to import the messagebox module from tkinter for error handling." + "Guidelines and Incremental Change": " +1. Guideline for calculator.py: Enhance the functionality of `calculator.py` by extending it to incorporate methods for subtraction, multiplication, and division. Additionally, implement robust error handling for the division operation to mitigate potential issues related to division by zero. +```python +class Calculator: + self.result = number1 + number2 + return self.result + +- def sub(self, number1, number2) -> float: ++ def subtract(self, number1: float, number2: float) -> float: ++ ''' ++ Subtracts the second number from the first and returns the result. ++ ++ Args: ++ number1 (float): The number to be subtracted from. ++ number2 (float): The number to subtract. ++ ++ Returns: ++ float: The difference of number1 and number2. ++ ''' ++ self.result = number1 - number2 ++ return self.result ++ + def multiply(self, number1: float, number2: float) -> float: +- pass ++ ''' ++ Multiplies two numbers and returns the result. ++ ++ Args: ++ number1 (float): The first number to multiply. ++ number2 (float): The second number to multiply. ++ ++ Returns: ++ float: The product of number1 and number2. ++ ''' ++ self.result = number1 * number2 ++ return self.result ++ + def divide(self, number1: float, number2: float) -> float: +- pass ++ ''' ++ ValueError: If the second number is zero. ++ ''' ++ if number2 == 0: ++ raise ValueError('Cannot divide by zero') ++ self.result = number1 / number2 ++ return self.result ++ +- def reset_result(self): ++ def clear(self): ++ if self.result != 0.0: ++ print("Result is not zero, clearing...") ++ else: ++ print("Result is already zero, no need to clear.") ++ + self.result = 0.0 +``` + +2. Guideline for main.py: Integrate new API endpoints for subtraction, multiplication, and division into the existing codebase of `main.py`. Then, ensure seamless integration with the overall application architecture and maintain consistency with coding standards. +```python +def add_numbers(): + result = calculator.add_numbers(num1, num2) + return jsonify({'result': result}), 200 + +-# TODO: Implement subtraction, multiplication, and division operations ++@app.route('/subtract_numbers', methods=['POST']) ++def subtract_numbers(): ++ data = request.get_json() ++ num1 = data.get('num1', 0) ++ num2 = data.get('num2', 0) ++ result = calculator.subtract_numbers(num1, num2) ++ return jsonify({'result': result}), 200 ++ ++@app.route('/multiply_numbers', methods=['POST']) ++def multiply_numbers(): ++ data = request.get_json() ++ num1 = data.get('num1', 0) ++ num2 = data.get('num2', 0) ++ try: ++ result = calculator.divide_numbers(num1, num2) ++ except ValueError as e: ++ return jsonify({'error': str(e)}), 400 ++ return jsonify({'result': result}), 200 ++ + if __name__ == '__main__': + app.run() +```" } """ @@ -207,7 +292,7 @@ async def test_write_code_guideline_an(): async def test_refine_code(): prompt = REFINED_CODE_TEMPLATE.format( requirement=REQUIREMENT_EXAMPLE, - guideline=INCREMENTAL_CHANGE_EXAMPLE, + guideline=GUIDELINES_AND_INCREMENTAL_CHANGE_EXAMPLE, design=DESIGN_EXAMPLE, tasks=TASKS_EXAMPLE, code=REFINE_CODE_SCRIPT_EXAMPLE, diff --git a/tests/metagpt/actions/test_write_prd_an.py b/tests/metagpt/actions/test_write_prd_an.py index 79cd913cd..693f4924d 100644 --- a/tests/metagpt/actions/test_write_prd_an.py +++ b/tests/metagpt/actions/test_write_prd_an.py @@ -85,4 +85,8 @@ def llm(): async def test_write_prd_an(llm): node = await REFINE_PRD_NODE.fill(CONTEXT, llm) assert node.instruct_content + assert "Refined Requirements" in node.instruct_content.model_dump_json() + assert "Refined Product Goals" in node.instruct_content.model_dump_json() + assert "Refined User Stories" in node.instruct_content.model_dump_json() + assert "Refined Requirement Analysis" in node.instruct_content.model_dump_json() assert "Refined Requirement Pool" in node.instruct_content.model_dump_json() diff --git a/tests/metagpt/test_incremental_dev.py b/tests/metagpt/test_incremental_dev.py index 44478adc9..8890137b0 100644 --- a/tests/metagpt/test_incremental_dev.py +++ b/tests/metagpt/test_incremental_dev.py @@ -11,24 +11,73 @@ import subprocess import pytest from typer.testing import CliRunner -from metagpt.const import DATA_PATH +from metagpt.const import TEST_DATA_PATH from metagpt.logs import logger from metagpt.startup import app runner = CliRunner() +IDEAS = [ + "Add subtraction, multiplication and division operations to the calculator. The current calculator can only perform basic addition operations, and it is necessary to introduce subtraction, multiplication, division operation into the calculator", + "Add a feature to remove deprecated words from the word cloud. The current word cloud generator does not support removing deprecated words. Now, The word cloud generator should support removing deprecated words. Customize deactivated words to exclude them from word cloud. Let users see all the words in the text file, and allow users to select the words they want to remove.", + "Add an AI opponent with fixed difficulty levels. Currently, the game only allows players to compete against themselves. Implement an AI algorithm that can playing with player. This will provide a more engaging and challenging experience for players.", + "Add functionality to view the history of scores. The original dice rolling game could only display the current game result, but the new requirement allows players to view the history of scores", + "Add functionality to view the history of scores and perform statistical analysis on them. The original dice rolling game could only display the current game result, but the new requirement allows players to view the history of scores and display the statistical analysis results of the current score", + "Changed score target for 2048 game from 2048 to 4096. Please change the game's score target from 2048 to 4096, and change the interface size from 4*4 to 8*8", + "Display the history score of the player in the 2048 game. Add a record board that can display players' historical score records so that players can trace their scores", + "Add limited time mode. The original game only had a default classic mode. The improved game should be able to support limited-time mode, allowing users to choose classic mode or limited-time mode from the available options before starting the game.", + "Incremental Idea Gradually increase the speed of the snake as the game progresses. In the current version of the game, the snake’s speed remains constant throughout the gameplay. Implement a feature where the snake’s speed gradually increases over time, making the game more challenging and intense as the player progresses.", + "Introduce power-ups and obstacles to the game. The current version of the game only involves eating food and growing the snake. Add new elements such as power-ups that can enhance the snake’s speed or make it invincible for a short duration. At the same time, introduce obstacles like walls or enemies that the snake must avoid or overcome to continue growing.", +] -def test_refined_simple_calculator(): - project_path = f"{DATA_PATH}/simple_add_calculator" - check_or_create_base_tag(project_path) +PROJECT_NAMES = [ + "calculator", + "word_cloud", + "Gomoku", + "dice_simulator_new", + "dice_simulator_new", + "pygame_2048", + "pygame_2048", + "pygame_2048", + "snake_game", + "snake_game", +] - args = [ - "Add subtraction, multiplication and division operations to the calculator. The current calculator can only perform basic addition operations, and it is necessary to introduce subtraction, multiplication, division operation into the calculator", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) + +def test_refined_calculator(): + result = get_incremental_dev_result(IDEAS[0], PROJECT_NAMES[0]) + log_and_check_result(result) + + +def test_refined_word_cloud(): + result = get_incremental_dev_result(IDEAS[1], PROJECT_NAMES[1]) + log_and_check_result(result) + + +def test_refined_gomoku(): + result = get_incremental_dev_result(IDEAS[2], PROJECT_NAMES[2]) + log_and_check_result(result) + + +def test_refined_dice_simulator_new(): + for idea, project_name in zip(IDEAS[3:5], PROJECT_NAMES[3:5]): + result = get_incremental_dev_result(idea, project_name) + log_and_check_result(result) + + +def test_refined_pygame_2048(): + for idea, project_name in zip(IDEAS[5:8], PROJECT_NAMES[5:8]): + result = get_incremental_dev_result(idea, project_name) + log_and_check_result(result) + + +def test_refined_snake_game(): + for idea, project_name in zip(IDEAS[8:10], PROJECT_NAMES[8:10]): + result = get_incremental_dev_result(idea, project_name) + log_and_check_result(result) + + +def log_and_check_result(result): logger.info(result) logger.info(result.output) if "Aborting" in result.output: @@ -46,274 +95,19 @@ def test_refined_simple_calculator(): raise e -def test_refined_number_guessing_game(): - project_path = f"{DATA_PATH}/number_guessing_game" +def get_incremental_dev_result(idea, project_name): + project_path = TEST_DATA_PATH / "incremental_dev_project" / project_name + if not os.path.exists(project_path): + raise Exception(f"Project {project_name} not exists") check_or_create_base_tag(project_path) - args = [ - "Adding graphical interface functionality to enhance the user experience in the number-guessing game. The existing number-guessing game currently relies on command-line input for numbers. The goal is to introduce a graphical interface to improve the game's usability and visual appeal", + idea, "--inc", "--project-path", project_path, ] result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_word_cloud(): - project_path = f"{DATA_PATH}/word_cloud" - check_or_create_base_tag(project_path) - - args = [ - "Add a feature to remove deprecated words from the word cloud. The current word cloud generator does not support removing deprecated words. Now, The word cloud generator should support removing deprecated words. Customize deactivated words to exclude them from word cloud. Let users see all the words in the text file, and allow users to select the words they want to remove.", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_gomoku(): - project_path = f"{DATA_PATH}/Gomoku" - check_or_create_base_tag(project_path) - - args = [ - "Add an AI opponent with fixed difficulty levels. Currently, the game only allows players to compete against themselves. Implement an AI algorithm that can playing with player. This will provide a more engaging and challenging experience for players.", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_dice_simulator_1(): - project_path = f"{DATA_PATH}/dice_simulator_new" - check_or_create_base_tag(project_path) - - args = [ - "Add functionality to view the history of scores. The original dice rolling game could only display the current game result, but the new requirement allows players to view the history of scores", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_1"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_dice_simulator_2(): - project_path = f"{DATA_PATH}/dice_simulator_new" - check_or_create_base_tag(project_path) - - args = [ - "Add functionality to view the history of scores and perform statistical analysis on them. The original dice rolling game could only display the current game result, but the new requirement allows players to view the history of scores and display the statistical analysis results of the current score", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_2"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_pygame_2048_1(): - project_path = f"{DATA_PATH}/pygame_2048" - check_or_create_base_tag(project_path) - - args = [ - "Changed score target for 2048 game from 2048 to 4096. Please change the game's score target from 2048 to 4096, and change the interface size from 4*4 to 8*8", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_1"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_pygame_2048_2(): - project_path = f"{DATA_PATH}/pygame_2048" - check_or_create_base_tag(project_path) - - args = [ - "Display the history score of the player in the 2048 game. Add a record board that can display players' historical score records so that players can trace their scores", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_2"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_pygame_2048_3(): - project_path = f"{DATA_PATH}/pygame_2048" - check_or_create_base_tag(project_path) - - args = [ - "Add limited time mode. The original game only had a default classic mode. The improved game should be able to support limited-time mode, allowing users to choose classic mode or limited-time mode from the available options before starting the game.", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_3"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_snake_game_1(): - project_path = f"{DATA_PATH}/snake_game" - check_or_create_base_tag(project_path) - - args = [ - "Incremental Idea Gradually increase the speed of the snake as the game progresses. In the current version of the game, the snake’s speed remains constant throughout the gameplay. Implement a feature where the snake’s speed gradually increases over time, making the game more challenging and intense as the player progresses.", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_1"], check=True) - except subprocess.CalledProcessError as e: - raise e - - -def test_refined_snake_game_2(): - project_path = f"{DATA_PATH}/snake_game" - check_or_create_base_tag(project_path) - - args = [ - "Introduce power-ups and obstacles to the game. The current version of the game only involves eating food and growing the snake. Add new elements such as power-ups that can enhance the snake’s speed or make it invincible for a short duration. At the same time, introduce obstacles like walls or enemies that the snake must avoid or overcome to continue growing.", - "--inc", - "--project-path", - project_path, - ] - result = runner.invoke(app, args) - logger.info(result) - logger.info(result.output) - if "Aborting" in result.output: - assert False - else: - tag = subprocess.run(["git", "describe", "--tags"], capture_output=True, text=True).stdout.strip() - if tag == "base": - assert False - else: - assert True - try: - subprocess.run(["git", "tag", "refine_2"], check=True) - except subprocess.CalledProcessError as e: - raise e + return result def check_or_create_base_tag(project_path):