phase: 98_sync_summary_visibility
Build and Push Containers / build-and-push-app (push) Successful in 1m51s
Build and Push Containers / build-and-push-db (push) Successful in 11s

All verification complete. Final report:

**Phase 98 — Sync summary visibility: final verification pass** (all 5 tasks already complete; implementation verified against the design, no defects found, no code changes needed)

- **Implementation checked:** `SyncStatus` phase machine (4 new keys, terminal-keep counts), `on_progress` hook in `generate_folder_summaries`, `summary_pending` on `KbTreeSource`/`KbTreeFolder` + D3 rule in `build_kb_tree`, phase-aware sync labels + pending UI in `sources.js`, `.kb-summary-pending` CSS — all match decisions D1–D5.
- **Unit + integration:** `uv run pytest` → 2184 tests, 0 failed/errors (exit 0)
- **Coverage:** `uv run pytest --cov=app --cov-report=term-missing` → **99%** on `app/` (criterion >90% ✓; `app/api/sync.py` and `app/rag/folder_summaries.py` at 100%)
- **Lint/types:** `uv run ruff check .` → All checks passed; `uv run pyright` → 0 errors, 0 warnings
- **Phase E2E (isolation):** `uv run pytest tests/e2e/test_sync_summary_visibility.py -v --no-cov` → **3 passed** (phase machine, live label, pending markers + gap-fill self-heal)
- **Regression suites (each isolated, `--no-cov`):** test_kb_tree ✓, test_ls_tree_drilldown 3 ✓, test_sync_button 3 ✓, test_sync_upload_progress 4 ✓, test_oneshot_llm_retry 2 ✓, test_local_directory_sources 3 ✓
- **Completion criteria:** all 7 verified green — status phase fields + terminal semantics; `Writing KB overview…`/`Summarizing folders… (n/m)` labels (title + aria-live); pending set == `missing_folder_summaries` (integration cross-check pinned at `test_docs_api.py:428`); CLI/`ls` byte-identity (no changes to those paths, pins green); suite/coverage/lint gates; dedicated + regression E2E. Commit left to the harness per protocol (no `git add`/`commit` run).
- **Decisions/deviations:** none — no fixes were required this pass.
- **Next pending phase:** `99_kb_tree_table_and_back_nav`.
This commit is contained in:
2026-09-13 00:23:05 -04:00
parent 909c96c7bc
commit f665a83b1a
39 changed files with 3265 additions and 112 deletions
+27 -3
View File
@@ -42,7 +42,12 @@ Chat turns never generate folder summaries — the agent's ``ls`` output
(phase 94, task 03) only reads the stored rows. Generation is the
caller's job at sync time (phase 94, task 02), and :func:`generate_
folder_summaries` only flushes — the sync path owns the transaction
(the phase-53 ``bump_sources_version`` convention).
(the phase-53 ``bump_sources_version`` convention). The sync path
additionally passes the optional ``on_progress`` hook (phase 98,
task 01) so the sync status can report the folder being summarized;
the ``scripts/import_docs.py`` CLI passes none — the hook defaults to
``None`` and is a zero-cost no-op, leaving the CLI's log-only stats
contract untouched.
Self-heal (phase 96): an exhausted one-shot retry can still leave a
candidate folder without a row. :func:`missing_folder_summaries`
@@ -55,7 +60,7 @@ byte-identical (text AND ``updated_at``), the prune pass still runs.
from __future__ import annotations
import logging
from collections.abc import Sequence
from collections.abc import Callable, Sequence
from datetime import UTC, datetime
from typing import Protocol
@@ -390,6 +395,7 @@ async def generate_folder_summaries(
*,
skip: bool = False,
only_missing: bool = False,
on_progress: Callable[[int, int, str, str], None] | None = None,
) -> dict[str, int]:
"""Regenerate the stored folder summaries for the current catalogue.
@@ -436,6 +442,17 @@ async def generate_folder_summaries(
unchanged catalogue it is a no-op (the invariant kept), and it
still drops rows whose folder fell below the minimum.
Progress (phase 98, task 01): when *on_progress* is given, it is
called once per candidate with ``(done, total, source,
folder_path)`` in the same sorted ``(source, folder_path)`` order,
BEFORE the attempt — so an instant manual skip and a failed folder
BOTH advance the counter (the UI's position moves on either), and
``total`` is ``len(keys)`` at loop start (under ``only_missing
=True`` that is the MISSING count, not the full candidate count).
``None`` — the ``scripts/import_docs.py`` CLI path — is a
zero-cost no-op (guarded at the call site; nothing observable
changes without it).
Only flushes — the CALLER commits (the phase-53
``bump_sources_version`` convention: the sync path owns the
transaction, so a failed sync rolls the summaries back with it).
@@ -463,8 +480,15 @@ async def generate_folder_summaries(
if only_missing:
keys = [key for key in keys if key not in existing]
for key in keys:
for i, key in enumerate(keys):
source, folder_path = key
# Phase 98 (task 01): the optional progress hook fires BEFORE
# the attempt — instant manual skips and failed folders both
# advance the counter (D5), and ``total`` is the loop-start
# count (the missing count under ``only_missing``). ``None``
# (the CLI path) is a zero-cost no-op.
if on_progress is not None:
on_progress(i + 1, len(keys), source, folder_path)
stored = existing.get(key)
if stored is not None and stored.manually_edited:
# Owner-edited description (phase 97, task 01): NEVER