feat(context): Integrate view modes into aggregate pipeline
This commit is contained in:
+37
-4
@@ -145,16 +145,21 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
|||||||
tier : int | None (optional tier for context management)
|
tier : int | None (optional tier for context management)
|
||||||
auto_aggregate : bool
|
auto_aggregate : bool
|
||||||
force_full : bool
|
force_full : bool
|
||||||
|
view_mode : str (summary, full, skeleton, outline, none)
|
||||||
[C: src/app_controller.py:AppController._bg_task, src/orchestrator_pm.py:module, tests/test_aggregate_flags.py:test_auto_aggregate_skip, tests/test_aggregate_flags.py:test_force_full, tests/test_tiered_context.py:test_build_file_items_with_tiers]
|
[C: src/app_controller.py:AppController._bg_task, src/orchestrator_pm.py:module, tests/test_aggregate_flags.py:test_auto_aggregate_skip, tests/test_aggregate_flags.py:test_force_full, tests/test_tiered_context.py:test_build_file_items_with_tiers]
|
||||||
"""
|
"""
|
||||||
with get_monitor().scope("build_file_items"):
|
with get_monitor().scope("build_file_items"):
|
||||||
items: list[dict[str, Any]] = []
|
items: list[dict[str, Any]] = []
|
||||||
|
parser = None
|
||||||
for entry_raw in files:
|
for entry_raw in files:
|
||||||
if isinstance(entry_raw, dict):
|
if isinstance(entry_raw, dict):
|
||||||
entry = cast(str, entry_raw.get("path", ""))
|
entry = cast(str, entry_raw.get("path", ""))
|
||||||
tier = entry_raw.get("tier")
|
tier = entry_raw.get("tier")
|
||||||
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", "summary")
|
||||||
|
if force_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", {})
|
||||||
@@ -164,6 +169,9 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
|||||||
tier = getattr(entry_raw, "tier", None)
|
tier = getattr(entry_raw, "tier", None)
|
||||||
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", "summary")
|
||||||
|
if force_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", {})
|
||||||
@@ -173,6 +181,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
|||||||
tier = None
|
tier = None
|
||||||
auto_aggregate = True
|
auto_aggregate = True
|
||||||
force_full = False
|
force_full = False
|
||||||
|
view_mode = "summary"
|
||||||
ast_signatures = False
|
ast_signatures = False
|
||||||
ast_definitions = False
|
ast_definitions = False
|
||||||
ast_mask = {}
|
ast_mask = {}
|
||||||
@@ -181,13 +190,30 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
|||||||
continue
|
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, "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")
|
||||||
mtime = path.stat().st_mtime
|
mtime = path.stat().st_mtime
|
||||||
error = False
|
error = False
|
||||||
|
if not error and view_mode != "full":
|
||||||
|
if view_mode == "summary":
|
||||||
|
content = summarize.summarise_file(path, content)
|
||||||
|
elif view_mode == "skeleton":
|
||||||
|
if path.suffix == ".py":
|
||||||
|
if not parser: parser = ASTParser("python")
|
||||||
|
content = parser.get_skeleton(content, path=str(path))
|
||||||
|
else:
|
||||||
|
content = summarize.summarise_file(path, content)
|
||||||
|
elif view_mode == "outline":
|
||||||
|
if path.suffix == ".py":
|
||||||
|
if not parser: parser = ASTParser("python")
|
||||||
|
content = parser.get_code_outline(content, path=str(path))
|
||||||
|
else:
|
||||||
|
content = summarize.summarise_file(path, content)
|
||||||
|
elif view_mode == "none":
|
||||||
|
content = "(context excluded)"
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
content = f"ERROR: file not found: {path}"
|
content = f"ERROR: file not found: {path}"
|
||||||
mtime = 0.0
|
mtime = 0.0
|
||||||
@@ -196,7 +222,7 @@ def build_file_items(base_dir: Path, files: list[str | dict[str, Any]]) -> list[
|
|||||||
content = f"ERROR: {e}"
|
content = f"ERROR: {e}"
|
||||||
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, "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
|
||||||
|
|
||||||
|
|
||||||
@@ -213,13 +239,20 @@ def _build_files_section_from_items(file_items: list[dict[str, Any]]) -> str:
|
|||||||
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", "")
|
||||||
|
view_mode = item.get("view_mode", "full")
|
||||||
if path is None:
|
if path is None:
|
||||||
sections.append(f"### `{entry}`\n\n```text\n{content}\n```")
|
if view_mode == "summary":
|
||||||
|
sections.append(f"### `{entry}`\n\n{content}")
|
||||||
|
else:
|
||||||
|
sections.append(f"### `{entry}`\n\n```text\n{content}\n```")
|
||||||
else:
|
else:
|
||||||
path_obj = Path(path) if isinstance(path, str) else path
|
path_obj = Path(path) if isinstance(path, str) else path
|
||||||
suffix = path_obj.suffix.lstrip(".") if path_obj.suffix else "text"
|
suffix = path_obj.suffix.lstrip(".") if path_obj.suffix else "text"
|
||||||
original = entry if "*" not in entry else str(path)
|
original = entry if "*" not in entry else str(path)
|
||||||
sections.append(f"### `{original}`\n\n```{suffix}\n{content}\n```")
|
if view_mode == "summary":
|
||||||
|
sections.append(f"### `{original}`\n\n{content}")
|
||||||
|
else:
|
||||||
|
sections.append(f"### `{original}`\n\n```{suffix}\n{content}\n```")
|
||||||
return "\n\n---\n\n".join(sections)
|
return "\n\n---\n\n".join(sections)
|
||||||
|
|
||||||
def build_beads_section(base_dir: Path) -> str:
|
def build_beads_section(base_dir: Path) -> str:
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import pytest
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
from src.app_controller import AppController
|
from src.app_controller import AppController
|
||||||
from src.models import FileItem
|
from src.models import FileItem
|
||||||
|
|
||||||
@@ -35,7 +36,7 @@ def test_do_generate_uses_context_files(monkeypatch):
|
|||||||
|
|
||||||
def mock_aggregate_run(flat, **kwargs):
|
def mock_aggregate_run(flat, **kwargs):
|
||||||
assert flat["files"]["paths"] == controller.context_files
|
assert flat["files"]["paths"] == controller.context_files
|
||||||
return ("md", "path", [], "stable_md", "disc_text")
|
return ("md", Path("path"), [])
|
||||||
|
|
||||||
monkeypatch.setattr(pm, "flat_config", mock_flat_config)
|
monkeypatch.setattr(pm, "flat_config", mock_flat_config)
|
||||||
monkeypatch.setattr(pm, "save_project", lambda *args: None)
|
monkeypatch.setattr(pm, "save_project", lambda *args: None)
|
||||||
|
|||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import pytest
|
||||||
|
from pathlib import Path
|
||||||
|
from src import aggregate
|
||||||
|
|
||||||
|
def test_view_mode_summary(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
test_file = base_dir / "test.py"
|
||||||
|
test_file.write_text("def hello():\n print('world')\n", encoding="utf-8")
|
||||||
|
|
||||||
|
files = [{"path": "test.py", "view_mode": "summary"}]
|
||||||
|
items = aggregate.build_file_items(base_dir, files)
|
||||||
|
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["view_mode"] == "summary"
|
||||||
|
# Content should be a summary, which typically starts with **Python** or similar
|
||||||
|
assert "**Python**" in items[0]["content"]
|
||||||
|
assert "functions: hello" in items[0]["content"]
|
||||||
|
|
||||||
|
def test_view_mode_full(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
test_file = base_dir / "test.py"
|
||||||
|
content = "def hello():\n print('world')\n"
|
||||||
|
test_file.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
files = [{"path": "test.py", "view_mode": "full"}]
|
||||||
|
items = aggregate.build_file_items(base_dir, files)
|
||||||
|
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["view_mode"] == "full"
|
||||||
|
assert items[0]["content"] == content
|
||||||
|
|
||||||
|
def test_view_mode_skeleton(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
test_file = base_dir / "test.py"
|
||||||
|
content = "def hello():\n \"\"\"Docstring.\"\"\"\n print('world')\n"
|
||||||
|
test_file.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
files = [{"path": "test.py", "view_mode": "skeleton"}]
|
||||||
|
items = aggregate.build_file_items(base_dir, files)
|
||||||
|
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["view_mode"] == "skeleton"
|
||||||
|
# Skeleton should have '...' instead of 'print'
|
||||||
|
assert "def hello():" in items[0]["content"]
|
||||||
|
assert "Docstring" in items[0]["content"]
|
||||||
|
assert "..." in items[0]["content"]
|
||||||
|
assert "print('world')" not in items[0]["content"]
|
||||||
|
|
||||||
|
def test_view_mode_outline(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
test_file = base_dir / "test.py"
|
||||||
|
content = "def hello():\n \"\"\"Docstring.\"\"\"\n print('world')\n"
|
||||||
|
test_file.write_text(content, encoding="utf-8")
|
||||||
|
|
||||||
|
files = [{"path": "test.py", "view_mode": "outline"}]
|
||||||
|
items = aggregate.build_file_items(base_dir, files)
|
||||||
|
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["view_mode"] == "outline"
|
||||||
|
# Outline should have [Func] hello (Lines 1-3)
|
||||||
|
assert "[Func] hello (Lines 1-3)" in items[0]["content"]
|
||||||
|
|
||||||
|
def test_view_mode_none(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
test_file = base_dir / "test.py"
|
||||||
|
test_file.write_text("def hello():\n print('world')\n", encoding="utf-8")
|
||||||
|
|
||||||
|
files = [{"path": "test.py", "view_mode": "none"}]
|
||||||
|
items = aggregate.build_file_items(base_dir, files)
|
||||||
|
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["view_mode"] == "none"
|
||||||
|
assert items[0]["content"] == "(context excluded)"
|
||||||
|
|
||||||
|
def test_view_mode_default_summary(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
test_file = base_dir / "test.py"
|
||||||
|
test_file.write_text("def hello():\n print('world')\n", encoding="utf-8")
|
||||||
|
|
||||||
|
# Test with simple string path
|
||||||
|
files = ["test.py"]
|
||||||
|
items = aggregate.build_file_items(base_dir, files)
|
||||||
|
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0]["view_mode"] == "summary"
|
||||||
|
assert "**Python**" in items[0]["content"]
|
||||||
|
|
||||||
|
def test_files_section_rendering(tmp_path):
|
||||||
|
base_dir = tmp_path / "project"
|
||||||
|
base_dir.mkdir()
|
||||||
|
|
||||||
|
# Full item
|
||||||
|
full_item = {
|
||||||
|
"path": base_dir / "full.txt",
|
||||||
|
"entry": "full.txt",
|
||||||
|
"content": "Full content",
|
||||||
|
"view_mode": "full",
|
||||||
|
"auto_aggregate": True
|
||||||
|
}
|
||||||
|
|
||||||
|
# Summary item
|
||||||
|
summary_item = {
|
||||||
|
"path": base_dir / "summary.txt",
|
||||||
|
"entry": "summary.txt",
|
||||||
|
"content": "**Summary** content",
|
||||||
|
"view_mode": "summary",
|
||||||
|
"auto_aggregate": True
|
||||||
|
}
|
||||||
|
|
||||||
|
sections = aggregate._build_files_section_from_items([full_item, summary_item])
|
||||||
|
|
||||||
|
# Full should be in backticks
|
||||||
|
assert "### `full.txt`" in sections
|
||||||
|
assert "```txt\nFull content\n```" in sections
|
||||||
|
|
||||||
|
# Summary should NOT be in backticks
|
||||||
|
assert "### `summary.txt`" in sections
|
||||||
|
assert "```txt\n**Summary** content\n```" not in sections
|
||||||
|
assert "**Summary** content" in sections
|
||||||
|
|
||||||
Reference in New Issue
Block a user