feat(rag): summarize single-document folders (MIN_DOCS_PER_FOLDER 2 → 1)
Relax the phase-94 folder-summary scope rule from ≥ 2 documents to ≥ 1: a folder (or source root) is a candidate while ANY document lives under it, so single-file folders and single-file source roots get their own lite-written description. A row is now pruned only when its folder loses its last document (vanishes from the catalogue). The constant is the single source of truth, so the flip propagates to the generator's candidate set, the prune pass, the missing_folder_summaries gap probe (the next sync self-heals the new gaps), and the KB-tree summary_pending markers (1-doc folders / sources now read "Summary pending" until their row lands). Docstrings/comments across app/, scripts/import_docs.py, and the E2E fixtures updated to the ≥ 1 wording. Unit + integration tests updated to the new semantics (the pruned-below-minimum scenario is now a folder losing its LAST doc; single-doc folders are pinned as candidates/pending). Full suite: 2314 passed, app coverage 99%; ruff + pyright clean; folder-summary E2E stories pass in isolation (ls_tree_drilldown, sync_summary_visibility, kb_tree, kb_tree_nav, document_dates, oneshot_llm_retry).
This commit is contained in:
@@ -26,7 +26,9 @@ real-Sync pattern; no git anywhere):
|
||||
Total: 7 documents; alpha counts 5 (root + 2 + 2), beta counts 2. Every
|
||||
stored description is deterministic: the mock's EXISTING
|
||||
``FOLDER_SUMMARY_MODE`` branch (phase 94 — no mock changes needed)
|
||||
stores, per ≥ 2-doc folder, the canned one-liner naming the folder,
|
||||
stores, per existing folder (the ≥ 1 rule — this fixture holds no
|
||||
single-doc folders, so the pinned rows are unchanged), the canned
|
||||
one-liner naming the folder,
|
||||
``Fixture folder summary for <source>[/<folder>].`` — the ``synced_kb``
|
||||
module fixture pins those exact rows (all with
|
||||
``manually_edited = false``) after the sync, and the tests assert on
|
||||
@@ -154,8 +156,9 @@ TOTAL_DOCS = ALPHA_COUNT + BETA_COUNT
|
||||
#: The sync-time folder descriptions the mock's canned
|
||||
#: ``FOLDER_SUMMARY_MODE`` branch stores (the phase-94 byte-stable
|
||||
#: template — the one-liner names the folder), in ``(source,
|
||||
#: folder_path)`` order: one row per ≥ 2-doc folder (the
|
||||
#: recursive-subtree rule) — the ``""`` rows are the source roots.
|
||||
#: folder_path)`` order: one row per existing folder (the ≥ 1-doc
|
||||
#: recursive-subtree rule — this fixture holds no single-doc folders)
|
||||
#: — the ``""`` rows are the source roots.
|
||||
SUMMARY_FOR = "Fixture folder summary for {}."
|
||||
ALPHA_ROOT_SUM = SUMMARY_FOR.format(ALPHA)
|
||||
ONE_SUM = SUMMARY_FOR.format(f"{ALPHA}/one")
|
||||
@@ -382,7 +385,8 @@ def synced_kb(app_server: str, kb_tree_dirs: tuple[Path, Path]) -> None:
|
||||
walk → chunk → embed → overview → folder summaries → version
|
||||
bump), and pins the stored folder descriptions: the mock's canned
|
||||
``FOLDER_SUMMARY_MODE`` branch (phase 94) makes the sync store one
|
||||
deterministic row per ≥ 2-doc folder — the tests assert on that
|
||||
deterministic row per existing folder (the ≥ 1-doc rule — this
|
||||
fixture holds no single-doc folders) — the tests assert on that
|
||||
exact text (and on the phase-97 ``manually_edited`` flag: every
|
||||
stored row starts out AI-written).
|
||||
"""
|
||||
@@ -409,7 +413,8 @@ def synced_kb(app_server: str, kb_tree_dirs: tuple[Path, Path]) -> None:
|
||||
assert detail["pruned"] == 0, detail
|
||||
assert detail["overview"] is True, detail
|
||||
# The change-gated folder descriptions (phase 94) landed: one row
|
||||
# per ≥ 2-doc folder, the mock's byte-stable text — and every row
|
||||
# per existing folder (the ≥ 1-doc rule — this fixture holds no
|
||||
# single-doc folders), the mock's byte-stable text — and every row
|
||||
# AI-written (the ``manually_edited`` flag starts false).
|
||||
with SessionLocal() as db:
|
||||
rows = db.execute(
|
||||
|
||||
@@ -42,7 +42,8 @@ of the mock's cosine distribution, and the ``<tools>`` section is present
|
||||
(the flow's precondition).
|
||||
|
||||
The mock's canned ``FOLDER_SUMMARY_MODE`` branch (phase 94 task 01)
|
||||
stores, per ≥ 2-doc folder, the deterministic one-liner
|
||||
stores, per existing folder (the ≥ 1-doc rule — this fixture holds
|
||||
no single-doc folders), the deterministic one-liner
|
||||
``Fixture folder summary for <source>[/<folder>].`` — the ``synced_kb``
|
||||
module fixture pins those exact rows in ``folder_summaries`` after the
|
||||
sync, and the drill answers assert on them in the ``ls`` output.
|
||||
@@ -159,8 +160,9 @@ assert "\n" not in TWO_A_CONTENT[:80] # the quote must stay one line
|
||||
|
||||
#: The sync-time folder summaries the mock's canned ``FOLDER_SUMMARY_MODE``
|
||||
#: branch stores (task 01's byte-stable template), in
|
||||
#: ``(source, folder_path)`` order: one row per ≥ 2-doc folder (the
|
||||
#: recursive-subtree rule) — the ``""`` rows are the source roots.
|
||||
#: ``(source, folder_path)`` order: one row per existing folder (the
|
||||
#: ≥ 1-doc recursive-subtree rule — this fixture holds no single-doc
|
||||
#: folders) — the ``""`` rows are the source roots.
|
||||
EXPECTED_SUMMARIES: list[tuple[str, str, str]] = [
|
||||
(ALPHA, "", f"Fixture folder summary for {ALPHA}."),
|
||||
(ALPHA, "one", f"Fixture folder summary for {ALPHA}/one."),
|
||||
@@ -426,7 +428,8 @@ def synced_kb(app_server: str, drill_dirs: tuple[Path, Path]) -> None:
|
||||
walk → chunk → embed → overview → folder summaries → version bump),
|
||||
and pins the stored folder summaries: the mock's canned
|
||||
``FOLDER_SUMMARY_MODE`` branch (task 01) makes the sync store one
|
||||
deterministic row per ≥ 2-doc folder — the drill turns' answers
|
||||
deterministic row per existing folder (the ≥ 1-doc rule — this
|
||||
fixture holds no single-doc folders) — the drill turns' answers
|
||||
assert on that exact text.
|
||||
"""
|
||||
alpha, beta = drill_dirs
|
||||
@@ -452,7 +455,8 @@ def synced_kb(app_server: str, drill_dirs: tuple[Path, Path]) -> None:
|
||||
assert detail["pruned"] == 0, detail
|
||||
assert detail["overview"] is True, detail
|
||||
# The change-gated folder summaries (phase 94 task 02) landed: one
|
||||
# row per ≥ 2-doc folder, the mock's byte-stable text (the drill
|
||||
# row per existing folder (the ≥ 1-doc rule — this fixture holds
|
||||
# no single-doc folders), the mock's byte-stable text (the drill
|
||||
# answers quote exactly these lines).
|
||||
with SessionLocal() as db:
|
||||
rows = db.execute(
|
||||
|
||||
@@ -46,7 +46,9 @@ the same host) registered as a ``kind=local`` source (the
|
||||
pattern; no git anywhere): ``syncsum/`` with TWO ≥ 2-doc folders —
|
||||
``alpha/`` (2 docs) and ``bravo/`` (2 docs), no root-level files. The
|
||||
generator's candidate set is therefore exactly 3: the source root
|
||||
(``folder_path ""``) + the two folders (the recursive-subtree ≥ 2 rule).
|
||||
(``folder_path ""``) + the two folders (the recursive-subtree ≥ 1
|
||||
rule — this fixture holds no single-doc folders, so the rule's
|
||||
relaxation is inert for it).
|
||||
|
||||
Autouse cleanup (the phase-96 pattern): before each test wait for no
|
||||
running sync on EITHER app, then truncate the shared registry + KB
|
||||
@@ -160,7 +162,7 @@ FOLDER_B = "bravo"
|
||||
FOLDERS = (FOLDER_A, FOLDER_B)
|
||||
|
||||
N_FILES = 4 # two folders × 2 docs
|
||||
N_CANDIDATES = 3 # the source root + the two folders (the ≥ 2-doc rule)
|
||||
N_CANDIDATES = 3 # the source root + the two folders (the ≥ 1-doc rule)
|
||||
|
||||
#: The mock's byte-stable ``FOLDER_SUMMARY_MODE`` lines for this fixture
|
||||
#: (the phase-94 template — the label is the ``FOLDER_HEADER_PREFIX``
|
||||
|
||||
Reference in New Issue
Block a user