tryassay 0.21.2 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/demo/.claude/.truth_last_prompt +1 -0
  2. package/demo/.claude/truth_status +1 -0
  3. package/demo/css/style.css +840 -0
  4. package/demo/index.html +78 -0
  5. package/demo/js/chat.js +535 -0
  6. package/demo/js/code-panel.js +206 -0
  7. package/demo/js/preview.js +456 -0
  8. package/demo/js/sse-client.js +600 -0
  9. package/demo/js/state.js +172 -0
  10. package/demo/js/timeline.js +80 -0
  11. package/dist/api/server.d.ts +3 -0
  12. package/dist/api/server.js +127 -20
  13. package/dist/api/server.js.map +1 -1
  14. package/dist/cli.js +11 -0
  15. package/dist/cli.js.map +1 -1
  16. package/dist/commands/assess.d.ts +2 -0
  17. package/dist/commands/assess.js +132 -164
  18. package/dist/commands/assess.js.map +1 -1
  19. package/dist/commands/demo.d.ts +5 -0
  20. package/dist/commands/demo.js +357 -0
  21. package/dist/commands/demo.js.map +1 -0
  22. package/dist/lib/__tests__/arithmetic-quick-test.d.ts +6 -0
  23. package/dist/lib/__tests__/arithmetic-quick-test.js +197 -0
  24. package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -0
  25. package/dist/lib/__tests__/arithmetic-real-llm-test.d.ts +13 -0
  26. package/dist/lib/__tests__/arithmetic-real-llm-test.js +284 -0
  27. package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -0
  28. package/dist/lib/__tests__/arithmetic-value-demo.d.ts +10 -0
  29. package/dist/lib/__tests__/arithmetic-value-demo.js +193 -0
  30. package/dist/lib/__tests__/arithmetic-value-demo.js.map +1 -0
  31. package/dist/lib/__tests__/flow-to-claims.test.d.ts +1 -0
  32. package/dist/lib/__tests__/flow-to-claims.test.js +91 -0
  33. package/dist/lib/__tests__/flow-to-claims.test.js.map +1 -0
  34. package/dist/lib/__tests__/formal-verifier-api-misuse.test.d.ts +9 -0
  35. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +391 -0
  36. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -0
  37. package/dist/lib/__tests__/formal-verifier-arithmetic.test.d.ts +7 -0
  38. package/dist/lib/__tests__/formal-verifier-arithmetic.test.js +318 -0
  39. package/dist/lib/__tests__/formal-verifier-arithmetic.test.js.map +1 -0
  40. package/dist/lib/__tests__/intent-extractor.test.d.ts +1 -0
  41. package/dist/lib/__tests__/intent-extractor.test.js +97 -0
  42. package/dist/lib/__tests__/intent-extractor.test.js.map +1 -0
  43. package/dist/lib/__tests__/intent-reviewer.test.d.ts +1 -0
  44. package/dist/lib/__tests__/intent-reviewer.test.js +55 -0
  45. package/dist/lib/__tests__/intent-reviewer.test.js.map +1 -0
  46. package/dist/lib/__tests__/mr-gsm8k-benchmark.d.ts +11 -0
  47. package/dist/lib/__tests__/mr-gsm8k-benchmark.js +224 -0
  48. package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -0
  49. package/dist/lib/anthropic.js +25 -33
  50. package/dist/lib/anthropic.js.map +1 -1
  51. package/dist/lib/assessment-reporter.js +9 -13
  52. package/dist/lib/assessment-reporter.js.map +1 -1
  53. package/dist/lib/claim-extractor.js +10 -19
  54. package/dist/lib/claim-extractor.js.map +1 -1
  55. package/dist/lib/code-verifier.js +16 -36
  56. package/dist/lib/code-verifier.js.map +1 -1
  57. package/dist/lib/constraint-engine.js +10 -19
  58. package/dist/lib/constraint-engine.js.map +1 -1
  59. package/dist/lib/formal-verifier.d.ts +1 -1
  60. package/dist/lib/formal-verifier.js +454 -0
  61. package/dist/lib/formal-verifier.js.map +1 -1
  62. package/dist/lib/guided-generator.js +19 -37
  63. package/dist/lib/guided-generator.js.map +1 -1
  64. package/dist/lib/intent-extractor.d.ts +47 -0
  65. package/dist/lib/intent-extractor.js +427 -0
  66. package/dist/lib/intent-extractor.js.map +1 -0
  67. package/dist/lib/intent-reviewer.d.ts +14 -0
  68. package/dist/lib/intent-reviewer.js +148 -0
  69. package/dist/lib/intent-reviewer.js.map +1 -0
  70. package/dist/lib/intent-types.d.ts +89 -0
  71. package/dist/lib/intent-types.js +5 -0
  72. package/dist/lib/intent-types.js.map +1 -0
  73. package/dist/lib/inventory-extractor.js +9 -22
  74. package/dist/lib/inventory-extractor.js.map +1 -1
  75. package/dist/lib/llm-provider.d.ts +23 -0
  76. package/dist/lib/llm-provider.js +130 -0
  77. package/dist/lib/llm-provider.js.map +1 -0
  78. package/dist/lib/remediator.js +20 -28
  79. package/dist/lib/remediator.js.map +1 -1
  80. package/dist/lib/requirements-generator.js +14 -19
  81. package/dist/lib/requirements-generator.js.map +1 -1
  82. package/dist/lib/spec-synthesizer.js +10 -19
  83. package/dist/lib/spec-synthesizer.js.map +1 -1
  84. package/dist/runtime/agents/planner-agent.d.ts +5 -2
  85. package/dist/runtime/agents/planner-agent.js +232 -1
  86. package/dist/runtime/agents/planner-agent.js.map +1 -1
  87. package/dist/runtime/app-create-orchestrator.d.ts +9 -1
  88. package/dist/runtime/app-create-orchestrator.js +265 -87
  89. package/dist/runtime/app-create-orchestrator.js.map +1 -1
  90. package/dist/runtime/check-catalog.js +5 -3
  91. package/dist/runtime/check-catalog.js.map +1 -1
  92. package/dist/runtime/check-definitions.d.ts +10 -0
  93. package/dist/runtime/check-definitions.js +52 -2
  94. package/dist/runtime/check-definitions.js.map +1 -1
  95. package/dist/runtime/composition-verifier.js +8 -12
  96. package/dist/runtime/composition-verifier.js.map +1 -1
  97. package/dist/runtime/gap-detector.js +8 -10
  98. package/dist/runtime/gap-detector.js.map +1 -1
  99. package/dist/runtime/input-validator.d.ts +7 -0
  100. package/dist/runtime/input-validator.js +162 -0
  101. package/dist/runtime/input-validator.js.map +1 -0
  102. package/dist/runtime/model-router.d.ts +10 -0
  103. package/dist/runtime/model-router.js +42 -0
  104. package/dist/runtime/model-router.js.map +1 -0
  105. package/dist/runtime/pattern-extractor.js +8 -10
  106. package/dist/runtime/pattern-extractor.js.map +1 -1
  107. package/dist/runtime/planner.js +11 -16
  108. package/dist/runtime/planner.js.map +1 -1
  109. package/dist/runtime/prompt-guard.d.ts +2 -0
  110. package/dist/runtime/prompt-guard.js +180 -0
  111. package/dist/runtime/prompt-guard.js.map +1 -0
  112. package/dist/runtime/prompt-safety-analyzer.js +8 -13
  113. package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
  114. package/dist/runtime/reasoner.js +19 -33
  115. package/dist/runtime/reasoner.js.map +1 -1
  116. package/dist/runtime/rule-meta-verifier.js +9 -11
  117. package/dist/runtime/rule-meta-verifier.js.map +1 -1
  118. package/dist/runtime/safe-executor.d.ts +23 -0
  119. package/dist/runtime/safe-executor.js +151 -0
  120. package/dist/runtime/safe-executor.js.map +1 -0
  121. package/dist/runtime/specialized-agent.js +10 -14
  122. package/dist/runtime/specialized-agent.js.map +1 -1
  123. package/dist/runtime/strategy-library.js +8 -10
  124. package/dist/runtime/strategy-library.js.map +1 -1
  125. package/dist/runtime/supabase-experience-store.js.map +1 -1
  126. package/dist/runtime/supabase-provisioner.d.ts +35 -0
  127. package/dist/runtime/supabase-provisioner.js +192 -0
  128. package/dist/runtime/supabase-provisioner.js.map +1 -0
  129. package/dist/runtime/types.d.ts +116 -0
  130. package/dist/sdk/forward-verify.js +16 -33
  131. package/dist/sdk/forward-verify.js.map +1 -1
  132. package/package.json +2 -1
