Skip to content

Releases: yahor-punko/mavericks-os

v0.48.1

Choose a tag to compare

@yahor-punko yahor-punko released this 02 Sep 18:46

[0.48.1] — 2026-09-02

Changed

  • The gate-ledger sweep now covers the bash-wrapper surface — scripts/mavp-operator and the wrapper the installer generates — closing a blind spot every prior sweep's .js-only glob had missed (T-732) — docs/core/GATE_LEDGER.md gains a dated sweep paragraph scoping scripts/mavp-operator (bash) plus the template string buildBashWrapper() emits in mavp-install.js, using bash-appropriate methods (exit-code sites, set -e/-u propagation, 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.md and 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 on model: fable, retry Fable once first — and the reasoning behind keeping both mechanisms is recorded as DR-015 in docs/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.md gains "Reachability is not correctness": when a task wires a check into a runner — a gate in a make target, 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's acceptance_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.md now 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_goal was emitted by --agent but never rendered by the session-start skill despite CLAUDE.md claiming otherwise, and no role had standing to refuse a task on worth grounds. .claude/agents/architect.md gains a "Worth gate" section: every gate now tests the request against the brief's initiative/wave_goal plus 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 that initiative/wave_goal are passed verbatim into every architect brief, and that a highest-value operator action is named directly in next_action instead of being turned into a task. The session-start skill now renders initiative and wave_goal in the digest, closing the discrepancy with CLAUDE.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 ~/.mavericks and ~/Documents/mavericks, not a guard, so when neither existed the wrapper (running under set -euo pipefail) deferred failure to node's own module-loader stack trace. The resolution order is now: MAVERICKS_HOME env (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 the MAVERICKS_HOME remedy and exits 1.
  • Omitting the role argument on --reflect-skill now prints a usage message instead of aborting with bash's own unbound-variable error (T-737) — both scripts/mavp-operator and the adopter wrapper template in mavp-install.js read the role positional bare (ROLE="$2") immediately before shift 2, under set -euo pipefail; a missing role therefore crashed with $2: unbound variable instead of a named refusal. Both sites now check ${2-} for emptiness before the shift and exit 1 with Usage: mavp-operator --reflect-skill <role> on stderr; --reflect-skill <role> dispatches exactly as before.
  • --set-strategy-note now honors MAVERICKS_PROJECT_ROOT like every other mutating operator script (T-741) — its ROOT constant was a bare path.resolve(__dirname, '..'), the one mutating script that never checked the env var. From an adopter's wrapper (which exports MAVERICKS_PROJECT_ROOT), ROOT silently resolved to the framework clone instead of the adopter's own project, and guardMutatingRoot() correctly refused the write against that never-a-project root — so an adopter could not set a strategy note at all. ROOT now reads process.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's PROCESS_STATE.json, caught only by the agent's own mandatory post-edit self-verification). --integrate is exempted on a recorded property: its git operations are already root-pinned and it writes no state artifacts of its own.

v0.48.0

Choose a tag to compare

@yahor-punko yahor-punko released this 27 Aug 13:01

