mindforge-cc 11.0.0 → 11.2.1

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 (70) hide show
  1. package/.agent/hooks/mindforge-statusline.js +2 -2
  2. package/.mindforge/config.json +14 -4
  3. package/CHANGELOG.md +137 -0
  4. package/MINDFORGE.md +5 -5
  5. package/RELEASENOTES.md +1 -1
  6. package/bin/autonomous/audit-writer.js +108 -86
  7. package/bin/autonomous/auto-runner.js +304 -19
  8. package/bin/autonomous/dependency-dag.js +59 -0
  9. package/bin/autonomous/mesh-self-healer.js +101 -28
  10. package/bin/autonomous/wave-executor.js +20 -1
  11. package/bin/browser/regression-writer.js +45 -3
  12. package/bin/browser/session-manager.js +21 -17
  13. package/bin/council-cli.js +161 -0
  14. package/bin/dashboard/approval-handler.js +3 -1
  15. package/bin/dashboard/server.js +1 -1
  16. package/bin/dashboard/sse-bridge.js +9 -12
  17. package/bin/engine/council-runtime.js +124 -0
  18. package/bin/engine/logic-drift-detector.js +14 -6
  19. package/bin/engine/logic-validator.js +155 -25
  20. package/bin/engine/orbital-guardian.js +56 -10
  21. package/bin/engine/otel-exporter.js +123 -0
  22. package/bin/engine/reason-source-aligner.js +19 -6
  23. package/bin/engine/remediation-engine.js +1 -1
  24. package/bin/engine/self-corrective-synthesizer.js +1 -1
  25. package/bin/engine/sre-manager.js +33 -6
  26. package/bin/engine/temporal-cli.js +4 -2
  27. package/bin/engine/verification-runner.js +131 -0
  28. package/bin/engine/verify-cli.js +34 -0
  29. package/bin/eval/eval-harness.js +82 -0
  30. package/bin/eval/golden-set-retrieval.json +46 -0
  31. package/bin/governance/audit-hash.js +12 -0
  32. package/bin/governance/audit-verifier.js +60 -0
  33. package/bin/governance/policy-engine.js +17 -4
  34. package/bin/governance/quantum-crypto.js +63 -9
  35. package/bin/governance/ztai-archiver.js +74 -9
  36. package/bin/governance/ztai-manager.js +33 -5
  37. package/bin/hindsight-injector.js +5 -6
  38. package/bin/hooks/instinct-capture-hook.js +186 -0
  39. package/bin/installer-core.js +31 -2
  40. package/bin/memory/auto-shadow.js +32 -3
  41. package/bin/memory/eis-client.js +45 -4
  42. package/bin/memory/identity-synthesizer.js +2 -2
  43. package/bin/memory/knowledge-store.js +30 -6
  44. package/bin/memory/retrieval-fusion.js +58 -0
  45. package/bin/memory/semantic-hub.js +2 -2
  46. package/bin/memory/vector-hub.js +143 -6
  47. package/bin/mindforge-cli.js +4 -5
  48. package/bin/models/anthropic-provider.js +13 -4
  49. package/bin/models/cost-tracker.js +3 -1
  50. package/bin/models/difficulty-scorer.js +54 -0
  51. package/bin/models/gemini-provider.js +6 -2
  52. package/bin/models/model-router.js +31 -18
  53. package/bin/models/openai-provider.js +6 -3
  54. package/bin/models/pricing-registry.js +128 -0
  55. package/bin/review/ads-engine.js +1 -1
  56. package/bin/review/finding-synthesizer.js +35 -6
  57. package/bin/security/trust-boundaries.js +194 -0
  58. package/bin/security/trust-gate-hook.js +49 -0
  59. package/bin/skill-registry.js +34 -22
  60. package/bin/skills-builder/marketplace-cli.js +5 -3
  61. package/bin/skills-builder/skill-registrar.js +4 -6
  62. package/bin/sre/sentinel.js +7 -5
  63. package/bin/sre/shadow-mirror.js +90 -40
  64. package/bin/utils/append-queue.js +67 -0
  65. package/bin/utils/file-io.js +29 -80
  66. package/bin/utils/version-check.js +75 -0
  67. package/bin/verify-audit.js +12 -0
  68. package/bin/wizard/theme.js +1 -2
  69. package/package.json +1 -1
  70. package/bin/dashboard/team-tracker.js +0 -0
