Releases: yahor-punko/mavericks-os
Release list
v0.48.1
[0.48.1] — 2026-09-02
Changed
- The gate-ledger sweep now covers the bash-wrapper surface —
scripts/mavp-operatorand the wrapper the installer generates — closing a blind spot every prior sweep's.js-only glob had missed (T-732) —docs/core/GATE_LEDGER.mdgains a dated sweep paragraph scopingscripts/mavp-operator(bash) plus the template stringbuildBashWrapper()emits inmavp-install.js, using bash-appropriate methods (exit-code sites,set -e/-upropagation, stderr refusal-shaped strings, TTY/interactive branches, the adopter wrapper's framework-root resolution failure path) instead of the.js-glob approach every prior sweep used. This is the fourth instance of that same structural blind spot and the first one found by being pre-named rather than by incident, and it is also what surfaced the defects the T-736/T-737/T-741 entries below fix. - The architect's own report must now open by stating which model it believes it is running as, held to account by a drift test (T-733) — a spec-embedded "Model self-report" line, pinned canonically in
docs/AGENT_SPEC.mdand tested for drift, requires the architect's report to name its believed model before anything else. Worth saying plainly: this detector caught a real mismatch between spawn intent and what the architect actually reported running as one task after it shipped, which is what went on to refute T-734's proposed change. - The architect model policy now separates two mechanisms the word "fallback" used to name at once, and adds an explicit escalation threshold (T-735) — the frontmatter default (
model: opus, described now as a no-override default and an Opus floor) is distinguished from the loud-failure re-spawn rule, which keeps the name "fallback." That escalation rule now has a stated threshold — two consecutive loud spawn failures onmodel: fable, retry Fable once first — and the reasoning behind keeping both mechanisms is recorded as DR-015 indocs/core/DECISIONS.md. - Four verification lessons that had each already cost a shipped defect are now framework rules every adopter gets, instead of habits one operator happened to remember (T-742) — all four were learned the hard way in a project using this framework and lived only in that operator's local notes, which meant nobody else was protected from repeating them. (1)
docs/core/ORCHESTRATION_RULES.mdgains "Reachability is not correctness": when a task wires a check into a runner — a gate in amaketarget, a CI step, a git hook — at least one acceptance criterion must now run the whole runner and quote the gate's own output from that run. Verifying the gate by calling it directly proves it is correct; it proves nothing about whether the workflow ever reaches it, and dry-run output cannot tell the difference because it only prints what would run. A real task wrote five criteria, every one satisfiable by calling the gate directly, and shipped a gate the build target never invoked.docs/ARCHITECT_OUTPUT.md'sacceptance_criteria:guidance now carries the same requirement, so wiring tasks get a whole-runner criterion at decomposition time. (2).claude/agents/developer.md's red-run rule now requires the mutation to hit the specific line implementing the contract under guard — two default constants were once swapped whose values were identical, so all 168 tests stayed green and only a contract-line mutation exposed it — and forbids deriving an assertion's expected value from current observed behaviour, which once froze a live bug into a test as though it were the contract. (3) A new developer rule treats any handed-down list of call sites, including an architect brief's own, as a hypothesis rather than a perimeter: re-derive it with your own tree-wide grep before editing. One such list named 14 sites, all in shell scripts, and missed one in a product file, which shipped as a regression. (4)docs/core/BOOTSTRAP_GUIDE.mdnow states plainly that an adopter session must never edit the resolved framework source — its docs, role specs, rules or state — and must route proposed framework changes to the framework repo's own gate, because such an edit leaves no diff in the adopter's own history while taking effect for every project on the machine. - The framework's own worth-carrying surfaces —
## Selection rules,initiative,wave_goal— are now wired into the point where tasks are born, instead of shipping unread (T-740) — a measured session on a project consuming this framework found six merged tasks, not one on the critical path, while the actual critical path was two operator actions and a single doc; the root cause was that every surface meant to catch this was inert:BACKLOG.md's Selection rules tiers were consumed by no script, doc, or spec,wave_goalwas emitted by--agentbut never rendered by the session-start skill despiteCLAUDE.mdclaiming otherwise, and no role had standing to refuse a task on worth grounds..claude/agents/architect.mdgains a "Worth gate" section: every gate now tests the request against the brief'sinitiative/wave_goalplus the Selection rules tiers, names each proposed task's tier in the narrative, and treats an empty decomposition on worth grounds as a legitimate, complete outcome rather than scope invention.CLAUDE.md's architect-gate block now states thatinitiative/wave_goalare passed verbatim into every architect brief, and that a highest-value operator action is named directly innext_actioninstead of being turned into a task. The session-start skill now rendersinitiativeandwave_goalin the digest, closing the discrepancy withCLAUDE.md's existing claim.
Fixed
- The generated adopter wrapper now honors the framework directory it was actually installed from, and refuses loudly instead of crashing when no framework directory resolves (T-736) —
buildBashWrapper(mavericksDirHint)previously never referenced its own parameter, so both call sites' install-time hint was silently discarded; the emitted resolution line then used a directory-existence test as a selector between~/.mavericksand~/Documents/mavericks, not a guard, so when neither existed the wrapper (running underset -euo pipefail) deferred failure to node's own module-loader stack trace. The resolution order is now:MAVERICKS_HOMEenv (unconditional) → the baked install-time hint, used only when it still probes framework-shaped →$HOME/.mavericks→$HOME/Documents/mavericks→ a single terminal existence check that, on failure, prints every candidate tried plus theMAVERICKS_HOMEremedy and exits 1. - Omitting the role argument on
--reflect-skillnow prints a usage message instead of aborting with bash's own unbound-variable error (T-737) — bothscripts/mavp-operatorand the adopter wrapper template inmavp-install.jsread the role positional bare (ROLE="$2") immediately beforeshift 2, underset -euo pipefail; a missing role therefore crashed with$2: unbound variableinstead of a named refusal. Both sites now check${2-}for emptiness before the shift and exit 1 withUsage: mavp-operator --reflect-skill <role>on stderr;--reflect-skill <role>dispatches exactly as before. --set-strategy-notenow honorsMAVERICKS_PROJECT_ROOTlike every other mutating operator script (T-741) — itsROOTconstant was a barepath.resolve(__dirname, '..'), the one mutating script that never checked the env var. From an adopter's wrapper (which exportsMAVERICKS_PROJECT_ROOT),ROOTsilently resolved to the framework clone instead of the adopter's own project, andguardMutatingRoot()correctly refused the write against that never-a-project root — so an adopter could not set a strategy note at all.ROOTnow readsprocess.env.MAVERICKS_PROJECT_ROOT || path.resolve(__dirname, '..'), matching every sibling mutating script; canonical usage with the env var unset is unaffected.- A mutating operator ritual invoked from inside a linked worktree, targeting that same repo's own primary checkout, is now refused before any write happens (T-743) —
checkNeverAProjectRoot()'s guard gains a fourth discriminator that fires when the caller's own worktree matches a non-primary git worktree of the same repo whose primary is the resolved write target, closing the vector behind this session's only successful worktree escape (a stray write into the primary checkout'sPROCESS_STATE.json, caught only by the agent's own mandatory post-edit self-verification).--integrateis exempted on a recorded property: its git operations are already root-pinned and it writes no state artifacts of its own.
v0.48.0
[0.48.0] — 2026-08-27
Added
- Six report-only role specs —
qa,ux,analyst,exa-researcher,architect,security-reviewer— now each state their own turn budget inside their own spec file, and a new automated check keeps that number honest (T-728) — if you ever change one of these roles' turn-budget cap, you now must change it in two places at once: the frontmatter cap itself, and the matching number written into that same spec's own "Budget awareness" section.scripts/test-agent-spec-sync.jsnow fails the whole test suite the moment those two numbers disagree, or if the set of six covered roles changes without a matching section being added or removed — so a drifted cap or a forgotten section is caught before it ships, not discovered later in a truncated report. Each of these six specs now self-counts its own tool calls against its stated number, converges on a partial-but-real report at roughly 80% of that budget instead of running to the cliff where the report itself gets cut off, and still honours a brief-lineTurn budget:override when the Main Agent supplies one.
Changed
qa's turn budget rose from 20 to 40 andarchitect's from 25 to 50, after evidence that both roles were hitting their prior caps mid-report with the write-up lost, not the underlying work (T-727) — a multi-run evidence table showed truncated runs, when resumed, made zero or almost zero new tool calls before producing their report: the research was already done, and only the final write-up was cut off by the cap. The new caps land below the full formula-derived ceiling, applying the same discount this project has already used for this role class's read-heavy tool-call over-counting, rather than granting the raw formula output unconditionally.docs/AGENT_SPEC.mdgains a canonical "Budget awareness" section, theTurn budget:brief line is reframed as an override/retry channel rather than the primary source of the number, and thetool_useslogging denominator is now spelled out explicitly (T-729) — the self-stated-budget mechanism T-728 shipped into six role specs now has one documented home explaining the roster rule (which roles are covered and why) and why making the brief-line field mandatory was rejected in favor of embedding the number in each spec instead — recorded as DR-014 indocs/core/DECISIONS.md. Because most roles now state their own default budget, theTurn budget:brief line is described as a way to override that default or retry after a cap-hit, not as the only place the number can come from.CLAUDE.md's per-spawn logging convention now states plainly that thetool_uses: N/capdenominator is the role's own frontmattermaxTurnsat spawn time, so a logged cap that doesn't match must name its source.
[0.47.3] — 2026-08-23
- The T-718 CHANGELOG-omission advisory no longer fires on a release bump-and-fold commit that touches only
scripts/mavp-version.jsandpackage.jsonalongsideCHANGELOG.mditself (T-724) — a shape-based exemption, not a path exemption: it recognizes the §5 version-ritual file set specifically, so a standalonepackage.jsondependency change (noCHANGELOG.mdfold) still fires, and a ritual commit that also smuggles in another shipped file still fires too.
[0.47.2] — 2026-08-23
Entries below carrying the inline marker (shipped in v0.47.1, documented late) — and only those — describe normative rules that were already in force at that tag: v0.47.1's CHANGELOG.md section covered only T-710 and froze the moment the tag was cut (docs/PUBLIC_RELEASE_STRATEGY.md §5 — "Frozen-section rule"), so their notes could not be added to it afterwards. The ## [0.47.1] section is left exactly as shipped; the marked entries are the correction record. If you are reading a marked entry under a later version heading, the rule it describes did not arrive in that version — it has been in force since v0.47.1. Every unmarked entry is an ordinary release note: the change it describes first ships in the version this section is published under.
Added
- Before booking
mergedon a task with no QA stage, the Main Agent now owes one executed check against the report's weakest unverified claim (T-712, T-713; shipped inv0.47.1, documented late) —docs/core/ORCHESTRATION_RULES.mdgains a "Booking-time claim verification (merge duty — before bookingmergedon a task with no QA stage)" section, the return-side sibling of the existing Executed-check rule that governs claims written before a spawn. Its premise: a prose characterization of runtime behavior is a runtime claim. "Fails silently", "never blocks", "exits 0", "warns and continues", "degrades to a no-op" are assertions with an executable check behind them, and being written as prose in a sub-agent's report rather than as an assertion in a test does not change what they are — a report is not evidence for its own factual claims, it is the set of claims to be checked. On a task withartifactorunitverification type,CLAUDE.md's "Verification types" convention waives the QA agent pass, so whatever the Main Agent accepts is what ships. Before bookingmergedon such a task, identify the report's factual claims that have no execution behind them and run the cheapest disconfirming check against the weakest one: one grep at the named function, one run of the named command, one read of the docstring the claim characterizes — not an exhaustive re-verification. If the weakest claim survives, the report has earned the rest of its trust; if it does not, nothing else in the report is trusted until it is corrected. Weakest, not pre-flagged: verification attention already follows the brief's own risk annotations, so the unflagged claim is the one that arrives unverified — the failure mode is thorough verification in exactly the place already marked as risky while an unanticipated claim rides through beside it, which is why the rule says to deliberately exclude the claims the brief pre-flagged when choosing the target. This is explicitly not "reaching in": the orchestrator discipline forbids the Main Agent from doing the sub-agent's work, but verifying a sub-agent's claims before making them durable is a merge-gate action, and on a QA-less task it is the only gate that exists. The incident it closes (2026-08-23, T-708/T-709): anartifact-verified report characterized a code path as "a silent-degradation question" and that characterization merged into a living normative doc a reviewer reads as fact — while the code prints a red WARNING at the moment of the skip and its own docstring says "never a silent skip", one grep from the code the sweep had just read. DR-013 indocs/core/DECISIONS.mdrecords the ruling behind the section, and itsDocumented in:line now names the section itself, completing the forward reference (T-713). --close-sessionnow warns when a just-completed task's evidence commit touched a ship-classified file but the task is never mentioned inCHANGELOG.md(T-718) — printed once, non-blocking, before the results table in both interactive and non-interactive modes; silent when the task is already mentioned, or when git,scripts/publish-manifest.json, orCHANGELOG.mditself is unavailable. Never changes the exit code or the session-commit contract.
Changed
- The T-718 CHANGELOG-omission advisory no longer flags a commit that touches only
CHANGELOG.md(T-721) — its first live run at wave 94's close proved a self-referential false positive: a commit that writes a release note tripped the check asking whether the release note mentions it (T-716, T-719), while a genuinely undocumented ship-touching commit (T-717) still fires correctly.CHANGELOG.mdis now exempted at the advisory's own ship-intersection check via a named constant; a commit touchingCHANGELOG.mdalongside another ship-classified file is unaffected and still fires. - Per-operator memory is now the residual codification route rather than one that fits everything, and the codification mandate governs any durable lesson — not only ones written up in an RCA (T-711, DR-013; shipped in
v0.47.1, documented late) —docs/core/RCA_CODIFICATION.mdroutes every root cause to exactly one of five durable mechanisms, but gave route (c), the per-operator memory-index entry, only a fit test. That test is unfalsifiable in practice: every lesson is rememberable, so route (c) fits every cause, and it is the one route that costs no task, no architect gate and no review — so absent a precedence rule the cheapest route silently won every tie, and on 2026-08-23 a framework-portable lesson went to memory instead of into the framework. The deciding test is now portability: would this lesson be equally true for a different operator running this framework on a different machine? Yes → framework-level: the cause must route to a.claude/rulesedit proposal, a role-spec proposal viaSKILL_PROPOSALS/, an armed recheck, or a mechanical enforcement change, and memory may then hold at most a pointer — an entry naming theT-NNNor artifact that actually carries the rule. No → operator-scoped: memory is the correct and only sensible home. This is a tie-break, not a ban and not a ranking of all five routes — operator-personal lessons (a language preference, this machine's git identity, this operator's permission posture) legitimately live in memory, and route (c) remains optional infrastructure: an operator whose harness provides no memory index at all loses nothing, because nothing framework-level may ever live only there. What the rule forbids is the one direction that hurts adopters — a lesson that would hold for any operator going only to memory, where it is invisible to adopters, invisible to the reflection loop indocs/SKILL_OPTIMIZATION.md(which mines task outcomes, never memory), and not greppable over the repo. The same routing t...
v0.47.1
[0.47.1] — 2026-08-23
Added
- The worktree-hygiene advisory now names the ready-to-run dry-run prune command whenever a worktree is actually prunable (T-710) — at a recent wave close the advisory read
... (15 prunable)and named no next step, leaving the operator to recall--prune-worktreesunprompted. A newformatWorktreePruneSuggestion()composes a second, propose-only line after the unchanged counts line in both--close-sessionmodes and--worktree-report: it isnullwhenever nothing is prunable (including when clean-and-integrated worktrees exist but are all held back by the mtime safety window — a stale hint would be worse than no hint), and otherwise names the exact dry-run form./scripts/mavp-operator --prune-worktrees, never--yes, appending a clause attributing any clean-and-integrated/prunable gap to the mtime window.--prune-worktreesitself never calls the new function, and the existingUnresolvableMainRefErrorstand-down line carries no suggestion text — a suggestion derived from a classification that never ran would be an unsound proposal.
v0.47.0
[0.47.0] — 2026-08-22
Added
- A skill-reflection proposal now discloses how many failure trajectories its recommendation actually rests on, and names them (T-700) — a proposal could previously generalize from a single failing case while the metadata alongside it showed a corpus of hundreds, with nothing to tell a human reviewer the two numbers described different things. Each proposal's contrast section now states the failure-batch size it was built from and lists the trajectories in it, and prints a non-blocking warning when that count sits below three — the same threshold the optimizer's own instructions now tell it not to generalize from. The disclosure text is shared between the printed warning and the proposal body itself, so the two cannot drift apart, and nothing about this is enforced by an exit code — a small batch still produces a proposal, just a clearly labeled one.
- The skill-reflection optimizer now discloses which of this project's own operating constraints it was never shown, and every generated proposal carries an explicit conflict-check step (T-703) — the optimizer prompt is built from only the role spec and two scored minibatches, and had never seen
.claude/rules/*.md,CLAUDE.md, ordocs/core/ORCHESTRATION_RULES.md; on a live adopter run it proposed, in good faith, a developer-spec edit that would have re-created a measured incident already prohibited by those rules. The prompt now names the corpora it was not given and prohibits edits mandating process-level behavior (test-execution scope, git operations, push/commit rituals, task registration or status, permissions), directing any such observation into rationale text instead. The optimizer's own JSON response contract is unchanged. A matching checklist now travels inside every generated proposal file, not only indocs/SKILL_OPTIMIZATION.md, so an adopter reviewing a proposal in their own repo sees the same reminder.
Changed
- Skill-reflection's train/holdout split is now deterministic and stratified, so re-running reflection today can produce different proposals than an earlier run over the same growing trajectory log (T-699) — the split previously behaved chronologically in effect, which meant training only ever saw the oldest era of trajectories and let recent failures quietly never reach the optimizer at all. The split is now built to guarantee failing trajectories are represented in proportion, ordered numerically rather than by insertion order, and reproducible given the same input. If you re-run
--reflect-skilland get a different proposal than before, this is why — the split is now seeing failures the old logic was structurally excluding, not a change in the underlying trajectories themselves. - Strings that previously blocked the
next_actionvolatile-facts gate now pass it (T-694) — the gate's own remediation message was telling writers to rewrite a blocked directive into a form the matcher itself still refused, because the message and the matcher disagreed about which nouns mark a version literal as an instruction's target rather than a state claim. Both now derive from one shared list, andpromotionhas joined the accepted nouns alongsidebump/release/section/version— so a directive like "run the 0.46.2 promotion" now passes where it previously blocked. This is an observable behavior change in a commit-blocking gate: anext_actionwrite that failed validation before this release may pass unchanged after it.
Fixed
--reflect-skillcould crash on a live optimizer response, and reported the crash under a misleading label (T-697) — the response handler assumed the model's first content block was always plain text and indexed into it unconditionally; a response that led with a different block type (observed in a live adopter run) crashed instead of being handled, and the resulting error was reported as if it were a transport failure rather than a response-shape problem it should have caught and named honestly.
v0.46.2
[0.46.2] — 2026-08-22
Added
--close-sessionnow prints a post-push CI-verification reminder at every push-adjacent output point (T-689) — a push completing was previously treated as the end of the wave-closure contract, and canonicalmainsat CI-red across two consecutive pushes because nothing after the push prompted anyone to go check. This project's own recorded precedent for a prose-only human step getting silently skipped is the DR-008 gate-ledger review, itself only closed by a printed reminder (T-668) — the same remedy now applies here. The reminder appears in the interactive confirmed-push path, the--pushauto-push path, and appended to the non-interactive git-push reminder, so all three ways a session can end with a push get the same nudge to confirm CI is green on the pushed commit before opening the next wave. Text only — the script still never executesghor any other external call, preserving both the zero-external-dependency posture and the single human checkpoint the push step already relies on.
Fixed
- A test executing a generated adopter artifact could silently exercise a different codebase than the one actually under test (T-690) — a spawn that inherits its environment unpinned can fall through a home-directory resolution chain to a machine-shared clone of the published framework rather than the checkout whose HEAD the test is meant to cover, so a broken change could pass locally and only fail — or worse, pass for the wrong reason — once it reached CI. This is exactly how canonical
mainsat CI-red across two consecutive pushes on 2026-08-21..claude/rules/scripts.mdnow codifies a hermetic-test authoring rule: any test executing a generated adopter artifact (a wrapper script, a hook command), or any process whose framework-root resolution consults the home-directory chain, must pin the framework-root environment variable explicitly rather than inherit it.CLAUDE.md's wave-closure contract is extended to match — a wave closes on the push being complete and CI on the pushed commit reading green, not on the push alone.
[0.46.1] — 2026-08-21
Fixed
- The adopter wrapper generated by
buildBashWrapper()(mavp-install.js) was silently missing dispatch entries for five flags the canonicalscripts/mavp-operatordispatches (T-685) —--archive-merged,--park-wave,--unpark-wave,--worktree-report, and--prune-worktreespreviously rendered the dashboard at exit 0 in every adopter project (T-679's unrecognized-flag gate later turned that into an honest exit-1 refusal, but the flags still didn't work). All five now dispatch via$MAVERICKS/...with matching--helplines, using zero script changes — each target script already resolves its root fromMAVERICKS_PROJECT_ROOT, which the adopter wrapper already exports. The dashboard fall-through branch and theMAVERICKS_PROJECT_ROOTexport are structurally untouched. A new self-deriving test,scripts/test-wrapper-flag-parity.js, extracts the dispatched-flag token set from both wrappers and asserts set equality (modulo an explicit, currently-empty exception list), so the next canonical-only flag is caught at test time instead of in an adopter project.
[0.46.0] — 2026-08-21
Added
mavp-publish-release.jsnow hard-gates on the mirror's own CI going green at the exact edge tip being promoted (T-680) — the ubuntu × mirror-tree verification cell (the one that historically caught the 0.39.0 and 0.40.0 defects, only after they had already reached the mirror) previously existed nowhere except at promotion time itself, andworkflow_dispatchcannot manufacture it (it routes exclusively to the macOS-only job — see.github/workflows/ci.yml)..github/workflows/ci.yml'spushtrigger now also fires onedge(inert on canonical, since noedgebranch exists there; free on the mirror, since it only measures the working-build cadence already in place). The release script queriesGET /repos/{owner}/{repo}/actions/runs?head_sha=<sha>vianode:httpsbuilt-ins — read-only, unauthenticated, nogh, no new dependency — for the step-3-resolvedorigin/edgeSHA, and refuses (naming the SHA, the observed state, a recovery action, and the mirror's Actions URL) unless the latest CI-workflow run iscompleted/success. Fails closed on any API error. Engages only whenoriginparses as a github.com remote — the local-path fixtures this script's own test suite uses stand down with one named skip line, unconditionally (no operator flag reaches that path). No skip flag, no--force.
Fixed
- The shipped 0.45.0 T-567 entry and
docs/core/ORCHESTRATION_RULES.mdboth misattributed ownership of the residual hand-typed raw-git integration vector to the T-626 accepted-boundary row indocs/core/GATE_LEDGER.md(T-678) — that row actually covers a different vector: the live-execution vector into the machine-shared~/.mavericksframework-source clone, undetected between installs. The residual raw-git vector (a hand-typedgit cherry-pick/git mergerun directly against the shared main checkout instead of through--integrate) now has its own dedicated accepted-boundary row indocs/core/GATE_LEDGER.md's "Accepted boundaries" section, anchored to T-567's 2026-08-05 scope ruling rather than to a deprecated task, with its own reopen trigger and an explicit statement of what holds the boundary today.docs/core/ORCHESTRATION_RULES.md's ownership sentence now points at that new row instead of the T-626 row. The already-tagged## [0.45.0]section's own copy of the false clause is frozen and left as shipped — this entry is the correction record. - Both operator wrappers (
scripts/mavp-operatorand the adopter wrappermavp-install.jsgenerates) silently rendered the dashboard at exit 0 for ANY unrecognized flag, instead of refusing (T-679) — an unrecognized argument previously fell through a bareelsestraight into the dashboard branch, masking typos and any flag newer than the wrapper (observed live 2026-08-21: a pre-T-567--integrate HEADcall rendered the dashboard and exited 0 instead of failing, which is how the missing dispatch entry stayed invisible). Both wrappers now gate the dashboard branch on exactly zero arguments or--watch(the dashboard's only self-parsed flag) and refuse any other unrecognized argument at exit 1, naming it and pointing at--help. Accepted consequence: the adopter wrapper's dispatch chain has never had entries for--worktree-report,--prune-worktrees,--park-wave,--unpark-wave, or--archive-merged— those five flags now go from silently rendering the dashboard at exit 0 to being explicitly refused at exit 1 in the adopter wrapper. They did not work in the adopter wrapper before this change either; this is an honest failure replacing a silent one, not a regression. Adding the missing dispatch entries for those five flags is a separate, not-yet-scheduled concern.
v0.45.0
[0.45.0] — 2026-08-21
Added
- The "never-a-project" refusal guard (T-624) now also catches a linked git worktree, not just a never-installed tree or
$HOME/.mavericks(T-670) — every mutating operator ritual command refuses — exit 1, before any file write — when the resolved repo root is a linked (non-primary) git worktree of a real project, closing the operator-command face of the cwd-persistence class: a relative operator invocation with cwd inside a linked worktree previously silently wrote the WORKTREE'sBACKLOG.md/TASK_STATUS.md, diverging from main.checkNeverAProjectRoot()gains a third discriminator via the existinglistGitWorktrees()helper: it blocks when the resolved root realpath-equals a NON-FIRST entry ingit worktree list --porcelain(which always lists the primary/main worktree first) AND that first entry is not bare. The bare exemption matters — a bare-repo-plus-worktrees layout has no primary checkout at all, so every checkout there is "linked" by this definition, and without the exemption the guard would permanently block every mutating command for that adopter layout. The refusal message names the primary checkout path alongside the existing discriminator and override-env-var text; the override env varMAVERICKS_ALLOW_NEVER_PROJECT_ROOTnow also covers this case. The guard never auto-retargets the write to the primary checkout — it refuses and names the primary path, leaving the retarget to a human or a separate--integratecommand. Degrade-silently is preserved: a non-git directory or a git failure leaveslistGitWorktrees()returning[], so this discriminator simply never fires rather than throwing. check-changelog-frozen.jsnow also blocks a stagedCHANGELOG.mdsection heading for a version the canonical version files never reached, at write time (T-666) — a section could previously be opened and accumulate entries across waves undetected until release time, since the existing mirror-tag check (T-604) only ever compares against tags that already exist on the mirror. This is a second, independent check: a staged NEW## [x.y.z]heading whose version compares strictly greater thanscripts/mavp-version.js(read from the staged blob, falling back to HEAD when unstaged) now blocks the commit, naming both versions — while a commit that stages the matching version-file bump in the same commit still passes, and an ordinary entry added under an already-existing heading never fires it. Coexists with the T-604 mirror-tag check; the two refusal messages are worded distinctly so it's clear which rule fired.- New
--integrate <commit|base..tip> [--task T-NNN]operator command removes the cwd-dependent hand-typed cherry-pick class from worktree integration (T-567) — the Main Agent previously integrated sub-agent worktree work by hand-typinggit cherry-pick, whose correctness depended entirely on the Bash tool's persistent-but-invisible cwd; one instance ran a cherry-pick inside the agent's OWN worktree instead of main.--integrateresolves the project root the same way every other mutating command does, runsguardMutatingRoot()(T-624/T-670, all three discriminators) FIRST — refusing and naming the primary checkout on a never-a-project root or a linked worktree, never auto-retargeting — and then pins every git subprocess it spawns to that resolved root explicitly, so the actual cherry-pick lands there regardless of the caller's cwd. It refuses when a cherry-pick or merge is already in progress in the resolved root, prints oneintegrated: <short-hash>line per landed commit (single commit or abase..tiprange), and — only when--task T-NNNis given and resolves to exactly one task block — prints a ready-to-run--set-statussuggestion;--taskis optional. On conflict it exits non-zero naminggit cherry-pick --abort/--continuewith no auto-abort. It writes no state artifact itself (BACKLOG.md/TASK_STATUS.md/PROCESS_STATE.*), keeping integration and status-booking decoupled. Dispatched from bothscripts/mavp-operatorand the installer's adopter wrapper.docs/core/ORCHESTRATION_RULES.md— "Worktree integration — Main Agent" now names this the required integration path; the residual hand-typed raw-git vector (a command run directly instead of through--integrate) is not intercepted by anything in this repo and is owned by thedocs/core/GATE_LEDGER.mdT-626 accepted-boundary row, not the deprecated T-626 task.
Fixed
- Frozen-section remediation guidance now names
## [Unreleased]as the section to add entries under, instead of steering a version-blind contributor toward inventing a numbered section (T-673) — bothcheck-changelog-frozen.jsblocked-commit messages (the T-604 mirror-tag refusal and the T-666 ahead-of-version-files refusal) anddocs/PUBLIC_RELEASE_STRATEGY.md§5 now point at## [Unreleased], the accumulator the guard already exempts, the release script already filters out of release bodies, and T-568 already gates against shipping unfolded. Message text and docs only — no comparison, condition, or exit code changed. - A CHANGELOG.md section for a version that was folded into a later release's tag but never tagged on its own could stay editable forever, letting an edit silently diverge the published changelog from notes already shipped, with no guard ever firing (T-604) —
check-changelog-frozen.jspreviously froze a section only when its exact version tag existed on the mirror; a version such as 0.41.0, published only inside 0.42.0's release body, never got its own tag and so was permanently missed. The freeze boundary is now "at or below the highest stable mirror tag," compared segment-wise and numerically — never lexicographically, which would rank "0.9.0" above "0.10.0" — whileUnreleasedand strictly-newer sections stay editable. Inert for adopters and the public mirror — this check only runs--if-canonical. - The release script could silently drop an entire wave of work from a published release's notes while every other gate reported green (T-568) —
mavp-publish-release.jsexcluded theUnreleasedsection from the release body by construction, and separately excluded any section newer than the version being tagged; either path could omit real content undetected. A live near-miss on 2026-07-29 rendered a release body at 45,866 characters where the correct one was 116,698 — a whole wave missing while assemble, scan, and both content-provenance gates stayed green. The script now refuses, before any mutation, when the edge-tip CHANGELOG has real (non-blank, non-sub-heading) content in either anUnreleasedsection or any section newer than the tagged version, naming the offending sections and the version. The emptiness check is fence-aware, so a heading-shaped line inside a fenced example counts as content. - The mandatory pre-release gate-ledger review (DR-008) went unmentioned at the exact moment an operator is about to run the release command, and was skipped on the first promotion after that review requirement shipped (T-668) —
docs/PUBLIC_RELEASE_STRATEGY.md§3b requires reviewingdocs/core/GATE_LEDGER.mdbefore running the printedgh release createcommand, but the release promoter's own printed next-steps never named that requirement. It now prints a non-blocking reminder namingdocs/core/GATE_LEDGER.mdand DR-008 immediately before the release command; no gate or exit code path changed.
v0.44.2
[0.44.2] — 2026-08-13
Added
--close-sessionnow warns, non-blockingly, when a completed task has zero mentions of its own id anywhere inEXECUTION_LOG.md(T-629, RC-2 ofdocs/rca/2026-08-operator-channel-state-artifacts.md) — wave 76 closed fourteen tasks across roughly ten-plus spawns and produced zero per-spawnEXECUTION_LOG.mdentries, and nothing noticed. The sweep that movesmerged/deployed_dev/deployed_prod/runtime_verifiedtasks out ofTASK_STATUS.md's Active tasks now also checks each of those task ids againstEXECUTION_LOG.md's full text and, before the results table, prints one line naming every id with zero occurrences — silent when all ids are present.deferred/deprecatedentries are never checked. This is deliberately a total-omission detector, not a precise per-spawn-entry lint: a task id mentioned anywhere (e.g. at registration) already satisfies it, even if the mandatorytool_uses:/outcome:per-spawn record is still missing — a stricter check was explicitly ruled out, since a wave can legitimately have more merged tasks than spawns (the XS fast lane) or more spawns than tasks (retries). Never blocking: the exit code and session-commit contract are unaffected either way.- Every mutating operator ritual command (new-task, quick-task, update-task, set-status, update-status, rename-task, rescope-task, merge-task, quick-merge, apply-decomposition, close-session, archive-merged, park-wave, unpark-wave, arm-recheck, ack-recheck, set-strategy-note, handoff) now refuses to run — exit 1, before any file write — against a "never-a-project" repo root (T-624) — a root is refused when either (a) its
PROCESS_STATE.jsonexists and itsmavericks_versionfield still carries the literal shipped placeholder (__MAVERICKS_VERSION__, fromtemplates/PROCESS_STATE_TEMPLATE.json— a tree that has never been installed/adopted bymavp-install.js), or (b) the root resolves to$HOME/.mavericks, the machine-shared adopter-resolved framework-source clone. A missingPROCESS_STATE.jsonnever triggers discriminator (a). The refusal message prints to BOTH stdout and stderr, naming the resolved path, the matched discriminator, and the override env varMAVERICKS_ALLOW_NEVER_PROJECT_ROOT— set it to a truthy value to permit the write for the rare sanctioned case. Read-only reporting surfaces (--agent,--snapshot,--validate,--emit-bundle, the dashboard,--check-sync) are unaffected.printRepoIdentityHeader()(scripts/mavp-operator-lib.js) additionally duplicates its identity line to stderr for these mutating commands, so a stdout-only pipe (e.g.tail) can no longer cut the one line naming the repo it is about to write to — the exact vector that defeated the header on 2026-08-05. --close-sessionnow keeps this framework's ownPROCESS_STATE.jsonmavericks_versionfield current automatically, in self-mode only (T-660) — nothing previously refreshedmavericks_versionafter a version bump in the framework's own repo, so a lagging value printed a false update-available notice at every session start and had to be hand-synced across two consecutive waves.--close-sessionnow stampsmavericks_versionto the current framework version during its existingPROCESS_STATE.jsonwrite, in both interactive and non-interactive modes and on both mid-wave and wave-complete closes — but strictly gated to self-mode, detected by comparing the resolved project root against the framework installation rootscripts/mavp-version.jslives in (realpath-compared, so a symlink hop or trailing separator can't misclassify it). In any adopter project, wheremavericks_versioninstead records the last versionmavp-install.jsinstalled, the field is left byte-unchanged — including staying absent when it was never set.--agent'spermission_modefield no longer reports declared file config as if it were confirmed runtime truth (T-663) — three additive fields now carry its provenance:permission_mode_source(hook_payload|persisted_runtime|settings_file),permission_mode_verified(trueonly when the value was observed on THIS session's SessionStart hook stdin payload — reading more files can never make this true), andpermission_mode_conflict(present only when a readable user-global~/.claude/settings.jsondefaultModediffers from the project-file resolution; reports both values and never picks a winner, since precedence between them is harness-owned and has been observed to diverge from what this project's docs assert). The persisted runtime-mode cache (.mavp/permission-mode) is now session-scoped JSON ({mode, session_id?, written_at}) instead of a bare string; a SessionStart hook payload that omitspermission_modenow deletes a stale cached value instead of leaving it to serve a confidently-wrong mode later, while a manual (no-payload) invocation leaves it untouched.--agentand--close-sessionnow share one fallback order (hook payload > persisted > settings file), closing the prior asymmetry where--agentskipped the persisted cache and--close-sessionskipped the live hook payload — the--close-sessionpush gate'sbypassPermissionsbehavior is unchanged. The session-start skill no longer renders a bare mode value: it labels the verified/declared status, surfaces the conflict line when present, and — when the best-known mode isn'tbypassPermissions— names the exact settings-file fix to restore prompt-free operation.
Changed
next_actionversion-number rule is now enforced, not just suggested — an adopter'snext_actionvalue can now be rejected by a check that never blocked before (T-628) — this project's convention has always saidnext_action(a one-line field describing what happens next) must never contain a version number describing current state, like "we are at 0.42.1." Previously, writing that anyway only printed an informational note nobody was required to act on; three real slips got through exactly that way. Now a version number ASSERTING current state ("is/are/at/on/now/currently/already/still 0.44.3") makes the check fail outright — at edit time if hooks are active, otherwise on the next commit. A version number that is instead the actual TARGET of an instruction — "bump to 0.44.3," "the 0.44.3 section" — is explicitly allowed, so legitimate release instructions keep working. If yournext_actionreads like a status report instead of an instruction, rewrite it before your next commit, or the commit will be blocked. The same edit-time check now also watchesPROCESS_STATE.jsondirectly, not only the two task-tracking files it previously covered, so a version-number slip in any of the three state files surfaces immediately instead of waiting for someone to run a commit.
Fixed
- Closing a wave could silently carry its architect-review gate state into the freshly opened next wave, making the new wave falsely read as already past the gate it hasn't been through yet (T-653) —
--close-sessionnever wrote thewave_statusfield at all, so on wave advance the ending wave's value (e.g.architect_reviewed) survived byte-for-byte into the new wave via the existing carry-forward of unrecognized fields. Observed live: a wave closed atarchitect_reviewedand the next wave opened still readingarchitect_reviewed, skipping the mandatory review gate for waves with three or more planned tasks. The fix resetswave_statustoplanninginside the same wave-advance code path that already clears the previous wave's goal and working notes — a mid-wave (not-yet-complete) close still leaveswave_statusuntouched, since the wave it describes is still open. Docs correction alongside this fix:CLAUDE.md,docs/core/ORCHESTRATION_RULES.md, and the project-state template previously documented aclosedvalue forwave_status, claiming--close-sessionsets it automatically — that was never reachable, since the close and the wave-counter increment happen as one atomic write, and persistingclosedwould have mislabeled the brand-new wave instead.closedis retired from the documented enum;wave_summaryis the artifact that records what a closed wave accomplished. - Framework version bumped 0.44.1 → 0.44.2 in both
scripts/mavp-version.jsandpackage.json(T-658) — a patch bump: wave 81 corrects existing behavior and adds no new command, flag, field, or schema.
v0.44.1
[0.44.1] — 2026-08-13
Fixed
- A gate-review mechanism (DR-008) got its first real test against this project's own publish gates, and kept every one of them (T-643, T-647) — DR-008 requires any blocking gate with zero real fires since the last stable release to get an explicit keep/demote/retire review, not sit unexamined. The review covered the publish-overlay's seven "tiers" (checks limiting how much of the shipped tree a sync can delete) plus the manifest-classification advisory, and retired none, for three reasons: a ledger claim that some tiers made others redundant had quietly gone false through ordinary growth — the floor a tier compares against only moves on deliberate re-seeding, while the real count it competes against keeps climbing on its own, so a "redundant" tier can end up the stricter, load-bearing one, and the gap widens every wave that adds a script; one tier exists purely to be lenient, forgiving file moves/renames, so removing it would make the remaining checks stricter and noisier — the opposite of what "retiring complexity" usually means; and the manifest advisory's firings are invisible by design, visible only in an agent's own transcript, so "it never fires" could not be verified either way. The lesson: zero recorded catches is a prompt to look closer, not evidence it's safe to remove.
- The session-start "must read" list now catches uncommitted and even untracked work from a session that ended abnormally, not only work that was already committed (T-644) — previously, the list of "what changed since we last formally wrapped up" was built purely by comparing git commits, so if a session crashed or was cut off before running its own close-out step, any notes or edits it had made — including its own log of what it had just done — simply vanished from the next session's reading list, because they were never committed in the first place. The fix makes the same computation also look at your working tree's uncommitted edits and any new files git doesn't yet track (skipping anything covered by
.gitignore), and combines all of that with the usual commit history. Under the hood, the three git lookups that feed this now fail independently of one another — a problem reading one no longer wipes out results the other two already found — and a generous 32 MiB memory ceiling was set explicitly so a very large batch of new files can't silently overflow the default and quietly return nothing. - The end-of-session close-out no longer overwrites the wave's stated goal and working notes just because you closed a session mid-wave, and no longer erases them either (T-648) — closing a session used to always reset two fields tracked in the project's state file: a short note describing what the current wave is working toward, and a longer scratch note holding in-progress context. Previously, the goal note would silently carry over into the next wave once the current one actually finished (so a new wave could start already displaying an old, unrelated goal), while the scratch note was wiped on every single close — including an ordinary pause partway through a wave that was still very much in progress, discarding context that wave still needed. Both fields now only reset when a wave genuinely completes and the next one opens; if you're just pausing mid-wave, both are preserved exactly as you left them. If you or your tooling depend on the previously documented behavior — this project's own guidance said the scratch note "persists until the next close-out" — that description no longer holds; it now persists across any close that doesn't finish the wave, and clears only when the wave advances.
- The reminder to bump the framework's version number after a session no longer fires just because a file got reclassified between "shipped" and "not shipped" in the publish manifest (T-649) — that reminder is driven purely by whether anything under the tooling folder (
scripts/) changed since the version was last bumped, and one specific kind of change there — moving an entry between the shipped and excluded lists in the manifest that decides what gets published — doesn't itself change what any adopter's copy of the tooling actually does, so it shouldn't have been prompting a bump. That one file is now excluded from the check, and when the reminder does fire for a genuine reason, it now also names the specific file(s) that changed, rather than only saying "something in scripts/ changed." Note the residual: if that same manifest file's entries change what actually ships in the published file tree (not just how one entry is classified), that's still covered by this project's other release safeguards — it just no longer double-counts as its own version-bump trigger. - Framework version bumped 0.44.0 → 0.44.1 in both
scripts/mavp-version.jsandpackage.json(T-650) — a patch bump: wave 80 corrects existing behavior and adds no new command, flag, field, or schema.
The 0.44.0 release notes are included below: that version was tagged on the mirror but never got a GitHub Release, so this entry carries both.
[0.44.0] — 2026-08-13
Added
- Non-interactive
--close-sessionnow proposes booking for shipped-but-unbooked work (T-637) — when a task sits atqa_passedand thecommit:hash in itsTASK_STATUS.mdEvidence is already reachable fromHEAD, the non-interactive close prints one advisory line naming the task and the exact command to book it (./scripts/mavp-operator --set-status <id> merged). This is propose-only by design: it never writes state itself, and it complements — never replaces — the existing "Wave N stays open" line, so wave-hold semantics are unchanged. On a shallow clone, where commit reachability cannot be answered, the advisory stands down by name and deliberately carries no--set-statussuggestion; when git is unavailable it stays silent. The commit-reachability helpers (extractCommitHashesFromEvidence,buildReachableHashIndex,isHashReachable) moved frommavp-validator.jsinto the sharedmavp-operator-lib.js, with the validator keeping re-exports so no consumer changes.
Fixed
--worktree-report/--prune-worktreesnow refuse an unresolvable main ref instead of silently classifying every worktreeunintegratedwith an inert prune (T-633) — on a repo whose default branch ismaster, or with a mistyped--main-ref, the report previously asserted falsehoods about every worktree's integration state. A classifier that gatesgit branch -Dmust refuse when it cannot check.- The shipped test suite no longer depends on git's
init.defaultBranch(T-632, T-634) — every fixturegit initcall is now pinned to an explicit initial branch, plus a new static guard test that fails naming the file and line for any future unpinned fixture init. test-close-session-mode.jsteardownENOTEMPTYrace on Node 22 (T-636) — fixed by converging fixture cleanup on the file's shared helper, withmaxRetries/retryDelay.
Docs
- The shipped-but-unbooked advisory is documented in
docs/core/TASK_LIFECYCLE.mdandCLAUDE.md(T-638).
v0.43.0
[0.43.0] — 2026-08-05
Added
--worktree-reportand--prune-worktrees, two new operator flags for the harness-created.claude/worktrees/agent-*pile that accumulates without bound (T-559) —--worktree-reportclassifies every linked git worktree, read-only, intodirty/unintegrated/clean-and-integratedvia patch-equivalence (git cherrysemantics), not raw reachability: this project integrates by cherry-pick, so an integrated worktree's tip is unreachable frommainby construction, andmerge-base/branch --mergedwould have misclassified every one of them as still-open work.--prune-worktreesremoves only theclean-and-integratedclass, additionally gated on a 1-hour mtime safety window so a just-spawned agent's still-clean worktree is never swept mid-flight. State the full destructive scope plainly, since it is easy to underestimate from the name alone: a real prune does not just delete the worktree directory — it also force-deletes the branch ref viagit branch -D. The-D(force) form is necessary, not careless:-drefuses every one of these branches outright, because integration here is by cherry-pick and no branch is ever "merged" by git's own reachability test — the same fact that forced patch-equivalence classification in the first place. Deletion only proceeds after a successfulgit worktree remove, wrapped in try/catch.--prune-worktreesdefaults to dry-run (prints what would be removed, touches nothing) and requires an explicit--yesto act; run the real (--yes) prune only with zero live sub-agents, since every linked worktree of a repo shares one.gitobject/ref database and this is a repository-global exclusive-resource operation. Adopter-visible output change — not breaking:--close-session(both interactive and non-interactive modes) now also prints a one-line worktree-hygiene advisory from the same classifier whenever.claude/worktreesis non-empty — purely informational, it never prunes anything itself, and nothing that parses--close-session's existing output needs to change.- Bootstrap now seeds/merges VS Code exclusion settings for
.claude/worktrees(T-577) — a fresh install and every--updateadditively and idempotently merge three keys into the target project's.vscode/settings.json:files.exclude,search.exclude, andfiles.watcherExclude, each carrying a**/.claude/worktreesglob set totrue(the watcher key uses the deeper**/.claude/worktrees/**glob, matching VS Code's own shipped convention for that surface). The merge follows the same contract as the existing managed-hooks merge: only the single managed glob sub-entry is ever added under each key, every other key and every other sub-entry already present survives byte-identical, and a pre-existing conflicting value for the managed sub-entry (anything other thantrue) is left untouched with a printed notice rather than a silent overwrite — a second run makes no further change. Read the residual before relying on this for the problem that motivated it. The field report behind this task was VS Code's Git extension surfacing the accumulated worktree checkouts as phantom repository state (down to an operator asking why ~10K lines of changes were queued). These three keys are expected to also suppress that SCM-panel symptom, not just the file-explorer/search/watcher noise — the reasoning is that VS Code's git extension is understood to reuse the samefiles.exclude-driven skip logic for its own nested-repository discovery scan — but this is NOT verified against a live VS Code session: driving a GUI is outside what an agent can execute, so the SCM-discovery half of this fix is documented-but-unconfirmed. If it doesn't hold in practice, the documented fallback lever isgit.autoRepositoryDetection: "openEditors"(scope nested-repo detection to files that are actually open), added manually to your own.vscode/settings.json— seedocs/core/BOOTSTRAP_GUIDE.md, "VS Code worktree exclusion". This release deliberately does not seedgit.openRepositoryInParentFolders: that setting is repo-global (affects every parent-folder repo the workspace might sit inside) with legitimate-use collateral, and independently of that it is the wrong lever for this specific problem — it governs discovery of repos in PARENT folders, not the SUBFOLDER nested-repo case.claude/worktrees/agent-*actually is.
Fixed
- The worktree-hygiene regression fixture (
test-worktree-hygiene.js, T-559) hard-depended on the host machine'sgit initdefault branch, which broke CI on ubuntu runners (T-632) — bothgit initcall sites in the fixture now explicitly pin-b main(the same pin already used bycheck-assembled-suite.js), soclassifyWorktrees(mainDir, { mainRef: 'main' })resolves correctly regardless of the host'sinit.defaultBranchsetting. Upstream git still defaultsgit inittomaster, so on any host without amain-default override, the fixture's repo never had amainref, every worktree's patch-equivalence check fell through to'unknown', and Test 1 (and, less visibly, Tests 2/3/5) asserted'unintegrated'where'clean-and-integrated'was expected. This stayed invisible in local development because Apple Git 2.50.1 patchesgit initto default tomaineven with global/system config suppressed — the CI runners run upstream git, where the fixture's dependency on the host default was exposed. classifyWorktrees()conflated an unresolvablemainRefwith genuine per-worktree unintegrated work, so an operator on any repo where the defaultmainRef('main') doesn't exist got a confidently wrong report instead of an error (T-633) — the escaped defect T-632 fixed at the fixture layer left the classifier itself unguarded: on a realmaster-default repo,--worktree-report's default invocation reported a genuinely cherry-pick-integrated worktree asunintegratedand exited 0, and--prune-worktreesreported "no prunable worktrees" and also exited 0 — no error either direction, and the exact inverse of reality.classifyWorktrees()now verifies once, up front, thatmainRefresolves to a commit, and throws a typedUnresolvableMainRefErrorbefore doing any per-worktree work when it doesn't; the pre-existing conservative fallback for an individually broken worktree (missing object,getPatchEquivalenceStatus()→'unknown'→ classified'unintegrated') is unchanged and now has its own dedicated test.--worktree-reportand--prune-worktreescatch the typed error and exit non-zero, naming the unresolved ref and pointing at--main-ref, before printing any per-worktree classification;--close-session's shared advisory degrades to a single line naming the unresolved ref instead of silently going quiet — close-session itself still always completes.
Docs
- Harness worktree placement is now documented as untrusted, with a mandatory
Base floor:preflight (T-621) — the developer role spec gained a start-of-run check, keyed on a brief-suppliedBase floor: <hash>, that runsgit log --oneline HEAD..<floor>before any edit and requires the exact output quoted in the report: empty means the worktree's base is at or after the floor (proceed), non-empty means the worktree is missing required history (stop — stale base), andfatal: Invalid revision rangemeans the worktree isn't even in the target repository (stop — wrong repo).docs/core/ORCHESTRATION_RULES.md's GAP C is corrected from a mechanism claim to an observation — harness-created worktree bases have been seen arbitrarily stale, sticky across separate runs, and not an ancestor of any live branch, and neither of the two candidate causes is established, so neither is written into a brief or doc as "the" mechanism. A new GAP E documents the companion fact that isolation itself is scoped to the spawning session's own repo, so a cross-repo task's worktree can land under an unrelated repo's tree with zero isolation for the actual target — this is stated as a documented scoping fact, not a defect being fixed. BothCLAUDE.mdanddocs/AGENT_SPEC.mdgained the optionalBase floor:brief field. docs/AGENT_SPEC.md's copy of the sub-agent brief template had silently drifted fromCLAUDE.md's — missing five fields and degraded completion-token wording — and is now both fixed and mechanically guarded against drifting again (T-623) —AGENT_SPEC.md's template gainedAdjacent docs read:,Read current main:,Model:,Effort:, andTurn budget:, plus a "Before exiting" line that now references the completion-token marker the wayCLAUDE.md's already did.scripts/test-agent-spec-sync.jsgained a parity check that parses both documents' brief-template fences, compares their field-name sets, and asserts bothBefore exiting:lines carry the completion-token marker — wording differences between the two copies are expected and not a failure; only the field-name set and the marker's presence are checked. A field added to one template and not the other will now fail the shipped test suite instead of silently drifting a second time.
v0.42.1
[0.42.1] — 2026-08-04
Added
- New mechanical guard,
scripts/test-no-unbounded-block-matchers.js, now fails the test suite when anyscripts/mavp-operator-*.jsfile contains a task-heading-anchored regex whose "match any character, non-greedy" gap isn't bounded by a lookahead or a literal end-delimiter — the exact shape behind T-606/T-607/T-608/T-609 (T-610) — the glob is the coverage mechanism, since a new operator command is only reachable through thescripts/mavp-operatorwrapper if it is namedmavp-operator-<verb>.js, so a future mutating script is covered by construction rather than by being added to a list. Worth knowing: the guard's own first detection pass matched only the single-backslash regex-literal spelling and passed vacuously against a real reintroduced instance, because every historical instance was actually written as a doubled-backslash template literal — a structurally different raw substring; it was caught only because an executed mutant check was mandatory, not a reasoned-about one. Patch bump to 0.42.1 (scripts/mavp-version.js,package.json) — no new capability, no schema change.
Fixed
--update-task,--set-status,--update-status, and--merge-taskcould silently write a field into the WRONG task — including rewriting an already-merged task's commit hash on an unrelated, closed piece of work (T-606, T-607, T-608, T-609). If you ran any of these four commands against your own project between v0.3.0 (2026-04-09) and v0.42.0 (2026-08-04), some of your BACKLOG.md/TASK_STATUS.md fields may already be wrong today, and no validator check catches it — see the audit recipe below. Each command located its target task by its heading, then read or wrote a field somewhere below that heading with no concept of where the task's own block actually ended. When the target block did NOT already contain the field being written (for example, a task with noEvidence:line yet), the write ran past the end of the target block and landed in the first LATER block in the same file that did have the field — typically an already-archived, already-closed task. The worst reproduced case: promoting an active task with no Evidence line tomergedwith a real commit hash instead wrote that hash into an ARCHIVED task's Evidence line, replacing its actual commit hash while leaving the rest of the line untouched — a closed task ends up on record as having shipped a commit it never had. Separately and independently,--update-task'sownerargument never worked against a well-formed block at all: it targeted a field name no entry builder in this codebase ever emits, so it either corrupted a foreign block (via the same over-run) or silently changed nothing — printing the identical success line either way. All four commands now resolve every read and write to the target task's own block boundary, and insert a missing field into the TARGET block instead of reaching past it. Scope of the bug, so you can judge your own exposure precisely: it only ever fired when the target block itself lacked the field being written, and it only ever wrote into a LATER block in the same file — never an earlier one, never across the BACKLOG.md/TASK_STATUS.md boundary. A target block that already carried the field was always safe.--quick-mergealways stamped a taskruntime/developerregardless of what was actually shipped, even though the XS-lane's own attested conditions already namedVerification type: artifactas a valid lane condition (T-613, T-614) — the documented capability and the actual code disagreed. Measured instance in this repo: a docs-onlyCHANGELOG.mdaddition, written by a docs sub-agent and verified by its diff, was registered asruntime/developer; both fields were wrong. The lane now accepts two batch-wide flags —--verification-type <artifact|runtime|unit>and--owner <role>— applied to every item registered in a single run (defaults unchanged:runtime/developer, so a flagless run stays byte-identical).visualandmanualverification types are mechanically REFUSED, not merely discouraged, since both require human review by definition and can never ride a lane that registers straight tomerged;--owneris validated against the known implementer-role set and refusesmain_agent(this lane always cites a sub-agent's commit). Any invalid value refuses the ENTIRE run with exit 1 before any input is collected or any file is written. A mixed batch needing different values per item should run the lane once per type/owner grouping instead of one combined run. If you used--quick-mergebefore this release, some previously-registered tasks may carry a wrongOwner roleorVerification type, and this release does not repair them — the signal was weak by design accident: the only validator finding that could have surfaced it,merged_missing_needs_fix_rounds, is info-severity and fires only for mergedruntime/manualtasks, so a wrong owner, or anartifacttask that happened to carry the field, produced no signal at all. There is no tool that fixes this after the fact — hand-correct the affectedBACKLOG.md/TASK_STATUS.mdentries directly, the same conclusion reached for the exposure documented above. Follow-up (T-616): the lane now also prints one line — after flag validation succeeds and before it collects any item input — naming the resolved verification type and owner, marking each(default)when its flag was not supplied, and stating in words that the resolved values apply to every item in the batch whenever either flag was explicitly given. A piped caller now sees this at the top of its transcript, and an interactive caller can abort before typing a title if the resolved metadata is wrong.- An operator command's post-write validator spawn, or a no-argument
mavp-validator.jsinvocation, could judge a different repo than the one the command had just written to (T-616, T-617, T-618) — a state mutation could be reported "healthy" while the repo it actually mutated was in repair-required state, because the mutating command's resolved root and the spawned validator's judged root could disagree. This was reproduced live; the trigger is a divergence between the current directory and the resolved project root — for example, invoking themavp-operatorwrapper from a subdirectory instead of the project root, or a harness/CI setup that setsMAVERICKS_PROJECT_ROOTwithout also setting a matching working directory. In the ordinary flow — running the wrapper from the project root, with or without the env var set — writer and judge already agreed, so most adopters will never have observed this; it is not the case that every command was mis-validating. The fix is deliberately two-sided: every operator command that spawns the validator now passes its own resolved root to it explicitly as an argument —--quick-merge's own spawn plus eight further spawn sites (--apply-decomposition,--merge-task,--new-task,--quick-task,--update-task,--close-session, and both spawns in--agent) converged onto the pattern six siblings (--set-status,--update-status,--rename-task,--rescope-task,--park-wave,--archive-merged) already used, and.claude/hooks/pre-commitnow passes its own working directory explicitly — so writer and judge agree whether or not an env var is set; separately,mavp-validator.js's own root resolution now falls back toMAVERICKS_PROJECT_ROOTbeforeprocess.cwd()when no path argument is given at all, covering third-party or harness invocations that pass no path.getProjectRoot()(used for project-level registries —docs/MODULES.md,docs/REPO_MAP.md) was deliberately left unchanged and still resolves from the env var or the current directory rather than an explicit argument; this is a known, tracked residual, not fixed in this release.
Docs
-
Audit recipe for adopters who ran the affected commands during the exposure window above. This release stops the defect going forward — it does not repair any field it already corrupted before you upgraded, because the correct pre-corruption value only survives in your own project's git history, and no tool here can distinguish a legitimately-changed field from a corrupted one after the fact. To check your own
BACKLOG.md/TASK_STATUS.md:- Pick a commit from before your first use of any of the four commands in the window above — any earlier commit in your own repo's history works.
- Diff each task's
StatusandOwner rolefields between that commit and HEAD; a change on a task you did not intentionally touch is a candidate to investigate:Repeat againstgit show <pre-upgrade-commit>:BACKLOG.md | grep -E '^(### T-|- \*\*(Status|Owner role):)' > /tmp/pre.txt git show HEAD:BACKLOG.md | grep -E '^(### T-|- \*\*(Status|Owner role):)' > /tmp/post.txt diff /tmp/pre.txt /tmp/post.txtTASK_STATUS.mdthe same way. - Spot-check archived/merged tasks' recorded commit hashes against your real history, since a corrupted Evidence line still reads as a well-formed hash and won't look wrong on its own:
and confirm the commit message that comes back actually describes the task you're checking, not a different one.
git log --oneline -1 <the-hash-in-that-task's-Evidence-line>
This is detection guidance for a historical exposure, not automated repair — nothing in this release rewrites a corrupted field back to its original value; only you, from your own history, can do that.
-
.claude/rules/scripts.md's "Reserved shapes" list gained this guard's shape as a fourth entry, plus a new job-to-helper mapping (T-610) — states which lib function is canonical for which job (single-block field access vialocateTaskBlock()/extractBlockField()/setBlockField()/updateTaskField(), whole-file enumeration viaparseAllTaskBlocks(), and the read-only compatib...