phase: 146_search_tool_scope
Build and Push Containers / build-and-push-app (push) Successful in 2m24s
Build and Push Containers / build-and-push-db (push) Successful in 13s

Phase 146 final verification pass complete — all green, tree untouched by me.

**Verified (final pass — both tasks already in `complete/`):**
- Scoped grep streams `{"type":"tool","name":"grep","argument":<pattern>,"path":<source/path>}`; bare grep/read/ls frames dump byte-identical (unit matrix in `test_sse_events.py` + builder pins in `test_chat_gate.py` L1364–1448)
- Tool line `🔎 Searching for <pattern> in <source/path>` (two `<code>` spans, `.grep-scope` sibling, no new emoji); bare line byte-identical (unit Node test + E2E)
- Status label "… is searching for X in Y" scoped-only; `path` persisted additively on the saved tool record and re-rendered on `/?chat=<id>` restore; pre-phase records restore byte-identically (E2E tests 2 & 3)

**Results (exact commands):**
- `uv run pytest --cov=app --cov-report=term-missing` → **3419 passed**, `app/` coverage **99%** (>90% ✓)
- `uv run pytest tests/e2e/test_search_tool_scope.py -v --no-cov` → **3 passed** in isolation ✓
- `uv run ruff check . && uv run pyright` → clean (0 errors) ✓

**Completion criteria:** all 6 ✓ (wire shape, UI line, status label, save/restore, gates, no completed-phase behavior change — full suite green includes all prior-phase pins).

**Documentation:** README.md (incl. its phase-146 section) and AGENTS.md reviewed in full against the codebase — verified consistent, no changes needed (`.env.example` ⊆ README config tables; no stale vars; phase-100 72rem rule in AGENTS.md §5 matches shipped CSS; no-CDN/smoke-DB/gpgsign claims all confirmed).

**Notable:** none — no defects found, no live-infrastructure touched, nothing committed per harness protocol.

**Next pending phase:** `147_sync_changed_only`.
This commit is contained in:
2026-10-01 12:48:04 -04:00
parent 32a3ec4f5e
commit ab3f5f685b
27 changed files with 2151 additions and 29 deletions
+24
View File
@@ -941,12 +941,36 @@ async def chat(
else None
)
limit = limit if _whole_line_number(limit) else None
# Phase 146 (task 01): a SCOPED grep's frame
# carries the scope — the re-searched
# document's combined ``source/path`` (the
# grep's optional one-doc argument, PLAN §6)
# — as the additive ``path`` field, so the
# UI can show WHERE the search runs (the
# "looks like the LLM is repeating itself"
# confusion, TODO L3). ONLY ``grep`` reads it
# (a non-grep ``path`` argument is that
# tool's ``argument``, never a scope); the
# same string guard as ``argument`` — a
# non-string value is a model error the agent
# already refuses; the guard just yields
# None → the key is omitted on
# serialization, so every non-scoped frame
# keeps its exact pre-phase wire bytes (the
# ChatToolEvent wrap serializer).
scope = (
piece.arguments.get("path")
if piece.name == "grep"
else None
)
scope = scope if isinstance(scope, str) else None
yield sse_event(
ChatToolEvent(
name=piece.name,
argument=argument,
offset=offset,
limit=limit,
path=scope,
).model_dump()
)
continue