phase: 162_search_recall_small_sources
**Phase 162 — final verification pass (all tasks already complete; verified + docs pass)** - Verified: `_fold` (NFKD+casefold, stdlib) inside `_normalize_name` — `BESTÅ`→`besta` question side, `BÅsta.md`→`basta` path side; A2 match classes/ranking/caps/bonus untouched; FTS/cosine/A8 untouched. - Verified: `grep_document` folds both sides via the imported `_fold` (no second copy); regex-teaching lines byte-identical; 3 copy sites updated. - Verified: `regenerate_overview` appends the deterministic `Sources: <name> (<k> doc|docs)` tail (happy path only; zero-doc + `LLMError` rows byte-identical; `build_overview_prompt`/`load_kb_overview`/`build_kb_section` untouched). - Verified: `TOOLS_SECTION` grep sentence updated + D4 search-discipline sentence present **verbatim**, immediately after the grep sentence; `PERSONA`/LOW pins byte-identical (`test_prompt_lock.py`). - Measured evidence (in `.agents/reports/162_…/task04_prefix_evidence_and_battery.md` + re-measured every run by `test_under_rank_is_measured_not_assumed`): pre-fix tokens `['build','best']` → IKEA doc rank 6 (outside top-5); post-fix `['build','besta']` → suggested #1, cosine unchanged 0.382. - `uv run pytest tests/e2e/test_search_recall_small_sources.py -v --no-cov` → **7 passed** (isolated); neighbours `test_kb_overview{,_freshness} test_name_signal_read_chips test_grep_regex_teaching test_harness_aligned_tools test_agent_document_tools test_tool_call_compact` → **24 passed**. - `uv run pytest --cov=app --cov-report=term-missing` → **3756 passed**, app coverage **99%** (>90%). - `uv run ruff check .` → All checks passed; `uv run pyright` → **0 errors**. - Completion criteria: fold both ways ✅ · `Sources:` tail ✅ · TOOLS_SECTION + D4 verbatim + re-cut pins ✅ · E2E green/battery recorded/full suite/coverage/lint ✅ · docs pass ✅ · commit + dir move = harness. - Battery: 5 phase-119 questions byte-identical to the phase-119 record (GATE PASS); the 2 Besta questions **skipped loudly** — verified the 2026-09-05 replica dump has zero `ikea` matches (`grep -c -i ikea data/bor_live_kb_replica.dump.sql` = 0); rank gate stays with the owner's live KB. - Docs fixed: README — overview-tail doc + `BOR_KB_OVERVIEW_MAX_CHARS` tail-cut constraint, the phase-162 search-discipline paragraph, per-phase (not per-story) E2E wording, new "Retrieval regression battery" section; AGENTS.md — rule 4 per-phase wording + battery command in Quick reference. (The overview's expected "AGENTS.md §6 tool table" does not exist in this repo — the grep contract line lives in README's tool table, already updated.) - Deviations (recorded, not assumed): `_fold` adds `casefold` (required for the pinned `ß → ss`); `①` NFKD-decomposes to `1` (the overview's parenthetical was wrong) and is excluded by `NAME_TOKEN_MIN_LEN`; pre-fix evidence lives in the reports dir because task files are read-only for this pass. - Owner follow-up A6 (runtime, not code): add the Tuning-tab steering note "IKEA's BESTÅ assembly PDF is a live source — search it as `BESTÅ` or `AA-1272068`". - Next pending phase: none — 162 is the only entry in `todo/` (harness moves it to `complete/`).
This commit is contained in:
+21
-11
@@ -131,8 +131,10 @@ task 04):
|
||||
the only capped read (the phase-95 cap unchanged). And ``grep``
|
||||
greps the
|
||||
indexed documents (or the one document a combined ``source/path``
|
||||
names) for a case-insensitive fixed substring and returns up to 20
|
||||
``source/path:line: text`` match lines (owner-locked A5, phase 68),
|
||||
names) for a case- and accent-insensitive fixed substring (accents
|
||||
fold — ``besta`` matches ``BESTÅ``; phase 162, LOCKED A3) and
|
||||
returns up to 20 ``source/path:line: text`` match lines
|
||||
(owner-locked A5, phase 68),
|
||||
each line truncated to 200 chars. A grep is a **locator**, not a
|
||||
context-adder: it never appends to the answer context (only
|
||||
``read`` does — ``holder.read_docs`` is untouched by a grep).
|
||||
@@ -312,7 +314,7 @@ from app.rag.llm import (
|
||||
ToolResultPiece,
|
||||
chat_stream_retried,
|
||||
)
|
||||
from app.rag.retriever import TRUNCATION_MARKER
|
||||
from app.rag.retriever import TRUNCATION_MARKER, _fold
|
||||
from app.rag.scaffolding import ScaffoldingFilter
|
||||
from app.rag.source_removal import resolve_source_name
|
||||
|
||||
@@ -437,7 +439,8 @@ AGENT_TOOLS: list[dict[str, Any]] = [
|
||||
"name": "grep",
|
||||
"description": (
|
||||
"Search the indexed documents for an exact string "
|
||||
"(case-insensitive) and return up to 20 matching lines "
|
||||
"(case- and accent-insensitive — accents fold: `besta` "
|
||||
"matches `BESTÅ`) and return up to 20 matching lines "
|
||||
"as `source/path:line: text` — a locator, not a "
|
||||
"context-adder: read the winner with `read` — for a "
|
||||
"large document, use the reported line number as "
|
||||
@@ -458,7 +461,9 @@ AGENT_TOOLS: list[dict[str, Any]] = [
|
||||
"type": "string",
|
||||
"description": (
|
||||
"The exact text to search for (a plain "
|
||||
"substring, not a regex — no '.*', no "
|
||||
"substring — case- and accent-insensitive, "
|
||||
"accents fold: `besta` matches `BESTÅ` — "
|
||||
"not a regex — no '.*', no "
|
||||
"'\\.', no character classes)"
|
||||
),
|
||||
},
|
||||
@@ -1560,16 +1565,21 @@ def format_covered_spans(spans: Sequence[tuple[int, int]]) -> str:
|
||||
def grep_document(content: str, pattern: str) -> list[tuple[int, str]]:
|
||||
"""Every line of *content* that contains *pattern*, in file order.
|
||||
|
||||
Case-insensitive **fixed substring** (owner-locked A5: no regex — no
|
||||
ReDoS surface, a simple contract for the model). Returns
|
||||
``(1-based line number, line.rstrip())`` pairs; an empty *content*
|
||||
never matches a non-empty pattern.
|
||||
Case- **and accent-insensitive fixed substring** (owner-locked A5:
|
||||
no regex — no ReDoS surface, a simple contract for the model). Both
|
||||
sides fold through the shared :func:`app.rag.retriever._fold` plus
|
||||
lowercase (phase 162, LOCKED A3 — the fold changes the alphabet it
|
||||
ignores, not the matching semantics: ``grep "besta"`` matches a
|
||||
content line ``The BESTÅ frame``, and a pattern carrying accents
|
||||
folds too), so grep and the name-hit path can never drift apart.
|
||||
Returns ``(1-based line number, line.rstrip())`` pairs; an empty
|
||||
*content* never matches a non-empty pattern.
|
||||
"""
|
||||
needle = pattern.lower()
|
||||
needle = _fold(pattern.lower())
|
||||
return [
|
||||
(number, line.rstrip())
|
||||
for number, line in enumerate(content.split("\n"), start=1)
|
||||
if needle in line.lower()
|
||||
if needle in _fold(line.lower())
|
||||
]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user