phase: 97_kb_tree_catalog
Build and Push Containers / build-and-push-app (push) Successful in 2m11s
Build and Push Containers / build-and-push-db (push) Successful in 11s

All completion criteria verified — everything is green, no defects found. Final report:

## Phase 97 final verification pass — ALL GREEN

**Verified (no code changes needed):**
- `GET /api/docs/tree` (admin), `build_kb_tree` pure builder, `PATCH /api/folders/summary`, migration 0018 (`manually_edited`, head confirmed), generator skip/keep + `kept_manual` stat, RAG tree UI + edit affordance in `sources.js`/`index.html`/`styles.css`
- `tests/e2e/test_kb_tree.py`: 8 passed — top level, drill source/folder, edit round-trip, clear, manual-desc-survives-sync, reload fallback, anonymous gate
- Integration: tree shape/order/403/empty/indexed-only + PATCH update/create/root/clear/404/403/no-LLM + stat-walk equivalence (in `test_docs_api.py`); 3-field `folder_summaries=` import token preserved

**Gates (exact commands):**
- `uv run pytest --cov=app --cov-report=term-missing` → **2053 passed**, TOTAL coverage **99%** (>90% ✓)
- `uv run ruff check . && uv run pyright` → **All checks passed / 0 errors**
- `uv run pytest tests/e2e/test_kb_tree.py -v --no-cov` → **8 passed** in isolation
- 30 story/RAG-view E2E suites run **one per process**: all passed, incl. `test_ls_tree_drilldown` (agent `ls` byte-identical ✓), `test_import_documents`, `test_edit_summaries`, `test_admin_auth`, `test_kb_overview`

**Completion criteria:** tree view ✓ · edit round-trip + clear ✓ · manual persists/clear resets ✓ · `ls` unchanged ✓ · pytest/coverage/lint ✓ · E2E isolation ✓ · commit — left to harness per protocol (working tree untouched, `git add/commit` not run)

**Deviations:** none. **Next pending phase:** none — `todo/` contains only 97 (96 already committed).
This commit is contained in:
2026-09-11 22:48:02 -04:00
parent a49be80b8e
commit ad7585d474
81 changed files with 6299 additions and 211 deletions
+38 -11
View File
@@ -122,7 +122,7 @@ from sqlalchemy.orm import Session
from app.config import Settings
from app.core.theming import COLOR_FIELDS
from app.db import SessionLocal
from app.models import Chunk, Document, SavedChat, UiSettings
from app.models import Chunk, Document, GitSource, SavedChat, UiSettings
from app.rag.sources_meta import bump_sources_version
from e2e.auth_helpers import login
from e2e.conftest import (
@@ -823,7 +823,22 @@ def _seed_tool_docs(db: Session) -> None:
"""The two-document pair (see the constants above): the retrievable
grounding document (one chunk carrying the mock's own bag-of-words
embedding) + the catalog-only read target (no chunks — retrieval
never puts it in context, so the agent's read tool accepts it)."""
never puts it in context, so the agent's read tool accepts it).
Phase 94: the drill-down ``ls`` top level reads the registry — the
seed registers BOTH sources (TRUNCATEd in
``_reset_kb_with_tool_docs``), ``Checklist`` FIRST: registry order
is ``(added_at, id)``, so the mock's drill (first source of the
listing) — and therefore the read — lands on ``read-me.md``
deterministically, independent of the shared dev DB's leftover
registry rows."""
# COMMIT between the inserts (not flush): ``added_at`` is
# ``server_default now()`` — the transaction timestamp — and the
# tie-break is the random uuid ``id``, so one-transaction rows order
# nondeterministically.
db.add(GitSource(url=READ_SOURCE, kind="local"))
db.commit()
db.add(GitSource(url=TOOL_DOC_SOURCE, kind="local"))
db.add(
Document(
source=TOOL_DOC_SOURCE,
@@ -866,11 +881,18 @@ def _seed_tool_docs(db: Session) -> None:
def _reset_kb_with_tool_docs() -> None:
"""Truncate the KB tables (the house reset) and seed the pair — a
direct DB seed, so the turn is genuinely grounded and the mock's
single-read flow runs to completion: ls → read on the first catalog
line (the catalog-only document) → the quoted answer."""
single-read flow runs to completion: ls → drill ls (phase 94 — the
top level lists sources only) → read on the first file line (the
catalog-only document) → the quoted answer. Phase 94: ``git_sources``
joins the TRUNCATE — the top-level listing reads the registry, so
the leftover rows of other suites would change what the drill
targets."""
with SessionLocal() as db:
db.execute(
text("TRUNCATE chunks, documents, query_log, steering_notes, kb_overview")
text(
"TRUNCATE chunks, documents, query_log, steering_notes, "
"kb_overview, git_sources"
)
)
db.commit()
_seed_tool_docs(db)
@@ -904,20 +926,25 @@ def test_tool_call_lines_gray(page: Page, app_url: str, db_ready: None) -> None:
# Screenshot 1: the yellow "Listing documents" / "Reading" lines —
# now gray accent-ink text with the gray accent-line left border,
# both lines' text intact.
# the lines' text intact. Phase 94: the drill-down ls adds a THIRD
# line between them — the drill ls scoped to the first source of
# the top level (registry order: Checklist — the read target's
# source).
lines = page.locator(".msg.brain .tool-call")
expect(lines).to_have_count(2)
expect(lines).to_have_count(3)
expect(lines.nth(0)).to_contain_text("Listing documents")
expect(lines.nth(1)).to_contain_text("Reading")
expect(lines.nth(1)).to_contain_text(READ_SP)
expect(lines.nth(1)).to_contain_text("Listing documents in")
expect(lines.nth(1)).to_contain_text(READ_SOURCE)
expect(lines.nth(2)).to_contain_text("Reading")
expect(lines.nth(2)).to_contain_text(READ_SP)
_assert_gray(lines.nth(0), "color", GRAY["accent_ink"], label="ls line text")
_assert_gray(
lines.nth(0), "borderLeftColor", GRAY["accent_line"], label="ls line border"
)
_assert_gray(lines.nth(1), "color", GRAY["accent_ink"], label="read line text")
_assert_gray(lines.nth(2), "color", GRAY["accent_ink"], label="read line text")
# The path chip on the Reading line: gray brand-soft background +
# gray ink (the screenshot's code chip — still gray under the ramp).
code = lines.nth(1).locator("code")
code = lines.nth(2).locator("code")
_assert_gray(code, "backgroundColor", GRAY["brand_soft"], label="read chip bg")
_assert_gray(code, "color", GRAY["ink"], label="read chip text")