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:
@@ -314,7 +314,7 @@ def test_cross_check_nested_level_matches_group_folder_listing() -> None:
|
||||
# --------------------------------------------------------------------
|
||||
# summary_pending (phase 98, task 03) — the D3 rule (ONE concept):
|
||||
# a SOURCE or FOLDER node is pending iff its recursive document count
|
||||
# ≥ MIN_DOCS_PER_FOLDER (2) AND it has NO stored folder_summaries row
|
||||
# ≥ MIN_DOCS_PER_FOLDER (1) AND it has NO stored folder_summaries row
|
||||
# (AI or manual — any row). That is exactly
|
||||
# ``app.rag.folder_summaries.missing_folder_summaries``'s candidate
|
||||
# set — the marker never drifts from the gap-fill (the integration
|
||||
@@ -359,10 +359,11 @@ def test_folder_with_stored_row_is_not_pending() -> None:
|
||||
assert source.summary_pending is True
|
||||
|
||||
|
||||
def test_single_document_folder_never_pending() -> None:
|
||||
"""A < 2-document folder is NEVER pending (it never gets a summary
|
||||
— its one file line IS its description), even with no stored row
|
||||
— while its ≥ 2-doc source root (no root row) still is."""
|
||||
def test_single_document_folder_is_pending() -> None:
|
||||
"""The ≥ 1 rule: a 1-document folder with NO stored row IS pending
|
||||
(its one file line no longer exempts it — the next sync
|
||||
summarizes it), as is its source root (2 docs, no root row).
|
||||
A registered 0-document source is the only never-pending case."""
|
||||
rows = [
|
||||
("S", "solo/only.md", "Only", 1, T0, C0), # 1-doc folder
|
||||
("S", "top.md", "Top", 1, T0, C1), # source total = 2
|
||||
@@ -370,7 +371,7 @@ def test_single_document_folder_never_pending() -> None:
|
||||
(source,) = build_kb_tree(["S"], rows, {})
|
||||
(solo,) = _folder_nodes(source)
|
||||
assert solo.documents == 1
|
||||
assert solo.summary_pending is False
|
||||
assert solo.summary_pending is True
|
||||
assert source.summary_pending is True
|
||||
|
||||
|
||||
@@ -407,7 +408,7 @@ def test_name_collision_pending_follows_recursive_count() -> None:
|
||||
|
||||
|
||||
def test_source_root_pending_and_zero_document_source_never() -> None:
|
||||
"""The source root: a source with ≥ 2 docs and NO ``(source, "")``
|
||||
"""The source root: a source with ≥ 1 docs and NO ``(source, "")``
|
||||
row → the SOURCE node is pending; the stored root row clears it.
|
||||
A registered 0-document source is NEVER pending (0 < the minimum —
|
||||
there is nothing to summarize), with or without a manual row."""
|
||||
|
||||
Reference in New Issue
Block a user