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.
Files changed (121) hide show
  1. package/.agent/CLAUDE.md +37 -13
  2. package/.agent/hooks/mindforge-block-no-verify.js +61 -13
  3. package/.agent/hooks/mindforge-config-protection.js +82 -3
  4. package/.agent/hooks/mindforge-context-monitor.js +1 -1
  5. package/.agent/hooks/mindforge-workflow-guard.js +2 -2
  6. package/.agent/hooks/run-with-flags.js +190 -20
  7. package/.agent/mindforge/browse.md +2 -2
  8. package/.agent/mindforge/checkpoint.md +1 -1
  9. package/.agent/mindforge/consult.md +1 -1
  10. package/.agent/mindforge/cost-report.md +1 -1
  11. package/.agent/mindforge/harness-audit.md +1 -1
  12. package/.agent/mindforge/orch-add-feature.md +1 -1
  13. package/.agent/mindforge/orch-build-mvp.md +1 -1
  14. package/.agent/mindforge/orch-change-feature.md +1 -1
  15. package/.agent/mindforge/orch-fix-defect.md +1 -1
  16. package/.agent/mindforge/orch-refine-code.md +1 -1
  17. package/.agent/mindforge/qa.md +2 -2
  18. package/.claude/CLAUDE.md +37 -13
  19. package/.claude/commands/mindforge/browse.md +2 -2
  20. package/.claude/commands/mindforge/checkpoint.md +1 -1
  21. package/.claude/commands/mindforge/consult.md +1 -1
  22. package/.claude/commands/mindforge/cost-report.md +1 -1
  23. package/.claude/commands/mindforge/harness-audit.md +1 -1
  24. package/.claude/commands/mindforge/orch-add-feature.md +1 -1
  25. package/.claude/commands/mindforge/orch-build-mvp.md +1 -1
  26. package/.claude/commands/mindforge/orch-change-feature.md +1 -1
  27. package/.claude/commands/mindforge/orch-fix-defect.md +1 -1
  28. package/.claude/commands/mindforge/orch-refine-code.md +1 -1
  29. package/.claude/commands/mindforge/qa.md +2 -2
  30. package/.mindforge/MINDFORGE-SCHEMA.json +126 -13
  31. package/.mindforge/config.json +4 -4
  32. package/.mindforge/engine/autonomous/headless-adapter.md +2 -2
  33. package/.mindforge/engine/cost-tracking/router.md +1 -1
  34. package/.mindforge/engine/cost-tracking/token-ledger.md +21 -24
  35. package/.mindforge/engine/temporal-protocol.md +2 -2
  36. package/.mindforge/governance/change-classifier.md +20 -4
  37. package/.mindforge/memory/sync-manifest.json +1 -1
  38. package/.mindforge/metrics/METRICS-SCHEMA.md +13 -4
  39. package/.mindforge/personas/cost-optimizer.md +2 -2
  40. package/.mindforge/personas/multi-model-bridge.md +1 -1
  41. package/.mindforge/skills/agent-architecture-audit/SKILL.md +2 -2
  42. package/.mindforge/skills/cost-aware-routing/SKILL.md +3 -3
  43. package/.mindforge/skills/multi-llm-consult/SKILL.md +2 -2
  44. package/.mindforge/skills/orch-pipeline/SKILL.md +4 -4
  45. package/CHANGELOG.md +402 -0
  46. package/MINDFORGE.md +13 -6
  47. package/README.md +51 -2
  48. package/RELEASENOTES.md +55 -2
  49. package/SECURITY.md +22 -3
  50. package/bin/autonomous/audit-writer.js +48 -33
  51. package/bin/autonomous/auto-runner.js +65 -2
  52. package/bin/change-classifier.js +151 -16
  53. package/bin/dashboard/api-router.js +28 -47
  54. package/bin/dashboard/error-response.js +44 -0
  55. package/bin/dashboard/frontend/app.js +429 -0
  56. package/bin/dashboard/frontend/index.html +14 -390
  57. package/bin/dashboard/metrics-aggregator.js +75 -30
  58. package/bin/dashboard/revops-api.js +12 -2
  59. package/bin/dashboard/server.js +245 -6
  60. package/bin/dashboard/sse-bridge.js +11 -8
  61. package/bin/dashboard/temporal-api.js +11 -5
  62. package/bin/engine/remediation-engine.js +12 -1
  63. package/bin/engine/sre-manager.js +1 -1
  64. package/bin/engine/temporal-cli.js +56 -6
  65. package/bin/engine/temporal-hub.js +41 -9
  66. package/bin/engine/verification-runner.js +134 -17
  67. package/bin/engine/verify-cli.js +25 -7
  68. package/bin/eval/eval-harness.js +212 -1
  69. package/bin/eval/golden-set-retrieval.json +9 -0
  70. package/bin/governance/approval-record.js +147 -0
  71. package/bin/governance/approve.js +12 -7
  72. package/bin/governance/policy-engine.js +41 -3
  73. package/bin/governance/policy-gate-hardened.js +36 -1
  74. package/bin/governance/verify-approvals.js +163 -0
  75. package/bin/harness-audit.js +224 -10
  76. package/bin/hindsight-injector.js +8 -2
  77. package/bin/hooks/instinct-capture-hook.js +19 -5
  78. package/bin/install.js +63 -3
  79. package/bin/installer/harness-adapter-compliance.js +339 -28
  80. package/bin/installer/hook-registration.js +504 -0
  81. package/bin/installer-core.js +451 -63
  82. package/bin/learning/instinct-cli.js +14 -24
  83. package/bin/memory/knowledge-capture.js +23 -3
  84. package/bin/memory/knowledge-graph.js +70 -31
  85. package/bin/memory/vector-hub.js +500 -44
  86. package/bin/migrations/0.6.0-to-1.0.0.js +30 -25
  87. package/bin/migrations/1.0.0-to-2.0.0.js +22 -23
  88. package/bin/mindforge-cli.js +110 -17
  89. package/bin/models/cost-tracker.js +126 -29
  90. package/bin/models/model-client.js +6 -1
  91. package/bin/models/model-router.js +28 -7
  92. package/bin/models/usage-record.js +71 -0
  93. package/bin/revops/debt-monitor.js +57 -13
  94. package/bin/security/trust-gate-hook.js +50 -6
  95. package/bin/skill-validator.js +6 -1
  96. package/bin/skills-builder/skill-scorer.js +46 -6
  97. package/bin/updater/self-update.js +6 -1
  98. package/bin/updater/version-comparator.js +21 -1
  99. package/bin/utils/file-lock.js +106 -0
  100. package/bin/utils/mindforge-params.js +124 -0
  101. package/bin/utils/mindforge-version.js +99 -0
  102. package/bin/utils/redact-secrets.js +106 -0
  103. package/bin/validate-config.js +75 -17
  104. package/bin/wizard/setup-wizard.js +4 -1
  105. package/bin/wizard/theme.js +9 -1
  106. package/changelogs/index.json +11 -9
  107. package/changelogs/v11.9.2.md +209 -0
  108. package/changelogs/v11.9.3.md +195 -0
  109. package/docs/References/config-reference.md +76 -14
  110. package/docs/References/sdk-api.md +1 -1
  111. package/docs/Templates/Codebase/architecture.md +1 -1
  112. package/docs/commands-reference.md +4 -5
  113. package/docs/faq.md +25 -5
  114. package/docs/getting-started.md +3 -3
  115. package/docs/sdk-reference.md +15 -7
  116. package/docs/troubleshooting.md +10 -6
  117. package/docs/user-guide.md +14 -14
  118. package/examples/sdk-integration/README.md +1 -1
  119. package/package.json +10 -4
  120. package/subagents/.claude-plugin/marketplace.json +1 -1
  121. 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 (Node has no fcntl): exclusive lockfile create, spin-with-timeout,
