prism-mcp-server 7.3.3 β†’ 7.5.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.
@@ -625,6 +625,15 @@ export function renderDashboardHTML(version) {
625
625
  <ul class="todo-list" id="todos"></ul>
626
626
  </div>
627
627
 
628
+ <!-- Intent Health (Feature A) -->
629
+ <div class="card" id="intentHealthCard" style="display: none;">
630
+ <div class="card-title" style="display: flex; justify-content: space-between; align-items: center;">
631
+ <div><span class="dot" style="background:var(--accent-purple)"></span> Intent Health</div>
632
+ <span style="font-size: 11px; color: var(--text-muted); cursor: help; border-bottom: 1px dotted var(--text-muted); letter-spacing: normal; text-transform: none;" title="Intent debt limits how AI agents can evolve the system (Storey / Fowler)">What is this?</span>
633
+ </div>
634
+ <div id="intentHealthCardContent"></div>
635
+ </div>
636
+
628
637
  <!-- Git Metadata -->
629
638
  <div class="card">
630
639
  <div class="card-title"><span class="dot" style="background:var(--accent-green)"></span> Git Metadata</div>
@@ -1465,15 +1474,15 @@ function loadPipelines() {
1465
1474
  html += '<span style="font-weight:600;color:var(--text-primary)">' + emoji + ' ' + p.status + '</span>';
1466
1475
  html += '<span style="font-size:0.7rem;font-family:var(--font-mono);color:var(--text-muted)">' + p.id.slice(0, 8) + '…</span>';
1467
1476
  html += '</div>';
1468
- html += '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.35rem">' + objective + '</div>';
1477
+ html += '<div style="font-size:0.82rem;color:var(--text-secondary);margin-bottom:0.35rem">' + escapeHtml(objective) + '</div>';
1469
1478
  html += '<div style="display:flex;gap:1rem;font-size:0.72rem;color:var(--text-muted);flex-wrap:wrap">';
1470
- html += '<span>πŸ“ ' + (p.project || '?') + '</span>';
1479
+ html += '<span>πŸ“ ' + escapeHtml(p.project || '?') + '</span>';
1471
1480
  html += '<span>πŸ”„ ' + p.iteration + ' / ' + maxIter + '</span>';
1472
- html += '<span>πŸ“ ' + (p.current_step || '?') + '</span>';
1481
+ html += '<span>πŸ“ ' + escapeHtml(p.current_step || '?') + '</span>';
1473
1482
  html += '<span>πŸ• ' + new Date(p.updated_at).toLocaleString() + '</span>';
1474
1483
  html += '</div>';
1475
1484
  if (p.error) {
1476
- html += '<div style="font-size:0.72rem;color:var(--accent-rose);margin-top:0.35rem;padding:0.3rem 0.5rem;background:rgba(244,63,94,0.08);border-radius:4px">⚠ ' + p.error.slice(0, 200) + '</div>';
1485
+ html += '<div style="font-size:0.72rem;color:var(--accent-rose);margin-top:0.35rem;padding:0.3rem 0.5rem;background:rgba(244,63,94,0.08);border-radius:4px">⚠ ' + escapeHtml(p.error.slice(0, 200)) + '</div>';
1477
1486
  }
1478
1487
  if (isActive) {
1479
1488
  html += '<div style="margin-top:0.5rem"><button onclick="abortPipeline(this.dataset.id)" data-id="' + p.id + '" class="cleanup-btn" style="font-size:0.72rem">πŸ›‘ Abort Pipeline</button></div>';
@@ -1495,7 +1504,7 @@ function loadPipelines() {
1495
1504
  }
1496
1505
  })
1497
1506
  .catch(function (err) {
1498
- document.getElementById('factoryList').innerHTML = '<div style="color:var(--accent-rose);padding:1rem">Failed to load pipelines: ' + err.message + '</div>';
1507
+ document.getElementById('factoryList').innerHTML = '<div style="color:var(--accent-rose);padding:1rem">Failed to load pipelines: ' + escapeHtml(err.message) + '</div>';
1499
1508
  });
1500
1509
  }