@@ -5,6 +5,7 @@
5
5
 
6
6
  const crypto = require('node:crypto');
7
7
  const { promisify } = require('node:util');
8
+ const configManager = require('./config-manager');
8
9
 
9
10
  const generateKeyPair = promisify(crypto.generateKeyPair);
10
11
 
@@ -64,7 +65,7 @@ class SecureEnclaveProvider extends KeyProvider {
64
65
  }
65
66
 
66
67
  async generate(did) {
67
- console.log(`[ZTAI-HSM] Provisioning protected identity enclave for ${did}...`);
68
+ console.log(`[ZTAI-HSM-SIM] Provisioning simulated (in-process) identity enclave for ${did}...`);
68
69
  const { publicKey, privateKey } = await generateKeyPair('ed25519');
69
70
  const pubPEM = publicKey.export({ type: 'spki', format: 'pem' });
70
71
 
@@ -81,7 +82,7 @@ class SecureEnclaveProvider extends KeyProvider {
81
82
  const record = this.enclaveStore.get(did);
82
83
  if (!record) throw new Error(`Enclave record not found for ${did}`);
83
84
 
84
- console.log(`[ZTAI-HSM] Delegating signature to hardware enclave [DID: ${did}]`);
85
+ console.log(`[ZTAI-HSM-SIM] Signing via simulated in-process enclave (NOT a hardware HSM/TPM) [DID: ${did}]`);
85
86
 
86
87
  // Simulate enclave "wrapping" or "sealing" logic
87
88
  const signature = crypto.sign(null, Buffer.from(data), record.privateKey);
@@ -92,7 +93,7 @@ class SecureEnclaveProvider extends KeyProvider {
92
93
  }
93
94
 
94
95
  async rotate(did) {
95
- console.log(`[ZTAI-HSM] Rotating enclave keys for ${did}...`);
96
+ console.log(`[ZTAI-HSM-SIM] Rotating simulated enclave keys for ${did}...`);
96
97
  return this.generate(did);
97
98
  }
98
99
 
@@ -147,6 +148,34 @@ class ZTAIManager {
147
148
  };
148
149
  }
149
150
 
151
+ /**
152
+ * Selects a key provider for a given trust tier.
153
+ *
154
+ * UC-24: All tiers route to REAL crypto by default — Tier 1-2 use the
155
+ * in-memory Ed25519 provider, Tier 3+ use the (simulated-HSM) enclave
156
+ * provider which also signs with REAL Ed25519. The SIMULATED post-quantum
157
+ * lattice provider ('quantum') is NEVER selected on the live trust path
158
+ * unless the operator has explicitly opted into the demo via
159
+ * experimental.pqc_demo. This keeps false-assurance signatures off the
160
+ * default trust path while preserving the demo for explicit exploration.
161
+ *
162
+ * @param {number} tier - Trust tier (1-4)
163
+ * @returns {'local'|'enclave'|'quantum'}
164
+ */
165
+ _selectProvider(tier) {
166
+ const pqcDemo = configManager.get('experimental.pqc_demo', false) === true;
167
+
168
+ // Tier 4+ MAY use the simulated lattice provider, but only when the
169
+ // operator has explicitly enabled the PQC demo. Otherwise it falls back to
170
+ // the real-Ed25519 enclave provider so the trust path stays verifiable.
171
+ if (tier >= 4) {
172
+ return pqcDemo ? 'quantum' : 'enclave';
173
+ }
174
+
175
+ // Tier 3 agents use the SecureEnclaveProvider (real Ed25519).
176
+ return tier >= 3 ? 'enclave' : 'local';
177
+ }
178
+
150
179
  /**
151
180
  * Registers a new agent and assigns a provider based on Trust Tier.
152
181
  * @param {string} persona - Agent persona identifier
@@ -157,8 +186,7 @@ class ZTAIManager {
157
186
  const uuid = crypto.randomUUID();
158
187
  const did = `did:mindforge:${uuid}`;
159
188
 
160
- // Tier 3 agents use the SecureEnclaveProvider
161
- const providerType = tier >= 3 ? 'enclave' : 'local';
189
+ const providerType = this._selectProvider(tier);
162
190
  const provider = this.providers[providerType];
163
191
 
164
192
  const publicKeyPEM = await provider.generate(did);
@@ -21,17 +21,16 @@ class HindsightInjector {
21
21
  // 1. Rollback .planning directory
22
22
  TemporalHub.rollbackTo(auditId);
23
23
 
24
- // 2. Append the "Hindsight" event to AUDIT.jsonl
24
+ // 2. Append the "Hindsight" event to AUDIT.jsonl via the unified, hash-chained,
25
+ // durable append (UC-04b) so this entry links into the single verifiable chain.
26
+ const { appendAuditEntrySync } = require('./autonomous/audit-writer');
25
27
  const auditPath = path.join(process.cwd(), '.planning', 'AUDIT.jsonl');
26
- const hindsightEvent = {
27
- id: require('crypto').randomBytes(8).toString('hex'),
28
- timestamp: new Date().toISOString(),
28
+ const hindsightEvent = appendAuditEntrySync(auditPath, {
29
29
  event: 'hindsight_injected',
30
30
  target_id: auditId,
31
31
  description: fixDescription,
32
32
  agent: 'temporal-hub'
33
- };
34
- fs.appendFileSync(auditPath, JSON.stringify(hindsightEvent) + '\n');
33
+ });
35
34
 
36
35
  // 3. Mark the state as "ready_for_regeneration"
37
36
  const statePath = path.join(process.cwd(), '.planning', 'auto-state.json');
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * MindForge — Instinct Auto-Capture Hook (UC-11)
5
+ * Invoked as a PostToolUse hook. Reads hook event JSON from stdin,
6
+ * detects successful task completions, and appends lightweight instinct
7
+ * entries to the configured store path.
8
+ *
9
+ * Session capture limit is enforced via a temp counter file to avoid
10
+ * flooding the store with low-signal entries.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+ const crypto = require('crypto');
16
+ const os = require('os');
17
+
18
+ // ── Configuration ────────────────────────────────────────────────────────────
19
+
20
+ const CONFIG_PATH = path.join(process.cwd(), '.mindforge', 'config.json');
21
+ const SESSION_ID = process.env.MINDFORGE_SESSION_ID || process.ppid || 'default';
22
+ const SESSION_COUNTER_PATH = path.join(
23
+ os.tmpdir(),
24
+ `mindforge-instinct-session-${SESSION_ID}.count`
25
+ );
26
+
27
+ function loadConfig() {
28
+ try {
29
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf8');
30
+ return JSON.parse(raw);
31
+ } catch {
32
+ return null;
33
+ }
34
+ }
35
+
36
+ function getSessionCount() {
37
+ try {
38
+ const raw = fs.readFileSync(SESSION_COUNTER_PATH, 'utf8');
39
+ return parseInt(raw, 10) || 0;
40
+ } catch {
41
+ return 0;
42
+ }
43
+ }
44
+
45
+ function incrementSessionCount() {
46
+ const current = getSessionCount();
47
+ fs.writeFileSync(SESSION_COUNTER_PATH, String(current + 1));
48
+ }
49
+
50
+ // ── Success Detection ────────────────────────────────────────────────────────
51
+
52
+ function isSuccessfulCompletion(payload) {
53
+ const tool = (payload.tool_name || payload.tool || '').toLowerCase();
54
+
55
+ // Bash tool with exit code 0
56
+ if (tool === 'bash') {
57
+ const exitCode = payload.exit_code ?? payload.result?.exit_code ?? null;
58
+ if (exitCode === 0) return true;
59
+ // If no explicit exit code but has output and no error marker
60
+ if (exitCode === null && payload.output && !payload.error) return true;
61
+ return false;
62
+ }
63
+
64
+ // Task tool with completed status
65
+ if (tool === 'task') {
66
+ const status = (payload.status || payload.result?.status || '').toLowerCase();
67
+ return status === 'completed' || status === 'done';
68
+ }
69
+
70
+ return false;
71
+ }
72
+
73
+ // ── Pattern Extraction ───────────────────────────────────────────────────────
74
+
75
+ function extractPattern(payload) {
76
+ const tool = (payload.tool_name || payload.tool || '').toLowerCase();
77
+
78
+ if (tool === 'bash') {
79
+ const command = payload.command || payload.input?.command || payload.tool_input?.command || '';
80
+ if (!command || command.length < 5) return null;
81
+ // Skip trivial commands
82
+ if (/^(ls|pwd|echo|cat|cd)\b/.test(command.trim())) return null;
83
+ return {
84
+ observation: `Bash command succeeded: ${command.slice(0, 200)}`,
85
+ behavior: `Use pattern: ${command.slice(0, 200)}`,
86
+ };
87
+ }
88
+
89
+ if (tool === 'task') {
90
+ const description = payload.description || payload.task_description || payload.name || '';
91
+ if (!description) return null;
92
+ return {
93
+ observation: `Task completed successfully: ${description.slice(0, 200)}`,
94
+ behavior: `Reuse approach for similar tasks: ${description.slice(0, 200)}`,
95
+ };
96
+ }
97
+
98
+ return null;
99
+ }
100
+
101
+ // ── Main ─────────────────────────────────────────────────────────────────────
102
+
103
+ function main() {
104
+ const config = loadConfig();
105
+ if (!config || !config.instincts) {
106
+ process.exit(0);
107
+ }
108
+
109
+ const { mode, max_capture_per_session, store_path } = config.instincts;
110
+ if (mode !== 'auto-capture') {
111
+ process.exit(0);
112
+ }
113
+
114
+ // Check session limit
115
+ const sessionCount = getSessionCount();
116
+ if (sessionCount >= (max_capture_per_session || 5)) {
117
+ process.exit(0);
118
+ }
119
+
120
+ // Read stdin (hook payload)
121
+ let input = '';
122
+ try {
123
+ input = fs.readFileSync(0, 'utf8');
124
+ } catch {
125
+ process.exit(0);
126
+ }
127
+
128
+ if (!input.trim()) {
129
+ process.exit(0);
130
+ }
131
+
132
+ let payload;
133
+ try {
134
+ payload = JSON.parse(input);
135
+ } catch {
136
+ process.exit(0);
137
+ }
138
+
139
+ // Check if this is a successful completion
140
+ if (!isSuccessfulCompletion(payload)) {
141
+ process.exit(0);
142
+ }
143
+
144
+ // Extract pattern
145
+ const pattern = extractPattern(payload);
146
+ if (!pattern) {
147
+ process.exit(0);
148
+ }
149
+
150
+ // Build instinct entry
151
+ const entry = {
152
+ id: `inst-${crypto.randomUUID()}`,
153
+ created_at: new Date().toISOString(),
154
+ updated_at: new Date().toISOString(),
155
+ observation: pattern.observation,
156
+ behavior: pattern.behavior,
157
+ confidence: 0.3,
158
+ times_applied: 0,
159
+ times_succeeded: 0,
160
+ times_failed: 0,
161
+ project: 'mindforge',
162
+ tags: [],
163
+ status: 'active',
164
+ promoted_to_skill: null,
165
+ last_applied_at: null,
166
+ source: 'auto-capture',
167
+ };
168
+
169
+ // Write to store
170
+ const storePath = path.resolve(process.cwd(), store_path);
171
+ const storeDir = path.dirname(storePath);
172
+
173
+ try {
174
+ if (!fs.existsSync(storeDir)) {
175
+ fs.mkdirSync(storeDir, { recursive: true });
176
+ }
177
+ fs.appendFileSync(storePath, JSON.stringify(entry) + '\n');
178
+ incrementSessionCount();
179
+ } catch {
180
+ // Non-fatal — hooks must not block
181
+ }
182
+
183
+ process.exit(0);
184
+ }
185
+
186
+ main();
@@ -95,6 +95,25 @@ const RUNTIMES = {
95
95
  },
96
96
  };
97
97
 
98
+ /**
99
+ * Reads the target project's experimental.pqc_demo flag — the SINGLE gate that
100
+ * the engine (bin/governance/quantum-crypto.js) uses to enable the simulated
101
+ * PQAS minter. Defaults to false (engine default) when the config is absent or
102
+ * unreadable, so the installer never over-claims that PQAS is enabled.
103
+ * @param {string} cwd - Target project root being installed into.
104
+ * @returns {boolean} - true only when experimental.pqc_demo === true.
105
+ */
106
+ function isPqcDemoEnabled(cwd) {
107
+ try {
108
+ const cfgPath = path.join(cwd, '.mindforge', 'config.json');
109
+ if (!fs.existsSync(cfgPath)) return false;
110
+ const cfg = JSON.parse(fs.readFileSync(cfgPath, 'utf8'));
111
+ return cfg && cfg.experimental && cfg.experimental.pqc_demo === true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
98
117
  /**
99
118
  * Generates runtime-specific entry file content.
100
119
  * e.g. replacing "Claude" with "Gemini" in GEMINI.md
@@ -650,9 +669,19 @@ async function install(runtime, scope, options = {}) {
650
669
  }
651
670
  });
652
671
 
653
- // ✨ SOVEREIGN INITIALIZATION: Mark project as PQAS & Proactive enabled
672
+ // ✨ SOVEREIGN INITIALIZATION: report actual security posture honestly.
673
+ // The PQAS minter is gated SOLELY behind experimental.pqc_demo (see
674
+ // bin/governance/quantum-crypto.js: getProvider/_assertPqcDemoEnabled). When
675
+ // that flag is off (the default) PQAS is inert/simulated — claiming it is
676
+ // "enabled" would contradict the engine and mislead operators (UC-22).
654
677
  Theme.printStatus(c.magenta('Sovereign Intelligence v8.2.0 activated'), 'done');
655
- Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS) enabled'), 'info');
678
+ if (isPqcDemoEnabled(process.cwd())) {
679
+ Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): SIMULATED demo ENABLED '
680
+ + '(experimental.pqc_demo=true — simulated lattice crypto, NOT production trust)'), 'info');
681
+ } else {
682
+ Theme.printStatus(c.dim(' - Post-Quantum Agentic Security (PQAS): available in simulated/experimental '
683
+ + 'mode (inactive by default — set experimental.pqc_demo=true to enable the simulated demo)'), 'info');
684
+ }
656
685
  Theme.printStatus(c.dim(' - Proactive Semantic Intent Harvesting active'), 'info');
657
686
 
658
687
  // bin/ utilities (remaining non-engine scripts)
@@ -18,6 +18,8 @@ const path = require('path');
18
18
  const Store = require('./knowledge-store');
19
19
  const Graph = require('./knowledge-graph');
20
20
  const Embedder = require('./embedding-engine');
21
+ const Indexer = require('./knowledge-indexer');
22
+ const { fuseResults } = require('./retrieval-fusion');
21
23
 
22
24
  // ── Configuration ─────────────────────────────────────────────────────────────
23
25
  const MAX_SHADOW_CHARS = 8000; // ~2KB tokens
@@ -63,13 +65,40 @@ function generateShadowContext(opts = {}) {
63
65
 
64
66
  const { vectors, df, N } = Embedder.buildEmbeddings(activeEntries);
65
67
 
66
- // 2. Hybrid query: embedding similarity + graph traversal
68
+ // 2. Multi-path retrieval with RRF fusion (UC-20)
69
+ // Path 1: Knowledge Graph (embedding + graph traversal)
70
+ // Path 2: Knowledge Indexer (BM25 + confidence)
71
+ // Results are fused via Reciprocal Rank Fusion for scale-free merging.
67
72
  const queryText = `${taskDescription} ${techStack.join(' ')}`;
68
- const related = Graph.findRelated(queryText, vectors, df, N, {
73
+ const fetchK = maxItems * 3; // Over-fetch for filtering headroom
74
+
75
+ const graphResults = Graph.findRelated(queryText, vectors, df, N, {
69
76
  maxHops: 2,
70
- topK: maxItems * 2, // Over-fetch for filtering
77
+ topK: fetchK,
71
78
  });
72
79
 
80
+ let indexerResults = [];
81
+ try {
82
+ const rawIndexer = Indexer.search(queryText, { includeGlobal: true }, fetchK);
83
+ indexerResults = rawIndexer.map((entry, rank) => ({
84
+ id: entry.id,
85
+ score: entry.confidence || 0,
86
+ source: 'indexer',
87
+ }));
88
+ } catch {
89
+ // Indexer may fail on empty store — non-fatal
90
+ }
91
+
92
+ // RRF fusion: merge both ranked lists by ordinal position
93
+ const fusedResults = fuseResults([graphResults, indexerResults]);
94
+
95
+ // Map fused results back to the legacy shape expected downstream
96
+ const related = fusedResults.map(item => ({
97
+ id: item.id,
98
+ score: item.rrfScore, // RRF score replaces incomparable linear blends
99
+ source: item.source || 'fused',
100
+ }));
101
+
73
102
  // 3. Filter and enrich results
74
103
  const excludeSet = new Set(excludeIds);
75
104
  const enriched = [];
@@ -100,10 +100,48 @@ class EISClient {
100
100
  return [];
101
101
  }
102
102
 
103
- // TODO: implement when remote nodes are available
103
+ /**
104
+ * Verifies the provenance of a remote knowledge entry by cryptographically
105
+ * checking its signature against the signer DID's registered public key.
106
+ *
107
+ * HONEST / FAIL-CLOSED CONTRACT (UC-22, finding #22): this method NEVER
108
+ * returns true for a signature it has not actually verified. It returns true
109
+ * ONLY when ZTAI.verifySignature confirms the signature against a public key
110
+ * that is resolvable in the local trust registry. Every other case fails
111
+ * closed → false:
112
+ * - no/empty signature,
113
+ * - no signer DID on the entry,
114
+ * - the DID is not resolvable here (e.g. a genuinely remote peer whose key
115
+ * is not in the local registry — there is no remote DID-resolution infra
116
+ * yet, see resolveRemoteNode),
117
+ * - tampered payload or signature (crypto.verify returns false / throws).
118
+ *
119
+ * @param {{did?: string, signedData?: string}} entry - Provenance-bearing entry.
120
+ * `did` is the signer's DID; `signedData` is the exact canonical bytes that
121
+ * were signed (defaults to a deterministic JSON of the entry if absent).
122
+ * @param {string} signature - Base64 signature to verify.
123
+ * @returns {boolean} true only if cryptographically verified; false otherwise.
124
+ */
104
125
  verifyRemoteProvenance(entry, signature) {
105
- if (!signature) return false;
106
- return true;
126
+ if (!signature || typeof signature !== 'string') return false;
127
+ if (!entry || typeof entry !== 'object') return false;
128
+
129
+ const did = entry.did;
130
+ if (!did || typeof did !== 'string') return false;
131
+
132
+ // Canonical signed bytes: prefer an explicit signedData field, else a
133
+ // deterministic JSON of the entry (excluding the signature envelope).
134
+ const signedData = typeof entry.signedData === 'string'
135
+ ? entry.signedData
136
+ : JSON.stringify(entry);
137
+
138
+ try {
139
+ // ZTAI.verifySignature throws for an unresolvable (unregistered) DID —
140
+ // treat that as fail-closed rather than asserting verified provenance.
141
+ return ZTAI.verifySignature(did, signedData, signature) === true;
142
+ } catch {
143
+ return false;
144
+ }
107
145
  }
108
146
 
109
147
  // TODO: implement when remote nodes are available
@@ -113,7 +151,10 @@ class EISClient {
113
151
 
114
152
  /**
115
153
  * [HARDEN] Generates a cryptographically signed auth header using the agent's DID.
116
- * This ensures verifiable provenance of knowledge within the mesh.
154
+ * This attaches OUTBOUND provenance to locally-originated requests (it signs
155
+ * what this node sends). It does NOT verify the provenance of inbound remote
156
+ * entries — that is verifyRemoteProvenance's job, which fails closed unless a
157
+ * signature is cryptographically verified against a resolvable public key.
117
158
  */
118
159
  async getAuthHeader(action, resource) {
119
160
  const manager = new ZTAI();
@@ -25,7 +25,7 @@ class IdentitySynthesizer {
25
25
  .replace(/{PROJECT_OBJECTIVE}/g, answers.goal || 'Maximizing engineering leverage');
26
26
 
27
27
  await fs.writeFile(this.soulPath, soulContent);
28
- console.log(`[IDENTITY] SOUL.md bootstrapped successfully from the Grand Blueprint.`);
28
+ console.log('[IDENTITY] SOUL.md bootstrapped successfully from the Grand Blueprint.');
29
29
  }
30
30
 
31
31
  /**
@@ -41,7 +41,7 @@ class IdentitySynthesizer {
41
41
  );
42
42
 
43
43
  if (traces.length === 0) {
44
- console.log(`[IDENTITY] No execution traces found in celestial.db. Evolution skipped.`);
44
+ console.log('[IDENTITY] No execution traces found in celestial.db. Evolution skipped.');
45
45
  return;
46
46
  }
47
47
 
@@ -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
@@ -201,12 +225,12 @@ function add(entry) {
201
225
 
202
226
  const filePath = getFilePath(entry.type);
203
227
  verifyFileIntegrity(filePath);
204
- fs.appendFileSync(filePath, JSON.stringify(full) + '\n');
228
+ appendDurableSync(filePath, JSON.stringify(full));
205
229
 
206
230
  // Also append to unified knowledge-base.jsonl for cross-type queries
207
231
  if (filePath !== paths.KB_PATH) {
208
232
  verifyFileIntegrity(paths.KB_PATH);
209
- fs.appendFileSync(paths.KB_PATH, JSON.stringify(full) + '\n');
233
+ appendDurableSync(paths.KB_PATH, JSON.stringify(full));
210
234
  }
211
235
 
212
236
  _invalidateIndex();
@@ -236,10 +260,10 @@ function deprecate(id, reason, supersededBy = null) {
236
260
  };
237
261
 
238
262
  verifyFileIntegrity(filePath);
239
- fs.appendFileSync(filePath, JSON.stringify(deprecated) + '\n');
263
+ appendDurableSync(filePath, JSON.stringify(deprecated));
240
264
  if (filePath !== paths.KB_PATH) {
241
265
  verifyFileIntegrity(paths.KB_PATH);
242
- fs.appendFileSync(paths.KB_PATH, JSON.stringify(deprecated) + '\n');
266
+ appendDurableSync(paths.KB_PATH, JSON.stringify(deprecated));
243
267
  }
244
268
 
245
269
  _invalidateIndex();
@@ -267,10 +291,10 @@ function reinforce(id) {
267
291
 
268
292
  const filePath = getFilePath(entry.type);
269
293
  verifyFileIntegrity(filePath);
270
- fs.appendFileSync(filePath, JSON.stringify(reinforced) + '\n');
294
+ appendDurableSync(filePath, JSON.stringify(reinforced));
271
295
  if (filePath !== paths.KB_PATH) {
272
296
  verifyFileIntegrity(paths.KB_PATH);
273
- fs.appendFileSync(paths.KB_PATH, JSON.stringify(reinforced) + '\n');
297
+ appendDurableSync(paths.KB_PATH, JSON.stringify(reinforced));
274
298
  }
275
299
 
276
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
- "SELECT * FROM traces WHERE event = ? LIMIT 20",
109
+ 'SELECT * FROM traces WHERE event = ? LIMIT 20',
110
110
  ['reasoning_trace']
111
111
  );
112
112
  }