Skip to content

v0.45.0

Choose a tag to compare

@yahor-punko yahor-punko released this 21 Aug 15:18
· 6 commits to main since this release

[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.