@@ -0,0 +1,600 @@
1
+ // sse-client.js — EventSource to AppState bridge
2
+ // Connects to the Assay API SSE stream and maps events to state updates
3
+
4
+ import { configureChat, addArchitectMessage, lockChat, unlockChat } from './chat.js';
5
+ import { downloadProject, collectProjectFiles } from './preview.js';
6
+
7
+ const AppState = window.AppState;
8
+
9
+ let eventSource = null;
10
+ let apiBase = '';
11
+ let apiKey = '';
12
+
13
+ function autoApprovePlan(appId) {
14
+ const headers = { 'Content-Type': 'application/json' };
15
+ if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
16
+ fetch(`${apiBase}/api/v1/app/${appId}/approve`, {
17
+ method: 'POST',
18
+ headers,
19
+ body: JSON.stringify({ decision: 'approved' }),
20
+ }).then(res => {
21
+ if (res.ok) {
22
+ AppState.addLog({ level: 'info', message: 'Plan approved — building...' });
23
+ } else {
24
+ console.warn('[SSE] Auto-approve failed:', res.status);
25
+ }
26
+ }).catch(err => {
27
+ console.error('[SSE] Auto-approve error:', err);
28
+ });
29
+ }
30
+
31
+ function showCompletionOverlay(data) {
32
+ const overlay = document.getElementById('completion-overlay');
33
+ if (!overlay) return;
34
+
35
+ const m = AppState.get().metrics;
36
+ const appName = data.appName || 'Application';
37
+ const isSuccess = data.status === 'completed';
38
+ const status = isSuccess ? 'Build Complete' : 'Build Finished with Issues';
39
+ const statusClass = isSuccess ? 'success' : 'partial';
40
+
41
+ const clockVal = document.getElementById('clock-value');
42
+ const totalTime = clockVal ? clockVal.textContent : '';
43
+
44
+ overlay.innerHTML = `
45
+ <div class="completion-card">
46
+ <button class="completion-close-btn" id="completion-close" title="Close">\u00d7</button>
47
+ <div class="completion-icon ${statusClass}">${isSuccess ? '\u2713' : '\u26a0'}</div>
48
+ <div class="completion-header">${status}</div>
49
+ <div class="completion-app-name">${appName}</div>
50
+ <div class="completion-metrics">
51
+ <span>${m.filesGenerated} files</span>
52
+ <span>${m.claimsVerified} claims verified</span>
53
+ <span>${m.claimsFailed} caught</span>
54
+ ${totalTime ? `<span>${totalTime} total</span>` : ''}
55
+ </div>
56
+ <div class="completion-actions">
57
+ <button class="completion-action-btn completion-download-btn" id="completion-download">
58
+ <span class="completion-action-icon">\u2913</span>
59
+ Download Project (.zip)
60
+ </button>
61
+ <button class="completion-action-btn completion-github-btn" id="completion-github">
62
+ <span class="completion-action-icon">\u2197</span>
63
+ Push to GitHub
64
+ </button>
65
+ </div>
66
+ <div class="completion-status" id="completion-status"></div>
67
+ </div>
68
+ `;
69
+ overlay.classList.add('visible');
70
+
71
+ // Close button — dismisses modal, reveals preview + chat
72
+ document.getElementById('completion-close')?.addEventListener('click', () => {
73
+ overlay.classList.remove('visible');
74
+ });
75
+
76
+ // Download button — calls existing zip download from preview.js
77
+ document.getElementById('completion-download')?.addEventListener('click', () => {
78
+ downloadProject();
79
+ });
80
+
81
+ // GitHub button — starts GitHub export flow
82
+ document.getElementById('completion-github')?.addEventListener('click', () => {
83
+ startGitHubExport(appName);
84
+ });
85
+ }
86
+
87
+ /**
88
+ * GitHub export flow:
89
+ * 1. Check if user is authenticated via GitHub OAuth
90
+ * 2. If not, open OAuth popup
91
+ * 3. Once authenticated, show repo form and push
92
+ */
93
+ async function startGitHubExport(appName) {
94
+ const statusEl = document.getElementById('completion-status');
95
+ const githubBtn = document.getElementById('completion-github');
96
+
97
+ // GitHub auth endpoints live on the UI server (same origin), not the API server
98
+ const uiOrigin = window.location.origin;
99
+
100
+ // Check auth status
101
+ if (statusEl) statusEl.textContent = 'Checking GitHub connection...';
102
+
103
+ try {
104
+ const res = await fetch(`${uiOrigin}/api/v1/auth/github/status`);
105
+ const authData = await res.json();
106
+
107
+ if (!authData.configured) {
108
+ if (statusEl) statusEl.textContent = 'GitHub not configured. Set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET env vars, then restart the demo.';
109
+ return;
110
+ }
111
+
112
+ if (authData.authenticated) {
113
+ showGitHubForm(appName, authData.username);
114
+ } else {
115
+ // Open OAuth popup
116
+ if (statusEl) statusEl.textContent = 'Opening GitHub authorization...';
117
+ const popup = window.open(
118
+ `${uiOrigin}/api/v1/auth/github`,
119
+ 'github-oauth',
120
+ 'width=600,height=700,menubar=no,toolbar=no'
121
+ );
122
+
123
+ // Poll for auth completion
124
+ const pollAuth = setInterval(async () => {
125
+ try {
126
+ if (popup && popup.closed) {
127
+ clearInterval(pollAuth);
128
+ const checkRes = await fetch(`${uiOrigin}/api/v1/auth/github/status`);
129
+ const checkData = await checkRes.json();
130
+ if (checkData.authenticated) {
131
+ showGitHubForm(appName, checkData.username);
132
+ } else {
133
+ if (statusEl) statusEl.textContent = 'GitHub authorization cancelled. You can still download the zip.';
134
+ }
135
+ return;
136
+ }
137
+ } catch { /* keep polling */ }
138
+ }, 1000);
139
+ }
140
+ } catch (err) {
141
+ console.error('[GitHub] Auth check failed:', err);
142
+ if (statusEl) statusEl.textContent = 'GitHub connection unavailable. Download the zip instead.';
143
+ }
144
+ }
145
+
146
+ function showGitHubForm(appName, username) {
147
+ const statusEl = document.getElementById('completion-status');
148
+ if (!statusEl) return;
149
+
150
+ const slugName = appName
151
+ .toLowerCase()
152
+ .replace(/[^a-z0-9]+/g, '-')
153
+ .replace(/^-|-$/g, '') || 'my-app';
154
+
155
+ statusEl.innerHTML = `
156
+ <div class="github-form">
157
+ <div class="github-form-row">
158
+ <label>Repository</label>
159
+ <input type="text" id="github-repo-name" value="${slugName}" placeholder="repo-name">
160
+ </div>
161
+ <div class="github-form-row">
162
+ <label>Visibility</label>
163
+ <label class="github-toggle">
164
+ <input type="checkbox" id="github-private" checked>
165
+ <span>Private</span>
166
+ </label>
167
+ </div>
168
+ <div class="github-form-row">
169
+ <span class="github-user">Pushing as ${username}</span>
170
+ <button class="completion-action-btn completion-github-push-btn" id="github-push">Create & Push</button>
171
+ </div>
172
+ </div>
173
+ `;
174
+
175
+ document.getElementById('github-push')?.addEventListener('click', () => {
176
+ executeGitHubPush();
177
+ });
178
+ }
179
+
180
+ async function executeGitHubPush() {
181
+ const repoNameEl = document.getElementById('github-repo-name');
182
+ const privateEl = document.getElementById('github-private');
183
+ const pushBtn = document.getElementById('github-push');
184
+ const statusEl = document.getElementById('completion-status');
185
+
186
+ if (!repoNameEl) return;
187
+
188
+ const repoName = repoNameEl.value.trim();
189
+ if (!repoName) return;
190
+
191
+ const isPrivate = privateEl?.checked ?? true;
192
+
193
+ if (pushBtn) {
194
+ pushBtn.disabled = true;
195
+ pushBtn.textContent = 'Creating repo...';
196
+ }
197
+
198
+ try {
199
+ // Collect files from WebContainer
200
+ const files = await collectProjectFiles();
201
+ if (files.length === 0) {
202
+ if (statusEl) statusEl.innerHTML = '<span class="github-error">No files found in project.</span>';
203
+ return;
204
+ }
205
+
206
+ if (pushBtn) pushBtn.textContent = `Pushing ${files.length} files...`;
207
+
208
+ // Export endpoint lives on the UI server (same origin)
209
+ const uiOrigin = window.location.origin;
210
+ const res = await fetch(`${uiOrigin}/api/v1/app/${window.__appId}/export/github`, {
211
+ method: 'POST',
212
+ headers: { 'Content-Type': 'application/json' },
213
+ body: JSON.stringify({ repoName, private: isPrivate, files }),
214
+ });
215
+
216
+ const result = await res.json();
217
+
218
+ if (result.success && result.repoUrl) {
219
+ if (statusEl) {
220
+ statusEl.innerHTML = `
221
+ <div class="github-success">
222
+ Pushed to <a href="${result.repoUrl}" target="_blank" class="github-link">${result.repoUrl}</a>
223
+ </div>
224
+ `;
225
+ }
226
+ } else {
227
+ if (statusEl) {
228
+ statusEl.innerHTML = `<span class="github-error">${result.error || 'Push failed. Try downloading the zip instead.'}</span>`;
229
+ }
230
+ }
231
+ } catch (err) {
232
+ console.error('[GitHub] Push failed:', err);
233
+ if (statusEl) {
234
+ statusEl.innerHTML = `<span class="github-error">Push failed: ${err.message}</span>`;
235
+ }
236
+ }
237
+
238
+ if (pushBtn) {
239
+ pushBtn.disabled = false;
240
+ pushBtn.textContent = 'Create & Push';
241
+ }
242
+ }
243
+
244
+ /**
245
+ * Append a compact activity item to the chat messages panel.
246
+ * Used during build to show real-time progress.
247
+ */
248
+ function appendActivityItem(icon, text, className) {
249
+ const messagesEl = document.getElementById('chat-messages');
250
+ if (!messagesEl) return;
251
+
252
+ const item = document.createElement('div');
253
+ item.className = `activity-item ${className || ''}`;
254
+
255
+ const iconSpan = document.createElement('span');
256
+ iconSpan.className = 'activity-icon';
257
+ iconSpan.textContent = icon;
258
+ item.appendChild(iconSpan);
259
+
260
+ const textSpan = document.createElement('span');
261
+ textSpan.className = 'activity-text';
262
+ textSpan.textContent = text;
263
+ item.appendChild(textSpan);
264
+
265
+ messagesEl.appendChild(item);
266
+ messagesEl.scrollTop = messagesEl.scrollHeight;
267
+ }
268
+
269
+ // Track whether completion overlay has been shown
270
+ let completionShown = false;
271
+
272
+ function handleSSEEvent(type, data) {
273
+ switch (type) {
274
+ case 'connected': {
275
+ console.log('[SSE] Stream connected:', data);
276
+ AppState.addLog({ level: 'info', message: `Connected to session: ${data.sessionId || 'unknown'}` });
277
+ completionShown = false;
278
+ break;
279
+ }
280
+ case 'progress': {
281
+ // The orchestrator emits AppCreateProgress with a phase object
282
+ const phase = data.phase;
283
+ const phaseName = typeof phase === 'string' ? phase : phase?.phase;
284
+ if (phaseName) {
285
+ AppState.update({ phase: phaseName });
286
+
287
+ // Activity feed: show phase changes
288
+ const featureName = phase.featureName || phase.featureId || data.currentFeature;
289
+ const phaseLabel = phaseName.replace(/_/g, ' ');
290
+ if (phaseName === 'provisioning_supabase') {
291
+ appendActivityItem('\u2601', 'Provisioning Supabase database...', 'activity-phase');
292
+ } else if (phaseName === 'supabase_migrations') {
293
+ appendActivityItem('\u2601', 'Pushing database migrations...', 'activity-phase');
294
+ } else if (featureName) {
295
+ appendActivityItem('\u25b6', `${phaseLabel} \u2014 ${featureName}`, 'activity-phase');
296
+ } else if (!['completed', 'failed'].includes(phaseName)) {
297
+ appendActivityItem('\u25b6', phaseLabel, 'activity-phase');
298
+ }
299
+
300
+ // Extract feature info for voice narration
301
+ if (phase.featureId || phase.featureName || data.currentFeature) {
302
+ AppState.update({
303
+ currentTask: {
304
+ name: phase.featureName || phase.featureId || data.currentFeature,
305
+ status: 'in_progress'
306
+ }
307
+ });
308
+ }
309
+
310
+ // If phase reports counts, update metrics
311
+ if (data.totalFeatures) {
312
+ AppState.update({
313
+ metrics: {
314
+ totalTasks: data.totalFeatures,
315
+ tasksComplete: data.completedFeatures?.length || 0,
316
+ }
317
+ });
318
+ }
319
+
320
+ // Fallback: if progress says completed/failed but we haven't shown the overlay yet,
321
+ // wait briefly for the 'complete' event, then show overlay from whatever data we have
322
+ if ((phaseName === 'completed' || phaseName === 'failed') && !completionShown) {
323
+ setTimeout(() => {
324
+ if (!completionShown) {
325
+ console.log('[SSE] Showing completion overlay from progress fallback');
326
+ unlockChat();
327
+ showCompletionOverlay({
328
+ status: phaseName,
329
+ appName: data.appName || data.currentFeature || 'Application',
330
+ });
331
+ completionShown = true;
332
+ disconnect();
333
+ }
334
+ }, 2000);
335
+ }
336
+ }
337
+ break;
338
+ }
339
+ case 'task_update': {
340
+ const existing = AppState.get().tasks.find(t => t.id === data.id);
341
+ if (existing) {
342
+ AppState.updateTask(data.id, data);
343
+ } else {
344
+ AppState.addTask(data);
345
+ }
346
+ // Activity feed: show task completion
347
+ if (data.status === 'completed' && data.claims) {
348
+ const passed = data.claims.passed || data.claims.totalClaims - (data.claims.failed || 0) || 0;
349
+ const failed = data.claims.failed || 0;
350
+ appendActivityItem('\u2713', `${data.name || data.id} (${passed} claims, ${failed} failed)`, failed > 0 ? 'activity-warn' : 'activity-success');
351
+ }
352
+ // Extract per-feature claim counts into verification metrics
353
+ if (data.claims) {
354
+ const c = data.claims;
355
+ const passed = c.passed || c.totalClaims - (c.failed || 0) || 0;
356
+ const failed = c.failed || 0;
357
+ for (let i = 0; i < passed; i++) {
358
+ AppState.addVerification({ claim: `${data.name || data.id}: claim ${i + 1}`, result: 'pass', method: 'formal' });
359
+ }
360
+ for (let i = 0; i < failed; i++) {
361
+ AppState.addVerification({ claim: `${data.name || data.id}: failed claim ${i + 1}`, result: 'fail', method: 'formal' });
362
+ }
363
+ }
364
+ break;
365
+ }
366
+ case 'verification': {
367
+ // Activity feed: show verification results
368
+ if (data.passed_count !== undefined || data.failed_count !== undefined) {
369
+ const p = data.passed_count || 0;
370
+ const f = data.failed_count || 0;
371
+ appendActivityItem('\u2714', `Verification: ${p} passed, ${f} failed`, f > 0 ? 'activity-warn' : 'activity-success');
372
+ }
373
+ // Add the phase-level verification result
374
+ AppState.addVerification({
375
+ claim: data.claim,
376
+ result: data.passed ? 'pass' : 'fail',
377
+ method: data.method || data.verification_method || 'llm'
378
+ });
379
+ // If it includes passed/failed counts, synthesize individual claim metrics
380
+ if (data.passed_count > 0 || data.failed_count > 0) {
381
+ for (let i = 0; i < (data.passed_count || 0); i++) {
382
+ AppState.addVerification({ claim: `${data.phase}: check ${i + 1}`, result: 'pass', method: 'formal' });
383
+ }
384
+ for (let i = 0; i < (data.failed_count || 0); i++) {
385
+ AppState.addVerification({ claim: `${data.phase}: failed check ${i + 1}`, result: 'fail', method: 'formal' });
386
+ }
387
+ }
388
+ break;
389
+ }
390
+ case 'code_generated': {
391
+ AppState.addCodeFile({
392
+ path: data.path,
393
+ language: data.language || 'typescript',
394
+ content: data.content
395
+ });
396
+ // Activity feed: show generated file
397
+ appendActivityItem('+', data.path, 'activity-file');
398
+ const previewPanel = document.getElementById('preview-panel');
399
+ if (previewPanel && !previewPanel.classList.contains('live')) {
400
+ previewPanel.classList.add('live');
401
+ }
402
+ break;
403
+ }
404
+ case 'agent_status': {
405
+ const agents = Array.isArray(data.agents) ? data.agents : (data.name ? [data] : []);
406
+ for (const agent of agents) {
407
+ const existing = AppState.get().agents.find(a => a.name === agent.name);
408
+ if (existing) {
409
+ AppState.updateAgent(agent.name, agent);
410
+ } else {
411
+ AppState.addAgent(agent);
412
+ }
413
+ }
414
+ break;
415
+ }
416
+ case 'plan_questions': {
417
+ AppState.update({ phase: 'plan_questioning' });
418
+ AppState.addLog({ level: 'info', message: `Requirements round ${data.round}: ${data.questions.length} questions (${Math.round(data.confidence * 100)}% confidence)` });
419
+ // Questions now handled via chat_message events
420
+ break;
421
+ }
422
+ case 'plan_readiness': {
423
+ if (data.ready) {
424
+ AppState.addLog({ level: 'success', message: `Requirements confirmed (${Math.round(data.confidence * 100)}% confidence): ${data.summary}` });
425
+ } else {
426
+ const gapList = data.gaps.length > 0 ? data.gaps.join(', ') : 'unclear';
427
+ AppState.addLog({ level: 'info', message: `Still gathering requirements (${Math.round(data.confidence * 100)}% confidence). Gaps: ${gapList}` });
428
+ }
429
+ break;
430
+ }
431
+ case 'chat_message': {
432
+ AppState.update({ phase: 'chat' });
433
+ addArchitectMessage(data);
434
+ if (data.readiness) {
435
+ if (data.readiness.ready) {
436
+ AppState.addLog({ level: 'success', message: `Ready to build (${Math.round(data.readiness.confidence * 100)}%): ${data.readiness.summary}` });
437
+ } else if (data.readiness.gaps && data.readiness.gaps.length > 0) {
438
+ AppState.addLog({ level: 'info', message: `Gathering requirements (${Math.round(data.readiness.confidence * 100)}%). Gaps: ${data.readiness.gaps.join(', ')}` });
439
+ }
440
+ }
441
+ break;
442
+ }
443
+ case 'plan_summary': {
444
+ // Architecture plan arrived — show summary in chat panel
445
+ AppState.update({ planSummary: data });
446
+ appendActivityItem('\u2692', `Architecture: ${data.featureCount} features, ${data.apiRouteCount} routes, ${data.pageCount} pages`, 'activity-phase');
447
+ AppState.addLog({ level: 'info', message: `Architecture: ${data.featureCount} features, ${data.apiRouteCount} routes, ${data.pageCount} pages` });
448
+ const featureList = (data.features || []).map(f => `${f.name || f.id} (${f.complexity})`).join(', ');
449
+ addArchitectMessage({
450
+ message: `Architecture plan ready: ${data.featureCount} features, ${data.apiRouteCount} routes, ${data.pageCount} pages. ${featureList ? 'Features: ' + featureList + '.' : ''} Auto-approving...`,
451
+ });
452
+ break;
453
+ }
454
+ case 'awaiting_approval': {
455
+ // Pipeline is blocked waiting for approval — auto-approve after showing plan
456
+ AppState.update({ phase: 'requirements_refining' });
457
+ AppState.addLog({ level: 'info', message: 'Plan ready — auto-approving...' });
458
+ // Auto-approve after 4 seconds so audience can see the plan
459
+ setTimeout(() => {
460
+ autoApprovePlan(data.sessionId || window.__appId);
461
+ }, 4000);
462
+ break;
463
+ }
464
+ case 'functional_test': {
465
+ // Functional test progress
466
+ const ftPhase = data.phase || 'functional_testing';
467
+ AppState.update({ phase: ftPhase });
468
+ if (data.passedCount !== undefined) {
469
+ const failed = data.failedCount || 0;
470
+ appendActivityItem('\u2731', `Testing: ${data.passedCount} passed, ${failed} failed`, failed > 0 ? 'activity-warn' : 'activity-success');
471
+ AppState.addLog({ level: 'info', message: `Functional: ${data.passedCount} passed, ${failed} failed` });
472
+ }
473
+ if (data.failureCount) {
474
+ appendActivityItem('\u21bb', `Repairing ${data.failureCount} failures (attempt ${data.attempt})`, 'activity-warn');
475
+ AppState.addLog({ level: 'warn', message: `Repairing ${data.failureCount} test failures (attempt ${data.attempt})` });
476
+ }
477
+ break;
478
+ }
479
+ case 'error': {
480
+ AppState.update({ phase: 'failed' });
481
+ AppState.addLog({ level: 'error', message: data.message || data.error || 'Unknown error' });
482
+ appendActivityItem('\u2718', data.message || data.error || 'Build failed', 'activity-error');
483
+ unlockChat();
484
+ if (!completionShown) {
485
+ showCompletionOverlay({ status: 'failed', appName: 'Application' });
486
+ completionShown = true;
487
+ }
488
+ disconnect();
489
+ break;
490
+ }
491
+ case 'complete': {
492
+ AppState.update({ phase: 'completed' });
493
+ if (data.metrics) {
494
+ AppState.update({ metrics: data.metrics });
495
+ }
496
+ appendActivityItem('\u2713', 'Build complete', 'activity-success');
497
+ unlockChat();
498
+ if (!completionShown) {
499
+ showCompletionOverlay(data);
500
+ completionShown = true;
501
+ }
502
+ disconnect();
503
+ break;
504
+ }
505
+ }
506
+ }
507
+
508
+ function updateStatusBar() {
509
+ const state = AppState.get();
510
+ const phaseEl = document.getElementById('status-phase');
511
+ const tasksEl = document.getElementById('status-tasks');
512
+ if (phaseEl) phaseEl.textContent = `phase: ${state.phase}`;
513
+ if (tasksEl) tasksEl.textContent = `tasks: ${state.metrics.tasksComplete}/${state.metrics.totalTasks}`;
514
+ }
515
+
516
+ function connect(appId) {
517
+ if (eventSource) {
518
+ eventSource.close();
519
+ eventSource = null;
520
+ }
521
+
522
+ const url = apiKey
523
+ ? `${apiBase}/api/v1/app/${appId}/stream?key=${encodeURIComponent(apiKey)}`
524
+ : `${apiBase}/api/v1/app/${appId}/stream`;
525
+ console.log('[SSE] Connecting to:', url);
526
+ eventSource = new EventSource(url);
527
+
528
+ // Listen for named event types
529
+ const eventTypes = [
530
+ 'connected', 'progress', 'task_update', 'verification',
531
+ 'code_generated', 'agent_status', 'error', 'complete',
532
+ 'plan_summary', 'awaiting_approval', 'functional_test',
533
+ 'plan_questions',
534
+ 'plan_readiness',
535
+ 'chat_message'
536
+ ];
537
+
538
+ for (const type of eventTypes) {
539
+ eventSource.addEventListener(type, (event) => {
540
+ try {
541
+ const data = JSON.parse(event.data);
542
+ handleSSEEvent(type, data);
543
+ } catch (err) {
544
+ console.error(`[SSE] Failed to parse ${type} event:`, err);
545
+ }
546
+ });
547
+ }
548
+
549
+ // Also handle unnamed messages (default event type)
550
+ eventSource.onmessage = (event) => {
551
+ try {
552
+ const data = JSON.parse(event.data);
553
+ // Try to infer the type from the data shape
554
+ if (data.phase) handleSSEEvent('progress', data);
555
+ else if (data.claim) handleSSEEvent('verification', data);
556
+ else if (data.path && data.content) handleSSEEvent('code_generated', data);
557
+ else if (data.error) handleSSEEvent('error', data);
558
+ } catch (err) {
559
+ console.error('[SSE] Failed to parse message:', err);
560
+ }
561
+ };
562
+
563
+ eventSource.onopen = () => {
564
+ console.log('[SSE] Connected successfully');
565
+ };
566
+
567
+ eventSource.onerror = (err) => {
568
+ console.error('[SSE] Connection error:', err);
569
+ if (eventSource.readyState === EventSource.CLOSED) {
570
+ console.warn('[SSE] Connection closed. Check API server and auth key.');
571
+ }
572
+ };
573
+ }
574
+
575
+ function disconnect() {
576
+ if (eventSource) {
577
+ eventSource.close();
578
+ eventSource = null;
579
+ }
580
+ }
581
+
582
+ export function initSSEClient(base, key) {
583
+ apiBase = base || '';
584
+ apiKey = key || '';
585
+
586
+ // Share config with chat module
587
+ configureChat(apiBase, apiKey);
588
+
589
+ // Expose connect for manual invocation from bootstrap
590
+ window.__connectSSE = connect;
591
+
592
+ // Status bar updates
593
+ AppState.on('phase_change', updateStatusBar);
594
+ AppState.on('metric_update', updateStatusBar);
595
+
596
+ // Reset completionShown on new runs
597
+ AppState.on('reset', () => {
598
+ completionShown = false;
599
+ });
600
+ }