Repository navigation
v5.0.0
Migration from 4.x: v5 migration guide. The major version reflects the changed readCurrentStatus() return contract and the now-enabled default close-time discovery scan.
Added
-
orchestrator.issue_budget.refundedevent (#1353) —hooks/post-bash-issue-budget-refund.mjsemitted only a stderr line, which under exit 0 reaches the debug log alone, so refunds were uncountable after the fact (HR-105). The event fires once per refund DECISION including the no-op branches, so a census has a denominator;reasonis a closed enum, and the payload carries no command text, issue title or path. Lazily imported, awaited and caught — a failing emit can never change the hook's exit code. -
scripts/lib/git-porcelain.mjs(#1355) — onegit status --porcelain -zparser for the three consumers that each had their own (quality-gate.mjs's private copy,project-hygiene.mjs's inline loop,pre-dispatch-check.mjs's hand-written unquoting). Malformed-entry drop and theR/Crename double-field rule now live in one place. -
sizingSubject()+session_profileon the session record (#1247) — see below. -
Time-bounded operations route in
session-start: explicit deadline, scoped accounts and products, rolling queue, a single publisher, and provider read-back evidence. Uses the current harness; installs no scheduler or new receipt engine. -
withFileLock(lockPath, fn, { onRelease })inscripts/lib/file-lock.mjs(#1336): an optional, throw-safe callback receives thereleaseFileLockresult; the return shape ofwithFileLockis unchanged.scripts/lib/vault-status/board-lock.mjsforwards it asonReleaseOutcome, andorchestrator.vault.board_writtennow carrieslock.release: 'not-owner' | 'busy'when the lease expired mid-write or the release gave up. -
checkCiStatus({ sha })inscripts/lib/ci-status-banner.mjs(#1332): an optional full 40/64-hex commit id (validated before any CLI spawns) replaces the internalgetHeadShafor GitLab and GitHub; without it behaviour is unchanged. Theci-statussession-start probe uses it through a new optional registryfollowUpstep: onno-pipeline-for-head-sha/no-check-runs-for-head, and only behindSO_PROBES_INCLUDE_NETWORK, it renders the pushed commit's verdict (last pushed: <sha8> — CI <status> (#<pipeline>)). The follow-up runs inside the probe's own budget race; if it throws or overruns, the HEAD reading already delivered stands (follow_up: 'threw' | 'budget-exceeded'inorchestrator.probes.completed). The GitLab re-query is skipped when the pushed SHA equals HEAD. -
isCoordinatorDirectHousekeeping(record)inscripts/lib/session-schema/filters.mjs(#1321) — the one definition of the coordinator-direct housekeeping record shape (every waverole: 'Housekeeping'withcoordinator_direct: true), used by the eval engine and cited by the session-end writer rule and/evolve analyze. -
Budget pre-flight in the reconcile writer (
scripts/lib/reconcile/writer.mjs, #1316): before writing,writeApprovedRulesprojects the instruction budget with the guard's owncomputeInstructionBudgeton a temporary copy of.claude/rules/plus the pending writes. If an axis would end over its ceiling and the batch grows it, nothing is written, no candidate is stamped, and the result carriesok: false, reason: 'instruction-budget-exceeded'(BUDGET_REFUSAL_REASON) with a hint to consolidate into a thematic file.tests/rules/receiving-review.test.mjsstays the second safety net. -
Campaign film embedded on
site/index.htmlandsite/de/index.html, inside the#loopsection (#1306):<figure class="fig film">withposter,preload="none", visible controls (the film carries sound, unlike the silent hero loop), no autoplay, the webm source before the mp4 source, and an AI-disclosurefigcaption.site/assets/ui.js's hero-loop<video>now reads its filename from that element'sdata-video-baseattribute instead of a hardcodedagent-production-loopliteral — needed once a second<video>exists on the same page. -
claude-md-drift-checkCheck 9 (rule-scoping) gained a sixth probe,fleet-intent-glob, and a third JSON output category,notes[]: a rule can declare aglobs:pattern as intentionally zero-match here via its ownfleet-intent-globs:frontmatter key, and the checker reports it innotes[]instead of warning on it..claude/rules/testing.mdnow declares this for**/*Tests*(#445) — on 2026-09-09 the unqualified zero-match warning had led an agent to delete that pattern (reverted only because a pinning test caught it). Measured against the live rule corpus: 37 warnings → 36 warnings + 1 note (total findings unchanged). -
scripts/lib/scope-echo.mjs+ CLI +orchestrator.wave_dispatch.scope_echo_checkedevent (#1092) — soft receive-side signal that a dispatched agent echoed its declared file-scope back in its report; a named ceiling, never blocking. Additiveapplicable: false+reason: 'scope-empty'payload keys fire for an EMPTY declared scope, so consumers filter that never-instructed population out of the echo rate; the CLI gained--helpand now exits 1 with a stderr WARN on an unreadable scope file instead of failing silently. -
native_source+resume_linkagefields onorchestrator.session.started(#1091) — measurement only, answers whether Claude Code preserves the rawsession_idacross a nativeresumeinstead of guessing. -
references/splits of thebootstrap/discovery/evolve/session-plan/wave-executorSKILL.md bodies (#1246) — byte-identical extraction; line counts 641→444, 571→404, 720→264, 620→478, 541→414. -
The optional tmux agent-status pane (Pane 5, #565) renders through that reader instead of
jq-ing the cache file (#1342):buildStatusPaneRenderCommand()inscripts/lib/tmux-layout/layouts.mjsis exported so a test can run the render once instead of the 2 s poll loop, and its header line carriessource=…,at=…plus TEXT markers (⚠,STALE,DEGRADED: <reasons>), never colour alone. Onlysource=absent(no ledger, no cache yet — the normal fresh-repo state) is deliberately unmarked (host-resources.mdHR-101); everydegradedreason on any other source rendersDEGRADED:. -
hooks/post-bash-issue-budget-refund.mjs(PostToolUseFailure, wired inhooks/hooks.jsonandhooks/hooks-cursor.json) plusrefundBooking()inscripts/lib/issue-budget.mjs(#1347): agh/glab issue createthat fails gives its budget slot back, so the retry does not spend a second one ofissue-budget.max-per-session. A refund requires PROOF OF CHARGE:chargeIssueBudget(called byhooks/pre-bash-issue-budget.mjswith the payload'stool_use_id+ statement index) records every counter-incrementing charge in the persistedcharged[]ledger ({id, key, unit, at},bookingId()=<tool_use_id>#<index>,bookingKey()= hash ofsession|command|index,CHARGED_LEDGER_MAX= 64, eviction fail-closed) andrefundBookingrefunds only a matched record, removes it, and decrements the unit it names (countorexempt). A create the pre-hook PARKED (#1314) or blocked was never charged and is never refunded, however often it is retried;overflow[]is never touched;count/exemptnever go below 0. The hook refunds only when the failure is attributable to the create itself —statementsCoverWholeCommand()(hooks/_lib/vcs-create-matcher.mjs) must hold, soglab issue create … && falseischain-not-attributableand refunds nothing (the review's cap-drain exploit).isFailure()reads fields only (exit_code !== 0, non-emptyerror,is_errorat top level or intool_response), never the bare event name; on the Cursor bridge (scripts/lib/cursor-hook-bridge.mjs) the only reachable failure field is top-levelis_error, and a failure event without any field is a loud no-op (stderr WARN). -
hooks/enforce-scope.mjsGate 5c (#1295): this repo's Claude Code auto-memory directory (~/.claude/projects/<encoded-repo-path>/memory/, encoded via the one repo encoderencodeProjectDir()) is always writable — for EVERY agent of the session, coordinator and dispatched subagents alike, because the hook cannot distinguish them (tracked as #1352, priority::high) — evaluated inside Gate 6's out-of-root branch. A sibling repo's memory directory and every other out-of-repo path stay denied, and each allow emits oneorchestrator.scope.memory_dir_allowedevent so the carveout is countable. Before this, every wave manifest blocked the coordinator's own memory writes until it was torn down (six sessions in a consumer repo re-documented the same workaround, bewerbungs-assistent#307).
Changed
-
hooks/enforce-scope.mjsclassifyCallerdistinguishes a PRESENT-but-unusableagent_id(number, object, array, blank string) as'malformed'rather than folding it into'absent'(#1352, review follow-up). The fail-open posture is unchanged — fail-closed would deny the coordinator's own/closememory writes — but the ledger can now tell a marker that arrived broken from a genuine main-thread call, which is what the HR-105 countability claim rested on. -
The
orchestrator.issue_budget.refundedemits now all sit behind themode: offcheck (#1353, review follow-up). Two of the four fired before it and so contradicted the contract written intodocs/events-schema.mdin the same change: a repo with the feature off contributed records to a census it can never supply a numerator for. Three enum values (no-signal,not-charged,unit: 'exempt') were emitted, reached by the suite and asserted by nothing — each is now pinned by a test proven red under mutation. -
scripts/emit-session.mjsadoptssession_profileonly when the STATE.md frontmattersessionmatches the record'ssession_id(#1247, review follow-up), and omits the field with a stderr WARN otherwise. Without the check, two sessions sharing one working copy cross-contaminate: a plaindeepclose reads a concurrentultradeepsession's STATE.md and files its wave numbers under the ultradeep sizing row — the contamination #1247 exists to remove, inverted. The same block is now fail-safe: an unreadable STATE.md (EISDIR/EACCES) previously escaped to the top-level handler and exited 2 having appended NO record at all, letting an optional enrichment read decide whether the ledger got a line. -
claude-md-drift-checkCheck 6's unknown-local-key warning states only what it measured (#1356, review follow-up): the key's absence from the template's two## Session Configblocks, that population named inline. It previously also claimed the key was documented nowhere and that nothing read it — both refuted by measurement for all three keys it fires on, each of which is documented in the template's own sections and read at runtime. -
hooks/enforce-scope.mjsGate 5c now grants the auto-memory carve-out to the COORDINATOR only (#1352). The discriminator is the PreToolUse payload'sagent_id, measured against the shipped Claude Code binary rather than assumed — its own schema states the field is present only inside a subagent and absent on the main thread. A dispatched wave agent (including Discovery underallowedPaths: []) now falls through to the normal gates, which denies it. Ambiguity stays fail-OPEN — fail-closed would deny the coordinator's own/closememory writes — and rides on the existingorchestrator.scope.memory_dir_allowedevent asdiscriminator, so the fail-open case is countable. -
scripts/lib/pre-dispatch-check.mjsreadsgit statuswith-z(#1354). Its hand-written unquoting handled only\"and\\, so an untracked path carrying a TAB or a non-ASCII byte (git's octal C-quoting) resolved to a non-existent path and the overlap guard silently passed. Both call sites in the file were on the same defect and both are fixed. -
claude-md-drift-checkCheck 6 routes opt-in session-config gaps tonotes[]instead ofwarnings[](#1356). All 36 live parity warnings in this repo were unactionable "not required" entries; after the split the live run reports 3 warnings — genuine template lag forauto-skill-dispatch,wave-reviewersandissue-budget— and 37 notes. Mandatory-key gaps stayerrors[]. Category separation, not a threshold change. -
scripts/lib/locks/state-md-lock.mjsandstaging-fence-lock.mjsrelease throughreleaseFileLock(#1349) instead of each carrying its own read-check-unlink sequence (net −60 production lines). Both gain the.acquiresibling guard from #1285: a release that races a concurrent takeover now answersbusyand leaves the lock intact, where the old code unlinked it and reported success. Not a security repair — both usestaleCheck: 'pid', under which the takeover race is unreachable; the revisit trigger is the first of them moving to a lease mode. -
readStdininscripts/lib/io.mjsaccepts an injectable timeout (#1358), replacing a permanently skipped test with an empty body that asserted nothing. The 5 s default and the public signature are unchanged. -
Probe work-time attribution is tested with synchronized invocation windows and a controlled clock (#1360). This removes scheduler-dependent duration assertions while retaining the regression check and the original 50 ms budget. Vitest worker fanout is bounded to reduce contention from integration child processes.
-
BREAKING (deep-import surface,
scripts/lib/agent-status.mjs): provenance on the agent-status reader (#1342) —readCurrentStatus()no longer returns the bare map.agent-status.jsonlis documented as the source of truth andagent-status-current.jsonas its rebuildable cache.rebuildCurrentFromLedger({ repoRoot, maxBytes })folds a bounded ledger tail (DEFAULT_REBUILD_MAX_BYTES= 256 KiB — named ceiling with a revisit trigger) into the same map shape, one entry peragentIdwon by the greatest parsablets, marking eachbinding: 'bound' | 'legacy' | 'unknown'so an older session's record can never overwrite a newer one.readCurrentStatus()now returns{ entries, source: 'live-map' | 'rebuilt-log' | 'stale-cache' | 'absent', at, degraded? }instead of the bare map, folding PERagentId(cache vs ledger, newertswins; equal-ms ties retain the cache only when its ledger payload agrees) — a global newest-timestamp comparison would have let one sibling's later successful push mask another agent's lost map write, which is the #1342 case itself (review finding, fake-regression-proven).absentmeans neither ledger nor cache exists;live-mapmeans every entry was verified against the ledger. Entries live in null-prototype maps and__proto__/constructor/prototypeagent ids are rejected as unbound records.readCurrentStatusEntries()is the backwards-compatible bare-map accessor. -
discovery-on-closenow defaults totruewhen the key is absent and acceptsauto(=true) (#1340). Consumer repos without the key now run the close-time discovery scan — one extra Explore agent per/close, and critical/high findings count against the issue budget; setdiscovery-on-close: falseto keep the previous behaviour. Before this, the parser hard-defaulted tofalse(the 2026-07-29 doc switch totruenever reached code) and the template's recommendedautomade the whole Session Config parse throw. -
project-hygiene's ignored-ballast check runs oneduper 500 paths instead of one per path (#1317):checkProjectHygiene~773 ms → ~142 ms on this repo (298 ignored entries), findings unchanged; a partialdufailure still parses the surviving output. -
The path-scoped instruction-budget axis now counts toward the aggregate verdict (#1316):
overBudgetORs inoverPathScopedBudget, and a path-scoped breach alone raises the session-start banner. The 124,000 B ceiling is unchanged — a replay over 90 recorded states of.claude/rules/showed it fires on 5/90 (5.6 %), while the peak + 2.5 % method used for the generated axis would have been an unfalsifiable raise (the peak was HEAD). Before folding, the 8 generated rule files were condensed prose-only (every provenance pair, heading, evidence line and frontmatter unchanged;alreadyMaterializedidentical):bySurface.generated99,774 → 75,130 B,bySurface.pathScoped147,407 → 122,763 B (headroom 1,237 B). A review-panel pass restored five qualifiers the condensation had weakened.