mindforge-cc 11.9.5 → 11.9.7

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.
@@ -6,15 +6,28 @@
6
6
 
7
7
  const { spawn, execSync } = require('child_process');
8
8
  const path = require('path');
9
- const fs = require('fs');
9
+ const fs = require('fs');
10
10
  const http = require('http');
11
11
 
12
12
  const PORT = process.env.BROWSER_PORT || 7338;
13
13
  const DAEMON_SCRIPT = path.join(__dirname, 'browser-daemon.js');
14
+ const TOKEN_FILE = path.join(
15
+ process.cwd(),
16
+ '.mindforge',
17
+ '.browser-daemon-token',
18
+ );
19
+
20
+ function readToken() {
21
+ try {
22
+ return fs.readFileSync(TOKEN_FILE, 'utf8').trim();
23
+ } catch {
24
+ return null;
25
+ }
26
+ }
14
27
 
15
28
  async function isRunning() {
16
- return new Promise(resolve => {
17
- const req = http.get(`http://127.0.0.1:${PORT}/status`, res => {
29
+ return new Promise((resolve) => {
30
+ const req = http.get(`http://127.0.0.1:${PORT}/status`, (res) => {
18
31
  resolve(res.statusCode === 200);
19
32
  });
20
33
  req.on('error', () => resolve(false));
@@ -25,13 +38,19 @@ async function isRunning() {
25
38
  async function start() {
26
39
  if (await isRunning()) return;
27
40
 
28
- const out = fs.openSync(path.join(process.cwd(), '.planning/browser-daemon.log'), 'a');
29
- const err = fs.openSync(path.join(process.cwd(), '.planning/browser-daemon.log'), 'a');
41
+ const out = fs.openSync(
42
+ path.join(process.cwd(), '.planning/browser-daemon.log'),
43
+ 'a',
44
+ );
45
+ const err = fs.openSync(
46
+ path.join(process.cwd(), '.planning/browser-daemon.log'),
47
+ 'a',
48
+ );
30
49
 
31
50
  const child = spawn(process.execPath, [DAEMON_SCRIPT], {
32
51
  detached: true,
33
52
  stdio: ['ignore', out, err],
34
- env: { ...process.env, BROWSER_HEADLESS: 'true' }
53
+ env: { ...process.env, BROWSER_HEADLESS: 'true' },
35
54
  });
36
55
 
37
56
  child.unref();
@@ -39,7 +58,7 @@ async function start() {
39
58
 
40
59
  // Wait for it to wake up
41
60
  for (let i = 0; i < 10; i++) {
42
- await new Promise(r => setTimeout(r, 500));
61
+ await new Promise((r) => setTimeout(r, 500));
43
62
  if (await isRunning()) {
44
63
  console.log('[manager] Browser daemon ready.');
45
64
  return;
@@ -53,7 +72,9 @@ async function stop() {
53
72
  // Simple way to kill on localhost
54
73
  try {
55
74
  if (process.platform === 'win32') {
56
- execSync(`for /f "tokens=5" %a in ('netstat -aon ^| find ":${PORT}" ^| find "LISTENING"') do taskkill /f /pid %a`);
75
+ execSync(
76
+ `for /f "tokens=5" %a in ('netstat -aon ^| find ":${PORT}" ^| find "LISTENING"') do taskkill /f /pid %a`,
77
+ );
57
78
  } else {
58
79
  execSync(`lsof -t -i:${PORT} | xargs kill -9`);
59
80
  }
@@ -69,20 +90,30 @@ async function ensureRunning(opts = {}) {
69
90
 
70
91
  async function request(method, endpoint, body = null) {
71
92
  return new Promise((resolve, reject) => {
72
- const req = http.request({
73
- hostname: '127.0.0.1',
74
- port: PORT,
75
- path: endpoint,
76
- method: method,
77
- headers: { 'Content-Type': 'application/json' }
78
- }, res => {
79
- let data = '';
80
- res.on('data', chunk => data += chunk);
81
- res.on('end', () => {
82
- try { resolve(JSON.parse(data)); } catch { resolve({ success: false, error: 'Invalid JSON response' }); }
83
- });
84
- });
85
- req.on('error', err => reject(err));
93
+ const headers = { 'Content-Type': 'application/json' };
94
+ const token = readToken();
95
+ if (token) headers.Authorization = `Bearer ${token}`;
96
+ const req = http.request(
97
+ {
98
+ hostname: '127.0.0.1',
99
+ port: PORT,
100
+ path: endpoint,
101
+ method: method,
102
+ headers,
103
+ },
104
+ (res) => {
105
+ let data = '';
106
+ res.on('data', (chunk) => (data += chunk));
107
+ res.on('end', () => {
108
+ try {
109
+ resolve(JSON.parse(data));
110
+ } catch {
111
+ resolve({ success: false, error: 'Invalid JSON response' });
112
+ }
113
+ });
114
+ },
115
+ );
116
+ req.on('error', (err) => reject(err));
86
117
  if (body) req.write(JSON.stringify(body));
87
118
  req.end();
88
119
  });
@@ -33,21 +33,33 @@
33
33
 
34
34
  // ── State ─────────────────────────────────────────────────────────────────
35
35
  let currentApprovals = [];
36
- let state = { costs: [], quality: [] };
36
+ let state = { sessions: [] }; // replaced wholesale by refreshData() with the /api/metrics response
37
37
 
38
38
  function showPage(id) {
39
- document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
40
- document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
39
+ document
40
+ .querySelectorAll('.page')
41
+ .forEach((p) => p.classList.remove('active'));
42
+ document
43
+ .querySelectorAll('.tab')
44
+ .forEach((t) => t.classList.remove('active'));
41
45
  document.getElementById(id).classList.add('active');
42
- const tab = Array.from(document.querySelectorAll('.tab')).find(t => t.innerText.toLowerCase() === id.toLowerCase());
46
+ const tab = Array.from(document.querySelectorAll('.tab')).find(
47
+ (t) => t.innerText.toLowerCase() === id.toLowerCase(),
48
+ );
43
49
  if (tab) tab.classList.add('active');
44
-
50
+
45
51
  if (id === 'metrics') drawCharts();
46
52
  }
47
53
 
48
54
  function escapeHtml(str) {
49
55
  if (!str) return '';
50
- return String(str).replace(/[&<>"']/g, m => ({ '&':'&amp;', '<':'&lt;', '>':'&gt;', '"':'&quot;', '\'':'&#39;' }[m]));
56
+ return String(str).replace(
57
+ /[&<>"']/g,
58
+ (m) =>
59
+ ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', '\'': '&#39;' })[
60
+ m
61
+ ],
62
+ );
51
63
  }
52
64
 
53
65
  // ── WebSocket / SSE ───────────────────────────────────────────────────────
@@ -95,9 +107,11 @@ async function refreshData() {
95
107
  refreshTeam();
96
108
  refreshRevOps();
97
109
  if (document.getElementById('temporal').classList.contains('active')) {
98
- refreshTemporal();
110
+ refreshTemporal();
99
111
  }
100
- } catch(e) { console.error('Refresh fail', e); }
112
+ } catch (e) {
113
+ console.error('Refresh fail', e);
114
+ }
101
115
  }
102
116
 
103
117
  async function refreshRevOps() {
@@ -105,9 +119,11 @@ async function refreshRevOps() {
105
119
  const res = await fetch('/api/revops/overview');
106
120
  const data = await res.json();
107
121
  if (data.success) {
108
- updateRevOpsUI(data);
122
+ updateRevOpsUI(data);
109
123
  }
110
- } catch { /* one panel failing must not abort the rest of the refresh; it keeps its last value */ }
124
+ } catch {
125
+ /* one panel failing must not abort the rest of the refresh; it keeps its last value */
126
+ }
111
127
  }
112
128
 
113
129
  let temporalHistory = [];
@@ -117,7 +133,9 @@ async function refreshTemporal() {
117
133
  const data = await res.json();
118
134
  temporalHistory = data;
119
135
  renderTemporalTimeline();
120
- } catch { /* one panel failing must not abort the rest of the refresh; it keeps its last value */ }
136
+ } catch {
137
+ /* one panel failing must not abort the rest of the refresh; it keeps its last value */
138
+ }
121
139
  }
122
140
 
123
141
  async function refreshApprovals() {
@@ -126,16 +144,25 @@ async function refreshApprovals() {
126
144
  const data = await res.json();
127
145
  currentApprovals = data;
128
146
  renderApprovals();
129
- } catch { /* one panel failing must not abort the rest of the refresh; it keeps its last value */ }
147
+ } catch {
148
+ /* one panel failing must not abort the rest of the refresh; it keeps its last value */
149
+ }
130
150
  }
131
151
 
132
152
  async function refreshMemory() {
133
153
  try {
134
154
  const res = await fetch('/api/memory');
155
+ // Metrics.getMemory() returns { entries, total } — a flat, confidence-sorted knowledge-base
156
+ // list, not a graph. This panel used to read `data.graph`/`data.count`, fields that endpoint
157
+ // has never produced, so it always rendered "{}" and "0 Items Indexed".
135
158
  const data = await res.json();
136
- document.getElementById('memory-map').innerHTML = `<pre>${escapeHtml(JSON.stringify(data.graph || {}, null, 2))}</pre>`;
137
- document.getElementById('memory-stats-list').innerHTML = `<div class="stat-value">${data.count || 0} <span class="stat-unit">Items Indexed</span></div>`;
138
- } catch { /* one panel failing must not abort the rest of the refresh; it keeps its last value */ }
159
+ document.getElementById('memory-map').innerHTML =
160
+ `<pre>${escapeHtml(JSON.stringify(data.entries || [], null, 2))}</pre>`;
161
+ document.getElementById('memory-stats-list').innerHTML =
162
+ `<div class="stat-value">${data.total || 0} <span class="stat-unit">Items Indexed</span></div>`;
163
+ } catch {
164
+ /* one panel failing must not abort the rest of the refresh; it keeps its last value */
165
+ }
139
166
  }
140
167
 
141
168
  async function refreshTeam() {
@@ -143,7 +170,9 @@ async function refreshTeam() {
143
170
  const res = await fetch('/api/team');
144
171
  const data = await res.json();
145
172
  renderTeam(data);
146
- } catch { /* one panel failing must not abort the rest of the refresh; it keeps its last value */ }
173
+ } catch {
174
+ /* one panel failing must not abort the rest of the refresh; it keeps its last value */
175
+ }
147
176
  }
148
177
 
149
178
  // decide() and submitDecision() are removed along with POST /api/approve/:id. They sent
@@ -153,25 +182,32 @@ async function refreshTeam() {
153
182
  // by bin/governance/approve.js, which takes identity from git and fails closed without a GPG
154
183
  // key unless MINDFORGE_ALLOW_UNVERIFIED_APPROVAL=1.
155
184
 
156
-
157
185
  function closeConfirm() {
158
186
  document.getElementById('confirm-overlay').style.display = 'none';
159
187
  }
160
188
 
161
189
  // ── UI Rendering ──────────────────────────────────────────────────────────
162
190
  function updateStatusUI(data) {
163
- document.getElementById('project-name').textContent = `PROJ: ${data.project_name || 'UNSET'}`;
191
+ document.getElementById('project-name').textContent =
192
+ `PROJ: ${data.project_name || 'UNSET'}`;
164
193
  document.getElementById('stat-phase').textContent = data.phase || '0';
165
- document.getElementById('stat-status').textContent = (data.auto_status || 'IDLE').toUpperCase();
166
- document.getElementById('stat-tasks').textContent = `${data.tasks_completed || 0}/${data.tasks_total || 0}`;
167
- document.getElementById('stat-elapsed').textContent = data.elapsed_ms ? (data.elapsed_ms / 1000).toFixed(1) + 's' : '0s';
194
+ document.getElementById('stat-status').textContent = (
195
+ data.auto_status || 'IDLE'
196
+ ).toUpperCase();
197
+ document.getElementById('stat-tasks').textContent =
198
+ `${data.tasks_completed || 0}/${data.tasks_total || 0}`;
199
+ document.getElementById('stat-elapsed').textContent = data.elapsed_ms
200
+ ? (data.elapsed_ms / 1000).toFixed(1) + 's'
201
+ : '0s';
168
202
  }
169
203
 
170
204
  function updateMetricsUI(data) {
171
205
  // /api/metrics returns { sessions, avg_quality, avg_cost_usd, ... } — see
172
206
  // bin/dashboard/metrics-aggregator.js getMetrics(). It has never returned
173
207
  // `costs` or `quality`, so these tiles used to render "$0.0000" and "NaN".
174
- document.getElementById('stat-avg-quality').textContent = (data.avg_quality ?? 0).toFixed(1);
208
+ document.getElementById('stat-avg-quality').textContent = (
209
+ data.avg_quality ?? 0
210
+ ).toFixed(1);
175
211
  }
176
212
 
177
213
  // Cumulative spend comes from the usage ledger via /api/costs (already wired
@@ -184,14 +220,22 @@ async function refreshCosts() {
184
220
  // and genuine zero spend look identical under the "Cost (7d)" label.
185
221
  if (!res.ok) throw new Error(`/api/costs ${res.status}`);
186
222
  const data = await res.json();
187
- document.getElementById('stat-total-cost').textContent = `${(data.total_usd ?? 0).toFixed(2)}`;
188
- } catch (e) { console.error('Cost refresh failed', e); }
223
+ document.getElementById('stat-total-cost').textContent =
224
+ `${(data.total_usd ?? 0).toFixed(2)}`;
225
+ } catch (e) {
226
+ console.error('Cost refresh failed', e);
227
+ }
189
228
  }
190
229
 
191
230
  function appendAuditEvent(data) {
192
231
  const div = document.createElement('div');
193
232
  div.className = `event type-${data.event}`;
194
- const time = new Date(data.timestamp).toLocaleTimeString([], { hour12:false, hour:'2-digit', minute:'2-digit', second:'2-digit' });
233
+ const time = new Date(data.timestamp).toLocaleTimeString([], {
234
+ hour12: false,
235
+ hour: '2-digit',
236
+ minute: '2-digit',
237
+ second: '2-digit',
238
+ });
195
239
  div.innerHTML = `<span class="event-time">${time}</span><span class="event-type type-${data.event}">${data.event}</span><span class="event-msg">${escapeHtml(data.message || data.task || '')}</span>`;
196
240
  feed.prepend(div);
197
241
  if (feed.children.length > 200) feed.lastChild.remove();
@@ -208,22 +252,33 @@ function renderApprovals() {
208
252
  const list = document.getElementById('approval-list');
209
253
  const records = Array.isArray(currentApprovals) ? currentApprovals : [];
210
254
  if (records.length === 0) {
211
- list.innerHTML = '<div style="text-align:center; padding: 40px; color:var(--muted)">'
212
- + 'No approval records. Tier-3 review is a recorded human acknowledgement; '
213
- + 'authorization is enforced by branch protection, not from here.</div>';
255
+ list.innerHTML =
256
+ '<div style="text-align:center; padding: 40px; color:var(--muted)">' +
257
+ 'No approval records. Tier-3 review is a recorded human acknowledgement; ' +
258
+ 'authorization is enforced by branch protection, not from here.</div>';
214
259
  return;
215
260
  }
216
261
 
217
- const colour = { valid: 'var(--green)', stale: 'var(--purple)', corrupt: 'var(--red)' };
218
- list.innerHTML = records.map(a => {
219
- const hrs = a.hours_remaining;
220
- const life = (hrs === null || hrs === undefined) ? ''
221
- : hrs > 0 ? `${hrs.toFixed(1)}h remaining` : `expired ${Math.abs(hrs).toFixed(1)}h ago`;
222
- const problems = (a.problems || []).length
223
- ? '<ul style="margin:8px 0 0 16px; font-size:11px; color:var(--red)">'
224
- + a.problems.map(x => `<li>${escapeHtml(x)}</li>`).join('') + '</ul>'
225
- : '';
226
- return `
262
+ const colour = {
263
+ valid: 'var(--green)',
264
+ stale: 'var(--purple)',
265
+ corrupt: 'var(--red)',
266
+ };
267
+ list.innerHTML = records
268
+ .map((a) => {
269
+ const hrs = a.hours_remaining;
270
+ const life =
271
+ hrs === null || hrs === undefined
272
+ ? ''
273
+ : hrs > 0
274
+ ? `${hrs.toFixed(1)}h remaining`
275
+ : `expired ${Math.abs(hrs).toFixed(1)}h ago`;
276
+ const problems = (a.problems || []).length
277
+ ? '<ul style="margin:8px 0 0 16px; font-size:11px; color:var(--red)">' +
278
+ a.problems.map((x) => `<li>${escapeHtml(x)}</li>`).join('') +
279
+ '</ul>'
280
+ : '';
281
+ return `
227
282
  <div class="card approval-card">
228
283
  <div class="card-body">
229
284
  <div style="display:flex; justify-content:space-between; align-items:flex-start; margin-bottom:12px;">
@@ -241,33 +296,64 @@ function renderApprovals() {
241
296
  ${problems}
242
297
  </div>
243
298
  </div>`;
244
- }).join('');
299
+ })
300
+ .join('');
245
301
  }
246
302
 
247
303
  function renderTeam(data) {
304
+ // Metrics.getTeamActivity() returns { active, conflicts } (not a flat array), and each
305
+ // `active` entry has email/last_seen/current_task/event fields, not user/action/timestamp.
306
+ // Reading `data.length`/`data.map()` on that object never rendered anything: `.length` is
307
+ // undefined so the empty-state guard below never fired, and `.map()` on a plain object threw,
308
+ // silently swallowed by refreshTeam()'s catch.
248
309
  const list = document.getElementById('team-activity-list');
249
- if (!data || data.length === 0) {
250
- list.innerHTML = '<div style="text-align:center; padding: 40px; color:var(--muted)">No recent team activity</div>';
310
+ const active = Array.isArray(data?.active) ? data.active : [];
311
+ if (active.length === 0) {
312
+ list.innerHTML =
313
+ '<div style="text-align:center; padding: 40px; color:var(--muted)">No recent team activity</div>';
251
314
  return;
252
315
  }
253
- list.innerHTML = data.map(ev => `
316
+ list.innerHTML = active
317
+ .map(
318
+ (ev) => `
254
319
  <div class="activity-row">
255
- <div class="avatar">${(ev.user || '?').charAt(0).toUpperCase()}</div>
320
+ <div class="avatar">${(ev.email || '?').charAt(0).toUpperCase()}</div>
256
321
  <div class="activity-info">
257
- <div><span class="activity-user">${escapeHtml(ev.user)}</span> <span class="activity-action">${escapeHtml(ev.action)}</span></div>
258
- <div class="activity-time">${new Date(ev.timestamp).toLocaleString()}</div>
322
+ <div><span class="activity-user">${escapeHtml(ev.email)}</span> <span class="activity-action">${escapeHtml(ev.current_task || ev.event || '')}</span></div>
323
+ <div class="activity-time">${new Date(ev.last_seen).toLocaleString()}</div>
259
324
  </div>
260
325
  </div>
261
- `).join('');
326
+ `,
327
+ )
328
+ .join('');
262
329
  }
263
330
 
264
331
  // ── Simple Charts Implementation ──────────────────────────────────────────
265
332
  function drawCharts() {
266
- drawCanvasChart('chart-costs', state.costs?.map(c => c.cost) || [], varColor('--accent'));
267
- drawCanvasChart('chart-quality', state.quality?.map(q => q.score) || [], varColor('--green'), 100);
333
+ // refreshData() does `state = data` with the raw /api/metrics response, which is
334
+ // { sessions, avg_quality, avg_cost_usd, ... } — it has never had `costs`/`quality`
335
+ // properties, so state.costs/state.quality were always undefined past the first load
336
+ // and these charts rendered permanently empty. The real per-session series lives at
337
+ // state.sessions[].cost_usd / state.sessions[].quality_score.
338
+ const sessions = state.sessions || [];
339
+ drawCanvasChart(
340
+ 'chart-costs',
341
+ sessions.map((s) => s.cost_usd),
342
+ varColor('--accent'),
343
+ );
344
+ drawCanvasChart(
345
+ 'chart-quality',
346
+ sessions.map((s) => s.quality_score),
347
+ varColor('--green'),
348
+ 100,
349
+ );
268
350
  }
269
351
 
270
- function varColor(name) { return getComputedStyle(document.documentElement).getPropertyValue(name).trim(); }
352
+ function varColor(name) {
353
+ return getComputedStyle(document.documentElement)
354
+ .getPropertyValue(name)
355
+ .trim();
356
+ }
271
357
 
272
358
  function drawCanvasChart(id, data, color, maxVal = null) {
273
359
  const canvas = document.getElementById(id);
@@ -280,27 +366,30 @@ function drawCanvasChart(id, data, color, maxVal = null) {
280
366
 
281
367
  const w = canvas.offsetWidth;
282
368
  const h = canvas.offsetHeight;
283
- ctx.clearRect(0,0,w,h);
284
-
369
+ ctx.clearRect(0, 0, w, h);
370
+
285
371
  if (data.length < 2) return;
286
-
372
+
287
373
  const max = maxVal || Math.max(...data) * 1.2 || 1;
288
374
  const step = w / (data.length - 1);
289
-
375
+
290
376
  ctx.beginPath();
291
377
  ctx.strokeStyle = color;
292
378
  ctx.lineWidth = 2;
293
379
  ctx.lineJoin = 'round';
294
-
380
+
295
381
  data.forEach((val, i) => {
296
382
  const x = i * step;
297
383
  const y = h - (val / max) * h;
298
- if (i === 0) ctx.moveTo(x, y); else ctx.lineTo(x, y);
384
+ if (i === 0) ctx.moveTo(x, y);
385
+ else ctx.lineTo(x, y);
299
386
  });
300
387
  ctx.stroke();
301
388
 
302
389
  // Area fill
303
- ctx.lineTo(w, h); ctx.lineTo(0, h); ctx.closePath();
390
+ ctx.lineTo(w, h);
391
+ ctx.lineTo(0, h);
392
+ ctx.closePath();
304
393
  ctx.fillStyle = color.replace(')', ', 0.1)').replace('rgb', 'rgba');
305
394
  ctx.fill();
306
395
  }
@@ -309,7 +398,7 @@ function drawCanvasChart(id, data, color, maxVal = null) {
309
398
  function renderTemporalTimeline() {
310
399
  const slider = document.getElementById('temporal-slider');
311
400
  const count = document.getElementById('timeline-count');
312
-
401
+
313
402
  if (temporalHistory.length === 0) {
314
403
  slider.max = 0;
315
404
  count.textContent = '0 Snapshots';
@@ -318,11 +407,11 @@ function renderTemporalTimeline() {
318
407
 
319
408
  slider.max = temporalHistory.length - 1;
320
409
  count.textContent = `${temporalHistory.length} Snapshots`;
321
-
410
+
322
411
  // Default to latest if not touching
323
412
  if (slider.value == 0 && temporalHistory.length > 0) {
324
- onSliderChange(temporalHistory.length - 1);
325
- slider.value = temporalHistory.length - 1;
413
+ onSliderChange(temporalHistory.length - 1);
414
+ slider.value = temporalHistory.length - 1;
326
415
  }
327
416
  }
328
417
 
@@ -332,16 +421,19 @@ async function onSliderChange(index) {
332
421
  if (!snap) return;
333
422
 
334
423
  selectedSnapshot = snap;
335
- document.getElementById('slider-current').textContent = `Point: ${snap.id.slice(0, 8)} (${new Date(snap.timestamp).toLocaleTimeString()})`;
424
+ document.getElementById('slider-current').textContent =
425
+ `Point: ${snap.id.slice(0, 8)} (${new Date(snap.timestamp).toLocaleTimeString()})`;
336
426
  document.getElementById('inject-btn').disabled = false;
337
427
 
338
428
  // Fetch sample audit file for this snapshot
339
429
  try {
340
430
  const res = await fetch(`/api/temporal/snapshot/${snap.id}/AUDIT.jsonl`);
341
431
  const content = await res.text();
342
- document.getElementById('snapshot-viewer').textContent = content || 'No audit log available for this point.';
343
- } catch(e) {
344
- document.getElementById('snapshot-viewer').textContent = 'Failed to load snapshot details.';
432
+ document.getElementById('snapshot-viewer').textContent =
433
+ content || 'No audit log available for this point.';
434
+ } catch (e) {
435
+ document.getElementById('snapshot-viewer').textContent =
436
+ 'Failed to load snapshot details.';
345
437
  }
346
438
  }
347
439
 
@@ -349,8 +441,8 @@ async function injectHindsight() {
349
441
  if (!selectedSnapshot) return;
350
442
  const instruction = document.getElementById('steering-input').value;
351
443
  if (!instruction) {
352
- alert('Please provide a steering instruction for the hindsight injection.');
353
- return;
444
+ alert('Please provide a steering instruction for the hindsight injection.');
445
+ return;
354
446
  }
355
447
 
356
448
  const btn = document.getElementById('inject-btn');
@@ -363,50 +455,59 @@ async function injectHindsight() {
363
455
  headers: { 'Content-Type': 'application/json' },
364
456
  body: JSON.stringify({
365
457
  auditId: selectedSnapshot.id,
366
- fixDescription: instruction
367
- })
458
+ fixDescription: instruction,
459
+ }),
368
460
  });
369
461
  const result = await res.json();
370
462
  if (result.success) {
371
- alert('Hindsight Injection Successful. Agent state rolled back and awaiting re-optimization.');
372
- showPage('activity');
463
+ alert(
464
+ 'Hindsight Injection Successful. Agent state rolled back and awaiting re-optimization.',
465
+ );
466
+ showPage('activity');
373
467
  } else {
374
- alert('Injection failed: ' + result.error);
468
+ alert('Injection failed: ' + result.error);
375
469
  }
376
- } catch(e) {
377
- alert('Failed to connect to temporal engine.');
470
+ } catch (e) {
471
+ alert('Failed to connect to temporal engine.');
378
472
  } finally {
379
- btn.disabled = false;
380
- btn.textContent = 'Rewind & Inject Fix';
473
+ btn.disabled = false;
474
+ btn.textContent = 'Rewind & Inject Fix';
381
475
  }
382
476
  }
383
477
 
384
478
  // ── RevOps Logic ─────────────────────────────────────────────────────────
385
479
  function updateRevOpsUI(data) {
386
- const { roi, velocity, debt } = data;
387
-
388
- document.getElementById('rev-roi').textContent = `${roi.roi_percentage}%`;
389
- document.getElementById('rev-net').textContent = `$${roi.net_value}`;
390
- document.getElementById('rev-velocity').innerHTML = `${velocity.avg_seconds_per_task} <span class="stat-unit">sec/task</span>`;
391
- document.getElementById('rev-eta').textContent = `ETA: ${velocity.eta}`;
392
- document.getElementById('rev-health').textContent = debt.security_health_score;
393
-
394
- const healthStatus = document.getElementById('rev-health-status');
395
- healthStatus.textContent = `STATUS: ${debt.governance_status.toUpperCase()}`;
396
- healthStatus.style.background = debt.security_health_score > 80 ? 'rgba(63, 185, 80, 0.15)' : 'rgba(248, 81, 73, 0.15)';
397
- healthStatus.style.color = debt.security_health_score > 80 ? 'var(--green)' : 'var(--red)';
398
-
399
- document.getElementById('roi-hours').textContent = `${roi.hours_saved}h`;
400
- document.getElementById('roi-gross').textContent = `$${roi.gross_value}`;
401
- document.getElementById('roi-burn').textContent = `$${roi.token_cost}`;
402
- document.getElementById('roi-total-pct').textContent = `${roi.roi_percentage}%`;
403
-
404
- document.getElementById('debt-critical').textContent = debt.critical_findings;
405
- document.getElementById('debt-tier3').textContent = debt.tier3_approvals;
406
- const riskBadge = document.getElementById('debt-risk');
407
- riskBadge.textContent = debt.debt_level.toUpperCase();
408
- riskBadge.className = `badge ${debt.debt_level === 'Minimal' ? 'badge-live' : ''}`;
409
- if (debt.debt_level !== 'Minimal') riskBadge.style.color = 'var(--yellow)';
480
+ const { roi, velocity, debt } = data;
481
+
482
+ document.getElementById('rev-roi').textContent = `${roi.roi_percentage}%`;
483
+ document.getElementById('rev-net').textContent = `$${roi.net_value}`;
484
+ document.getElementById('rev-velocity').innerHTML =
485
+ `${velocity.avg_seconds_per_task} <span class="stat-unit">sec/task</span>`;
486
+ document.getElementById('rev-eta').textContent = `ETA: ${velocity.eta}`;
487
+ document.getElementById('rev-health').textContent =
488
+ debt.security_health_score;
489
+
490
+ const healthStatus = document.getElementById('rev-health-status');
491
+ healthStatus.textContent = `STATUS: ${debt.governance_status.toUpperCase()}`;
492
+ healthStatus.style.background =
493
+ debt.security_health_score > 80
494
+ ? 'rgba(63, 185, 80, 0.15)'
495
+ : 'rgba(248, 81, 73, 0.15)';
496
+ healthStatus.style.color =
497
+ debt.security_health_score > 80 ? 'var(--green)' : 'var(--red)';
498
+
499
+ document.getElementById('roi-hours').textContent = `${roi.hours_saved}h`;
500
+ document.getElementById('roi-gross').textContent = `$${roi.gross_value}`;
501
+ document.getElementById('roi-burn').textContent = `$${roi.token_cost}`;
502
+ document.getElementById('roi-total-pct').textContent =
503
+ `${roi.roi_percentage}%`;
504
+
505
+ document.getElementById('debt-critical').textContent = debt.critical_findings;
506
+ document.getElementById('debt-tier3').textContent = debt.tier3_approvals;
507
+ const riskBadge = document.getElementById('debt-risk');
508
+ riskBadge.textContent = debt.debt_level.toUpperCase();
509
+ riskBadge.className = `badge ${debt.debt_level === 'Minimal' ? 'badge-live' : ''}`;
510
+ if (debt.debt_level !== 'Minimal') riskBadge.style.color = 'var(--yellow)';
410
511
  }
411
512
 
412
513
  window.onresize = drawCharts;
@@ -421,9 +522,13 @@ document.querySelectorAll('nav .tab[data-page]').forEach((btn) => {
421
522
  btn.addEventListener('click', () => showPage(btn.dataset.page));
422
523
  });
423
524
  const __slider = document.getElementById('temporal-slider');
424
- if (__slider) __slider.addEventListener('input', (e) => onSliderChange(e.target.value));
525
+ if (__slider)
526
+ __slider.addEventListener('input', (e) => onSliderChange(e.target.value));
425
527
  document.querySelectorAll('[data-action]').forEach((el) => {
426
- const fns = { 'inject-hindsight': () => injectHindsight(), 'close-confirm': () => closeConfirm() };
528
+ const fns = {
529
+ 'inject-hindsight': () => injectHindsight(),
530
+ 'close-confirm': () => closeConfirm(),
531
+ };
427
532
  const fn = fns[el.dataset.action];
428
533
  if (fn) el.addEventListener('click', fn);
429
534
  });
@@ -169,7 +169,7 @@ if (require.main === module) {
169
169
  context: 'CLI Manual Test',
170
170
  category: 'Architecture'
171
171
  });
172
- console.log(' │ 🛡️ SOVEREIGN INTELLIGENCE v8.2.0 — PQAS & Proactive Homing Enabled');
172
+ console.log(' │ 🛡️ SOVEREIGN INTELLIGENCE v8.2.0 — PQAS (simulated) & Proactive Homing');
173
173
  const status = await manager.getStatus();
174
174
  console.log('📊 Learning System Status:', status);
175
175
  } else {