feat(rag): summarize single-document folders (MIN_DOCS_PER_FOLDER 2 → 1)
Build and Push Containers / build-and-push-app (push) Successful in 2m10s
Build and Push Containers / build-and-push-db (push) Successful in 13s

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:
2026-09-14 08:57:57 -04:00
parent 3a81793565
commit 35d65d2f25
13 changed files with 297 additions and 212 deletions
+13 -11
View File
@@ -65,8 +65,9 @@ Data model — see ``.agents/PLAN.md`` §Data Model:
(``id = 1``); every column NULL = "use the
default" (env value for the strings, the built-in
palette for the colors — task 01).
* ``folder_summaries`` — one row per folder with ≥ 2 documents:
the sync-time ``lite`` summary the drill-down
* ``folder_summaries`` — one row per folder (recursive count
≥ 1 — every existing folder): the sync-time
``lite`` summary the drill-down
``ls`` shows next to each folder (phase 94;
``folder_path = ""`` = the source root).
"""
@@ -501,14 +502,15 @@ class FolderSummary(Base):
``documents.path`` (String(1000)) and is the source-relative
folder prefix. ``folder_path = ""`` is the SOURCE ROOT — the
top-level source summary (the whole source's recursive subtree).
* Rows exist only for folders with ≥ 2 documents (recursive count
— the same set the ``ls`` count rule counts): a
single-document folder is fully described by its one file line,
so no ``lite`` burn. After a changed sync, rows whose folder
dropped below 2 documents are pruned (a pruned/renamed folder's
summary would otherwise go stale); rows for folders that still
have ≥ 2 documents persist (an unchanged folder's summary is
still true). Both rules are generator policy (``app.rag.
* Rows exist for folders with ≥ 1 document (recursive count —
the same set the ``ls`` count rule counts): a folder is a
candidate while any document lives under it, single-file folders
and single-file source roots included. After a changed sync,
rows whose folder dropped below 1 document (vanished from the
catalogue) are pruned (a pruned/renamed folder's summary would
otherwise go stale); rows for folders that still hold at least
one document persist (an unchanged folder's summary is still
true). Both rules are generator policy (``app.rag.
folder_summaries``), not schema constraints.
* ``summary`` — the 1–3 sentence plain-text description the aipi
``lite`` model wrote at sync time (``FOLDER_SUMMARY_MODE``,
@@ -521,7 +523,7 @@ class FolderSummary(Base):
folder_summaries``) SKIPS a manual row on regeneration (no
``lite`` burn on owner text — counted ``kept_manual``) and never
prunes it (owner content persists until cleared — even for a
folder below the 2-document minimum); an owner correction is
folder below the 1-document minimum); an owner correction is
never silently rewritten (the phase-97 ``00_phase.md`` decision).
Clearing the description deletes the row — the next KB-changing
sync regenerates an AI description (the reset path).