phase: 139_read_range_tool_lines
Build and Push Containers / build-and-push-app (push) Successful in 2m54s
Build and Push Containers / build-and-push-db (push) Successful in 17s

All verification is complete and green. Phase 139's three tasks (01–03) were already implemented in prior passes; this was the final verification pass, and every gate holds.

## Phase 139 — final verification report

**Verified in working tree (no code changes needed):**
- `readRangeSuffix` single-source-of-truth helper in `app.js` + byte-identical parity copy in `shared.js` (EN-dash ` (lines N–M)`, `(from line N)`, `(first N lines)`, `Number.isInteger(x) && x >= 1` guard mirroring the server's `_whole_line_number`)
- `appendToolLine(wrap, name, argument, offset = null, limit = null)` — `<span class="read-range">` sibling after `<code>`, only when suffix non-empty; `shared.js` `addToolLines` parity
- Live `tool` branch: guarded `ev.offset`/`ev.limit`, additive `toolAcc` records (non-range keeps pre-phase shape), status label carries the suffix (A2); restore path passes stored `t.offset`/`t.limit` through, phase-95 truncation note untouched
- E2E: `SAME_FILE_TRIGGER` mock flow (same file read twice, offsets 10/20 then 40/15) + 3-test Playwright suite

**Test / lint / coverage results:**
- `uv run pytest` (unit + integration): **3283 passed, 9 skipped, 0 failed**
- `uv run pytest --cov=app --cov-report=term-missing`: **TOTAL 99%** (>90% gate)
- `uv run pytest tests/e2e/test_read_range_tool_lines.py -v --no-cov`: **3 passed** in isolation
- `uv run ruff check .`: clean; `uv run pyright`: **0 errors, 0 warnings**

**Completion criteria:**
1. Range read renders `(lines N–M)` live; two same-file reads show DIFFERENT ranges — ✅ (E2E test 1)
2. Plain read byte-identical; phase-87/117 pins + emoji guard pass unedited — ✅ (E2E test 2 + full suite)
3. Suffix survives save → restore on chat page + shared page — ✅ (E2E test 3)
4. Full suite green, coverage >90%, E2E isolated green, ruff + pyright clean — ✅
5. Commit + phase move — left to the harness per executor rules (no `git add`/`commit` run)

**Notable:** the missing pytest summary line on one run was a transient output-buffering artifact, confirmed by re-run; DB was already up and healthy.

**Next pending phase:** `140_pictorial_pdf_pages`
This commit is contained in:
2026-09-30 12:55:23 -04:00
parent 1a657ce4ca
commit 9e7262a5c8
31 changed files with 2197 additions and 25 deletions
+213
View File
@@ -603,6 +603,49 @@ Implements just enough of the aipi surface:
never hit it); verified 2026-09-28: no existing E2E question or
fixture file contains the phrase, so every other suite is
unaffected.
- user message containing ``read the same file twice``
(``SAME_FILE_TRIGGER``, phase 139 task 03 — the read
range-tool-lines' dedicated story suite
``tests/e2e/test_read_range_tool_lines.py``) **and** the system
prompt carries the ``<tools>`` section -> the owner's exact
"same file over and over" scenario (TODO.md L4), end to end: the
model range-reads the SAME document TWICE with different
``offset``/``limit``, so the two Reading lines are distinguishable.
The question carries its own target after the colon (``read the
same file twice: read source/path`` — the question-carried-call
convention of ``READ_CAP_TRIGGER`` / ``DRILL_TRIGGER``), and the
step is then discriminated statelessly by the tool results in the
messages (the house count pattern — streaming only):
* request 1 (``tools`` offered, no read results yet): a
``tool_calls`` delta — ``read`` on the parsed target with
``offset`` = ``SAME_FILE_FIRST_OFFSET`` (10) and ``limit`` =
``SAME_FILE_FIRST_LIMIT`` (20) (id ``call_0``);
* a ``tool``-role read result is in the messages (the FIRST read
landed): the SECOND range read — a ``tool_calls`` delta —
``read`` on the SAME target with ``offset`` =
``SAME_FILE_SECOND_OFFSET`` (40) and ``limit`` =
``SAME_FILE_SECOND_LIMIT`` (15) (id ``call_1``) — the same
file again, a different range;
* TWO or more ``tool``-role read results are in the messages
(both range reads landed): the forced answer, byte-stable, in
the phase-131 read-echo shape — ``Read the same file twice —
lines 10–29: <q1>; lines 40–54: <q2>`` where each ``<qN>`` is
that read result's content with the header lines (the
phase-106 D5 ``date:`` line + the phase-131 ``lines: a-b of
N`` extent) skipped, first 80 chars (the phase-94 read-echo
convention — BOTH ranges reached the model's context). NOTE:
for the second read to PROCEED (not be refused
``ALREADY_IN_CONTEXT``) the document must already be in the
agent's truncated set — the story suite boots the app under
test with a ``BOR_READ_MAX_CHARS`` small enough that the
FIRST (20-line) range slice truncates, which is exactly the
large-document premise of the owner's confusion.
Checked BEFORE the plain ``TOOLS_TRIGGER`` flow (disjoint trigger
phrases — the phase-71/72/94/118/131 ordering convention; the
trigger needs the ``<tools>`` section, so deflected turns never
hit it); verified 2026-09-30: no existing E2E question or
fixture file contains the phrase, so every other suite is
unaffected.
Failure injection (phase 67, LLM retry, TODO.md L3) — deterministic
dead-endpoint behavior for the retry E2E suite (``tests/e2e/
@@ -2283,6 +2326,121 @@ def _read_lines_flow(body: dict[str, Any]) -> tuple[str, ...] | None:
return ("grep",)
# ---------------------------------------------------------------------------
# Phase 139 (task 03): the owner's "same file over and over" scenario —
# the model range-reads the SAME document TWICE with different
# offset/limit, so the two Reading lines are distinguishable. See the
# module docstring.
# ---------------------------------------------------------------------------
#: A user message containing this substring (case-insensitive) —
#: combined with the ``<tools>`` section in the system prompt — drives
#: the deterministic SAME-FILE-TWICE flow (the read range-tool-lines'
#: story suite, ``tests/e2e/test_read_range_tool_lines.py`` — the
#: owner's "same file over and over" confusion, TODO.md L4). Checked
#: BEFORE the plain ``TOOLS_TRIGGER`` flow (disjoint trigger phrases —
#: the phase-71/72/94/118/131 ordering convention); verified
#: 2026-09-30: no existing E2E question or fixture file contains the
#: phrase, so every other suite is unaffected.
SAME_FILE_TRIGGER = "read the same file twice"
#: The scripted target in the same-file-twice question (case-
#: insensitive — the suite's question capitalizes the trigger's first
#: letter): the verb (``read``) plus the target — a combined
#: ``source/path``, parsed from the RAW user message so the target
#: keeps its case. The target is a ``[a-z0-9_./-]`` run (case-
#: insensitively), so a `` — `` flavor separator can never bleed into
#: it (the read-cap / drill-down convention, ``_READ_CAP_CALL_RE`` /
#: ``_DRILL_CALL_RE``).
_SAME_FILE_CALL_RE = re.compile(
r"read the same file twice:\s*read\s+(?P<arg>[a-z0-9_./-]+)",
re.I,
)
#: The FIRST range read's requested extent — the model's REQUEST
#: (ASSUMPTION A1: the suffix shows the request, not the actual slice
#: extent). ``offset`` 10 + ``limit`` 20 renders `` (lines 10–29)``.
SAME_FILE_FIRST_OFFSET = 10
SAME_FILE_FIRST_LIMIT = 20
#: The SECOND range read's requested extent — the SAME file again, a
#: different range (the owner's confusion made visible). ``offset`` 40
#: + ``limit`` 15 renders `` (lines 40–54)``.
SAME_FILE_SECOND_OFFSET = 40
SAME_FILE_SECOND_LIMIT = 15
def _same_file_quote(doc_content: str) -> str:
"""The phase-94/131 read-echo quote of ONE read result's content.
The read result's header lines sit between the ``Document …:``
header and the document text — the phase-106 D5 ``date:`` line
always, the phase-131 ``lines: a-b of N`` extent on a range read
(a plain read carries no extent line) — skipped (the drill-flow
convention) so the quote is the pure read content, first 80 chars
(the RANGE reached the model's context). Mirrors the phase-131
``_read_lines_flow`` quote logic so the two flows' echo shapes
stay byte-consistent.
"""
content = doc_content
if content.startswith("date: "):
content = content.partition("\n")[2]
if content.startswith("lines: "):
content = content.partition("\n")[2]
return content[:80]
def _same_file_flow(body: dict[str, Any]) -> tuple[str, ...] | None:
"""Classify a phase-139 same-file-twice request (see the module
docstring). The question carries its own target (``read the same
file twice: read source/path``); the step is then discriminated
statelessly by the READ results in the messages (the house count
pattern — the flow only ever issues ``read`` calls, so counting
read results is reliable):
* ``("answer", quote1, quote2)`` — TWO or more ``tool``-role read
results are in the messages (both range reads landed): the
forced answer, byte-stable, in the phase-131 read-echo shape —
``Read the same file twice — lines 10–29: <q1>; lines 40–54:
<q2>`` where each ``<qN>`` is that read result's pure content
(header lines skipped, first 80 chars — the :func:
``_same_file_quote`` convention).
* ``("read2", target)`` — EXACTLY ONE read result is in the
messages (the first read landed): the SECOND range read on the
SAME target — ``offset`` = :data:`SAME_FILE_SECOND_OFFSET`,
``limit`` = :data:`SAME_FILE_SECOND_LIMIT` (id ``call_1``).
* ``("read1", target)`` — ``tools`` are offered and no read result
is in the messages yet: the FIRST range read on the parsed
target — ``offset`` = :data:`SAME_FILE_FIRST_OFFSET`, ``limit``
= :data:`SAME_FILE_FIRST_LIMIT` (id ``call_0``).
* ``None`` — not the flow: the trigger is absent, the ``<tools>``
section is missing (deflected turns never carry it), the
target is unparseable, or ``tools`` are not offered and no read
result is in the messages yet (e.g. ``agent_max_rounds=0``).
"""
user = _user(body)
if SAME_FILE_TRIGGER not in user.lower():
return None
if "<tools>" not in _system(body):
return None
match = _SAME_FILE_CALL_RE.search(user)
if match is None:
return None
target = match.group("arg")
reads = _read_results(body)
if len(reads) >= 2:
return (
"answer",
_same_file_quote(reads[0][1]),
_same_file_quote(reads[1][1]),
)
if len(reads) == 1:
return ("read2", target)
if not body.get("tools"):
return None
return ("read1", target)
def long_answer() -> str:
"""~900-word deterministic walkthrough (phase 11): numbered steps plus
a unique final line that must survive the stream untruncated."""
@@ -3525,6 +3683,61 @@ def chat_completions(body: dict[str, Any]) -> Any:
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
# Phase 139 (task 03): the owner's "same file over and over"
# scenario (the read range-tool-lines' story suite,
# tests/e2e/test_read_range_tool_lines.py — TODO.md L4): the
# model range-reads the SAME document TWICE with different
# offset/limit, so the two Reading lines are distinguishable.
# Checked BEFORE the plain TOOLS_TRIGGER flow (disjoint trigger
# phrase — the phase-71/72/94/118/131 ordering convention; the
# trigger needs the ``<tools>`` section, so deflected turns
# never hit it).
same_file = _same_file_flow(body)
if same_file is not None:
if same_file[0] == "read1":
# The FIRST range read on the parsed target — the
# model's REQUEST (ASSUMPTION A1: the suffix shows the
# request, not the actual slice extent).
stream = _tool_call_stream(
"read",
{
"path": same_file[1],
"offset": SAME_FILE_FIRST_OFFSET,
"limit": SAME_FILE_FIRST_LIMIT,
},
"call_0",
)
elif same_file[0] == "read2":
# The SECOND range read — the SAME file again, a
# different range (the owner's confusion made visible).
stream = _tool_call_stream(
"read",
{
"path": same_file[1],
"offset": SAME_FILE_SECOND_OFFSET,
"limit": SAME_FILE_SECOND_LIMIT,
},
"call_1",
)
else: # "answer" — the forced read-echo of BOTH ranges
stream = _sse_stream(
_apply_max_tokens(
f"Read the same file twice — lines "
f"{SAME_FILE_FIRST_OFFSET}–"
f"{SAME_FILE_FIRST_OFFSET + SAME_FILE_FIRST_LIMIT - 1}: "
f"{same_file[1]}; lines "
f"{SAME_FILE_SECOND_OFFSET}–"
f"{SAME_FILE_SECOND_OFFSET + SAME_FILE_SECOND_LIMIT - 1}: "
f"{same_file[2]}",
body.get("max_tokens"),
),
0.0,
)
return StreamingResponse(
stream,
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
)
# Phase 124 (task 07, image chips and answers, TODO L3): the
# scripted SHOW_IMAGE flow — the mock shows the seeded image
# document (the ``<documents>`` block with the image-