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 %}
+