@mutmutco/kilo-plugin 3.79.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. package/agent/reviewer.md +108 -0
  2. package/package.json +23 -0
  3. package/scripts/command-ladder-core.mjs +334 -0
  4. package/scripts/command-ladder-gate.mjs +126 -0
  5. package/scripts/deny-gate-crash.mjs +179 -0
  6. package/scripts/edit-tool-paths.mjs +113 -0
  7. package/scripts/env-write-lint.mjs +137 -0
  8. package/scripts/hook-io.mjs +17 -0
  9. package/scripts/hook-policy.mjs +73 -0
  10. package/scripts/hook-run.mjs +170 -0
  11. package/scripts/hook-trace.mjs +108 -0
  12. package/scripts/pretooluse-shell-gates.mjs +420 -0
  13. package/scripts/secret-echo-lint.mjs +170 -0
  14. package/scripts/secret-redact.mjs +537 -0
  15. package/scripts/throttle-core.mjs +324 -0
  16. package/scripts/validate-hook.mjs +156 -0
  17. package/scripts/vault-edit-gate.mjs +94 -0
  18. package/server.mjs +237 -0
  19. package/skills/bootstrap/SKILL.md +493 -0
  20. package/skills/bootstrap/seeds/Dockerfile.template +30 -0
  21. package/skills/bootstrap/seeds/README.template.md +36 -0
  22. package/skills/bootstrap/seeds/architecture.template.md +34 -0
  23. package/skills/bootstrap/seeds/decisions-readme.template.md +46 -0
  24. package/skills/bootstrap/seeds/docker-compose.template.yml +26 -0
  25. package/skills/bootstrap/seeds/gate.template.yml +90 -0
  26. package/skills/bootstrap/seeds/google-login.template.md +33 -0
  27. package/skills/bootstrap/seeds/manifest.json +26 -0
  28. package/skills/bootstrap/seeds/mmi-product-required-checks.template.json +23 -0
  29. package/skills/browser-automation/SKILL.md +93 -0
  30. package/skills/doctor/SKILL.md +76 -0
  31. package/skills/epic/SKILL.md +87 -0
  32. package/skills/hotfix/SKILL.md +113 -0
  33. package/skills/mmi/SKILL.md +400 -0
  34. package/skills/onboard/SKILL.md +70 -0
  35. package/skills/rcand/SKILL.md +194 -0
  36. package/skills/release/SKILL.md +546 -0
  37. package/skills/resume/SKILL.md +68 -0
  38. package/skills/secrets/SKILL.md +157 -0
  39. package/skills/stage/SKILL.md +151 -0
  40. package/skills/worktree/SKILL.md +86 -0