[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.js now 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-line Turn budget: override when the Main Agent supplies one.

Changed

  • qa's turn budget rose from 20 to 40 and architect'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.md gains a canonical "Budget awareness" section, the Turn budget: brief line is reframed as an override/retry channel rather than the primary source of the number, and the tool_uses logging 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 in docs/core/DECISIONS.md. Because most roles now state their own default budget, the Turn 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 the tool_uses: N/cap denominator is the role's own frontmatter maxTurns at 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.js and package.json alongside CHANGELOG.md itself (T-724) — a shape-based exemption, not a path exemption: it recognizes the §5 version-ritual file set specifically, so a standalone package.json dependency change (no CHANGELOG.md fold) 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 merged on 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 in v0.47.1, documented late) — docs/core/ORCHESTRATION_RULES.md gains a "Booking-time claim verification (merge duty — before booking merged on 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 with artifact or unit verification type, CLAUDE.md's "Verification types" convention waives the QA agent pass, so whatever the Main Agent accepts is what ships. Before booking merged on 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): an artifact-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 in docs/core/DECISIONS.md records the ruling behind the section, and its Documented in: line now names the section itself, completing the forward reference (T-713).
  • --close-session now warns when a just-completed task's evidence commit touched a ship-classified file but the task is never mentioned in CHANGELOG.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, or CHANGELOG.md itself 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.md is now exempted at the advisory's own ship-intersection check via a named constant; a commit touching CHANGELOG.md alongside 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.md routes 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/rules edit proposal, a role-spec proposal via SKILL_PROPOSALS/, an armed recheck, or a mechanical enforcement change, and memory may then hold at most a pointer — an entry naming the T-NNN or 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 in docs/SKILL_OPTIMIZATION.md (which mines task outcomes, never memory), and not greppable over the repo. The same routing t...
Read more

v0.47.1

Choose a tag to compare

@yahor-punko yahor-punko released this 23 Aug 12:44

