mindforge-cc 11.4.0 → 11.5.1
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.
- package/.agent/CLAUDE.md +13 -0
- package/.agent/hooks/lib/hook-flags.js +78 -0
- package/.agent/hooks/lib/pretooluse-visible-output.js +46 -0
- package/.agent/hooks/mindforge-block-no-verify.js +552 -0
- package/.agent/hooks/mindforge-config-protection.js +144 -0
- package/.agent/hooks/run-with-flags.js +207 -0
- package/.agent/mindforge/checkpoint.md +76 -0
- package/.agent/mindforge/harness-audit.md +59 -0
- package/.agent/mindforge/instinct.md +46 -0
- package/.agent/mindforge/orch-add-feature.md +43 -0
- package/.agent/mindforge/orch-build-mvp.md +48 -0
- package/.agent/mindforge/orch-change-feature.md +45 -0
- package/.agent/mindforge/orch-fix-defect.md +43 -0
- package/.agent/mindforge/orch-refine-code.md +43 -0
- package/.claude/CLAUDE.md +13 -0
- package/.claude/commands/mindforge/checkpoint.md +76 -0
- package/.claude/commands/mindforge/execute-phase.md +47 -6
- package/.claude/commands/mindforge/harness-audit.md +59 -0
- package/.claude/commands/mindforge/instinct.md +46 -0
- package/.claude/commands/mindforge/orch-add-feature.md +43 -0
- package/.claude/commands/mindforge/orch-build-mvp.md +48 -0
- package/.claude/commands/mindforge/orch-change-feature.md +45 -0
- package/.claude/commands/mindforge/orch-fix-defect.md +43 -0
- package/.claude/commands/mindforge/orch-refine-code.md +43 -0
- package/.claude/commands/mindforge/plan-write.md +11 -0
- package/.claude/commands/mindforge/product-spec.md +76 -0
- package/.mindforge/config.json +2 -2
- package/.mindforge/engine/instincts/instinct-schema.md +17 -9
- package/.mindforge/imported-agents.jsonl +10 -0
- package/.mindforge/manifests/install-components.json +36 -0
- package/.mindforge/manifests/install-modules.json +193 -0
- package/.mindforge/manifests/install-profiles.json +57 -0
- package/.mindforge/memory/sync-manifest.json +1 -1
- package/.mindforge/personas/gan-evaluator.md +226 -0
- package/.mindforge/personas/gan-generator.md +151 -0
- package/.mindforge/personas/gan-planner.md +118 -0
- package/.mindforge/personas/harness-optimizer.md +55 -0
- package/.mindforge/personas/loop-operator.md +58 -0
- package/.mindforge/schemas/hooks.schema.json +199 -0
- package/.mindforge/schemas/install-modules.schema.json +44 -0
- package/.mindforge/schemas/install-state.schema.json +95 -0
- package/.mindforge/schemas/plugin.schema.json +75 -0
- package/.mindforge/schemas/provenance.schema.json +31 -0
- package/.mindforge/skills/agent-architecture-audit/SKILL.md +272 -0
- package/.mindforge/skills/continuous-learning/SKILL.md +16 -0
- package/.mindforge/skills/orch-pipeline/SKILL.md +284 -0
- package/.mindforge/skills/writing-plans/SKILL.md +76 -0
- package/CHANGELOG.md +120 -0
- package/MINDFORGE.md +3 -3
- package/README.md +0 -1
- package/RELEASENOTES.md +131 -0
- package/SECURITY.md +16 -0
- package/bin/autonomous/auto-runner.js +46 -5
- package/bin/autonomous/handoff-schema.js +114 -0
- package/bin/autonomous/session-guardian.sh +138 -0
- package/bin/autonomous/supervisor.js +98 -0
- package/bin/change-classifier.js +19 -5
- package/bin/dashboard/api-router.js +10 -1
- package/bin/governance/approve.js +65 -28
- package/bin/governance/config-manager.js +3 -1
- package/bin/governance/rbac-manager.js +14 -6
- package/bin/harness-audit.js +520 -0
- package/bin/hooks/instinct-capture-hook.js +16 -1
- package/bin/hooks/lib/detect-project.js +72 -0
- package/bin/installer/harness-adapter-compliance.js +321 -0
- package/bin/installer/install-manifests.js +200 -0
- package/bin/installer/install-state.js +243 -0
- package/bin/installer-core.js +1 -1
- package/bin/learning/instinct-cli.js +359 -0
- package/bin/learning/lib/ssrf-guard.js +252 -0
- package/bin/memory/eis-client.js +31 -10
- package/bin/memory/federated-sync.js +11 -2
- package/bin/memory/knowledge-capture.js +10 -1
- package/bin/memory/pillar-health-tracker.js +9 -1
- package/bin/models/llm-errors.js +79 -0
- package/bin/models/model-client.js +39 -4
- package/bin/models/ollama-provider.js +115 -0
- package/bin/models/openai-provider.js +40 -9
- package/bin/models/profiles-loader.js +147 -0
- package/bin/models/provider-registry.js +59 -0
- package/bin/review/ads-engine.js +2 -2
- package/bin/revops/market-evaluator.js +23 -2
- package/bin/revops/router-steering-v2.js +17 -2
- package/bin/security/trust-boundaries.js +20 -3
- package/bin/utils/readiness-gate.js +169 -0
- package/bin/worktree/engine.js +497 -0
- package/package.json +8 -2
- package/subagents/categories/04-quality-security/.claude-plugin/plugin.json +10 -0
- package/subagents/categories/04-quality-security/go-build-resolver.md +105 -0
- package/subagents/categories/04-quality-security/go-reviewer.md +87 -0
- package/subagents/categories/04-quality-security/python-reviewer.md +109 -0
- package/subagents/categories/04-quality-security/react-build-resolver.md +215 -0
- package/subagents/categories/04-quality-security/react-reviewer.md +167 -0
- package/subagents/categories/04-quality-security/rust-build-resolver.md +159 -0
- package/subagents/categories/04-quality-security/rust-reviewer.md +105 -0
- package/subagents/categories/04-quality-security/silent-failure-hunter.md +67 -0
- package/subagents/categories/04-quality-security/type-design-analyzer.md +58 -0
- package/subagents/categories/04-quality-security/typescript-reviewer.md +126 -0
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MindForge harness-adapter compliance scorecard.
|
|
5
|
+
*
|
|
6
|
+
* Records, validates, and renders the cross-harness support matrix — which
|
|
7
|
+
* runtimes MindForge can install natively vs. via adapter vs. instruction-only.
|
|
8
|
+
* A `--check` mode gates CI against documentation drift: the rendered matrix in
|
|
9
|
+
* docs must be generated from these records, not hand-edited.
|
|
10
|
+
*
|
|
11
|
+
* Ported from ECC (scripts/lib/harness-adapter-compliance.js): the validator +
|
|
12
|
+
* renderer + doc-drift gate are kept verbatim (fs/path only, zero deps); the
|
|
13
|
+
* ADAPTER_RECORDS are re-authored to reflect MindForge's actual RUNTIMES
|
|
14
|
+
* (bin/installer-core.js): claude, antigravity, cursor, opencode, gemini,
|
|
15
|
+
* copilot — graded by whether each exposes the slash/hook surface MindForge
|
|
16
|
+
* needs (supportsSlash) plus the terminal-only fallback contract.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const MATRIX_BLOCK_START = '<!-- harness-adapter-compliance:matrix-start -->';
|
|
23
|
+
const MATRIX_BLOCK_END = '<!-- harness-adapter-compliance:matrix-end -->';
|
|
24
|
+
|
|
25
|
+
const COMPLIANCE_STATES = Object.freeze({
|
|
26
|
+
Native: 'MindForge can install or verify the surface directly for this harness.',
|
|
27
|
+
'Adapter-backed': 'MindForge has a thin adapter/transform surface, but parity differs by harness.',
|
|
28
|
+
'Instruction-backed': 'MindForge can provide guidance and files, but the harness does not expose the runtime hook/slash surface MindForge needs for enforcement.',
|
|
29
|
+
'Reference-only': 'Useful as design pressure or external runtime, but MindForge does not ship a direct installer/adapter for it.',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const REQUIRED_FIELDS = Object.freeze([
|
|
33
|
+
'id',
|
|
34
|
+
'harness',
|
|
35
|
+
'state',
|
|
36
|
+
'supported_assets',
|
|
37
|
+
'unsupported_surfaces',
|
|
38
|
+
'install_or_onramp',
|
|
39
|
+
'verification_commands',
|
|
40
|
+
'risk_notes',
|
|
41
|
+
'last_verified_at',
|
|
42
|
+
'owner',
|
|
43
|
+
'source_docs',
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
function freezeRecord(record) {
|
|
47
|
+
return Object.freeze({
|
|
48
|
+
...record,
|
|
49
|
+
supported_assets: Object.freeze(record.supported_assets.slice()),
|
|
50
|
+
unsupported_surfaces: Object.freeze(record.unsupported_surfaces.slice()),
|
|
51
|
+
install_or_onramp: Object.freeze(record.install_or_onramp.slice()),
|
|
52
|
+
verification_commands: Object.freeze(record.verification_commands.slice()),
|
|
53
|
+
risk_notes: Object.freeze(record.risk_notes.slice()),
|
|
54
|
+
source_docs: Object.freeze(record.source_docs.slice()),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const ADAPTER_RECORDS = Object.freeze([
|
|
59
|
+
{
|
|
60
|
+
id: 'claude-code',
|
|
61
|
+
harness: 'Claude Code',
|
|
62
|
+
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'],
|
|
65
|
+
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`'],
|
|
67
|
+
risk_notes: ['Do not load every skill by default; keep hooks opt-in and inspectable.'],
|
|
68
|
+
last_verified_at: '2026-06-10',
|
|
69
|
+
owner: 'MindForge maintainers',
|
|
70
|
+
source_docs: ['.claude/settings.json', '.claude-plugin/', 'bin/installer-core.js'],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
id: 'antigravity',
|
|
74
|
+
harness: 'Antigravity (Gemini)',
|
|
75
|
+
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'],
|
|
78
|
+
install_or_onramp: ['`npx mindforge-cc@latest --antigravity --local`'],
|
|
79
|
+
verification_commands: ['`node bin/harness-audit.js`'],
|
|
80
|
+
risk_notes: ['Keep the .agent settings mirror in sync with .claude (Gemini mirror is live, not dead).'],
|
|
81
|
+
last_verified_at: '2026-06-10',
|
|
82
|
+
owner: 'MindForge maintainers',
|
|
83
|
+
source_docs: ['.agent/settings.json', 'bin/installer-core.js'],
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: 'opencode',
|
|
87
|
+
harness: 'OpenCode',
|
|
88
|
+
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'],
|
|
91
|
+
install_or_onramp: ['`npx mindforge-cc@latest --opencode --local`'],
|
|
92
|
+
verification_commands: ['`node bin/harness-audit.js`'],
|
|
93
|
+
risk_notes: ['Keep hook logic in shared scripts; adapt only event shape at the edge.'],
|
|
94
|
+
last_verified_at: '2026-06-10',
|
|
95
|
+
owner: 'MindForge maintainers',
|
|
96
|
+
source_docs: ['bin/installer-core.js'],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
id: 'gemini',
|
|
100
|
+
harness: 'Gemini CLI',
|
|
101
|
+
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'],
|
|
104
|
+
install_or_onramp: ['`npx mindforge-cc@latest --gemini --local`'],
|
|
105
|
+
verification_commands: ['`node bin/harness-audit.js`'],
|
|
106
|
+
risk_notes: ['Treat Gemini ports as ecosystem adapters until validated end-to-end inside Gemini CLI.'],
|
|
107
|
+
last_verified_at: '2026-06-10',
|
|
108
|
+
owner: 'MindForge maintainers',
|
|
109
|
+
source_docs: ['bin/installer-core.js', '.agent/skills/mindforge-neural-orchestrator/references/gemini-tools.md'],
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
id: 'cursor',
|
|
113
|
+
harness: 'Cursor',
|
|
114
|
+
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'],
|
|
117
|
+
install_or_onramp: ['`npx mindforge-cc@latest --cursor --local`'],
|
|
118
|
+
verification_commands: ['`node bin/harness-audit.js`'],
|
|
119
|
+
risk_notes: ['Cursor adapters must preserve existing project rules and avoid silent overwrite.'],
|
|
120
|
+
last_verified_at: '2026-06-10',
|
|
121
|
+
owner: 'MindForge maintainers',
|
|
122
|
+
source_docs: ['bin/installer-core.js'],
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
id: 'copilot',
|
|
126
|
+
harness: 'GitHub Copilot',
|
|
127
|
+
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'],
|
|
130
|
+
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',
|
|
134
|
+
owner: 'MindForge maintainers',
|
|
135
|
+
source_docs: ['bin/installer-core.js'],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
id: 'terminal-only',
|
|
139
|
+
harness: 'Terminal-only',
|
|
140
|
+
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'],
|
|
143
|
+
install_or_onramp: ['Clone repo', 'run bin/ commands directly', 'use --local for project installs'],
|
|
144
|
+
verification_commands: ['`node bin/harness-audit.js`', '`node tests/run-all.js`'],
|
|
145
|
+
risk_notes: ['This is the fallback contract; every higher-level adapter should degrade to it.'],
|
|
146
|
+
last_verified_at: '2026-06-10',
|
|
147
|
+
owner: 'MindForge maintainers',
|
|
148
|
+
source_docs: ['bin/harness-audit.js', 'bin/mindforge-cli.js'],
|
|
149
|
+
},
|
|
150
|
+
].map(freezeRecord));
|
|
151
|
+
|
|
152
|
+
function toTextList(value) {
|
|
153
|
+
return Array.isArray(value) ? value.join('; ') : String(value || '');
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function escapeMarkdownCell(value) {
|
|
157
|
+
return toTextList(value).replace(/\|/g, '\\|').trim();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function renderMarkdownTable(records = ADAPTER_RECORDS) {
|
|
161
|
+
const lines = [
|
|
162
|
+
'| Harness or runtime | State | Supported assets | Unsupported or different surfaces | Install or onramp | Verification command | Risk notes |',
|
|
163
|
+
'| --- | --- | --- | --- | --- | --- | --- |',
|
|
164
|
+
];
|
|
165
|
+
|
|
166
|
+
for (const record of records) {
|
|
167
|
+
lines.push([
|
|
168
|
+
record.harness,
|
|
169
|
+
record.state,
|
|
170
|
+
record.supported_assets,
|
|
171
|
+
record.unsupported_surfaces,
|
|
172
|
+
record.install_or_onramp,
|
|
173
|
+
record.verification_commands,
|
|
174
|
+
record.risk_notes,
|
|
175
|
+
].map(escapeMarkdownCell).join(' | ').replace(/^/, '| ').replace(/$/, ' |'));
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return lines.join('\n');
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function renderStateTable() {
|
|
182
|
+
const lines = ['| State | Meaning |', '| --- | --- |'];
|
|
183
|
+
for (const [state, meaning] of Object.entries(COMPLIANCE_STATES)) {
|
|
184
|
+
lines.push(`| ${escapeMarkdownCell(state)} | ${escapeMarkdownCell(meaning)} |`);
|
|
185
|
+
}
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function validateAdapterRecords(records = ADAPTER_RECORDS) {
|
|
190
|
+
const errors = [];
|
|
191
|
+
const ids = new Set();
|
|
192
|
+
|
|
193
|
+
records.forEach((record, index) => {
|
|
194
|
+
const label = record?.id || `record[${index}]`;
|
|
195
|
+
|
|
196
|
+
for (const field of REQUIRED_FIELDS) {
|
|
197
|
+
if (!Object.prototype.hasOwnProperty.call(record, field)) {
|
|
198
|
+
errors.push(`${label}: missing required field ${field}`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (typeof record.id !== 'string' || !/^[a-z0-9-]+$/.test(record.id)) {
|
|
203
|
+
errors.push(`${label}: id must be a lowercase slug`);
|
|
204
|
+
} else if (ids.has(record.id)) {
|
|
205
|
+
errors.push(`${label}: duplicate id`);
|
|
206
|
+
} else {
|
|
207
|
+
ids.add(record.id);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (!Object.prototype.hasOwnProperty.call(COMPLIANCE_STATES, record.state)) {
|
|
211
|
+
errors.push(`${label}: unknown state ${record.state}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
for (const field of [
|
|
215
|
+
'supported_assets',
|
|
216
|
+
'unsupported_surfaces',
|
|
217
|
+
'install_or_onramp',
|
|
218
|
+
'verification_commands',
|
|
219
|
+
'risk_notes',
|
|
220
|
+
'source_docs',
|
|
221
|
+
]) {
|
|
222
|
+
if (!Array.isArray(record[field]) || record[field].length === 0) {
|
|
223
|
+
errors.push(`${label}: ${field} must be a non-empty array`);
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
record[field].forEach((value, valueIndex) => {
|
|
228
|
+
if (typeof value !== 'string' || !value.trim()) {
|
|
229
|
+
errors.push(`${label}: ${field}[${valueIndex}] must be a non-empty string`);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (typeof record.harness !== 'string' || !record.harness.trim()) {
|
|
235
|
+
errors.push(`${label}: harness must be a non-empty string`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (typeof record.owner !== 'string' || !record.owner.trim()) {
|
|
239
|
+
errors.push(`${label}: owner must be a non-empty string`);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
if (typeof record.last_verified_at !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(record.last_verified_at)) {
|
|
243
|
+
errors.push(`${label}: last_verified_at must be YYYY-MM-DD`);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
return errors;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function extractMatrixBlock(markdown) {
|
|
251
|
+
const normalized = String(markdown).replace(/\r\n/g, '\n');
|
|
252
|
+
const start = normalized.indexOf(MATRIX_BLOCK_START);
|
|
253
|
+
const end = normalized.indexOf(MATRIX_BLOCK_END);
|
|
254
|
+
|
|
255
|
+
if (start < 0 || end < 0 || end <= start) {
|
|
256
|
+
return null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return normalized.slice(start + MATRIX_BLOCK_START.length, end).trim();
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function validateDocumentation(options = {}) {
|
|
263
|
+
const repoRoot = options.repoRoot || path.resolve(__dirname, '..', '..');
|
|
264
|
+
const docPath = options.docPath || path.join(repoRoot, 'docs', 'architecture', 'harness-adapter-compliance.md');
|
|
265
|
+
const errors = [];
|
|
266
|
+
|
|
267
|
+
let source;
|
|
268
|
+
try {
|
|
269
|
+
source = fs.readFileSync(docPath, 'utf8');
|
|
270
|
+
} catch (_error) {
|
|
271
|
+
errors.push(`compliance doc not found: ${path.relative(repoRoot, docPath)}`);
|
|
272
|
+
return errors;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const actual = extractMatrixBlock(source);
|
|
276
|
+
const expected = renderMarkdownTable();
|
|
277
|
+
|
|
278
|
+
if (actual === null) {
|
|
279
|
+
errors.push(`missing matrix block markers in ${path.relative(repoRoot, docPath)}`);
|
|
280
|
+
} else if (actual !== expected) {
|
|
281
|
+
errors.push(`matrix block in ${path.relative(repoRoot, docPath)} is not generated from adapter records`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return errors;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
module.exports = {
|
|
288
|
+
ADAPTER_RECORDS,
|
|
289
|
+
COMPLIANCE_STATES,
|
|
290
|
+
MATRIX_BLOCK_END,
|
|
291
|
+
MATRIX_BLOCK_START,
|
|
292
|
+
REQUIRED_FIELDS,
|
|
293
|
+
extractMatrixBlock,
|
|
294
|
+
renderMarkdownTable,
|
|
295
|
+
renderStateTable,
|
|
296
|
+
validateAdapterRecords,
|
|
297
|
+
validateDocumentation,
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
// CLI: `node bin/installer/harness-adapter-compliance.js [--check]`
|
|
301
|
+
if (require.main === module) {
|
|
302
|
+
const args = process.argv.slice(2);
|
|
303
|
+
const recordErrors = validateAdapterRecords();
|
|
304
|
+
if (recordErrors.length > 0) {
|
|
305
|
+
process.stderr.write('❌ adapter record errors:\n ' + recordErrors.join('\n ') + '\n');
|
|
306
|
+
process.exit(1);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (args.includes('--check')) {
|
|
310
|
+
const docErrors = validateDocumentation();
|
|
311
|
+
if (docErrors.length > 0) {
|
|
312
|
+
process.stderr.write('❌ compliance doc drift:\n ' + docErrors.join('\n ') + '\n');
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
process.stdout.write('✅ harness-adapter compliance: records valid, doc in sync\n');
|
|
316
|
+
process.exit(0);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Default: print the rendered matrix (for pasting into the doc block).
|
|
320
|
+
process.stdout.write(renderStateTable() + '\n\n' + renderMarkdownTable() + '\n');
|
|
321
|
+
}
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MindForge — Manifest-driven install selection engine (INERT / additive).
|
|
5
|
+
*
|
|
6
|
+
* Ports the PURE resolver core from ECC's install-manifests.js: profile→module
|
|
7
|
+
* expansion, circular-dependency detection, target intersection, --with/--without
|
|
8
|
+
* component selection, and per-target default exclusion. Replaces the
|
|
9
|
+
* two-boolean (--minimal/--with-utils) selectivity ONCE WIRED.
|
|
10
|
+
*
|
|
11
|
+
* Scope guard: this engine does NOT touch bin/installer-core.js's live install()
|
|
12
|
+
* path. The adapter-factory that would consume a resolved plan
|
|
13
|
+
* (planInstallTargetScaffold) is DEFERRED to a future, separately-reviewed PR —
|
|
14
|
+
* so `resolveInstallPlan({ target })` THROWS for any target rather than silently
|
|
15
|
+
* returning a partial/empty plan a future wiring commit could blindly drive.
|
|
16
|
+
*
|
|
17
|
+
* Canonical constants (SUPPORTED_INSTALL_TARGETS from RUNTIMES, the exclude
|
|
18
|
+
* lists) are IMPORTED from installer-core.js — never re-declared — so they can't
|
|
19
|
+
* drift. A sync test asserts this.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const installerCore = require('../installer-core');
|
|
25
|
+
|
|
26
|
+
const DEFAULT_REPO_ROOT = path.join(__dirname, '..', '..');
|
|
27
|
+
// Derive supported targets from the canonical RUNTIMES map (single source of truth).
|
|
28
|
+
const SUPPORTED_INSTALL_TARGETS = Object.keys(installerCore.RUNTIMES);
|
|
29
|
+
const { SENSITIVE_EXCLUDE, MINDFORGE_DEV_EXCLUDE } = installerCore;
|
|
30
|
+
|
|
31
|
+
function readJson(filePath, label) {
|
|
32
|
+
try {
|
|
33
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
34
|
+
} catch (error) {
|
|
35
|
+
throw new Error(`Failed to read ${label}: ${error.message}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function dedupe(values) {
|
|
40
|
+
return [...new Set((Array.isArray(values) ? values : []).map(v => String(v).trim()).filter(Boolean))];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function getManifestPaths(repoRoot = DEFAULT_REPO_ROOT) {
|
|
44
|
+
return {
|
|
45
|
+
modulesPath: path.join(repoRoot, '.mindforge', 'manifests', 'install-modules.json'),
|
|
46
|
+
profilesPath: path.join(repoRoot, '.mindforge', 'manifests', 'install-profiles.json'),
|
|
47
|
+
componentsPath: path.join(repoRoot, '.mindforge', 'manifests', 'install-components.json'),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function loadInstallManifests(options = {}) {
|
|
52
|
+
const repoRoot = options.repoRoot || DEFAULT_REPO_ROOT;
|
|
53
|
+
const { modulesPath, profilesPath, componentsPath } = getManifestPaths(repoRoot);
|
|
54
|
+
if (!fs.existsSync(modulesPath) || !fs.existsSync(profilesPath)) {
|
|
55
|
+
throw new Error(`Install manifests not found under ${repoRoot}/.mindforge/manifests`);
|
|
56
|
+
}
|
|
57
|
+
const modulesData = readJson(modulesPath, 'install-modules.json');
|
|
58
|
+
const profilesData = readJson(profilesPath, 'install-profiles.json');
|
|
59
|
+
const componentsData = fs.existsSync(componentsPath)
|
|
60
|
+
? readJson(componentsPath, 'install-components.json')
|
|
61
|
+
: { version: null, components: [] };
|
|
62
|
+
|
|
63
|
+
const modules = Array.isArray(modulesData.modules) ? modulesData.modules.slice() : [];
|
|
64
|
+
const profiles = (profilesData && typeof profilesData.profiles === 'object') ? profilesData.profiles : {};
|
|
65
|
+
const components = Array.isArray(componentsData.components) ? componentsData.components.slice() : [];
|
|
66
|
+
|
|
67
|
+
for (const m of modules) validateModuleTargets(m);
|
|
68
|
+
|
|
69
|
+
return {
|
|
70
|
+
repoRoot, modules, profiles, components,
|
|
71
|
+
modulesById: new Map(modules.map(m => [m.id, m])),
|
|
72
|
+
componentsById: new Map(components.map(c => [c.id, c])),
|
|
73
|
+
modulesVersion: modulesData.version,
|
|
74
|
+
profilesVersion: profilesData.version,
|
|
75
|
+
componentsVersion: componentsData.version,
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function validateModuleTargets(module) {
|
|
80
|
+
const id = (module && module.id) || '<unknown>';
|
|
81
|
+
if (!Array.isArray(module.targets)) {
|
|
82
|
+
throw new Error(`Install module ${id} has invalid targets; expected an array`);
|
|
83
|
+
}
|
|
84
|
+
const unsupported = module.targets.filter(t => !SUPPORTED_INSTALL_TARGETS.includes(t));
|
|
85
|
+
if (unsupported.length) {
|
|
86
|
+
throw new Error(`Install module ${id} has unsupported targets: ${unsupported.join(', ')}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Targets common to every module in the set (intersection). */
|
|
91
|
+
function intersectTargets(modules) {
|
|
92
|
+
if (!Array.isArray(modules) || !modules.length) return [];
|
|
93
|
+
return SUPPORTED_INSTALL_TARGETS.filter(t => modules.every(m => Array.isArray(m.targets) && m.targets.includes(t)));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function expandComponentsToModuleIds(componentIds, manifests) {
|
|
97
|
+
const out = [];
|
|
98
|
+
for (const cid of dedupe(componentIds)) {
|
|
99
|
+
const c = manifests.componentsById.get(cid);
|
|
100
|
+
if (!c) throw new Error(`Unknown install component: ${cid}`);
|
|
101
|
+
out.push(...(c.modules || []));
|
|
102
|
+
}
|
|
103
|
+
return dedupe(out);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function listInstallProfiles(options = {}) {
|
|
107
|
+
const m = loadInstallManifests(options);
|
|
108
|
+
return Object.entries(m.profiles).map(([id, p]) => ({ id, description: p.description, moduleCount: (p.modules || []).length }));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function listInstallModules(options = {}) {
|
|
112
|
+
const m = loadInstallManifests(options);
|
|
113
|
+
return m.modules.map(mod => ({ id: mod.id, kind: mod.kind, description: mod.description, targets: mod.targets, defaultInstall: mod.defaultInstall }));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function listInstallComponents(options = {}) {
|
|
117
|
+
const m = loadInstallManifests(options);
|
|
118
|
+
return m.components.map(c => {
|
|
119
|
+
const modules = dedupe(c.modules).map(id => m.modulesById.get(id)).filter(Boolean);
|
|
120
|
+
return { id: c.id, family: c.family, description: c.description, moduleIds: modules.map(x => x.id), targets: intersectTargets(modules) };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Resolve a selection (profile + explicit modules + included/excluded components)
|
|
126
|
+
* into a concrete, dependency-complete, cycle-free module list.
|
|
127
|
+
*
|
|
128
|
+
* NOTE: passing `target` THROWS — target scaffolding requires the deferred
|
|
129
|
+
* adapter factory. Until that lands, this engine resolves target-agnostic plans.
|
|
130
|
+
*/
|
|
131
|
+
function resolveInstallPlan(options = {}) {
|
|
132
|
+
if (options.target) {
|
|
133
|
+
throw new Error(
|
|
134
|
+
'target-scoped install planning requires the install-target adapter factory, ' +
|
|
135
|
+
'which is deferred to a future PR. Resolve without a target for now.'
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const manifests = loadInstallManifests(options);
|
|
140
|
+
const requested = [];
|
|
141
|
+
|
|
142
|
+
if (options.profileId) {
|
|
143
|
+
const profile = manifests.profiles[options.profileId];
|
|
144
|
+
if (!profile) throw new Error(`Unknown install profile: ${options.profileId}`);
|
|
145
|
+
requested.push(...(profile.modules || []));
|
|
146
|
+
}
|
|
147
|
+
requested.push(...dedupe(options.moduleIds));
|
|
148
|
+
requested.push(...expandComponentsToModuleIds(options.includeComponentIds, manifests));
|
|
149
|
+
|
|
150
|
+
const excludedIds = new Set(expandComponentsToModuleIds(options.excludeComponentIds, manifests));
|
|
151
|
+
const effective = dedupe(requested).filter(id => !excludedIds.has(id));
|
|
152
|
+
|
|
153
|
+
if (!requested.length) throw new Error('No install profile, module IDs, or included component IDs were provided');
|
|
154
|
+
if (!effective.length) throw new Error('Selection excludes every requested install module');
|
|
155
|
+
|
|
156
|
+
const selected = new Set();
|
|
157
|
+
const visiting = new Set();
|
|
158
|
+
const resolved = new Set();
|
|
159
|
+
|
|
160
|
+
function resolveModule(moduleId, dependencyOf) {
|
|
161
|
+
const module = manifests.modulesById.get(moduleId);
|
|
162
|
+
if (!module) throw new Error(`Unknown install module: ${moduleId}`);
|
|
163
|
+
if (excludedIds.has(moduleId)) {
|
|
164
|
+
if (dependencyOf) throw new Error(`Module ${dependencyOf} depends on excluded module ${moduleId}`);
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
if (resolved.has(moduleId)) return;
|
|
168
|
+
if (visiting.has(moduleId)) throw new Error(`Circular install dependency detected at ${moduleId}`);
|
|
169
|
+
visiting.add(moduleId);
|
|
170
|
+
for (const dep of (module.dependencies || [])) resolveModule(dep, moduleId);
|
|
171
|
+
visiting.delete(moduleId);
|
|
172
|
+
resolved.add(moduleId);
|
|
173
|
+
selected.add(moduleId);
|
|
174
|
+
}
|
|
175
|
+
for (const id of effective) resolveModule(id, null);
|
|
176
|
+
|
|
177
|
+
const selectedModules = manifests.modules.filter(m => selected.has(m.id));
|
|
178
|
+
return {
|
|
179
|
+
repoRoot: manifests.repoRoot,
|
|
180
|
+
profileId: options.profileId || null,
|
|
181
|
+
requestedModuleIds: effective,
|
|
182
|
+
selectedModuleIds: selectedModules.map(m => m.id),
|
|
183
|
+
excludedModuleIds: [...excludedIds],
|
|
184
|
+
selectedModules,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
module.exports = {
|
|
189
|
+
DEFAULT_REPO_ROOT,
|
|
190
|
+
SUPPORTED_INSTALL_TARGETS,
|
|
191
|
+
SENSITIVE_EXCLUDE,
|
|
192
|
+
MINDFORGE_DEV_EXCLUDE,
|
|
193
|
+
getManifestPaths,
|
|
194
|
+
loadInstallManifests,
|
|
195
|
+
listInstallProfiles,
|
|
196
|
+
listInstallModules,
|
|
197
|
+
listInstallComponents,
|
|
198
|
+
intersectTargets,
|
|
199
|
+
resolveInstallPlan,
|
|
200
|
+
};
|