From 580d89b28168ae79a364f1e6e5395ac77255ba83 Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 9 Jan 2026 14:22:13 +0000 Subject: [PATCH] Implement MVP milestone tracking with multi-iteration task loop and roadmap utilities. Update Web UI to display MVP status. --- ROADMAP.md | 2 +- src/autometabuilder/main.py | 75 ++++++++++++-------- src/autometabuilder/roadmap_utils.py | 40 +++++++++++ src/autometabuilder/web/server.py | 8 ++- src/autometabuilder/web/templates/index.html | 13 +++- 5 files changed, 106 insertions(+), 32 deletions(-) create mode 100644 src/autometabuilder/roadmap_utils.py diff --git a/ROADMAP.md b/ROADMAP.md index 5738239..141604d 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -12,7 +12,7 @@ - [x] **Declarative Task Processing**: Move more logic into JSON/YAML specifications. - [x] **Feedback Loop**: Support for the AI to read comments on PRs it created. -## Phase 3: Advanced Automation +## Phase 3: Advanced Automation (MVP) - [x] **Automated Testing**: Integration with test runners to verify changes before PR. - [x] **Linting Integration**: Automatically run and fix linting issues. - [x] **Multi-Model Support**: Easily switch between different LLM providers. diff --git a/src/autometabuilder/main.py b/src/autometabuilder/main.py index 36a5da3..c89ab66 100644 --- a/src/autometabuilder/main.py +++ b/src/autometabuilder/main.py @@ -17,6 +17,7 @@ from .github_integration import GitHubIntegration, get_repo_name_from_env from .docker_utils import run_command_in_docker from .web.server import start_web_ui from .integrations.notifications import notify_all +from .roadmap_utils import update_roadmap, is_mvp_reached load_dotenv() @@ -77,11 +78,6 @@ def get_sdlc_context(gh: GitHubIntegration, msgs: dict) -> str: return sdlc_context -def update_roadmap(content: str): - """Update ROADMAP.md with new content.""" - with open("ROADMAP.md", "w", encoding="utf-8") as f: - f.write(content) - logger.info("ROADMAP.md updated successfully.") def list_files(directory: str = "."): @@ -349,34 +345,55 @@ def main(): messages.append({"role": "user", "content": msgs["user_next_step"]}) model_name = os.environ.get("LLM_MODEL", prompt.get("model", DEFAULT_MODEL)) - response = get_completion(client, model_name, messages, tools) - resp_msg = response.choices[0].message - logger.info( - resp_msg.content - if resp_msg.content - else msgs["info_tool_call_requested"] - ) - - # Handle tool calls - tool_results = handle_tool_calls(resp_msg, tool_map, gh, msgs, dry_run=args.dry_run, yolo=args.yolo) - - if args.once and tool_results: - logger.info(msgs.get("info_second_pass", "Performing second pass with tool results...")) + # Multi-iteration loop + iteration = 0 + max_iterations = 10 + + while iteration < max_iterations: + iteration += 1 + logger.info(f"--- Iteration {iteration} ---") + + response = get_completion(client, model_name, messages, tools) + resp_msg = response.choices[0].message + + logger.info( + resp_msg.content + if resp_msg.content + else msgs["info_tool_call_requested"] + ) + messages.append(resp_msg) + + if not resp_msg.tool_calls: + # If no more tools requested, we are done + notify_all(f"AutoMetabuilder task complete: {resp_msg.content[:100]}...") + break + + # Handle tool calls + tool_results = handle_tool_calls(resp_msg, tool_map, gh, msgs, dry_run=args.dry_run, yolo=args.yolo) messages.extend(tool_results) - response = get_completion(client, model_name, messages, tools) - final_msg = response.choices[0].message - logger.info(final_msg.content if final_msg.content else msgs["info_tool_call_requested"]) - - # Notify about task completion - notify_all(f"AutoMetabuilder task complete: {final_msg.content[:100]}...") - - # In a multi-iteration loop, we would call handle_tool_calls again here. - # For --once, we just do one more pass. - if final_msg.tool_calls: - handle_tool_calls(final_msg, tool_map, gh, msgs, dry_run=args.dry_run, yolo=args.yolo) + if args.yolo and is_mvp_reached(): + logger.info("MVP reached. Stopping YOLO loop.") + notify_all("AutoMetabuilder YOLO loop stopped: MVP reached.") + break + + if args.once: + # If --once is set, we do one more pass to show the final result + logger.info(msgs.get("info_second_pass", "Performing second pass with tool results...")) + response = get_completion(client, model_name, messages, tools) + final_msg = response.choices[0].message + logger.info(final_msg.content if final_msg.content else msgs["info_tool_call_requested"]) + notify_all(f"AutoMetabuilder task complete: {final_msg.content[:100]}...") + + # For --once, we still handle tool calls if any in the second pass, but then stop. + if final_msg.tool_calls: + handle_tool_calls(final_msg, tool_map, gh, msgs, dry_run=args.dry_run, yolo=args.yolo) + break + else: + logger.warning(f"Reached maximum iterations ({max_iterations}). Stopping.") + notify_all(f"AutoMetabuilder stopped: Reached {max_iterations} iterations.") if __name__ == "__main__": diff --git a/src/autometabuilder/roadmap_utils.py b/src/autometabuilder/roadmap_utils.py new file mode 100644 index 0000000..328afae --- /dev/null +++ b/src/autometabuilder/roadmap_utils.py @@ -0,0 +1,40 @@ +import os +import re +import logging + +logger = logging.getLogger("autometabuilder") + +def update_roadmap(content: str): + """Update ROADMAP.md with new content.""" + with open("ROADMAP.md", "w", encoding="utf-8") as f: + f.write(content) + logger.info("ROADMAP.md updated successfully.") + + +def is_mvp_reached() -> bool: + """Check if the MVP section in ROADMAP.md is completed.""" + if not os.path.exists("ROADMAP.md"): + return False + + with open("ROADMAP.md", "r", encoding="utf-8") as f: + content = f.read() + + # Find the MVP section + mvp_match = re.search(r"## .*?\(MVP\)(.*?)##", content, re.DOTALL | re.IGNORECASE) + if not mvp_match: + # Try finding it if it's the last section + mvp_match = re.search(r"## .*?\(MVP\)(.*)", content, re.DOTALL | re.IGNORECASE) + + if not mvp_match: + return False + + mvp_section = mvp_match.group(1) + # Check if there are any unchecked items [ ] + if "[ ]" in mvp_section: + return False + + # If there are checked items [x], and no unchecked items, we consider it reached + if "[x]" in mvp_section: + return True + + return False diff --git a/src/autometabuilder/web/server.py b/src/autometabuilder/web/server.py index 8a4994e..a7de809 100644 --- a/src/autometabuilder/web/server.py +++ b/src/autometabuilder/web/server.py @@ -8,6 +8,7 @@ from fastapi.security import HTTPBasic, HTTPBasicCredentials from dotenv import load_dotenv, set_key import subprocess import sys +from ..roadmap_utils import is_mvp_reached app = FastAPI() security = HTTPBasic() @@ -92,6 +93,7 @@ async def read_item(request: Request, username: str = Depends(get_current_user)) translations = get_translations() prompt_content = get_prompt_content() is_running = bot_process is not None + mvp_status = is_mvp_reached() return templates.TemplateResponse("index.html", { "request": request, "logs": logs, @@ -99,6 +101,7 @@ async def read_item(request: Request, username: str = Depends(get_current_user)) "translations": translations, "prompt_content": prompt_content, "is_running": is_running, + "mvp_reached": mvp_status, "username": username }) @@ -135,7 +138,10 @@ async def update_settings(request: Request, username: str = Depends(get_current_ @app.get("/api/status") async def get_status(username: str = Depends(get_current_user)): - return {"is_running": bot_process is not None} + return { + "is_running": bot_process is not None, + "mvp_reached": is_mvp_reached() + } @app.get("/api/logs") async def get_logs(username: str = Depends(get_current_user)): diff --git a/src/autometabuilder/web/templates/index.html b/src/autometabuilder/web/templates/index.html index abd04e2..ec04ed9 100644 --- a/src/autometabuilder/web/templates/index.html +++ b/src/autometabuilder/web/templates/index.html @@ -25,6 +25,13 @@ Idle {% endif %}

+

MVP Milestone: + {% if mvp_reached %} + Reached ✓ + {% else %} + In Progress + {% endif %} +

@@ -111,13 +118,17 @@ if (data.is_running) { statusContainer.innerHTML = `

Status: Bot Running...

+

MVP Milestone: ${data.mvp_reached ? 'Reached ✓' : 'In Progress'}

`; runBtn.disabled = true; } else { - statusContainer.innerHTML = `

Status: Idle

`; + statusContainer.innerHTML = ` +

Status: Idle

+

MVP Milestone: ${data.mvp_reached ? 'Reached ✓' : 'In Progress'}

+ `; runBtn.disabled = false; } } catch (error) {