mindforge-cc 10.7.0 → 11.2.0

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 (85) hide show
  1. package/.agent/hooks/mindforge-statusline.js +2 -2
  2. package/.mindforge/MINDFORGE-V2-SCHEMA.json +43 -10
  3. package/.mindforge/config.json +18 -4
  4. package/CHANGELOG.md +165 -0
  5. package/MINDFORGE.md +3 -3
  6. package/README.md +49 -4
  7. package/RELEASENOTES.md +81 -1
  8. package/SECURITY.md +20 -8
  9. package/bin/autonomous/audit-writer.js +105 -70
  10. package/bin/autonomous/auto-runner.js +377 -34
  11. package/bin/autonomous/context-refactorer.js +26 -11
  12. package/bin/autonomous/dependency-dag.js +59 -0
  13. package/bin/autonomous/state-manager.js +62 -6
  14. package/bin/autonomous/stuck-monitor.js +46 -7
  15. package/bin/autonomous/wave-executor.js +86 -26
  16. package/bin/council-cli.js +161 -0
  17. package/bin/dashboard/api-router.js +43 -0
  18. package/bin/dashboard/approval-handler.js +3 -1
  19. package/bin/dashboard/metrics-aggregator.js +28 -1
  20. package/bin/dashboard/server.js +68 -5
  21. package/bin/dashboard/sse-bridge.js +10 -13
  22. package/bin/engine/council-runtime.js +124 -0
  23. package/bin/engine/feedback-loop.js +8 -0
  24. package/bin/engine/intelligence-interlock.js +32 -15
  25. package/bin/engine/logic-drift-detector.js +2 -1
  26. package/bin/engine/nexus-tracer.js +3 -2
  27. package/bin/engine/otel-exporter.js +123 -0
  28. package/bin/engine/remediation-engine.js +155 -32
  29. package/bin/engine/self-corrective-synthesizer.js +84 -10
  30. package/bin/engine/sre-manager.js +12 -4
  31. package/bin/engine/temporal-cli.js +4 -2
  32. package/bin/engine/temporal-hub.js +131 -34
  33. package/bin/engine/verification-runner.js +131 -0
  34. package/bin/engine/verify-cli.js +34 -0
  35. package/bin/eval/eval-harness.js +82 -0
  36. package/bin/eval/golden-set-retrieval.json +46 -0
  37. package/bin/governance/approve.js +41 -5
  38. package/bin/governance/audit-hash.js +12 -0
  39. package/bin/governance/audit-verifier.js +60 -0
  40. package/bin/governance/impact-analyzer.js +28 -0
  41. package/bin/governance/policy-engine.js +10 -3
  42. package/bin/governance/quantum-crypto.js +95 -28
  43. package/bin/governance/rbac-manager.js +74 -2
  44. package/bin/governance/ztai-manager.js +79 -9
  45. package/bin/hindsight-injector.js +8 -9
  46. package/bin/hooks/instinct-capture-hook.js +186 -0
  47. package/bin/memory/auto-shadow.js +32 -3
  48. package/bin/memory/eis-client.js +71 -34
  49. package/bin/memory/embedding-engine.js +61 -0
  50. package/bin/memory/identity-synthesizer.js +2 -2
  51. package/bin/memory/knowledge-graph.js +58 -5
  52. package/bin/memory/knowledge-indexer.js +53 -6
  53. package/bin/memory/knowledge-store.js +52 -6
  54. package/bin/memory/retrieval-fusion.js +58 -0
  55. package/bin/memory/semantic-hub.js +2 -2
  56. package/bin/memory/vector-hub.js +111 -6
  57. package/bin/migrations/10.7.0-to-11.0.0.js +110 -0
  58. package/bin/migrations/schema-versions.js +13 -0
  59. package/bin/mindforge-cli.js +4 -5
  60. package/bin/models/anthropic-provider.js +58 -4
  61. package/bin/models/cloud-broker.js +68 -20
  62. package/bin/models/cost-tracker.js +3 -1
  63. package/bin/models/difficulty-scorer.js +54 -0
  64. package/bin/models/gemini-provider.js +57 -2
  65. package/bin/models/model-client.js +20 -0
  66. package/bin/models/model-router.js +59 -26
  67. package/bin/models/openai-provider.js +50 -3
  68. package/bin/models/pricing-registry.js +128 -0
  69. package/bin/review/ads-engine.js +1 -1
  70. package/bin/security/trust-boundaries.js +102 -0
  71. package/bin/security/trust-gate-hook.js +39 -0
  72. package/bin/skill-registry.js +3 -2
  73. package/bin/skills-builder/marketplace-cli.js +5 -3
  74. package/bin/skills-builder/skill-registrar.js +4 -6
  75. package/bin/sre/sentinel.js +7 -5
  76. package/bin/utils/append-queue.js +55 -0
  77. package/bin/utils/file-io.js +90 -38
  78. package/bin/utils/index.js +58 -0
  79. package/bin/utils/version-check.js +59 -0
  80. package/bin/verify-audit.js +12 -0
  81. package/bin/wizard/theme.js +1 -2
  82. package/docs/getting-started.md +1 -1
  83. package/docs/user-guide.md +2 -2
  84. package/package.json +2 -2
  85. package/bin/dashboard/team-tracker.js +0 -0
