thevoidforge 21.0.0 → 21.0.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.
@@ -0,0 +1,285 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ const FAST_POLL_MS = 5000; // 5s — live feed (context)
5
+ const SLOW_POLL_MS = 60000; // 60s — system status (version, deploy, experiments)
6
+
7
+ // ── Data fetchers ────────────────────────────────
8
+
9
+ async function fetchJSON(url) {
10
+ try {
11
+ const res = await fetch(url, { headers: { 'X-VoidForge-Request': '1' } });
12
+ if (!res.ok) return null;
13
+ return await res.json();
14
+ } catch { return null; }
15
+ }
16
+
17
+ // ── Campaign Timeline ────────────────────────────
18
+
19
+ function renderTimeline(campaignData) {
20
+ const container = document.getElementById('campaign-timeline');
21
+ if (!campaignData || !campaignData.missions) {
22
+ container.innerHTML = '<span style="font-size:12px;color:var(--text-dim)">No campaign active</span>';
23
+ return;
24
+ }
25
+ container.innerHTML = '';
26
+ for (const mission of campaignData.missions) {
27
+ const el = document.createElement('div');
28
+ el.className = 'timeline-item';
29
+ el.textContent = mission.number;
30
+ el.title = mission.name + ' — ' + mission.status;
31
+ switch (mission.status) {
32
+ case 'COMPLETE': el.classList.add('timeline-complete'); break;
33
+ case 'ACTIVE': el.classList.add('timeline-active'); break;
34
+ case 'BLOCKED': el.classList.add('timeline-blocked'); break;
35
+ default: el.classList.add('timeline-pending');
36
+ }
37
+ container.appendChild(el);
38
+ }
39
+ }
40
+
41
+ // ── Phase Pipeline ───────────────────────────────
42
+
43
+ function renderPipeline(phaseData) {
44
+ const container = document.getElementById('phase-pipeline');
45
+ if (!phaseData || !phaseData.phases) {
46
+ container.innerHTML = '<div class="pipeline-phase"><span class="pipeline-dot pending"></span><span class="pipeline-label">No active build</span></div>';
47
+ return;
48
+ }
49
+ container.innerHTML = '';
50
+ for (const phase of phaseData.phases) {
51
+ const el = document.createElement('div');
52
+ el.className = 'pipeline-phase';
53
+ const dot = document.createElement('span');
54
+ dot.className = 'pipeline-dot ' + (phase.status || 'pending');
55
+ const label = document.createElement('span');
56
+ label.className = 'pipeline-label';
57
+ label.textContent = phase.name;
58
+ el.appendChild(dot);
59
+ el.appendChild(label);
60
+ container.appendChild(el);
61
+ }
62
+ }
63
+
64
+ // ── Finding Scoreboard ───────────────────────────
65
+
66
+ function renderScoreboard(findings) {
67
+ document.getElementById('score-critical').textContent = (findings && findings.critical) || 0;
68
+ document.getElementById('score-high').textContent = (findings && findings.high) || 0;
69
+ document.getElementById('score-medium').textContent = (findings && findings.medium) || 0;
70
+ document.getElementById('score-low').textContent = (findings && findings.low) || 0;
71
+ }
72
+
73
+ // ── Context Gauge ────────────────────────────────
74
+
75
+ function renderGauge(usage) {
76
+ const fill = document.getElementById('gauge-fill');
77
+ const text = document.getElementById('gauge-text');
78
+ const gauge = document.getElementById('context-gauge');
79
+ if (!usage) {
80
+ text.textContent = '\u2014%';
81
+ fill.style.strokeDashoffset = 88;
82
+ if (gauge) gauge.removeAttribute('aria-valuenow');
83
+ return;
84
+ }
85
+ const pct = Math.round(usage.percent);
86
+ // Circle circumference = 2 * PI * r = 2 * 3.14159 * 14 ≈ 88
87
+ const offset = 88 - (88 * pct / 100);
88
+ fill.style.strokeDashoffset = offset;
89
+ // Color: green <50, yellow 50-70, red >70
90
+ if (pct < 50) fill.setAttribute('stroke', '#34d399');
91
+ else if (pct < 70) fill.setAttribute('stroke', '#fbbf24');
92
+ else fill.setAttribute('stroke', '#ef4444');
93
+ text.textContent = pct + '%';
94
+ if (gauge) gauge.setAttribute('aria-valuenow', pct);
95
+ }
96
+
97
+ // ── Version & Branch ─────────────────────────────
98
+
99
+ function renderVersion(versionData) {
100
+ document.getElementById('version-badge').textContent = versionData ? ('v' + versionData.version) : '—';
101
+ document.getElementById('version-display').textContent = versionData ? ('VoidForge v' + versionData.version) : 'VoidForge';
102
+ document.getElementById('branch-status').textContent = versionData ? versionData.branch : '—';
103
+ }
104
+
105
+ // ── Deploy Status ────────────────────────────────
106
+
107
+ function renderDeploy(deployData) {
108
+ const container = document.getElementById('deploy-status');
109
+ if (!deployData || !deployData.url) {
110
+ container.innerHTML = '<span class="deploy-dot unknown"></span><span>No deploy data</span>';
111
+ return;
112
+ }
113
+ const dotClass = deployData.healthy ? 'live' : 'down';
114
+ container.innerHTML = '';
115
+ const dot = document.createElement('span');
116
+ dot.className = 'deploy-dot ' + dotClass;
117
+ const label = document.createElement('span');
118
+ label.textContent = deployData.url;
119
+ container.appendChild(dot);
120
+ container.appendChild(label);
121
+ }
122
+
123
+ // ── Agent Activity Ticker ────────────────────────
124
+
125
+ const tickerMessages = [];
126
+ const MAX_TICKER = 10;
127
+
128
+ function addTickerMessage(agent, action) {
129
+ tickerMessages.unshift({ agent, action, time: Date.now() });
130
+ if (tickerMessages.length > MAX_TICKER) tickerMessages.pop();
131
+ renderTicker();
132
+ }
133
+
134
+ function renderTicker() {
135
+ const container = document.getElementById('agent-ticker');
136
+ if (tickerMessages.length === 0) {
137
+ container.innerHTML = '<span class="ticker-item"><span class="ticker-agent">Sisko</span> standing by...</span>';
138
+ return;
139
+ }
140
+ container.innerHTML = tickerMessages.map(m =>
141
+ `<span class="ticker-item"><span class="ticker-agent">${escapeHtml(m.agent)}</span> ${escapeHtml(m.action)}</span>`
142
+ ).join('');
143
+ }
144
+
145
+ function escapeHtml(str) {
146
+ const div = document.createElement('div');
147
+ div.appendChild(document.createTextNode(str));
148
+ return div.innerHTML;
149
+ }
150
+
151
+ // ── PRD Coverage ─────────────────────────────────
152
+
153
+ function renderPrdCoverage(coverage) {
154
+ const container = document.getElementById('prd-coverage');
155
+ if (!coverage || !coverage.sections) {
156
+ container.textContent = 'No campaign active';
157
+ return;
158
+ }
159
+ const complete = coverage.sections.filter(s => s.status === 'COMPLETE').length;
160
+ const total = coverage.sections.length;
161
+ const pct = total > 0 ? Math.round(complete / total * 100) : 0;
162
+ container.innerHTML = `<div style="margin-bottom:6px">${complete}/${total} sections (${pct}%)</div>` +
163
+ `<div style="height:6px;background:var(--border);border-radius:3px;overflow:hidden">` +
164
+ `<div style="height:100%;width:${pct}%;background:#34d399;border-radius:3px;transition:width 0.5s"></div></div>`;
165
+ }
166
+
167
+ // ── Test Suite ───────────────────────────────────
168
+
169
+ function renderTests(testData) {
170
+ const container = document.getElementById('test-status');
171
+ if (!testData) { container.textContent = 'No test data'; return; }
172
+ container.innerHTML =
173
+ `<span style="color:#34d399">${testData.pass || 0} pass</span> · ` +
174
+ `<span style="color:#ef4444">${testData.fail || 0} fail</span> · ` +
175
+ `<span style="color:var(--text-dim)">${testData.skip || 0} skip</span>`;
176
+ }
177
+
178
+ // ── Experiment Dashboard ──────────────────────────
179
+
180
+ function renderExperiments(data) {
181
+ const container = document.getElementById('experiment-dashboard');
182
+ if (!data || !data.experiments || data.experiments.length === 0) {
183
+ container.textContent = 'No experiments';
184
+ return;
185
+ }
186
+ var complete = data.experiments.filter(function(e) { return e.status === 'complete'; }).length;
187
+ var running = data.experiments.filter(function(e) { return e.status === 'running'; }).length;
188
+ var planned = data.experiments.filter(function(e) { return e.status === 'planned'; }).length;
189
+ container.innerHTML =
190
+ '<span style="color:#34d399">' + complete + ' complete</span> · ' +
191
+ '<span style="color:#fbbf24">' + running + ' running</span> · ' +
192
+ '<span style="color:var(--text-dim)">' + planned + ' planned</span>';
193
+ }
194
+
195
+ // ── Tiered poll loops (v13.0 — fast for live data, slow for system status) ──
196
+
197
+ async function refreshFast() {
198
+ const [context] = await Promise.all([
199
+ fetchJSON('/api/war-room/context'),
200
+ ]);
201
+ renderGauge(context);
202
+ }
203
+
204
+ async function refreshCampaign() {
205
+ const [campaign, build, findings] = await Promise.all([
206
+ fetchJSON('/api/war-room/campaign'),
207
+ fetchJSON('/api/war-room/build'),
208
+ fetchJSON('/api/war-room/findings'),
209
+ ]);
210
+ renderTimeline(campaign);
211
+ renderPipeline(build);
212
+ renderScoreboard(findings);
213
+ renderPrdCoverage(campaign);
214
+ if (typeof window.renderProphecyGraph === 'function') {
215
+ window.renderProphecyGraph(document.getElementById('prophecy-graph'), campaign);
216
+ }
217
+ }
218
+
219
+ async function refreshSlow() {
220
+ const [version, deploy, experiments] = await Promise.all([
221
+ fetchJSON('/api/war-room/version'),
222
+ fetchJSON('/api/war-room/deploy'),
223
+ fetchJSON('/api/war-room/experiments'),
224
+ ]);
225
+ renderVersion(version);
226
+ renderDeploy(deploy);
227
+ renderExperiments(experiments);
228
+ }
229
+
230
+ async function refresh() {
231
+ await Promise.all([refreshFast(), refreshCampaign(), refreshSlow()]);
232
+ }
233
+
234
+ // ── WebSocket for real-time updates ──────────────
235
+
236
+ var wsRetryDelay = 1000;
237
+ var WS_MAX_RETRY_DELAY = 30000;
238
+
239
+ function connectWebSocket() {
240
+ var wsProtocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
241
+ var ws = new WebSocket(wsProtocol + '//' + location.host + '/ws/war-room');
242
+
243
+ ws.onopen = function () {
244
+ wsRetryDelay = 1000;
245
+ refresh(); // Full refresh on reconnect — data may be stale (Infinity Gauntlet B-002)
246
+ };
247
+
248
+ ws.onmessage = function (event) {
249
+ try {
250
+ var msg = JSON.parse(event.data);
251
+ if (msg.type === 'agent-activity') {
252
+ addTickerMessage(msg.agent, msg.action);
253
+ } else if (msg.type === 'finding') {
254
+ var el = document.getElementById('score-' + msg.severity);
255
+ if (el) el.textContent = parseInt(el.textContent) + 1;
256
+ } else if (msg.type === 'phase-update') {
257
+ refresh();
258
+ }
259
+ } catch { /* ignore malformed messages */ }
260
+ };
261
+
262
+ ws.onerror = function () {
263
+ // Error fires before close — logged for debugging, close handles reconnect
264
+ };
265
+
266
+ ws.onclose = function () {
267
+ // Retry with ceiling — stop after 2 minutes of failure (Infinity Gauntlet B-001)
268
+ if (wsRetryDelay >= WS_MAX_RETRY_DELAY * 4) return; // permanent failure — stop retrying
269
+ setTimeout(connectWebSocket, wsRetryDelay);
270
+ wsRetryDelay = Math.min(wsRetryDelay * 2, WS_MAX_RETRY_DELAY);
271
+ };
272
+ }
273
+
274
+ // ── Init ─────────────────────────────────────────
275
+
276
+ async function init() {
277
+ await refresh();
278
+ setInterval(refreshFast, FAST_POLL_MS);
279
+ setInterval(refreshCampaign, 10000);
280
+ setInterval(refreshSlow, SLOW_POLL_MS);
281
+ connectWebSocket();
282
+ }
283
+
284
+ init();
285
+ })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "thevoidforge",
3
- "version": "21.0.0",
3
+ "version": "21.0.1",
4
4
  "description": "From nothing, everything. A methodology framework for building with Claude Code.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -12,7 +12,7 @@
12
12
  "scripts": {
13
13
  "wizard": "npx tsx scripts/voidforge.ts init",
14
14
  "deploy": "npx tsx scripts/voidforge.ts deploy",
15
- "build": "tsc",
15
+ "build": "tsc && bash scripts/copy-assets.sh",
16
16
  "prepack": "bash scripts/prepack-patterns.sh && npm run build",
17
17
  "typecheck": "npx tsc --noEmit",
18
18
  "test": "vitest run --pool forks",