phase: 158_thinking_timeline
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:
@@ -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))
|
||||
|
||||
Reference in New Issue
Block a user