@mutmutco/opencode-mmi 2.48.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 (39) hide show
  1. package/dist/index.d.ts +35 -0
  2. package/dist/index.js +194 -0
  3. package/package.json +44 -0
  4. package/skills/_shared/doctrine.md +238 -0
  5. package/skills/bootstrap/SKILL.md +419 -0
  6. package/skills/bootstrap/seeds/Dockerfile.template +25 -0
  7. package/skills/bootstrap/seeds/README.template.md +36 -0
  8. package/skills/bootstrap/seeds/architecture.template.md +19 -0
  9. package/skills/bootstrap/seeds/components.json.template +31 -0
  10. package/skills/bootstrap/seeds/cursor-environment.template.json +3 -0
  11. package/skills/bootstrap/seeds/cursor-rules.template.mdc +11 -0
  12. package/skills/bootstrap/seeds/design-system.paths.template.json +8 -0
  13. package/skills/bootstrap/seeds/docker-compose.template.yml +17 -0
  14. package/skills/bootstrap/seeds/gate.template.yml +42 -0
  15. package/skills/bootstrap/seeds/google-login.template.md +35 -0
  16. package/skills/bootstrap/seeds/manifest.json +32 -0
  17. package/skills/bootstrap/seeds/mcp-playwright.template.json +13 -0
  18. package/skills/bootstrap/seeds/mmi-product-required-checks.template.json +23 -0
  19. package/skills/browser-automation/SKILL.md +137 -0
  20. package/skills/build/SKILL.md +237 -0
  21. package/skills/build/references/halt-report.md +38 -0
  22. package/skills/build/references/loops.md +13 -0
  23. package/skills/build/references/worked-example.md +18 -0
  24. package/skills/build/templates/campaign-northstar.md +40 -0
  25. package/skills/coop/SKILL.md +77 -0
  26. package/skills/grind/SKILL.md +469 -0
  27. package/skills/grind/references/auto.md +107 -0
  28. package/skills/grind/references/build-notes.md +56 -0
  29. package/skills/grind/references/routing.md +76 -0
  30. package/skills/grind/references/verify.md +83 -0
  31. package/skills/grind/templates/saga-snapshot.md +28 -0
  32. package/skills/grind/templates/synthesize-panel.md +104 -0
  33. package/skills/handoff/SKILL.md +67 -0
  34. package/skills/hotfix/SKILL.md +219 -0
  35. package/skills/mmi/SKILL.md +372 -0
  36. package/skills/rcand/SKILL.md +169 -0
  37. package/skills/release/SKILL.md +309 -0
  38. package/skills/secrets/SKILL.md +137 -0
  39. package/skills/stage/SKILL.md +150 -0
