phase: 94_ls_tree_drilldown
Build and Push Containers / build-and-push-app (push) Successful in 1m45s
Build and Push Containers / build-and-push-db (push) Successful in 25s

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:
2026-09-11 00:59:35 -04:00
parent 9188be259b
commit d4943b4822
61 changed files with 6289 additions and 666 deletions
+52
View File
@@ -63,6 +63,10 @@ Data model — see ``.agents/PLAN.md`` §Data Model:
(``id = 1``); every column NULL = "use the
default" (env value for the strings, the built-in
palette for the colors — task 01).
* ``folder_summaries`` — one row per folder with ≥ 2 documents:
the sync-time ``lite`` summary the drill-down
``ls`` shows next to each folder (phase 94;
``folder_path = ""`` = the source root).
"""
from __future__ import annotations
@@ -446,3 +450,51 @@ class UiSettings(Base):
accent_bg: Mapped[str | None] = mapped_column(String(7), nullable=True)
accent_ink: Mapped[str | None] = mapped_column(String(7), nullable=True)
accent_line: Mapped[str | None] = mapped_column(String(7), nullable=True)
class FolderSummary(Base):
"""One sync-time folder summary (phase 94, task 01).
``ls`` is a drill-down tree (phase 94, ``00_phase.md``): the top
level lists the synced projects, each with its stored source-root
summary; drilling into a source lists its folders, each with its
stored folder summary. This table holds those summaries:
* PK ``(source, folder_path)`` — ``source`` mirrors
``documents.source`` (String(120)); ``folder_path`` mirrors
``documents.path`` (String(1000)) and is the source-relative
folder prefix. ``folder_path = ""`` is the SOURCE ROOT — the
top-level source summary (the whole source's recursive subtree).
* Rows exist only for folders with ≥ 2 documents (recursive count
— the same set the ``ls`` count rule counts): a
single-document folder is fully described by its one file line,
so no ``lite`` burn. After a changed sync, rows whose folder
dropped below 2 documents are pruned (a pruned/renamed folder's
summary would otherwise go stale); rows for folders that still
have ≥ 2 documents persist (an unchanged folder's summary is
still true). Both rules are generator policy (``app.rag.
folder_summaries``), not schema constraints.
* ``summary`` — the 1–3 sentence plain-text description the aipi
``lite`` model wrote at sync time (``FOLDER_SUMMARY_MODE``,
``app.rag.folder_summaries`` — change-gated and fail-soft like
the KB overview: an old summary is better than none).
Chat turns only READ these rows (the agent's ``ls`` output, phase
94 task 03) — generation happens at sync time only (task 02).
"""
__tablename__ = "folder_summaries"
#: Mirrors ``documents.source`` (String(120)) — PK part 1.
source: Mapped[str] = mapped_column(String(120), primary_key=True)
#: Mirrors ``documents.path`` (String(1000)) — the source-relative
#: folder prefix; ``""`` = the source root. PK part 2.
folder_path: Mapped[str] = mapped_column(String(1000), primary_key=True)
#: The lite-written 1–3 sentence description — never empty (the
#: generator validates before storing, ``app.rag.folder_summaries``).
summary: Mapped[str] = mapped_column(Text)
#: Fresh UTC stamp on every upsert (the ``kb_overview.updated_at``
#: precedent; the generator always sets it explicitly).
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)