mindforge-cc 11.9.2 → 11.9.3

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 (94) hide show
  1. package/.agent/CLAUDE.md +37 -13
  2. package/.agent/hooks/mindforge-block-no-verify.js +61 -13
  3. package/.agent/hooks/mindforge-config-protection.js +82 -3
  4. package/.agent/hooks/mindforge-context-monitor.js +1 -1
  5. package/.agent/hooks/mindforge-workflow-guard.js +2 -2
  6. package/.agent/hooks/run-with-flags.js +190 -20
  7. package/.agent/mindforge/browse.md +2 -2
  8. package/.agent/mindforge/checkpoint.md +1 -1
  9. package/.agent/mindforge/harness-audit.md +1 -1
  10. package/.agent/mindforge/orch-add-feature.md +1 -1
  11. package/.agent/mindforge/orch-build-mvp.md +1 -1
  12. package/.agent/mindforge/orch-change-feature.md +1 -1
  13. package/.agent/mindforge/orch-fix-defect.md +1 -1
  14. package/.agent/mindforge/orch-refine-code.md +1 -1
  15. package/.agent/mindforge/qa.md +2 -2
  16. package/.claude/CLAUDE.md +37 -13
  17. package/.claude/commands/mindforge/browse.md +2 -2
  18. package/.claude/commands/mindforge/checkpoint.md +1 -1
  19. package/.claude/commands/mindforge/harness-audit.md +1 -1
  20. package/.claude/commands/mindforge/orch-add-feature.md +1 -1
  21. package/.claude/commands/mindforge/orch-build-mvp.md +1 -1
  22. package/.claude/commands/mindforge/orch-change-feature.md +1 -1
  23. package/.claude/commands/mindforge/orch-fix-defect.md +1 -1
  24. package/.claude/commands/mindforge/orch-refine-code.md +1 -1
  25. package/.claude/commands/mindforge/qa.md +2 -2
  26. package/.mindforge/MINDFORGE-SCHEMA.json +1 -1
  27. package/.mindforge/config.json +3 -3
  28. package/.mindforge/engine/autonomous/headless-adapter.md +2 -2
  29. package/.mindforge/engine/temporal-protocol.md +2 -2
  30. package/.mindforge/governance/change-classifier.md +20 -4
  31. package/.mindforge/memory/sync-manifest.json +1 -1
  32. package/.mindforge/skills/agent-architecture-audit/SKILL.md +2 -2
  33. package/.mindforge/skills/orch-pipeline/SKILL.md +4 -4
  34. package/CHANGELOG.md +194 -0
  35. package/MINDFORGE.md +13 -6
  36. package/README.md +4 -3
  37. package/RELEASENOTES.md +2 -2
  38. package/SECURITY.md +22 -3
  39. package/bin/autonomous/auto-runner.js +65 -2
  40. package/bin/change-classifier.js +151 -16
  41. package/bin/dashboard/api-router.js +18 -38
  42. package/bin/dashboard/frontend/app.js +429 -0
  43. package/bin/dashboard/frontend/index.html +13 -406
  44. package/bin/dashboard/metrics-aggregator.js +46 -22
  45. package/bin/dashboard/server.js +160 -1
  46. package/bin/dashboard/sse-bridge.js +11 -8
  47. package/bin/engine/sre-manager.js +1 -1
  48. package/bin/engine/temporal-cli.js +56 -6
  49. package/bin/engine/verification-runner.js +134 -17
  50. package/bin/engine/verify-cli.js +25 -7
  51. package/bin/governance/approval-record.js +147 -0
  52. package/bin/governance/approve.js +12 -7
  53. package/bin/governance/policy-engine.js +33 -3
  54. package/bin/governance/policy-gate-hardened.js +36 -1
  55. package/bin/governance/verify-approvals.js +163 -0
  56. package/bin/harness-audit.js +224 -10
  57. package/bin/hooks/instinct-capture-hook.js +12 -4
  58. package/bin/install.js +63 -3
  59. package/bin/installer/harness-adapter-compliance.js +339 -28
  60. package/bin/installer/hook-registration.js +504 -0
  61. package/bin/installer-core.js +451 -63
  62. package/bin/learning/instinct-cli.js +7 -0
  63. package/bin/memory/vector-hub.js +196 -13
  64. package/bin/migrations/0.6.0-to-1.0.0.js +30 -25
  65. package/bin/migrations/1.0.0-to-2.0.0.js +22 -23
  66. package/bin/mindforge-cli.js +67 -6
  67. package/bin/models/cost-tracker.js +104 -6
  68. package/bin/models/model-client.js +6 -1
  69. package/bin/revops/debt-monitor.js +57 -13
  70. package/bin/security/trust-gate-hook.js +50 -6
  71. package/bin/skill-validator.js +6 -1
  72. package/bin/skills-builder/skill-scorer.js +46 -6
  73. package/bin/updater/self-update.js +6 -1
  74. package/bin/updater/version-comparator.js +21 -1
  75. package/bin/utils/mindforge-version.js +99 -0
  76. package/bin/utils/redact-secrets.js +106 -0
  77. package/bin/validate-config.js +42 -2
  78. package/bin/wizard/setup-wizard.js +4 -1
  79. package/bin/wizard/theme.js +9 -1
  80. package/changelogs/index.json +11 -9
  81. package/changelogs/v11.9.3.md +195 -0
  82. package/docs/References/config-reference.md +5 -2
  83. package/docs/References/sdk-api.md +1 -1
  84. package/docs/Templates/Codebase/architecture.md +1 -1
  85. package/docs/commands-reference.md +4 -5
  86. package/docs/faq.md +25 -5
  87. package/docs/getting-started.md +3 -3
  88. package/docs/sdk-reference.md +15 -7
  89. package/docs/troubleshooting.md +10 -6
  90. package/docs/user-guide.md +14 -14
  91. package/examples/sdk-integration/README.md +1 -1
  92. package/package.json +7 -3
  93. package/subagents/.claude-plugin/marketplace.json +1 -1
  94. package/bin/dashboard/approval-handler.js +0 -136
