mindforge-cc 8.1.1 → 9.0.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 (74) hide show
  1. package/.claude/settings.local.json +16 -0
  2. package/.mindforge/celestial.db +0 -0
  3. package/.mindforge/config.json +7 -7
  4. package/.planning/REQUIREMENTS.md +13 -1
  5. package/.planning/STATE.md +13 -8
  6. package/.planning/jira-sync.json +1 -5
  7. package/.planning/slack-threads.json +1 -3
  8. package/CHANGELOG.md +39 -0
  9. package/LICENSE +1 -1
  10. package/MINDFORGE.md +14 -13
  11. package/README.md +9 -2
  12. package/RELEASENOTES.md +55 -0
  13. package/bin/autonomous/auto-runner.js +307 -14
  14. package/bin/engine/learning-manager.js +4 -2
  15. package/bin/governance/impact-analyzer.js +4 -2
  16. package/bin/installer-core.js +18 -2
  17. package/bin/memory/vector-hub.js +148 -16
  18. package/bin/migrations/migrate.js +1 -0
  19. package/bin/migrations/v9-unified-memory.js +98 -0
  20. package/bin/models/cloud-broker.js +7 -6
  21. package/bin/models/model-broker.js +6 -5
  22. package/bin/models/model-client.js +12 -8
  23. package/bin/models/model-router.js +13 -11
  24. package/bin/sre/adversarial-sre.js +109 -0
  25. package/bin/sre/sentinel.js +128 -0
  26. package/bin/sre/shadow-mirror.js +122 -0
  27. package/bin/sre/sli-verifier.js +81 -0
  28. package/docs/Context/Master-Context.md +39 -2
  29. package/docs/PERSONAS.md +40 -0
  30. package/docs/architecture/V8-SRE.md +88 -0
  31. package/docs/architecture/V9-BEDROCK.md +162 -0
  32. package/docs/governance-guide.md +52 -17
  33. package/package.json +2 -2
  34. package/.mindforge/bypasses.json +0 -8
  35. package/.mindforge/memory/decision-library.jsonl +0 -0
  36. package/.mindforge/memory/knowledge-base.jsonl +0 -15
  37. package/.mindforge/memory/pattern-library.jsonl +0 -1
  38. package/.mindforge/memory/team-preferences.jsonl +0 -5
  39. package/.mindforge/remediation-queue.json +0 -47
  40. package/.planning/AUDIT.jsonl +0 -45
  41. package/.planning/RISK-AUDIT.jsonl +0 -53
  42. package/.planning/ROI.jsonl +0 -2
  43. package/.planning/TEMPORAL-TEST.md +0 -1
  44. package/.planning/history/36525e1d9da1b674/ARCHITECTURE.md +0 -0
  45. package/.planning/history/36525e1d9da1b674/HANDOFF.json +0 -8
  46. package/.planning/history/36525e1d9da1b674/PROJECT.md +0 -33
  47. package/.planning/history/36525e1d9da1b674/RELEASE-CHECKLIST.md +0 -68
  48. package/.planning/history/36525e1d9da1b674/REQUIREMENTS.md +0 -0
  49. package/.planning/history/36525e1d9da1b674/ROADMAP.md +0 -12
  50. package/.planning/history/36525e1d9da1b674/SNAPSHOT-META.json +0 -18
  51. package/.planning/history/36525e1d9da1b674/STATE.md +0 -31
  52. package/.planning/history/36525e1d9da1b674/TEMPORAL-TEST.md +0 -1
  53. package/.planning/history/36525e1d9da1b674/jira-sync.json +0 -5
  54. package/.planning/history/36525e1d9da1b674/slack-threads.json +0 -3
  55. package/.planning/history/test-audit-001/ARCHITECTURE.md +0 -0
  56. package/.planning/history/test-audit-001/HANDOFF.json +0 -8
  57. package/.planning/history/test-audit-001/PROJECT.md +0 -33
  58. package/.planning/history/test-audit-001/RELEASE-CHECKLIST.md +0 -68
  59. package/.planning/history/test-audit-001/REQUIREMENTS.md +0 -0
  60. package/.planning/history/test-audit-001/ROADMAP.md +0 -12
  61. package/.planning/history/test-audit-001/SNAPSHOT-META.json +0 -17
  62. package/.planning/history/test-audit-001/STATE.md +0 -31
  63. package/.planning/history/test-audit-001/TEMPORAL-TEST.md +0 -1
  64. package/.planning/history/test-audit-001/jira-sync.json +0 -5
  65. package/.planning/history/test-audit-001/slack-threads.json +0 -3
  66. package/bin/engine/test-ceg.js +0 -59
  67. package/bin/engine/test-interlock.js +0 -40
  68. package/bin/engine/test-remediation.js +0 -61
  69. package/bin/engine/test-rsa.js +0 -64
  70. package/bin/engine/test-scs.js +0 -57
  71. package/bin/engine/test-v7-blueprint.js +0 -44
  72. package/bin/governance/test-config.js +0 -40
  73. package/bin/governance/test-crypto-pluggable.js +0 -50
  74. package/bin/governance/test-hardened-gate.js +0 -71
