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
+17 -6
View File
@@ -62,7 +62,10 @@ def test_tool_branch_is_a_first_class_turn_branch() -> None:
assert "uiState === UI_STATE.thinking" in branch, (
"label updates only while the state is still thinking"
)
assert "appendToolLine(wrap, name, argument, frameOffset, frameLimit)" in branch
assert (
"appendToolLine(wrap, name, argument, frameOffset, frameLimit, framePath)"
in branch
)
# No setUiState in the branch: the state stays "thinking" (never stale).
assert "setUiState" not in branch, (
"the tool branch must keep uiState=thinking (button stays disabled)"
@@ -98,9 +101,15 @@ def test_calling_tool_label_strings() -> None:
"the read status requires the name (new or legacy) AND a string argument"
)
# The search status — new + legacy name, sitting BETWEEN the read
# branch and the listing fallback in the ternary.
# branch and the listing fallback in the ternary. Phase 146
# (task 02): the label gains the additive scope — `` in <path>``
# ONLY when the frame carried one (a bare grep renders the exact
# pre-phase label — the template's empty-string fallback).
assert 'name === "grep" || name === "search_documents") && argument' in branch
assert "`${brand()} is searching for ${argument}`" in branch
assert (
"`${brand()} is searching for ${argument}"
"${framePath ? ` in ${framePath}` : \"\"}`" in branch
)
# Phase 70: the scoped ls status mirrors the scoped tool line; the
# unscoped listing stays the final fallback.
assert 'name === "ls" && argument' in branch
@@ -227,9 +236,11 @@ def test_tool_lines_re_render_on_restore() -> None:
end = js.find("function restoreConversation")
body = js[fn:end]
assert "Array.isArray(m.tools)" in body
assert "appendToolLine(wrap, t.name, arg, tOffset, tLimit)" in body, (
"phase 139: the stored range rides the SAME helper — a pre-phase"
" record (no fields) renders byte-identical"
assert (
"appendToolLine(wrap, t.name, arg, tOffset, tLimit, tPath)" in body
), (
"phase 139: the stored range + the phase-146 stored scope ride the "
"SAME helper — a pre-phase record (no fields) renders byte-identical"
)
thinking_restore = body.find("if (m.thinking)")
tools_restore = body.find("Array.isArray(m.tools)")