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
|
@@ -15,6 +15,30 @@ const path = require('path');
|
|
|
15
15
|
const os = require('os');
|
|
16
16
|
const crypto = require('crypto');
|
|
17
17
|
|
|
18
|
+
// ── Durable append (UC-09) ──────────────────────────────────────────────────
|
|
19
|
+
// The knowledge-store public API (add/deprecate/reinforce) is SYNCHRONOUS and
|
|
20
|
+
// callers read-after-write synchronously (e.g. `const id = Store.add(...)`
|
|
21
|
+
// immediately followed by `Store.readAll()`). Routing through the async
|
|
22
|
+
// append-queue would make those reads observe stale data and would require an
|
|
23
|
+
// API change across 9+ consumers — out of scope for UC-09.
|
|
24
|
+
//
|
|
25
|
+
// Instead we centralize every append through one durable, fsync'd, synchronous
|
|
26
|
+
// writer. This delivers UC-09's durability guarantee (acknowledged writes are on
|
|
27
|
+
// disk before the call returns) and a single serialized append path per file,
|
|
28
|
+
// while preserving the synchronous read-after-write contract. appendFileSync's
|
|
29
|
+
// per-call append is atomic on POSIX, so concurrent in-process appends do not
|
|
30
|
+
// interleave at the byte level.
|
|
31
|
+
function appendDurableSync(filePath, line) {
|
|
32
|
+
const record = line.endsWith('\n') ? line : line + '\n';
|
|
33
|
+
const fd = fs.openSync(filePath, 'a');
|
|
34
|
+
try {
|
|
35
|
+
fs.writeSync(fd, record);
|
|
36
|
+
fs.fsyncSync(fd);
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(fd);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
18
42
|
// ── ID Index for fast lookups (built lazily, invalidated on writes) ───────────
|
|
19
43
|
let _idIndex = null; // Map<id, entry> — latest version per ID
|
|
20
44
|
let _indexDirty = true; // Invalidated whenever entries are appended
|
|
@@ -126,6 +150,22 @@ function getFilePath(type) {
|
|
|
126
150
|
}
|
|
127
151
|
}
|
|
128
152
|
|
|
153
|
+
// ── File Integrity ────────────────────────────────────────────────────────────
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Ensures a JSONL file doesn't end with a partial/truncated line.
|
|
157
|
+
* Appends a trailing newline if missing — prevents corruption from propagating.
|
|
158
|
+
*/
|
|
159
|
+
function verifyFileIntegrity(filePath) {
|
|
160
|
+
if (!fs.existsSync(filePath)) return true;
|
|
161
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
162
|
+
if (content.length === 0) return true;
|
|
163
|
+
if (!content.endsWith('\n')) {
|
|
164
|
+
fs.appendFileSync(filePath, '\n');
|
|
165
|
+
}
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
|
|
129
169
|
// ── Write operations ──────────────────────────────────────────────────────────
|
|
130
170
|
|
|
131
171
|
/**
|
|
@@ -184,11 +224,13 @@ function add(entry) {
|
|
|
184
224
|
};
|
|
185
225
|
|
|
186
226
|
const filePath = getFilePath(entry.type);
|
|
187
|
-
|
|
227
|
+
verifyFileIntegrity(filePath);
|
|
228
|
+
appendDurableSync(filePath, JSON.stringify(full));
|
|
188
229
|
|
|
189
230
|
// Also append to unified knowledge-base.jsonl for cross-type queries
|
|
190
231
|
if (filePath !== paths.KB_PATH) {
|
|
191
|
-
|
|
232
|
+
verifyFileIntegrity(paths.KB_PATH);
|
|
233
|
+
appendDurableSync(paths.KB_PATH, JSON.stringify(full));
|
|
192
234
|
}
|
|
193
235
|
|
|
194
236
|
_invalidateIndex();
|
|
@@ -217,9 +259,11 @@ function deprecate(id, reason, supersededBy = null) {
|
|
|
217
259
|
deprecated_at: new Date().toISOString(),
|
|
218
260
|
};
|
|
219
261
|
|
|
220
|
-
|
|
262
|
+
verifyFileIntegrity(filePath);
|
|
263
|
+
appendDurableSync(filePath, JSON.stringify(deprecated));
|
|
221
264
|
if (filePath !== paths.KB_PATH) {
|
|
222
|
-
|
|
265
|
+
verifyFileIntegrity(paths.KB_PATH);
|
|
266
|
+
appendDurableSync(paths.KB_PATH, JSON.stringify(deprecated));
|
|
223
267
|
}
|
|
224
268
|
|
|
225
269
|
_invalidateIndex();
|
|
@@ -246,9 +290,11 @@ function reinforce(id) {
|
|
|
246
290
|
};
|
|
247
291
|
|
|
248
292
|
const filePath = getFilePath(entry.type);
|
|
249
|
-
|
|
293
|
+
verifyFileIntegrity(filePath);
|
|
294
|
+
appendDurableSync(filePath, JSON.stringify(reinforced));
|
|
250
295
|
if (filePath !== paths.KB_PATH) {
|
|
251
|
-
|
|
296
|
+
verifyFileIntegrity(paths.KB_PATH);
|
|
297
|
+
appendDurableSync(paths.KB_PATH, JSON.stringify(reinforced));
|
|
252
298
|
}
|
|
253
299
|
|
|
254
300
|
_invalidateIndex();
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* MindForge — Reciprocal Rank Fusion (UC-20).
|
|
4
|
+
* Merges multiple ranked lists using scale-free RRF scoring.
|
|
5
|
+
*
|
|
6
|
+
* RRF eliminates the need for score normalization across retrieval paths
|
|
7
|
+
* with incomparable scoring functions (embedding similarity, BM25, graph
|
|
8
|
+
* traversal, FTS rank). Only ordinal rank matters, not score magnitude.
|
|
9
|
+
*
|
|
10
|
+
* Formula:
|
|
11
|
+
* rrfScore(item) = SUM( 1 / (K + rank_i) ) for all lists containing the item
|
|
12
|
+
*
|
|
13
|
+
* Where K=60 is the standard constant from the original RRF paper
|
|
14
|
+
* (Cormack, Clarke, Butt — 2009).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const K = 60; // Standard RRF constant — dampens the influence of high ranks
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Fuse multiple ranked result lists using Reciprocal Rank Fusion.
|
|
21
|
+
*
|
|
22
|
+
* Each list is an array of objects with at least an `id` field.
|
|
23
|
+
* Items appearing in multiple lists accumulate RRF score and rank higher.
|
|
24
|
+
*
|
|
25
|
+
* @param {Array<Array<{id: string, [key: string]: any}>>} rankedLists
|
|
26
|
+
* Array of ranked lists. Each list is ordered by relevance (index 0 = most relevant).
|
|
27
|
+
* @returns {Array<{id: string, rrfScore: number, [key: string]: any}>}
|
|
28
|
+
* Fused results sorted by RRF score descending. Each item retains its
|
|
29
|
+
* original properties from the first list it appeared in.
|
|
30
|
+
*/
|
|
31
|
+
function fuseResults(rankedLists) {
|
|
32
|
+
if (!rankedLists || rankedLists.length === 0) return [];
|
|
33
|
+
|
|
34
|
+
const scores = new Map(); // id -> merged item with rrfScore
|
|
35
|
+
|
|
36
|
+
for (const list of rankedLists) {
|
|
37
|
+
if (!Array.isArray(list)) continue;
|
|
38
|
+
|
|
39
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
40
|
+
const item = list[rank];
|
|
41
|
+
if (!item || !item.id) continue;
|
|
42
|
+
|
|
43
|
+
const id = item.id;
|
|
44
|
+
const rrfContribution = 1 / (K + rank + 1); // rank is 0-based, +1 makes it 1-based
|
|
45
|
+
|
|
46
|
+
if (scores.has(id)) {
|
|
47
|
+
const existing = scores.get(id);
|
|
48
|
+
existing.rrfScore += rrfContribution;
|
|
49
|
+
} else {
|
|
50
|
+
scores.set(id, { ...item, rrfScore: rrfContribution });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return [...scores.values()].sort((a, b) => b.rrfScore - a.rrfScore);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
module.exports = { fuseResults, K };
|
|
@@ -83,7 +83,7 @@ class SemanticHub {
|
|
|
83
83
|
try {
|
|
84
84
|
const data = await fs.readFile(this.syncManifest, 'utf8');
|
|
85
85
|
manifest = JSON.parse(data);
|
|
86
|
-
} catch (e) {}
|
|
86
|
+
} catch (e) { /* intentionally empty */ }
|
|
87
87
|
|
|
88
88
|
manifest[libraryName] = {
|
|
89
89
|
lastSync: new Date().toISOString(),
|
|
@@ -106,7 +106,7 @@ class SemanticHub {
|
|
|
106
106
|
sqliteTraces = await vectorHub.searchTraces(skillFilter);
|
|
107
107
|
} else {
|
|
108
108
|
sqliteTraces = vectorHub.query(
|
|
109
|
-
|
|
109
|
+
'SELECT * FROM traces WHERE event = ? LIMIT 20',
|
|
110
110
|
['reasoning_trace']
|
|
111
111
|
);
|
|
112
112
|
}
|
package/bin/memory/vector-hub.js
CHANGED
|
@@ -23,6 +23,30 @@ class VectorHub {
|
|
|
23
23
|
this.initialized = false;
|
|
24
24
|
this._writeCount = 0;
|
|
25
25
|
this._batchSize = 10;
|
|
26
|
+
// UC-09: serialized async persistence chain. Successive save() calls queue
|
|
27
|
+
// behind one another so two exports never write the .db file concurrently
|
|
28
|
+
// (a corrupted half-written database would otherwise be possible).
|
|
29
|
+
this._saveChain = Promise.resolve();
|
|
30
|
+
// Count of async save()s that have been SCHEDULED but not yet COMPLETED their
|
|
31
|
+
// durable disk write. A boolean here is unsafe: with two rapid saves the chain
|
|
32
|
+
// is [writeA → clear → writeB → clear], leaving a window where the flag reads
|
|
33
|
+
// "clean" while writeB is still pending — a hard process.exit() in that window
|
|
34
|
+
// would make the exit guard skip saveSync() and lose the last batch (the exact
|
|
35
|
+
// data loss this guard exists to prevent). A counter has no such gap: it only
|
|
36
|
+
// returns to 0 once EVERY scheduled save has completed. saveSync() always
|
|
37
|
+
// exports the current in-memory DB, so over-flushing on exit is harmless — we
|
|
38
|
+
// deliberately bias toward flushing.
|
|
39
|
+
this._pendingSaves = 0;
|
|
40
|
+
this._exitGuardInstalled = false;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
_installExitGuard() {
|
|
44
|
+
if (this._exitGuardInstalled) return;
|
|
45
|
+
this._exitGuardInstalled = true;
|
|
46
|
+
// 'exit' handlers can only run synchronous code — saveSync() fits exactly.
|
|
47
|
+
process.once('exit', () => {
|
|
48
|
+
if (this._db && this._pendingSaves > 0) this.saveSync();
|
|
49
|
+
});
|
|
26
50
|
}
|
|
27
51
|
|
|
28
52
|
_ensureDir() {
|
|
@@ -167,22 +191,74 @@ class VectorHub {
|
|
|
167
191
|
this._db.run('CREATE UNIQUE INDEX IF NOT EXISTS idx_migrations_name ON _migrations(name)');
|
|
168
192
|
|
|
169
193
|
this.initialized = true;
|
|
194
|
+
this._installExitGuard();
|
|
170
195
|
this.save();
|
|
171
196
|
console.log(`[VectorHub] Initialized WASM SQLite persistence at ${this.dbPath}`);
|
|
172
197
|
}
|
|
173
198
|
|
|
174
199
|
/**
|
|
175
|
-
* Persist the in-memory database to disk.
|
|
200
|
+
* Persist the in-memory database to disk (UC-09).
|
|
201
|
+
*
|
|
202
|
+
* sql.js export() is intrinsically synchronous, but the (potentially large)
|
|
203
|
+
* FILE WRITE no longer blocks the event loop: we snapshot the bytes
|
|
204
|
+
* synchronously, then write+fsync them asynchronously. Successive saves are
|
|
205
|
+
* serialized on a single chain so two exports never write the .db file
|
|
206
|
+
* concurrently. The write is crash-safe (tmp file + atomic rename + fsync),
|
|
207
|
+
* so a partial write can never leave a corrupted database on disk.
|
|
208
|
+
*
|
|
209
|
+
* @returns {Promise<void>} Resolves once the snapshot is durably on disk.
|
|
176
210
|
*/
|
|
177
211
|
save() {
|
|
212
|
+
if (!this._db) return Promise.resolve();
|
|
213
|
+
|
|
214
|
+
let buffer;
|
|
215
|
+
try {
|
|
216
|
+
this._ensureDir();
|
|
217
|
+
// Snapshot the DB synchronously so the bytes reflect this exact moment.
|
|
218
|
+
buffer = Buffer.from(this._db.export());
|
|
219
|
+
} catch (err) {
|
|
220
|
+
console.warn(`[VectorHub] Failed to export database: ${err.message}`);
|
|
221
|
+
return Promise.resolve();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const dbPath = this.dbPath;
|
|
225
|
+
// Increment when SCHEDULED; decrement only once this specific save has
|
|
226
|
+
// COMPLETED (success or failure). The exit guard fires saveSync() while any
|
|
227
|
+
// scheduled save is still outstanding — see _installExitGuard().
|
|
228
|
+
this._pendingSaves++;
|
|
229
|
+
this._saveChain = this._saveChain.then(() => writeDbDurable(dbPath, buffer))
|
|
230
|
+
.catch((err) => {
|
|
231
|
+
console.warn(`[VectorHub] Failed to save database: ${err.message}`);
|
|
232
|
+
})
|
|
233
|
+
.then(() => { this._pendingSaves--; });
|
|
234
|
+
return this._saveChain;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Synchronous, crash-safe persistence — used only on shutdown to GUARANTEE
|
|
239
|
+
* no acknowledged write is lost if the process exits before the async save
|
|
240
|
+
* chain drains. Correctness over non-blocking here.
|
|
241
|
+
*/
|
|
242
|
+
saveSync() {
|
|
178
243
|
if (!this._db) return;
|
|
179
244
|
try {
|
|
180
245
|
this._ensureDir();
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
fs.
|
|
246
|
+
const buffer = Buffer.from(this._db.export());
|
|
247
|
+
const tmpPath = `${this.dbPath}.tmp.${process.pid}`;
|
|
248
|
+
const fd = fs.openSync(tmpPath, 'w');
|
|
249
|
+
try {
|
|
250
|
+
fs.writeSync(fd, buffer);
|
|
251
|
+
fs.fsyncSync(fd);
|
|
252
|
+
} finally {
|
|
253
|
+
fs.closeSync(fd);
|
|
254
|
+
}
|
|
255
|
+
fs.renameSync(tmpPath, this.dbPath);
|
|
256
|
+
// A sync export captures the full in-memory DB — a superset of anything the
|
|
257
|
+
// outstanding async saves would have written — so the pending work is now
|
|
258
|
+
// durably satisfied. Clearing the counter prevents a redundant second flush.
|
|
259
|
+
this._pendingSaves = 0;
|
|
184
260
|
} catch (err) {
|
|
185
|
-
console.warn(`[VectorHub] Failed to save database: ${err.message}`);
|
|
261
|
+
console.warn(`[VectorHub] Failed to save database (sync): ${err.message}`);
|
|
186
262
|
}
|
|
187
263
|
}
|
|
188
264
|
|
|
@@ -199,10 +275,13 @@ class VectorHub {
|
|
|
199
275
|
|
|
200
276
|
/**
|
|
201
277
|
* Close the database and save final state to disk.
|
|
278
|
+
* Drains any pending async saves, then performs a guaranteed synchronous
|
|
279
|
+
* durable write so no acknowledged data is lost on shutdown (UC-09).
|
|
202
280
|
*/
|
|
203
281
|
async close() {
|
|
204
282
|
if (this._db) {
|
|
205
|
-
this.save()
|
|
283
|
+
try { await this._saveChain; } catch { /* logged in save() */ }
|
|
284
|
+
this.saveSync();
|
|
206
285
|
this._db.close();
|
|
207
286
|
this._db = null;
|
|
208
287
|
this.initialized = false;
|
|
@@ -455,6 +534,32 @@ class VectorHub {
|
|
|
455
534
|
}
|
|
456
535
|
}
|
|
457
536
|
|
|
537
|
+
// ── Durable async DB file write (UC-09) ───────────────────────────────────────
|
|
538
|
+
// Crash-safe: write to a tmp file, fsync, then atomically rename over the target.
|
|
539
|
+
// A crash mid-write leaves the previous good .db intact (rename is atomic on POSIX).
|
|
540
|
+
function writeDbDurable(dbPath, buffer) {
|
|
541
|
+
return new Promise((resolve, reject) => {
|
|
542
|
+
const tmpPath = `${dbPath}.tmp.${process.pid}`;
|
|
543
|
+
const fail = (err) => { fs.unlink(tmpPath, () => reject(err)); };
|
|
544
|
+
fs.open(tmpPath, 'w', (openErr, fd) => {
|
|
545
|
+
if (openErr) return reject(openErr);
|
|
546
|
+
fs.write(fd, buffer, 0, buffer.length, 0, (writeErr) => {
|
|
547
|
+
if (writeErr) { fs.close(fd, () => fail(writeErr)); return; }
|
|
548
|
+
fs.fsync(fd, (syncErr) => {
|
|
549
|
+
fs.close(fd, (closeErr) => {
|
|
550
|
+
if (syncErr) return fail(syncErr);
|
|
551
|
+
if (closeErr) return fail(closeErr);
|
|
552
|
+
fs.rename(tmpPath, dbPath, (renameErr) => {
|
|
553
|
+
if (renameErr) return fail(renameErr);
|
|
554
|
+
resolve();
|
|
555
|
+
});
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
});
|
|
559
|
+
});
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
|
|
458
563
|
// ── Factory Function ──────────────────────────────────────────────────────────
|
|
459
564
|
|
|
460
565
|
/**
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
const MIGRATION_ID = '10.7.0-to-11.0.0';
|
|
7
|
+
const TARGET_VERSION = '11.0.0';
|
|
8
|
+
|
|
9
|
+
async function migrate(projectRoot) {
|
|
10
|
+
const results = { steps: [], success: true };
|
|
11
|
+
|
|
12
|
+
// Step 1: Backup config.json
|
|
13
|
+
const configPath = path.join(projectRoot, '.mindforge', 'config.json');
|
|
14
|
+
if (fs.existsSync(configPath)) {
|
|
15
|
+
const backupPath = configPath + '.v10-backup';
|
|
16
|
+
fs.copyFileSync(configPath, backupPath);
|
|
17
|
+
results.steps.push({ step: 'backup_config', status: 'done', path: backupPath });
|
|
18
|
+
|
|
19
|
+
// Step 2: Add new config sections
|
|
20
|
+
try {
|
|
21
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
22
|
+
|
|
23
|
+
if (!config.temporal) {
|
|
24
|
+
config.temporal = { max_snapshots: 50, max_age_days: 7 };
|
|
25
|
+
}
|
|
26
|
+
if (!config.rate_limiting) {
|
|
27
|
+
config.rate_limiting = { dashboard_rpm: 100, model_rpm: {} };
|
|
28
|
+
}
|
|
29
|
+
if (!config.session) {
|
|
30
|
+
config.session = { token_expiry_hours: 24 };
|
|
31
|
+
}
|
|
32
|
+
if (!config.wave_execution) {
|
|
33
|
+
config.wave_execution = { max_concurrency: 3 };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
config.version = TARGET_VERSION;
|
|
37
|
+
|
|
38
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
|
|
39
|
+
results.steps.push({ step: 'update_config', status: 'done' });
|
|
40
|
+
} catch (e) {
|
|
41
|
+
results.steps.push({ step: 'update_config', status: 'warning', error: e.message });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Step 3: Archive old AUDIT lines if > 5000
|
|
46
|
+
const auditPath = path.join(projectRoot, '.planning', 'AUDIT.jsonl');
|
|
47
|
+
if (fs.existsSync(auditPath)) {
|
|
48
|
+
try {
|
|
49
|
+
const content = fs.readFileSync(auditPath, 'utf8');
|
|
50
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
51
|
+
if (lines.length > 5000) {
|
|
52
|
+
const archiveDir = path.join(projectRoot, '.planning', 'audit-archive');
|
|
53
|
+
if (!fs.existsSync(archiveDir)) fs.mkdirSync(archiveDir, { recursive: true });
|
|
54
|
+
|
|
55
|
+
const zlib = require('zlib');
|
|
56
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
57
|
+
const archivePath = path.join(archiveDir, `AUDIT-pre-v11-${timestamp}.jsonl.gz`);
|
|
58
|
+
const toArchive = lines.slice(0, -500).join('\n') + '\n';
|
|
59
|
+
fs.writeFileSync(archivePath, zlib.gzipSync(toArchive));
|
|
60
|
+
|
|
61
|
+
const remaining = lines.slice(-500).join('\n') + '\n';
|
|
62
|
+
fs.writeFileSync(auditPath, remaining);
|
|
63
|
+
results.steps.push({ step: 'archive_audit', status: 'done', archived: lines.length - 500 });
|
|
64
|
+
} else {
|
|
65
|
+
results.steps.push({ step: 'archive_audit', status: 'skipped', reason: 'under_threshold' });
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {
|
|
68
|
+
results.steps.push({ step: 'archive_audit', status: 'warning', error: e.message });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Step 4: GC old snapshots
|
|
73
|
+
try {
|
|
74
|
+
const TemporalHub = require('../engine/temporal-hub');
|
|
75
|
+
const gcResult = await TemporalHub.gc({ maxSnapshots: 50, maxAgeDays: 30 });
|
|
76
|
+
results.steps.push({ step: 'snapshot_gc', status: 'done', deleted: gcResult.deleted });
|
|
77
|
+
} catch (e) {
|
|
78
|
+
results.steps.push({ step: 'snapshot_gc', status: 'warning', error: e.message });
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Step 5: Bump schema_version in HANDOFF.json
|
|
82
|
+
const handoffPath = path.join(projectRoot, '.planning', 'HANDOFF.json');
|
|
83
|
+
if (fs.existsSync(handoffPath)) {
|
|
84
|
+
try {
|
|
85
|
+
const handoff = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
|
|
86
|
+
handoff.schema_version = TARGET_VERSION;
|
|
87
|
+
fs.writeFileSync(handoffPath, JSON.stringify(handoff, null, 2) + '\n');
|
|
88
|
+
results.steps.push({ step: 'bump_handoff_version', status: 'done' });
|
|
89
|
+
} catch (e) {
|
|
90
|
+
results.steps.push({ step: 'bump_handoff_version', status: 'warning', error: e.message });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Step 6: Update MINDFORGE.md VERSION
|
|
95
|
+
const mindforgeFile = path.join(projectRoot, 'MINDFORGE.md');
|
|
96
|
+
if (fs.existsSync(mindforgeFile)) {
|
|
97
|
+
try {
|
|
98
|
+
let content = fs.readFileSync(mindforgeFile, 'utf8');
|
|
99
|
+
content = content.replace(/VERSION\s*=\s*[\d.]+/, `VERSION = ${TARGET_VERSION}`);
|
|
100
|
+
fs.writeFileSync(mindforgeFile, content);
|
|
101
|
+
results.steps.push({ step: 'bump_mindforge_version', status: 'done' });
|
|
102
|
+
} catch (e) {
|
|
103
|
+
results.steps.push({ step: 'bump_mindforge_version', status: 'warning', error: e.message });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return results;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
module.exports = { MIGRATION_ID, TARGET_VERSION, migrate };
|
|
@@ -71,6 +71,19 @@ const SCHEMA_HISTORY = [
|
|
|
71
71
|
'Plugin API version upgraded to 2.0.0',
|
|
72
72
|
],
|
|
73
73
|
},
|
|
74
|
+
{
|
|
75
|
+
version: '11.0.0',
|
|
76
|
+
date: '2026-05-28',
|
|
77
|
+
description: 'v11.0.0 - Persona Expansion: temporal config, rate limiting, wave execution, audit archival',
|
|
78
|
+
handoff_fields_added: [],
|
|
79
|
+
handoff_fields_removed: [],
|
|
80
|
+
audit_fields_added: [],
|
|
81
|
+
breaking: [
|
|
82
|
+
'config.json gains temporal, rate_limiting, session, wave_execution sections',
|
|
83
|
+
'AUDIT.jsonl auto-archived if exceeding 5000 lines',
|
|
84
|
+
'MINDFORGE.md VERSION format drops suffix (was X.Y.Z-SUFFIX, now X.Y.Z)',
|
|
85
|
+
],
|
|
86
|
+
},
|
|
74
87
|
];
|
|
75
88
|
|
|
76
89
|
module.exports = { SCHEMA_HISTORY };
|
package/bin/mindforge-cli.js
CHANGED
|
@@ -115,11 +115,6 @@ const COMMANDS = {
|
|
|
115
115
|
script: 'bin/autonomous/mesh-self-healer.js',
|
|
116
116
|
description: 'Auto-detect and repair reasoning drifts in the active swarm'
|
|
117
117
|
},
|
|
118
|
-
'quantum-verify': {
|
|
119
|
-
script: 'bin/governance/quantum-crypto.js',
|
|
120
|
-
description: 'Verify framework integrity using post-quantum signatures',
|
|
121
|
-
defaultArgs: ['--verify', '.mindforge/engine/']
|
|
122
|
-
},
|
|
123
118
|
// Planned: jira-sync, confluence-sync (not yet implemented)
|
|
124
119
|
'metrics': {
|
|
125
120
|
script: 'bin/dashboard/metrics-aggregator.js',
|
|
@@ -138,6 +133,10 @@ const COMMANDS = {
|
|
|
138
133
|
script: 'bin/engine/learning-manager.js',
|
|
139
134
|
description: 'Append a new Learning Entry to the Evolution Log',
|
|
140
135
|
defaultArgs: ['record']
|
|
136
|
+
},
|
|
137
|
+
'verify': {
|
|
138
|
+
script: 'bin/engine/verify-cli.js',
|
|
139
|
+
description: 'Run unified verification (tests, lint, audit, typecheck) and write report'
|
|
141
140
|
}
|
|
142
141
|
};
|
|
143
142
|
|
|
@@ -15,7 +15,7 @@ class AnthropicProvider {
|
|
|
15
15
|
|
|
16
16
|
const data = JSON.stringify({
|
|
17
17
|
model,
|
|
18
|
-
system: systemPrompt,
|
|
18
|
+
system: [{ type: 'text', text: systemPrompt, cache_control: { type: 'ephemeral' } }],
|
|
19
19
|
messages: [{ role: 'user', content: userMessage }],
|
|
20
20
|
max_tokens: maxTokens,
|
|
21
21
|
temperature,
|
|
@@ -45,15 +45,24 @@ class AnthropicProvider {
|
|
|
45
45
|
|
|
46
46
|
const inputTokens = json.usage.input_tokens;
|
|
47
47
|
const outputTokens = json.usage.output_tokens;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
48
|
+
const cacheRead = json.usage.cache_read_input_tokens || 0;
|
|
49
|
+
const cacheCreate = json.usage.cache_creation_input_tokens || 0;
|
|
50
|
+
|
|
51
|
+
const { priceCall } = require('./pricing-registry');
|
|
52
|
+
const cost = priceCall(json.model, {
|
|
53
|
+
input_tokens: inputTokens,
|
|
54
|
+
output_tokens: outputTokens,
|
|
55
|
+
cache_read_input_tokens: cacheRead,
|
|
56
|
+
cache_creation_input_tokens: cacheCreate,
|
|
57
|
+
});
|
|
51
58
|
|
|
52
59
|
resolve({
|
|
53
60
|
model: json.model,
|
|
54
61
|
content: json.content[0].text,
|
|
55
62
|
input_tokens: inputTokens,
|
|
56
63
|
output_tokens: outputTokens,
|
|
64
|
+
cache_read_input_tokens: cacheRead,
|
|
65
|
+
cache_creation_input_tokens: cacheCreate,
|
|
57
66
|
cost_usd: cost,
|
|
58
67
|
provider: 'anthropic'
|
|
59
68
|
});
|
|
@@ -72,6 +81,51 @@ class AnthropicProvider {
|
|
|
72
81
|
req.end();
|
|
73
82
|
});
|
|
74
83
|
}
|
|
84
|
+
|
|
85
|
+
async streamComplete(messages, options = {}) {
|
|
86
|
+
const model = options.model || 'claude-sonnet-4-6';
|
|
87
|
+
const maxTokens = options.maxTokens || 4096;
|
|
88
|
+
|
|
89
|
+
const data = JSON.stringify({
|
|
90
|
+
model,
|
|
91
|
+
messages,
|
|
92
|
+
max_tokens: maxTokens,
|
|
93
|
+
stream: true,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return new Promise((resolve, reject) => {
|
|
97
|
+
const req = https.request({
|
|
98
|
+
hostname: 'api.anthropic.com',
|
|
99
|
+
path: '/v1/messages',
|
|
100
|
+
method: 'POST',
|
|
101
|
+
headers: {
|
|
102
|
+
'Content-Type': 'application/json',
|
|
103
|
+
'x-api-key': this.apiKey,
|
|
104
|
+
'anthropic-version': '2023-06-01',
|
|
105
|
+
'Content-Length': Buffer.byteLength(data),
|
|
106
|
+
},
|
|
107
|
+
timeout: 300_000,
|
|
108
|
+
}, res => {
|
|
109
|
+
if (res.statusCode !== 200) {
|
|
110
|
+
let body = '';
|
|
111
|
+
res.on('data', chunk => body += chunk);
|
|
112
|
+
res.on('end', () => {
|
|
113
|
+
reject(new Error(`Anthropic streaming failed: ${res.statusCode}`));
|
|
114
|
+
});
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
resolve(res);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
req.on('error', reject);
|
|
121
|
+
req.on('timeout', () => {
|
|
122
|
+
req.destroy();
|
|
123
|
+
reject(new Error('Anthropic stream timeout'));
|
|
124
|
+
});
|
|
125
|
+
req.write(data);
|
|
126
|
+
req.end();
|
|
127
|
+
});
|
|
128
|
+
}
|
|
75
129
|
}
|
|
76
130
|
|
|
77
131
|
module.exports = AnthropicProvider;
|