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 compatibility wrapperfindTaskBlockById()), because "use the lib helpers" alone was ambiguous while several block-access functions coexisted — that ambiguity is how the nine pre-guard instances got hand-written across four scripts in the first place. -
docs/core/ORCHESTRATION_RULES.mdgained a "Role-scope check" brief-composition duty and a Cap-hit triage addendum (T-615) — every brief item must fall within the target role's mandate as stated in its own spec, with the concrete test that an item requiring inspection of components the slice did not touch is architect work, not QA work; Cap-hit triage now directs checking for role misrouting before narrowing scope or re-spawning a bounded role.