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
|
@@ -23,7 +23,9 @@ const fs = require('fs');
|
|
|
23
23
|
const path = require('path');
|
|
24
24
|
|
|
25
25
|
const guard = require('./lib/ssrf-guard');
|
|
26
|
+
const { redactSecrets } = require('../utils/redact-secrets');
|
|
26
27
|
const { detectProject } = require('../hooks/lib/detect-project');
|
|
28
|
+
const { withFileLock } = require('../utils/file-lock');
|
|
27
29
|
|
|
28
30
|
const CONFIG_PATH = path.join(process.cwd(), '.mindforge', 'config.json');
|
|
29
31
|
|
|
@@ -71,33 +73,15 @@ function writeStoreAtomic(p, entries) {
|
|
|
71
73
|
}
|
|
72
74
|
|
|
73
75
|
/**
|
|
74
|
-
* Advisory lock
|
|
75
|
-
*
|
|
76
|
+
* Advisory lock — now a thin delegate to the shared fail-closed lock (LOCK-01).
|
|
77
|
+
* The implementation moved to bin/utils/file-lock.js so the audit chain and the
|
|
78
|
+
* knowledge graph use the SAME lock semantics as the instinct store. Behaviour is
|
|
79
|
+
* unchanged: O_EXCL create, 50 tries, ~20ms waits, 10s stale reclaim, unlink in
|
|
80
|
+
* finally, THROW (never write anyway) when unacquirable — same error text.
|
|
76
81
|
* Read the store INSIDE this so a prune/import rewrite can't race a hook append.
|
|
77
82
|
*/
|
|
78
83
|
function withStoreLock(p, fn) {
|
|
79
|
-
|
|
80
|
-
const maxTries = 50, waitMs = 20, staleMs = 10000;
|
|
81
|
-
let held = false;
|
|
82
|
-
for (let i = 0; i < maxTries && !held; i++) {
|
|
83
|
-
try {
|
|
84
|
-
const fd = fs.openSync(lock, 'wx');
|
|
85
|
-
fs.closeSync(fd);
|
|
86
|
-
held = true;
|
|
87
|
-
} catch (err) {
|
|
88
|
-
if (err.code !== 'EEXIST') throw err;
|
|
89
|
-
// stale-break: if the lockfile is older than staleMs, remove it.
|
|
90
|
-
try {
|
|
91
|
-
const age = Date.now() - fs.statSync(lock).mtimeMs;
|
|
92
|
-
if (age > staleMs) { fs.unlinkSync(lock); continue; }
|
|
93
|
-
} catch { /* lock vanished — retry */ }
|
|
94
|
-
const until = Date.now() + waitMs;
|
|
95
|
-
while (Date.now() < until) { /* busy-wait (short) */ }
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
if (!held) throw new Error(`could not acquire instinct-store lock: ${lock}`);
|
|
99
|
-
try { return fn(); }
|
|
100
|
-
finally { try { fs.unlinkSync(lock); } catch { /* already gone */ } }
|
|
84
|
+
return withFileLock(p, fn, { label: 'instinct-store' });
|
|
101
85
|
}
|
|
102
86
|
|
|
103
87
|
function currentProjectId() {
|
|
@@ -214,7 +198,13 @@ async function cmdImport(args, cfg) {
|
|
|
214
198
|
for (const e of incoming) {
|
|
215
199
|
if (!e || !guard.validateInstinctId(e.id || '')) { process.stderr.write(`skipping entry with invalid id: ${e && e.id}\n`); continue; }
|
|
216
200
|
if ((Number(e.confidence) || 0) < minC) continue;
|
|
201
|
+
// Import is the store's second ingress and it accepts an https:// source, so incoming text is
|
|
202
|
+
// untrusted twice over: it may carry a credential from whoever exported it. Redacting here
|
|
203
|
+
// rather than at every egress means export, promote, list and the LLM promotion path into a
|
|
204
|
+
// published SKILL.md all inherit already-clean data — one choke point instead of five.
|
|
217
205
|
valid.push(Object.assign({}, e, {
|
|
206
|
+
observation: redactSecrets(e.observation),
|
|
207
|
+
behavior: redactSecrets(e.behavior),
|
|
218
208
|
project: projectName, project_id: scopeId, source: 'imported',
|
|
219
209
|
imported_from: source, created_at: now, updated_at: now,
|
|
220
210
|
}));
|
|
@@ -133,7 +133,14 @@ function createCausalEdges(bugId, allEntries, vectors) {
|
|
|
133
133
|
weight: sim,
|
|
134
134
|
reason: `Bug pattern potentially caused by code pattern (sim: ${sim.toFixed(3)})`,
|
|
135
135
|
});
|
|
136
|
-
} catch {
|
|
136
|
+
} catch (err) {
|
|
137
|
+
// LOCK-01: this catch predates the graph-edges lock. Without the guard below,
|
|
138
|
+
// an edge LOST to lock contention is reported as a skipped duplicate — a
|
|
139
|
+
// silent write loss wearing the label of a no-op.
|
|
140
|
+
if (/could not acquire/.test(err.message)) {
|
|
141
|
+
console.warn(`[KnowledgeCapture] edge NOT written — graph-edges lock unavailable: ${err.message}`);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
137
144
|
}
|
|
138
145
|
}
|
|
139
146
|
}
|
|
@@ -165,7 +172,14 @@ function createInformsEdges(decisionId, allEntries, vectors) {
|
|
|
165
172
|
weight: sim,
|
|
166
173
|
reason: `Decision informs domain knowledge (sim: ${sim.toFixed(3)})`,
|
|
167
174
|
});
|
|
168
|
-
} catch {
|
|
175
|
+
} catch (err) {
|
|
176
|
+
// LOCK-01: this catch predates the graph-edges lock. Without the guard below,
|
|
177
|
+
// an edge LOST to lock contention is reported as a skipped duplicate — a
|
|
178
|
+
// silent write loss wearing the label of a no-op.
|
|
179
|
+
if (/could not acquire/.test(err.message)) {
|
|
180
|
+
console.warn(`[KnowledgeCapture] edge NOT written — graph-edges lock unavailable: ${err.message}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
169
183
|
}
|
|
170
184
|
}
|
|
171
185
|
}
|
|
@@ -180,7 +194,13 @@ function reinforceRelatedEdges(nodeId) {
|
|
|
180
194
|
for (const edge of edges.slice(0, 3)) { // Top 3 edges only
|
|
181
195
|
Graph.reinforceEdge(edge.id);
|
|
182
196
|
}
|
|
183
|
-
} catch {
|
|
197
|
+
} catch (err) {
|
|
198
|
+
// LOCK-01: reinforceEdge now takes the graph-edges lock, so "non-critical" would
|
|
199
|
+
// also absorb a contention failure. Surface that one; keep the rest quiet.
|
|
200
|
+
if (/could not acquire/.test(err.message)) {
|
|
201
|
+
console.warn(`[KnowledgeCapture] reinforcement NOT recorded — graph-edges lock unavailable: ${err.message}`);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
184
204
|
}
|
|
185
205
|
|
|
186
206
|
// ── Event-specific capture functions ─────────────────────────────────────────
|
|
@@ -17,6 +17,7 @@ const crypto = require('crypto');
|
|
|
17
17
|
const Store = require('./knowledge-store');
|
|
18
18
|
const Embedder = require('./embedding-engine');
|
|
19
19
|
const EISClient = require('./eis-client');
|
|
20
|
+
const { withFileLock } = require('../utils/file-lock');
|
|
20
21
|
|
|
21
22
|
// ── Edge Types ────────────────────────────────────────────────────────────────
|
|
22
23
|
const EDGE_TYPES = Object.freeze({
|
|
@@ -59,6 +60,19 @@ function ensureDir(dir) {
|
|
|
59
60
|
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
60
61
|
}
|
|
61
62
|
|
|
63
|
+
/**
|
|
64
|
+
* LOCK-01: runs fn() holding the exclusive lock on graph-edges.jsonl.
|
|
65
|
+
* Every edge write below is a READ-modify-APPEND (readAllEdges -> mutate -> append),
|
|
66
|
+
* so the read MUST be inside the same critical section as the append or concurrent
|
|
67
|
+
* writers lose updates. Measured over 4 runs of 8 processes x 20 reinforceEdge calls:
|
|
68
|
+
* unlocked lost 93-129 of 160 increments (final traversal_count 31-67); locked lost 0
|
|
69
|
+
* of 160 in every run.
|
|
70
|
+
* Re-entrant: applyDecay() holds this lock and calls deprecateEdge(), which re-takes it.
|
|
71
|
+
*/
|
|
72
|
+
function withEdgesLock(fn, opts = {}) {
|
|
73
|
+
return withFileLock(getPaths().EDGES_PATH, fn, { maxTries: 150, label: 'graph-edges', ...opts });
|
|
74
|
+
}
|
|
75
|
+
|
|
62
76
|
// ── Edge CRUD ─────────────────────────────────────────────────────────────────
|
|
63
77
|
|
|
64
78
|
/**
|
|
@@ -108,8 +122,12 @@ function addEdge(edge) {
|
|
|
108
122
|
const payload = JSON.stringify({ ...record, checksum: '' });
|
|
109
123
|
record.checksum = crypto.createHash('sha256').update(payload).digest('hex');
|
|
110
124
|
|
|
111
|
-
|
|
112
|
-
|
|
125
|
+
// LOCK-01: serialise against the read-modify-append paths below, whose readAllEdges()
|
|
126
|
+
// snapshot would otherwise go stale if this append landed inside their window.
|
|
127
|
+
withEdgesLock(() => {
|
|
128
|
+
fs.appendFileSync(paths.EDGES_PATH, JSON.stringify(record) + '\n');
|
|
129
|
+
invalidateAdjacencyCache();
|
|
130
|
+
});
|
|
113
131
|
return id;
|
|
114
132
|
}
|
|
115
133
|
|
|
@@ -144,19 +162,23 @@ function readAllEdges() {
|
|
|
144
162
|
*/
|
|
145
163
|
function deprecateEdge(edgeId, reason) {
|
|
146
164
|
const paths = getPaths();
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
165
|
+
// LOCK-01: read-modify-append is ONE critical section — the readAllEdges() snapshot
|
|
166
|
+
// must not be able to go stale before our append lands.
|
|
167
|
+
return withEdgesLock(() => {
|
|
168
|
+
const edges = readAllEdges();
|
|
169
|
+
const edge = edges.find(e => e.id === edgeId);
|
|
170
|
+
if (!edge) return;
|
|
171
|
+
|
|
172
|
+
const deprecated = {
|
|
173
|
+
...edge,
|
|
174
|
+
deprecated: true,
|
|
175
|
+
deprecated_reason: reason,
|
|
176
|
+
deprecated_at: new Date().toISOString(),
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
fs.appendFileSync(paths.EDGES_PATH, JSON.stringify(deprecated) + '\n');
|
|
180
|
+
invalidateAdjacencyCache();
|
|
181
|
+
});
|
|
160
182
|
}
|
|
161
183
|
|
|
162
184
|
/**
|
|
@@ -165,22 +187,26 @@ function deprecateEdge(edgeId, reason) {
|
|
|
165
187
|
*/
|
|
166
188
|
function reinforceEdge(edgeId) {
|
|
167
189
|
const paths = getPaths();
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
// LOCK-01: weight += 0.1 and traversal_count += 1 are read-modify-writes. Unlocked,
|
|
191
|
+
// 8 concurrent processes x 20 reinforcements lost 93-129 of 160 increments (4 runs).
|
|
192
|
+
return withEdgesLock(() => {
|
|
193
|
+
const edges = readAllEdges();
|
|
194
|
+
const edge = edges.find(e => e.id === edgeId);
|
|
195
|
+
if (!edge) return;
|
|
196
|
+
|
|
197
|
+
const reinforced = {
|
|
198
|
+
...edge,
|
|
199
|
+
weight: Math.min(2.0, edge.weight + 0.1),
|
|
200
|
+
last_traversed: new Date().toISOString(),
|
|
201
|
+
traversal_count: (edge.traversal_count || 0) + 1,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
// Recompute checksum
|
|
205
|
+
const payload = JSON.stringify({ ...reinforced, checksum: '' });
|
|
206
|
+
reinforced.checksum = crypto.createHash('sha256').update(payload).digest('hex');
|
|
207
|
+
|
|
208
|
+
fs.appendFileSync(paths.EDGES_PATH, JSON.stringify(reinforced) + '\n');
|
|
209
|
+
});
|
|
184
210
|
}
|
|
185
211
|
|
|
186
212
|
// ── Adjacency Index (with persistent cache) ─────────────────────────────────
|
|
@@ -489,6 +515,19 @@ function autoCreateEdges(entryId, vectors) {
|
|
|
489
515
|
* @returns {{ decayed: number, pruned: number }}
|
|
490
516
|
*/
|
|
491
517
|
function applyDecay() {
|
|
518
|
+
// LOCK-01: the ENTIRE pass is one read-modify-write — one readAllEdges() snapshot
|
|
519
|
+
// drives appends at the bottom of this loop AND nested deprecateEdge() calls. The
|
|
520
|
+
// nested calls re-take this same lock; withFileLock is re-entrant for that reason.
|
|
521
|
+
// staleMs is raised far above the 10s module default. This pass is O(n^2) — the
|
|
522
|
+
// nested deprecateEdge() re-reads the whole edges file per prune — and was measured
|
|
523
|
+
// holding this lock 9.1s at 2,000 edges and 14.4s at 2,500. Past STALE_MS another
|
|
524
|
+
// process classifies this LIVE lock as orphaned, unlinks it, and enters the critical
|
|
525
|
+
// section alongside us, so mutual exclusion would silently vanish on any real-sized
|
|
526
|
+
// graph — the same "looks guarded, isn't" failure this item exists to remove.
|
|
527
|
+
return withEdgesLock(() => applyDecayLocked(), { staleMs: 900000 });
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function applyDecayLocked() {
|
|
492
531
|
const edges = readAllEdges();
|
|
493
532
|
const now = Date.now();
|
|
494
533
|
let decayed = 0;
|