# Invariants, detail and history

SKILL.md's own "Invariants" section states each rule; this expands the
history and mechanics behind the denser ones.

## 1. Lockstep coherence

Every roster package bumps together (`check:lockstep`). **The roster is
`scripts/package-paths.mjs`'s `PACKAGE_ROSTER`, read it, never a copy.**
This line used to enumerate the names inline and went stale the first time
the roster moved (ADR-0048 P4 added a package while the list still read the
pre-split six); a name list has no gate behind it, so it can only decay.
Each plugin's `.claude-plugin/plugin.json` version moves with its
package.json, `bump.mjs` handles both; `verify:plugins` gates the sync.

## 2. PATCH-cut asymmetry

Internal `@adia-ai/*` ranges hold at `^X.Y.0` during PATCH cuts; only MINOR
bumps the floor. `^0.0.x` is forbidden (pre-1.0 caret locks exact, shipped
a real stale-sibling bug).

## 3. Release commits land via PR, never a direct push to `main`

(Repo policy, operator ruling 2026-07-12.) Commit on `release/vX.Y.Z` → PR →
CI → merge, THEN tag at `main`'s post-merge HEAD, not at the bump commit;
post-bump fixes merge before tagging and belong in the tarball. Exception:
batch push tags each version at its own release-merge SHA.

## 4. One umbrella + one per-package tag per cut

One per lockstep-roster package (`scripts/package-paths.mjs`); publish
workflows key off the per-package tags. Push tags **one per `git push`**, a
batched multi-tag push drops the trigger event.

## 5. F-N1 per-package clean

`check:release --all-pending`: the umbrella-tag mismatch error is expected
noise. Coverage is mechanized at cut time: Step 4f
(`check-release.mjs --pending-version X.Y.Z --fix`) runs the SAME matcher
pre-PR and auto-appends verified Maintenance bullets, so a post-tag warn
means something novel, stop and investigate, don't loop enrichment PRs.

## 6. `npm dist-tag latest` is set by publish order

Batch pushes publish oldest first and WAIT for settle.

## 7. A breaking (MINOR) cut MUST ship its MIGRATION GUIDE section in the same cycle

MINOR is reserved for removed/renamed API symbols; everything else,
including visible behavior changes, stays PATCH.

## 8. The adia-factory plugin's `.mcp.json` pins the generation MCP exactly

Bump the pin in `packages/plugins/adia-ui-factory/.mcp.json` in the same cut
(the consumer never floats; with the plugins in-lockstep this lands in the
release commit itself, not a follow-up). The pinned NAME is `@adia-ai/mcp`
with the `gen-ui` subcommand (`npx -y @adia-ai/mcp@<exact> gen-ui`), the
single live name since the ADR-0048 P7 cut (v0.8.37) per the gh#1240
one-package amendment; `@adia-ai/a2ui-mcp` is retired and the P2-planned
`@adia-ai/gen-ui-mcp` never published. `check:lockstep`'s mcp-pin guard and
`bump.mjs` both enforce this pin.
