organizing (mostly aggregate.py)

This commit is contained in:
ed
2026-06-05 22:34:26 -04:00
parent 8b83c5d0b7
commit aa56981c87
5 changed files with 148 additions and 186 deletions
+53 -75
View File
@@ -12,19 +12,24 @@ Instead of sending every file to the AI raw (which blows up tokens), this uses a
This is essential for keeping prompt tokens low while giving the AI enough structural info This is essential for keeping prompt tokens low while giving the AI enough structural info
to use the MCP tools to fetch only what it needs. to use the MCP tools to fetch only what it needs.
""" """
import ast
import glob import glob
import os import os
import re import re
import tomllib import tomllib
import traceback
from pathlib import Path, PureWindowsPath from pathlib import Path, PureWindowsPath
from typing import Any, cast from typing import Any, cast
from src import beads_client from src import beads_client
from src import mcp_client
from src import project_manager from src import project_manager
from src import summarize from src import summarize
from src.fuzzy_anchor import FuzzyAnchor
from src.file_cache import ASTParser from src.file_cache import ASTParser
from src.paths import get_config_path
from src.performance_monitor import get_monitor from src.performance_monitor import get_monitor
@@ -59,8 +64,7 @@ def resolve_paths(base_dir: Path, entry: str) -> list[Path]:
filtered = [] filtered = []
for p in matches: for p in matches:
name = p.name.lower() name = p.name.lower()
if name == "history.toml" or name.endswith("_history.toml"): if name == "history.toml" or name.endswith("_history.toml"): continue
continue
filtered.append(p) filtered.append(p)
return sorted(filtered) return sorted(filtered)
@@ -93,7 +97,6 @@ def compute_file_stats(abs_path: str) -> dict[str, int]:
content = f.read() content = f.read()
stats["lines"] = len(content.splitlines()) stats["lines"] = len(content.splitlines())
if abs_path.endswith('.py'): if abs_path.endswith('.py'):
import ast
try: try:
tree = ast.parse(content) tree = ast.parse(content)
stats["ast_elements"] = sum(1 for node in ast.walk(tree) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef))) stats["ast_elements"] = sum(1 for node in ast.walk(tree) if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)))
@@ -116,14 +119,14 @@ def build_discussion_section(history: list[Any]) -> str:
text = f"{role}: {content}" text = f"{role}: {content}"
else: else:
text = str(entry).strip() text = str(entry).strip()
sections.append(f"### Discussion Excerpt {i}\n\n{text}") sections.append(f"### Discussion Excerpt {i}\n\n{text}")
return "\n\n---\n\n".join(sections) return "\n\n---\n\n".join(sections)
def build_screenshots_section(base_dir: Path, screenshots: list[str]) -> str: def build_screenshots_section(base_dir: Path, screenshots: list[str]) -> str:
sections = [] sections = []
for entry in screenshots: for entry in screenshots:
if not entry or not isinstance(entry, str): if not entry or not isinstance(entry, str): continue
continue
paths = resolve_paths(base_dir, entry) paths = resolve_paths(base_dir, entry)
if not paths: if not paths:
sections.append(f"### `{entry}`\n\n_ERROR: no files matched: {entry}_") sections.append(f"### `{entry}`\n\n_ERROR: no files matched: {entry}_")
@@ -163,8 +166,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
auto_aggregate = entry_raw.get("auto_aggregate", True) auto_aggregate = entry_raw.get("auto_aggregate", True)
force_full = entry_raw.get("force_full", False) force_full = entry_raw.get("force_full", False)
view_mode = entry_raw.get("view_mode", "full") view_mode = entry_raw.get("view_mode", "full")
if force_full: if force_full: view_mode = "full"
view_mode = "full"
ast_signatures = entry_raw.get("ast_signatures", False) ast_signatures = entry_raw.get("ast_signatures", False)
ast_definitions = entry_raw.get("ast_definitions", False) ast_definitions = entry_raw.get("ast_definitions", False)
ast_mask = entry_raw.get("ast_mask", {}) ast_mask = entry_raw.get("ast_mask", {})
@@ -175,8 +177,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
auto_aggregate = getattr(entry_raw, "auto_aggregate", True) auto_aggregate = getattr(entry_raw, "auto_aggregate", True)
force_full = getattr(entry_raw, "force_full", False) force_full = getattr(entry_raw, "force_full", False)
view_mode = getattr(entry_raw, "view_mode", "full") view_mode = getattr(entry_raw, "view_mode", "full")
if force_full: if force_full: view_mode = "full"
view_mode = "full"
ast_signatures = getattr(entry_raw, "ast_signatures", False) ast_signatures = getattr(entry_raw, "ast_signatures", False)
ast_definitions = getattr(entry_raw, "ast_definitions", False) ast_definitions = getattr(entry_raw, "ast_definitions", False)
ast_mask = getattr(entry_raw, "ast_mask", {}) ast_mask = getattr(entry_raw, "ast_mask", {})
@@ -191,12 +192,14 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
ast_definitions = False ast_definitions = False
ast_mask = {} ast_mask = {}
custom_slices = [] custom_slices = []
if not entry or not isinstance(entry, str):
continue if not entry or not isinstance(entry, str): continue
paths = resolve_paths(base_dir, entry) paths = resolve_paths(base_dir, entry)
if not paths: if not paths:
items.append({"path": None, "entry": entry, "content": f"ERROR: no files matched: {entry}", "error": True, "mtime": 0.0, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices}) items.append({"path": None, "entry": entry, "content": f"ERROR: no files matched: {entry}", "error": True, "mtime": 0.0, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices})
continue continue
for path in paths: for path in paths:
try: try:
content = path.read_text(encoding="utf-8") content = path.read_text(encoding="utf-8")
@@ -204,15 +207,13 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
error = False error = False
if not error and view_mode != "full": if not error and view_mode != "full":
try: try:
if view_mode == "summary": if view_mode == "summary": content = summarize.summarise_file(path, content)
content = summarize.summarise_file(path, content)
elif view_mode == "skeleton": elif view_mode == "skeleton":
suffix_lower = path.suffix.lower() suffix_lower = path.suffix.lower()
if suffix_lower == ".py": if suffix_lower == ".py":
if not parser: parser = ASTParser("python") if not parser: parser = ASTParser("python")
content = parser.get_skeleton(content, path=str(path)) content = parser.get_skeleton(content, path=str(path))
elif suffix_lower in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']: elif suffix_lower in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
from src import mcp_client
if suffix_lower in ['.c', '.h']: content = mcp_client.ts_c_get_skeleton(str(path)) if suffix_lower in ['.c', '.h']: content = mcp_client.ts_c_get_skeleton(str(path))
else: content = mcp_client.ts_cpp_get_skeleton(str(path)) else: content = mcp_client.ts_cpp_get_skeleton(str(path))
else: else:
@@ -223,7 +224,6 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
if not parser: parser = ASTParser("python") if not parser: parser = ASTParser("python")
content = parser.get_code_outline(content, path=str(path)) content = parser.get_code_outline(content, path=str(path))
elif suffix_lower in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']: elif suffix_lower in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc']:
from src import mcp_client
if suffix_lower in ['.c', '.h']: content = mcp_client.ts_c_get_code_outline(str(path)) if suffix_lower in ['.c', '.h']: content = mcp_client.ts_c_get_code_outline(str(path))
else: content = mcp_client.ts_cpp_get_code_outline(str(path)) else: content = mcp_client.ts_cpp_get_code_outline(str(path))
else: else:
@@ -232,29 +232,22 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
suffix_lower = path.suffix.lower() suffix_lower = path.suffix.lower()
if ast_mask: if ast_mask:
mask_sections = [] mask_sections = []
from src import mcp_client
for symbol_raw, mode in ast_mask.items(): for symbol_raw, mode in ast_mask.items():
if mode == "hide": continue if mode == "hide": continue
import re
symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw) symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw)
res = "" res = ""
if suffix_lower == ".py": if suffix_lower == ".py":
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol) res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
elif suffix_lower in [".c", ".h", ".cpp", ".hpp", ".cxx", ".cc"]: elif suffix_lower in [".c", ".h", ".cpp", ".hpp", ".cxx", ".cc"]:
is_cpp = any(ext in suffix_lower for ext in [".cpp", ".hpp", ".cxx", ".cc"]) is_cpp = any(ext in suffix_lower for ext in [".cpp", ".hpp", ".cxx", ".cc"])
if mode == "def": if mode == "def": res = mcp_client.ts_cpp_get_definition(str(path), symbol) if is_cpp else mcp_client.ts_c_get_definition(str(path), symbol)
res = mcp_client.ts_cpp_get_definition(str(path), symbol) if is_cpp else mcp_client.ts_c_get_definition(str(path), symbol) else: res = mcp_client.ts_cpp_get_signature(str(path), symbol) if is_cpp else mcp_client.ts_c_get_signature(str(path), symbol)
else:
res = mcp_client.ts_cpp_get_signature(str(path), symbol) if is_cpp else mcp_client.ts_c_get_signature(str(path), symbol)
if res: mask_sections.append(res) if res: mask_sections.append(res)
if mask_sections: if mask_sections: content = "\n\n".join(mask_sections)
content = "\n\n".join(mask_sections) else: content = "(no masked sections visible)"
else:
content = "(no masked sections visible)"
else: else:
content = "(no ast mask defined)" content = "(no ast mask defined)"
elif view_mode == "none": elif view_mode == "none": content = "(context excluded)"
content = "(context excluded)"
elif view_mode == "custom": elif view_mode == "custom":
if custom_slices: if custom_slices:
lines = content.splitlines() lines = content.splitlines()
@@ -272,7 +265,6 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
else: else:
content = summarize.summarise_file(path, content) content = summarize.summarise_file(path, content)
except Exception as e: except Exception as e:
import traceback
content = f"ERROR in {view_mode} view mode for {path}:\n{traceback.format_exc()}" content = f"ERROR in {view_mode} view mode for {path}:\n{traceback.format_exc()}"
error = True error = True
except FileNotFoundError: except FileNotFoundError:
@@ -280,10 +272,10 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
mtime = 0.0 mtime = 0.0
error = True error = True
except Exception as e: except Exception as e:
import traceback
content = f"ERROR reading {path}:\n{traceback.format_exc()}" content = f"ERROR reading {path}:\n{traceback.format_exc()}"
mtime = 0.0 mtime = 0.0
error = True error = True
items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices}) items.append({"path": path, "entry": entry, "content": content, "error": error, "mtime": mtime, "tier": tier, "auto_aggregate": auto_aggregate, "force_full": force_full, "view_mode": view_mode, "ast_signatures": ast_signatures, "ast_definitions": ast_definitions, "ast_mask": ast_mask, "custom_slices": custom_slices})
return items return items
@@ -294,8 +286,7 @@ def _build_files_section_from_items(file_items: list[dict[str, Any]]) -> str:
""" """
sections = [] sections = []
for item in file_items: for item in file_items:
if not item.get("auto_aggregate", True): if not item.get("auto_aggregate", True): continue
continue
path = item.get("path") path = item.get("path")
entry = item.get("entry", "unknown") entry = item.get("entry", "unknown")
content = item.get("content", "") content = item.get("content", "")
@@ -320,11 +311,9 @@ def build_beads_section(base_dir: Path) -> str:
[C: tests/test_aggregate_beads.py:test_build_beads_compaction] [C: tests/test_aggregate_beads.py:test_build_beads_compaction]
""" """
client = beads_client.BeadsClient(base_dir) client = beads_client.BeadsClient(base_dir)
if not client.is_initialized(): if not client.is_initialized(): return ""
return ""
beads = client.list_beads() beads = client.list_beads()
if not beads: if not beads: return ""
return ""
active = [b for b in beads if b.status == "active"] active = [b for b in beads if b.status == "active"]
completed = [b for b in beads if b.status == "completed"] completed = [b for b in beads if b.status == "completed"]
parts = [] parts = []
@@ -335,8 +324,7 @@ def build_beads_section(base_dir: Path) -> str:
parts.append(comp_list) parts.append(comp_list)
if active: if active:
parts.append("### Active Beads") parts.append("### Active Beads")
for b in active: for b in active: parts.append(f"- **{b.title}** ({b.id}): {b.description}")
parts.append(f"- **{b.title}** ({b.id}): {b.description}")
return "\n\n".join(parts) 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: 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:
@@ -344,24 +332,17 @@ def build_markdown_from_items(file_items: list[dict[str, Any]], screenshot_base_
parts = [] parts = []
# STATIC PREFIX: Files and Screenshots must go first to maximize Cache Hits # STATIC PREFIX: Files and Screenshots must go first to maximize Cache Hits
if file_items: if file_items:
if aggregation_strategy == "summarize": if aggregation_strategy == "summarize": parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items)) elif aggregation_strategy == "full": parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
elif aggregation_strategy == "full":
parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
else: # auto else: # auto
if summary_only: if summary_only: parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items))
parts.append("## Files (Summary)\n\n" + summarize.build_summary_markdown(file_items)) else: parts.append("## Files\n\n" + _build_files_section_from_items(file_items))
else: if screenshots: parts.append("## Screenshots\n\n" + build_screenshots_section(screenshot_base_dir, screenshots))
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))
if execution_mode == "beads" and base_dir: if execution_mode == "beads" and base_dir:
beads_md = build_beads_section(base_dir) beads_md = build_beads_section(base_dir)
if beads_md: if beads_md: parts.append(beads_md)
parts.append(beads_md)
# DYNAMIC SUFFIX: History changes every turn, must go last # DYNAMIC SUFFIX: History changes every turn, must go last
if history: if history: parts.append("## Discussion History\n\n" + build_discussion_section(history))
parts.append("## Discussion History\n\n" + build_discussion_section(history))
return "\n\n---\n\n".join(parts) return "\n\n---\n\n".join(parts)
def build_markdown_no_history(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str: def build_markdown_no_history(file_items: list[dict[str, Any]], screenshot_base_dir: Path, screenshots: list[str], summary_only: bool = False, aggregation_strategy: str = "auto") -> str:
@@ -391,8 +372,7 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
parser = ASTParser("python") parser = ASTParser("python")
sections = [] sections = []
for item in file_items: for item in file_items:
if not item.get("auto_aggregate", True): if not item.get("auto_aggregate", True): continue
continue
path = item.get("path") path = item.get("path")
entry = item.get("entry", "") entry = item.get("entry", "")
path_str = str(path) if path else "" path_str = str(path) if path else ""
@@ -403,17 +383,17 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
ast_definitions = item.get("ast_definitions", False) ast_definitions = item.get("ast_definitions", False)
ast_mask = item.get("ast_mask", {}) ast_mask = item.get("ast_mask", {})
content = item.get("content", "") content = item.get("content", "")
is_focus = entry in focus_set or (name and name in focus_set) or (path_str and path_str in focus_set) is_focus = entry in focus_set or (name and name in focus_set) or (path_str and path_str in focus_set)
if not is_focus and path_str: if not is_focus and path_str:
for focus in focus_set: for focus in focus_set:
if focus in path_str: if focus in path_str:
is_focus = True is_focus = True
break break
original = entry if entry and "*" not in entry else (str(path) if path else (entry or "unknown"))
original = entry if entry and "*" not in entry else (str(path) if path else (entry or "unknown"))
slices = item.get('custom_slices', []) slices = item.get('custom_slices', [])
if slices and not item.get('error'): if slices and not item.get('error'):
from src.fuzzy_anchor import FuzzyAnchor
resolved_blocks = [] resolved_blocks = []
content = item.get('content', '') content = item.get('content', '')
suffix = path.suffix.lstrip(".") if path and path.suffix else "text" suffix = path.suffix.lstrip(".") if path and path.suffix else "text"
@@ -434,21 +414,16 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
elif path: elif path:
if ast_mask and not item.get("error"): if ast_mask and not item.get("error"):
mask_sections = [] mask_sections = []
from src import mcp_client
for symbol_raw, mode in ast_mask.items(): for symbol_raw, mode in ast_mask.items():
if mode == "hide": if mode == "hide": continue
continue
import re
symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw) symbol = re.sub(r'\(\d+-\d+\)$', '', symbol_raw)
res = "" res = ""
if path.suffix == ".py": if path.suffix == ".py":
res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol) res = mcp_client.py_get_definition(str(path), symbol) if mode == "def" else mcp_client.py_get_signature(str(path), symbol)
elif path.suffix in [".c", ".h", ".cpp", ".hpp", ".cxx", ".cc"]: elif path.suffix in [".c", ".h", ".cpp", ".hpp", ".cxx", ".cc"]:
is_cpp = any(ext in path.suffix for ext in [".cpp", ".hpp", ".cxx", ".cc"]) is_cpp = any(ext in path.suffix for ext in [".cpp", ".hpp", ".cxx", ".cc"])
if mode == "def": if mode == "def": res = mcp_client.ts_cpp_get_definition(str(path), symbol) if is_cpp else mcp_client.ts_c_get_definition(str(path), symbol)
res = mcp_client.ts_cpp_get_definition(str(path), symbol) if is_cpp else mcp_client.ts_c_get_definition(str(path), symbol) else: res = mcp_client.ts_cpp_get_signature(str(path), symbol) if is_cpp else mcp_client.ts_c_get_signature(str(path), symbol)
else:
res = mcp_client.ts_cpp_get_signature(str(path), symbol) if is_cpp else mcp_client.ts_c_get_signature(str(path), symbol)
if res: if res:
mask_sections.append(res) mask_sections.append(res)
if mask_sections: if mask_sections:
@@ -456,7 +431,6 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
sections.append(f"### `{original}` (Masked)\n\n```{suffix}\n" + "\n\n".join(mask_sections) + "\n```") sections.append(f"### `{original}` (Masked)\n\n```{suffix}\n" + "\n\n".join(mask_sections) + "\n```")
continue continue
if path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc'] and not item.get("error"): if path.suffix in ['.c', '.h', '.cpp', '.hpp', '.cxx', '.cc'] and not item.get("error"):
from src import mcp_client
if ast_definitions: if ast_definitions:
skeleton = mcp_client.ts_cpp_get_skeleton(str(path)) if 'cpp' in path.suffix or 'hpp' in path.suffix or 'cxx' in path.suffix or 'cc' in path.suffix else mcp_client.ts_c_get_skeleton(str(path)) skeleton = mcp_client.ts_cpp_get_skeleton(str(path)) if 'cpp' in path.suffix or 'hpp' in path.suffix or 'cxx' in path.suffix or 'cc' in path.suffix else mcp_client.ts_c_get_skeleton(str(path))
sections.append(f"### `{original}` (AST Definitions)\n\n```{path.suffix.lstrip('.')}\n{skeleton}\n```") sections.append(f"### `{original}` (AST Definitions)\n\n```{path.suffix.lstrip('.')}\n{skeleton}\n```")
@@ -474,12 +448,9 @@ def build_tier3_context(file_items: list[dict[str, Any]], screenshot_base_dir: P
else: else:
sections.append(f"### `{original}`\n\n{summarize.summarise_file(path, content)}") sections.append(f"### `{original}`\n\n{summarize.summarise_file(path, content)}")
parts = [] parts = []
if sections: if sections: parts.append("## Files (Tier 3 - Focused)\n\n" + "\n\n---\n\n".join(sections))
parts.append("## Files (Tier 3 - Focused)\n\n" + "\n\n---\n\n".join(sections)) if screenshots: parts.append("## Screenshots\n\n" + build_screenshots_section(screenshot_base_dir, screenshots))
if screenshots: if history: parts.append("## Discussion History\n\n" + build_discussion_section(history))
parts.append("## Screenshots\n\n" + build_screenshots_section(screenshot_base_dir, screenshots))
if history:
parts.append("## Discussion History\n\n" + build_discussion_section(history))
return "\n\n---\n\n".join(parts) 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, execution_mode: str = "standard") -> 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:
@@ -491,8 +462,7 @@ def run(config: dict[str, Any], aggregation_strategy: str = "auto") -> tuple[str
[C: simulation/sim_base.py:run_sim, src/ai_client.py:_send_anthropic, src/ai_client.py:_send_deepseek, src/ai_client.py:_send_gemini, src/ai_client.py:_send_gemini_cli, src/ai_client.py:_send_minimax, src/app_controller.py:AppController._cb_start_track, src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._process_event_queue, src/app_controller.py:AppController._start_track_logic, src/external_editor.py:_find_vscode_in_registry, src/gui_2.py:App._render_snapshot_tab, src/gui_2.py:App.run, src/gui_2.py:main, src/mcp_client.py:get_git_diff, src/project_manager.py:get_git_commit, src/rag_engine.py:RAGEngine._search_mcp, src/shell_runner.py:run_powershell, tests/conftest.py:kill_process_tree, tests/conftest.py:live_gui, tests/test_conductor_abort_event.py:test_conductor_abort_event_populated, tests/test_conductor_engine_v2.py:test_conductor_engine_dynamic_parsing_and_execution, tests/test_conductor_engine_v2.py:test_conductor_engine_run_executes_tickets_in_order, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:get_vscode_processes, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_custom_window.py:test_app_window_is_borderless, tests/test_headless_simulation.py:module, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_mock_gemini_cli.py:run_mock, tests/test_orchestration_logic.py:test_conductor_engine_run, tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_sim_ai_settings.py:test_ai_settings_simulation_run, tests/test_sim_context.py:test_context_simulation_run, tests/test_sim_execution.py:test_execution_simulation_run, tests/test_sim_tools.py:test_tools_simulation_run] [C: simulation/sim_base.py:run_sim, src/ai_client.py:_send_anthropic, src/ai_client.py:_send_deepseek, src/ai_client.py:_send_gemini, src/ai_client.py:_send_gemini_cli, src/ai_client.py:_send_minimax, src/app_controller.py:AppController._cb_start_track, src/app_controller.py:AppController._do_generate, src/app_controller.py:AppController._process_event_queue, src/app_controller.py:AppController._start_track_logic, src/external_editor.py:_find_vscode_in_registry, src/gui_2.py:App._render_snapshot_tab, src/gui_2.py:App.run, src/gui_2.py:main, src/mcp_client.py:get_git_diff, src/project_manager.py:get_git_commit, src/rag_engine.py:RAGEngine._search_mcp, src/shell_runner.py:run_powershell, tests/conftest.py:kill_process_tree, tests/conftest.py:live_gui, tests/test_conductor_abort_event.py:test_conductor_abort_event_populated, tests/test_conductor_engine_v2.py:test_conductor_engine_dynamic_parsing_and_execution, tests/test_conductor_engine_v2.py:test_conductor_engine_run_executes_tickets_in_order, tests/test_extended_sims.py:test_ai_settings_sim_live, tests/test_extended_sims.py:test_context_sim_live, tests/test_extended_sims.py:test_execution_sim_live, tests/test_extended_sims.py:test_tools_sim_live, tests/test_external_editor_gui.py:get_vscode_processes, tests/test_external_editor_gui.py:test_vscode_launches_with_diff_view, tests/test_gui_custom_window.py:test_app_window_is_borderless, tests/test_headless_simulation.py:module, tests/test_headless_verification.py:test_headless_verification_error_and_qa_interceptor, tests/test_headless_verification.py:test_headless_verification_full_run, tests/test_mock_gemini_cli.py:run_mock, tests/test_orchestration_logic.py:test_conductor_engine_run, tests/test_parallel_execution.py:test_conductor_engine_pool_integration, tests/test_sim_ai_settings.py:test_ai_settings_simulation_run, tests/test_sim_context.py:test_context_simulation_run, tests/test_sim_execution.py:test_execution_simulation_run, tests/test_sim_tools.py:test_tools_simulation_run]
""" """
namespace = config.get("project", {}).get("name") namespace = config.get("project", {}).get("name")
if not namespace: if not namespace: namespace = config.get("output", {}).get("namespace", "project")
namespace = config.get("output", {}).get("namespace", "project")
output_dir = Path(config["output"]["output_dir"]) output_dir = Path(config["output"]["output_dir"])
base_dir = Path(config["files"]["base_dir"]) base_dir = Path(config["files"]["base_dir"])
files = config["files"].get("paths", []) files = config["files"].get("paths", [])
@@ -502,12 +472,21 @@ def run(config: dict[str, Any], aggregation_strategy: str = "auto") -> tuple[str
output_dir.mkdir(parents=True, exist_ok=True) output_dir.mkdir(parents=True, exist_ok=True)
increment = find_next_increment(output_dir, namespace) increment = find_next_increment(output_dir, namespace)
output_file = output_dir / f"{namespace}_{increment:03d}.md" output_file = output_dir / f"{namespace}_{increment:03d}.md"
# Build file items once, then construct markdown from them (avoids double I/O) # Build file items once, then construct markdown from them (avoids double I/O)
file_items = build_file_items(base_dir, files) file_items = build_file_items(base_dir, files)
summary_only = config.get("project", {}).get("summary_only", False) summary_only = config.get("project", {}).get("summary_only", False)
execution_mode = config.get("project", {}).get("execution_mode", "standard") execution_mode = config.get("project", {}).get("execution_mode", "standard")
markdown = build_markdown_from_items(file_items, screenshot_base_dir, screenshots, history, markdown = build_markdown_from_items(
summary_only=summary_only, aggregation_strategy=aggregation_strategy, execution_mode=execution_mode, base_dir=base_dir) file_items,
screenshot_base_dir,
screenshots,
history,
summary_only = summary_only,
aggregation_strategy = aggregation_strategy,
execution_mode = execution_mode,
base_dir = base_dir)
output_file.write_text(markdown, encoding="utf-8") output_file.write_text(markdown, encoding="utf-8")
return markdown, output_file, file_items return markdown, output_file, file_items
@@ -516,7 +495,6 @@ def main() -> None:
""" """
[C: simulation/live_walkthrough.py:module, simulation/ping_pong.py:module, src/ai_server.py:module, src/api_hooks.py:WebSocketServer._run_loop, src/gui_2.py:module, tests/mock_concurrent_mma.py:module, tests/mock_gemini_cli.py:module, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_allow_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_deny_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_unreachable_hook_server, tests/test_cli_tool_bridge.py:module, tests/test_cli_tool_bridge_mapping.py:TestCliToolBridgeMapping.test_mapping_from_api_format, tests/test_cli_tool_bridge_mapping.py:module, tests/test_discussion_takes.py:module, tests/test_external_editor_gui.py:module, tests/test_headless_service.py:TestHeadlessStartup.test_headless_flag_triggers_run, tests/test_headless_service.py:TestHeadlessStartup.test_normal_startup_calls_app_run, tests/test_mma_skeleton.py:module, tests/test_orchestrator_pm.py:module, tests/test_orchestrator_pm_history.py:module, tests/test_presets.py:module, tests/test_project_serialization.py:module, tests/test_run_worker_lifecycle_abort.py:module, tests/test_symbol_lookup.py:module, tests/test_system_prompt_exposure.py:module, tests/test_theme_nerv_fx.py:module] [C: simulation/live_walkthrough.py:module, simulation/ping_pong.py:module, src/ai_server.py:module, src/api_hooks.py:WebSocketServer._run_loop, src/gui_2.py:module, tests/mock_concurrent_mma.py:module, tests/mock_gemini_cli.py:module, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_allow_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_deny_decision, tests/test_cli_tool_bridge.py:TestCliToolBridge.test_unreachable_hook_server, tests/test_cli_tool_bridge.py:module, tests/test_cli_tool_bridge_mapping.py:TestCliToolBridgeMapping.test_mapping_from_api_format, tests/test_cli_tool_bridge_mapping.py:module, tests/test_discussion_takes.py:module, tests/test_external_editor_gui.py:module, tests/test_headless_service.py:TestHeadlessStartup.test_headless_flag_triggers_run, tests/test_headless_service.py:TestHeadlessStartup.test_normal_startup_calls_app_run, tests/test_mma_skeleton.py:module, tests/test_orchestrator_pm.py:module, tests/test_orchestrator_pm_history.py:module, tests/test_presets.py:module, tests/test_project_serialization.py:module, tests/test_run_worker_lifecycle_abort.py:module, tests/test_symbol_lookup.py:module, tests/test_system_prompt_exposure.py:module, tests/test_theme_nerv_fx.py:module]
""" """
from src.paths import get_config_path
config_path = get_config_path() config_path = get_config_path()
if not config_path.exists(): if not config_path.exists():
-8
View File
@@ -45,8 +45,6 @@ def _now_ts() -> str:
def open_session(label: Optional[str] = None) -> None: def open_session(label: Optional[str] = None) -> None:
""" """
Called once at GUI startup. Creates the log directories if needed and Called once at GUI startup. Creates the log directories if needed and
opens the log files for this session within a sub-directory. opens the log files for this session within a sub-directory.
[C: tests/test_app_controller_offloading.py:tmp_session_dir, tests/test_logging_e2e.py:test_logging_e2e, tests/test_session_logger_optimization.py:test_log_tool_call_saves_in_session_scripts, tests/test_session_logger_optimization.py:test_log_tool_output_saves_in_session_outputs, tests/test_session_logger_optimization.py:test_session_directory_and_subdirectories_creation, tests/test_session_logger_reset.py:test_reset_session, tests/test_session_logging.py:test_open_session_creates_subdir_and_registry] [C: tests/test_app_controller_offloading.py:tmp_session_dir, tests/test_logging_e2e.py:test_logging_e2e, tests/test_session_logger_optimization.py:test_log_tool_call_saves_in_session_scripts, tests/test_session_logger_optimization.py:test_log_tool_output_saves_in_session_outputs, tests/test_session_logger_optimization.py:test_session_directory_and_subdirectories_creation, tests/test_session_logger_reset.py:test_reset_session, tests/test_session_logging.py:test_open_session_creates_subdir_and_registry]
@@ -139,8 +137,6 @@ def log_api_hook(method: str, path: str, payload: str) -> None:
def log_comms(entry: dict[str, Any]) -> None: def log_comms(entry: dict[str, Any]) -> None:
""" """
Append one comms entry to the comms log file as a JSON-L line. Append one comms entry to the comms log file as a JSON-L line.
Thread-safe (GIL + line-buffered file). Thread-safe (GIL + line-buffered file).
[C: tests/test_logging_e2e.py:test_logging_e2e] [C: tests/test_logging_e2e.py:test_logging_e2e]
@@ -154,8 +150,6 @@ def log_comms(entry: dict[str, Any]) -> None:
def log_tool_call(script: str, result: str, script_path: Optional[str]) -> Optional[str]: def log_tool_call(script: str, result: str, script_path: Optional[str]) -> Optional[str]:
""" """
Append a tool-call record to the toolcalls log and write the PS1 script to Append a tool-call record to the toolcalls log and write the PS1 script to
the session's scripts directory. Returns the path of the written script file. the session's scripts directory. Returns the path of the written script file.
[C: tests/test_session_logger_optimization.py:test_log_tool_call_saves_in_session_scripts] [C: tests/test_session_logger_optimization.py:test_log_tool_call_saves_in_session_scripts]
@@ -199,8 +193,6 @@ def log_tool_call(script: str, result: str, script_path: Optional[str]) -> Optio
def log_tool_output(content: str) -> Optional[str]: def log_tool_output(content: str) -> Optional[str]:
""" """
Save tool output content to a unique file in the session's outputs directory. Save tool output content to a unique file in the session's outputs directory.
Returns the path of the written file. Returns the path of the written file.
[C: tests/test_session_logger_optimization.py:test_log_tool_output_returns_none_if_no_session, tests/test_session_logger_optimization.py:test_log_tool_output_saves_in_session_outputs] [C: tests/test_session_logger_optimization.py:test_log_tool_output_returns_none_if_no_session, tests/test_session_logger_optimization.py:test_log_tool_output_saves_in_session_outputs]
-2
View File
@@ -3,8 +3,6 @@ from imgui_bundle import imgui
def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None: def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: imgui.ImVec2, color: imgui.ImVec4, shadow_size: float = 10.0, rounding: float = 0.0) -> None:
""" """
Simulates a soft shadow effect by drawing multiple concentric rounded rectangles Simulates a soft shadow effect by drawing multiple concentric rounded rectangles
with decreasing alpha values. This is a faux-shader effect using primitive batching. with decreasing alpha values. This is a faux-shader effect using primitive batching.
""" """
-2
View File
@@ -55,8 +55,6 @@ def _build_subprocess_env() -> dict[str, str]:
def run_powershell(script: str, base_dir: str, qa_callback: Optional[Callable[[str], str]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str: def run_powershell(script: str, base_dir: str, qa_callback: Optional[Callable[[str], str]] = None, patch_callback: Optional[Callable[[str, str], Optional[str]]] = None) -> str:
""" """
Run a PowerShell script with working directory set to base_dir. Run a PowerShell script with working directory set to base_dir.
Returns a string combining stdout, stderr, and exit code. Returns a string combining stdout, stderr, and exit code.
Environment is configured via mcp_env.toml (project root). Environment is configured via mcp_env.toml (project root).
-4
View File
@@ -205,8 +205,6 @@ def summarise_file(path: Path, content: str) -> str:
def summarise_items(file_items: list[dict[str, Any]]) -> list[dict[str, Any]]: def summarise_items(file_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
""" """
Given a list of file_item dicts (as returned by aggregate.build_file_items), Given a list of file_item dicts (as returned by aggregate.build_file_items),
return a parallel list of dicts with an added `summary` key. return a parallel list of dicts with an added `summary` key.
""" """
@@ -225,8 +223,6 @@ def summarise_items(file_items: list[dict[str, Any]]) -> list[dict[str, Any]]:
def build_summary_markdown(file_items: list[dict[str, Any]]) -> str: def build_summary_markdown(file_items: list[dict[str, Any]]) -> str:
""" """
Build a compact markdown string of file summaries, suitable for the Build a compact markdown string of file summaries, suitable for the
initial <context> block instead of full file contents. initial <context> block instead of full file contents.
""" """