@@ -0,0 +1,102 @@
1
+ 'use strict';
2
+
3
+ const crypto = require('crypto');
4
+
5
+ /**
6
+ * Recursively sorts object keys for deterministic JSON serialization.
7
+ * Arrays are preserved in order; nested objects get sorted keys.
8
+ */
9
+ function stableStringify(value) {
10
+ if (value === null || typeof value !== 'object') {
11
+ return JSON.stringify(value);
12
+ }
13
+ if (Array.isArray(value)) {
14
+ return '[' + value.map(item => stableStringify(item)).join(',') + ']';
15
+ }
16
+ const sortedKeys = Object.keys(value).sort();
17
+ const pairs = sortedKeys.map(key => {
18
+ return JSON.stringify(key) + ':' + stableStringify(value[key]);
19
+ });
20
+ return '{' + pairs.join(',') + '}';
21
+ }
22
+
23
+ /**
24
+ * Computes SHA-256 hash of a manifest using stable key-sorted serialization.
25
+ * Returns { name, hash, pinnedAt }.
26
+ */
27
+ function pinManifest(manifest) {
28
+ const serialized = stableStringify(manifest);
29
+ const hash = crypto.createHash('sha256').update(serialized).digest('hex');
30
+ return {
31
+ name: manifest.name,
32
+ hash,
33
+ pinnedAt: Date.now()
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Verifies a manifest against a previously pinned hash.
39
+ * Returns { valid: true } or { valid: false, reason }.
40
+ */
41
+ function verifyManifest(manifest, pin) {
42
+ const serialized = stableStringify(manifest);
43
+ const computed = crypto.createHash('sha256').update(serialized).digest('hex');
44
+ if (computed === pin.hash) {
45
+ return { valid: true };
46
+ }
47
+ return {
48
+ valid: false,
49
+ reason: `hash mismatch: expected ${pin.hash}, got ${computed}`
50
+ };
51
+ }
52
+
53
+ /**
54
+ * Wraps content with untrusted provenance metadata.
55
+ * Returns { content, trusted: false, provenance: { source, tool, timestamp } }.
56
+ */
57
+ function tagUntrusted(content, meta) {
58
+ return {
59
+ content,
60
+ trusted: false,
61
+ provenance: {
62
+ source: meta.source,
63
+ tool: meta.tool,
64
+ timestamp: Date.now()
65
+ }
66
+ };
67
+ }
68
+
69
+ // Null byte (char code 0). Built via fromCharCode so we never embed a control
70
+ // character in a regex literal (eslint no-control-regex).
71
+ const NUL = String.fromCharCode(0);
72
+
73
+ /**
74
+ * Detects high-impact / destructive commands via case-insensitive pattern matching.
75
+ * Returns true if the command matches known destructive patterns.
76
+ */
77
+ function isHighImpact(command) {
78
+ // Strip null bytes first — shells ignore them, so an attacker must not be
79
+ // able to use a NUL to split a destructive token and slip past the patterns.
80
+ const sanitized = String(command).split(NUL).join('');
81
+ const patterns = [
82
+ /rm\s+(-\w*r\w*\s+-\w*f|(-\w*f\w*\s+-\w*r)|-\w*rf|-\w*fr)/i,
83
+ /git\s+push\s+.*--force/i,
84
+ /git\s+push\s+.*-f/i,
85
+ /drop\s+(table|database)/i,
86
+ /git\s+reset\s+--hard/i,
87
+ /delete\s+from/i,
88
+ /truncate\s+table/i,
89
+ /\bmkfs(\.\w+)?\s+\/dev\//i,
90
+ /\bdd\b.*\bof=\/dev\//i,
91
+ /\b(curl|wget)\b.*\|\s*(bash|sh|zsh)\b/i,
92
+ /^\s*find\s+.*-delete\b/i,
93
+ ];
94
+ return patterns.some(pattern => pattern.test(sanitized));
95
+ }
96
+
97
+ module.exports = {
98
+ pinManifest,
99
+ verifyManifest,
100
+ tagUntrusted,
101
+ isHighImpact
102
+ };
@@ -0,0 +1,39 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { isHighImpact } = require('./trust-boundaries');
5
+
6
+ let input = '';
7
+ process.stdin.setEncoding('utf8');
8
+ process.stdin.on('data', (chunk) => { input += chunk; });
9
+ process.stdin.on('end', () => {
10
+ try {
11
+ const event = JSON.parse(input);
12
+
13
+ // Only gate Bash tool calls
14
+ if (event.tool_name !== 'Bash') {
15
+ process.exit(0); // allow
16
+ }
17
+
18
+ const fullCommand = event.tool_input?.command || '';
19
+ const command = fullCommand.split('\n')[0];
20
+
21
+ if (isHighImpact(command)) {
22
+ // Output a block reason (Claude Code shows this to the user)
23
+ process.stdout.write(JSON.stringify({
24
+ decision: 'block',
25
+ reason: `[TrustGate] High-impact command detected: "${command.substring(0, 80)}..." — requires explicit user approval`
26
+ }));
27
+ process.exit(2); // block
28
+ }
29
+
30
+ process.exit(0); // allow
31
+ } catch (e) {
32
+ process.stderr.write('[trust-gate-hook] parse error (BLOCKING): ' + e.message + '\n');
33
+ process.stdout.write(JSON.stringify({
34
+ decision: 'block',
35
+ reason: '[TrustGate] Could not verify command safety — parse error'
36
+ }));
37
+ process.exit(2);
38
+ }
39
+ });
@@ -215,7 +215,6 @@ function handleAudit() {
215
215
  }
216
216
 
217
217
  const entry = {
218
- timestamp: new Date().toISOString(),
219
218
  event: 'skill_installed',
220
219
  skill_name: skillName,
221
220
  skill_version: version,
@@ -224,7 +223,9 @@ function handleAudit() {
224
223
  validation_passed: true
225
224
  };
226
225
 
227
- fs.appendFileSync(auditPath, JSON.stringify(entry) + '\n', 'utf8');
226
+ // UC-04b: unified, hash-chained, durable append into the single verifiable chain.
227
+ const { appendAuditEntrySync } = require('./autonomous/audit-writer');
228
+ appendAuditEntrySync(auditPath, entry);
228
229
  console.log(` 📝 Audit entry written for ${skillName}`);
229
230
  process.exit(0);
230
231
  }
@@ -19,7 +19,7 @@ if (!CMD) {
19
19
  async function main() {
20
20
  try {
21
21
  switch (CMD) {
22
- case 'search':
22
+ case 'search': {
23
23
  const results = await Marketplace.search(QUERY);
24
24
  console.table(results.map(r => ({
25
25
  name: r.name,
@@ -28,12 +28,14 @@ async function main() {
28
28
  description: r.description.slice(0, 50) + '...'
29
29
  })));
30
30
  break;
31
-
31
+ }
32
+
32
33
  case 'featured':
33
- case 'trending':
34
+ case 'trending': {
34
35
  const list = await Marketplace.getFeatured();
35
36
  console.table(list);
36
37
  break;
38
+ }
37
39
 
38
40
  case 'install':
39
41
  if (!QUERY) throw new Error('Package name required for install');
@@ -82,11 +82,10 @@ function register(params) {
82
82
  );
83
83
  }
84
84
 
85
- // Write AUDIT entry
85
+ // Write AUDIT entry via the unified, hash-chained, durable append (UC-04b).
86
86
  if (fs.existsSync(path.dirname(AUDIT_PATH))) {
87
- const entry = {
88
- id: require('crypto').randomBytes(8).toString('hex'),
89
- timestamp: new Date().toISOString(),
87
+ const { appendAuditEntrySync } = require('../autonomous/audit-writer');
88
+ appendAuditEntrySync(AUDIT_PATH, {
90
89
  event: 'skill_learned',
91
90
  agent: 'mindforge-skills-builder',
92
91
  phase: null,
@@ -97,8 +96,7 @@ function register(params) {
97
96
  source_type: sourceType,
98
97
  source: String(source).slice(0, 200),
99
98
  skill_path: relativePath,
100
- };
101
- fs.appendFileSync(AUDIT_PATH, JSON.stringify(entry) + '\n');
99
+ });
102
100
  }
103
101
 
104
102
  return { registered: true, skillName, tier, qualityScore };
@@ -109,13 +109,15 @@ class Sentinel {
109
109
  }
110
110
 
111
111
  logToAudit(event, targetPath) {
112
- const logEntry = {
113
- id: crypto.randomBytes(8).toString('hex'),
114
- timestamp: new Date().toISOString(),
112
+ // UC-04b: route through the unified, hash-chained, durable append so Sentinel's
113
+ // incident entries link into the single verifiable chain (was a raw appendFileSync
114
+ // that broke the chain). appendAuditEntrySync caches the chain head per resolved
115
+ // path, so an explicit targetPath is chained correctly too.
116
+ const { appendAuditEntrySync } = require('../autonomous/audit-writer');
117
+ appendAuditEntrySync(targetPath || this.auditPath, {
115
118
  agent: 'mindforge-sentinel',
116
119
  ...event
117
- };
118
- fs.appendFileSync(targetPath || this.auditPath, JSON.stringify(logEntry) + '\n');
120
+ });
119
121
  }
120
122
 
121
123
  stop() {
@@ -0,0 +1,55 @@
1
+ 'use strict';
2
+ /**
3
+ * MindForge — Single-writer serialized append queue (UC-09).
4
+ * Guarantees: (1) appends to a given file are serialized (no interleaving across
5
+ * concurrent callers in this process), (2) each append() resolves only after the
6
+ * bytes are fsync'd to disk (durability), (3) a trailing newline delimits records.
7
+ *
8
+ * Scope: protects against in-process concurrent-write interleaving and crash-loss
9
+ * of acknowledged writes. Cross-PROCESS locking is out of scope for the
10
+ * single-operator localhost model (documented; revisit if multi-process writers appear).
11
+ */
12
+ const fs = require('fs');
13
+
14
+ // path -> Promise chain tail. NOTE: this is intended for a small, fixed set of
15
+ // known paths (e.g. AUDIT.jsonl). Per-path entries are NEVER evicted, so do NOT
16
+ // key this by high-cardinality dynamic paths — doing so would leak memory
17
+ // unboundedly. Revisit with an LRU/eviction policy if dynamic paths are needed.
18
+ const queues = new Map();
19
+
20
+ function createAppendQueue(filePath) {
21
+ if (!queues.has(filePath)) queues.set(filePath, Promise.resolve());
22
+
23
+ function append(line) {
24
+ const record = line.endsWith('\n') ? line : line + '\n';
25
+ const tail = queues.get(filePath).then(() => writeDurable(filePath, record));
26
+ queues.set(filePath, tail.catch(() => {}));
27
+ return tail;
28
+ }
29
+
30
+ function drain() {
31
+ return queues.get(filePath);
32
+ }
33
+
34
+ return Object.freeze({ append, drain });
35
+ }
36
+
37
+ function writeDurable(filePath, data) {
38
+ return new Promise((resolve, reject) => {
39
+ fs.open(filePath, 'a', (openErr, fd) => {
40
+ if (openErr) return reject(openErr);
41
+ fs.write(fd, data, (writeErr) => {
42
+ if (writeErr) { fs.close(fd, () => {}); return reject(writeErr); }
43
+ fs.fsync(fd, (syncErr) => {
44
+ fs.close(fd, (closeErr) => {
45
+ if (syncErr) return reject(syncErr);
46
+ if (closeErr) return reject(closeErr);
47
+ resolve();
48
+ });
49
+ });
50
+ });
51
+ });
52
+ });
53
+ }
54
+
55
+ module.exports = { createAppendQueue };
@@ -3,55 +3,46 @@
3
3
  const fs = require('fs');
4
4
  const fsp = require('fs/promises');
5
5
  const path = require('path');
6
- const crypto = require('crypto');
6
+ const zlib = require('zlib');
7
7
 
8
+ /**
9
+ * Hash-chained audit writer (class API preserved for nexus-tracer + policy-engine).
10
+ *
11
+ * UC-04b: this class previously maintained a SECOND, DIVERGENT chain implementation
12
+ * (it hashed {...entry, timestamp, previous_hash} — injecting timestamp into the
13
+ * material differently from the canonical writer), so entries it wrote could never
14
+ * verify against bin/governance/audit-verifier.js. It now delegates every write to
15
+ * the SINGLE shared `appendAuditEntrySync` (canonical hashAuditEntry, synchronous +
16
+ * fsync-durable), so there is ONE hasher and ONE on-disk chain per file.
17
+ *
18
+ * The async API (write/flush/close returning promises) is kept because callers do
19
+ * `await this._auditWriter.write(entry)`; the underlying append is now synchronous
20
+ * and durable, so flush()/close() are no-ops retained for API compatibility.
21
+ */
8
22
  class AuditWriter {
9
23
  constructor(filePath) {
10
24
  this._path = filePath;
11
- this._buffer = [];
12
- this._flushTimer = null;
25
+ // Retained for API compatibility; the unified append is synchronous so there
26
+ // is no longer an internal buffer or timer to manage.
13
27
  this._lastHash = null;
14
28
  }
15
29
 
16
- write(entry) {
17
- // Add Merkle chain link
18
- const entryWithHash = {
19
- ...entry,
20
- timestamp: entry.timestamp || new Date().toISOString(),
21
- previous_hash: this._lastHash
22
- };
23
- const serialized = JSON.stringify(entryWithHash);
24
- this._lastHash = crypto.createHash('sha256').update(serialized).digest('hex');
25
- entryWithHash._hash = this._lastHash;
26
-
27
- this._buffer.push(JSON.stringify(entryWithHash));
28
-
29
- if (this._buffer.length >= 10) {
30
- return this.flush();
31
- }
32
- if (!this._flushTimer) {
33
- this._flushTimer = setTimeout(() => this.flush(), 100);
34
- }
35
- return Promise.resolve();
30
+ async write(entry) {
31
+ // Lazy require to avoid a require-cycle: audit-writer.js requires this file
32
+ // (AuditRotator) at load time, so we cannot require it at module top level.
33
+ const { appendAuditEntrySync } = require('../autonomous/audit-writer');
34
+ const chained = appendAuditEntrySync(this._path, entry);
35
+ this._lastHash = chained._hash;
36
+ return chained;
36
37
  }
37
38
 
38
- async flush() {
39
- if (this._buffer.length === 0) return;
40
- clearTimeout(this._flushTimer);
41
- this._flushTimer = null;
42
-
43
- const lines = this._buffer.splice(0);
44
- const content = lines.join('\n') + '\n';
39
+ async flush() { /* no-op: appendAuditEntrySync is synchronous + fsync-durable */ }
45
40
 
46
- await fsp.mkdir(path.dirname(this._path), { recursive: true });
47
- await fsp.appendFile(this._path, content);
48
- }
49
-
50
- async close() {
51
- await this.flush();
52
- }
41
+ async close() { /* no-op: nothing buffered */ }
53
42
 
54
43
  async initLastHash() {
44
+ // The unified append seeds its own chain head from the file tail; this remains
45
+ // for callers that expect to prime _lastHash explicitly.
55
46
  try {
56
47
  const content = await fsp.readFile(this._path, 'utf8');
57
48
  const lines = content.trim().split('\n').filter(Boolean);
@@ -99,4 +90,65 @@ function readJSONSync(filePath) {
99
90
  }
100
91
  }
101
92
 
102
- module.exports = { AuditWriter, readJSON, writeJSON, readJSONL, readJSONSync };
93
+ function atomicWriteJSON(filePath, data) {
94
+ const tmpPath = filePath + '.tmp.' + process.pid;
95
+ const content = JSON.stringify(data, null, 2) + '\n';
96
+ const fd = fs.openSync(tmpPath, 'w');
97
+ fs.writeSync(fd, content);
98
+ fs.fsyncSync(fd);
99
+ fs.closeSync(fd);
100
+ fs.renameSync(tmpPath, filePath);
101
+ }
102
+
103
+ async function atomicWriteJSONAsync(filePath, data) {
104
+ const tmpPath = filePath + '.tmp.' + process.pid;
105
+ const content = JSON.stringify(data, null, 2) + '\n';
106
+ const fh = await fsp.open(tmpPath, 'w');
107
+ await fh.write(content);
108
+ await fh.sync();
109
+ await fh.close();
110
+ await fsp.rename(tmpPath, filePath);
111
+ }
112
+
113
+ class AuditRotator {
114
+ constructor(options = {}) {
115
+ this.maxLines = options.maxLines || 5000;
116
+ }
117
+
118
+ shouldRotate(filePath) {
119
+ if (!fs.existsSync(filePath)) return false;
120
+ const content = fs.readFileSync(filePath, 'utf8');
121
+ const lineCount = content.split('\n').filter(l => l.trim()).length;
122
+ return lineCount >= this.maxLines;
123
+ }
124
+
125
+ rotate(filePath, archiveDir) {
126
+ const dir = archiveDir || path.join(path.dirname(filePath), '..', 'audit-archive');
127
+ if (!fs.existsSync(dir)) {
128
+ fs.mkdirSync(dir, { recursive: true });
129
+ }
130
+
131
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
132
+ const baseName = path.basename(filePath, path.extname(filePath));
133
+ const archiveName = `${baseName}-${timestamp}.jsonl.gz`;
134
+ const archivePath = path.join(dir, archiveName);
135
+
136
+ // Crash-safe: write archive FIRST, then truncate source
137
+ const content = fs.readFileSync(filePath);
138
+ const compressed = zlib.gzipSync(content);
139
+ fs.writeFileSync(archivePath, compressed);
140
+
141
+ const lines = content.toString('utf8').split('\n').filter(l => l.trim());
142
+ const carryover = lines.slice(-100).join('\n') + '\n';
143
+ const tmpPath = filePath + '.tmp.' + process.pid;
144
+ const fd = fs.openSync(tmpPath, 'w');
145
+ fs.writeSync(fd, carryover);
146
+ fs.fsyncSync(fd);
147
+ fs.closeSync(fd);
148
+ fs.renameSync(tmpPath, filePath);
149
+
150
+ return archivePath;
151
+ }
152
+ }
153
+
154
+ module.exports = { AuditWriter, AuditRotator, readJSON, writeJSON, readJSONL, readJSONSync, atomicWriteJSON, atomicWriteJSONAsync };
@@ -1,5 +1,63 @@
1
1
  'use strict';
2
+
3
+ class LRUMap {
4
+ constructor(maxSize, options = {}) {
5
+ this._max = maxSize;
6
+ this._onEvict = options.onEvict || null;
7
+ this._map = new Map();
8
+ }
9
+
10
+ get(key) {
11
+ if (!this._map.has(key)) return undefined;
12
+ const value = this._map.get(key);
13
+ this._map.delete(key);
14
+ this._map.set(key, value);
15
+ return value;
16
+ }
17
+
18
+ set(key, value) {
19
+ if (this._map.has(key)) {
20
+ this._map.delete(key);
21
+ } else if (this._map.size >= this._max) {
22
+ const oldest = this._map.keys().next().value;
23
+ const oldestValue = this._map.get(oldest);
24
+ this._map.delete(oldest);
25
+ if (this._onEvict) this._onEvict(oldest, oldestValue);
26
+ }
27
+ this._map.set(key, value);
28
+ }
29
+
30
+ has(key) {
31
+ return this._map.has(key);
32
+ }
33
+
34
+ delete(key) {
35
+ return this._map.delete(key);
36
+ }
37
+
38
+ clear() {
39
+ this._map.clear();
40
+ }
41
+
42
+ get size() {
43
+ return this._map.size;
44
+ }
45
+
46
+ keys() {
47
+ return this._map.keys();
48
+ }
49
+
50
+ values() {
51
+ return this._map.values();
52
+ }
53
+
54
+ entries() {
55
+ return this._map.entries();
56
+ }
57
+ }
58
+
2
59
  module.exports = {
60
+ LRUMap,
3
61
  ...require('./paths'),
4
62
  ...require('./file-io'),
5
63
  ...require('./errors')
@@ -0,0 +1,59 @@
1
+ 'use strict';
2
+ /**
3
+ * MindForge version single-source-of-truth + drift detector.
4
+ * package.json is canonical; everything else must agree.
5
+ */
6
+ const path = require('path');
7
+ // Use the repo's stricter reader: returns null only on ENOENT and RE-THROWS on
8
+ // parse errors. Re-throwing on a corrupt JSON source is the fail-closed
9
+ // behavior we want — a file we cannot parse means we cannot establish truth.
10
+ const { readJSONSync } = require('./file-io');
11
+
12
+ /**
13
+ * @param {string} projectRoot
14
+ * @returns {{ canonical: string|null, sources: Record<string,string|null>, drift: string[] }}
15
+ */
16
+ function checkVersionConsistency(projectRoot) {
17
+ const pkg = readJSONSync(path.join(projectRoot, 'package.json'));
18
+ const canonical = pkg ? pkg.version : null;
19
+
20
+ const configJson = readJSONSync(path.join(projectRoot, '.mindforge', 'config.json'));
21
+ // Runtime drift coverage is intentionally limited to package.json (canonical)
22
+ // vs .mindforge/config.json — the live config is the operational drift risk
23
+ // during `auto`. Wider agreement (sdk/package.json, MINDFORGE.md [VERSION]) is
24
+ // enforced by the test suite (tests/version-consistency.test.js), not at
25
+ // runtime — do not assume this checker provides full version coverage.
26
+ const sources = {
27
+ 'package.json': canonical,
28
+ '.mindforge/config.json': configJson ? configJson.version : null,
29
+ };
30
+
31
+ const drift = [];
32
+ // Fail-closed: if we cannot establish the canonical version (package.json
33
+ // missing or its `version` field absent), treat it as a drift/error condition
34
+ // rather than silently passing. A genuinely corrupt package.json would have
35
+ // already thrown out of readJSONSync above.
36
+ if (!canonical) {
37
+ drift.push('package.json version could not be determined (canonical source missing or unparseable)');
38
+ return { canonical, sources, drift };
39
+ }
40
+
41
+ for (const [file, version] of Object.entries(sources)) {
42
+ if (version && version !== canonical) {
43
+ drift.push(`${file} declares ${version} but canonical (package.json) is ${canonical}`);
44
+ }
45
+ }
46
+ return { canonical, sources, drift };
47
+ }
48
+
49
+ /**
50
+ * Fail-closed assertion for pre-flight. Throws on drift.
51
+ */
52
+ function assertVersionConsistency(projectRoot) {
53
+ const { drift } = checkVersionConsistency(projectRoot);
54
+ if (drift.length > 0) {
55
+ throw new Error('Version drift detected (fail-closed):\n - ' + drift.join('\n - '));
56
+ }
57
+ }
58
+
59
+ module.exports = { checkVersionConsistency, assertVersionConsistency };
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const { verifyAuditChain } = require('./governance/audit-verifier');
4
+ const auditPath = process.argv[2] || '.planning/AUDIT.jsonl';
5
+ const result = verifyAuditChain(auditPath);
6
+ if (result.valid) {
7
+ process.stdout.write(`✅ audit chain valid: ${result.count} entries\n`);
8
+ process.exit(0);
9
+ } else {
10
+ process.stderr.write(`❌ audit chain BROKEN at entry ${result.brokenAt}: ${result.reason}\n`);
11
+ process.exit(1);
12
+ }
@@ -177,8 +177,7 @@ const Theme = {
177
177
  },
178
178
 
179
179
  // --- Aliases for legacy compatibility ---
180
- status(label, state) { this.printStatus(label, state); },
181
- printSuccess(runtime, scope, stats) { this.printSuccessV2(runtime, scope, stats); }
180
+ status(label, state) { this.printStatus(label, state); }
182
181
  };
183
182
 
184
183
  module.exports = Theme;
@@ -1,4 +1,4 @@
1
- # MindForge — Getting Started (v10.0.1)
1
+ # MindForge — Getting Started (v11.0.0)
2
2
 
3
3
  This guide gets you from zero to a working MindForge project in under five minutes.
4
4
 
@@ -1,4 +1,4 @@
1
- # MindForge User Guide (v10.0.1)
1
+ # MindForge User Guide (v11.0.0)
2
2
 
3
3
  This guide gets you from install to productive, with the minimum needed to run MindForge in a real project.
4
4
 
@@ -136,7 +136,7 @@ Observe your agent waves, token spend, and milestone progress in real-time.
136
136
 
137
137
  The dashboard provides a premium web interface at `http://localhost:7339`.
138
138
 
139
- > **Authentication (v10.0.1+):** The dashboard now requires a bearer token for access. The token is printed to the console at startup. Pass it in the `Authorization` header or use the login prompt in the browser UI.
139
+ > **Authentication (v11.0.0+):** The dashboard now requires a bearer token for access. The token is printed to the console at startup. Pass it in the `Authorization` header or use the login prompt in the browser UI.
140
140
 
141
141
  ---
142
142
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "mindforge-cc",
3
- "version": "10.7.0",
4
- "description": "MindForge \u2014 Sovereign Agentic Intelligence Framework. Bedrock Fortified: Production-Grade Architecture (v10)",
3
+ "version": "11.2.0",
4
+ "description": "MindForge \u2014 Sovereign Agentic Intelligence Framework. Sovereign Stability: Production-Hardened Agentic Intelligence (v11)",
5
5
  "bin": {
6
6
  "mindforge-cc": "bin/install.js",
7
7
  "mindforge": "bin/mindforge-cli.js"
File without changes