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.
- package/.agent/hooks/mindforge-statusline.js +2 -2
- package/.mindforge/MINDFORGE-V2-SCHEMA.json +43 -10
- package/.mindforge/config.json +18 -4
- package/CHANGELOG.md +165 -0
- package/MINDFORGE.md +3 -3
- package/README.md +49 -4
- package/RELEASENOTES.md +81 -1
- package/SECURITY.md +20 -8
- package/bin/autonomous/audit-writer.js +105 -70
- package/bin/autonomous/auto-runner.js +377 -34
- package/bin/autonomous/context-refactorer.js +26 -11
- package/bin/autonomous/dependency-dag.js +59 -0
- package/bin/autonomous/state-manager.js +62 -6
- package/bin/autonomous/stuck-monitor.js +46 -7
- package/bin/autonomous/wave-executor.js +86 -26
- package/bin/council-cli.js +161 -0
- package/bin/dashboard/api-router.js +43 -0
- package/bin/dashboard/approval-handler.js +3 -1
- package/bin/dashboard/metrics-aggregator.js +28 -1
- package/bin/dashboard/server.js +68 -5
- package/bin/dashboard/sse-bridge.js +10 -13
- package/bin/engine/council-runtime.js +124 -0
- package/bin/engine/feedback-loop.js +8 -0
- package/bin/engine/intelligence-interlock.js +32 -15
- package/bin/engine/logic-drift-detector.js +2 -1
- package/bin/engine/nexus-tracer.js +3 -2
- package/bin/engine/otel-exporter.js +123 -0
- package/bin/engine/remediation-engine.js +155 -32
- package/bin/engine/self-corrective-synthesizer.js +84 -10
- package/bin/engine/sre-manager.js +12 -4
- package/bin/engine/temporal-cli.js +4 -2
- package/bin/engine/temporal-hub.js +131 -34
- package/bin/engine/verification-runner.js +131 -0
- package/bin/engine/verify-cli.js +34 -0
- package/bin/eval/eval-harness.js +82 -0
- package/bin/eval/golden-set-retrieval.json +46 -0
- package/bin/governance/approve.js +41 -5
- package/bin/governance/audit-hash.js +12 -0
- package/bin/governance/audit-verifier.js +60 -0
- package/bin/governance/impact-analyzer.js +28 -0
- package/bin/governance/policy-engine.js +10 -3
- package/bin/governance/quantum-crypto.js +95 -28
- package/bin/governance/rbac-manager.js +74 -2
- package/bin/governance/ztai-manager.js +79 -9
- package/bin/hindsight-injector.js +8 -9
- package/bin/hooks/instinct-capture-hook.js +186 -0
- package/bin/memory/auto-shadow.js +32 -3
- package/bin/memory/eis-client.js +71 -34
- package/bin/memory/embedding-engine.js +61 -0
- package/bin/memory/identity-synthesizer.js +2 -2
- package/bin/memory/knowledge-graph.js +58 -5
- package/bin/memory/knowledge-indexer.js +53 -6
- package/bin/memory/knowledge-store.js +52 -6
- package/bin/memory/retrieval-fusion.js +58 -0
- package/bin/memory/semantic-hub.js +2 -2
- package/bin/memory/vector-hub.js +111 -6
- package/bin/migrations/10.7.0-to-11.0.0.js +110 -0
- package/bin/migrations/schema-versions.js +13 -0
- package/bin/mindforge-cli.js +4 -5
- package/bin/models/anthropic-provider.js +58 -4
- package/bin/models/cloud-broker.js +68 -20
- package/bin/models/cost-tracker.js +3 -1
- package/bin/models/difficulty-scorer.js +54 -0
- package/bin/models/gemini-provider.js +57 -2
- package/bin/models/model-client.js +20 -0
- package/bin/models/model-router.js +59 -26
- package/bin/models/openai-provider.js +50 -3
- package/bin/models/pricing-registry.js +128 -0
- package/bin/review/ads-engine.js +1 -1
- package/bin/security/trust-boundaries.js +102 -0
- package/bin/security/trust-gate-hook.js +39 -0
- package/bin/skill-registry.js +3 -2
- package/bin/skills-builder/marketplace-cli.js +5 -3
- package/bin/skills-builder/skill-registrar.js +4 -6
- package/bin/sre/sentinel.js +7 -5
- package/bin/utils/append-queue.js +55 -0
- package/bin/utils/file-io.js +90 -38
- package/bin/utils/index.js +58 -0
- package/bin/utils/version-check.js +59 -0
- package/bin/verify-audit.js +12 -0
- package/bin/wizard/theme.js +1 -2
- package/docs/getting-started.md +1 -1
- package/docs/user-guide.md +2 -2
- package/package.json +2 -2
- 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
|
+
});
|
package/bin/skill-registry.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
88
|
-
|
|
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 };
|
package/bin/sre/sentinel.js
CHANGED
|
@@ -109,13 +109,15 @@ class Sentinel {
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
logToAudit(event, targetPath) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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 };
|
package/bin/utils/file-io.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
12
|
-
|
|
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
|
-
//
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 };
|
package/bin/utils/index.js
CHANGED
|
@@ -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
|
+
}
|
package/bin/wizard/theme.js
CHANGED
|
@@ -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;
|
package/docs/getting-started.md
CHANGED
package/docs/user-guide.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# MindForge User Guide (
|
|
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 (
|
|
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": "
|
|
4
|
-
"description": "MindForge \u2014 Sovereign Agentic Intelligence Framework.
|
|
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
|