workerssuper 5.0.4

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 (135) hide show
  1. package/.claude-plugin/marketplace.json +20 -0
  2. package/.claude-plugin/plugin.json +13 -0
  3. package/.codex/INSTALL.md +67 -0
  4. package/.cursor-plugin/plugin.json +18 -0
  5. package/.gitattributes +18 -0
  6. package/.github/FUNDING.yml +3 -0
  7. package/.github/ISSUE_TEMPLATE/bug_report.md +52 -0
  8. package/.github/ISSUE_TEMPLATE/config.yml +5 -0
  9. package/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
  10. package/.github/ISSUE_TEMPLATE/platform_support.md +23 -0
  11. package/.github/PULL_REQUEST_TEMPLATE.md +87 -0
  12. package/.opencode/INSTALL.md +83 -0
  13. package/.opencode/plugins/superpowers.js +107 -0
  14. package/CHANGELOG.md +13 -0
  15. package/CODE_OF_CONDUCT.md +128 -0
  16. package/GEMINI.md +2 -0
  17. package/LICENSE +21 -0
  18. package/README.md +187 -0
  19. package/RELEASE-NOTES.md +1057 -0
  20. package/agents/code-reviewer.md +48 -0
  21. package/commands/brainstorm.md +5 -0
  22. package/commands/execute-plan.md +5 -0
  23. package/commands/write-plan.md +5 -0
  24. package/docs/README.codex.md +126 -0
  25. package/docs/README.opencode.md +130 -0
  26. package/docs/plans/2025-11-22-opencode-support-design.md +294 -0
  27. package/docs/plans/2025-11-22-opencode-support-implementation.md +1095 -0
  28. package/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +711 -0
  29. package/docs/plans/2026-01-17-visual-brainstorming.md +571 -0
  30. package/docs/superpowers/plans/2026-01-22-document-review-system.md +301 -0
  31. package/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md +523 -0
  32. package/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md +479 -0
  33. package/docs/superpowers/specs/2026-01-22-document-review-system-design.md +136 -0
  34. package/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md +162 -0
  35. package/docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md +118 -0
  36. package/docs/testing.md +303 -0
  37. package/docs/windows/polyglot-hooks.md +212 -0
  38. package/gemini-extension.json +6 -0
  39. package/hooks/hooks-cursor.json +10 -0
  40. package/hooks/hooks.json +16 -0
  41. package/hooks/run-hook.cmd +46 -0
  42. package/hooks/session-start +57 -0
  43. package/package.json +5 -0
  44. package/skills/brainstorming/SKILL.md +164 -0
  45. package/skills/brainstorming/scripts/frame-template.html +214 -0
  46. package/skills/brainstorming/scripts/helper.js +88 -0
  47. package/skills/brainstorming/scripts/server.cjs +338 -0
  48. package/skills/brainstorming/scripts/start-server.sh +153 -0
  49. package/skills/brainstorming/scripts/stop-server.sh +55 -0
  50. package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
  51. package/skills/brainstorming/visual-companion.md +286 -0
  52. package/skills/dispatching-parallel-agents/SKILL.md +182 -0
  53. package/skills/executing-plans/SKILL.md +70 -0
  54. package/skills/finishing-a-development-branch/SKILL.md +200 -0
  55. package/skills/receiving-code-review/SKILL.md +213 -0
  56. package/skills/requesting-code-review/SKILL.md +105 -0
  57. package/skills/requesting-code-review/code-reviewer.md +146 -0
  58. package/skills/subagent-driven-development/SKILL.md +277 -0
  59. package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
  60. package/skills/subagent-driven-development/implementer-prompt.md +113 -0
  61. package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
  62. package/skills/systematic-debugging/CREATION-LOG.md +119 -0
  63. package/skills/systematic-debugging/SKILL.md +296 -0
  64. package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
  65. package/skills/systematic-debugging/condition-based-waiting.md +115 -0
  66. package/skills/systematic-debugging/defense-in-depth.md +122 -0
  67. package/skills/systematic-debugging/find-polluter.sh +63 -0
  68. package/skills/systematic-debugging/root-cause-tracing.md +169 -0
  69. package/skills/systematic-debugging/test-academic.md +14 -0
  70. package/skills/systematic-debugging/test-pressure-1.md +58 -0
  71. package/skills/systematic-debugging/test-pressure-2.md +68 -0
  72. package/skills/systematic-debugging/test-pressure-3.md +69 -0
  73. package/skills/test-driven-development/SKILL.md +371 -0
  74. package/skills/test-driven-development/testing-anti-patterns.md +299 -0
  75. package/skills/using-git-worktrees/SKILL.md +218 -0
  76. package/skills/using-superpowers/SKILL.md +115 -0
  77. package/skills/using-superpowers/references/codex-tools.md +25 -0
  78. package/skills/using-superpowers/references/gemini-tools.md +33 -0
  79. package/skills/verification-before-completion/SKILL.md +139 -0
  80. package/skills/writing-plans/SKILL.md +145 -0
  81. package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
  82. package/skills/writing-skills/SKILL.md +655 -0
  83. package/skills/writing-skills/anthropic-best-practices.md +1150 -0
  84. package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
  85. package/skills/writing-skills/graphviz-conventions.dot +172 -0
  86. package/skills/writing-skills/persuasion-principles.md +187 -0
  87. package/skills/writing-skills/render-graphs.js +168 -0
  88. package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
  89. package/tests/brainstorm-server/package-lock.json +36 -0
  90. package/tests/brainstorm-server/package.json +10 -0
  91. package/tests/brainstorm-server/server.test.js +424 -0
  92. package/tests/brainstorm-server/windows-lifecycle.test.sh +351 -0
  93. package/tests/brainstorm-server/ws-protocol.test.js +392 -0
  94. package/tests/claude-code/README.md +158 -0
  95. package/tests/claude-code/analyze-token-usage.py +168 -0
  96. package/tests/claude-code/run-skill-tests.sh +187 -0
  97. package/tests/claude-code/test-document-review-system.sh +177 -0
  98. package/tests/claude-code/test-helpers.sh +202 -0
  99. package/tests/claude-code/test-subagent-driven-development-integration.sh +314 -0
  100. package/tests/claude-code/test-subagent-driven-development.sh +165 -0
  101. package/tests/explicit-skill-requests/prompts/action-oriented.txt +3 -0
  102. package/tests/explicit-skill-requests/prompts/after-planning-flow.txt +17 -0
  103. package/tests/explicit-skill-requests/prompts/claude-suggested-it.txt +11 -0
  104. package/tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt +8 -0
  105. package/tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt +3 -0
  106. package/tests/explicit-skill-requests/prompts/please-use-brainstorming.txt +1 -0
  107. package/tests/explicit-skill-requests/prompts/skip-formalities.txt +3 -0
  108. package/tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt +1 -0
  109. package/tests/explicit-skill-requests/prompts/use-systematic-debugging.txt +1 -0
  110. package/tests/explicit-skill-requests/run-all.sh +70 -0
  111. package/tests/explicit-skill-requests/run-claude-describes-sdd.sh +100 -0
  112. package/tests/explicit-skill-requests/run-extended-multiturn-test.sh +113 -0
  113. package/tests/explicit-skill-requests/run-haiku-test.sh +144 -0
  114. package/tests/explicit-skill-requests/run-multiturn-test.sh +143 -0
  115. package/tests/explicit-skill-requests/run-test.sh +136 -0
  116. package/tests/opencode/run-tests.sh +163 -0
  117. package/tests/opencode/setup.sh +73 -0
  118. package/tests/opencode/test-plugin-loading.sh +72 -0
  119. package/tests/opencode/test-priority.sh +198 -0
  120. package/tests/opencode/test-tools.sh +104 -0
  121. package/tests/skill-triggering/prompts/dispatching-parallel-agents.txt +8 -0
  122. package/tests/skill-triggering/prompts/executing-plans.txt +1 -0
  123. package/tests/skill-triggering/prompts/requesting-code-review.txt +3 -0
  124. package/tests/skill-triggering/prompts/systematic-debugging.txt +11 -0
  125. package/tests/skill-triggering/prompts/test-driven-development.txt +7 -0
  126. package/tests/skill-triggering/prompts/writing-plans.txt +10 -0
  127. package/tests/skill-triggering/run-all.sh +60 -0
  128. package/tests/skill-triggering/run-test.sh +88 -0
  129. package/tests/subagent-driven-dev/go-fractals/design.md +81 -0
  130. package/tests/subagent-driven-dev/go-fractals/plan.md +172 -0
  131. package/tests/subagent-driven-dev/go-fractals/scaffold.sh +45 -0
  132. package/tests/subagent-driven-dev/run-test.sh +106 -0
  133. package/tests/subagent-driven-dev/svelte-todo/design.md +70 -0
  134. package/tests/subagent-driven-dev/svelte-todo/plan.md +222 -0
  135. package/tests/subagent-driven-dev/svelte-todo/scaffold.sh +46 -0
