mindforge-cc 11.9.1 → 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.
- package/.agent/CLAUDE.md +37 -13
- package/.agent/hooks/mindforge-block-no-verify.js +61 -13
- package/.agent/hooks/mindforge-config-protection.js +82 -3
- package/.agent/hooks/mindforge-context-monitor.js +1 -1
- package/.agent/hooks/mindforge-workflow-guard.js +2 -2
- package/.agent/hooks/run-with-flags.js +190 -20
- package/.agent/mindforge/browse.md +2 -2
- package/.agent/mindforge/checkpoint.md +1 -1
- package/.agent/mindforge/consult.md +1 -1
- package/.agent/mindforge/cost-report.md +1 -1
- package/.agent/mindforge/harness-audit.md +1 -1
- package/.agent/mindforge/orch-add-feature.md +1 -1
- package/.agent/mindforge/orch-build-mvp.md +1 -1
- package/.agent/mindforge/orch-change-feature.md +1 -1
- package/.agent/mindforge/orch-fix-defect.md +1 -1
- package/.agent/mindforge/orch-refine-code.md +1 -1
- package/.agent/mindforge/qa.md +2 -2
- package/.claude/CLAUDE.md +37 -13
- package/.claude/commands/mindforge/browse.md +2 -2
- package/.claude/commands/mindforge/checkpoint.md +1 -1
- package/.claude/commands/mindforge/consult.md +1 -1
- package/.claude/commands/mindforge/cost-report.md +1 -1
- package/.claude/commands/mindforge/harness-audit.md +1 -1
- package/.claude/commands/mindforge/orch-add-feature.md +1 -1
- package/.claude/commands/mindforge/orch-build-mvp.md +1 -1
- package/.claude/commands/mindforge/orch-change-feature.md +1 -1
- package/.claude/commands/mindforge/orch-fix-defect.md +1 -1
- package/.claude/commands/mindforge/orch-refine-code.md +1 -1
- package/.claude/commands/mindforge/qa.md +2 -2
- package/.mindforge/MINDFORGE-SCHEMA.json +126 -13
- package/.mindforge/config.json +4 -4
- package/.mindforge/engine/autonomous/headless-adapter.md +2 -2
- package/.mindforge/engine/cost-tracking/router.md +1 -1
- package/.mindforge/engine/cost-tracking/token-ledger.md +21 -24
- package/.mindforge/engine/temporal-protocol.md +2 -2
- package/.mindforge/governance/change-classifier.md +20 -4
- package/.mindforge/memory/sync-manifest.json +1 -1
- package/.mindforge/metrics/METRICS-SCHEMA.md +13 -4
- package/.mindforge/personas/cost-optimizer.md +2 -2
- package/.mindforge/personas/multi-model-bridge.md +1 -1
- package/.mindforge/skills/agent-architecture-audit/SKILL.md +2 -2
- package/.mindforge/skills/cost-aware-routing/SKILL.md +3 -3
- package/.mindforge/skills/multi-llm-consult/SKILL.md +2 -2
- package/.mindforge/skills/orch-pipeline/SKILL.md +4 -4
- package/CHANGELOG.md +402 -0
- package/MINDFORGE.md +13 -6
- package/README.md +51 -2
- package/RELEASENOTES.md +55 -2
- package/SECURITY.md +22 -3
- package/bin/autonomous/audit-writer.js +48 -33
- package/bin/autonomous/auto-runner.js +65 -2
- package/bin/change-classifier.js +151 -16
- package/bin/dashboard/api-router.js +28 -47
- package/bin/dashboard/error-response.js +44 -0
- package/bin/dashboard/frontend/app.js +429 -0
- package/bin/dashboard/frontend/index.html +14 -390
- package/bin/dashboard/metrics-aggregator.js +75 -30
- package/bin/dashboard/revops-api.js +12 -2
- package/bin/dashboard/server.js +245 -6
- package/bin/dashboard/sse-bridge.js +11 -8
- package/bin/dashboard/temporal-api.js +11 -5
- package/bin/engine/remediation-engine.js +12 -1
- package/bin/engine/sre-manager.js +1 -1
- package/bin/engine/temporal-cli.js +56 -6
- package/bin/engine/temporal-hub.js +41 -9
- package/bin/engine/verification-runner.js +134 -17
- package/bin/engine/verify-cli.js +25 -7
- package/bin/eval/eval-harness.js +212 -1
- package/bin/eval/golden-set-retrieval.json +9 -0
- package/bin/governance/approval-record.js +147 -0
- package/bin/governance/approve.js +12 -7
- package/bin/governance/policy-engine.js +41 -3
- package/bin/governance/policy-gate-hardened.js +36 -1
- package/bin/governance/verify-approvals.js +163 -0
- package/bin/harness-audit.js +224 -10
- package/bin/hindsight-injector.js +8 -2
- package/bin/hooks/instinct-capture-hook.js +19 -5
- package/bin/install.js +63 -3
- package/bin/installer/harness-adapter-compliance.js +339 -28
- package/bin/installer/hook-registration.js +504 -0
- package/bin/installer-core.js +451 -63
- package/bin/learning/instinct-cli.js +14 -24
- package/bin/memory/knowledge-capture.js +23 -3
- package/bin/memory/knowledge-graph.js +70 -31
- package/bin/memory/vector-hub.js +500 -44
- package/bin/migrations/0.6.0-to-1.0.0.js +30 -25
- package/bin/migrations/1.0.0-to-2.0.0.js +22 -23
- package/bin/mindforge-cli.js +110 -17
- package/bin/models/cost-tracker.js +126 -29
- package/bin/models/model-client.js +6 -1
- package/bin/models/model-router.js +28 -7
- package/bin/models/usage-record.js +71 -0
- package/bin/revops/debt-monitor.js +57 -13
- package/bin/security/trust-gate-hook.js +50 -6
- package/bin/skill-validator.js +6 -1
- package/bin/skills-builder/skill-scorer.js +46 -6
- package/bin/updater/self-update.js +6 -1
- package/bin/updater/version-comparator.js +21 -1
- package/bin/utils/file-lock.js +106 -0
- package/bin/utils/mindforge-params.js +124 -0
- package/bin/utils/mindforge-version.js +99 -0
- package/bin/utils/redact-secrets.js +106 -0
- package/bin/validate-config.js +75 -17
- package/bin/wizard/setup-wizard.js +4 -1
- package/bin/wizard/theme.js +9 -1
- package/changelogs/index.json +11 -9
- package/changelogs/v11.9.2.md +209 -0
- package/changelogs/v11.9.3.md +195 -0
- package/docs/References/config-reference.md +76 -14
- package/docs/References/sdk-api.md +1 -1
- package/docs/Templates/Codebase/architecture.md +1 -1
- package/docs/commands-reference.md +4 -5
- package/docs/faq.md +25 -5
- package/docs/getting-started.md +3 -3
- package/docs/sdk-reference.md +15 -7
- package/docs/troubleshooting.md +10 -6
- package/docs/user-guide.md +14 -14
- package/examples/sdk-integration/README.md +1 -1
- package/package.json +10 -4
- package/subagents/.claude-plugin/marketplace.json +1 -1
- package/bin/dashboard/approval-handler.js +0 -136
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve MINDFORGE's own version, from any context it might be running in.
|
|
4
|
+
*
|
|
5
|
+
* THE DEFECT THIS REPLACES. Three modules read `require('../../package.json').version`:
|
|
6
|
+
*
|
|
7
|
+
* bin/updater/self-update.js:13 const CURRENT_VERSION = ...
|
|
8
|
+
* bin/updater/version-comparator.js:47 User-Agent: `mindforge-cc/${...}`
|
|
9
|
+
* bin/wizard/setup-wizard.js:9 const VERSION = ...
|
|
10
|
+
*
|
|
11
|
+
* In the repo that path IS MindForge's manifest, so it looks correct. In an INSTALL it is not: the
|
|
12
|
+
* installer copies bin/updater/ into the consumer's project, where `../../package.json` is the
|
|
13
|
+
* CONSUMER's manifest. Measured on a clean `--claude --local` into an app at 1.0.0:
|
|
14
|
+
*
|
|
15
|
+
* ../../package.json from bin/updater/ -> <project>/package.json
|
|
16
|
+
* its version: 1.0.0 (the consumer app)
|
|
17
|
+
* MindForge actual: 11.9.2
|
|
18
|
+
*
|
|
19
|
+
* That is not cosmetic. self-update.js:89 classifies the upgrade with
|
|
20
|
+
* `upgradeType(CURRENT_VERSION, latestVersion)`, so 1.0.0 -> 11.9.x reads as a MAJOR upgrade, and
|
|
21
|
+
* :133 uses `readHandoffSchemaVersion() || CURRENT_VERSION` as the "from" version driving
|
|
22
|
+
* migrations. A bogus origin version steers migration behaviour.
|
|
23
|
+
*
|
|
24
|
+
* Same family as the auto-runner defect fixed in ed977e9, which passed process.cwd() to a version
|
|
25
|
+
* check that compares MindForge's own manifests: both mistake "the directory I am running in" for
|
|
26
|
+
* "the package I am part of".
|
|
27
|
+
*
|
|
28
|
+
* HOW IT RESOLVES, in order, with the discriminator that makes it work:
|
|
29
|
+
*
|
|
30
|
+
* 1. The nearest ancestor package.json whose `name` is 'mindforge-cc'. The NAME is the whole
|
|
31
|
+
* trick — it is what distinguishes MindForge's manifest from a consumer's, which a relative
|
|
32
|
+
* path cannot do. Covers the repo, a global npm install, and node_modules layouts.
|
|
33
|
+
* 2. <cwd>/.mindforge/config.json `.version`. In an install this is written BY the installer and
|
|
34
|
+
* carries MindForge's version (measured: 11.9.2 in a project whose app is 1.0.0).
|
|
35
|
+
* tests/version-consistency.test.js asserts it equals package.json in the repo, so the two
|
|
36
|
+
* sources cannot silently disagree.
|
|
37
|
+
* 3. <cwd>/node_modules/mindforge-cc/package.json, for a consumer that has the package but no
|
|
38
|
+
* .mindforge/ yet.
|
|
39
|
+
*
|
|
40
|
+
* It THROWS rather than guessing. A wrong version is worse than an absent one here: it produces a
|
|
41
|
+
* confident, incorrect upgrade classification instead of an error somebody can act on.
|
|
42
|
+
*/
|
|
43
|
+
|
|
44
|
+
const fs = require('fs');
|
|
45
|
+
const path = require('path');
|
|
46
|
+
|
|
47
|
+
const PACKAGE_NAME = 'mindforge-cc';
|
|
48
|
+
|
|
49
|
+
function readJson(file) {
|
|
50
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch { return null; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Walk up from `dir` looking for a package.json that belongs to MindForge itself. */
|
|
54
|
+
function findOwnManifest(dir) {
|
|
55
|
+
let current = path.resolve(dir);
|
|
56
|
+
// Bounded by the filesystem root; path.dirname('/') === '/' terminates the loop.
|
|
57
|
+
for (let depth = 0; depth < 40; depth++) {
|
|
58
|
+
const candidate = path.join(current, 'package.json');
|
|
59
|
+
const pkg = readJson(candidate);
|
|
60
|
+
if (pkg && pkg.name === PACKAGE_NAME && pkg.version) {
|
|
61
|
+
return { version: pkg.version, source: candidate };
|
|
62
|
+
}
|
|
63
|
+
const parent = path.dirname(current);
|
|
64
|
+
if (parent === current) break;
|
|
65
|
+
current = parent;
|
|
66
|
+
}
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* @param {{fromDir?: string, cwd?: string}} [opts]
|
|
72
|
+
* fromDir - where to start walking for MindForge's own manifest (defaults to this file).
|
|
73
|
+
* cwd - the project root to consult for installed-context sources.
|
|
74
|
+
* @returns {{version: string, source: string}}
|
|
75
|
+
* @throws {Error} when no source can establish MindForge's version.
|
|
76
|
+
*/
|
|
77
|
+
function resolveMindforgeVersion(opts = {}) {
|
|
78
|
+
const fromDir = opts.fromDir || __dirname;
|
|
79
|
+
const cwd = opts.cwd || process.cwd();
|
|
80
|
+
|
|
81
|
+
const own = findOwnManifest(fromDir);
|
|
82
|
+
if (own) return own;
|
|
83
|
+
|
|
84
|
+
const cfgPath = path.join(cwd, '.mindforge', 'config.json');
|
|
85
|
+
const cfg = readJson(cfgPath);
|
|
86
|
+
if (cfg && cfg.version) return { version: cfg.version, source: cfgPath };
|
|
87
|
+
|
|
88
|
+
const depPath = path.join(cwd, 'node_modules', PACKAGE_NAME, 'package.json');
|
|
89
|
+
const dep = readJson(depPath);
|
|
90
|
+
if (dep && dep.version) return { version: dep.version, source: depPath };
|
|
91
|
+
|
|
92
|
+
throw new Error(
|
|
93
|
+
'Cannot determine the MindForge version. Looked for a package.json named ' +
|
|
94
|
+
`"${PACKAGE_NAME}" above ${fromDir}, then ${path.join(cwd, '.mindforge/config.json')}, then ` +
|
|
95
|
+
`${depPath}. Refusing to guess: a wrong version produces a confident but incorrect upgrade ` +
|
|
96
|
+
'classification.');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
module.exports = { resolveMindforgeVersion, findOwnManifest, PACKAGE_NAME };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Redact credential-shaped substrings from text that is about to be persisted.
|
|
4
|
+
*
|
|
5
|
+
* WHY THIS EXISTS. `bin/hooks/instinct-capture-hook.js` writes the first 200 characters of a raw
|
|
6
|
+
* Bash command — and of a raw Task description — into BOTH an `observation` and a `behavior` field
|
|
7
|
+
* of `.mindforge/engine/instincts/instinct-store.jsonl`. Measured on real store files left by
|
|
8
|
+
* probes: an entry whose `observation` begins `Bash command succeeded: AWS_SE...`, i.e. an
|
|
9
|
+
* `AWS_SECRET_ACCESS_KEY=<value>` assignment captured verbatim. The store path is not gitignored,
|
|
10
|
+
* `.mindforge/engine/` is inside `package.json` files[] with no negation for instincts, and the
|
|
11
|
+
* promotion path (`.mindforge/engine/instincts/promotion-engine.md:33,41,46`) copies these fields
|
|
12
|
+
* into a generated, tracked, PUBLISHED `SKILL.md`. So a captured secret is amplified from a
|
|
13
|
+
* data file into committed source.
|
|
14
|
+
*
|
|
15
|
+
* WHY REDACT RATHER THAN HASH. Measured, not assumed: three consumers read this text and all three
|
|
16
|
+
* need it readable.
|
|
17
|
+
*
|
|
18
|
+
* bin/learning/instinct-cli.js:114 `list` prints observation AND behavior verbatim to a human
|
|
19
|
+
* bin/learning/instinct-cli.js:241 `promote` prints behavior verbatim as the suggestion
|
|
20
|
+
* .mindforge/skills/instinct-clustering/SKILL.md:57-63 word-overlap Jaccard on observation
|
|
21
|
+
*
|
|
22
|
+
* And nothing in code dedups on the text — `cmdImport` keys on `e.id` and the hook stamps a fresh
|
|
23
|
+
* randomUUID per event — so a hash would preserve no collision that anything relies on. Hashing
|
|
24
|
+
* would break three consumers to buy nothing.
|
|
25
|
+
*
|
|
26
|
+
* WHY NOT "KEEP argv[0], REDACT THE REST". Because of the env-assignment form: in
|
|
27
|
+
* `SECRET=value cmd ...` the secret precedes the program, so the first retained token IS the
|
|
28
|
+
* secret. That is not hypothetical — it is the shape of the measured on-disk entry above.
|
|
29
|
+
*
|
|
30
|
+
* FAIL CLOSED. If redaction throws, this returns a placeholder rather than the input. A redactor
|
|
31
|
+
* that fails open is not a redactor. Note this does NOT make the calling hook fail closed: the hook
|
|
32
|
+
* stays advisory and still exits 0. "Do not block the tool call" and "do not write the secret
|
|
33
|
+
* anyway" are different promises, and both are kept.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const PLACEHOLDER = (kind) => `<redacted:${kind}>`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Ordered most-specific first. Each entry replaces only the SECRET portion where a prefix carries
|
|
40
|
+
* meaning worth keeping (a scheme, a key name), so the redacted text still reads as the same shape
|
|
41
|
+
* of command — which is what the human-facing and Jaccard consumers need.
|
|
42
|
+
*/
|
|
43
|
+
const RULES = [
|
|
44
|
+
// PEM private key blocks, header through footer.
|
|
45
|
+
{ kind: 'pem', re: /-----BEGIN[^-]{0,40}(?:PRIVATE )?KEY-----[\s\S]*?-----END[^-]{0,40}KEY-----/g,
|
|
46
|
+
replace: () => PLACEHOLDER('pem') },
|
|
47
|
+
|
|
48
|
+
// Vendor tokens with recognisable prefixes. Keep the prefix: it says WHICH credential leaked,
|
|
49
|
+
// which is exactly what an operator needs in order to rotate the right one.
|
|
50
|
+
{ kind: 'vendor-token',
|
|
51
|
+
re: /\b(sk-(?:proj-|ant-)?|ghp_|gho_|ghu_|ghs_|ghr_|github_pat_|glpat-|npm_|xox[baprse]-|AKIA|ASIA|AIza|hf_|dop_v1_)[A-Za-z0-9_-]{16,}/g,
|
|
52
|
+
replace: (m, prefix) => `${prefix}${PLACEHOLDER('token')}` },
|
|
53
|
+
|
|
54
|
+
// Authorization headers. Keep the scheme, drop the credential.
|
|
55
|
+
{ kind: 'auth-header', re: /\b(Bearer|Basic|Token)\s+[A-Za-z0-9+/=._~-]{8,}/gi,
|
|
56
|
+
replace: (m, scheme) => `${scheme} ${PLACEHOLDER('auth')}` },
|
|
57
|
+
|
|
58
|
+
// Credentials embedded in a URL: scheme://user:pass@host
|
|
59
|
+
{ kind: 'url-credential', re: /\b([a-z][a-z0-9+.-]*:\/\/[^\s:/@]+):[^\s@/]+@/gi,
|
|
60
|
+
replace: (m, upToUser) => `${upToUser}:${PLACEHOLDER('url-password')}@` },
|
|
61
|
+
|
|
62
|
+
// Environment-assignment form, keyed on the NAME looking secret-bearing. This is the case that
|
|
63
|
+
// defeats prefix-preserving designs, because the assignment can precede the program name.
|
|
64
|
+
{ kind: 'assignment',
|
|
65
|
+
re: /\b([A-Za-z_][A-Za-z0-9_]*(?:SECRET|TOKEN|PASSWORD|PASSWD|APIKEY|API_KEY|ACCESS_KEY|PRIVATE_KEY|CREDENTIAL|AUTH)[A-Za-z0-9_]*)\s*=\s*("[^"]*"|'[^']*'|[^\s;|&]+)/gi,
|
|
66
|
+
replace: (m, name) => `${name}=${PLACEHOLDER('assignment')}` },
|
|
67
|
+
|
|
68
|
+
// Flag form: --password X, --token=X, -p X. Keep the flag so the command still parses visually.
|
|
69
|
+
{ kind: 'flag',
|
|
70
|
+
re: /(--?(?:password|passwd|token|secret|api[-_]?key|access[-_]?key|auth|credential)(?:[=\s]))(?:"[^"]*"|'[^']*'|[^\s;|&]+)/gi,
|
|
71
|
+
replace: (m, flag) => `${flag}${PLACEHOLDER('flag-value')}` },
|
|
72
|
+
|
|
73
|
+
// Generic high-entropy run, last resort for credentials with no recognisable prefix. Requires
|
|
74
|
+
// BOTH a letter and a digit and >= 28 characters, so English words, long flag names and ordinary
|
|
75
|
+
// identifiers are left alone. `/` is excluded so a long path is examined segment by segment.
|
|
76
|
+
{ kind: 'opaque', re: /\b(?=[A-Za-z0-9+=_-]{28,}\b)(?=[^\s]*[A-Za-z])(?=[^\s]*\d)[A-Za-z0-9+=_-]{28,}\b/g,
|
|
77
|
+
replace: () => PLACEHOLDER('opaque') },
|
|
78
|
+
];
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* @param {string} text
|
|
82
|
+
* @returns {string} the text with credential-shaped substrings replaced
|
|
83
|
+
*/
|
|
84
|
+
function redactSecrets(text) {
|
|
85
|
+
if (typeof text !== 'string' || text === '') return text;
|
|
86
|
+
try {
|
|
87
|
+
let out = text;
|
|
88
|
+
for (const rule of RULES) out = out.replace(rule.re, rule.replace);
|
|
89
|
+
return out;
|
|
90
|
+
} catch {
|
|
91
|
+
// Fail closed. Withholding telemetry is always cheaper than persisting a credential.
|
|
92
|
+
return PLACEHOLDER('redaction-failed');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* True when redactSecrets would change the text. Useful for asserting a fixture is actually
|
|
98
|
+
* secret-shaped before relying on it, so a test cannot pass because its fixture was inert.
|
|
99
|
+
* @param {string} text
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
function containsSecretShape(text) {
|
|
103
|
+
return typeof text === 'string' && text !== '' && redactSecrets(text) !== text;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
module.exports = { redactSecrets, containsSecretShape, RULES };
|
package/bin/validate-config.js
CHANGED
|
@@ -7,14 +7,56 @@
|
|
|
7
7
|
|
|
8
8
|
'use strict';
|
|
9
9
|
|
|
10
|
-
const fs
|
|
10
|
+
const fs = require('fs');
|
|
11
11
|
const path = require('path');
|
|
12
12
|
|
|
13
|
+
// The one bracket-aware MINDFORGE.md reader (see bin/utils/mindforge-params.js).
|
|
14
|
+
const { readParams } = require('./utils/mindforge-params');
|
|
15
|
+
|
|
16
|
+
// The config belongs to the CALLER, so it stays relative to cwd.
|
|
13
17
|
const CONFIG_PATH = process.argv[2] || 'MINDFORGE.md';
|
|
14
|
-
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The schema belongs to the FRAMEWORK, so it is resolved from __dirname first.
|
|
21
|
+
*
|
|
22
|
+
* This was the bare relative `'.mindforge/MINDFORGE-SCHEMA.json'`, which only ever resolved
|
|
23
|
+
* because bin/mindforge-cli.js pinned the child's cwd to MindForge's own install directory. That
|
|
24
|
+
* pin is what made `security-scan` validate the vendor's MINDFORGE.md and report
|
|
25
|
+
* `✅ valid — 43 settings configured` over any caller's config. Removing the pin without anchoring
|
|
26
|
+
* the schema here would have swapped one broken outcome for another: every consumer with a real
|
|
27
|
+
* MINDFORGE.md would hit the `not found` branch below and exit 0 having validated NOTHING. Measured
|
|
28
|
+
* that exact regression on a fixture — `ℹ️ MINDFORGE-SCHEMA.json not found — skipping schema
|
|
29
|
+
* validation`, rc=0 — which is why the two changes ship together.
|
|
30
|
+
*
|
|
31
|
+
* Every check in this file is schema-driven (required, recommended, type, minimum, maximum, enum,
|
|
32
|
+
* pattern, nonOverridable). There is no schema-free validation path, so a missing schema is not a
|
|
33
|
+
* degraded check, it is no check.
|
|
34
|
+
*
|
|
35
|
+
* Package-relative first, cwd-relative second, because the two supported install shapes put the
|
|
36
|
+
* schema in different places:
|
|
37
|
+
* - `npx mindforge-cc` — runs from node_modules/mindforge-cc/, where .mindforge/ ships. Fixed.
|
|
38
|
+
* - a copied bin/ tree — the installer copies bin/ but creates no .mindforge/, verified on a
|
|
39
|
+
* clean `--claude --local` install. There the schema is genuinely absent and the honest skip
|
|
40
|
+
* below is the correct outcome; the cwd fallback still finds one if the project has its own.
|
|
41
|
+
*/
|
|
42
|
+
function resolveSchemaPath() {
|
|
43
|
+
const candidates = [
|
|
44
|
+
path.join(__dirname, '..', '.mindforge', 'MINDFORGE-SCHEMA.json'),
|
|
45
|
+
path.join(process.cwd(), '.mindforge', 'MINDFORGE-SCHEMA.json'),
|
|
46
|
+
];
|
|
47
|
+
return candidates.find((p) => fs.existsSync(p)) || candidates[0];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const SCHEMA_PATH = resolveSchemaPath();
|
|
15
51
|
|
|
16
52
|
if (!fs.existsSync(CONFIG_PATH)) {
|
|
17
|
-
|
|
53
|
+
// Name the path actually looked for, and where. This said "MINDFORGE.md not found" verbatim even
|
|
54
|
+
// when argv[2] supplied a different file, so `validate-config.js custom-config.md` reported a
|
|
55
|
+
// missing MINDFORGE.md — a message about a file the caller never mentioned. It cost real time
|
|
56
|
+
// during this change: a test failure was diagnosed as "a defaultArgs positional shadowed argv[2]"
|
|
57
|
+
// when the truth was simply that the named file was not in the working directory.
|
|
58
|
+
console.log(`ℹ️ ${CONFIG_PATH} not found in ${process.cwd()} — using all defaults. `
|
|
59
|
+
+ 'Create one to customise.');
|
|
18
60
|
process.exit(0);
|
|
19
61
|
}
|
|
20
62
|
|
|
@@ -23,28 +65,36 @@ if (!fs.existsSync(SCHEMA_PATH)) {
|
|
|
23
65
|
process.exit(0);
|
|
24
66
|
}
|
|
25
67
|
|
|
26
|
-
const content = fs.readFileSync(CONFIG_PATH, 'utf8');
|
|
27
68
|
const schema = JSON.parse(fs.readFileSync(SCHEMA_PATH, 'utf8'));
|
|
28
69
|
|
|
29
70
|
const errors = [];
|
|
30
71
|
const warnings = [];
|
|
31
72
|
|
|
32
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
73
|
+
// MINDFORGE.md declares parameters as bracketed assignments ([KEY] = value).
|
|
74
|
+
// v11.9.2 and earlier matched /^([A-Z_]+)=(.+)$/, which matches ZERO lines of a
|
|
75
|
+
// real MINDFORGE.md — the validator parsed 0 settings and could never fail.
|
|
76
|
+
// readParams() also still handles the legacy plain KEY=value form.
|
|
77
|
+
const settings = readParams(CONFIG_PATH);
|
|
78
|
+
|
|
79
|
+
// Required / recommended key sets (top-level arrays in the schema). Kept
|
|
80
|
+
// deliberately identical to sdk/src/client.ts validateConfig() so the SDK and
|
|
81
|
+
// the CLI validator enforce ONE contract.
|
|
82
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required : []);
|
|
83
|
+
const recommended = new Set(Array.isArray(schema.recommended) ? schema.recommended : []);
|
|
84
|
+
|
|
85
|
+
for (const key of required) {
|
|
86
|
+
if (!settings[key]) errors.push(`${key} is required but not set`);
|
|
87
|
+
}
|
|
88
|
+
for (const key of recommended) {
|
|
89
|
+
if (!settings[key]) warnings.push(`${key} is recommended but not set`);
|
|
90
|
+
}
|
|
42
91
|
|
|
43
92
|
// Validate against schema
|
|
44
93
|
for (const [key, def] of Object.entries(schema.properties || {})) {
|
|
45
94
|
const value = settings[key];
|
|
46
95
|
|
|
47
|
-
|
|
96
|
+
// Legacy per-property `required` flag still honoured (schema.required wins).
|
|
97
|
+
if (def.required && !value && !required.has(key)) {
|
|
48
98
|
errors.push(`${key} is required but not set`);
|
|
49
99
|
continue;
|
|
50
100
|
}
|
|
@@ -68,8 +118,16 @@ for (const [key, def] of Object.entries(schema.properties || {})) {
|
|
|
68
118
|
errors.push(`${key}: expected true or false, got "${value}"`);
|
|
69
119
|
}
|
|
70
120
|
|
|
71
|
-
if (def.
|
|
72
|
-
|
|
121
|
+
if (def.type === 'string' && def.pattern && !new RegExp(def.pattern).test(value)) {
|
|
122
|
+
errors.push(`${key}: "${value}" does not match required pattern ${def.pattern}`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// A non-overridable governance primitive may be SET (MINDFORGE.md is its
|
|
126
|
+
// source of truth) but never DISABLED. Mirrors sdk/src/client.ts:176-182,
|
|
127
|
+
// which errors on `[KEY] = false`. The old code warned on every occurrence
|
|
128
|
+
// and claimed the value "will be ignored" — which was untrue and pure noise.
|
|
129
|
+
if (def.nonOverridable && def.type === 'boolean' && value === 'false') {
|
|
130
|
+
errors.push(`${key}: non-overridable governance primitive cannot be disabled`);
|
|
73
131
|
}
|
|
74
132
|
}
|
|
75
133
|
|
|
@@ -6,7 +6,10 @@ const readline = require('readline');
|
|
|
6
6
|
const detector = require('./environment-detector');
|
|
7
7
|
const generator = require('./config-generator');
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
// Resolved by package NAME, not by relative path: in an install `../../package.json` is the
|
|
10
|
+
// consumer's manifest, so the wizard displayed the host app's version as MindForge's.
|
|
11
|
+
const { resolveMindforgeVersion } = require('../utils/mindforge-version');
|
|
12
|
+
const VERSION = resolveMindforgeVersion({ fromDir: __dirname }).version;
|
|
10
13
|
const ARGS = process.argv.slice(2);
|
|
11
14
|
const IS_INTERACTIVE =
|
|
12
15
|
!ARGS.some((a) => ['--claude', '--antigravity', '--all', '--help'].includes(a)) &&
|
package/bin/wizard/theme.js
CHANGED
|
@@ -106,7 +106,15 @@ const Theme = {
|
|
|
106
106
|
console.log(` ${this.colors.dim('│')} ${this.colors.bold('Environment')}: ${this.colors.cyan(runtime)} (${this.colors.dim(scope)}) ${this.colors.dim('│')}`);
|
|
107
107
|
console.log(` ${this.colors.dim('│')} ${this.colors.dim('│')}`);
|
|
108
108
|
console.log(` ${this.colors.dim('│')} ${this.colors.bold('Next steps:')} ${this.colors.dim('│')}`);
|
|
109
|
-
|
|
109
|
+
// Was `mindforge-cc init`, which does not exist. mindforge-cc takes flags only and silently
|
|
110
|
+
// ignored the positional, so obeying this line re-ran the installer against whatever directory
|
|
111
|
+
// the user was standing in: measured 1,836 files written into an empty temp dir, exit 0, and
|
|
112
|
+
// this same panel printed again — so it loops. `/mindforge:init-project` is the real command and
|
|
113
|
+
// the install has just placed it in the harness, so the instruction now names something the
|
|
114
|
+
// user actually has. bin/install.js rejects stray positionals too: a wrong instruction and a
|
|
115
|
+
// silent acceptance of it are two separate defects, and fixing either alone leaves a trap.
|
|
116
|
+
// Padding keeps the visible width at 65 so the box border still lines up.
|
|
117
|
+
console.log(` ${this.colors.dim('│')} ${this.colors.bold('/mindforge:init-project')} ${this.colors.dim('— Initialize your workspace')} ${this.colors.dim('│')}`);
|
|
110
118
|
console.log(` ${this.colors.dim('│')} ${this.colors.bold('/mindforge:help')} ${this.colors.dim('— Explore the command suite')} ${this.colors.dim('│')}`);
|
|
111
119
|
console.log(` ${this.colors.dim('│')} ${this.colors.dim('│')}`);
|
|
112
120
|
console.log(` ${this.colors.dim('├' + '─'.repeat(boxWidth) + '╯')}\n`);
|
package/changelogs/index.json
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"versions": [
|
|
3
|
+
"11.9.3",
|
|
4
|
+
"11.9.2",
|
|
3
5
|
"11.9.1",
|
|
4
6
|
"11.9.0",
|
|
5
7
|
"11.8.3",
|
|
@@ -47,8 +49,8 @@
|
|
|
47
49
|
"6.5.0",
|
|
48
50
|
"6.4.0",
|
|
49
51
|
"6.3.0",
|
|
50
|
-
"6.2.0",
|
|
51
52
|
"6.2.0-alpha",
|
|
53
|
+
"6.2.0",
|
|
52
54
|
"6.1.0-alpha",
|
|
53
55
|
"6.0.0-alpha",
|
|
54
56
|
"5.9.0",
|
|
@@ -60,8 +62,8 @@
|
|
|
60
62
|
"5.3.0",
|
|
61
63
|
"5.2.0",
|
|
62
64
|
"5.1.0",
|
|
63
|
-
"5.0.0-alpha.2",
|
|
64
65
|
"5.0.0-alpha.1",
|
|
66
|
+
"5.0.0-alpha.2",
|
|
65
67
|
"4.3.0",
|
|
66
68
|
"4.1.0",
|
|
67
69
|
"4.0.0",
|
|
@@ -76,17 +78,17 @@
|
|
|
76
78
|
"2.1.2",
|
|
77
79
|
"2.1.1",
|
|
78
80
|
"2.1.0",
|
|
79
|
-
"2.0.0",
|
|
80
|
-
"2.0.0-alpha.12",
|
|
81
|
-
"2.0.0-alpha.11",
|
|
82
|
-
"2.0.0-alpha.10",
|
|
83
81
|
"2.0.0-alpha.9",
|
|
84
82
|
"2.0.0-alpha.8",
|
|
85
|
-
"2.0.0
|
|
86
|
-
"2.0.0-alpha.
|
|
87
|
-
"2.0.0-alpha.3",
|
|
83
|
+
"2.0.0",
|
|
84
|
+
"2.0.0-alpha.10",
|
|
88
85
|
"2.0.0-alpha.2",
|
|
89
86
|
"2.0.0-alpha.1",
|
|
87
|
+
"2.0.0-alpha.11",
|
|
88
|
+
"2.0.0-alpha.7",
|
|
89
|
+
"2.0.0-alpha.12",
|
|
90
|
+
"2.0.0-alpha.3",
|
|
91
|
+
"2.0.0-alpha.4",
|
|
90
92
|
"1.0.5",
|
|
91
93
|
"1.0.4",
|
|
92
94
|
"1.0.3",
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [11.9.2] — 2026-08-16 — Correctness: audit-chain integrity, dashboard crash policy, secret scanning
|
|
4
|
+
|
|
5
|
+
Patch release. No new features. Correctness work closing defects found by a
|
|
6
|
+
multi-agent audit of v11.9.1, plus the regression suites that keep them closed.
|
|
7
|
+
Contains a breaking change to the dashboard HTTP surface — see BREAKING below.
|
|
8
|
+
|
|
9
|
+
### BREAKING
|
|
10
|
+
|
|
11
|
+
Shipped under a PATCH bump. The break is confined to the dashboard's own HTTP
|
|
12
|
+
surface, which binds to 127.0.0.1 only — but if you script against it, read this.
|
|
13
|
+
|
|
14
|
+
- **Dashboard error responses changed shape.** `detail` is removed from 5 endpoints and
|
|
15
|
+
raw errno strings from 10 more; `correlation_id` is added to 15; a malformed request
|
|
16
|
+
body now returns `application/json` instead of express's `text/html` error page.
|
|
17
|
+
Anything parsing `detail` must correlate on the logged `correlation_id` instead. This
|
|
18
|
+
was deliberate — those fields leaked absolute filesystem paths, and therefore the
|
|
19
|
+
operator's username and home directory, into an unauthenticated response body
|
|
20
|
+
(`requireAuth` exempts GET).
|
|
21
|
+
- **The dashboard now EXITS on an unhandled rejection or uncaught exception** where
|
|
22
|
+
11.9.1 logged and continued. If you supervise the process, expect restarts where you
|
|
23
|
+
previously saw a logged error. Rationale in the Fixed section below: log-and-continue
|
|
24
|
+
held client sockets open until the client timed out, and had made `shutdown()` swallow
|
|
25
|
+
a throwing token unlink and keep serving the authenticated mutation API after SIGTERM.
|
|
26
|
+
- **`node bin/validate-config.js` and `mindforge security-scan` can now fail.** They
|
|
27
|
+
previously reported `MINDFORGE.md valid — 0 settings configured` and exited 0 on every
|
|
28
|
+
input. If you run either in CI, a genuinely invalid registry will now red-line where it
|
|
29
|
+
used to pass. Note this reaches **fresh installs and `--force` reinstalls only** — the
|
|
30
|
+
installer does not overwrite an existing `.mindforge/MINDFORGE-SCHEMA.json`, so a plain
|
|
31
|
+
upgrade keeps the old permissive schema.
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
|
|
35
|
+
- **`security-scan` could not fail.** `bin/validate-config.js` and
|
|
36
|
+
`bin/models/model-router.js` each parsed `MINDFORGE.md` with a plain `KEY=value` regex, but
|
|
37
|
+
the registry declares its 43 parameters as bracketed `[KEY] = value`. Every schema property
|
|
38
|
+
resolved to `undefined` and short-circuited, so the command reported
|
|
39
|
+
`MINDFORGE.md valid — 0 settings configured` and exited 0 on any input. The schema also had
|
|
40
|
+
no `required` key at all. Both parsers now share `bin/utils/mindforge-params.js`, which also
|
|
41
|
+
accepts the legacy plain form (`examples/starter-project/MINDFORGE.md` ships 28 such lines),
|
|
42
|
+
and the schema declares real `required`/`recommended` sets.
|
|
43
|
+
**Behaviour change for consumers:** three CI gates go from unfailable to failable —
|
|
44
|
+
`.github/workflows/mindforge-ci.yml:38`, `.gitlab-ci-mindforge.yml:12`, and
|
|
45
|
+
`.github/workflows/control-plane.yml:100`. If one red-lines on a valid value, the schema
|
|
46
|
+
bound is wrong; do not "fix" it by editing `MINDFORGE.md`. Model routing is unchanged —
|
|
47
|
+
30 persona x tier combinations resolve identically.
|
|
48
|
+
- **Trace retrieval returned nothing usable.** Queries were wrapped as a single FTS phrase, so
|
|
49
|
+
any query containing one absent term scored zero; and `traces_search` was keyed on `trace_id`
|
|
50
|
+
rather than the primary key, so each span's DELETE evicted the previous span and only the last
|
|
51
|
+
span per trace stayed searchable — 2,270 of 5,117 content-bearing traces, 44.4%, unsearchable.
|
|
52
|
+
Queries are now tokenised, OR-joined and ranked by tf-idf (`matchinfo('pcnx')`); the index is
|
|
53
|
+
re-keyed and rebuilt losslessly from the base table. `bin/eval/eval-harness.js` and its golden
|
|
54
|
+
set had zero callers and are now reachable as `npm run eval:retrieval`, with the baseline
|
|
55
|
+
committed: mean recall@10 0.6417, nDCG 0.5698 over 519 documents.
|
|
56
|
+
- **The cost ledger reported two totals for one concept.** `sum(cost_usd)` was $13.73 while
|
|
57
|
+
`sum(total_cost_usd)` was $0.00, and `tests/dashboard.test.js` wrote the reader's field name,
|
|
58
|
+
so the mismatch tested green. `bin/models/usage-record.js` is now the single definition of the
|
|
59
|
+
ledger path, record shape, per-entry cost and day bucket. The configured `ledger_path` pointed
|
|
60
|
+
at `token-ledger.jsonl`, a file that has never existed; that ghost filename had spread to 17
|
|
61
|
+
places across 13 files and is now absent. The dashboard cost tile no longer renders `$0.00`
|
|
62
|
+
on a 500 — it had no `res.ok` check, and because errors return well-formed JSON the catch
|
|
63
|
+
never fired, making an outage indistinguishable from zero spend.
|
|
64
|
+
A maintainer tool, `scripts/purge-synthetic-usage.js`, removes fixture rows: dry-run by
|
|
65
|
+
default, backs up first, idempotent, and aborts leaving the ledger untouched if the rewrite
|
|
66
|
+
fails. It is run from a repository checkout — `scripts/` is not in the published tarball, so
|
|
67
|
+
installed consumers do not have it.
|
|
68
|
+
- **Audit hash chain could fork under concurrent writers.** `bin/autonomous/audit-writer.js`
|
|
69
|
+
read the chain head and appended with no mutual exclusion, and cached the head in-process
|
|
70
|
+
indefinitely — so once a second process appended, the first kept chaining from a superseded
|
|
71
|
+
hash. Added `bin/utils/file-lock.js` (a fail-closed advisory lock promoted from
|
|
72
|
+
`bin/learning/instinct-cli.js`, deliberately NOT from `.agent/bin/lib/state.cjs`, which
|
|
73
|
+
writes anyway when the lock cannot be taken) held across read-head-through-fsync, and made
|
|
74
|
+
the cached head carry the file size that witnesses it is still the tail. 8 concurrent
|
|
75
|
+
appenders went from 199 broken links + 4 forks per 200 entries to 0. A lock alone was
|
|
76
|
+
measured insufficient — it still left 2 breaks and 1 fork, because the stale cache is a
|
|
77
|
+
second, independent defect.
|
|
78
|
+
- **Knowledge-graph edge updates were lost under concurrency.** `deprecateEdge`,
|
|
79
|
+
`reinforceEdge` and `applyDecay` in `bin/memory/knowledge-graph.js` each did
|
|
80
|
+
`readAllEdges()` -> mutate -> append with no lock, and `addEdge` appended unserialised
|
|
81
|
+
against them. Measured at HEAD over 4 runs of 8 processes x 20 `reinforceEdge` calls:
|
|
82
|
+
93-129 of 160 increments lost, final `traversal_count` 31-67 instead of 160. All four
|
|
83
|
+
write paths now hold the `graph-edges.jsonl` lock across read-through-append; the same
|
|
84
|
+
probe then loses 0 of 160 in every run, with no lock-acquisition failures.
|
|
85
|
+
- `bin/governance/policy-engine.js`: `logAudit`'s un-awaited audit write now has a
|
|
86
|
+
`.catch()` — a lock-contention failure is reported at the decision site instead of
|
|
87
|
+
escaping as an unhandled rejection.
|
|
88
|
+
- `bin/hooks/instinct-capture-hook.js` appended to the instinct store without the lock that
|
|
89
|
+
`instinct-cli`'s prune/import rewrite holds, so a hook append landing in that window was
|
|
90
|
+
clobbered by the rename. It now takes the same lock.
|
|
91
|
+
- `tests/v7-sovereign-security.test.js`: `new PolicyEngine()` no longer defaults
|
|
92
|
+
`planningDir` to `process.cwd()`, which appended test verdicts to the operator's real
|
|
93
|
+
`.planning/RISK-AUDIT.jsonl`.
|
|
94
|
+
- Packaging: `package.json` files[] now excludes `**/*.lock` so a lockfile orphaned by a
|
|
95
|
+
hard kill cannot leak into the npm tarball (verified: without the negation, a
|
|
96
|
+
`.mindforge/memory/graph-edges.jsonl.lock` does ship).
|
|
97
|
+
|
|
98
|
+
- **Audit-chain forgery via un-awaited rollback.** `HindsightInjector.inject` called
|
|
99
|
+
the async `TemporalHub.rollbackTo` without `await`, so its rejection escaped the
|
|
100
|
+
surrounding `try/catch` while execution continued: a failed rollback still fsync'd a
|
|
101
|
+
hash-chained `hindsight_injected` entry and flipped `auto-state.json` to
|
|
102
|
+
`awaiting_regeneration` for something that never happened. The log gained a
|
|
103
|
+
cryptographically valid record of a non-event, and `verify-audit` reported the chain
|
|
104
|
+
valid — valid and wrong.
|
|
105
|
+
- **CLI `defaultArgs` were replaced by user arguments, not prepended.** `mindforge
|
|
106
|
+
health <anything>` lost `--check` and fell through to `installer-core`'s real
|
|
107
|
+
`install()` with `force: true`. Now prepended.
|
|
108
|
+
- **`_verifyMetadata` compared UTF-16 code units, not bytes**, so a 64-unit / 65-byte
|
|
109
|
+
`integrity` still threw `RangeError` — and the caller degraded that throw into
|
|
110
|
+
"proceeding without integrity check" and restored the snapshot anyway. Read and
|
|
111
|
+
verify are now separate stages; only a genuinely absent `SNAPSHOT-META.json` reaches
|
|
112
|
+
the tolerant path.
|
|
113
|
+
- **Dashboard `RevOpsAPI` was required but never mounted**, so `/api/revops/overview`
|
|
114
|
+
404'd while the AgRevOps panels and docs described it as live. Even mounted it threw:
|
|
115
|
+
`getAuditEntries()` returns `{entries,total,limit,offset}` and three engines call
|
|
116
|
+
`.filter()` on it.
|
|
117
|
+
- **Dashboard leaked error internals to clients.** `err.message` reached response
|
|
118
|
+
bodies from 14 sites (`api-router.js` ×10, `temporal-api.js` ×3, `revops-api.js` ×1);
|
|
119
|
+
for fs-sourced errors that string carries absolute paths, disclosing the operator's
|
|
120
|
+
username and home directory. All sites now log server-side and return a generic
|
|
121
|
+
message plus a `correlation_id`, behind a 4-arg terminal handler that stops express
|
|
122
|
+
rendering `err.stack` when `NODE_ENV !== 'production'`.
|
|
123
|
+
- **Test runner discovery was a flat `readdirSync`**, so any suite in a subdirectory was
|
|
124
|
+
invisible. Now a recursive walk that prunes `tmp-*` / `node_modules` / dot
|
|
125
|
+
directories — directories only, never files.
|
|
126
|
+
- Three orphan files removed: one truncated `.planning/AUDIT.jsonl` to zero, one
|
|
127
|
+
overwrote `.planning/STATE.md`, one called a function absent from `bin/`. Recursion
|
|
128
|
+
made the `STATE.md` clobberer reachable by a single in-place rename; the other two
|
|
129
|
+
were unreachable by the runner at any depth. **The claim in `5177225`'s message that
|
|
130
|
+
all three were newly armed by recursion is correct for one of the three and
|
|
131
|
+
over-attributed for the other two.**
|
|
132
|
+
- Three relocated demos kept one-level-up requires after moving a directory deeper, so
|
|
133
|
+
all three exited 1; corrected to `../../bin/`.
|
|
134
|
+
|
|
135
|
+
### Changed
|
|
136
|
+
|
|
137
|
+
- **`mindforge audit-skill`, `register-skill`, `install-skill` and `record-learning` no
|
|
138
|
+
longer carry `defaultArgs`.** Prepending turned them from inert into live state
|
|
139
|
+
writers: `audit-skill <name> <ver> <tier>` appended a hash-chained
|
|
140
|
+
`{event:'skill_installed', validation_passed:true}` entry for a skill that does not
|
|
141
|
+
exist, and `register-skill` wrote a malformed row above the table header of
|
|
142
|
+
`.mindforge/org/skills/MANIFEST.md`, which ships in the tarball. Bare invocations now
|
|
143
|
+
print usage and exit 0 without reaching those writers, as at v11.9.1.
|
|
144
|
+
- **`subagent` is a first-class command.** Prepending `spawn` shadowed `spawn-agent`'s
|
|
145
|
+
`subagent` mode, whose documented route was `mindforge spawn subagent <name>`.
|
|
146
|
+
- **The dashboard now exits on an unhandled rejection instead of logging and
|
|
147
|
+
continuing.** An escaped rejection is the only reliable signal that an async call was
|
|
148
|
+
left un-awaited, and express 4.22.1 does not route async handler rejections to error
|
|
149
|
+
middleware — log-and-continue held the client socket open until the client gave up
|
|
150
|
+
(2.5s, 4s and 8s clients all timed out) versus a ~15ms reset on exit. Symmetric with
|
|
151
|
+
`uncaughtException`, whose log-and-continue form had made `shutdown()` swallow a
|
|
152
|
+
throwing token unlink and keep serving the token-authenticated mutation API **after
|
|
153
|
+
SIGTERM**, with the bearer token still on disk and valid in memory.
|
|
154
|
+
- **Dashboard error responses changed shape.** `detail` is removed from 5 endpoints,
|
|
155
|
+
raw errno strings from 10 more, `correlation_id` is added to 15, and a malformed
|
|
156
|
+
request body now returns `application/json` rather than express's `text/html` error
|
|
157
|
+
page. Anything parsing `detail` must correlate on the logged id instead.
|
|
158
|
+
|
|
159
|
+
### Added
|
|
160
|
+
|
|
161
|
+
- **Secret scanning enforced at three layers**: `.gitleaks.toml`, a `.husky/pre-commit`
|
|
162
|
+
gate that fails loudly when gitleaks is absent rather than skipping, and
|
|
163
|
+
`.github/workflows/secret-scan.yml` scanning full history. `scripts/ci/verify-secret-scan.sh`
|
|
164
|
+
self-tests the scanner — it distinguishes "scanned clean" from "scanned and found"
|
|
165
|
+
from "did not scan", because gitleaks exits 1 for both a finding and a failed config
|
|
166
|
+
load, and writes no report in the latter case.
|
|
167
|
+
|
|
168
|
+
### Tests
|
|
169
|
+
|
|
170
|
+
- Suite totals for this release: **105 files, 103 pass, 2 environment-dependent skips**
|
|
171
|
+
(`browser`, `sre-integration`). Eight new suites across the release:
|
|
172
|
+
`temporal-integrity`, `dashboard-error-leak`, `dashboard-crash-guards`,
|
|
173
|
+
`dashboard-wiring`, `cli-router`, `mindforge-params`, `file-lock`, `retrieval-fts`.
|
|
174
|
+
- **Four suites could not report failure and now can.** `v8-persistence`,
|
|
175
|
+
`v8-skill-evolution` and `v8-orbital-governance` ended `finally { process.exit(0) }`, and
|
|
176
|
+
`v7-pillar-integration` had zero assertions with a premium-model gate that named two models
|
|
177
|
+
absent from the registry for several releases. `npm test` is the only quality step before
|
|
178
|
+
`npm publish`, and the runner gates on child exit codes, so a blind suite blinded the publish
|
|
179
|
+
gate. Verified by injected failure rather than inspection.
|
|
180
|
+
`dashboard-wiring` derives the expected router set
|
|
181
|
+
from `server.js`'s own requires, so adding a router without mounting it fails.
|
|
182
|
+
`cli-router` runs against a mirror-root sandbox under `os.tmpdir()` — required, not
|
|
183
|
+
tidiness: the case that proves audit forgery is prevented would otherwise forge an
|
|
184
|
+
entry into the real chain on every run. `revops-roi.test.js` had 0 assertions and
|
|
185
|
+
could not fail; it now has 6.
|
|
186
|
+
|
|
187
|
+
### Not fixed — deferred to v12
|
|
188
|
+
|
|
189
|
+
- **No hook is registered in any consumer install.** The installer copies 9 hook
|
|
190
|
+
scripts into `<runtime>/hooks/`, but nothing writes `.claude/settings.json` and it is
|
|
191
|
+
absent from `package.json` `files[]`. Verified by installing the tarball into a
|
|
192
|
+
scratch project. Every gate this release hardens is inert until that lands.
|
|
193
|
+
- **`requireAuth` exempts GET and OPTIONS**, so every read route — including
|
|
194
|
+
`/api/audit`, which serves the hash-chained audit log — is credential-free to any
|
|
195
|
+
local process. Mutations are protected. This is a threat-model decision, not a patch.
|
|
196
|
+
- **`audit-skill audit <name> <ver> <tier>`** — the explicit form — still reaches a
|
|
197
|
+
writer that performs no existence check and hardcodes `validation_passed: true`. Only
|
|
198
|
+
the bare invocation is closed.
|
|
199
|
+
- **Snapshot integrity is not an authenticity control.** `HMAC_KEY` is a literal in
|
|
200
|
+
shipped source, the HMAC covers only the metadata object so file **contents** are
|
|
201
|
+
unsigned (editing a file inside a signed snapshot leaves the signature valid), and
|
|
202
|
+
deleting `SNAPSHOT-META.json` bypasses verification entirely.
|
|
203
|
+
- `cwd: ROOT` in the CLI, which resolves consumer state inside `node_modules`.
|
|
204
|
+
- `security-scan` cannot fail: its parser expects `KEY=value` while `MINDFORGE.md` uses
|
|
205
|
+
`[KEY] = value`, so it always reports 0 settings and exits 0.
|
|
206
|
+
- Version drift in six publishable manifests (`Formula/mindforge.rb`, `Dockerfile`,
|
|
207
|
+
`mcp-server/server.json`, `mcp-server/src/index.ts`, the plugin manifest and the
|
|
208
|
+
marketplace entry) is untouched here — none is gated, and the Formula pins a tarball
|
|
209
|
+
sha256 that cannot exist before publish.
|