[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-worktrees unprompted. A new formatWorktreePruneSuggestion() composes a second, propose-only line after the unchanged counts line in both --close-session modes and --worktree-report: it is null whenever 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-worktrees itself never calls the new function, and the existing UnresolvableMainRefError stand-down line carries no suggestion text — a suggestion derived from a classification that never ran would be an unsound proposal.

v0.47.0

Choose a tag to compare

@yahor-punko yahor-punko released this 22 Aug 23:32

[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, or docs/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 in docs/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-skill and 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_action volatile-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, and promotion has joined the accepted nouns alongside bump/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: a next_action write that failed validation before this release may pass unchanged after it.

Fixed

  • --reflect-skill could 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

Choose a tag to compare

@yahor-punko yahor-punko released this 22 Aug 10:31

[0.46.2] — 2026-08-22

Added

  • --close-session now 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 canonical main sat 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 --push auto-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 executes gh or 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 main sat CI-red across two consecutive pushes on 2026-08-21. .claude/rules/scripts.md now 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 canonical scripts/mavp-operator dispatches (T-685) — --archive-merged, --park-wave, --unpark-wave, --worktree-report, and --prune-worktrees previously 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 --help lines, using zero script changes — each target script already resolves its root from MAVERICKS_PROJECT_ROOT, which the adopter wrapper already exports. The dashboard fall-through branch and the MAVERICKS_PROJECT_ROOT export 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.js now 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, and workflow_dispatch cannot manufacture it (it routes exclusively to the macOS-only job — see .github/workflows/ci.yml). .github/workflows/ci.yml's push trigger now also fires on edge (inert on canonical, since no edge branch exists there; free on the mirror, since it only measures the working-build cadence already in place). The release script queries GET /repos/{owner}/{repo}/actions/runs?head_sha=<sha> via node:https built-ins — read-only, unauthenticated, no gh, no new dependency — for the step-3-resolved origin/edge SHA, and refuses (naming the SHA, the observed state, a recovery action, and the mirror's Actions URL) unless the latest CI-workflow run is completed/success. Fails closed on any API error. Engages only when origin parses 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.md both misattributed ownership of the residual hand-typed raw-git integration vector to the T-626 accepted-boundary row in docs/core/GATE_LEDGER.md (T-678) — that row actually covers a different vector: the live-execution vector into the machine-shared ~/.mavericks framework-source clone, undetected between installs. The residual raw-git vector (a hand-typed git cherry-pick/git merge run directly against the shared main checkout instead of through --integrate) now has its own dedicated accepted-boundary row in docs/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-operator and the adopter wrapper mavp-install.js generates) silently rendered the dashboard at exit 0 for ANY unrecognized flag, instead of refusing (T-679) — an unrecognized argument previously fell through a bare else straight into the dashboard branch, masking typos and any flag newer than the wrapper (observed live 2026-08-21: a pre-T-567 --integrate HEAD call 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

Choose a tag to compare

@yahor-punko yahor-punko released this 21 Aug 15:18

[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's BACKLOG.md/TASK_STATUS.md, diverging from main. checkNeverAProjectRoot() gains a third discriminator via the existing listGitWorktrees() helper: it blocks when the resolved root realpath-equals a NON-FIRST entry in git 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 var MAVERICKS_ALLOW_NEVER_PROJECT_ROOT now 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 --integrate command. Degrade-silently is preserved: a non-git directory or a git failure leaves listGitWorktrees() returning [], so this discriminator simply never fires rather than throwing.
  • check-changelog-frozen.js now also blocks a staged CHANGELOG.md section 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 than scripts/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-typing git 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. --integrate resolves the project root the same way every other mutating command does, runs guardMutatingRoot() (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 one integrated: <short-hash> line per landed commit (single commit or a base..tip range), and — only when --task T-NNN is given and resolves to exactly one task block — prints a ready-to-run --set-status suggestion; --task is optional. On conflict it exits non-zero naming git cherry-pick --abort/--continue with no auto-abort. It writes no state artifact itself (BACKLOG.md/TASK_STATUS.md/PROCESS_STATE.*), keeping integration and status-booking decoupled. Dispatched from both scripts/mavp-operator and 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 the docs/core/GATE_LEDGER.md T-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) — both check-changelog-frozen.js blocked-commit messages (the T-604 mirror-tag refusal and the T-666 ahead-of-version-files refusal) and docs/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.js previously 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" — while Unreleased and 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.js excluded the Unreleased section 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 an Unreleased section 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 reviewing docs/core/GATE_LEDGER.md before running the printed gh release create command, but the release promoter's own printed next-steps never named that requirement. It now prints a non-blocking reminder naming docs/core/GATE_LEDGER.md and DR-008 immediately before the release command; no gate or exit code path changed.

v0.44.2

Choose a tag to compare

@yahor-punko yahor-punko released this 15 Aug 15:26

[0.44.2] — 2026-08-13

Added

  • --close-session now warns, non-blockingly, when a completed task has zero mentions of its own id anywhere in EXECUTION_LOG.md (T-629, RC-2 of docs/rca/2026-08-operator-channel-state-artifacts.md) — wave 76 closed fourteen tasks across roughly ten-plus spawns and produced zero per-spawn EXECUTION_LOG.md entries, and nothing noticed. The sweep that moves merged/deployed_dev/deployed_prod/runtime_verified tasks out of TASK_STATUS.md's Active tasks now also checks each of those task ids against EXECUTION_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/deprecated entries 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 mandatory tool_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.json exists and its mavericks_version field still carries the literal shipped placeholder (__MAVERICKS_VERSION__, from templates/PROCESS_STATE_TEMPLATE.json — a tree that has never been installed/adopted by mavp-install.js), or (b) the root resolves to $HOME/.mavericks, the machine-shared adopter-resolved framework-source clone. A missing PROCESS_STATE.json never triggers discriminator (a). The refusal message prints to BOTH stdout and stderr, naming the resolved path, the matched discriminator, and the override env var MAVERICKS_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-session now keeps this framework's own PROCESS_STATE.json mavericks_version field current automatically, in self-mode only (T-660) — nothing previously refreshed mavericks_version after 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-session now stamps mavericks_version to the current framework version during its existing PROCESS_STATE.json write, 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 root scripts/mavp-version.js lives in (realpath-compared, so a symlink hop or trailing separator can't misclassify it). In any adopter project, where mavericks_version instead records the last version mavp-install.js installed, the field is left byte-unchanged — including staying absent when it was never set.
  • --agent's permission_mode field 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 (true only when the value was observed on THIS session's SessionStart hook stdin payload — reading more files can never make this true), and permission_mode_conflict (present only when a readable user-global ~/.claude/settings.json defaultMode differs 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 omits permission_mode now 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. --agent and --close-session now share one fallback order (hook payload > persisted > settings file), closing the prior asymmetry where --agent skipped the persisted cache and --close-session skipped the live hook payload — the --close-session push gate's bypassPermissions behavior 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't bypassPermissions — names the exact settings-file fix to restore prompt-free operation.

Changed

  • next_action version-number rule is now enforced, not just suggested — an adopter's next_action value can now be rejected by a check that never blocked before (T-628) — this project's convention has always said next_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 your next_action reads 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 watches PROCESS_STATE.json directly, 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-session never wrote the wave_status field 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 at architect_reviewed and the next wave opened still reading architect_reviewed, skipping the mandatory review gate for waves with three or more planned tasks. The fix resets wave_status to planning inside 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 leaves wave_status untouched, 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 a closed value for wave_status, claiming --close-session sets it automatically — that was never reachable, since the close and the wave-counter increment happen as one atomic write, and persisting closed would have mislabeled the brand-new wave instead. closed is retired from the documented enum; wave_summary is the artifact that records what a closed wave accomplished.
  • Framework version bumped 0.44.1 → 0.44.2 in both scripts/mavp-version.js and package.json (T-658) — a patch bump: wave 81 corrects existing behavior and adds no new command, flag, field, or schema.

v0.44.1

Choose a tag to compare

@yahor-punko yahor-punko released this 13 Aug 18:48

[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.js and package.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-session now proposes booking for shipped-but-unbooked work (T-637) — when a task sits at qa_passed and the commit: hash in its TASK_STATUS.md Evidence is already reachable from HEAD, 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-status suggestion; when git is unavailable it stays silent. The commit-reachability helpers (extractCommitHashesFromEvidence, buildReachableHashIndex, isHashReachable) moved from mavp-validator.js into the shared mavp-operator-lib.js, with the validator keeping re-exports so no consumer changes.

Fixed

  • --worktree-report / --prune-worktrees now refuse an unresolvable main ref instead of silently classifying every worktree unintegrated with an inert prune (T-633) — on a repo whose default branch is master, or with a mistyped --main-ref, the report previously asserted falsehoods about every worktree's integration state. A classifier that gates git branch -D must refuse when it cannot check.
  • The shipped test suite no longer depends on git's init.defaultBranch (T-632, T-634) — every fixture git init call 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.js teardown ENOTEMPTY race on Node 22 (T-636) — fixed by converging fixture cleanup on the file's shared helper, with maxRetries/retryDelay.

Docs

  • The shipped-but-unbooked advisory is documented in docs/core/TASK_LIFECYCLE.md and CLAUDE.md (T-638).

v0.43.0

Choose a tag to compare

@yahor-punko yahor-punko released this 06 Aug 11:30

[0.43.0] — 2026-08-05

Added

  • --worktree-report and --prune-worktrees, two new operator flags for the harness-created .claude/worktrees/agent-* pile that accumulates without bound (T-559) — --worktree-report classifies every linked git worktree, read-only, into dirty / unintegrated / clean-and-integrated via patch-equivalence (git cherry semantics), not raw reachability: this project integrates by cherry-pick, so an integrated worktree's tip is unreachable from main by construction, and merge-base/branch --merged would have misclassified every one of them as still-open work. --prune-worktrees removes only the clean-and-integrated class, 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 via git branch -D. The -D (force) form is necessary, not careless: -d refuses 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 successful git worktree remove, wrapped in try/catch. --prune-worktrees defaults to dry-run (prints what would be removed, touches nothing) and requires an explicit --yes to act; run the real (--yes) prune only with zero live sub-agents, since every linked worktree of a repo shares one .git object/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/worktrees is 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 --update additively and idempotently merge three keys into the target project's .vscode/settings.json: files.exclude, search.exclude, and files.watcherExclude, each carrying a **/.claude/worktrees glob set to true (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 than true) 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 same files.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 is git.autoRepositoryDetection: "openEditors" (scope nested-repo detection to files that are actually open), added manually to your own .vscode/settings.json — see docs/core/BOOTSTRAP_GUIDE.md, "VS Code worktree exclusion". This release deliberately does not seed git.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's git init default branch, which broke CI on ubuntu runners (T-632) — both git init call sites in the fixture now explicitly pin -b main (the same pin already used by check-assembled-suite.js), so classifyWorktrees(mainDir, { mainRef: 'main' }) resolves correctly regardless of the host's init.defaultBranch setting. Upstream git still defaults git init to master, so on any host without a main-default override, the fixture's repo never had a main ref, 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 patches git init to default to main even 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 unresolvable mainRef with genuine per-worktree unintegrated work, so an operator on any repo where the default mainRef ('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 real master-default repo, --worktree-report's default invocation reported a genuinely cherry-pick-integrated worktree as unintegrated and exited 0, and --prune-worktrees reported "no prunable worktrees" and also exited 0 — no error either direction, and the exact inverse of reality. classifyWorktrees() now verifies once, up front, that mainRef resolves to a commit, and throws a typed UnresolvableMainRefError before 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-report and --prune-worktrees catch 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-supplied Base floor: <hash>, that runs git 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), and fatal: Invalid revision range means 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. Both CLAUDE.md and docs/AGENT_SPEC.md gained the optional Base floor: brief field.
  • docs/AGENT_SPEC.md's copy of the sub-agent brief template had silently drifted from CLAUDE.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 gained Adjacent docs read:, Read current main:, Model:, Effort:, and Turn budget:, plus a "Before exiting" line that now references the completion-token marker the way CLAUDE.md's already did. scripts/test-agent-spec-sync.js gained a parity check that parses both documents' brief-template fences, compares their field-name sets, and asserts both Before 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

Choose a tag to compare

@yahor-punko yahor-punko released this 05 Aug 13:54

[0.42.1] — 2026-08-04

Added

  • New mechanical guard, scripts/test-no-unbounded-block-matchers.js, now fails the test suite when any scripts/mavp-operator-*.js file 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 the scripts/mavp-operator wrapper if it is named mavp-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-task could 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 no Evidence: 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 to merged with 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's owner argument 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-merge always stamped a task runtime / developer regardless of what was actually shipped, even though the XS-lane's own attested conditions already named Verification type: artifact as a valid lane condition (T-613, T-614) — the documented capability and the actual code disagreed. Measured instance in this repo: a docs-only CHANGELOG.md addition, written by a docs sub-agent and verified by its diff, was registered as runtime / 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). visual and manual verification types are mechanically REFUSED, not merely discouraged, since both require human review by definition and can never ride a lane that registers straight to merged; --owner is validated against the known implementer-role set and refuses main_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-merge before this release, some previously-registered tasks may carry a wrong Owner role or Verification 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 merged runtime/manual tasks, so a wrong owner, or an artifact task that happened to carry the field, produced no signal at all. There is no tool that fixes this after the fact — hand-correct the affected BACKLOG.md/TASK_STATUS.md entries 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.js invocation, 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 the mavp-operator wrapper from a subdirectory instead of the project root, or a harness/CI setup that sets MAVERICKS_PROJECT_ROOT without 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-commit now 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 to MAVERICKS_PROJECT_ROOT before process.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:

    1. 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.
    2. Diff each task's Status and Owner role fields between that commit and HEAD; a change on a task you did not intentionally touch is a candidate to investigate:
      git 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.txt
      
      Repeat against TASK_STATUS.md the same way.
    3. 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:
      git log --oneline -1 <the-hash-in-that-task's-Evidence-line>
      
      and confirm the commit message that comes back actually describes the task you're checking, not a different one.

    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 via locateTaskBlock()/extractBlockField()/setBlockField()/updateTaskField(), whole-file enumeration via parseAllTaskBlocks(), and the read-only compatib...

Read more