v0.8.6
The loop guard finally works, and the release script can no longer lie about what it bumped.
Fixed
- The planning gate now fires on silence, not on volume of work. It keyed on
session.toolCallsSinceLastText— the right quantity — but nothing ever reset it: the only code that zeroed it on assistant text (engine/hooks/llm-output.ts) is imported solely by a test and is not among the hook methods the daemon registers, and Claude Code's hook surface has no assistant-text event at all. So it trackedtoolCallCountexactly and interrupted long, legitimate investigations to "summarize progress". The count is now derived from the transcript (countToolCallsSinceText) rather than tracked in memory, which also makes it correct across daemon restarts, relay reconnects and session-map eviction. Two structural facts were verified against a live transcript rather than assumed: Claude Code writes each content block as its own JSONL entry, and the entry carrying atool_useis flushed before the tool runs.thinkingblocks deliberately do not reset the count (thinking happens inside loops too), andtool_resultblocks — which arrive asuser-typed entries — do not either, since treating them as user input would reset on every call and disable the gate. Fails open: an unreadable transcript keeps the gate quiet. Measured live mid-session: old predicate 26 (fires), new predicate 2 (quiet). Closes #20. scripts/bump-version.shmissed surfaces and printed a green summary anyway.package-lock.json(both refs) andsrc/mcp-server.tswere never bumped, and its staleness check only verified surfaces it already knew about — self-fulfilling, since it cannot discover a surface nobody wired in. v0.8.5 shipped with two stale surfaces because of this. The script now bumps all seven surfaces (package-lock vianode, never a blanketsedthat would corrupt dependency pins), verifies each explicitly, and then sweeps the repo for the old version, failing on anything still carrying it — so an unknown surface breaks the run instead of passing quietly.bump-version.shno longer touches git by default. It rangit add -A && git commit && git tagunconditionally, which on v0.8.5 swept ten unrelated fixes intochore: bump to v0.8.5and violated QA-BEFORE-BUMP.--commitand--tagare now opt-in, and--commitrefuses when the working tree holds changes outside the version surfaces. Adds--skip-tests(the README badge ran the full suite on every bump) and semver validation of the argument.
Added
test/loop-guard-transcript.test.ts(16) — synthetic transcripts, no database, runs in CI. Verified fail-on-revert.test/tier1-legacy-migration.test.ts(2) — exercises the v0.8.5 legacy tier-1 migration against a live throwaway database, becausestring::starts_withthrows on a NONEsession_idandqueryExecswallows it, so a missing guard would make the migration a silent no-op that still reports success.