phase: 146_search_tool_scope
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:
@@ -167,6 +167,69 @@ def test_tool_event_range_fields_absent_not_null_when_omitted() -> None:
|
||||
assert set(dumped.keys()) == {"type", "name", "argument"}, dumped
|
||||
|
||||
|
||||
def test_tool_frame_scoped_grep_carries_path() -> None:
|
||||
"""Phase 146 (task 01): a SCOPED grep's frame carries the scope —
|
||||
the re-searched document's combined ``source/path`` — as the
|
||||
additive ``path`` field (the ONLY case the field is present on the
|
||||
wire), AFTER ``argument`` (the model's ``pattern``)."""
|
||||
frame = sse_event(
|
||||
ChatToolEvent(name="grep", argument="pattern", path="srcs/doc.md").model_dump()
|
||||
)
|
||||
assert frame == (
|
||||
'data: {"type": "tool", "name": "grep", "argument": "pattern", '
|
||||
'"path": "srcs/doc.md"}\n\n'
|
||||
)
|
||||
assert _payload(frame) == {
|
||||
"type": "tool",
|
||||
"name": "grep",
|
||||
"argument": "pattern",
|
||||
"path": "srcs/doc.md",
|
||||
}
|
||||
|
||||
|
||||
def test_tool_frame_bare_grep_has_no_path_key() -> None:
|
||||
"""Phase 146 (task 01): a bare grep's frame has NO ``path`` key at
|
||||
all — ABSENT, never ``null`` (the omission pattern; a ``null`` value
|
||||
would change the pre-phase wire bytes)."""
|
||||
dumped = ChatToolEvent(name="grep", argument="pattern").model_dump()
|
||||
assert "path" not in dumped
|
||||
assert dumped == {"type": "tool", "name": "grep", "argument": "pattern"}
|
||||
|
||||
|
||||
def test_tool_frame_non_scoped_tools_dump_the_exact_pre_phase_shape() -> None:
|
||||
"""Phase 146 (task 01): every frame that is not a scoped grep —
|
||||
``read`` with and without a range, ``ls`` with and without an
|
||||
argument — dumps to the EXACT pre-phase dict (the equality pins:
|
||||
byte-identity of the non-scoped frames is the point of the
|
||||
additive-omission serializer). A non-string ``path`` never reaches
|
||||
the model at all: the frame builder (``app.api.chat``) string-guards
|
||||
it to ``None`` before construction (pinned at the builder level in
|
||||
``tests/unit/test_chat_gate.py``)."""
|
||||
assert (
|
||||
ChatToolEvent(name="read", argument="S/p.md").model_dump()
|
||||
== {"type": "tool", "name": "read", "argument": "S/p.md"}
|
||||
)
|
||||
assert (
|
||||
ChatToolEvent(name="read", argument="S/p.md", offset=1500, limit=30).model_dump()
|
||||
== {
|
||||
"type": "tool",
|
||||
"name": "read",
|
||||
"argument": "S/p.md",
|
||||
"offset": 1500,
|
||||
"limit": 30,
|
||||
}
|
||||
)
|
||||
assert ChatToolEvent(name="ls").model_dump() == {
|
||||
"type": "tool",
|
||||
"name": "ls",
|
||||
"argument": None,
|
||||
}
|
||||
assert (
|
||||
ChatToolEvent(name="ls", argument="Homelab").model_dump()
|
||||
== {"type": "tool", "name": "ls", "argument": "Homelab"}
|
||||
)
|
||||
|
||||
|
||||
def test_retry_frame_serializes_exactly() -> None:
|
||||
"""Phase 67: the ``retry`` frame is exactly ``{type: "retry",
|
||||
attempt: int, max_attempts: int}`` — a transient status the client's
|
||||
|
||||
Reference in New Issue
Block a user