@@ -0,0 +1,35 @@
1
+ type Config = {
2
+ skills?: {
3
+ paths?: string[];
4
+ urls?: string[];
5
+ };
6
+ command?: Record<string, {
7
+ template: string;
8
+ description?: string;
9
+ agent?: string;
10
+ }>;
11
+ permission?: Record<string, unknown> | string;
12
+ };
13
+ type HookOutput = {
14
+ env?: Record<string, string>;
15
+ args?: Record<string, unknown>;
16
+ context?: string[];
17
+ };
18
+ type ToolInput = {
19
+ tool?: string;
20
+ };
21
+ type MmiOptions = {
22
+ skillsPath?: string;
23
+ commandAgent?: string;
24
+ };
25
+ declare function beforeTool(input: ToolInput, output: HookOutput): Promise<void>;
26
+ declare function shellEnv(_input: unknown, output: HookOutput): Promise<void>;
27
+ declare function compacting(_input: unknown, output: HookOutput): Promise<void>;
28
+ declare function MmiOpenCodePlugin(_ctx: unknown, rawOptions?: MmiOptions): Promise<{
29
+ config: (cfg: Config) => void;
30
+ 'shell.env': typeof shellEnv;
31
+ 'tool.execute.before': typeof beforeTool;
32
+ 'experimental.session.compacting': typeof compacting;
33
+ }>;
34
+ export default MmiOpenCodePlugin;
35
+ export { MmiOpenCodePlugin };
package/dist/index.js ADDED
@@ -0,0 +1,194 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { fileURLToPath } from 'node:url';
3
+ const WORKFLOW_SKILLS = [
4
+ 'mmi',
5
+ 'secrets',
6
+ 'stage',
7
+ 'rcand',
8
+ 'release',
9
+ 'hotfix',
10
+ 'bootstrap',
11
+ 'grind',
12
+ 'build',
13
+ 'handoff',
14
+ 'coop',
15
+ 'browser-automation',
16
+ ];
17
+ const DEFAULT_SKILLS_PATH = fileURLToPath(new URL('../skills', import.meta.url));
18
+ const DEFAULT_COMMAND_AGENT = 'build';
19
+ // The adapter knows its own version at load (it IS the installed npm package), which is the reliable
20
+ // "installed opencode plugin version" signal doctor reads back via MMI_OPENCODE_PLUGIN_VERSION. Resolve
21
+ // the package's own package.json relative to this compiled module; on any failure return undefined so the
22
+ // stamp is simply omitted — the guardrail hooks must never throw.
23
+ function readPluginVersion() {
24
+ try {
25
+ const pkgUrl = new URL('../package.json', import.meta.url);
26
+ const pkg = JSON.parse(readFileSync(pkgUrl, 'utf8'));
27
+ return typeof pkg.version === 'string' ? pkg.version : undefined;
28
+ }
29
+ catch {
30
+ return undefined;
31
+ }
32
+ }
33
+ function addSkillPermission(cfg) {
34
+ if (cfg.permission === undefined) {
35
+ cfg.permission = { skill: { '*': 'allow' } };
36
+ return;
37
+ }
38
+ if (cfg.permission && typeof cfg.permission === 'object' && !Array.isArray(cfg.permission)) {
39
+ cfg.permission.skill = cfg.permission.skill ?? { '*': 'allow' };
40
+ }
41
+ }
42
+ function uniqueAppend(values, value) {
43
+ return Array.from(new Set([...(values ?? []), value]));
44
+ }
45
+ function commandDescription(skill) {
46
+ if (skill === 'mmi')
47
+ return 'Run the MMI work-board workflow.';
48
+ if (skill === 'browser-automation')
49
+ return 'Run the MMI browser automation workflow.';
50
+ return `Run the MMI ${skill} workflow.`;
51
+ }
52
+ function commandTemplate(skill) {
53
+ return [
54
+ `Use the \`${skill}\` skill and follow it exactly.`,
55
+ '',
56
+ '$ARGUMENTS',
57
+ ].join('\n');
58
+ }
59
+ function ensureConfig(cfg, options) {
60
+ cfg.skills = cfg.skills ?? {};
61
+ cfg.skills.paths = uniqueAppend(cfg.skills.paths, options.skillsPath);
62
+ cfg.command = cfg.command ?? {};
63
+ for (const skill of WORKFLOW_SKILLS) {
64
+ cfg.command[skill] = cfg.command[skill] ?? {
65
+ template: commandTemplate(skill),
66
+ description: commandDescription(skill),
67
+ agent: options.commandAgent,
68
+ };
69
+ }
70
+ addSkillPermission(cfg);
71
+ }
72
+ function normalizeToolName(name) {
73
+ return String(name ?? '').trim().toLowerCase();
74
+ }
75
+ function toolFilePath(args) {
76
+ return String(args.filePath ?? args.file_path ?? args.path ?? args.target_file ?? '').trim();
77
+ }
78
+ function isRealEnvPath(filePath) {
79
+ const normalized = filePath.replace(/\\/g, '/');
80
+ const base = normalized.split('/').pop() ?? '';
81
+ if (!base.startsWith('.env'))
82
+ return false;
83
+ if (base === '.env.example' || base.endsWith('.example') || base.includes('.template'))
84
+ return false;
85
+ return true;
86
+ }
87
+ function stripQuoted(command) {
88
+ return command.replace(/'[^']*'/g, ' ').replace(/"[^"]*"/g, ' ');
89
+ }
90
+ function gitSubcommandRest(command, subcommand) {
91
+ const re = new RegExp(`\\bgit(?:\\s+(?:-C\\s+\\S+|--no-pager|-c\\s+\\S+))*\\s+${subcommand}\\b(.*)$`, 'is');
92
+ const match = re.exec(command);
93
+ return match ? match[1] : null;
94
+ }
95
+ function hasNonFlagToken(rest, skip = new Set()) {
96
+ for (const token of rest.trim().split(/\s+/).filter(Boolean)) {
97
+ if (token.startsWith('-') || skip.has(token))
98
+ continue;
99
+ return true;
100
+ }
101
+ return false;
102
+ }
103
+ function hasGitBounds(subcommand, rest) {
104
+ if (subcommand === 'diff') {
105
+ return /--stat\b/i.test(rest) || /--name-only\b/i.test(rest) || /--numstat\b/i.test(rest) || /-U\d+/i.test(rest) || /\s--\s+\S/.test(rest) || hasNonFlagToken(rest);
106
+ }
107
+ if (subcommand === 'show') {
108
+ return /--stat\b/i.test(rest) || /--name-only\b/i.test(rest) || /-U\d+/i.test(rest) || /\S+:\S+/.test(rest);
109
+ }
110
+ if (subcommand === 'log') {
111
+ return /-n\s*\d+/i.test(rest) || /--max-count\b/i.test(rest) || /\s-\d+\b/.test(rest) || hasNonFlagToken(rest, new Set(['--oneline']));
112
+ }
113
+ return false;
114
+ }
115
+ function boundedLogCommand(command) {
116
+ if (/\bGet-Content\b/i.test(command)) {
117
+ if (/-(?:Tail|TotalCount)\s+\d+/i.test(command))
118
+ return true;
119
+ if (/\|\s*Select-Object\s+-(?:Last|First)\s+\d+/i.test(command))
120
+ return true;
121
+ }
122
+ if (/\b(cat|type)\s+[^\s|;&]+\.(log|txt)\b/i.test(command)) {
123
+ if (/\|\s*tail\s+/i.test(command))
124
+ return true;
125
+ if (/\|\s*head\s+/i.test(command))
126
+ return true;
127
+ if (/\|\s*Select-Object\s+-(?:Last|First)\s+\d+/i.test(command))
128
+ return true;
129
+ }
130
+ return false;
131
+ }
132
+ function shellBlockReason(command) {
133
+ const cmd = stripQuoted(command).trim();
134
+ const diffRest = gitSubcommandRest(cmd, 'diff');
135
+ if (diffRest != null && !hasGitBounds('diff', diffRest)) {
136
+ return 'Unbounded `git diff` floods context. Re-run with `git diff --stat` first, then `git diff -U20 -- <path>` on the file you will edit.';
137
+ }
138
+ const showRest = gitSubcommandRest(cmd, 'show');
139
+ if (showRest != null && !hasGitBounds('show', showRest)) {
140
+ return 'Unbounded `git show` floods context. Re-run with `git show --stat <ref>`, `-U20`, or `<ref>:<path>`.';
141
+ }
142
+ const logRest = gitSubcommandRest(cmd, 'log');
143
+ if (logRest != null && !hasGitBounds('log', logRest)) {
144
+ return 'Unbounded `git log` floods context. Re-run with `git log -n 20 --oneline`, `git log -5`, or a path-scoped log.';
145
+ }
146
+ if (/\bdocker\s+logs\b/i.test(cmd) && !/--tail\b/i.test(cmd)) {
147
+ return 'Unbounded `docker logs` floods context. Re-run with `docker logs --tail 100 <container>`.';
148
+ }
149
+ if (/\bjournalctl\b/i.test(cmd) && !/-n\s*\d+/i.test(cmd) && !/--lines\b/i.test(cmd)) {
150
+ return 'Unbounded `journalctl` floods context. Re-run with `journalctl -n 100` or `-u <unit> -n 50`.';
151
+ }
152
+ const logDump = /\b(cat|type)\s+[^\s|;&]+\.(log|txt)\b/i.test(cmd) || /\bGet-Content\b[^\n|;&]*\.(log|txt)\b/i.test(cmd);
153
+ if (logDump && !boundedLogCommand(cmd)) {
154
+ return 'Full log dumps flood context. Re-run with `Get-Content file.log -Tail 80`, `... | Select-Object -Last 80` (PowerShell), or `tail -n 80 file.log` (Bash).';
155
+ }
156
+ return undefined;
157
+ }
158
+ async function beforeTool(input, output) {
159
+ const args = (output.args ?? {});
160
+ const tool = normalizeToolName(input.tool);
161
+ if ((tool === 'write' || tool === 'edit' || tool === 'apply_patch') && isRealEnvPath(toolFilePath(args))) {
162
+ throw new Error('Vault only: do not edit or write .env files. Use /secrets for runtime config.');
163
+ }
164
+ if (tool === 'bash') {
165
+ const reason = shellBlockReason(String(args.command ?? ''));
166
+ if (reason)
167
+ throw new Error(reason);
168
+ }
169
+ }
170
+ async function shellEnv(_input, output) {
171
+ output.env = output.env ?? {};
172
+ output.env.MMI_AGENT_SURFACE = 'opencode';
173
+ const version = readPluginVersion();
174
+ if (version)
175
+ output.env.MMI_OPENCODE_PLUGIN_VERSION = version;
176
+ }
177
+ async function compacting(_input, output) {
178
+ output.context = output.context ?? [];
179
+ output.context.push('MMI continuity: preserve issue/PR refs, saga and North Star anchors, blockers, decisions, verification state, and NEXT.');
180
+ }
181
+ async function MmiOpenCodePlugin(_ctx, rawOptions) {
182
+ const options = {
183
+ skillsPath: rawOptions?.skillsPath ?? DEFAULT_SKILLS_PATH,
184
+ commandAgent: rawOptions?.commandAgent ?? DEFAULT_COMMAND_AGENT,
185
+ };
186
+ return {
187
+ config: (cfg) => ensureConfig(cfg, options),
188
+ 'shell.env': shellEnv,
189
+ 'tool.execute.before': beforeTool,
190
+ 'experimental.session.compacting': compacting,
191
+ };
192
+ }
193
+ export default MmiOpenCodePlugin;
194
+ export { MmiOpenCodePlugin };
package/package.json ADDED
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "@mutmutco/opencode-mmi",
3
+ "version": "2.48.0",
4
+ "description": "MMI Future OpenCode adapter — registers mmi, secrets, stage, rcand, release, hotfix, bootstrap, grind, build, handoff, coop, and browser-automation skills, workflow commands, and deterministic guardrail hooks.",
5
+ "type": "module",
6
+ "license": "UNLICENSED",
7
+ "author": {
8
+ "name": "MMI Future",
9
+ "email": "suphi@mutatismutandis.co"
10
+ },
11
+ "homepage": "https://github.com/mutmutco/MMI-Hub#readme",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/mutmutco/MMI-Hub.git",
15
+ "directory": "plugins/opencode-mmi"
16
+ },
17
+ "bugs": {
18
+ "url": "https://github.com/mutmutco/MMI-Hub/issues"
19
+ },
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "files": [
27
+ "dist",
28
+ "skills"
29
+ ],
30
+ "publishConfig": {
31
+ "access": "public"
32
+ },
33
+ "scripts": {
34
+ "build": "tsc -p tsconfig.json",
35
+ "typecheck": "tsc --noEmit"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^22.0.0",
39
+ "typescript": "^5.7.0"
40
+ },
41
+ "engines": {
42
+ "node": ">=22"
43
+ }
44
+ }
@@ -0,0 +1,238 @@
1
+ # Shared build/grind doctrine
2
+
3
+ Single source for doctrine shared by `/grind` and `/build`. Both skills reference this file — do not duplicate these blocks in `SKILL.md`.
4
+
5
+ ## Fusion doctrine
6
+
7
+ **Fusion** is one shape used everywhere — planning, exploration, and verification:
8
+
9
+ > **2–3 cross-vendor agents tackle the same hard problem in parallel → a judge synthesizes the best parts into one outcome.**
10
+
11
+ - **Planning / explore:** N parallel planner sub-agents → verifier-tier **judge** fuses approaches into one plan + criteria.
12
+ - **Verification:** N parallel lenses on the same diff → **synthesizer** reconciles into one `PanelReport`.
13
+
14
+ Fusion is **not** assigning different models to different pipeline steps (builder on step 1, verifier on step 2 as a workflow handoff). It is **parallel independent work on the same artifact, then reconcile**.
15
+
16
+ Hard rules:
17
+
18
+ - Fusion is **planner-judge with vendor diversity**, not "different models for different steps".
19
+ - **Verifier (or judge) is never the builder.** Hard lenses run at verifier tier minimum.
20
+ - When the host exposes multiple vendors, prefer maximum spread on each fusion round. Document any gap honestly — never claim fusion ran cross-vendor when only one vendor was available.
21
+ - Invoking `/grind`, `/grind --auto`, or `/build` authorizes the planner, verifier, judge, and synthesizer panels those skills require, within their normal bounds. On Codex, use `multi_agent_v1` for standard panels when that tool is available.
22
+ - A single-vendor host degrades gracefully: different temperatures or repeat passes; still run the synthesizer. Note degradation in saga and halt/end-of-grind reports.
23
+
24
+ Use fusion on the hard parts — plan choices, ambiguous specs, security-critical surfaces, verification rounds at checkpoints. Do not spend fusion tokens on trivial slices.
25
+
26
+ ## Panel evidence contract
27
+
28
+ - Use the host's multi-agent panel mechanism when it is available. Degrade only when the tool is unavailable, a spawn is denied/null, or the user explicitly disables delegation/panels.
29
+ - State degraded fallback explicitly in saga notes and final reports. A degraded round is evidence with a ceiling, not a normal clean panel.
30
+ - `mmi-cli verify synthesize` is a reconciliation helper for real verifier lane JSON. It must not replace an available host panel, and it must not be fed empty input or controller-authored all-pass stubs.
31
+ - Two stable clean rounds count only when the required verifier process actually produced the lens outputs. Empty, missing, or self-authored pass JSON is invalid fresh-eyes evidence.
32
+
33
+ ## Parallelism doctrine
34
+
35
+ **Maximum parallelism is the default.** Fan out independent planners, lenses, sub-agents, sites, and research concurrently. Serialize only where work shares seams (same files, ordering dependencies, shared generated artifacts such as `cli/dist/*`).
36
+
37
+ - **Independent sites / issues** → parallel loops in separate worktrees, each its own PR.
38
+ - **Planners on one hard part** → parallel fan-out, then judge fuses.
39
+ - **Verification lenses** → always parallel; synthesizer runs once after.
40
+ - **Research spikes** → parallel sub-agents when more than one angle needs grounding.
41
+
42
+ When parallel work collides on a generated artifact at merge time, the second-mover **rebases, rebuilds the artifact, re-verifies**, then merges — never resolve the artifact conflict by hand.
43
+
44
+ **Batch by default.** Each tier's `parallelSitesCap` (`build-policy.ts`) is a ceiling to fill, not a number to stay under. When independent slices exist, **batch them up to the cap** rather than serializing one at a time — group same-shaped edits, fan out side-issue fixes, and claim several board refs in one `board claim` call. Serialize only where work shares seams. Lean toward more batching whenever the slices are genuinely independent.
45
+
46
+ ## Effort tiers (shared)
47
+
48
+ Both `/grind` and `/build` self-select one of four effort tiers — **`light` · `standard` · `deep` · `ultra`** — encoded in `build-policy.ts` (`pickEffortTier`, `getTierBudgets`). The tier scales agent count, planner/fusion count, `parallelSitesCap`, verification depth, and reasoning effort together.
49
+
50
+ - Tiers are auto-selected from signals (scope, risk, ambiguity, blast radius, foundational depth). When unsure, default `standard`.
51
+ - **Explicit flags force a tier on either skill:** `--light`, `--standard`, `--deep`, `--ultra`. An explicit flag always wins over auto-selection.
52
+ - `--ultra` is the top tier = whole-loop YOLO (max planners, fusion on hard parts, deepest verification, highest reasoning). It is **distinct from auto-ultra** (the automatic verify-panel uplift, which raises verification only).
53
+ - Inspect a tier: `mmi-cli build tier [--scope … --risk …] [--explicit <tier>]`.
54
+
55
+ ## Model economy
56
+
57
+ **Prefer the lightest model that clears the bar.** Reach for a heavier model only when the work needs it — a hard plan, an ambiguous spec, a security-critical lens, a failing verify round. Routine builder turns, mechanical edits, and light-tier slices run on a lighter model. Reserve top-tier models for fusion planners, hard lenses, and `ultra`-tier sites. Spending a frontier model on a one-liner is waste, not rigor.
58
+
59
+ **Fusion single-API models (opencode `fugu` / `fugu-ultra`, `codex-fugu`):** these already integrate multiple models behind one API, so the cross-vendor panel the skills would otherwise spawn is **built into the model**. On a fusion model:
60
+
61
+ - **Cap the tier at `light`** — never run higher (`capTierForModel` in `build-policy.ts`). For now only `fugu`, `fugu-ultra` (opencode) and `codex-fugu` are fusion models.
62
+ - **Give them the most straightforward method** — single straight-through pass, **no** multi-agent planner/lens fan-out. The fusion is internal; an external panel is redundant cost.
63
+ - Detect by active model id, or let the opencode plugin pass the signal explicitly when it lands.
64
+
65
+ ## Subagent depth cap (flat fan-out)
66
+
67
+ Claude Code **2.1.181** hard-caps subagents at **5 levels of nesting depth**; foreground subagents respect the same limit.
68
+
69
+ **Flat fan-out rule:**
70
+
71
+ - The **orchestrator** spawns planners, lenses, research spikes, and side-issue fixes as **siblings** — never nested chains (child spawns grandchild spawns great-grandchild).
72
+ - Keep fan-out **wide, not deep** — never let nesting approach 5 levels.
73
+ - If a panel member is missing because depth truncated the fan-out, treat that as **degraded evidence** (see classifier-denied handling below), not a clean round.
74
+
75
+ Concrete ownership: only the top-level orchestrator (the skill invoker) launches fusion planners and verify lenses. Sub-agents return JSON or markdown; they do **not** spawn their own sub-panels unless the skill explicitly says otherwise for that one role.
76
+
77
+ **Sub-agent continuation is host-scoped (#1943/#1956).** `SendMessage` can continue a previously-spawned agent only on hosts that expose it to the top-level orchestrator; it is **not** in a sub-agent's toolset, and Claude Code has surfaced misleading Agent-tool footers for a continuation tool that is not callable there. So a sub-agent never messages or continues a peer. It **returns its result** and the orchestrator either uses a host-exposed continuation tool or re-spawns a fresh sub-agent with a compact handoff packet: issue/PR refs, branch/worktree, changed files, objective, what passed/failed, and exact NEXT. Cross-agent / cross-PC coordination is **`/coop`**, never `SendMessage` or ad-hoc messaging. Keep this narration out of user-facing output — a one-line `saga note` at most.
78
+
79
+ ## Classifier-denied spawn handling
80
+
81
+ Claude Code **2.1.178**: in auto mode, subagent spawns are evaluated by a classifier before launch. A spawn can return **denied** or **null**.
82
+
83
+ **Degraded round — not clean:**
84
+
85
+ - A denied, null, or missing planner/lens/research sub-agent is a **gap**, not a pass.
86
+ - Log the gap: `mmi-cli saga note "spawn-denied role=<planner|lens|research> round=<n>"`.
87
+ - Surface gaps in halt reports, end-of-grind summaries, and build halt reports.
88
+ - A round with any denied/null spawn does **not** count toward **two synthesis-stable clean rounds**.
89
+ - When aggregating parallel results, use explicit null handling — `.filter(Boolean)` alone is insufficient unless paired with a gap count check; a denied spawn must never inflate a clean count.
90
+ - Retry once with a narrower scope (fewer parallel spawns, smaller diff pin) when useful; if still denied, proceed with fewer lenses and document the ceiling.
91
+
92
+ ## Panel economics
93
+
94
+ Why panel + synthesize beats solo self-review: parallel lenses produce independent JSON; synthesis **reconciles** (dedupes blockers, surfaces contradictions) into a `PanelReport` — **synthesize-and-reconcile**, not majority vote or debate. Cross-vendor pairs beat same-model self-fusion — hence verifier **≠** builder.
95
+
96
+ **Routine default (no explicit `--ultra`, auto-ultra false):** **2 models** — builder + verifier (synthesizer = verifier).
97
+
98
+ **Relative verify cost (qualitative):** `Budget` < `Balanced` < `Paranoid` < `Ultra`.
99
+
100
+ **Variance:** a single clean verify round can miss blockers. Loops require **2 consecutive synthesis-stable clean rounds** (identical blocker `id` sets; empty counts as stable).
101
+
102
+ **Measure-first / cost ceiling.** Before unsupervised running, print worst-case **agent-call proxy** via `mmi-cli grind estimate` (includes **2 stable-round** minimum; not a dollar promise). On projected exceed → ask human (cap/stuck path); honor `GRIND_COST_CEILING` from `grind-policy.ts`.
103
+
104
+ **Terminal done hierarchy** (ordered — never skip a lower layer):
105
+
106
+ 1. Repo checks / CI green (objective gate)
107
+ 2. Two synthesis-stable clean rounds (quality layer)
108
+ 3. Human merge authority (grind Gate 2; `--auto` / build auto-merge into `development` only)
109
+
110
+ "Two clean rounds" alone is never sufficient without layer 1.
111
+
112
+ **Single-model host:** lenses may share one model; **still run the synthesizer** — self-fusion uplift remains.
113
+
114
+ ## Verify tiers
115
+
116
+ Local verify and CI gate serve different jobs — don't clone the full CI matrix on every edit.
117
+
118
+ **While editing (loop):**
119
+
120
+ - Scope to the touched package and files (`vitest run <paths>`, `tsc --noEmit` in one package).
121
+ - Apply the same path filters as the repo's `gate.yml` jobs — don't run `infra` for a `cli`-only diff (and vice versa).
122
+ - Grind/build checkpoints: **light verify only** (targeted tests, requirement slice, single lens when stakes are high).
123
+
124
+ **Before PR / claiming done (final):**
125
+
126
+ - Run the **full gate mirror once** for every `gate.yml` job your diff would trigger — read each job's `relevant-pattern`.
127
+ - Include artifact-parity checks the gate enforces (e.g. committed `cli/dist/` must match `npm run build`).
128
+ - After the PR's CI is green, **do not** re-run the full local suite unless code changed again — CI is merge authority (layer 1 above).
129
+
130
+ **Humans:** may push and watch CI instead of a local full mirror when willing to trade slower feedback for less duplicate work; agents still owe the once-before-PR mirror unless the repo defines a lighter contract.
131
+
132
+ ## Blocker tiers
133
+
134
+ Every blocker is classified before action. **When unsure: treat as hard.** Never guess.
135
+
136
+ - **Solvable** — clear fix, in scope, within authority. File, `board claim --for <login>`, fix, PR, merge into `development`, continue.
137
+ - **Hard-decision** — needs a call the loop cannot make alone (auth model, schema migration, public API shape, vendor choice, prod-touching change, master-gated ops). File with full context and options; **park** the site/slice; keep the frontier moving.
138
+ - **Genuinely-not-grindable** — privileged master-only ops, pure coordination with no PR deliverable, work outside authority. Surface in halt report; do not fake a PR.
139
+
140
+ Build encodes classification signals in `build-policy.ts`; grind uses the same vocabulary.
141
+
142
+ ## Worktree + node_modules hygiene
143
+
144
+ Org-wide session doctrine lives in **`AGENTS.md` § Session workflow** — grind/build own git + memory; `mmi-cli wave status` for visibility. This block covers grind/build-specific hygiene only.
145
+
146
+ - **Trivial-task escape hatch.** A genuinely tiny, low-risk change — a one-line edit, a typo, a string tweak — does **not** need a worktree or even a feature branch. Spinning a full worktree for a ten-letter one-liner is waste. Edit on a normal branch (or directly where the user is working), verify, PR. Use a worktree once the change has real scope, parallel sites, or `/stage` needs. When unsure whether it's trivial, use a worktree.
147
+ - Branch from latest `origin/development` into `../mmi-worktrees/<branch>` — **never edit the main checkout** during grind/build runs.
148
+ - One worktree per grind/build run until integration boundary; multi-worktree waves merge via `mmi-cli wave land`.
149
+ - Return to the main checkout mid-run only for a new unrelated branch, real base drift, final integrated validation, cleanup after merge, explicit user request, or a broken/unsafe worktree. Otherwise keep implementation, verification, commits, PR prep, saga, and North Star updates in the active worktree.
150
+ - A local `/stage` belongs to the active worktree that started it. Do not disrupt a running stage for git bookkeeping alone. Before moving to a different worktree, stop/destroy the stage and recreate it from the new worktree, or warn first when the user's intent is unclear. Linked worktrees share stage state through the git common dir, so a new worktree can still stop the previous local stage.
151
+ - Give each worktree its **own** `node_modules` via clean `npm ci` — never symlink/junction into the main checkout's `node_modules` (junctions can be followed by teardown and destroy the main checkout).
152
+ - **Cross-platform lockfile — validate on the CI OS, not the build OS (#1840).** When a build/grind on **Windows** regenerates `package-lock.json` (any `npm install` — e.g. adding a workspace or devDep), npm 11 **prunes Linux-only optional deps** (`@emnapi/*`, the `@node-rs/argon2` / `@tailwindcss/oxide` `*-wasm32-wasi` fallbacks). Local `npm ci` + the gate then pass on Windows while the Linux CI runner's `npm ci` dies `EUSAGE` (`Missing: @emnapi/core@… from lock file`) in ~11s — **local-OS green never proves a cross-platform lockfile.** Before merge, regenerate **and** validate the lock in a Linux container matching CI's Node (`node:24` at present): `docker run --rm -v "${PWD}:/app" -w /app node:<ci-node> npm install --package-lock-only --ignore-scripts`, then validate with `node_modules`-shadowed anonymous volumes — `docker run --rm -v "${PWD}:/app" -v /app/node_modules -w /app node:<ci-node> sh -c "npm ci && npm run check"`. The container lock is a win32+linux+wasm **superset** that satisfies Linux CI (local Windows `npm ci` may then reject it on an npm-minor mismatch — CI is the gate).
153
+ - Remove the worktree's `node_modules` **before** ordinary `pr merge`; run `git status` on the main checkout immediately **after** every merge. If `pr land/merge --preserve-worktree` is used for an explicit active batch, leave the worktree/stage in place and do that cleanup at the batch boundary (#1888).
154
+ - **Worktree tests run from the worktree cwd.** A subagent's green run is not reproducible evidence unless `cwd` is that worktree. The orchestrator must independently reproduce subagent green from the same worktree cwd before merge.
155
+ - **Worktree base freshness (#1906):** Before `pr create`, and at long-run Phase 1 checkpoints when the base may have moved: `git -C <wt> fetch origin && git -C <wt> merge-base --is-ancestor origin/<base> HEAD`. If false, rebase onto the updated base, rebuild checked-in artifacts if the diff touches them, re-run repo checks, then push/PR. Cross-repo grinds: each worktree checks independently.
156
+
157
+ ## Delegated verify+commit (#1595)
158
+
159
+ **Re-test status (2026-06-19, #1705):** Reviewed against Claude Code CHANGELOG **2.1.178** ("backgrounding no longer restarts from scratch") and **2.1.183** (teammate background-task kill fix). **No live Claude Code repro in this session** (Cursor host). Rule **confirmed in force** pending live repro on Claude Code — see #1705.
160
+
161
+ Until repro proves otherwise:
162
+
163
+ - When delegating verify-then-commit to a sub-agent on **Claude Code**, run the authoritative merge-gate suite **in the foreground** and **not return** until committed and pushed — or report a concrete failure.
164
+ - Never background the full test suite inside a sub-agent whose turn can end before results arrive; that strand leaves an uncommitted worktree.
165
+ - **(Recommended)** the orchestrator owns the final verify+commit on the worktree rather than delegating both.
166
+ - On surfaces without Claude's background-strand fixes, treat foreground-only as the floor regardless of changelog.
167
+
168
+ ## Saga snapshot / resume
169
+
170
+ Loop memory composes with saga HEAD — enforced via **`mmi-cli saga snapshot`**.
171
+
172
+ - **Read-first on resume:** `mmi-cli saga snapshot show --kind <grind|build>` (`--json` for machine use). Reconcile with diff + `PanelReport` — never collapsed chat.
173
+ - **Write-last each phase:** one-line `mmi-cli saga note "<audit>"` **plus** `mmi-cli saga snapshot set --kind <grind|build>` (or `--json-file`).
174
+ - **Hygiene:** bounded summary only; one-line phase notes = append-only trail beneath HEAD.
175
+
176
+ Grind schema: `skills/grind/templates/saga-snapshot.md`. Build uses `--kind build`.
177
+
178
+ ## Autonomy ladder (shared vocabulary)
179
+
180
+ | Level | Grind | Build |
181
+ |-------|-------|-------|
182
+ | **L1** suggest only | rare | rare |
183
+ | **L2** draft for human | — | — |
184
+ | **L3** apply + human merges | interactive default (Gate 2) | — |
185
+ | **L4** apply + auto-complete + audit log | `--auto` | auto by default (Phase 0 authorization) |
186
+
187
+ L4 audit expectation: silent clean-run retro; cap/stuck terminate-and-report.
188
+
189
+ ## Epistemic honesty
190
+
191
+ Not knowing is fine; **pretending is banned**.
192
+
193
+ - Never claim cross-vendor fusion when only one vendor was available — name the gap.
194
+ - Never claim "live-verified" when only "built + tested" or "CI green" is true.
195
+ - Never invent sources, paths, line numbers, or API contracts.
196
+ - Every halt/round report states the **verification ceiling** (what was actually checked vs assumed).
197
+
198
+ **Verification ceiling ladder:**
199
+
200
+ | Rung | Means | Loop can reach? |
201
+ |------|-------|-----------------|
202
+ | **built** | compiles / typechecks on the worktree | yes |
203
+ | **tested** | repo local tests + lints pass | yes |
204
+ | **CI green** | full gate green on the PR | yes |
205
+ | **merged** | landed into `development` | yes (auto paths) |
206
+ | **live-verified** | real deployed environment | **no** — outside grind/build ceiling |
207
+
208
+ ## No shortcuts
209
+
210
+ Hold the quality bar. Re-tackle and rebuild rather than patch to green.
211
+
212
+ - If verification keeps failing on the same root cause, **rebuild the design**, do not stack patches.
213
+ - If a test had to be loosened to pass, that is a blocker.
214
+ - Patch-to-green costs the next orient its evidence and the milestone its quality.
215
+
216
+ ## Self-learning + retro
217
+
218
+ Both skills file process uplifts via `mmi-cli skill-lesson --skill <grind|build>` — at most **one** lesson per run; lands on Hub board; fixed only via reviewed PR — never edit a skill live.
219
+
220
+ **Retro** (after PR / halt / cap-stuck): did the **loop itself** misfire? Process failures only — not code bugs the panel caught. Clean run → skip silently.
221
+
222
+ ## Enforcement matrix (harness vs skill)
223
+
224
+ Some floors are **harness-enforced** on Claude Code; others remain **skill-enforced** on all surfaces.
225
+
226
+ | Rule | Claude Code harness | Skill text (all surfaces) |
227
+ |------|---------------------|---------------------------|
228
+ | No edit/write real `.env` | `scripts/vault-edit-gate.mjs` PreToolUse on `Edit\|Write` | Vault only — `/secrets` |
229
+ | Destructive git (`add -A`, amend pushed, etc.) | Native blocking since **2.1.183** | Keep one-line pointer; do not duplicate full prose |
230
+ | Worktree-only edits during run | **Deferred** — needs reliable session marker (#1706 residual) | Skill-enforced: edit worktree only |
231
+ | Read/Shell throttle | `throttle-gate.mjs` | Tool-economy rule |
232
+ | Shell dialect mismatch | `shell-redirect-lint.mjs` | AGENTS naming/shell |
233
+
234
+ Trim skill prose that re-asserts harness-owned git guardrails; point at this matrix instead.
235
+
236
+ ## Cross-surface note
237
+
238
+ These skills are host-agnostic. Each surface (Claude Code, Codex, Cursor Composer) spawns sub-agents with its own mechanism and model list. Fusion, gates, tier engine, and CLI calls are identical everywhere. All behavior runs through `mmi-cli`; no surface-specific plumbing beyond hook availability noted in the enforcement matrix.