feat(beads): integrate Beads Mode backend, MCP tools, and GUI support

This commit is contained in:
2026-05-06 13:48:47 -04:00
parent b1ddaa50f4
commit 2b66f3569b
17 changed files with 525 additions and 77 deletions
+36 -5
View File
@@ -19,6 +19,7 @@ from pathlib import Path, PureWindowsPath
from typing import Any, cast
from src import summarize
from src import project_manager
from src import beads_client
from src.file_cache import ASTParser
def find_next_increment(output_dir: Path, namespace: str) -> int:
@@ -197,7 +198,28 @@ def _build_files_section_from_items(file_items: list[dict[str, Any]]) -> str:
sections.append(f"### `{original}`\n\n```{lang}\n{content}\n```")
return "\n\n---\n\n".join(sections)
def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str:
def build_beads_section(base_dir: Path) -> str:
client = beads_client.BeadsClient(base_dir)
if not client.is_initialized():
return ""
beads = client.list_beads()
if not beads:
return ""
active = [b for b in beads if b.status == "active"]
completed = [b for b in beads if b.status == "completed"]
parts = []
parts.append("## Beads Mode: Progress Track")
if completed:
parts.append("### Completed Beads")
comp_list = ", ".join([f"`{b.title}`" for b in completed])
parts.append(comp_list)
if active:
parts.append("### Active Beads")
for b in active:
parts.append(f"- **{b.title}** ({b.id}): {b.description}")
return "\n\n".join(parts)
def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, aggregation_strategy: str = "auto", execution_mode: str = "standard", base_dir: Path | None = None) -> str:
"""Build markdown from pre-read file items instead of re-reading from disk."""
parts = []
# STATIC PREFIX: Files and Screenshots must go first to maximize Cache Hits
@@ -213,7 +235,11 @@ def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_
parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
if screenshots:
parts.append("## Screenshots\n\n" + build_screenshots_section(screenshot_base_dir, screenshots))
# DYNAMIC SUFFIX: History changes every turn, must go last
if execution_mode == "beads" and base_dir:
beads_md = build_beads_section(base_dir)
if beads_md:
parts.append(beads_md)
# DYNAMIC SUFFIX: History changes every turn, must go last
if history:
parts.append("## Discussion History\n\n" + build_discussion_section(history))
return "\n\n---\n\n".join(parts)
@@ -309,7 +335,7 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
parts.append("## Discussion History\n\n" + build_discussion_section(history))
return "\n\n---\n\n".join(parts)
def build_markdown(base_dir: Path, files: list[str | dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False) -> str:
def build_markdown(base_dir: Path, files: list[str | dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], history: list[str], summary_only: bool = False, execution_mode: str = "standard") -> str:
parts = []
# STATIC PREFIX: Files and Screenshots must go first to maximize Cache Hits
if files:
@@ -319,7 +345,11 @@ def build_markdown(base_dir: Path, files: list[str | dict[str, Any]], screenshot
parts.append("## Files\n\n" + build_files_section(base_dir, files))
if screenshots:
parts.append("## Screenshots\n\n" + build_screenshots_section(screenshot_base_dir, screenshots))
# DYNAMIC SUFFIX: History changes every turn, must go last
if execution_mode == "beads":
beads_md = build_beads_section(base_dir)
if beads_md:
parts.append(beads_md)
# DYNAMIC SUFFIX: History changes every turn, must go last
if history:
parts.append("## Discussion History\n\n" + build_discussion_section(history))
return "\n\n---\n\n".join(parts)
@@ -340,8 +370,9 @@ def run(config: dict[str, Any], aggregation_strategy: str = "auto") -> tuple[str
# Build file items once, then construct markdown from them (avoids double I/O)
file_items = build_file_items(base_dir, files)
summary_only = config.get("project", {}).get("summary_only", False)
execution_mode = config.get("project", {}).get("execution_mode", "standard")
markdown = build_markdown_from_items(file_items, screenshot_base_dir, screenshots, history,
summary_only=summary_only, aggregation_strategy=aggregation_strategy)
summary_only=summary_only, aggregation_strategy=aggregation_strategy, execution_mode=execution_mode, base_dir=base_dir)
output_file.write_text(markdown, encoding="utf-8")
return markdown, output_file, file_items