@@ -41,8 +41,21 @@ const REQUIRED_FIELDS = Object.freeze([
41
41
  'last_verified_at',
42
42
  'owner',
43
43
  'source_docs',
44
+ // install_claims is the machine-checkable half of the record. Before it existed, every claim in
45
+ // this file was prose that only `--check` looked at, and `--check` compares the doc against these
46
+ // records — so a record that was false about the installer propagated INTO the documentation and
47
+ // nothing ever compared either side to a real install. See tests/harness-emitted-tree.test.js.
48
+ 'install_claims',
44
49
  ]);
45
50
 
51
+ function deepFreeze(value) {
52
+ if (Array.isArray(value)) return Object.freeze(value.map(deepFreeze));
53
+ if (value && typeof value === 'object') {
54
+ return Object.freeze(Object.fromEntries(Object.entries(value).map(([k, v]) => [k, deepFreeze(v)])));
55
+ }
56
+ return value;
57
+ }
58
+
46
59
  function freezeRecord(record) {
47
60
  return Object.freeze({
48
61
  ...record,
@@ -52,100 +65,325 @@ function freezeRecord(record) {
52
65
  verification_commands: Object.freeze(record.verification_commands.slice()),
53
66
  risk_notes: Object.freeze(record.risk_notes.slice()),
54
67
  source_docs: Object.freeze(record.source_docs.slice()),
68
+ install_claims: deepFreeze(record.install_claims),
55
69
  });
56
70
  }
57
71
 
72
+ /**
73
+ * The seven asset trees the installer copies into a harness dir, one per subdir key declared in that
74
+ * harness's RUNTIMES entry (bin/installer-core.js:604-627 — `if (subDir && fsu.exists(asset.src))`,
75
+ * so a harness that declares no key silently receives nothing).
76
+ *
77
+ * Every count below was MEASURED on `git clone --no-hardlinks` of 963902d followed by
78
+ * `node bin/install.js --<runtime> --local` in a fresh mktemp -d with HOME also confined to a fresh
79
+ * mktemp -d (the installer writes ~/.mindforge/registry.json — bin/installer-core.js:835).
80
+ * They are floors, not equalities, so adding a skill does not turn the suite red; a *missing* tree
81
+ * counts 0 and fails. `verbatim_subset_of` is the content half: every file under that repo-relative
82
+ * source dir must exist in the emitted dir with byte-identical content, so a stale or truncated copy
83
+ * fails even though its name and count are unchanged.
84
+ */
85
+ function sharedAssetTrees(base, { hooks = 11 } = {}) {
86
+ return [
87
+ { asset: 'skills', dir: `${base}/skills`, min_files: 282, verbatim_subset_of: '.agent/skills' },
88
+ { asset: 'hooks', dir: `${base}/hooks`, min_files: hooks, verbatim_subset_of: '.agent/hooks' },
89
+ { asset: 'personas', dir: `${base}/personas`, min_files: 218, verbatim_subset_of: '.mindforge/personas' },
90
+ { asset: 'docs references', dir: `${base}/docs/references`, min_files: 20, verbatim_subset_of: 'docs/References' },
91
+ { asset: 'docs templates', dir: `${base}/docs/templates`, min_files: 42, verbatim_subset_of: 'docs/Templates' },
92
+ { asset: 'memory specs', dir: `${base}/memory`, min_files: 4, verbatim_subset_of: '.mindforge/memory' },
93
+ { asset: 'plugin specs', dir: `${base}/plugins`, min_files: 4, verbatim_subset_of: '.mindforge/plugins' },
94
+ ];
95
+ }
96
+
97
+ /**
98
+ * Commands come from two sources: `.agent/mindforge` (221 files) and `.agent/forge` (3 files).
99
+ *
100
+ * THIS RECORD USED TO DOCUMENT A DATA-LOSS BUG AS A SPECIFICATION. Both sources were written into the
101
+ * same directory under their bare filenames, forge second, and all three forge names collide with
102
+ * mindforge names — so forge won. The previous version of this comment said so plainly ("The three
103
+ * forge names collide with mindforge names and WIN, which is why the total is 221 and not 224") and
104
+ * then encoded it: `min_files: 221` and a probe asserting `help.md` was byte-identical to
105
+ * `.agent/forge/help.md`. Someone observed the overwrite, understood it, and pinned it as correct.
106
+ *
107
+ * What was actually being lost, measured against the sources:
108
+ * help.md 33 -> 11 lines, init-project.md 170 -> 36, plan-phase.md 131 -> 34. 253 lines, silently,
109
+ * on every install — and the three surviving forge commands pointed at `.claude/commands/forge/`,
110
+ * a directory the installer never created.
111
+ *
112
+ * The installer now gives each namespace its own destination (resolveCommandTarget in
113
+ * bin/installer-core.js), so nothing is overwritten. Measured after that change:
114
+ * claude / opencode / gemini / copilot commands/mindforge 221 files, sibling forge/ 3 files
115
+ * cursor (flat `rules/`) rules 224 files (forge prefixed `forge:`)
116
+ *
117
+ * So min_files is 221 for the namespaced harnesses — the probed directory holds mindforge's set — and
118
+ * 224 for cursor, where both land side by side. And `help.md` in the probed directory is now
119
+ * mindforge's, which is what the probe asserts.
120
+ *
121
+ * @param {string} asset
122
+ * @param {string} dir
123
+ * @param {number} [minFiles=221] 224 for harnesses whose commands directory is not per-namespace.
124
+ */
125
+ function flatCommandTree(asset, dir, minFiles = 221) {
126
+ return {
127
+ asset,
128
+ dir,
129
+ min_files: minFiles,
130
+ probes: [
131
+ { file: 'security-scan.md', identical_to: '.agent/mindforge/security-scan.md' },
132
+ { file: 'help.md', identical_to: '.agent/mindforge/help.md' },
133
+ ],
134
+ };
135
+ }
136
+
58
137
  const ADAPTER_RECORDS = Object.freeze([
59
138
  {
60
139
  id: 'claude-code',
61
140
  harness: 'Claude Code',
62
141
  state: 'Native',
63
- supported_assets: ['skills', 'commands (slash)', 'hooks', 'MCP config', 'subagents', 'settings.json', 'plugin assets'],
64
- unsupported_surfaces: ['Claude-native hooks do not imply parity in other harnesses'],
142
+ supported_assets: ['skills', 'commands (slash)', 'hooks (scripts + registered settings.json)', 'subagents', 'personas', 'docs references and templates', 'memory and plugin specs'],
143
+ unsupported_surfaces: [
144
+ 'Claude-native hooks do not imply parity in other harnesses',
145
+ 'No MCP config: the installer writes no .mcp.json and no mcpServers key for ANY harness — the only mcpServers strings in an emitted tree are inside two content files (.mindforge/schemas/plugin.schema.json and .claude/agents/scientific-literature-researcher.md)',
146
+ 'Commands arrive as TWO namespaces, not one: .claude/commands/mindforge/ (221 files) and .claude/commands/forge/ (3). The three forge basenames collide with mindforge names, so they get their own directory — which is also where .agent/forge/help.md says forge commands live. Until this was fixed the forge copies overwrote the mindforge ones in place, destroying 253 lines across help.md, init-project.md and plan-phase.md on every install',
147
+ ],
65
148
  install_or_onramp: ['`npx mindforge-cc@latest --claude --local`', 'Claude plugin install'],
66
- verification_commands: ['`node bin/harness-audit.js`', '`node bin/install.js --check`'],
149
+ // `node bin/install.js --check` was listed here and is an npm UPDATE check (bin/install.js:114
150
+ // "Check for updates without installing"), not an install verification — it exited 1 against a
151
+ // freshly installed project. Replaced with the gate that actually inspects the emitted tree.
152
+ verification_commands: ['`node bin/harness-audit.js`', '`node tests/harness-emitted-tree.test.js`'],
67
153
  risk_notes: ['Do not load every skill by default; keep hooks opt-in and inspectable.'],
68
- last_verified_at: '2026-06-10',
154
+ last_verified_at: '2026-08-19',
69
155
  owner: 'MindForge maintainers',
70
156
  source_docs: ['.claude/settings.json', '.claude-plugin/', 'bin/installer-core.js'],
157
+ install_claims: {
158
+ runtime: 'claude',
159
+ base_dir: '.claude',
160
+ entry_files: ['.claude/CLAUDE.md', 'CLAUDE.md'],
161
+ emits: [
162
+ flatCommandTree('commands (slash)', '.claude/commands/mindforge'),
163
+ // 16, not 11: REG-01 adds instinct/, security/ and utils/file-lock.js on top of the 11
164
+ // copied from .agent/hooks (bin/installer/hook-registration.js).
165
+ ...sharedAssetTrees('.claude', { hooks: 16 }),
166
+ {
167
+ asset: 'subagents',
168
+ dir: '.claude/agents',
169
+ min_files: 164,
170
+ probes: [{ file: 'backend-developer.md', identical_to: 'subagents/categories/01-core-development/backend-developer.md' }],
171
+ },
172
+ {
173
+ asset: 'settings.json (registered hooks)',
174
+ file: '.claude/settings.json',
175
+ contains: ['"PreToolUse"', '"PostToolUse"', '"SessionStart"', 'run-with-flags.js', 'mindforge-config-protection'],
176
+ },
177
+ ],
178
+ absent_dirs: [],
179
+ no_harness_surface_for: [],
180
+ claude_mirror: 'own',
181
+ },
71
182
  },
72
183
  {
73
184
  id: 'antigravity',
74
185
  harness: 'Antigravity (Gemini)',
75
186
  state: 'Adapter-backed',
76
- supported_assets: ['skills', 'commands (namespace:prefix)', 'rules', 'MCP reference config'],
77
- unsupported_surfaces: ['Command naming uses mindforge: namespace prefix; hook parity differs from Claude'],
187
+ // 'rules' and 'MCP reference config' were both false: a clean `--antigravity --local` emits no
188
+ // .agents/rules and no MCP config anywhere. What it DOES emit and the record omitted: hooks,
189
+ // personas, docs and the memory/plugin specs.
190
+ supported_assets: ['skills', 'commands (namespace:prefix, .agents/workflows/)', 'hooks (scripts only, unregistered)', 'personas', 'docs references and templates', 'memory and plugin specs', 'subagents via the shared .claude/ mirror'],
191
+ unsupported_surfaces: [
192
+ 'Command naming uses mindforge: namespace prefix; hook parity differs from Claude',
193
+ 'No .agents/settings.json is written, so the 11 copied hook scripts are inert — REG-01 registers claude + local only (bin/installer/hook-registration.js)',
194
+ 'No .agents/rules directory and no MCP config are emitted',
195
+ ],
78
196
  install_or_onramp: ['`npx mindforge-cc@latest --antigravity --local`'],
79
- verification_commands: ['`node bin/harness-audit.js`'],
197
+ verification_commands: ['`node bin/harness-audit.js`', '`node tests/harness-emitted-tree.test.js`'],
80
198
  risk_notes: ['Keep the .agent settings mirror in sync with .claude (Gemini mirror is live, not dead).'],
81
- last_verified_at: '2026-06-10',
199
+ last_verified_at: '2026-08-19',
82
200
  owner: 'MindForge maintainers',
83
201
  source_docs: ['.agent/settings.json', 'bin/installer-core.js'],
202
+ install_claims: {
203
+ runtime: 'antigravity',
204
+ base_dir: '.agents',
205
+ entry_files: ['.agents/CLAUDE.md', 'CLAUDE.md'],
206
+ emits: [
207
+ // 224, and the only transformed copy in the installer: frontmatter is re-emitted as a lone
208
+ // `description:` and the source frontmatter stripped (bin/installer-core.js:571-578), so this
209
+ // one cannot be checked byte-for-byte against its source.
210
+ {
211
+ asset: 'commands (namespace:prefix)',
212
+ dir: '.agents/workflows',
213
+ min_files: 224,
214
+ probes: [{ file: 'mindforge:security-scan.md', contains: ['description:', '# MindForge — Security Scan Command'] }],
215
+ },
216
+ ...sharedAssetTrees('.agents'),
217
+ ],
218
+ absent_dirs: [{ asset: 'subagents (harness dir)', dir: '.agents/agents' }],
219
+ no_harness_surface_for: ['rules', 'settings.json'],
220
+ claude_mirror: 'shared',
221
+ },
84
222
  },
85
223
  {
86
224
  id: 'opencode',
87
225
  harness: 'OpenCode',
88
226
  state: 'Adapter-backed',
89
- supported_assets: ['skills', 'commands (slash)', 'MCP config', 'event adapter patterns'],
90
- unsupported_surfaces: ['Event names and command dispatch differ from Claude Code'],
227
+ // 'MCP config' and 'event adapter patterns' were both false: no MCP config is emitted for any
228
+ // harness, and no OpenCode event-adapter file is emitted either — the 11 hook scripts under
229
+ // .opencode/hooks/ are byte-identical Claude-shaped scripts from .agent/hooks.
230
+ supported_assets: ['skills', 'commands (slash)', 'hooks (scripts only, unregistered)', 'personas', 'docs references and templates', 'memory and plugin specs', 'subagents via the shared .claude/ mirror'],
231
+ unsupported_surfaces: [
232
+ 'Event names and command dispatch differ from Claude Code',
233
+ 'No .opencode/settings.json is written, so the 11 copied hook scripts are inert — REG-01 registers claude + local only',
234
+ 'No MCP config and no OpenCode-shaped event-adapter files are emitted; the copied hooks are the unmodified Claude scripts',
235
+ ],
91
236
  install_or_onramp: ['`npx mindforge-cc@latest --opencode --local`'],
92
- verification_commands: ['`node bin/harness-audit.js`'],
237
+ verification_commands: ['`node bin/harness-audit.js`', '`node tests/harness-emitted-tree.test.js`'],
93
238
  risk_notes: ['Keep hook logic in shared scripts; adapt only event shape at the edge.'],
94
- last_verified_at: '2026-06-10',
239
+ last_verified_at: '2026-08-19',
95
240
  owner: 'MindForge maintainers',
96
241
  source_docs: ['bin/installer-core.js'],
242
+ install_claims: {
243
+ runtime: 'opencode',
244
+ base_dir: '.opencode',
245
+ entry_files: ['.opencode/CLAUDE.md', 'CLAUDE.md'],
246
+ emits: [
247
+ flatCommandTree('commands (slash)', '.opencode/commands/mindforge'),
248
+ ...sharedAssetTrees('.opencode'),
249
+ ],
250
+ absent_dirs: [{ asset: 'subagents (harness dir)', dir: '.opencode/agents' }],
251
+ no_harness_surface_for: ['settings.json'],
252
+ claude_mirror: 'shared',
253
+ },
97
254
  },
98
255
  {
99
256
  id: 'gemini',
100
257
  harness: 'Gemini CLI',
101
258
  state: 'Instruction-backed',
102
- supported_assets: ['project-local instructions (GEMINI.md)', 'skills', 'rules'],
103
- unsupported_surfaces: ['No full hook parity; slash surface differs; ports must document drift'],
259
+ // 'rules' was false (no .gemini/rules is emitted). The record also omitted the slash commands it
260
+ // DOES receive: 221 files under .gemini/commands/mindforge/ (supportsSlash:true in RUNTIMES).
261
+ supported_assets: ['project-local instructions (GEMINI.md)', 'skills', 'commands (slash)', 'hooks (scripts only, unregistered)', 'personas', 'docs references and templates', 'memory and plugin specs', 'subagents via the shared .claude/ mirror'],
262
+ unsupported_surfaces: [
263
+ 'No full hook parity; ports must document drift',
264
+ 'No .gemini/settings.json is written, so the 11 copied hook scripts are inert — REG-01 registers claude + local only',
265
+ 'No .gemini/rules directory and no MCP config are emitted',
266
+ ],
104
267
  install_or_onramp: ['`npx mindforge-cc@latest --gemini --local`'],
105
- verification_commands: ['`node bin/harness-audit.js`'],
268
+ verification_commands: ['`node bin/harness-audit.js`', '`node tests/harness-emitted-tree.test.js`'],
106
269
  risk_notes: ['Treat Gemini ports as ecosystem adapters until validated end-to-end inside Gemini CLI.'],
107
- last_verified_at: '2026-06-10',
270
+ last_verified_at: '2026-08-19',
108
271
  owner: 'MindForge maintainers',
109
272
  source_docs: ['bin/installer-core.js', '.agent/skills/mindforge-neural-orchestrator/references/gemini-tools.md'],
273
+ install_claims: {
274
+ runtime: 'gemini',
275
+ base_dir: '.gemini',
276
+ entry_files: ['.gemini/GEMINI.md', 'GEMINI.md', 'CLAUDE.md'],
277
+ emits: [
278
+ flatCommandTree('commands (slash)', '.gemini/commands/mindforge'),
279
+ ...sharedAssetTrees('.gemini'),
280
+ ],
281
+ absent_dirs: [{ asset: 'subagents (harness dir)', dir: '.gemini/agents' }],
282
+ no_harness_surface_for: ['rules', 'settings.json'],
283
+ claude_mirror: 'shared',
284
+ },
110
285
  },
111
286
  {
112
287
  id: 'cursor',
113
288
  harness: 'Cursor',
114
289
  state: 'Instruction-backed',
115
- supported_assets: ['Cursor rules', 'project-local skills', 'instruction entry file'],
116
- unsupported_surfaces: ['No slash-command surface (supportsSlash:false); hook events differ from Claude'],
290
+ // All three original claims were true; they were merely incomplete — the record named 3 of the
291
+ // 8 asset kinds a Cursor install actually receives.
292
+ supported_assets: ['Cursor rules (commands rendered into .cursor/rules/)', 'project-local skills', 'instruction entry file (.cursorrules)', 'hooks (scripts only, unregistered)', 'personas', 'docs references and templates', 'memory and plugin specs', 'subagents via the shared .claude/ mirror'],
293
+ unsupported_surfaces: [
294
+ 'No slash-command surface (supportsSlash:false); hook events differ from Claude',
295
+ 'No .cursor/settings.json is written, so the 11 copied hook scripts are inert — REG-01 registers claude + local only',
296
+ 'No MCP config is emitted',
297
+ ],
117
298
  install_or_onramp: ['`npx mindforge-cc@latest --cursor --local`'],
118
- verification_commands: ['`node bin/harness-audit.js`'],
299
+ verification_commands: ['`node bin/harness-audit.js`', '`node tests/harness-emitted-tree.test.js`'],
119
300
  risk_notes: ['Cursor adapters must preserve existing project rules and avoid silent overwrite.'],
120
- last_verified_at: '2026-06-10',
301
+ last_verified_at: '2026-08-19',
121
302
  owner: 'MindForge maintainers',
122
303
  source_docs: ['bin/installer-core.js'],
304
+ install_claims: {
305
+ runtime: 'cursor',
306
+ base_dir: '.cursor',
307
+ entry_files: ['.cursor/.cursorrules', '.cursorrules', 'CLAUDE.md'],
308
+ emits: [
309
+ flatCommandTree('Cursor rules', '.cursor/rules', 224),
310
+ ...sharedAssetTrees('.cursor'),
311
+ ],
312
+ absent_dirs: [{ asset: 'subagents (harness dir)', dir: '.cursor/agents' }],
313
+ no_harness_surface_for: ['settings.json'],
314
+ claude_mirror: 'shared',
315
+ },
123
316
  },
124
317
  {
125
318
  id: 'copilot',
126
319
  harness: 'GitHub Copilot',
127
320
  state: 'Instruction-backed',
128
- supported_assets: ['copilot-instructions.md entry', 'rules', 'project-local skills'],
129
- unsupported_surfaces: ['No slash-command surface (supportsSlash:false); no native hook enforcement'],
321
+ // 'rules' and 'project-local skills' were both FALSE, and the second is the dangerous one: a
322
+ // clean `--copilot --local` emits .github/copilot-instructions/mindforge/ (221 files) and
323
+ // .github/copilot-instructions.md, and NOTHING ELSE UNDER .github/ — 0 skills, 0 hooks, 0
324
+ // personas, 0 docs, 0 memory, 0 plugin specs ON ANY COPILOT-READABLE SURFACE.
325
+ //
326
+ // Scoped to .github/ deliberately. Read as an absolute this is false: the same install receives
327
+ // the shared .mindforge/ tree, measured at 232 SKILL.md files (322 files) under
328
+ // .mindforge/skills/ and 218 under .mindforge/personas/, identical for every harness and read by
329
+ // the MindForge runtime in bin/ rather than by Copilot. An earlier draft of this record said
330
+ // Copilot gets "zero skills in total"; that would point a reader at delivering assets that are
331
+ // already present instead of at the real gap, which is the unpopulated Copilot surface.
332
+ // The copilot RUNTIMES entry (bin/installer-core.js:89-96) declares
333
+ // none of the six subdir keys, and bin/installer-core.js:614-616 skips any asset whose key is
334
+ // undefined. Deliberately NOT fixed by inventing a Copilot file contract for skills: nothing in
335
+ // this repository documents a layout Copilot reads for them, and guessing one reproduces exactly
336
+ // the fail-open registration REG-01 existed to remove. The honest boundary is stated instead.
337
+ supported_assets: ['copilot-instructions.md entry (project root and .github/)', 'commands as instruction files (.github/copilot-instructions/mindforge/)', 'shared .mindforge/ framework tree (identical for every harness)', 'subagents and command copies via the shared .claude/ mirror'],
338
+ unsupported_surfaces: [
339
+ 'No slash-command surface (supportsSlash:false); no native hook enforcement',
340
+ 'Receives NO harness-surface skills, hooks, personas, docs, memory or plugin assets: the copilot RUNTIMES entry (bin/installer-core.js:89-96) declares no skillsSubdir/hooksSubdir/personasSubdir/docsSubdir/memorySubdir/pluginsSubdir, and bin/installer-core.js:614-616 copies an asset only when its subdir key is set',
341
+ 'No .github/settings.json and no MCP config',
342
+ ],
130
343
  install_or_onramp: ['`npx mindforge-cc@latest --copilot --local`'],
131
- verification_commands: ['`node bin/harness-audit.js`'],
132
- risk_notes: ['Treat hooks as policy text; Copilot has no runtime hook surface.'],
133
- last_verified_at: '2026-06-10',
344
+ verification_commands: ['`node bin/harness-audit.js`', '`node tests/harness-emitted-tree.test.js`'],
345
+ // The previous note ("Treat hooks as policy text") implied hook files arrive as text. None do.
346
+ risk_notes: ['Copilot has no runtime hook surface AND receives no hook files at all; do not add a Copilot contract for skills or hooks without evidence of a layout Copilot reads, or the install fails open again.'],
347
+ last_verified_at: '2026-08-19',
134
348
  owner: 'MindForge maintainers',
135
349
  source_docs: ['bin/installer-core.js'],
350
+ install_claims: {
351
+ runtime: 'copilot',
352
+ base_dir: '.github',
353
+ entry_files: ['.github/copilot-instructions.md', 'copilot-instructions.md', 'CLAUDE.md'],
354
+ emits: [flatCommandTree('commands as instruction files', '.github/copilot-instructions/mindforge')],
355
+ absent_dirs: [
356
+ { asset: 'skills', dir: '.github/skills' },
357
+ { asset: 'hooks', dir: '.github/hooks' },
358
+ { asset: 'personas', dir: '.github/personas' },
359
+ { asset: 'docs', dir: '.github/docs' },
360
+ { asset: 'memory', dir: '.github/memory' },
361
+ { asset: 'plugins', dir: '.github/plugins' },
362
+ { asset: 'subagents (harness dir)', dir: '.github/agents' },
363
+ ],
364
+ no_harness_surface_for: ['skills', 'hooks', 'personas', 'docs', 'memory', 'plugins', 'rules', 'settings.json'],
365
+ claude_mirror: 'shared',
366
+ },
136
367
  },
137
368
  {
138
369
  id: 'terminal-only',
139
370
  harness: 'Terminal-only',
140
371
  state: 'Native',
141
- supported_assets: ['skills', 'rules', 'commands', 'bin/ scripts', 'harness audit', 'AUDIT.jsonl'],
142
- unsupported_surfaces: ['No external UI; no automatic session control unless scripts run explicitly'],
372
+ supported_assets: ['skills', 'rules', 'commands', 'bin/ scripts', 'harness audit'],
373
+ unsupported_surfaces: [
374
+ 'No external UI; no automatic session control unless scripts run explicitly',
375
+ '.planning/AUDIT.jsonl is gitignored and absent from a fresh clone — it is created by the first audited run, not shipped',
376
+ ],
143
377
  install_or_onramp: ['Clone repo', 'run bin/ commands directly', 'use --local for project installs'],
144
378
  verification_commands: ['`node bin/harness-audit.js`', '`node tests/run-all.js`'],
145
379
  risk_notes: ['This is the fallback contract; every higher-level adapter should degrade to it.'],
146
- last_verified_at: '2026-06-10',
380
+ last_verified_at: '2026-08-19',
147
381
  owner: 'MindForge maintainers',
148
382
  source_docs: ['bin/harness-audit.js', 'bin/mindforge-cli.js'],
383
+ // Not an installer target — there is no key for it in installer-core RUNTIMES, so there is no
384
+ // emitted tree to assert. null rather than omitted, and tests/harness-emitted-tree.test.js pins
385
+ // the set of nulls against Object.keys(RUNTIMES) so a harness cannot dodge the gate by nulling.
386
+ install_claims: null,
149
387
  },
150
388
  ].map(freezeRecord));
151
389
 
@@ -242,6 +480,78 @@ function validateAdapterRecords(records = ADAPTER_RECORDS) {
242
480
  if (typeof record.last_verified_at !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(record.last_verified_at)) {
243
481
  errors.push(`${label}: last_verified_at must be YYYY-MM-DD`);
244
482
  }
483
+
484
+ errors.push(...validateInstallClaims(record, label));
485
+ });
486
+
487
+ return errors;
488
+ }
489
+
490
+ /**
491
+ * Shape-check install_claims so a malformed claim fails `npm run harness:compliance` rather than
492
+ * silently disabling an assertion in tests/harness-emitted-tree.test.js. A claim with no `emits` is
493
+ * the specific hazard: the tree loop would iterate zero times and the harness would look verified.
494
+ */
495
+ function validateInstallClaims(record, label) {
496
+ const errors = [];
497
+ const claims = record.install_claims;
498
+
499
+ if (claims === null) return errors;
500
+
501
+ if (typeof claims !== 'object' || Array.isArray(claims)) {
502
+ errors.push(`${label}: install_claims must be an object or null`);
503
+ return errors;
504
+ }
505
+
506
+ for (const field of ['runtime', 'base_dir', 'claude_mirror']) {
507
+ if (typeof claims[field] !== 'string' || !claims[field].trim()) {
508
+ errors.push(`${label}: install_claims.${field} must be a non-empty string`);
509
+ }
510
+ }
511
+
512
+ if (!['own', 'shared'].includes(claims.claude_mirror)) {
513
+ errors.push(`${label}: install_claims.claude_mirror must be "own" or "shared"`);
514
+ }
515
+
516
+ if (!Array.isArray(claims.entry_files) || claims.entry_files.length === 0) {
517
+ errors.push(`${label}: install_claims.entry_files must be a non-empty array`);
518
+ }
519
+
520
+ for (const field of ['absent_dirs', 'no_harness_surface_for']) {
521
+ if (!Array.isArray(claims[field])) {
522
+ errors.push(`${label}: install_claims.${field} must be an array`);
523
+ }
524
+ }
525
+
526
+ if (!Array.isArray(claims.emits) || claims.emits.length === 0) {
527
+ errors.push(`${label}: install_claims.emits must be a non-empty array`);
528
+ return errors;
529
+ }
530
+
531
+ claims.emits.forEach((entry, index) => {
532
+ const at = `${label}: install_claims.emits[${index}]`;
533
+ if (typeof entry.asset !== 'string' || !entry.asset.trim()) errors.push(`${at}.asset must be a non-empty string`);
534
+
535
+ if (typeof entry.file === 'string') {
536
+ if (!Array.isArray(entry.contains) || entry.contains.length === 0) {
537
+ errors.push(`${at}.contains must be a non-empty array for a file claim`);
538
+ }
539
+ return;
540
+ }
541
+
542
+ if (typeof entry.dir !== 'string' || !entry.dir.trim()) {
543
+ errors.push(`${at} must declare either a dir or a file`);
544
+ return;
545
+ }
546
+ // A floor of 0 would make the count assertion unfalsifiable — an absent directory counts 0.
547
+ if (!Number.isInteger(entry.min_files) || entry.min_files < 1) {
548
+ errors.push(`${at}.min_files must be an integer >= 1`);
549
+ }
550
+ const hasContentCheck = typeof entry.verbatim_subset_of === 'string'
551
+ || (Array.isArray(entry.probes) && entry.probes.length > 0);
552
+ if (!hasContentCheck) {
553
+ errors.push(`${at} must carry a content check (verbatim_subset_of or probes), not just a count`);
554
+ }
245
555
  });
246
556
 
247
557
  return errors;
@@ -295,6 +605,7 @@ module.exports = {
295
605
  renderStateTable,
296
606
  validateAdapterRecords,
297
607
  validateDocumentation,
608
+ validateInstallClaims,
298
609
  };
299
610
 
300
611
  // CLI: `node bin/installer/harness-adapter-compliance.js [--check]`