75
- * stale-break after 10s by mtime. Runs fn() while held, releases in finally.
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
- const lock = `${p}.lock`;
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 { /* skip duplicates or self-refs */ }
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 { /* skip duplicates or self-refs */ }
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 { /* non-critical */ }
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
- fs.appendFileSync(paths.EDGES_PATH, JSON.stringify(record) + '\n');
112
- invalidateAdjacencyCache();
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
- const edges = readAllEdges();
148
- const edge = edges.find(e => e.id === edgeId);
149
- if (!edge) return;
150
-
151
- const deprecated = {
152
- ...edge,
153
- deprecated: true,
154
- deprecated_reason: reason,
155
- deprecated_at: new Date().toISOString(),
156
- };
157
-
158
- fs.appendFileSync(paths.EDGES_PATH, JSON.stringify(deprecated) + '\n');
159
- invalidateAdjacencyCache();
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
- const edges = readAllEdges();
169
- const edge = edges.find(e => e.id === edgeId);
170
- if (!edge) return;
171
-
172
- const reinforced = {
173
- ...edge,
174
- weight: Math.min(2.0, edge.weight + 0.1),
175
- last_traversed: new Date().toISOString(),
176
- traversal_count: (edge.traversal_count || 0) + 1,
177
- };
178
-
179
- // Recompute checksum
180
- const payload = JSON.stringify({ ...reinforced, checksum: '' });
181
- reinforced.checksum = crypto.createHash('sha256').update(payload).digest('hex');
182
-
183
- fs.appendFileSync(paths.EDGES_PATH, JSON.stringify(reinforced) + '\n');
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;