@@ -1,24 +1,25 @@
1
1
  const Database = require('better-sqlite3');
2
2
  const { Kysely, SqliteDialect, sql } = require('kysely');
3
+ const crypto = require('crypto');
3
4
  const path = require('path');
4
5
  const fs = require('fs');
5
6
 
6
7
  /**
7
- * MindForge v8 VectorHub
8
- * Unified Persistence Layer for Trace, Remediation, and Skill data.
8
+ * MindForge v9 VectorHub
9
+ * Unified Persistence Layer traces, remediations, skills, knowledge, and graph edges.
9
10
  */
10
11
  class VectorHub {
11
12
  constructor(dbPath = null) {
12
13
  this.dbPath = dbPath || path.join(process.cwd(), '.mindforge', 'celestial.db');
13
14
  this._ensureDir();
14
-
15
+
15
16
  const nativeDb = new Database(this.dbPath);
16
17
  this.db = new Kysely({
17
18
  dialect: new SqliteDialect({
18
19
  database: nativeDb,
19
20
  }),
20
21
  });
21
-
22
+
22
23
  this.initialized = false;
23
24
  }
24
25
 
@@ -51,13 +52,6 @@ class VectorHub {
51
52
  .addColumn('mesh_node_id', 'text') // v8 Pillar XVI
52
53
  .execute();
53
54
 
54
- // v8 Migration: ensure mesh_node_id exists on existing table
55
- try {
56
- await sql`ALTER TABLE traces ADD COLUMN mesh_node_id TEXT`.execute(this.db);
57
- } catch (e) {
58
- // Column might already exist, ignore error.
59
- }
60
-
61
55
  // Remediations Table
62
56
  await this.db.schema
63
57
  .createTable('remediations')
@@ -101,12 +95,61 @@ class VectorHub {
101
95
  .addColumn('value', 'text')
102
96
  .execute();
103
97
 
104
- // Enable Full-Text Search for traces (FTS5)
98
+ // v9 Pillar XXVI: Unified Knowledge table (migrated from JSONL stores)
99
+ await this.db.schema
100
+ .createTable('knowledge')
101
+ .ifNotExists()
102
+ .addColumn('id', 'text', (col) => col.primaryKey())
103
+ .addColumn('type', 'text', (col) => col.notNull())
104
+ .addColumn('content', 'text', (col) => col.notNull())
105
+ .addColumn('tags', 'text')
106
+ .addColumn('source', 'text')
107
+ .addColumn('confidence', 'real', (col) => col.defaultTo(1.0))
108
+ .addColumn('created_at', 'text', (col) => col.notNull())
109
+ .addColumn('updated_at', 'text')
110
+ .addColumn('metadata', 'text')
111
+ .execute();
112
+
113
+ // v9 Pillar XXVI: Graph edges table (migrated from knowledge-graph JSONL)
114
+ await this.db.schema
115
+ .createTable('graph_edges')
116
+ .ifNotExists()
117
+ .addColumn('id', 'text', (col) => col.primaryKey())
118
+ .addColumn('source_id', 'text', (col) => col.notNull())
119
+ .addColumn('target_id', 'text', (col) => col.notNull())
120
+ .addColumn('edge_type', 'text', (col) => col.notNull())
121
+ .addColumn('weight', 'real', (col) => col.defaultTo(1.0))
122
+ .addColumn('created_at', 'text', (col) => col.notNull())
123
+ .execute();
124
+
125
+ // v9 Pillar XXVII: Migration tracking table
126
+ await this.db.schema
127
+ .createTable('_migrations')
128
+ .ifNotExists()
129
+ .addColumn('id', 'integer', (col) => col.primaryKey())
130
+ .addColumn('name', 'text', (col) => col.notNull())
131
+ .addColumn('applied_at', 'text', (col) => col.notNull())
132
+ .execute();
133
+
134
+ // Enable Full-Text Search (FTS5) for traces and knowledge
105
135
  await sql`
106
- CREATE VIRTUAL TABLE IF NOT EXISTS traces_search
136
+ CREATE VIRTUAL TABLE IF NOT EXISTS traces_search
107
137
  USING fts5(trace_id, content, agent, tokenize='porter');
108
138
  `.execute(this.db);
109
139
 
140
+ await sql`
141
+ CREATE VIRTUAL TABLE IF NOT EXISTS knowledge_search
142
+ USING fts5(id, content, tags, tokenize='porter');
143
+ `.execute(this.db);
144
+
145
+ // v9: Secondary indexes for common query patterns
146
+ await sql`CREATE INDEX IF NOT EXISTS idx_traces_trace_id ON traces(trace_id)`.execute(this.db);
147
+ await sql`CREATE INDEX IF NOT EXISTS idx_traces_timestamp ON traces(timestamp)`.execute(this.db);
148
+ await sql`CREATE INDEX IF NOT EXISTS idx_knowledge_type ON knowledge(type)`.execute(this.db);
149
+ await sql`CREATE INDEX IF NOT EXISTS idx_graph_edges_source ON graph_edges(source_id)`.execute(this.db);
150
+ await sql`CREATE INDEX IF NOT EXISTS idx_graph_edges_target ON graph_edges(target_id)`.execute(this.db);
151
+ await sql`CREATE UNIQUE INDEX IF NOT EXISTS idx_migrations_name ON _migrations(name)`.execute(this.db);
152
+
110
153
  this.initialized = true;
111
154
  console.log(`[VectorHub] Initialized SQLite persistence at ${this.dbPath}`);
112
155
  }
@@ -120,7 +163,7 @@ class VectorHub {
120
163
  */
121
164
  async recordTrace(data) {
122
165
  const entry = {
123
- id: data.id || Math.random().toString(36).substr(2, 9),
166
+ id: data.id || crypto.randomBytes(8).toString('hex'),
124
167
  trace_id: data.trace_id,
125
168
  span_id: data.span_id || null,
126
169
  event: data.event,
@@ -143,6 +186,7 @@ class VectorHub {
143
186
 
144
187
  // Update FTS5 index if content exists
145
188
  if (entry.content) {
189
+ await sql`DELETE FROM traces_search WHERE trace_id = ${entry.trace_id}`.execute(this.db);
146
190
  await sql`INSERT INTO traces_search (trace_id, content, agent) VALUES (${entry.trace_id}, ${entry.content}, ${entry.agent})`
147
191
  .execute(this.db);
148
192
  }
@@ -153,9 +197,10 @@ class VectorHub {
153
197
  /**
154
198
  * Semantic search for previous traces.
155
199
  */
156
- async searchTraces(query) {
200
+ async searchTraces(rawQuery) {
201
+ const query = '"' + rawQuery.replace(/"/g, '""') + '"';
157
202
  const results = await sql`
158
- SELECT t.*, ts.rank
203
+ SELECT t.*, ts.rank
159
204
  FROM traces t
160
205
  JOIN traces_search ts ON t.trace_id = ts.trace_id
161
206
  WHERE traces_search MATCH ${query}
@@ -164,6 +209,93 @@ class VectorHub {
164
209
  `.execute(this.db);
165
210
  return results.rows;
166
211
  }
212
+ // ── v9 Pillar XXVI: Unified Knowledge API ─────────────────────────────────
213
+
214
+ async saveKnowledge(entry) {
215
+ const now = new Date().toISOString();
216
+ const record = {
217
+ id: entry.id || `k_${crypto.randomBytes(8).toString('hex')}`,
218
+ type: entry.type || 'insight',
219
+ content: entry.content,
220
+ tags: Array.isArray(entry.tags) ? entry.tags.join(',') : (entry.tags || ''),
221
+ source: entry.source || 'unknown',
222
+ confidence: entry.confidence ?? 1.0,
223
+ created_at: entry.created_at || now,
224
+ updated_at: now,
225
+ metadata: entry.metadata ? JSON.stringify(entry.metadata) : null,
226
+ };
227
+
228
+ await this.db.insertInto('knowledge')
229
+ .values(record)
230
+ .onConflict(oc => oc.column('id').doUpdateSet({
231
+ content: record.content,
232
+ tags: record.tags,
233
+ confidence: record.confidence,
234
+ updated_at: record.updated_at,
235
+ metadata: record.metadata,
236
+ }))
237
+ .execute();
238
+
239
+ await sql`DELETE FROM knowledge_search WHERE id = ${record.id}`.execute(this.db);
240
+ await sql`INSERT INTO knowledge_search (id, content, tags) VALUES (${record.id}, ${record.content}, ${record.tags})`
241
+ .execute(this.db);
242
+
243
+ return record.id;
244
+ }
245
+
246
+ async searchKnowledge(rawQuery, limit = 10) {
247
+ const query = '"' + rawQuery.replace(/"/g, '""') + '"';
248
+ const results = await sql`
249
+ SELECT k.*, ks.rank
250
+ FROM knowledge k
251
+ JOIN knowledge_search ks ON k.id = ks.id
252
+ WHERE knowledge_search MATCH ${query}
253
+ ORDER BY ks.rank
254
+ LIMIT ${limit}
255
+ `.execute(this.db);
256
+ return results.rows;
257
+ }
258
+
259
+ async saveEdge(edge) {
260
+ const record = {
261
+ id: edge.id || `e_${crypto.randomBytes(8).toString('hex')}`,
262
+ source_id: edge.source_id,
263
+ target_id: edge.target_id,
264
+ edge_type: edge.edge_type,
265
+ weight: edge.weight ?? 1.0,
266
+ created_at: edge.created_at || new Date().toISOString(),
267
+ };
268
+
269
+ await this.db.insertInto('graph_edges')
270
+ .values(record)
271
+ .onConflict(oc => oc.column('id').doNothing())
272
+ .execute();
273
+
274
+ return record.id;
275
+ }
276
+
277
+ async getEdges(nodeId) {
278
+ const results = await this.db.selectFrom('graph_edges')
279
+ .selectAll()
280
+ .where((eb) => eb.or([
281
+ eb('source_id', '=', nodeId),
282
+ eb('target_id', '=', nodeId),
283
+ ]))
284
+ .execute();
285
+ return results;
286
+ }
287
+
288
+ // ── v9 Pillar XXVII: Migration tracking ──────────────────────────────────
289
+
290
+ async getAppliedMigrations() {
291
+ const rows = await this.db.selectFrom('_migrations').selectAll().execute();
292
+ return rows.map(r => r.name);
293
+ }
294
+
295
+ async recordMigration(name) {
296
+ await sql`INSERT OR IGNORE INTO _migrations (name, applied_at) VALUES (${name}, ${new Date().toISOString()})`
297
+ .execute(this.db);
298
+ }
167
299
  }
168
300
 
169
301
  module.exports = new VectorHub();
@@ -44,6 +44,7 @@ async function runMigrations(fromVersion, toVersion) {
44
44
  require('./0.1.0-to-0.5.0'),
45
45
  require('./0.5.0-to-0.6.0'),
46
46
  require('./0.6.0-to-1.0.0'),
47
+ require('./v9-unified-memory'),
47
48
  ];
48
49
 
49
50
  // A migration should run if its DESTINATION VERSION falls within the range:
@@ -0,0 +1,98 @@
1
+ /**
2
+ * MindForge v9 Migration — Unified Memory Architecture (Pillar XXVI)
3
+ * Migrates knowledge-store JSONL and knowledge-graph JSONL into celestial.db.
4
+ */
5
+ 'use strict';
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const vectorHub = require('../memory/vector-hub');
10
+
11
+ const fromVersion = '8.2.1';
12
+ const toVersion = '9.0.0';
13
+ const description = 'Unified Memory: migrate JSONL knowledge stores into SQLite';
14
+
15
+ async function run() {
16
+ await vectorHub.init();
17
+
18
+ const applied = await vectorHub.getAppliedMigrations();
19
+ if (applied.includes('v9-unified-memory')) {
20
+ console.log('[v9-MIGRATION] Already applied — skipping.');
21
+ return;
22
+ }
23
+
24
+ let knowledgeMigrated = 0;
25
+ let edgesMigrated = 0;
26
+ let skippedLines = 0;
27
+
28
+ await vectorHub.db.transaction().execute(async (trx) => {
29
+ // 1. Migrate knowledge-base.jsonl → knowledge table
30
+ const kbPaths = [
31
+ path.join(process.cwd(), '.mindforge', 'memory', 'knowledge-base.jsonl'),
32
+ path.join(process.cwd(), '.mindforge', 'memory', 'global-knowledge-base.jsonl'),
33
+ ];
34
+
35
+ for (const kbPath of kbPaths) {
36
+ if (!fs.existsSync(kbPath)) continue;
37
+ const lines = fs.readFileSync(kbPath, 'utf8').split('\n').filter(Boolean);
38
+
39
+ for (const line of lines) {
40
+ try {
41
+ const entry = JSON.parse(line);
42
+ if (entry.status === 'deleted') continue;
43
+ await vectorHub.saveKnowledge({
44
+ id: entry.id,
45
+ type: entry.type || 'insight',
46
+ content: entry.content || entry.insight || '',
47
+ tags: entry.tags || [],
48
+ source: entry.source || (kbPath.includes('global') ? 'global' : 'project'),
49
+ confidence: entry.confidence ?? 1.0,
50
+ created_at: entry.timestamp || entry.created_at,
51
+ metadata: entry,
52
+ });
53
+ knowledgeMigrated++;
54
+ } catch (e) {
55
+ skippedLines++;
56
+ }
57
+ }
58
+ }
59
+
60
+ // 2. Migrate knowledge-graph edges → graph_edges table
61
+ const graphPath = path.join(process.cwd(), '.mindforge', 'memory', 'graph-edges.jsonl');
62
+ if (fs.existsSync(graphPath)) {
63
+ const lines = fs.readFileSync(graphPath, 'utf8').split('\n').filter(Boolean);
64
+
65
+ for (const line of lines) {
66
+ try {
67
+ const edge = JSON.parse(line);
68
+ await vectorHub.saveEdge({
69
+ id: edge.id || edge.edge_id,
70
+ source_id: edge.source_id || edge.from,
71
+ target_id: edge.target_id || edge.to,
72
+ edge_type: edge.edge_type || edge.type || 'RELATED_TO',
73
+ weight: edge.weight ?? 1.0,
74
+ created_at: edge.timestamp || edge.created_at,
75
+ });
76
+ edgesMigrated++;
77
+ } catch (e) {
78
+ skippedLines++;
79
+ }
80
+ }
81
+ }
82
+ });
83
+
84
+ // 3. Record migration completion (outside transaction — it committed successfully)
85
+ await vectorHub.recordMigration('v9-unified-memory');
86
+
87
+ console.log(`[v9-MIGRATION] Knowledge entries migrated: ${knowledgeMigrated}`);
88
+ console.log(`[v9-MIGRATION] Graph edges migrated: ${edgesMigrated}`);
89
+ if (skippedLines > 0) {
90
+ console.warn(`[v9-MIGRATION] WARNING: ${skippedLines} malformed lines skipped`);
91
+ }
92
+ }
93
+
94
+ if (require.main === module) {
95
+ run().then(() => process.exit(0)).catch(err => { console.error(err); process.exit(1); });
96
+ }
97
+
98
+ module.exports = { fromVersion, toVersion, description, run };
@@ -118,12 +118,13 @@ class CloudBroker {
118
118
  /**
119
119
  * Retrieves provider-specific model mapping.
120
120
  */
121
+ // v9: Provider model mappings aligned to Claude 4.x family
121
122
  mapToProviderModel(provider, modelGroup) {
122
123
  const mappings = {
123
- 'anthropic': { 'sonnet': 'claude-3-5-sonnet', 'opus': 'claude-3-opus', 'haiku': 'claude-3-haiku' },
124
- 'google': { 'sonnet': 'gemini-1.5-pro', 'haiku': 'gemini-1.5-flash' },
125
- 'aws': { 'sonnet': 'anthropic.claude-3-5-sonnet-v2:0', 'haiku': 'anthropic.claude-3-haiku-v1:0' },
126
- 'azure': { 'sonnet': 'gpt-4o', 'haiku': 'gpt-35-turbo' }
124
+ 'anthropic': { 'sonnet': 'claude-sonnet-4-6', 'opus': 'claude-opus-4-7', 'haiku': 'claude-haiku-4-5' },
125
+ 'google': { 'sonnet': 'gemini-2.5-pro', 'haiku': 'gemini-2.5-flash' },
126
+ 'aws': { 'sonnet': 'anthropic.claude-sonnet-4-6-v1:0', 'haiku': 'anthropic.claude-haiku-4-5-v1:0' },
127
+ 'azure': { 'sonnet': 'claude-sonnet-4-6', 'haiku': 'claude-haiku-4-5' }
127
128
  };
128
129
 
129
130
  return mappings[provider]?.[modelGroup] || mappings[provider]?.['sonnet'];
@@ -151,9 +152,9 @@ class CloudBroker {
151
152
  startChaosMode() {
152
153
  console.log('[ENTERPRISE-RESILIENCE] CloudBroker Chaos Mode ACTIVE. Simulating jitter and provider dropouts...');
153
154
  setInterval(() => {
154
- const providers = Object.keys(this.latencyMap);
155
+ const providers = Object.keys(this.state);
155
156
  const randomProvider = providers[Math.floor(Math.random() * providers.length)];
156
- this.latencyMap[randomProvider] = Math.random() > 0.7 ? 5000 : 100; // Spike latency
157
+ this.state[randomProvider].latency = Math.random() > 0.7 ? 5000 : 100;
157
158
  }, 10000);
158
159
  }
159
160
  }
@@ -114,14 +114,15 @@ class ModelBroker {
114
114
  }
115
115
 
116
116
  estimateCost(modelId, input, output) {
117
- // Mock cost estimation logic per 1M tokens
117
+ // v9: Pricing aligned to Claude 4.x family (per 1M tokens)
118
118
  const rates = {
119
- 'claude-3-opus': { in: 15, out: 75 },
120
- 'claude-3-5-sonnet': { in: 3, out: 15 },
121
- 'claude-3-haiku': { in: 0.25, out: 1.25 },
119
+ 'claude-opus-4-7': { in: 15, out: 75 },
120
+ 'claude-sonnet-4-6': { in: 3, out: 15 },
121
+ 'claude-haiku-4-5': { in: 0.80, out: 4.0 },
122
+ 'gemini-2.5-pro': { in: 1.25, out: 10 },
122
123
  };
123
124
 
124
- const rate = rates[modelId] || rates['claude-3-5-sonnet'];
125
+ const rate = rates[modelId] || rates['claude-sonnet-4-6'];
125
126
  return (input / 1_000_000) * rate.in + (output / 1_000_000) * rate.out;
126
127
  }
127
128
  }
@@ -10,10 +10,11 @@ const AnthropicProvider = require('./anthropic-provider');
10
10
  const OpenAIProvider = require('./openai-provider');
11
11
  const GeminiProvider = require('./gemini-provider');
12
12
 
13
+ // v9: Fallback chains aligned to Claude 4.x family
13
14
  const FALLBACK_CHAINS = {
14
- 'claude-3-opus-20240229': ['gpt-4o', 'claude-3-5-sonnet-20240620'],
15
- 'gpt-4o': ['claude-3-5-sonnet-20240620'],
16
- 'gemini-1.5-pro': ['claude-3-5-sonnet-20240620'],
15
+ 'claude-opus-4-7': ['claude-sonnet-4-6', 'gemini-2.5-pro'],
16
+ 'claude-sonnet-4-6': ['claude-haiku-4-5', 'gemini-2.5-pro'],
17
+ 'gemini-2.5-pro': ['claude-sonnet-4-6'],
17
18
  };
18
19
 
19
20
  class ModelClient {
@@ -70,24 +71,27 @@ class ModelClient {
70
71
  return result;
71
72
 
72
73
  } catch (err) {
73
- process.stderr.write(`[model-client] ${currentModel} failed: ${err.message}\n`);
74
+ const safeMsg = (err.message || '').replace(/sk-[a-zA-Z0-9_-]+/g, 'sk-***').replace(/key-[a-zA-Z0-9_-]+/g, 'key-***');
75
+ process.stderr.write(`[model-client] ${currentModel} failed: ${safeMsg}\n`);
74
76
  if (attempts.indexOf(currentModel) === attempts.length - 1) {
75
- throw err;
77
+ const safeErr = new Error(safeMsg);
78
+ safeErr.code = err.code;
79
+ throw safeErr;
76
80
  }
77
81
  }
78
82
  }
79
83
  }
80
84
 
81
85
  static _getProvider(modelId) {
82
- if (modelId.includes('claude')) {
86
+ if (modelId.startsWith('claude') || modelId.startsWith('anthropic.claude')) {
83
87
  if (!process.env.ANTHROPIC_API_KEY) return null;
84
88
  return new AnthropicProvider(process.env.ANTHROPIC_API_KEY);
85
89
  }
86
- if (modelId.includes('gpt')) {
90
+ if (modelId.startsWith('gpt') || modelId.startsWith('o1') || modelId.startsWith('o3')) {
87
91
  if (!process.env.OPENAI_API_KEY) return null;
88
92
  return new OpenAIProvider(process.env.OPENAI_API_KEY);
89
93
  }
90
- if (modelId.includes('gemini')) {
94
+ if (modelId.startsWith('gemini')) {
91
95
  if (!process.env.GOOGLE_API_KEY) return null;
92
96
  return new GeminiProvider(process.env.GOOGLE_API_KEY);
93
97
  }
@@ -7,18 +7,18 @@
7
7
  const fs = require('fs');
8
8
  const path = require('path');
9
9
 
10
- // Default model assignments
10
+ // v9: Model topology aligned to Claude 4.x family (2026-04)
11
11
  const DEFAULTS = {
12
- PLANNER_MODEL: 'claude-3-opus-20240229',
13
- EXECUTOR_MODEL: 'claude-3-5-sonnet-20240620',
14
- REVIEWER_MODEL: 'gpt-4o',
15
- SECURITY_MODEL: 'claude-3-opus-20240229',
16
- RESEARCH_MODEL: 'gemini-1.5-pro',
17
- QA_MODEL: 'claude-3-5-sonnet-20240620',
18
- DEBUG_MODEL: 'claude-3-opus-20240229',
19
- QUICK_MODEL: 'claude-3-5-haiku-20241022',
20
- CROSS_REVIEW_SECONDARY: 'gpt-4o',
21
- CROSS_REVIEW_TERTIARY: 'gemini-1.5-pro',
12
+ PLANNER_MODEL: 'claude-opus-4-7',
13
+ EXECUTOR_MODEL: 'claude-sonnet-4-6',
14
+ REVIEWER_MODEL: 'claude-sonnet-4-6',
15
+ SECURITY_MODEL: 'claude-opus-4-7',
16
+ RESEARCH_MODEL: 'gemini-2.5-pro',
17
+ QA_MODEL: 'claude-sonnet-4-6',
18
+ DEBUG_MODEL: 'claude-opus-4-7',
19
+ QUICK_MODEL: 'claude-haiku-4-5',
20
+ CROSS_REVIEW_SECONDARY: 'claude-sonnet-4-6',
21
+ CROSS_REVIEW_TERTIARY: 'gemini-2.5-pro',
22
22
  };
23
23
 
24
24
  // Persona to setting key mapping
@@ -31,6 +31,8 @@ const PERSONA_MAP = {
31
31
  'research-agent': 'RESEARCH_MODEL',
32
32
  'debug-specialist': 'DEBUG_MODEL',
33
33
  'decision-architect': 'PLANNER_MODEL',
34
+ 'sre-engineer': 'EXECUTOR_MODEL',
35
+ 'sre-auditor': 'PLANNER_MODEL',
34
36
  };
35
37
 
36
38
  let _settingsCache = null;
@@ -0,0 +1,109 @@
1
+ /**
2
+ * MindForge v9.0.0 — Adversarial SRE Debate (Pillar XXII)
3
+ * Specialized three-way consensus for high-stakes remediation.
4
+ */
5
+ 'use strict';
6
+
7
+ const fs = require('node:fs');
8
+ const path = require('node:path');
9
+ const ModelClient = require('../models/model-client');
10
+ const crypto = require('node:crypto');
11
+
12
+ class AdversarialSRE {
13
+ constructor(options = {}) {
14
+ this.sessionId = options.sessionId || 'sre-session';
15
+ }
16
+
17
+ /**
18
+ * Runs the SRE debate loop for a specific incident.
19
+ * @param {Object} incident - The incident details from Sentinel
20
+ * @param {string} mirrorPath - Path to the Shadow Mirror environment
21
+ */
22
+ async runDebate(incident, mirrorPath) {
23
+ console.log(`⚖️ SRE DEBATE: Starting consensus loop for ${incident.incident_type}...`);
24
+
25
+ // 1. Proposer (SRE Engineer) - Tier 2
26
+ console.log(' [1/3] SRE Engineer proposing fix...');
27
+ const proposerResult = await ModelClient.complete({
28
+ persona: 'sre-engineer',
29
+ tier: 2,
30
+ systemPrompt: `You are the SRE Proposer. An incident of type ${incident.incident_type} was detected.
31
+ Incident Details: ${JSON.stringify(incident.details)}
32
+ Propose a surgical remediation plan that minimizes downtime and avoids regressions.
33
+ Include an [SRE_PLAN] block with specific file mutations.`,
34
+ userMessage: `Environment: ${mirrorPath}\nIncident: ${incident.remediation_id}`,
35
+ taskName: `sre-proposal-${incident.remediation_id}`
36
+ });
37
+ const proposal = proposerResult.content;
38
+
39
+ // 2. Hunter (Chaos/Regression Agent) - Tier 2
40
+ console.log(' [2/3] Chaos Hunter identifying regressions...');
41
+ const hunterResult = await ModelClient.complete({
42
+ persona: 'qa-engineer',
43
+ tier: 2,
44
+ systemPrompt: `You are the SRE Chaos Hunter. Your goal is to find ways the proposed fix could break production.
45
+ Check for resource leaks, performance hits, or new security vulnerabilities introduced by the fix.
46
+ Include an [SRE_CRITIQUE] block with at least 2 critical concerns.`,
47
+ userMessage: `Proposed Fix:\n${proposal}`,
48
+ taskName: `sre-chaos-${incident.remediation_id}`
49
+ });
50
+ const critique = hunterResult.content;
51
+
52
+ // 3. Auditor (Specialist SRE Auditor) - Tier 3 (Opus Locked)
53
+ console.log(' [3/3] SRE Auditor (Opus) rendering final verdict...');
54
+ const auditorResult = await ModelClient.complete({
55
+ persona: 'sre-auditor',
56
+ tier: 3, // Force tier 3 for Opus binding
57
+ systemPrompt: `You are the High-Intelligence SRE Auditor (Opus-class).
58
+ You must analyze the struggle between the Proposer and the Hunter.
59
+ Your goal is to ensure "Deterministic Reliability."
60
+ Render a final [VERDICT]: APPROVED | REJECTED | AMENDED.
61
+ If AMENDED, provide the final corrected logic.`,
62
+ userMessage: `Incident: ${incident.incident_type}\n\nPROPOSAL:\n${proposal}\n\nCRITIQUE:\n${critique}`,
63
+ taskName: `sre-audit-${incident.remediation_id}`
64
+ });
65
+ const finalVerdict = auditorResult.content;
66
+
67
+ // Record the decision
68
+ const decisionId = crypto.randomBytes(4).toString('hex');
69
+ const decisionPath = path.join(process.cwd(), '.planning', 'decisions', `SRE-${decisionId}.md`);
70
+
71
+ const record = `
72
+ # SRE Decision Trace — ${decisionId}
73
+ - **Incident**: ${incident.incident_type}
74
+ - **Timestamp**: ${new Date().toISOString()}
75
+ - **Mirror**: ${mirrorPath}
76
+
77
+ ## Proposer Logic
78
+ ${proposal}
79
+
80
+ ## Chaos Critique
81
+ ${critique}
82
+
83
+ ## Final Auditor Verdict (Opus)
84
+ ${finalVerdict}
85
+ `;
86
+
87
+ if (!fs.existsSync(path.dirname(decisionPath))) {
88
+ fs.mkdirSync(path.dirname(decisionPath), { recursive: true });
89
+ }
90
+ fs.writeFileSync(decisionPath, record);
91
+
92
+ console.log(`✅ SRE Debate Complete. Verdict: ${this.parseVerdict(finalVerdict)}`);
93
+ return {
94
+ decision_id: decisionId,
95
+ verdict: this.parseVerdict(finalVerdict),
96
+ trace_path: decisionPath,
97
+ final_logic: finalVerdict
98
+ };
99
+ }
100
+
101
+ parseVerdict(text) {
102
+ if (text.includes('[VERDICT]: APPROVED')) return 'APPROVED';
103
+ if (text.includes('[VERDICT]: REJECTED')) return 'REJECTED';
104
+ if (text.includes('[VERDICT]: AMENDED')) return 'AMENDED';
105
+ return 'UNKNOWN';
106
+ }
107
+ }
108
+
109
+ module.exports = AdversarialSRE;