@@ -0,0 +1,424 @@
1
+ /**
2
+ * Integration tests for the brainstorm server.
3
+ *
4
+ * Tests the full server behavior: HTTP serving, WebSocket communication,
5
+ * file watching, and the brainstorming workflow.
6
+ *
7
+ * Uses the `ws` npm package as a test client (test-only dependency,
8
+ * not shipped to end users).
9
+ */
10
+
11
+ const { spawn } = require('child_process');
12
+ const http = require('http');
13
+ const WebSocket = require('ws');
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const assert = require('assert');
17
+
18
+ const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
19
+ const TEST_PORT = 3334;
20
+ const TEST_DIR = '/tmp/brainstorm-test';
21
+
22
+ function cleanup() {
23
+ if (fs.existsSync(TEST_DIR)) {
24
+ fs.rmSync(TEST_DIR, { recursive: true });
25
+ }
26
+ }
27
+
28
+ async function sleep(ms) {
29
+ return new Promise(resolve => setTimeout(resolve, ms));
30
+ }
31
+
32
+ async function fetch(url) {
33
+ return new Promise((resolve, reject) => {
34
+ http.get(url, (res) => {
35
+ let data = '';
36
+ res.on('data', chunk => data += chunk);
37
+ res.on('end', () => resolve({
38
+ status: res.statusCode,
39
+ headers: res.headers,
40
+ body: data
41
+ }));
42
+ }).on('error', reject);
43
+ });
44
+ }
45
+
46
+ function startServer() {
47
+ return spawn('node', [SERVER_PATH], {
48
+ env: { ...process.env, BRAINSTORM_PORT: TEST_PORT, BRAINSTORM_DIR: TEST_DIR }
49
+ });
50
+ }
51
+
52
+ async function waitForServer(server) {
53
+ let stdout = '';
54
+ let stderr = '';
55
+
56
+ return new Promise((resolve, reject) => {
57
+ server.stdout.on('data', (data) => {
58
+ stdout += data.toString();
59
+ if (stdout.includes('server-started')) {
60
+ resolve({ stdout, stderr, getStdout: () => stdout });
61
+ }
62
+ });
63
+ server.stderr.on('data', (data) => { stderr += data.toString(); });
64
+ server.on('error', reject);
65
+
66
+ setTimeout(() => reject(new Error(`Server didn't start. stderr: ${stderr}`)), 5000);
67
+ });
68
+ }
69
+
70
+ async function runTests() {
71
+ cleanup();
72
+ fs.mkdirSync(TEST_DIR, { recursive: true });
73
+
74
+ const server = startServer();
75
+ let stdoutAccum = '';
76
+ server.stdout.on('data', (data) => { stdoutAccum += data.toString(); });
77
+
78
+ const { stdout: initialStdout } = await waitForServer(server);
79
+ let passed = 0;
80
+ let failed = 0;
81
+
82
+ function test(name, fn) {
83
+ return fn().then(() => {
84
+ console.log(` PASS: ${name}`);
85
+ passed++;
86
+ }).catch(e => {
87
+ console.log(` FAIL: ${name}`);
88
+ console.log(` ${e.message}`);
89
+ failed++;
90
+ });
91
+ }
92
+
93
+ try {
94
+ // ========== Server Startup ==========
95
+ console.log('\n--- Server Startup ---');
96
+
97
+ await test('outputs server-started JSON on startup', () => {
98
+ const msg = JSON.parse(initialStdout.trim());
99
+ assert.strictEqual(msg.type, 'server-started');
100
+ assert.strictEqual(msg.port, TEST_PORT);
101
+ assert(msg.url, 'Should include URL');
102
+ assert(msg.screen_dir, 'Should include screen_dir');
103
+ return Promise.resolve();
104
+ });
105
+
106
+ await test('writes .server-info file', () => {
107
+ const infoPath = path.join(TEST_DIR, '.server-info');
108
+ assert(fs.existsSync(infoPath), '.server-info should exist');
109
+ const info = JSON.parse(fs.readFileSync(infoPath, 'utf-8').trim());
110
+ assert.strictEqual(info.type, 'server-started');
111
+ assert.strictEqual(info.port, TEST_PORT);
112
+ return Promise.resolve();
113
+ });
114
+
115
+ // ========== HTTP Serving ==========
116
+ console.log('\n--- HTTP Serving ---');
117
+
118
+ await test('serves waiting page when no screens exist', async () => {
119
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
120
+ assert.strictEqual(res.status, 200);
121
+ assert(res.body.includes('Waiting for Claude'), 'Should show waiting message');
122
+ });
123
+
124
+ await test('injects helper.js into waiting page', async () => {
125
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
126
+ assert(res.body.includes('WebSocket'), 'Should have helper.js injected');
127
+ assert(res.body.includes('toggleSelect'), 'Should have toggleSelect from helper');
128
+ assert(res.body.includes('brainstorm'), 'Should have brainstorm API from helper');
129
+ });
130
+
131
+ await test('returns Content-Type text/html', async () => {
132
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
133
+ assert(res.headers['content-type'].includes('text/html'), 'Should be text/html');
134
+ });
135
+
136
+ await test('serves full HTML documents as-is (not wrapped)', async () => {
137
+ const fullDoc = '<!DOCTYPE html>\n<html><head><title>Custom</title></head><body><h1>Custom Page</h1></body></html>';
138
+ fs.writeFileSync(path.join(TEST_DIR, 'full-doc.html'), fullDoc);
139
+ await sleep(300);
140
+
141
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
142
+ assert(res.body.includes('<h1>Custom Page</h1>'), 'Should contain original content');
143
+ assert(res.body.includes('WebSocket'), 'Should still inject helper.js');
144
+ assert(!res.body.includes('indicator-bar'), 'Should NOT wrap in frame template');
145
+ });
146
+
147
+ await test('wraps content fragments in frame template', async () => {
148
+ const fragment = '<h2>Pick a layout</h2>\n<div class="options"><div class="option" data-choice="a"><div class="letter">A</div></div></div>';
149
+ fs.writeFileSync(path.join(TEST_DIR, 'fragment.html'), fragment);
150
+ await sleep(300);
151
+
152
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
153
+ assert(res.body.includes('indicator-bar'), 'Fragment should get indicator bar');
154
+ assert(!res.body.includes('<!-- CONTENT -->'), 'Placeholder should be replaced');
155
+ assert(res.body.includes('Pick a layout'), 'Fragment content should be present');
156
+ assert(res.body.includes('data-choice="a"'), 'Fragment interactive elements intact');
157
+ });
158
+
159
+ await test('serves newest file by mtime', async () => {
160
+ fs.writeFileSync(path.join(TEST_DIR, 'older.html'), '<h2>Older</h2>');
161
+ await sleep(100);
162
+ fs.writeFileSync(path.join(TEST_DIR, 'newer.html'), '<h2>Newer</h2>');
163
+ await sleep(300);
164
+
165
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
166
+ assert(res.body.includes('Newer'), 'Should serve newest file');
167
+ });
168
+
169
+ await test('ignores non-html files for serving', async () => {
170
+ // Write a newer non-HTML file — should still serve newest .html
171
+ fs.writeFileSync(path.join(TEST_DIR, 'data.json'), '{"not": "html"}');
172
+ await sleep(300);
173
+
174
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
175
+ assert(res.body.includes('Newer'), 'Should still serve newest HTML');
176
+ assert(!res.body.includes('"not"'), 'Should not serve JSON');
177
+ });
178
+
179
+ await test('returns 404 for non-root paths', async () => {
180
+ const res = await fetch(`http://localhost:${TEST_PORT}/other`);
181
+ assert.strictEqual(res.status, 404);
182
+ });
183
+
184
+ // ========== WebSocket Communication ==========
185
+ console.log('\n--- WebSocket Communication ---');
186
+
187
+ await test('accepts WebSocket upgrade on /', async () => {
188
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
189
+ await new Promise((resolve, reject) => {
190
+ ws.on('open', resolve);
191
+ ws.on('error', reject);
192
+ });
193
+ ws.close();
194
+ });
195
+
196
+ await test('relays user events to stdout with source field', async () => {
197
+ stdoutAccum = '';
198
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
199
+ await new Promise(resolve => ws.on('open', resolve));
200
+
201
+ ws.send(JSON.stringify({ type: 'click', text: 'Test Button' }));
202
+ await sleep(300);
203
+
204
+ assert(stdoutAccum.includes('"source":"user-event"'), 'Should tag with source');
205
+ assert(stdoutAccum.includes('Test Button'), 'Should include event data');
206
+ ws.close();
207
+ });
208
+
209
+ await test('writes choice events to .events file', async () => {
210
+ // Clean up events from prior tests
211
+ const eventsFile = path.join(TEST_DIR, '.events');
212
+ if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
213
+
214
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
215
+ await new Promise(resolve => ws.on('open', resolve));
216
+
217
+ ws.send(JSON.stringify({ type: 'click', choice: 'b', text: 'Option B' }));
218
+ await sleep(300);
219
+
220
+ assert(fs.existsSync(eventsFile), '.events should exist');
221
+ const lines = fs.readFileSync(eventsFile, 'utf-8').trim().split('\n');
222
+ const event = JSON.parse(lines[lines.length - 1]);
223
+ assert.strictEqual(event.choice, 'b');
224
+ assert.strictEqual(event.text, 'Option B');
225
+ ws.close();
226
+ });
227
+
228
+ await test('does NOT write non-choice events to .events file', async () => {
229
+ const eventsFile = path.join(TEST_DIR, '.events');
230
+ if (fs.existsSync(eventsFile)) fs.unlinkSync(eventsFile);
231
+
232
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
233
+ await new Promise(resolve => ws.on('open', resolve));
234
+
235
+ ws.send(JSON.stringify({ type: 'hover', text: 'Something' }));
236
+ await sleep(300);
237
+
238
+ // Non-choice events should not create .events file
239
+ assert(!fs.existsSync(eventsFile), '.events should not exist for non-choice events');
240
+ ws.close();
241
+ });
242
+
243
+ await test('handles multiple concurrent WebSocket clients', async () => {
244
+ const ws1 = new WebSocket(`ws://localhost:${TEST_PORT}`);
245
+ const ws2 = new WebSocket(`ws://localhost:${TEST_PORT}`);
246
+ await Promise.all([
247
+ new Promise(resolve => ws1.on('open', resolve)),
248
+ new Promise(resolve => ws2.on('open', resolve))
249
+ ]);
250
+
251
+ let ws1Reload = false;
252
+ let ws2Reload = false;
253
+ ws1.on('message', (data) => {
254
+ if (JSON.parse(data.toString()).type === 'reload') ws1Reload = true;
255
+ });
256
+ ws2.on('message', (data) => {
257
+ if (JSON.parse(data.toString()).type === 'reload') ws2Reload = true;
258
+ });
259
+
260
+ fs.writeFileSync(path.join(TEST_DIR, 'multi-client.html'), '<h2>Multi</h2>');
261
+ await sleep(500);
262
+
263
+ assert(ws1Reload, 'Client 1 should receive reload');
264
+ assert(ws2Reload, 'Client 2 should receive reload');
265
+ ws1.close();
266
+ ws2.close();
267
+ });
268
+
269
+ await test('cleans up closed clients from broadcast list', async () => {
270
+ const ws1 = new WebSocket(`ws://localhost:${TEST_PORT}`);
271
+ await new Promise(resolve => ws1.on('open', resolve));
272
+ ws1.close();
273
+ await sleep(100);
274
+
275
+ // This should not throw even though ws1 is closed
276
+ fs.writeFileSync(path.join(TEST_DIR, 'after-close.html'), '<h2>After</h2>');
277
+ await sleep(300);
278
+ // If we got here without error, the test passes
279
+ });
280
+
281
+ await test('handles malformed JSON from client gracefully', async () => {
282
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
283
+ await new Promise(resolve => ws.on('open', resolve));
284
+
285
+ // Send invalid JSON — server should not crash
286
+ ws.send('not json at all {{{');
287
+ await sleep(300);
288
+
289
+ // Verify server is still responsive
290
+ const res = await fetch(`http://localhost:${TEST_PORT}/`);
291
+ assert.strictEqual(res.status, 200, 'Server should still be running');
292
+ ws.close();
293
+ });
294
+
295
+ // ========== File Watching ==========
296
+ console.log('\n--- File Watching ---');
297
+
298
+ await test('sends reload on new .html file', async () => {
299
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
300
+ await new Promise(resolve => ws.on('open', resolve));
301
+
302
+ let gotReload = false;
303
+ ws.on('message', (data) => {
304
+ if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
305
+ });
306
+
307
+ fs.writeFileSync(path.join(TEST_DIR, 'watch-new.html'), '<h2>New</h2>');
308
+ await sleep(500);
309
+
310
+ assert(gotReload, 'Should send reload on new file');
311
+ ws.close();
312
+ });
313
+
314
+ await test('sends reload on .html file change', async () => {
315
+ const filePath = path.join(TEST_DIR, 'watch-change.html');
316
+ fs.writeFileSync(filePath, '<h2>Original</h2>');
317
+ await sleep(500);
318
+
319
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
320
+ await new Promise(resolve => ws.on('open', resolve));
321
+
322
+ let gotReload = false;
323
+ ws.on('message', (data) => {
324
+ if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
325
+ });
326
+
327
+ fs.writeFileSync(filePath, '<h2>Modified</h2>');
328
+ await sleep(500);
329
+
330
+ assert(gotReload, 'Should send reload on file change');
331
+ ws.close();
332
+ });
333
+
334
+ await test('does NOT send reload for non-.html files', async () => {
335
+ const ws = new WebSocket(`ws://localhost:${TEST_PORT}`);
336
+ await new Promise(resolve => ws.on('open', resolve));
337
+
338
+ let gotReload = false;
339
+ ws.on('message', (data) => {
340
+ if (JSON.parse(data.toString()).type === 'reload') gotReload = true;
341
+ });
342
+
343
+ fs.writeFileSync(path.join(TEST_DIR, 'data.txt'), 'not html');
344
+ await sleep(500);
345
+
346
+ assert(!gotReload, 'Should NOT reload for non-HTML files');
347
+ ws.close();
348
+ });
349
+
350
+ await test('clears .events on new screen', async () => {
351
+ // Create an .events file
352
+ const eventsFile = path.join(TEST_DIR, '.events');
353
+ fs.writeFileSync(eventsFile, '{"choice":"a"}\n');
354
+ assert(fs.existsSync(eventsFile));
355
+
356
+ fs.writeFileSync(path.join(TEST_DIR, 'clear-events.html'), '<h2>New screen</h2>');
357
+ await sleep(500);
358
+
359
+ assert(!fs.existsSync(eventsFile), '.events should be cleared on new screen');
360
+ });
361
+
362
+ await test('logs screen-added on new file', async () => {
363
+ stdoutAccum = '';
364
+ fs.writeFileSync(path.join(TEST_DIR, 'log-test.html'), '<h2>Log</h2>');
365
+ await sleep(500);
366
+
367
+ assert(stdoutAccum.includes('screen-added'), 'Should log screen-added');
368
+ });
369
+
370
+ await test('logs screen-updated on file change', async () => {
371
+ const filePath = path.join(TEST_DIR, 'log-update.html');
372
+ fs.writeFileSync(filePath, '<h2>V1</h2>');
373
+ await sleep(500);
374
+
375
+ stdoutAccum = '';
376
+ fs.writeFileSync(filePath, '<h2>V2</h2>');
377
+ await sleep(500);
378
+
379
+ assert(stdoutAccum.includes('screen-updated'), 'Should log screen-updated');
380
+ });
381
+
382
+ // ========== Helper.js Content ==========
383
+ console.log('\n--- Helper.js Verification ---');
384
+
385
+ await test('helper.js defines required APIs', () => {
386
+ const helperContent = fs.readFileSync(
387
+ path.join(__dirname, '../../skills/brainstorming/scripts/helper.js'), 'utf-8'
388
+ );
389
+ assert(helperContent.includes('toggleSelect'), 'Should define toggleSelect');
390
+ assert(helperContent.includes('sendEvent'), 'Should define sendEvent');
391
+ assert(helperContent.includes('selectedChoice'), 'Should track selectedChoice');
392
+ assert(helperContent.includes('brainstorm'), 'Should expose brainstorm API');
393
+ return Promise.resolve();
394
+ });
395
+
396
+ // ========== Frame Template ==========
397
+ console.log('\n--- Frame Template Verification ---');
398
+
399
+ await test('frame template has required structure', () => {
400
+ const template = fs.readFileSync(
401
+ path.join(__dirname, '../../skills/brainstorming/scripts/frame-template.html'), 'utf-8'
402
+ );
403
+ assert(template.includes('indicator-bar'), 'Should have indicator bar');
404
+ assert(template.includes('indicator-text'), 'Should have indicator text');
405
+ assert(template.includes('<!-- CONTENT -->'), 'Should have content placeholder');
406
+ assert(template.includes('claude-content'), 'Should have content container');
407
+ return Promise.resolve();
408
+ });
409
+
410
+ // ========== Summary ==========
411
+ console.log(`\n--- Results: ${passed} passed, ${failed} failed ---`);
412
+ if (failed > 0) process.exit(1);
413
+
414
+ } finally {
415
+ server.kill();
416
+ await sleep(100);
417
+ cleanup();
418
+ }
419
+ }
420
+
421
+ runTests().catch(err => {
422
+ console.error('Test failed:', err);
423
+ process.exit(1);
424
+ });