phase: 158_thinking_timeline
Build and Push Containers / build-and-push-app (push) Successful in 8m38s
Build and Push Containers / build-and-push-db (push) Successful in 32s

All verification complete. Every gate is green and the docs check out. Final report:

## Phase 158 final verification pass — COMPLETE

**Verified (all tasks pre-completed; this pass re-ran the full gate):**
- Unit + integration: `uv run pytest --cov=app --cov-report=term-missing` → **3618 passed, 99% coverage on `app/`** (>90% gate)
- Phase E2E in isolation: `uv run pytest tests/e2e/test_thinking_timeline.py -v --no-cov` → **6/6 passed** (order pin, fold-at-rest, live-open, restore+`segments` array, shared page, no-thinking single disclosure)
- Regression E2E, each in isolation: `test_thinking_display` 5✓, `test_tool_call_compact` 4✓, `test_turn_progress_loader` 3✓ (per-run pin updated), `test_big_read_progress` 4✓, `test_agent_document_tools` 4✓, `test_share_chat` 4✓, `test_chat_persistence` 4✓, `test_smoke` 3✓
- Lint/types: `uv run ruff check . && uv run pyright` → **clean (0 errors)**

**Completion criteria:** all 9 met — timeline E2E pins ✓, restore/shared `segments` ✓, no-thinking single-disclosure + the three UNCHANGED regression suites green ✓, pre-phase byte-identical round-trip (integration pin: key absent, never null; >50 / >32 000 / empty text / unknown kind all 422) ✓, full suite + coverage + E2E + lint ✓, `git diff --stat` footprint limited to `app/schemas.py`, `frontend/assets/{app,shared}.js`, `tests/`, README, phase files (a pre-existing owner `TODO.md` edit is out of scope, not staged by the phase commit) ✓, D15→per-run supersession recorded in the updated pin ✓. (Commit + phase move are the harness's job; task dirs already in `complete/`.)

**Documentation:** README.md — Thinking section correctly describes the phase-158 timeline; config reference matches `.env.example` exactly; page table matches registered routes; no stale single-block wording. AGENTS.md — rules and quick-reference commands all accurate. **Verified consistent — no changes needed.**

**Decisions/deviations:** none. `test_tool_call_compact.py` carries a one-line docstring-only update (referenced the removed `closeToolCalls`); its test logic is byte-identical to the phase-117 contract and green.

**Next pending phase:** none — `todo/` contains only 158.
This commit is contained in:
2026-10-05 02:52:55 -04:00
parent 3f752fb77f
commit 31bd903a93
49 changed files with 4280 additions and 482 deletions
+53 -35
View File
@@ -89,16 +89,20 @@ def _run_node(script: str) -> str:
def test_append_tool_line_signature_takes_optional_scope() -> None:
"""appendToolLine gains the additive ``scope`` param (fifth,
default ``null`` — a pre-phase 5-arg call renders byte-identical),
and the grep arm appends the scope span through the pinned
structure (a ``.grep-scope`` span SIBLING after the pattern
``<code>`` — the phase-139 read-range-suffix precedent)."""
"""appendToolLine takes the CONTAINER as its first param (phase
158, D1 — the caller passes the run's disclosure; the
single-container derivation is gone) + the additive ``scope``
param (fifth, default ``null`` — a pre-phase 5-arg call renders
byte-identical), and the grep arm appends the scope span through
the pinned structure (a ``.grep-scope`` span SIBLING after the
pattern ``<code>`` — the phase-139 read-range-suffix
precedent)."""
js = _js()
assert (
"function appendToolLine(wrap, name, argument, offset = null, "
"function appendToolLine(container, name, argument, offset = null, "
"limit = null, scope = null)" in js
), "the signature carries the additive scope (default null)"
), ("the signature carries the container + the additive scope "
"(default null)")
body = _extract(js, "appendToolLine")
assert "if (typeof scope === \"string\" && scope)" in body, (
"the span is appended ONLY when the scope is a non-empty string"
@@ -184,8 +188,8 @@ def test_live_tool_branch_reads_the_scope_with_the_guard() -> None:
assert "toolAcc.push(toolRecord)" in branch, "every tool frame is recorded"
# The scope rides the SAME helper (the line's fifth argument).
assert (
"appendToolLine(wrap, name, argument, frameOffset, frameLimit, "
"framePath)" in branch
"const line = appendToolLine(" in branch
and "armToolLineClock(wrap, line)" in branch
)
@@ -229,8 +233,9 @@ def test_restore_path_passes_the_stored_scope_through() -> None:
"typeof t.path === \"string\" && t.path ? t.path : null" in body
), "the stored scope reads through the house string guard"
assert (
"appendToolLine(wrap, t.name, arg, tOffset, tLimit, tPath)" in body
), "the stored scope rides the SAME helper"
"appendToolLine(restoreDisc, t.name, arg, tOffset, tLimit, tPath)" in body
), ("the stored scope rides the SAME helper (the caller passes the "
"run's disclosure — phase 158)")
# --------------------------------------------------------------------------
@@ -380,6 +385,19 @@ function makeWrap() {
wrap.appendChild(body);
return wrap;
}
function makeDisclosure() {
const container = document.createElement("details");
container.className = "tool-calls-disclosure";
const summary = document.createElement("summary");
summary.className = "tool-calls-summary";
container.appendChild(summary);
const listEl = document.createElement("div");
listEl.className = "tool-calls";
container.appendChild(listEl);
const wrap = makeWrap();
wrap.querySelector(".msg-body").appendChild(container);
return { wrap, container };
}
function describe(el) {
return {
text: el.textContent,
@@ -418,24 +436,24 @@ def test_scoped_grep_line_and_bare_byte_identity_under_node() -> None:
+ '\nconst PATTERN = "reese-sentinel-42";\n'
+ 'const SCOPE = "search_docs/reese-notes.md";\n'
# bare grep (no scope)
+ "const w0 = makeWrap();\n"
+ 'appendToolLine(w0, "grep", PATTERN);\n'
+ 'const bare = describe(w0.querySelector(".tool-call"));\n'
+ "const a0 = makeDisclosure();\n"
+ 'appendToolLine(a0.container, "grep", PATTERN);\n'
+ 'const bare = describe(a0.wrap.querySelector(".tool-call"));\n'
# bare grep, legacy name (no scope)
+ "const w1 = makeWrap();\n"
+ 'appendToolLine(w1, "search_documents", PATTERN);\n'
+ 'const legacy = describe(w1.querySelector(".tool-call"));\n'
+ "const a1 = makeDisclosure();\n"
+ 'appendToolLine(a1.container, "search_documents", PATTERN);\n'
+ 'const legacy = describe(a1.wrap.querySelector(".tool-call"));\n'
# guarded fallbacks: empty string + non-string scope
+ "const w2 = makeWrap();\n"
+ 'appendToolLine(w2, "grep", PATTERN, null, null, "");\n'
+ 'const emptyScope = describe(w2.querySelector(".tool-call"));\n'
+ "const w3 = makeWrap();\n"
+ 'appendToolLine(w3, "grep", PATTERN, null, null, 42);\n'
+ 'const badScope = describe(w3.querySelector(".tool-call"));\n'
+ "const a2 = makeDisclosure();\n"
+ 'appendToolLine(a2.container, "grep", PATTERN, null, null, "");\n'
+ 'const emptyScope = describe(a2.wrap.querySelector(".tool-call"));\n'
+ "const a3 = makeDisclosure();\n"
+ 'appendToolLine(a3.container, "grep", PATTERN, null, null, 42);\n'
+ 'const badScope = describe(a3.wrap.querySelector(".tool-call"));\n'
# the scoped grep
+ "const w4 = makeWrap();\n"
+ 'appendToolLine(w4, "grep", PATTERN, null, null, SCOPE);\n'
+ 'const scoped = describe(w4.querySelector(".tool-call"));\n'
+ "const a4 = makeDisclosure();\n"
+ 'appendToolLine(a4.container, "grep", PATTERN, null, null, SCOPE);\n'
+ 'const scoped = describe(a4.wrap.querySelector(".tool-call"));\n'
"console.log(JSON.stringify("
"{ bare, legacy, emptyScope, badScope, scoped }));"
)
@@ -511,15 +529,15 @@ def test_non_grep_arms_ignoring_the_scope_stay_byte_identical() -> None:
+ range_suffix
+ "\n"
+ append
+ "const w0 = makeWrap();\n"
+ 'appendToolLine(w0, "read", "s/d.md", null, null, "x");\n'
+ 'const readLine = describe(w0.querySelector(".tool-call"));\n'
+ "const w1 = makeWrap();\n"
+ 'appendToolLine(w1, "ls", "s", null, null, "x");\n'
+ 'const lsLine = describe(w1.querySelector(".tool-call"));\n'
+ "const w2 = makeWrap();\n"
+ 'appendToolLine(w2, "ls", null, null, null, "x");\n'
+ 'const plainLine = describe(w2.querySelector(".tool-call"));\n'
+ "const a0 = makeDisclosure();\n"
+ 'appendToolLine(a0.container, "read", "s/d.md", null, null, "x");\n'
+ 'const readLine = describe(a0.wrap.querySelector(".tool-call"));\n'
+ "const a1 = makeDisclosure();\n"
+ 'appendToolLine(a1.container, "ls", "s", null, null, "x");\n'
+ 'const lsLine = describe(a1.wrap.querySelector(".tool-call"));\n'
+ "const a2 = makeDisclosure();\n"
+ 'appendToolLine(a2.container, "ls", null, null, null, "x");\n'
+ 'const plainLine = describe(a2.wrap.querySelector(".tool-call"));\n'
"console.log(JSON.stringify({ readLine, lsLine, plainLine }));"
)
out = json.loads(_run_node(driver))