@@ -0,0 +1,546 @@
1
+ ---
2
+ name: release
3
+ description: Ship rc or direct-track development to main and production.
4
+ ---
5
+
6
+ # /release — ship to main + prod
7
+
8
+ Full-track repos ship **exactly what is on `rc`** (never pulls `development`): merge `rc → main`, tag
9
+ `vX.Y.0`, publish a GitHub Release, dispatch the Hub central tenant deploy workflow for prod, then roll
10
+ `development` forward. Direct-track repos — product repos with `releaseTrack: direct`, plus MMI-Hub via the `isHubControlRepo` special-case (its `releaseTrack` stays unset) — skip rc: release
11
+ merges `development → main`, tags, publishes the GitHub Release, and the release event fires the repo's own
12
+ deploy/publish workflow (for MMI-Hub, `deploy.yml` + `publish.yml`).
13
+ `rc` is **ephemeral**: `/rcand` creates the rc runtime, `/release` retires it — after a confirmed full-track
14
+ prod deploy the train stops the rc stage (reported as `rcRetirement` in the result; never fatal to the
15
+ release). Full-track repos may also pass **`--dev`** to release `development → main` directly, skipping rc —
16
+ the default stays `rc → main`. `--dev` **fails closed** when `origin/rc` carries content not yet in
17
+ `development` (a dev → main release would drop it), and is a friendly no-op on direct-track repos.
18
+ **Train-authority gated (D14):** the repo's project-admin or the master; the Hub repo's train is
19
+ master-only. This is the **only** sanctioned prod path; a prod release still needs the authorized human's
20
+ explicit per-turn go — an agent never self-initiates it. The board needs no
21
+ touch — items reached `Done` when their PRs merged to `development`; `rc`/`main` are deploy stages, not lanes.
22
+
23
+ Authority is structural + server-checked: step 0 asks the Hub (`mmi-cli org access role`), and step 3 pushes
24
+ to the protected `main` branch, whose per-repo allowlist carries the same people (master + that repo's
25
+ project-admins; Hub: master + App only). Gate ordering: the tag lands the release SHA for checks, and
26
+ nothing deploys before the protected `main` push accepts that checked SHA.
27
+
28
+ ## Merge floor (#3167 — non-negotiable, applies to every step below)
29
+
30
+ - **Never squash-merge a tagged commit.** A squash re-mints the SHA, so the tag's commit vanishes from the
31
+ branch's lineage and every tag-anchored check (coverage trailers, misalignment guard, version probes)
32
+ re-flags or mis-reads forever after. Any merge that carries an already-tagged commit — the Step 5
33
+ `main → development` roll-forward, any alignment PR — lands as a **true merge** (`--merge`), never squash.
34
+ - **A refused train is a stop, never a license to finish by hand.** Every fail-closed halt below — authority
35
+ probe, coverage guard, untolerated conflict, required checks, stray tag — ends the run. The recovery is
36
+ always to fix the cause and re-run the train; it is never hand-resolving on `main`, bare-pushing a train
37
+ branch, acking to save time, or bypassing a check.
38
+ - **Version probes anchor on the latest Release tag, not a branch.** `next-version.mjs` and the fold derive
39
+ the next version from tags; a branch manifest or a stale local tag is not a version source. (This is why a
40
+ stray tag must be deleted locally too — a surviving local tag silently mints the *next* version, Step 3.)
41
+
42
+ ## Step 0 — confirm + probe
43
+
44
+ Confirm the human holding train authority for THIS repo authorized a prod release this turn. Probe:
45
+ ```bash
46
+ mmi-cli org access role {owner}/{repo} --json # Hub-verified: { role, train }
47
+ ```
48
+ `train: false` — or any error (fail closed) → stop: a product repo's train belongs to that repo's
49
+ project-admin or the master; the Hub train is master-only. **Two exceptions to fail-closed here, and both
50
+ mean "unverified — retry", never an authority verdict:**
51
+
52
+ - An `unknown command 'org'` error is a STALE CLI — the `org` namespace ships in newer trains, so an old
53
+ installed CLI can't even parse the probe. Jump to Step 0a, heal the CLI, then re-run this probe; never
54
+ conclude the command form is wrong from a pre-heal failure (#3150).
55
+ - A **TIMEOUT or network error** (`operation aborted due to timeout`, DNS/socket failures) is **not** a
56
+ `train: false` denial (#3321). Read the probe's own `verified` field: `verified: false` means the verdict
57
+ could not be established, so the `train: false` beside it carries NO authority meaning. The client
58
+ already retries transient failures internally, so a *persistent* timeout points at the local network
59
+ rather than the Hub — resolve the Hub API host first (fails in seconds and rules out the whole
60
+ local-network class), then re-run the probe. Conclude "no train authority" ONLY from a `verified: true`
61
+ response. Aborting a legitimate release on an unverified probe is the failure this carve-out exists to
62
+ prevent.
63
+
64
+ Then
65
+ preconditions: clean tree; full-track repos run from `rc` (or from `development` with `--dev`), while
66
+ direct-track repos run from `development`.
67
+
68
+ **Run from the checkout that already has that branch — never a fresh isolated worktree (#2770).** This is
69
+ a shared-branch train operation (merge/tag/push against origin's protected `main`/`rc`), not an isolated
70
+ feature-branch edit. A background-job harness that isolates every task into a fresh worktree by default
71
+ breaks here: the new worktree's branch is never literally named `development`/`rc`, and `git checkout
72
+ development` inside it fails outright when `development` is already checked out in the primary checkout (git
73
+ worktrees cannot have the same branch checked out twice). If you are in such a worktree, exit it first and
74
+ run the release from the primary checkout.
75
+ The clean-tree check rejects UNTRACKED scratch too, not just modified tracked files — if `--apply` stops
76
+ with `working tree must be clean before …`, run `git status` and gitignore the `??` scratch (or move it to a
77
+ gitignored path like `tmp/`) before retrying (#1472).
78
+
79
+ Full-track repos:
80
+ ```bash
81
+ git fetch origin
82
+ git rev-list --count origin/main..origin/rc
83
+ ```
84
+
85
+ Direct-track repos (e.g. MMI-Hub):
86
+ ```bash
87
+ git fetch origin
88
+ git rev-list --count origin/main..origin/development
89
+ ```
90
+ `0` → stop ("nothing to release").
91
+
92
+ ## Step 0a — stale CLI preflight (#1410)
93
+
94
+ Before local gates or `release --apply`, ensure the repo-local / PATH `mmi-cli` matches the released train
95
+ path — a stale checkout (e.g. 2.32.0 while 2.32.4 is released) fails release gates with opaque errors.
96
+ Self-heal in place without repo writes:
97
+ ```bash
98
+ mmi-cli doctor --apply --no-repo-writes
99
+ ```
100
+ **Read the checks, not the exit code.** `doctor` exits non-zero on any failed check it could have healed —
101
+ not just a version gap — so a red exit here does not by itself mean the train is blocked (#2962). The
102
+ inverse is also true since #3485: a run can exit **0** while printing ✗ lines, because three rows
103
+ (`worktree roots`, `schedules`, `docs-audit`) are ruled report-only — real faults the design deliberately
104
+ declines to gate on, since their remedy is a human review or lives in the owning repo. Neither direction
105
+ is a gate. Read the rows.
106
+
107
+ - **Blocking:** a red `mmi-cli` / `Claude plugin` version line, or a reported `minClientVersion` gap. Since
108
+ #3485 `--no-repo-writes` no longer blocks the env heals — the step above self-updates the npm global CLI
109
+ (#3272) and reinstalls the plugin (#3282), suppressing only repo-file writes — so the re-run should clear
110
+ it. If a gap persists, fix it — `mmi-cli doctor --apply`, or `npm install -g @mutmutco/cli` — before
111
+ continuing. Do **not** proceed to Step 0b while the installed CLI is behind the Hub's `minClientVersion`.
112
+ - **Not blocking:** every other red check is hygiene, not a train gate. Stale branches and scratch heal with
113
+ `mmi-cli doctor --apply`; `plugin cache` needs a different verb, `mmi-cli plugin prune --apply` — saying
114
+ `doctor --apply` cleared it was wrong. All three still exit 1 until you run the right verb. The three
115
+ report-only rows (`worktree roots`, `schedules`, `docs-audit`) print ✗ without moving the exit code
116
+ since #3485 — not because no verb exists, but because the design refuses to gate on a human review or
117
+ on a fault owned by another repo. Read them, then proceed.
118
+
119
+ Treating doctor's exit code as the gate halts a healthy train on cosmetic drift — or, worse, teaches an
120
+ operator to ignore doctor's exit code entirely.
121
+
122
+ **Then check the version yourself — do not rely on doctor having printed a row (#3674).** "Read the rows"
123
+ resolves to "proceed" when the row is *absent*, and doctor's version row is conditional. On 2026-07-27 this
124
+ step reported healthy with no version line while the installed CLI was 3.70.0 against a released 3.71.0;
125
+ `release --apply` refused one command later. Two lines, no dependency on another command's reporting
126
+ completeness:
127
+ ```bash
128
+ mmi-cli --version
129
+ npm view @mutmutco/cli version # public package; a bare view is trustworthy here
130
+ ```
131
+ Mismatch → `npm install -g @mutmutco/cli@<released>` before Step 0b, **regardless of what doctor's rows
132
+ said**. This is the check whose absence Step 0a's own title promises to cover, so it is not optional.
133
+
134
+ ## Step 0b — registry + main secret-name preflight
135
+
136
+ Resolve the project META first; its `deployModel` decides the deploy path. `tenant-container` repos use the
137
+ central tenant deployer and therefore need DEPLOY# coords. `hub-serverless` (MMI-Hub), `serverless`,
138
+ `registry-publish`, and `solo-container` repos deploy from their own branch/release-triggered or model
139
+ workflow, so do **not** dispatch `tenant-deploy.yml` for them.
140
+ Verify META + required SSM secret names before touching `main`:
141
+ ```bash
142
+ mmi-cli org project get {owner}/{repo}
143
+ mmi-cli secrets preflight --stage main --repo {owner}/{repo}
144
+ ```
145
+ Missing META or secret names → stop and repair the registry/secrets first.
146
+
147
+ ## Step 0c — hotfix-coverage guard (fail closed, #839, #958)
148
+
149
+ Full-track repos only. Direct-track repos skip this specific guard — not because they are exposed to no
150
+ risk, but because the risk this guard checks structurally cannot arise there: a direct-track `/hotfix`
151
+ cherry-picks straight from `development` onto `main`, so every hotfix trailer's source sha is *by
152
+ construction* an ancestor of `origin/development` — a trailer-ancestry check would be permanently green and
153
+ add no signal (#2786 — a literal port was considered and rejected as a false-confidence guard).
154
+
155
+ Hotfixes are cherry-picked from `development` to `main` with **no back-merge** (see `/hotfix`), so a
156
+ candidate cut *before* a fix landed on `development` would silently revert that hotfix in prod. The guard
157
+ proves every main-only commit is in the candidate before the `rc → main` merge.
158
+
159
+ **Direct-track repos are not risk-free, though — they carry a different, real failure mode:** a hotfix
160
+ freezes a snapshot of the files it cherry-picks (e.g. docs, a test assertion) while `development` keeps
161
+ evolving those same files afterward. Step 1's `development → main` merge preflight (untolerated-path
162
+ conflict check) is what actually catches this — a genuine content conflict on the **next** release is
163
+ expected when a hotfix's cherry-picked commits touched files `development` went on to rewrite
164
+ incompatibly. That is routine, working-as-designed friction, not a fluke or a misconfiguration (incident:
165
+ hotfix v3.16.1 #2765 cherry-picked #2762/#2768's doc sweep onto `main`; `development` then rewrote the same
166
+ files further via #2767/#2771; `/release` correctly stopped on 4 conflicted files, resolved by confirming
167
+ `development`'s side already represented the correct, current content and landing alignment PR #2784 — see
168
+ #2786). When Step 1 stops this way: run `git merge-tree` (or read the conflict content directly) to confirm
169
+ which side carries the correct current content, then land a true-merge alignment PR (never hand-resolve on
170
+ `main`) before rerunning release.
171
+
172
+ It runs **automatically inside `mmi-cli release --apply`** (Step 1+ below) — built into the CLI so it
173
+ works in every product repo with no repo-local script. You do not invoke it separately.
174
+
175
+ Per main-only commit it accepts: the `(cherry picked from commit <sha>)` trailer with that dev SHA an
176
+ ancestor of `origin/rc` (immune to conflict-resolved ports); a matching `git patch-id` on the rc side
177
+ (trailer-less picks); or a distribution-manifest-only bump (exempt — rc carries its own). Anything else
178
+ **fails the release closed** → **stop**: the right fix is a re-cut `/rcand` from `development`. Only when
179
+ the authorized human has manually verified the content is in the candidate, rerun with
180
+ `mmi-cli release --apply --ack <sha>[,<sha>…]` — the ack is recorded in the verdict. Never ack to save time.
181
+
182
+ ## Step 0d — release-time surface freshness (repo-owned, source-only docs gate)
183
+
184
+ As early as the probe — the released diff (`origin/main..origin/development`, or `..rc`) is already
185
+ known — refresh the repo's own docs against **exactly what is shipping**. An agent reads the
186
+ shipping PRs and quickly scans the surfaces; most releases change nothing they describe, so most
187
+ releases do nothing here. It is **model-agnostic** — describe the work, not the model; under Claude,
188
+ **Sonnet latest (`claude-sonnet-5`)** is the right tier (never the release model, never Haiku).
189
+ Reviewed, repo-owned + release-triggered — never a central, scheduled, cross-repo keeper (that stays
190
+ retired, #2135).
191
+
192
+ **The two in-repo docs (`README.md`, `architecture.md`): in the released commit.**
193
+ Run this **at the probe, before Step 1**. First scan both against the shipping diff. **If nothing a
194
+ shipped change touches made them drift, skip this docs pass entirely — no PR, no wait** (the common case).
195
+
196
+ When a shipped change did make a doc drift, establish **where the drifted text comes from** before editing
197
+ it — from the repo's own tooling: a generator script, a "do not edit" header, a generated-region marker.
198
+ **If you cannot establish that a region is hand-written, treat it as generated.** Hand-written prose takes
199
+ the fast path below; a generated region does not, and never lands by hand.
200
+
201
+ **Hand-written prose — the fast docs-only path:**
202
+ - Edit the doc to current truth (no change-comments — AGENTS docs rule).
203
+ - Land it on `development`: open a PR touching **only** `README.md` /
204
+ `architecture.md` and auto-merge it (`mmi-cli pr merge <n> --auto --squash` — raw `gh pr merge` is
205
+ denied by the org command ladder, #2347). Because the PR touches only
206
+ those two docs, it runs **only the checks its paths trigger** — the repo's gate skips the code/test
207
+ jobs a docs change can't affect — so it normally goes green + merges in about a minute, with no
208
+ force-push and no protected-branch bypass (CI stays honest). (On the Hub that is the fast `docs` job
209
+ while `cli` / `infra` no-op; a single-gate repo short-circuits its one gate the same way.) A
210
+ docs-only PR also can't expand the release scope (it touches only those two files).
211
+
212
+ **Generated surface — fix the source, and default to the next cycle** (#2916). The Hub's compact README
213
+ agent loop and `docs/Architecture/agent-playbook.md` come from the CLI loop definitions via
214
+ `scripts/generate-agent-playbook.mjs`; CLI `--help` comes from Commander descriptions. There, the fast path
215
+ does not apply:
216
+ - **Never hand-edit the generated file.** It is a band-aid the next regen silently overwrites, and it
217
+ leaves `--help` — the surface users actually hit — still wrong.
218
+ - **Fix the source, not the artifact:** edit the loop in `cli/src/explain-command.ts` or the relevant
219
+ `.description()` in `cli/src/index.ts`, then rebuild/regenerate.
220
+ - **Do not race the train with it.** That fix touches code + built artifacts, so it runs the **full** gate,
221
+ and it would add a **code commit to the very diff Step 1 is about to cut**. This docs pass is freshness, not
222
+ a scope hatch. So **default to deferring it**: land the source fix on `development` as an ordinary
223
+ CI-gated PR **after** the release, note the deferral in the release report, and let it ship next cycle.
224
+ The drift is cosmetic and already shipped; one more cycle costs nothing.
225
+ - Only the **human release owner** may pull a generated-surface fix into the current release, and only by
226
+ saying so explicitly. An agent never makes that call on its own drift judgment, and never `--auto`-merges
227
+ a code-bearing PR at the probe.
228
+
229
+ **Bounded, never a hang (fast path):** wait for that docs-only auto-merge up to a short bound (~5 min). If
230
+ CI is contended and it has not merged in time, **do not block the train** — proceed with the release as-is.
231
+ Leave the auto-merge armed: the doc fix is not lost, it just lands whenever CI clears. Docs freshness
232
+ improves the release; it never holds it hostage.
233
+
234
+ Then **Step 1** cuts `development → main` from the current HEAD. Because an armed auto-merge can still land
235
+ between the bound and the cut, **re-check the PR's state right before Step 1** and report what actually
236
+ shipped — never predict it from the bound.
237
+
238
+ ## Step 1 — merge to main (never force)
239
+
240
+ Full-track repos:
241
+
242
+ ```bash
243
+ git checkout main
244
+ git pull --ff-only origin main
245
+ git merge rc --no-edit
246
+ ```
247
+
248
+ Direct-track repos (e.g. MMI-Hub):
249
+ ```bash
250
+ git checkout main
251
+ git pull --ff-only origin main
252
+ git merge development --no-edit
253
+ ```
254
+ Conflict → abort + stop (the train is misaligned — investigate; don't hand-resolve on `main`). On a
255
+ direct-track repo, a conflict here right after a hotfix is expected friction, not a fluke — see Step 0c for
256
+ why (a hotfix freezes a snapshot that `development` can keep rewriting).
257
+ Alignment PRs are the exception to the org's squash default: land them with a true merge —
258
+ `mmi-cli pr merge <n> --auto --merge` (not squash; a squash discards the merge parentage, so the
259
+ misalignment guard re-flags the same divergence on the next run). `--auto` clears the checks the PR
260
+ triggers, which block an immediate merge right after a release.
261
+
262
+ **Exception — version-manifest and `.gitignore` paths.** `mmi-cli release --apply` tolerates
263
+ conflicts confined to the version-fold paths (Step 1b) and `.gitignore` (#1037 — a
264
+ repo bootstrapped before the managed-gitignore era still carries the legacy file on `main`; the candidate
265
+ carries the Hub-managed copy with project-local entries preserved). The org spine is no longer delivered per
266
+ repo (hub-v3 WS4), so a spine-path conflict is treated like any other untolerated path. A hotfix bumps the manifests +
267
+ committed CLI bundle on `main` only, so the next merge re-conflicts there even when the train is healthy —
268
+ and the fold rewrites those exact paths right after the merge. For all tolerated paths the CLI takes the
269
+ incoming side deterministically and continues. Any other conflicted path → abort + stop as above.
270
+
271
+ ## Step 1b — version fold (automatic, inside `mmi-cli release --apply`)
272
+
273
+ Every release folds the version bump into the release itself (#976): after the merge onto local `main` and
274
+ before the tag, the CLI bumps the version manifests to the release version and commits — **unconditionally,
275
+ changed or not** — then the tag-first push (Step 3) earns that fresh commit its required checks. There is
276
+ no separate bump PR, no `development` prep cycle, and `development` never sits ahead of the published
277
+ version (the back-merge in Step 5 carries the bump back).
278
+
279
+ What the fold bumps, by repo:
280
+ - **Hub (`hub-serverless`):** the full locked distribution set via `scripts/release-distribution.mjs
281
+ prepare` — spine dogfood (`scripts/spine-dogfood.mjs`: verify docs/surfaces + the managed `.gitignore` block),
282
+ every registry-declared version holder, adapter payload synchronization, build output, and the public
283
+ artifact bill of materials — then verifies the set (`verify --skip-npm-view`). Publication and staging
284
+ both derive from `surfaces.json`; there is no second package list. Claude, Codex, Kimi, Cursor, and
285
+ Kilo are active.
286
+ - **App-style repos with a root `package.json`** (most products): the manifest + lockfile version via
287
+ `npm version --no-git-tag-version`, kept in lockstep with the release tag.
288
+ - **Repos with neither:** nothing to fold — the tag is the version.
289
+
290
+ Nothing to do by hand; the `--apply` result reports the fold outcome (`versionFold`).
291
+
292
+ ## Step 2 — tag the release
293
+
294
+ Full-track repos drop the `-rc.N` suffix from the open cycle. Direct-track repos use the next cycle directly
295
+ because they have no rc tag:
296
+ ```bash
297
+ TAG=$(node scripts/next-version.mjs release) # full-track repos -> vX.Y.0
298
+ TAG=$(node scripts/next-version.mjs cycle) # direct-track repos -> vX.Y.0
299
+ git tag "$TAG"
300
+ ```
301
+
302
+ ## Step 3 — push tag, wait for the REQUIRED checks, then push main (the gate)
303
+
304
+ Required status checks are **per-repo branch protection, not a fixed list** — MMI-Hub's `main` requires
305
+ `cli` · `infra` · `docs`, but a product repo may require different contexts or none at all (#1045). The
306
+ release SHA is always fresh (the Step 1b fold commits on local `main`), so when checks ARE required,
307
+ pushing the branch *first* is structurally rejected until CI catches up. Push the **tag first** (it lands
308
+ the SHA and triggers the repo's CI — in MMI-Hub `gate.yml` runs on `v*` tags — without touching the
309
+ protected ref), then probe what `main` actually requires and wait only for those contexts:
310
+
311
+ ```bash
312
+ git push origin "vX.Y.0" # lands the SHA + triggers the repo's CI
313
+ SHA=$(git rev-parse main)
314
+ # discover the REQUIRED contexts on main (classic protection + rulesets; 404 = none from that source):
315
+ gh api repos/{owner}/{repo}/branches/main/protection/required_status_checks --jq '[.contexts[]]'
316
+ gh api repos/{owner}/{repo}/rules/branches/main \
317
+ --jq '[.[]|select(.type=="required_status_checks")|.parameters.required_status_checks[].context]'
318
+ # ZERO required contexts -> push main immediately (the GitHub push gate is the backstop).
319
+ # Otherwise poll until every required context is "success" on $SHA — never a hard-coded list, and bound
320
+ # the wait (~10 min): on timeout, stop with a clear failure naming the pending/failed contexts.
321
+ gh api repos/{owner}/{repo}/commits/$SHA/check-runs \
322
+ --jq '[.check_runs[]|{name:.name,conclusion:.conclusion}]'
323
+ git push origin main
324
+ ```
325
+ (`mmi-cli release --apply` performs this discovery + bounded wait itself.) A required context can only ever
326
+ resolve on a tag SHA if its workflow runs on `push: tags` (or is otherwise SHA-addressable) — a PR/issue-event
327
+ job (e.g. a stale hand-added `add-to-project` / `mark-merged-pr-done` board-automation check) structurally
328
+ never produces a check-run there. The train recognizes that fixed pair by name and, after a short grace
329
+ window, treats it as satisfied-for-the-tag instead of burning the full ~10-minute budget on a check-run that
330
+ can never appear (#2404). Any OTHER required context still waits the full budget and, on timeout, names
331
+ exactly which contexts never materialized vs. which were merely pending. If your repo's ruleset requires a
332
+ PR-only job outside that pair, fix the ruleset (drop it, or replace with a real gate) — don't rely on the
333
+ grace window for anything not on the allowlist.
334
+ Rejected → stop (nothing released).
335
+
336
+ **Halted on a FAILED required check (not merely pending)?** That is a **stray unreleased tag** (#2734): the
337
+ tag is pushed but `main` was not, no GitHub Release exists, and nothing deployed/published off it — so it can
338
+ never go forward (a failed check will not pass on that SHA). This is the **opposite** of a *resumable* partial
339
+ state (checks passing/pending, only the branch push / Release / deploy left, where the recovery is to finish
340
+ forward and you must NOT delete the tag). Here: fix the cause on `development` via a CI-gated PR, then **delete
341
+ the stray tag** — `git push origin --delete vX.Y.0` **and** `git tag -d vX.Y.0` (delete the local tag too; a
342
+ surviving local tag makes `next-version cycle` silently mint the *next* version) — and re-run `mmi-cli release
343
+ --apply` (the fold re-tags the same version on the fixed HEAD). **Tag deletion is deletion-class → it needs the
344
+ authorized human's explicit per-turn go.** Exception: if a publish/deploy DID run off the stray tag, do **not**
345
+ delete — mint the next version with `MMI_RELEASE_VERSION` instead. The CLI already classifies this
346
+ (`isStrayUnreleasedTag`, `cli/src/train-apply.ts`); this note surfaces the doctrine so a halted operator does
347
+ not re-derive it from source.
348
+
349
+ ## Step 4 — GitHub Release + start prod deploy (non-blocking)
350
+
351
+ For `tenant-container` repos, publish the GitHub Release and dispatch the central tenant deploy.
352
+ ```bash
353
+ gh release create "vX.Y.0" --target main --generate-notes --latest
354
+ gh workflow run tenant-deploy.yml --repo mutmutco/MMI-Hub \
355
+ -f slug={slug} -f repo={owner}/{repo} -f ref=main -f stage=main
356
+ gh run watch "$(gh run list --workflow tenant-deploy.yml --limit 1 --json databaseId -q '.[0].databaseId')" \
357
+ --exit-status # the central prod-deploy run — run this in the BACKGROUND (Bash run_in_background)
358
+ ```
359
+
360
+ For `hub-serverless` (MMI-Hub), publish the GitHub Release but do **not** dispatch `tenant-deploy.yml`:
361
+ the release event auto-fires `deploy.yml` for prod and `publish.yml` for the
362
+ plugin/CLI package. A missing `DEPLOY#main` registry row for MMI-Hub is expected, not a tenant stack repair
363
+ task. Watch/report the release-triggered `deploy.yml` and `publish.yml` runs instead.
364
+
365
+ For other direct-track repos, the train dispatches nothing centrally: a `registry-publish` repo's release
366
+ event fires its own `publish.yml` (npm / plugin marketplace); a `solo-container` repo deploys via its own
367
+ workflow. Publish the GitHub Release, then watch/report that repo's own release-triggered run.
368
+
369
+ `mmi-cli release --apply --json` returns the relevant run id/url data with `deployStatus`; `--watch` blocks
370
+ on the run(s) and resolves `deployStatus` to `success`/`failure`. For tenant-container repos, that is the
371
+ dispatched `tenant-deploy.yml` run. For Hub serverless, that is the auto-fired release `deploy.yml` and
372
+ `publish.yml` workflow pair — watched on `mutmutco/MMI-Hub`. For `registry-publish`, that is the target
373
+ repo's own release-triggered `publish.yml` run — watched on that repo, never dispatched, and never the
374
+ central `tenant-publish.yml` (#2428: dispatching both is a guaranteed npm E409 double-publish, since the
375
+ repo's own release-triggered publish always wins the version race).
376
+ `promoted: true` stays set even on a failed deploy — promotion and deploy are separate outcomes.
377
+
378
+ **`deployStatus: 'pending'` is NEVER a terminal, reportable state (#3322).** A release that returns
379
+ `pending` has an UNRESOLVED deploy, not a clean one. The report now prints `deploy: UNVERIFIED` instead of
380
+ omitting the deploy line entirely — which is exactly how a failed deploy once hid behind a "successful"
381
+ release (Jerv-PowerTools v1.22.0: the release-triggered `publish.yml` went green, a separate `push: main`
382
+ SAM deploy FAILED, and the train returned `deployStatus: pending`, so the run read as clean). Resolve it,
383
+ or report it as unverified-RED. Never as success.
384
+
385
+ **Read the `--apply` result's `workflowRuns` array — the train already enumerates every run on the release
386
+ SHA (#3699).** That enumeration includes `push: main` infra deploys (`deploy.yml`, SAM) the deploy model does
387
+ not name, and `deployStatus` is held at `pending` until they resolve. Any non-success entry — including
388
+ `pending` — keeps the release unverified: watch those listed runs to conclusion:
389
+ ```bash
390
+ gh run watch <run-id> --repo {owner}/{repo} --exit-status
391
+ ```
392
+ The one remaining caveat: `--watch` blocks only on deploy-model-named runs, not extra push-triggered
393
+ deploys — those you conclude from the `workflowRuns` list yourself. Any `failure` there is a failed release
394
+ deploy even when the train reported success — flag it loudly and fix it before calling the release healthy.
395
+
396
+ **Hub releases announce to Slack (#883).** Hub scope is **only** `mutmutco/MMI-Hub`: the commits/PRs on
397
+ `origin/main..origin/development`, this repo's `deploy.yml` + `publish.yml`, and Hub tooling (`mmi-cli`,
398
+ skills, plugin, registry, central workflows). Do **not** read another repo's board (`mmi-cli board` / `/mmi`
399
+ on a product), watch or dispatch `ds-propagate.yml`, run `design-system` / `doctor` design-system heals for
400
+ consumers, or treat a product's deploy state as part of this release — those belong to that product's own
401
+ `/release` or `/rcand`, never a Hub train.
402
+
403
+ Before running `--apply` for MMI-Hub, resolve the real tag first:
404
+ `TAG=$(node scripts/next-version.mjs cycle)` — always print `$TAG` (e.g. `v2.43.0`) in summaries, Slack,
405
+ chat, and the final report; never a placeholder like `vX.Y.0` or `v0.x.0`.
406
+
407
+ Write a curated summary — 3-6 very short plain lines, one change per line, dev-readable, no PR-dump —
408
+ to a fresh temp file (`f=$(mktemp tmp/release-summary.XXXXXX)`, so a stale prior summary is never reused).
409
+ Source from **Hub PR titles only** (`origin/main..origin/development` on `mutmutco/MMI-Hub`), but **rewrite**
410
+ each line in neutral Hub-subsystem terms (CLI, skills, plugin, workflows, registry, deploy hub) — **never**
411
+ a product or brand name (FoFu, Katip, etc.) anywhere in the summary file, Slack post, chat, or release
412
+ report. Product names are allowed only when releasing **that product's repo**. Then pass the file through:
413
+ `mmi-cli release --apply --announce-summary-file "$f"`. After the GitHub Release publishes, the CLI posts
414
+ the summary to the org alerts channel as the MMI-Future Slack app (token + channel from SSM at run time).
415
+ For a new MMI-Hub `--apply`, the CLI refuses before promotion when the file is missing, unreadable, or does
416
+ not contain 3–6 non-empty lines; generated-note fallback is not an agent release path. `--resume` never
417
+ requires or republishes a summary, and non-Hub repos skip the announcement automatically. After a valid
418
+ summary is accepted, Slack delivery remains best-effort: a transport failure is reported in the result and
419
+ never rolls back an otherwise completed release.
420
+
421
+ **Don't block on the deploy.** Start the watch as a background task and proceed to Steps 4b–5 (docs, project
422
+ info, branch alignment) while prod deploys. The verdict is collected in Step 6 — verification is not
423
+ skipped, only un-blocked. Deploy failure → report plainly, then **retry the existing promoted ref by deploy
424
+ model** once the runtime is repaired — never a re-tag or republish (`main` is already at the release, which is
425
+ correct). Read the model from the registry (`mmi-cli org project get {owner}/{repo}` → `deployModel`):
426
+ - **tenant-container** — the central tenant deployer owns the redeploy: `mmi-cli runtime tenant redeploy
427
+ {owner}/{repo} main --watch`.
428
+ - **repository-owned serverless / registry-publish** (e.g. Jerv-PowerTools' `Deploy Jerv Memory`) — there is
429
+ no `runtime tenant redeploy` for these; re-run the repo's OWN declared `workflow_dispatch` deploy workflow at
430
+ the promoted-equivalent ref: `gh workflow run <deploy-workflow> --repo {owner}/{repo} --ref <ref>`.
431
+
432
+ Name the sanctioned dispatch mechanism you used in the train report.
433
+
434
+ ## Step 4b — re-sync project info (in-repo docs handled in Step 0d)
435
+
436
+ The two in-repo docs were already scanned against the shipping diff and, if they drifted, refreshed +
437
+ merged in **Step 0d** (fast docs-only path for hand-written prose; generated regions are fixed at
438
+ their source and deferred to the next cycle) — so the released commit already carries
439
+ current `README.md` / `architecture.md` (and, in the hub, `docs/org-readme.md` /
440
+ `docs/org-architecture.md`). The `distribution` gate also blocks empty/retired-term descriptions at PR
441
+ time. Nothing to author here. `mmi-cli release --apply` now re-syncs the **Project** short description +
442
+ thin README from those docs and the registry's current member repos through the command ladder. For a
443
+ manual repair or preview use:
444
+ ```bash
445
+ mmi-cli org project sync-info --apply # omit --apply for the read-only plan
446
+ ```
447
+
448
+ ## Step 5 — roll development forward
449
+
450
+ - Keep branches aligned: `mmi-cli release --apply` back-merges the released `main` (incl. the version
451
+ fold) into `development` and reports it as `devRollForward`. When `development` has no required checks it
452
+ pushes directly (`status: pushed`). When `development` *requires* checks (e.g. MMI-Hub needs
453
+ `cli`/`infra`/`docs`), the fresh merge commit carries no passing checks, so a direct push is structurally
454
+ rejected — the train instead **opens an alignment PR** `main → development` (`status: pr-pending`) and the
455
+ release report prints the exact land command. The release itself has already shipped; **land the alignment
456
+ PR with a true merge** — `mmi-cli pr merge <number> --auto --merge` (never squash — a squash drops the merge
457
+ parentage and the misalignment guard re-flags the divergence). `--auto` is what makes it land right after a
458
+ release: the alignment PR's own `cli`/`infra`/`docs` checks are still running, so a plain immediate merge is
459
+ policy-blocked — `--auto` merges once they pass. Never force.
460
+ - Full-track repos: `mmi-cli release --apply` already aligned `rc` to the released `main` (#1036 — the
461
+ push runs inside the authority-gated train; the result reports it as `rcAlignment`). No manual `rc`
462
+ push — if the result reports a failed alignment, investigate and rerun via the train, never bare-push.
463
+ - **Local branches never left stale (#2582).** A release advances `main`/`development`/`rc` on **origin**;
464
+ `mmi-cli release --apply` also fast-forwards the LOCAL train branches to match, so the checkout you
465
+ released from does not lag what you just pushed. The train restores + fast-forwards the start branch
466
+ (`checkout`, #2340) and fast-forwards the others (`localSync`) — non-destructively: only a true
467
+ fast-forward moves a branch; a diverged local branch is reported and left untouched (never a force).
468
+ The one branch it **cannot** sync at release time is a check-gated `development` whose roll-forward is a
469
+ `pr-pending` alignment PR (Hub) — that PR merges on origin *after* the release returns. That case is
470
+ healed automatically by the **next session**: `mmi-cli plugin session-start` (invoked by the resume
471
+ channel — #3630 took SessionStart off the hook surface) runs the
472
+ same fast-forward first thing, so a stale local `development`/`main`/`rc` self-heals to origin before you
473
+ work — no manual `git pull`. Nothing here ever blocks or fails the release.
474
+
475
+ ## Step 6 — collect deploy verdict + report
476
+
477
+ Collect the backgrounded prod-deploy watch from Step 4 (it has typically finished by now). Confirm prod is
478
+ healthy (the central deploy workflow smoke step / a health check); **red** → report the failure prominently and flag
479
+ that the release shipped on a failed deploy (re-run just the deploy — `main` is already correct).
480
+
481
+ Hub releases always carry a distribution bump (the Step 1b fold), so the **publish workflow**
482
+ (`publish.yml`) ships every registry-declared public npm artifact on the GitHub Release from Step 4 —
483
+ don't publish by hand.
484
+ Watch it, then confirm npm caught up:
485
+ ```bash
486
+ gh run watch "$(gh run list --workflow publish.yml --event release --limit 1 --json databaseId -q '.[0].databaseId')" --exit-status
487
+ node scripts/release-distribution.mjs verify "vX.Y.0" # asserts registry versions, BOM identities, and published npm artifacts
488
+ ```
489
+ Run that verify from a checkout **at the tag** (`main` right after the release merge, `git checkout
490
+ "vX.Y.0"`, or a worktree at the tag) — verify checks the working tree and refuses any other commit once
491
+ the tag exists, so a stale `development`/`rc` checkout can't masquerade as a broken release.
492
+ Release-blocking for Hub tooling changes: every public artifact must match the registry version and BOM
493
+ identity. Manual fallback if CI can't publish: `node scripts/release-distribution.mjs publish "vX.Y.0"`
494
+ from a machine with npm auth.
495
+
496
+ **Private packages (#2405) — never trust a bare `npm view` to mean "not published".** An unauthenticated
497
+ `npm view <pkg>` returns a hard 404 on any package published with `publishConfig.access=restricted`
498
+ (private) — indistinguishable from "not published" — because npm masks private packages from anonymous
499
+ reads. Before verifying ANY npm publish (Hub's own, a `registry-publish` product repo's own `publish.yml`,
500
+ or a `publishRequired` product repo's companion `tenant-publish.yml`), check that surface's
501
+ `publishVisibility` in the target repo's `surfaces.json`
502
+ (`node -e "console.log(require('./surfaces.json').surfaces.find(s=>s.id==='<id>').publishVisibility)"`, or
503
+ the repo-agnostic reader `scripts/release-distribution.mjs` exports as `npmVerifyModeFor`):
504
+ - `public` → an unauthenticated `npm view <pkg>@<version> version` is a trustworthy verify signal.
505
+ - `private` → do **not** run a bare `npm view`. Verify from an authenticated source instead: the
506
+ release-triggered run's conclusion (`gh run watch … --exit-status` — the repo's own `publish.yml` for
507
+ `registry-publish`, #2428; the central `tenant-publish.yml` for a `publishRequired` companion publish) and
508
+ its logged `+ pkg@ver` / `"already on npm — nothing to do"` / `"already on npm ... treating as idempotent
509
+ success"` output (`gh run view <id> --log`), or `npm view` with a registry auth token in env. A 404 here is
510
+ "unverifiable from here", never "not published" — report it as such, don't block or alarm on it.
511
+ - `n/a` → nothing publishes to npm for that surface; skip.
512
+ Hub's registry-declared npm surfaces are public today, so `release-distribution.mjs verify` uses a bare
513
+ `npm view` for them — this changes only if a package's declared visibility changes.
514
+
515
+ **tenant-publish.yml E409 belt-and-braces (#2428).** The central `tenant-publish.yml` lane (the
516
+ `publishRequired` companion publish only — never dispatched for `registry-publish`, see above) already
517
+ treats a lost `npm publish` race as idempotent inside the run (a re-check `npm view` after a failed publish;
518
+ see the workflow file). `mmi-cli release --apply --watch` mirrors that at the CLI layer: a watched
519
+ `tenant-publish.yml` run that concludes `failure` is re-checked against that SAME run's own log (bounded
520
+ retries, visibility-agnostic — never a bare `npm view`) for the idempotent-success marker before the CLI
521
+ reports the publish as failed. A run whose log never shows the version landed stays a loud failure.
522
+
523
+ Report: Release `$TAG` (the resolved tag from Step 2 — never a placeholder) + the GitHub Release URL · prod
524
+ deploy run + URL + **green/red** · branch-alignment note · npm publish run + CLI version (Hub releases).
525
+
526
+ ## Notes
527
+
528
+ - PATCH-level releases are `/hotfix` only (a hotfix always skips rc — it cherry-picks `development → main`
529
+ directly); planned releases are MINOR or MAJOR. Never force-push `main`.
530
+ - **`--dev` (full-track only):** releases `development → main` skipping rc, with the same fold/tag/Release/
531
+ deploy machinery plus the post-release rc retirement and rc alignment. Refuses (fail closed) when
532
+ `origin/rc` carries commits not in `development`; on direct-track repos it's a no-op (they already
533
+ release from `development`).
534
+ - **MAJOR / exact-target release:** to ship a version the tag math can't derive (a MAJOR like `2.0.0`, or
535
+ skipping a version already on npm), product repos export `MMI_RELEASE_VERSION=X.Y.Z` for **both** `/rcand`
536
+ and `/release`; MMI-Hub exports it for `/release` only. Steps 1b/2 then fold and tag exactly that
537
+ version. Unset, the train ships the open cycle.
538
+
539
+ ## Retro — one check before you finish
540
+ Before your final report, answer one question honestly: did **this skill's own instructions** misfire
541
+ this run — ambiguous wording, a misleading message, or an environment failure it should have warned
542
+ about? (Process only — never the user's code or task; e.g. a misleading authority or gate message, or an
543
+ ambiguous version-fold or back-merge step.) If yes, file **one** lesson and move on; a clean run is silent
544
+ (hard cap: one per run). It lands on the Hub board (deduped) and is fixed only via a reviewed PR — never
545
+ edit the skill live; the retro is advisory, so if the call fails, note it and continue:
546
+ `mmi-cli skill-lesson --skill release --title "<what misfired>" --body "<what; evidence; proposed amendment>"`
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: resume
3
+ description: Open a session — self-check, repo status, and the next actionable board item in one pass.
4
+ ---
5
+
6
+ # /resume — start of session
7
+
8
+ One flow to re-anchor at the top of a session: is the toolchain sound, what does this repo/worktree look
9
+ like right now, and what is the single next thing worth doing. Read-only — it renders the picture and gets
10
+ out of the way. `/mmi` is the board-only hello-to-work; `/resume` is the wider session open that folds in
11
+ the toolchain self-check and the current-branch snapshot.
12
+
13
+ ## Step 0 — toolchain self-check
14
+
15
+ ```bash
16
+ mmi-cli doctor --self
17
+ ```
18
+
19
+ `doctor --self` verifies the two things that silently break a session: CLI/plugin version parity against
20
+ the published version, and whether the active `gh` token can resolve this repo. It is fast, but it reads
21
+ npm and GitHub — it is not offline-safe, and it checks neither PATH nor hook wiring (#3485). On a **hard**
22
+ gap it points at the heal path — relay it and run it before working:
23
+
24
+ ```bash
25
+ mmi-cli plugin heal # only on a hard version/plugin gap; then restart Claude Code / reopen the workspace
26
+ ```
27
+
28
+ A clean self-check is silent-enough — move straight on. Do not block the snapshot on a soft/advisory note.
29
+
30
+ ## Step 1 — repo snapshot
31
+
32
+ ```bash
33
+ mmi-cli status
34
+ ```
35
+
36
+ `status` is the unified current-state read for **this** checkout: branch, linked worktrees, your open PRs,
37
+ your claimed board items, and whether a local stage is running. It is the "where am I" line — render it,
38
+ don't re-derive it from raw `git`/`gh`.
39
+
40
+ ## Step 2 — the next move
41
+
42
+ ```bash
43
+ mmi-cli next
44
+ ```
45
+
46
+ `next` recommends the single most actionable claimable board item (unblocked, priority-ranked) and prints
47
+ the exact `mmi-cli board claim <n>` to take it. Offer it; do not claim on the user's behalf without a go.
48
+ For the full board partition (yours / claimable / taken) run `/mmi`.
49
+
50
+ ## Step 3 — render, then stop
51
+
52
+ Lead with one line: toolchain OK (or the heal to run), the branch + what's in flight, and the one next
53
+ item with its claim command. Then stop. The user drives — claim, continue, or file when they say so.
54
+
55
+ ## Notes
56
+
57
+ - `/resume` is read-only session orientation. It never claims, moves, or writes.
58
+ - Board status moves are automatic (claim, PR open, merge). Never suggest a manual status move.
59
+ - Slower, board-partition view → `/mmi`. Readiness-gate view for a new repo/first session → `/onboard`.
60
+
61
+ ## Retro — one check before you finish
62
+ Before your final report, answer one question honestly: did **this skill's own instructions** misfire
63
+ this run — ambiguous wording, a misleading message, or an environment failure it should have warned
64
+ about? (Process only — never the user's code or task; e.g. a self-check that reported a false gap, or a
65
+ snapshot that named the wrong branch.) If yes, file **one** lesson and move on; a clean run is silent
66
+ (hard cap: one per run). It lands on the Hub board (deduped) and is fixed only via a reviewed PR — never
67
+ edit the skill live; the retro is advisory, so if the call fails, note it and continue:
68
+ `mmi-cli skill-lesson --skill resume --title "<what misfired>" --body "<what; evidence; proposed amendment>"`