phase: 94_ls_tree_drilldown
All green. Verification complete. **Phase 94 — `ls` drill-down tree: final verification pass (all 5 tasks were already complete; verified, nothing to fix)** - Verified `ls` 3-level tree (`app/rag/agent.py`): `ls()` sources + summaries, `ls(source)`/`ls(source/folder)` drill-down, 50-line file cap + grep-pointer note, NOT-A-FOLDER teaching refusal - Verified `folder_summaries` (migration 0017, model, `app/rag/folder_summaries.py` generator: `FOLDER_SUMMARY_MODE` marker, fail-soft per folder, ≥2-doc scope + prune) wired change-gated in both sync paths - Verified 10-turn fixture battery verdict recorded in `TOOL_CALLING_TESTING.md` §9 (2026-09-11): turbo PASS 19/19 contract, 98.7 s (−12.5…−13.2 % vs baseline); lite PASS 18/18, 43.6 s (+7.7 %) — accuracy at/above baseline, gate met - `uv run pytest --cov=app --cov-report=term-missing` → 1939 passed, 0 failed; TOTAL coverage **99 %** (folder_summaries.py 100 %) - `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings - E2E in isolation: `test_ls_tree_drilldown.py` 3 passed; `test_agent_document_tools` 4, `test_agent_unlimited_tools` 4, `test_harness_aligned_tools` 3, `test_search_tool` 3, `test_grep_regex_teaching` 2, `test_response_to_docs` 4 — all passed (read/grep contracts untouched) - Dedicated folder-summary tests (fail-soft, prune, both sync paths, migration): 46 passed - Completion criteria: all 6 met; working tree holds only phase-94 changes (commit left to harness per protocol) **Next pending phase:** `95_read_truncation_cap`
This commit is contained in:
@@ -48,9 +48,16 @@ and every failure path (git error, model down) never bumps. The
|
||||
counter is pinned to the migration-0010 seed (0) around every test by
|
||||
:func:`_reset_sources_version`.
|
||||
|
||||
The git / import / overview layers are monkeypatched in ``app.api.sync``
|
||||
(same fake style as ``test_import_docs_git.py``) — no real git, no LLM:
|
||||
the runner's state machine and HTTP surface are under test.
|
||||
The git / import / overview / folder-summary layers are monkeypatched
|
||||
in ``app.api.sync`` (same fake style as ``test_import_docs_git.py``) —
|
||||
no real git, no LLM: the runner's state machine and HTTP surface are
|
||||
under test. Phase 94 (task 02): the folder-summary layer gets the same
|
||||
treatment — the fake-import tests' canned summaries would otherwise
|
||||
steer the REAL ``generate_folder_summaries`` at the global
|
||||
``documents`` table and the real ``LLMClient`` (network); the
|
||||
real-import tests (host temp dirs, deterministic ``FakeEmbedder``) keep
|
||||
the real generator, with ``folder_summaries`` truncated around every
|
||||
test (:func:`_clean_folder_summaries`).
|
||||
|
||||
The admin client is used **as a context manager** on purpose: the
|
||||
background sync task lives on the app's event loop, so the loop must
|
||||
@@ -182,6 +189,19 @@ def clean_documents(db: Session) -> Iterator[None]:
|
||||
db.commit()
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def _clean_folder_summaries(db: Session) -> Iterator[None]:
|
||||
"""Phase 94: the ``folder_summaries`` table is global state the real
|
||||
generator (the real-import tests) writes — truncated around every
|
||||
sync test so the change-gate / table-empty-gate assertions start
|
||||
from a known (empty) table."""
|
||||
db.execute(text("TRUNCATE folder_summaries"))
|
||||
db.commit()
|
||||
yield
|
||||
db.execute(text("TRUNCATE folder_summaries"))
|
||||
db.commit()
|
||||
|
||||
|
||||
def _real_llm(monkeypatch: pytest.MonkeyPatch) -> None:
|
||||
"""The pipeline's ``LLMClient`` becomes the deterministic in-process
|
||||
``FakeEmbedder`` (real import, no network). ``FakeEmbedder``
|
||||
@@ -276,6 +296,34 @@ class FakeOverview:
|
||||
return self.ok
|
||||
|
||||
|
||||
class FakeFolderSummaries:
|
||||
"""Records every ``generate_folder_summaries`` call; canned stats.
|
||||
|
||||
Phase 94 (task 02): keeps the fake-import tests at the deterministic
|
||||
layer boundary — the real generator would read the global
|
||||
``documents`` table and call the (real) ``LLMClient`` over the
|
||||
network. The generator only flushes, so the fake honours the
|
||||
``skip`` flag the same way (the zero stats, no side effects)."""
|
||||
|
||||
ZERO = {"generated": 0, "failed": 0, "pruned": 0}
|
||||
|
||||
def __init__(self, stats: dict[str, int] | None = None) -> None:
|
||||
self.stats = stats if stats is not None else dict(self.ZERO)
|
||||
self.llms: list[LLMClient] = []
|
||||
self.sessions: list[Session] = []
|
||||
self.skip_flags: list[bool] = []
|
||||
|
||||
async def __call__(
|
||||
self, db: Session, llm: LLMClient, *, skip: bool = False
|
||||
) -> dict[str, int]:
|
||||
self.skip_flags.append(skip)
|
||||
if skip:
|
||||
return dict(self.ZERO)
|
||||
self.llms.append(llm)
|
||||
self.sessions.append(db)
|
||||
return dict(self.stats)
|
||||
|
||||
|
||||
def _fake_clone() -> tuple[list[tuple[str, Path]], object]:
|
||||
"""A ``clone_or_pull`` that materialises a checkout with one .md file."""
|
||||
calls: list[tuple[str, Path]] = []
|
||||
@@ -327,6 +375,7 @@ def test_admin_sync_success_reports_full_detail(
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
fake_overview = FakeOverview(ok=True)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", fake_overview)
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", FakeFolderSummaries())
|
||||
|
||||
_login(sync_client)
|
||||
assert sync_client.get("/api/sync/status").json() == {
|
||||
@@ -399,6 +448,7 @@ def test_unchanged_kb_skips_overview_refresh(
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
fake_overview = FakeOverview(ok=True)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", fake_overview)
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", FakeFolderSummaries())
|
||||
|
||||
_login(sync_client)
|
||||
assert sync_client.post("/api/sync").status_code == 202
|
||||
@@ -435,6 +485,7 @@ def test_double_trigger_while_running_returns_409(
|
||||
fake_import = FakeImportSources(ImportSummary(files=1, added=1), delay=0.5)
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", FakeOverview(ok=True))
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", FakeFolderSummaries())
|
||||
|
||||
_login(sync_client)
|
||||
assert sync_client.post("/api/sync").status_code == 202
|
||||
@@ -551,12 +602,17 @@ def test_db_rows_win_over_env(
|
||||
fake_import = FakeImportSources(ImportSummary(files=1, added=1))
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", FakeOverview(ok=True))
|
||||
fake_folders = FakeFolderSummaries()
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", fake_folders)
|
||||
|
||||
_login(sync_client)
|
||||
with caplog.at_level(logging.INFO, logger="app.api.sync"):
|
||||
assert sync_client.post("/api/sync").status_code == 202
|
||||
body = _poll(sync_client, "success")
|
||||
|
||||
# Phase 94: the changed canned summary fired the (stubbed) folder
|
||||
# step with the run's own session + the import's LLM client.
|
||||
assert fake_folders.llms == [fake_import.llms[0]]
|
||||
assert clone_calls == [(db_url, tmp_path / "bor" / "managed")]
|
||||
assert fake_import.sources == [[tmp_path / "bor" / "managed"]]
|
||||
assert "env.example.com" not in str(body) # the env URL never reaches the UI
|
||||
@@ -584,6 +640,7 @@ def test_env_fallback_when_table_empty(
|
||||
fake_import = FakeImportSources(ImportSummary(files=1, added=1))
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", FakeOverview(ok=True))
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", FakeFolderSummaries())
|
||||
|
||||
_login(sync_client)
|
||||
with caplog.at_level(logging.INFO, logger="app.api.sync"):
|
||||
@@ -988,6 +1045,7 @@ def test_sync_builds_ignore_map_by_root_string_with_union(
|
||||
fake_import = FakeImportSources(ImportSummary(files=1, added=1))
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", FakeOverview(ok=True))
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", FakeFolderSummaries())
|
||||
|
||||
_login(sync_client)
|
||||
assert sync_client.post("/api/sync").status_code == 202
|
||||
@@ -1022,6 +1080,7 @@ def test_sync_without_ignore_lists_passes_empty_map(
|
||||
fake_import = FakeImportSources(ImportSummary(files=1, added=1))
|
||||
monkeypatch.setattr(sync_api, "import_sources", fake_import)
|
||||
monkeypatch.setattr(sync_api, "regenerate_overview", FakeOverview(ok=True))
|
||||
monkeypatch.setattr(sync_api, "generate_folder_summaries", FakeFolderSummaries())
|
||||
|
||||
_login(sync_client)
|
||||
assert sync_client.post("/api/sync").status_code == 202
|
||||
|
||||
Reference in New Issue
Block a user