phase: 98_sync_summary_visibility
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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user