1501
1510
  function abortPipeline(id) {
@@ -1672,11 +1681,36 @@ function loadIdentityChip() {
1672
1681
  select = document.getElementById('projectSelect');
1673
1682
  if (data.projects && data.projects.length > 0) {
1674
1683
  select.innerHTML = '<option value="">β€” Select a project β€”</option>' +
1675
- data.projects.map(function (p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
1684
+ data.projects.map(function (p) { return '<option value="' + escapeHtml(p) + '">' + escapeHtml(p) + '</option>'; }).join('');
1685
+
1686
+ // v7.5.0: Background fetch intent health to add global traffic-light dots
1687
+ // Fetch sequentially to prevent SQLite WAL saturation with N concurrent loadContext calls
1688
+ var pIndex = 0;
1689
+ function fetchNextHealth() {
1690
+ if (pIndex >= data.projects.length) return;
1691
+ var p = data.projects[pIndex++];
1692
+ fetch('/api/intent-health?project=' + encodeURIComponent(p))
1693
+ .then(function (res) { return res.json(); })
1694
+ .then(function (hData) {
1695
+ if (hData && typeof hData.score === 'number') {
1696
+ var icon = hData.score >= 80 ? "🟒" : (hData.score >= 50 ? "🟑" : "πŸ”΄");
1697
+ var opt = null;
1698
+ for (var oi = 0; oi < select.options.length; oi++) {
1699
+ if (select.options[oi].value === p) { opt = select.options[oi]; break; }
1700
+ }
1701
+ if (opt) opt.textContent = icon + " " + p;
1702
+ }
1703
+ fetchNextHealth();
1704
+ }).catch(function () { fetchNextHealth(); });
1705
+ }
1706
+ if (data.projects && data.projects.length > 0) {
1707
+ fetchNextHealth();
1708
+ }
1709
+
1676
1710
  gp = document.getElementById('graphProjectFilter');
1677
1711
  if (gp) {
1678
1712
  gp.innerHTML = '<option value="">All Projects</option>' +
1679
- data.projects.map(function (p) { return '<option value="' + p + '">' + p + '</option>'; }).join('');
1713
+ data.projects.map(function (p) { return '<option value="' + escapeHtml(p) + '">' + escapeHtml(p) + '</option>'; }).join('');
1680
1714
  }
1681
1715
  // Restore last selected project from localStorage
1682
1716
  var lastProject = localStorage.getItem('prism_last_project');
@@ -1708,8 +1742,15 @@ function loadProject() {
1708
1742
  switch (_a.label) {
1709
1743
  case 0:
1710
1744
  project = document.getElementById('projectSelect').value;
1711
- if (!project)
1745
+ if (!project) {
1746
+ var hc = document.getElementById('intentHealthCard');
1747
+ if (hc) hc.style.display = 'none';
1748
+ var welcomeEl = document.getElementById('welcome');
1749
+ var contentEl = document.getElementById('content');
1750
+ if (contentEl) contentEl.style.display = 'none';
1751
+ if (welcomeEl) welcomeEl.style.display = 'flex';
1712
1752
  return [2 /*return*/];
1753
+ }
1713
1754
  // Persist last selected project
1714
1755
  try { localStorage.setItem('prism_last_project', project); } catch(e) {}
1715
1756
  document.getElementById('welcome').style.display = 'none';
@@ -1767,7 +1808,7 @@ function loadProject() {
1767
1808
  var snap = h.snapshot || {};
1768
1809
  var summary = snap.last_summary || snap.summary || 'Snapshot';
1769
1810
  return '<div class="timeline-item history">' +
1770
- '<div class="meta"><span class="badge badge-purple">v' + h.version + '</span>' +
1811
+ '<div class="meta"><span class="badge badge-purple">v' + escapeHtml(h.version) + '</span>' +
1771
1812
  '<span>' + formatDate(h.created_at) + '</span></div>' +
1772
1813
  escapeHtml(summary) + '</div>';
1773
1814
  }).join('');
@@ -1785,7 +1826,7 @@ function loadProject() {
1785
1826
  try {
1786
1827
  var parsed = typeof decisions === 'string' ? JSON.parse(decisions) : decisions;
1787
1828
  if (Array.isArray(parsed) && parsed.length > 0) {
1788
- extra = '<div style="margin-top:0.3rem;font-size:0.75rem;color:var(--accent-cyan)">Decisions: ' + parsed.join(', ') + '</div>';
1829
+ extra = '<div style="margin-top:0.3rem;font-size:0.75rem;color:var(--accent-cyan)">Decisions: ' + parsed.map(function(d) { return escapeHtml(d); }).join(', ') + '</div>';
1789
1830
  }
1790
1831
  }
1791
1832
  catch (e) { }
@@ -1851,6 +1892,10 @@ function loadProject() {
1851
1892
  document.getElementById('content').className = 'grid grid-main fade-in';
1852
1893
  document.getElementById('content').style.display = 'grid';
1853
1894
  projectLoaded = true; // Fix 3: mark project as loaded for tab-switch restore
1895
+
1896
+ // Feature A: Run Intent Health Fetch
1897
+ fetchIntentHealth(project);
1898
+
1854
1899
  // v3.1: Analytics + Lifecycle Controls + Import
1855
1900
  document.getElementById('analyticsCard').style.display = 'block';
1856
1901
  document.getElementById('lifecycleCard').style.display = 'block';
@@ -2393,7 +2438,7 @@ function showToast(msg, isErr) {
2393
2438
  function escapeHtml(str) {
2394
2439
  if (!str)
2395
2440
  return '';
2396
- return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2441
+ return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;').replace(/'/g, '&#039;');
2397
2442
  }
2398
2443
  function formatDate(isoStr) {
2399
2444
  if (!isoStr)
@@ -4060,6 +4105,67 @@ if ('serviceWorker' in navigator) {
4060
4105
  });
4061
4106
  }
4062
4107
 
4108
+ /* --- INTENT HEALTH COMPONENT (ES5 Safe) --- */
4109
+
4110
+ function fetchIntentHealth(project) {
4111
+ var container = document.getElementById('intentHealthCardContent');
4112
+ var card = document.getElementById('intentHealthCard');
4113
+ if (!container || !card) return;
4114
+
4115
+ card.style.display = 'block';
4116
+ container.innerHTML = '<div style="color: #8b949e; padding: 16px;">Computing intent debt...</div>';
4117
+
4118
+ fetch('/api/intent-health?project=' + encodeURIComponent(project))
4119
+ .then(function(res) { return res.json(); })
4120
+ .then(function(data) {
4121
+ if (data.error) {
4122
+ container.innerHTML = '<div style="color: #ff7b72; padding: 16px;">Error: ' + escapeHtml(data.error) + '</div>';
4123
+ return;
4124
+ }
4125
+ renderIntentHealthCard(data);
4126
+ })
4127
+ .catch(function(err) {
4128
+ container.innerHTML = '<div style="color: #ff7b72; padding: 16px;">Failed to load intent health.</div>';
4129
+ });
4130
+ }
4131
+
4132
+ function renderIntentHealthCard(data) {
4133
+ var container = document.getElementById('intentHealthCardContent');
4134
+ if (!container) return;
4135
+
4136
+ // Determine Gauge Color
4137
+ var scoreColor = '#2ea043'; // Green
4138
+ if (data.score < 50) {
4139
+ scoreColor = '#ff7b72'; // Red
4140
+ } else if (data.score < 80) {
4141
+ scoreColor = '#d29922'; // Yellow
4142
+ }
4143
+
4144
+ // Render Signals
4145
+ var signalsHtml = '';
4146
+ for (var i = 0; i < data.signals.length; i++) {
4147
+ var sig = data.signals[i];
4148
+ var icon = '🟒';
4149
+ if (sig.severity === 'warn') icon = '🟑';
4150
+ if (sig.severity === 'critical') icon = 'πŸ”΄';
4151
+ signalsHtml += '<div style="margin-bottom: 8px; font-size: 13px;">' + icon + ' ' + escapeHtml(sig.message) + '</div>';
4152
+ }
4153
+
4154
+ // Render Layout
4155
+ var html = '' +
4156
+ '<div style="display: flex; gap: 24px; align-items: center; padding: 16px;">' +
4157
+ '<div style="text-align: center; flex-shrink: 0; min-width: 80px;">' +
4158
+ '<div style="font-size: 42px; font-weight: bold; color: ' + scoreColor + '; line-height: 1;">' + (typeof data.score === 'number' ? data.score : 'β€”') + '</div>' +
4159
+ '<div style="font-size: 10px; color: #8b949e; text-transform: uppercase; letter-spacing: 1px; margin-top: 8px;">Health Score</div>' +
4160
+ '</div>' +
4161
+ '<div style="flex-grow: 1; border-left: 1px solid var(--border-glass); padding-left: 24px;">' +
4162
+ signalsHtml +
4163
+ '</div>' +
4164
+ '</div>';
4165
+
4166
+ container.innerHTML = html;
4167
+ }
4168
+
4063
4169
  </script>
4064
4170
  </body>
4065
4171
  </html>`;
package/dist/server.js CHANGED
@@ -1184,6 +1184,25 @@ export async function startServer() {
1184
1184
  console.error(`[DarkFactory] Startup failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
1185
1185
  });
1186
1186
  }
1187
+ // ─── v7.4: TurboQuant Compressor Async Warmup ────────────
1188
+ // The first call to getDefaultCompressor() triggers construction of a
1189
+ // 768Γ—768 rotation matrix (~15ms of synchronous CPU). Pre-warm via
1190
+ // setImmediate so it runs after the current event-loop tick completes,
1191
+ // preventing the stdio handshake from being blocked during startup.
1192
+ // Fire-and-forget β€” non-critical; subsequent calls hit the singleton cache.
1193
+ setImmediate(() => {
1194
+ try {
1195
+ // Dynamic import avoids loading turboquant.ts at module-parse time
1196
+ // (the construction side-effects run only when actually needed).
1197
+ import("./utils/turboquant.js").then(({ getDefaultCompressor }) => {
1198
+ getDefaultCompressor();
1199
+ console.error("[Prism] TurboQuant compressor pre-warmed (rotation matrix ready)");
1200
+ }).catch(err => {
1201
+ console.error(`[TurboQuant] Warmup failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
1202
+ });
1203
+ }
1204
+ catch { /* warmup is a best-effort optimization */ }
1205
+ });
1187
1206
  // Keep the process alive β€” without this, Node.js would exit
1188
1207
  // because there are no active event loop handles after the
1189
1208
  // synchronous setup completes.
@@ -566,13 +566,37 @@ export class SqliteStorage {
566
566
  status TEXT NOT NULL,
567
567
  current_step TEXT NOT NULL,
568
568
  iteration INTEGER NOT NULL,
569
+ eval_revisions INTEGER DEFAULT 0,
569
570
  started_at TEXT NOT NULL,
570
571
  updated_at TEXT NOT NULL,
571
572
  spec TEXT NOT NULL,
572
573
  error TEXT,
573
- last_heartbeat TEXT
574
+ last_heartbeat TEXT,
575
+ contract_payload TEXT,
576
+ notes TEXT
574
577
  )
575
578
  `);
579
+ // ─── v7.4.0 Migration: Adversarial Eval Revisions ─────────
580
+ try {
581
+ await this.db.execute(`ALTER TABLE dark_factory_pipelines ADD COLUMN eval_revisions INTEGER DEFAULT 0`);
582
+ debugLog("[SqliteStorage] v7.4.0 migration: added eval_revisions column");
583
+ // Backfill existing rows β€” ALTER TABLE DEFAULT only applies to new inserts;
584
+ // rows that existed before the migration will have NULL until explicitly set.
585
+ await this.db.execute(`UPDATE dark_factory_pipelines SET eval_revisions = 0 WHERE eval_revisions IS NULL`);
586
+ debugLog("[SqliteStorage] v7.4.0 migration: backfilled eval_revisions = 0");
587
+ }
588
+ catch (e) {
589
+ if (!e.message?.includes("duplicate column name"))
590
+ throw e;
591
+ }
592
+ try {
593
+ await this.db.execute(`ALTER TABLE dark_factory_pipelines ADD COLUMN contract_payload TEXT`);
594
+ await this.db.execute(`ALTER TABLE dark_factory_pipelines ADD COLUMN notes TEXT`);
595
+ }
596
+ catch (e) {
597
+ if (!e.message?.includes("duplicate column name"))
598
+ throw e;
599
+ }
576
600
  await this.db.execute(`CREATE INDEX IF NOT EXISTS idx_pipelines_status ON dark_factory_pipelines(user_id, project, status)`);
577
601
  // ─── v7.2.0 Migration: Verification Harness ────────────────
578
602
  await this.db.execute(`
@@ -1160,6 +1184,30 @@ export class SqliteStorage {
1160
1184
  importance: r.importance,
1161
1185
  }));
1162
1186
  }
1187
+ // ─── v7.5.0: Validation Pulse (Standard & Deep) ─────────────
1188
+ context.recent_validations = []; // Default empty
1189
+ try {
1190
+ const validationResult = await this.db.execute({
1191
+ sql: `SELECT run_at, passed, pass_rate, gate_action, critical_failures
1192
+ FROM verification_runs
1193
+ WHERE project = ? AND user_id = ?
1194
+ ORDER BY run_at DESC
1195
+ LIMIT 3`,
1196
+ args: [project, userId],
1197
+ });
1198
+ if (validationResult.rows.length > 0) {
1199
+ context.recent_validations = validationResult.rows.map(r => ({
1200
+ run_at: r.run_at,
1201
+ passed: Boolean(r.passed),
1202
+ pass_rate: r.pass_rate,
1203
+ gate_action: r.gate_action,
1204
+ critical_failures: Number(r.critical_failures) || 0,
1205
+ }));
1206
+ }
1207
+ }
1208
+ catch (e) {
1209
+ // Graceful degradation if verification_runs table hasn't been migrated yet
1210
+ }
1163
1211
  if (level === "standard") {
1164
1212
  // Add recent ledger entries (role-scoped)
1165
1213
  const recentLedger = await this.db.execute({
@@ -2888,16 +2936,19 @@ export class SqliteStorage {
2888
2936
  }
2889
2937
  await this.db.execute({
2890
2938
  sql: `
2891
- INSERT INTO dark_factory_pipelines (id, project, user_id, status, current_step, iteration, started_at, updated_at, spec, error, last_heartbeat)
2892
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2939
+ INSERT INTO dark_factory_pipelines (id, project, user_id, status, current_step, iteration, eval_revisions, started_at, updated_at, spec, error, last_heartbeat, contract_payload, notes)
2940
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
2893
2941
  ON CONFLICT(id) DO UPDATE SET
2894
2942
  status = excluded.status,
2895
2943
  current_step = excluded.current_step,
2896
2944
  iteration = excluded.iteration,
2945
+ eval_revisions = excluded.eval_revisions,
2897
2946
  updated_at = excluded.updated_at,
2898
2947
  spec = excluded.spec,
2899
2948
  error = excluded.error,
2900
- last_heartbeat = excluded.last_heartbeat
2949
+ last_heartbeat = excluded.last_heartbeat,
2950
+ contract_payload = excluded.contract_payload,
2951
+ notes = excluded.notes
2901
2952
  `,
2902
2953
  args: [
2903
2954
  updatedState.id,
@@ -2906,11 +2957,14 @@ export class SqliteStorage {
2906
2957
  updatedState.status,
2907
2958
  updatedState.current_step,
2908
2959
  updatedState.iteration,
2960
+ updatedState.eval_revisions ?? 0,
2909
2961
  updatedState.started_at,
2910
2962
  updatedState.updated_at,
2911
2963
  updatedState.spec,
2912
2964
  updatedState.error || null,
2913
- updatedState.last_heartbeat || null
2965
+ updatedState.last_heartbeat || null,
2966
+ updatedState.contract_payload ? JSON.stringify(updatedState.contract_payload) : null,
2967
+ updatedState.notes || null
2914
2968
  ]
2915
2969
  });
2916
2970
  }
@@ -2921,7 +2975,11 @@ export class SqliteStorage {
2921
2975
  });
2922
2976
  if (result.rows.length === 0)
2923
2977
  return null;
2924
- return result.rows[0];
2978
+ const row = result.rows[0];
2979
+ return {
2980
+ ...row,
2981
+ contract_payload: row.contract_payload ? JSON.parse(row.contract_payload) : undefined
2982
+ };
2925
2983
  }
2926
2984
  async listPipelines(project, status, userId) {
2927
2985
  const conditions = ['user_id = ?'];
@@ -2937,7 +2995,10 @@ export class SqliteStorage {
2937
2995
  const where = conditions.length > 0 ? `WHERE ${conditions.join(' AND ')}` : '';
2938
2996
  const sql = `SELECT * FROM dark_factory_pipelines ${where} ORDER BY updated_at DESC`;
2939
2997
  const result = await this.db.execute({ sql, args });
2940
- return result.rows;
2998
+ return result.rows.map((row) => ({
2999
+ ...row,
3000
+ contract_payload: row.contract_payload ? JSON.parse(row.contract_payload) : undefined
3001
+ }));
2941
3002
  }
2942
3003
  // ─── Verification Harness (v7.2.0) ───────────────────────────
2943
3004
  async saveVerificationHarness(harness, userId) {
@@ -170,7 +170,36 @@ export class SupabaseStorage {
170
170
  p_role: role || "global", // v3.0: pass role to RPC
171
171
  });
172
172
  const data = Array.isArray(result) ? result[0] : result;
173
- return data ?? null;
173
+ const context = data ?? null;
174
+ if (!context)
175
+ return null;
176
+ // ─── v7.5.0: Validation Pulse (Standard & Deep) ─────────────
177
+ if (level === "standard" || level === "deep") {
178
+ context.recent_validations = [];
179
+ try {
180
+ const valData = await supabaseGet("verification_runs", {
181
+ project: `eq.${project}`,
182
+ user_id: `eq.${userId}`,
183
+ select: "run_at,passed,pass_rate,gate_action,critical_failures",
184
+ order: "run_at.desc",
185
+ limit: "3"
186
+ });
187
+ const validations = Array.isArray(valData) ? valData : [];
188
+ if (validations.length > 0) {
189
+ context.recent_validations = validations.map((r) => ({
190
+ run_at: r.run_at,
191
+ passed: Boolean(r.passed),
192
+ pass_rate: r.pass_rate,
193
+ gate_action: r.gate_action,
194
+ critical_failures: Number(r.critical_failures) || 0,
195
+ }));
196
+ }
197
+ }
198
+ catch (e) {
199
+ // Graceful degradation if table doesn't exist yet
200
+ }
201
+ }
202
+ return context;
174
203
  }
175
204
  catch (e) {
176
205
  debugLog("[SupabaseStorage] loadContext RPC failed: " + (e instanceof Error ? e.message : String(e)));
@@ -1222,7 +1251,13 @@ export class SupabaseStorage {
1222
1251
  updated_at: updatedState.updated_at,
1223
1252
  spec: updatedState.spec,
1224
1253
  error: updatedState.error || null,
1225
- last_heartbeat: updatedState.last_heartbeat || null
1254
+ last_heartbeat: updatedState.last_heartbeat || null,
1255
+ // ─── v7.4: Adversarial Evaluation fields ───
1256
+ eval_revisions: updatedState.eval_revisions ?? 0,
1257
+ contract_payload: updatedState.contract_payload
1258
+ ? JSON.stringify(updatedState.contract_payload)
1259
+ : null,
1260
+ notes: updatedState.notes || null,
1226
1261
  }, { on_conflict: "id" }, { Prefer: "return=minimal,resolution=merge-duplicates" });
1227
1262
  }
1228
1263
  catch (e) {
@@ -1244,7 +1279,15 @@ export class SupabaseStorage {
1244
1279
  const rows = Array.isArray(result) ? result : [];
1245
1280
  if (rows.length === 0)
1246
1281
  return null;
1247
- return rows[0];
1282
+ const row = rows[0];
1283
+ // ─── v7.4: Deserialize contract_payload from JSON TEXT ───
1284
+ if (row.contract_payload && typeof row.contract_payload === "string") {
1285
+ try {
1286
+ row.contract_payload = JSON.parse(row.contract_payload);
1287
+ }
1288
+ catch { /* leave as-is */ }
1289
+ }
1290
+ return row;
1248
1291
  }
1249
1292
  catch (e) {
1250
1293
  if (e.message?.includes("PGRST202") || e.message?.includes("Could not find the relation"))
@@ -1263,7 +1306,17 @@ export class SupabaseStorage {
1263
1306
  if (status)
1264
1307
  query.status = `eq.${status}`;
1265
1308
  const result = await supabaseGet("dark_factory_pipelines", query);
1266
- return (Array.isArray(result) ? result : []);
1309
+ const rows = (Array.isArray(result) ? result : []);
1310
+ // ─── v7.4: Deserialize contract_payload from JSON TEXT ───
1311
+ return rows.map(row => {
1312
+ if (row.contract_payload && typeof row.contract_payload === "string") {
1313
+ try {
1314
+ row.contract_payload = JSON.parse(row.contract_payload);
1315
+ }
1316
+ catch { /* leave as-is */ }
1317
+ }
1318
+ return row;
1319
+ });
1267
1320
  }
1268
1321
  catch (e) {
1269
1322
  if (e.message?.includes("PGRST202") || e.message?.includes("Could not find the relation"))
@@ -657,6 +657,19 @@ export async function sessionLoadContextHandler(args) {
657
657
  if (d.session_history?.length) {
658
658
  formattedContext += `\nπŸ“‚ Session History (${d.session_history.length} entries):\n` + d.session_history.map((s) => ` [${s.session_date?.split("T")[0]}] ${s.summary}`).join("\n") + `\n`;
659
659
  }
660
+ if (d.recent_validations?.length) {
661
+ formattedContext += `\nπŸ”¬ Recent Validations:\n` + d.recent_validations.map((v) => {
662
+ const passStr = v.passed ? "βœ… PASS" : "❌ FAIL";
663
+ const icon = v.gate_action ? (v.passed ? "πŸš€" : "πŸ›‘") : "ℹ️";
664
+ const dateStr = (v.run_at || "unknown").split("T")[0];
665
+ const rateStr = Math.round((Number(v.pass_rate) || 0) * 100);
666
+ let out = ` ${icon} [${dateStr}] ${passStr} (${rateStr}%)`;
667
+ if (v.critical_failures > 0) {
668
+ out += ` -> Critical Blockers: ${v.critical_failures}`;
669
+ }
670
+ return out;
671
+ }).join("\n") + `\n`;
672
+ }
660
673
  // ─── Role-Scoped Skill Injection ─────────────────────────────
661
674
  // If the active role has a skill document stored, append it so the
662
675
  // agent loads its rules/conventions automatically at session start.
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "prism-mcp-server",
3
- "version": "7.3.3",
3
+ "version": "7.5.0",
4
4
  "mcpName": "io.github.dcostenco/prism-mcp",
5
- "description": "The Mind Palace for AI Agents — fail-closed Dark Factory autonomous pipelines (3-gate parse→type→scope validation), persistent memory (SQLite/Supabase), ACT-R cognitive retrieval, behavioral learning & IDE rules sync, multi-agent Hivemind, time travel, visual dashboard. Zero-config local mode.",
5
+ "description": "The Mind Palace for AI Agents — adversarial evaluation (PLAN_CONTRACT→EVALUATE anti-sycophancy), fail-closed Dark Factory autonomous pipelines (3-gate parse→type→scope), persistent memory (SQLite/Supabase), ACT-R cognitive retrieval, behavioral learning & IDE rules sync, multi-agent Hivemind, time travel, visual dashboard. Zero-config local mode.",
6
6
  "module": "index.ts",
7
7
  "type": "module",
8
8
  "main": "dist/server.js",