phase: 131_read_line_ranges
All 44 progress lines are pure dots, final line at 100%, exit 0. Everything is verified. Final report: **Phase 131 — final verification pass (all 4 tasks already complete in working tree)** - **Verified implemented:** `read(offset, limit)` schema + teaching descriptions; rewritten truncation notice; full 6-row dedupe matrix + range validation + `lines: a-b of N` header + slice cap in `_execute_tool`; wire-level `offset`/`limit` on `ChatToolEvent` (absent keys dropped → non-range frames byte-identical); mock-LLM `read the lines` flow; handbook repro (wording + line numbers derived from the real PDF at test time) - **Tests:** `uv run pytest --no-cov` → 3097 passed, exit 0; `uv run pytest --cov=app` → **99% on app/** (>90%); `uv run pytest tests/e2e/test_read_line_ranges.py -v --no-cov` → 2/2 in isolation (47.6 s); `tests/integration/test_read_line_ranges_handbook.py` → 2/2 (3.5 s); 10-file targeted E2E batch pinning pre-phase surfaces (grep/ls/plain-read/ALREADY_IN_CONTEXT/truncation) → 31/31; `ruff check .` clean; `pyright` 0 errors - **Completion criteria:** all 5 green — (1) exact range lines + `lines:` header, plain reads byte-identical (existing pins pass unchanged) ✅ (2) truncated-then-range succeeds end-to-end on the real handbook; other re-reads get teaching refusals ✅ (3) find-traps answered from real PDF text, no hardcoded wording ✅ (4) coverage + isolated E2E ✅ (5) no behavior change in completed phases ✅ - **Docs:** README + AGENTS reviewed in full. Fixed 3 inconsistencies: README missing `BOR_HISTORY_MAX_TURNS`/`BOR_HISTORY_MAX_CHARS` rows (pre-existing phase-74 gap — env-tunable, unit-pinned, undocumented); `.env.example` missing the same two vars (added); `.env.example` stale `BOR_READ_MAX_CHARS` comment (still said "grep-pointer notice (phase 95)" — updated to the phase-131 workflow notice). AGENTS.md verified consistent — no changes needed. All 67 `.env.example` vars documented, all defaults match `app/config.py`, pages/auth tables match live routes - **Note:** PLAN.md §7.1 still states the retired 46rem chat column (superseded by phase 100, which AGENTS.md rule 5 records correctly) — PLAN.md is off-limits for this pass, flagged only - **Next pending phase:** `132_image_upload_feedback`
This commit is contained in:
@@ -197,11 +197,39 @@ class ChatToolEvent(BaseModel):
|
||||
``done`` shapes are unchanged — the read document is reflected in
|
||||
``done.sources`` instead (a grep adds no source: it is a locator,
|
||||
locked A5).
|
||||
|
||||
Phase 131 (task 04): ``offset`` / ``limit`` — the LINE RANGE a
|
||||
``read`` call requested (the optional 1-based first line + line
|
||||
count, PLAN §6's phase-131 revision). Additive in the
|
||||
:class:`ChatDoneEvent.answer_image` style: absent (``None``) on
|
||||
every non-range call — ``ls`` / ``grep`` / plain ``read``, and
|
||||
every pre-phase frame — where BOTH keys are DROPPED on
|
||||
serialization (ABSENT, never ``null``), so those frames stay
|
||||
byte-identical to pre-phase (the story E2E suites pin them as
|
||||
exact dicts). Only a valid range read carries them (a bad range
|
||||
value is a refused call — the refusal surfaces as the round's
|
||||
result, and the frame carries no range). The client ignores the
|
||||
extra fields (PLAN §4); they exist so the wire — and the phase-131
|
||||
story E2E — can assert the range the model requested.
|
||||
"""
|
||||
|
||||
type: Literal["tool"] = "tool"
|
||||
name: str # "ls" | "read" | "grep" (whatever AGENT_TOOLS names)
|
||||
argument: str | None = None # the single string argument passed, or null
|
||||
offset: int | None = None # read only: the 1-based first line requested
|
||||
limit: int | None = None # read only: the line count requested
|
||||
|
||||
@model_serializer(mode="wrap")
|
||||
def _serialize(self, handler: SerializerFunctionWrapHandler) -> Any:
|
||||
data = handler(self)
|
||||
# Absent range → ABSENT keys (never null): the non-range frame
|
||||
# stays byte-identical to pre-phase-131 (the omission pattern
|
||||
# of SourceRef.image_url / ChatDoneEvent.answer_image).
|
||||
if self.offset is None:
|
||||
data.pop("offset", None)
|
||||
if self.limit is None:
|
||||
data.pop("limit", None)
|
||||
return data
|
||||
|
||||
|
||||
class ChatToolResultEvent(BaseModel):
|
||||
|
||||
Reference in New Issue
Block a user