phase: 155_folder_summary_voice
All verification is complete — the whole project is 100% green. Final report: **Phase 155 final verification pass — complete.** **Verified (all tasks 01–04 done in `complete/`):** - Instruction nudge in `app/rag/folder_summaries.py` (forbidden openers as the pin, 4 constraints preserved, docstring cites the phase) - `scripts/regen_folder_summaries.py` (import_docs idiom, full mode, cron-safe summary line, fail-loud boot) - Unit pins, integration suite, mock voice update, dedicated E2E suite **Test / lint / coverage:** - `uv run pytest --cov=app -q` → 100% passed (exit 0, zero failures); coverage **99%** on `app/` (>90% ✓) - `uv run pytest tests/e2e/test_folder_summary_voice.py -v --no-cov` → **3 passed** in isolation - `uv run ruff check .` → clean; `uv run pyright` → 0 errors, 0 warnings **Completion criteria:** 1. Marker + header prefix byte-identical — ✅ (git vs HEAD + unit pins verbatim, green) 2. Regen script full regeneration — ✅ (live smoke: exit 0, `folders=… limit=7` summary line; misconfig boot → named stderr, exit 1; `updated_at`-advance/no-prune/canned-text semantics proven by the integration suite, green) 3. Sync path untouched — ✅ (0 diffs to `app/api/sync.py` / `app/rag/importer.py`; phase-127 pins green) 4. E2E stored + rendered without forbidden openers — ✅ (3/3, exact canned-string assertions + phase-127 regression guard) 5. Full suite green, E2E isolated green, ruff + pyright clean, coverage >90% — ✅ 6. Atomic commit — deferred to the harness (working tree left intact per protocol) **Documentation:** README.md and AGENTS.md reviewed in full — verified consistent, no changes needed (README already documents the new script in the update workflow; all `.env.example` vars documented; pages table matches routes; 17 colors = 9+8; no stale mock-voice references). **Decisions:** No live-infrastructure changes (dev-DB read + a 0-row script smoke only). The `TODO.md` working-tree edit is the owner's own, left untouched. **Next pending phase:** `156_table_ux_polish`.
This commit is contained in:
@@ -272,6 +272,40 @@ def test_system_prompt_has_marker_and_locked_instruction() -> None:
|
||||
assert FOLDER_SUMMARY_MODE in system # the marker the E2E mock keys on
|
||||
|
||||
|
||||
def test_instruction_pins_the_variety_nudge_and_preserved_constraints() -> None:
|
||||
"""Phase 155 task 01: the locked instruction keeps the four phase-94
|
||||
constraints (1–3 sentences; plain text; no markdown; no invention)
|
||||
and ADDS the variety direction — the two forbidden openers are the
|
||||
pin (an explicit NOT, not a mere mention)."""
|
||||
# The four preserved constraints (phase 94 — unchanged).
|
||||
for fragment in (
|
||||
"1-3 sentence",
|
||||
"plain-text",
|
||||
"natural language",
|
||||
"Do not use markdown",
|
||||
"Do not invent anything that is not in the list",
|
||||
):
|
||||
assert fragment in FOLDER_SUMMARY_INSTRUCTION, fragment
|
||||
# The variety direction (phase 155): the "varied, natural way"
|
||||
# direction must survive the grammar polish.
|
||||
assert "Open in a varied, natural way" in FOLDER_SUMMARY_INSTRUCTION
|
||||
assert "state what the folder is for" in FOLDER_SUMMARY_INSTRUCTION
|
||||
# The two forbidden openers, banned by an explicit NOT.
|
||||
assert "Do NOT begin with" in FOLDER_SUMMARY_INSTRUCTION
|
||||
assert "'This folder contains'" in FOLDER_SUMMARY_INSTRUCTION
|
||||
assert "'The <folder name> folder contains'" in FOLDER_SUMMARY_INSTRUCTION
|
||||
|
||||
|
||||
def test_marker_and_header_prefix_stay_byte_identical() -> None:
|
||||
"""Phase 155 task 01: the E2E mock keys on ``FOLDER_SUMMARY_MODE``
|
||||
and the ``FOLDER_HEADER_PREFIX`` line — the instruction nudge must
|
||||
not touch them (the mock-contract pin).``SYSTEM_PROMPT`` keeps the
|
||||
marker-first shape: the nudge lands AFTER the marker."""
|
||||
assert FOLDER_SUMMARY_MODE == "FOLDER_SUMMARY_MODE"
|
||||
assert FOLDER_HEADER_PREFIX == "Folder: "
|
||||
assert SYSTEM_PROMPT.startswith("FOLDER_SUMMARY_MODE: ")
|
||||
|
||||
|
||||
# ---------- build_folder_summary_prompt: user ----------
|
||||
|
||||
|
||||
@@ -369,22 +403,55 @@ def test_mock_returns_canned_folder_summary_naming_the_folder() -> None:
|
||||
marker in the system prompt and returns the canned one-liner naming
|
||||
the folder from the ``Folder: …`` header — driven through the
|
||||
GENERATOR's real prompt, so the two can never drift (the drill-down
|
||||
E2E asserts on this exact template)."""
|
||||
E2E asserts on this exact template). Phase 155 task 01: the canned
|
||||
voice is the natural-opener ``Documentation for <folder>.``."""
|
||||
system, user = build_folder_summary_prompt(
|
||||
"Homelab", "deployments/ansible",
|
||||
[("Homelab", "deployments/ansible/lab-inventory.md", "Lab Inventory", None)],
|
||||
)
|
||||
assert compose_answer(_mock_body(system, user)) == (
|
||||
"Fixture folder summary for Homelab/deployments/ansible."
|
||||
"Documentation for Homelab/deployments/ansible."
|
||||
)
|
||||
# The source-root row names the source itself.
|
||||
system, user = build_folder_summary_prompt("Homelab", "",
|
||||
[("Homelab", "top.md", "Top", None)])
|
||||
assert compose_answer(_mock_body(system, user)) == (
|
||||
"Fixture folder summary for Homelab."
|
||||
"Documentation for Homelab."
|
||||
)
|
||||
|
||||
|
||||
def test_mock_canned_folder_reply_shape_and_determinism() -> None:
|
||||
"""Phase 155 task 01: the canned folder reply is the new voice —
|
||||
it NAMES the folder (from the ``Folder: …`` header), AVOIDS the
|
||||
two forbidden openers, and is a PURE function of the request (the
|
||||
determinism contract: two identical requests → identical replies).
|
||||
"""
|
||||
def reply_for(source: str, folder_path: str) -> str:
|
||||
system, user = build_folder_summary_prompt(
|
||||
source, folder_path, [(source, "x.md", "X", None)]
|
||||
)
|
||||
return compose_answer(_mock_body(system, user))
|
||||
|
||||
first = reply_for("Homelab", "deployments/ansible")
|
||||
second = reply_for("Homelab", "deployments/ansible") # same request → same reply
|
||||
assert first == second, "pure function of the request (determinism contract)"
|
||||
assert "Homelab/deployments/ansible" in first, "the reply names the folder"
|
||||
# The two forbidden openers (phase 155's pin), case-insensitively.
|
||||
assert not first.lower().startswith("this folder contains"), (
|
||||
"the formulaic opener must not survive the canned reply"
|
||||
)
|
||||
assert not first.lower().startswith(
|
||||
"the homelab/deployments/ansible folder contains"
|
||||
), "the named-folder formulaic opener must not survive either"
|
||||
# A different folder yields a different (but same-shape) reply —
|
||||
# still names its own folder, still neither forbidden opener.
|
||||
root = reply_for("Homelab", "")
|
||||
assert root != first, "the reply is a function of the request, not a constant"
|
||||
assert "Homelab" in root
|
||||
assert not root.lower().startswith("this folder contains")
|
||||
assert not root.lower().startswith("the homelab folder contains")
|
||||
|
||||
|
||||
def test_mock_folder_marker_is_not_shadowed_by_the_summary_branch() -> None:
|
||||
"""``FOLDER_SUMMARY_MODE`` contains ``SUMMARY_MODE`` as a substring —
|
||||
the mock must check the folder branch FIRST, or every folder call
|
||||
@@ -394,7 +461,7 @@ def test_mock_folder_marker_is_not_shadowed_by_the_summary_branch() -> None:
|
||||
)
|
||||
assert "SUMMARY_MODE" in system # the shadowing hazard is real
|
||||
answer = compose_answer(_mock_body(system, user))
|
||||
assert answer == "Fixture folder summary for S/a."
|
||||
assert answer == "Documentation for S/a."
|
||||
assert not answer.startswith("This document covers")
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user