phase: 97_kb_tree_catalog
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:
@@ -23,6 +23,13 @@ Phase 79 (API tokens): the anonymous pins moved to the gated contract —
|
||||
phase-16 "the viewer stays open" soft rule is SUPERSEDED, shared chats
|
||||
are the only open surface). The password sign-in / sign-out /
|
||||
wrong-password assertions are UNCHANGED.
|
||||
|
||||
Phase 97 adaptation: the admin catalog pin (test 4) is re-pointed at
|
||||
the DRILL-DOWN TREE — the top level lists the fixture's single
|
||||
indexed-only source (``docs``), the flat 13-row tbody no longer
|
||||
renders, so the total is re-asserted PER LEVEL (drill → count; the
|
||||
stat cards carry the KB total). The asserted document behavior is
|
||||
unchanged; navigation only.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
@@ -81,6 +88,28 @@ def _reset_db(mock_port: int, seed: bool) -> ImportSummary | None:
|
||||
return _run_in_thread(_import_fixtures(mock_port))
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase 97: the catalog is the drill-down tree the agent's `ls` walks —
|
||||
# top level = the sources (the fixture's single indexed-only source,
|
||||
# `docs`), then one folder link per path segment. Small per-suite
|
||||
# drill helpers (the test_kb_tree house pattern).
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def _drill(page: Page, *names: str) -> None:
|
||||
"""Drill one level at a time: each name is the EXACT text of the
|
||||
source/folder link at the current level (client-side — no fetch,
|
||||
no URL change)."""
|
||||
for name in names:
|
||||
page.click(f'#folders-tbody a.folder-link:text-is("{name}")')
|
||||
|
||||
|
||||
def _go_top(page: Page) -> None:
|
||||
"""Back to the top level: the breadcrumb's top-level link (call
|
||||
between drills only — the breadcrumb is hidden at the top)."""
|
||||
page.locator("#kb-crumb a.kb-crumb-link").first.click()
|
||||
|
||||
|
||||
def _ask(page: Page, question: str) -> None:
|
||||
"""Send one turn and wait until the grounded answer has fully landed."""
|
||||
page.fill("#message-input", question)
|
||||
@@ -247,8 +276,32 @@ def test_admin_login_unlocks_sources_and_tuning(
|
||||
expect(page.locator("#sources-gate")).to_be_hidden()
|
||||
expect(page.locator("#stat-docs")).to_have_text("13") # phase 47: +quadlet/j2
|
||||
expect(page.locator("#stat-chunks")).not_to_have_text("–")
|
||||
# Phase 97: the catalog is the drill-down tree — the top level
|
||||
# lists the source (the catalog-rendered signal), the file table
|
||||
# is hidden there; the flat 13-row total is re-asserted PER LEVEL
|
||||
# (drill → count; the sum is 3 + 1 + 2 + 1 + 3 + 1 + 1 + 1 = 13).
|
||||
page.locator("#folders-tbody .folder-link").first.wait_for(state="visible")
|
||||
expect(page.locator("#docs-table")).to_be_hidden()
|
||||
_drill(page, "docs")
|
||||
expect(page.locator("#folders-tbody tr")).to_have_count(2)
|
||||
expect(page.locator("#docs-tbody tr")).to_have_count(0) # no root-level files
|
||||
_drill(page, "homelab")
|
||||
expect(page.locator("#docs-table")).to_be_visible()
|
||||
expect(page.locator("#docs-tbody tr")).to_have_count(13)
|
||||
expect(page.locator("#docs-tbody tr")).to_have_count(3)
|
||||
_go_top(page)
|
||||
_drill(page, "docs", "deployments")
|
||||
expect(page.locator("#docs-tbody tr")).to_have_count(1)
|
||||
for folder, count in (
|
||||
("container_gitlab", 2),
|
||||
("networking", 1),
|
||||
("quadlet", 3),
|
||||
("scripts", 1),
|
||||
("ssh", 1),
|
||||
("templates", 1),
|
||||
):
|
||||
_go_top(page)
|
||||
_drill(page, "docs", "homelab", folder)
|
||||
expect(page.locator("#docs-tbody tr")).to_have_count(count)
|
||||
|
||||
# Chat: the tuning UI is back — Sign out instead of Sign in, Tune
|
||||
# under the answer. The header toggle is NOT back: removed from the
|
||||
|
||||
Reference in New Issue
Block a user