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.
- package/.claude-plugin/marketplace.json +20 -0
- package/.claude-plugin/plugin.json +13 -0
- package/.codex/INSTALL.md +67 -0
- package/.cursor-plugin/plugin.json +18 -0
- package/.gitattributes +18 -0
- package/.github/FUNDING.yml +3 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +52 -0
- package/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +34 -0
- package/.github/ISSUE_TEMPLATE/platform_support.md +23 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +87 -0
- package/.opencode/INSTALL.md +83 -0
- package/.opencode/plugins/superpowers.js +107 -0
- package/CHANGELOG.md +13 -0
- package/CODE_OF_CONDUCT.md +128 -0
- package/GEMINI.md +2 -0
- package/LICENSE +21 -0
- package/README.md +187 -0
- package/RELEASE-NOTES.md +1057 -0
- package/agents/code-reviewer.md +48 -0
- package/commands/brainstorm.md +5 -0
- package/commands/execute-plan.md +5 -0
- package/commands/write-plan.md +5 -0
- package/docs/README.codex.md +126 -0
- package/docs/README.opencode.md +130 -0
- package/docs/plans/2025-11-22-opencode-support-design.md +294 -0
- package/docs/plans/2025-11-22-opencode-support-implementation.md +1095 -0
- package/docs/plans/2025-11-28-skills-improvements-from-user-feedback.md +711 -0
- package/docs/plans/2026-01-17-visual-brainstorming.md +571 -0
- package/docs/superpowers/plans/2026-01-22-document-review-system.md +301 -0
- package/docs/superpowers/plans/2026-02-19-visual-brainstorming-refactor.md +523 -0
- package/docs/superpowers/plans/2026-03-11-zero-dep-brainstorm-server.md +479 -0
- package/docs/superpowers/specs/2026-01-22-document-review-system-design.md +136 -0
- package/docs/superpowers/specs/2026-02-19-visual-brainstorming-refactor-design.md +162 -0
- package/docs/superpowers/specs/2026-03-11-zero-dep-brainstorm-server-design.md +118 -0
- package/docs/testing.md +303 -0
- package/docs/windows/polyglot-hooks.md +212 -0
- package/gemini-extension.json +6 -0
- package/hooks/hooks-cursor.json +10 -0
- package/hooks/hooks.json +16 -0
- package/hooks/run-hook.cmd +46 -0
- package/hooks/session-start +57 -0
- package/package.json +5 -0
- package/skills/brainstorming/SKILL.md +164 -0
- package/skills/brainstorming/scripts/frame-template.html +214 -0
- package/skills/brainstorming/scripts/helper.js +88 -0
- package/skills/brainstorming/scripts/server.cjs +338 -0
- package/skills/brainstorming/scripts/start-server.sh +153 -0
- package/skills/brainstorming/scripts/stop-server.sh +55 -0
- package/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/skills/brainstorming/visual-companion.md +286 -0
- package/skills/dispatching-parallel-agents/SKILL.md +182 -0
- package/skills/executing-plans/SKILL.md +70 -0
- package/skills/finishing-a-development-branch/SKILL.md +200 -0
- package/skills/receiving-code-review/SKILL.md +213 -0
- package/skills/requesting-code-review/SKILL.md +105 -0
- package/skills/requesting-code-review/code-reviewer.md +146 -0
- package/skills/subagent-driven-development/SKILL.md +277 -0
- package/skills/subagent-driven-development/code-quality-reviewer-prompt.md +26 -0
- package/skills/subagent-driven-development/implementer-prompt.md +113 -0
- package/skills/subagent-driven-development/spec-reviewer-prompt.md +61 -0
- package/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/skills/systematic-debugging/SKILL.md +296 -0
- package/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/skills/systematic-debugging/find-polluter.sh +63 -0
- package/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/skills/systematic-debugging/test-academic.md +14 -0
- package/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/skills/test-driven-development/SKILL.md +371 -0
- package/skills/test-driven-development/testing-anti-patterns.md +299 -0
- package/skills/using-git-worktrees/SKILL.md +218 -0
- package/skills/using-superpowers/SKILL.md +115 -0
- package/skills/using-superpowers/references/codex-tools.md +25 -0
- package/skills/using-superpowers/references/gemini-tools.md +33 -0
- package/skills/verification-before-completion/SKILL.md +139 -0
- package/skills/writing-plans/SKILL.md +145 -0
- package/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/skills/writing-skills/SKILL.md +655 -0
- package/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/skills/writing-skills/persuasion-principles.md +187 -0
- package/skills/writing-skills/render-graphs.js +168 -0
- package/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/tests/brainstorm-server/package-lock.json +36 -0
- package/tests/brainstorm-server/package.json +10 -0
- package/tests/brainstorm-server/server.test.js +424 -0
- package/tests/brainstorm-server/windows-lifecycle.test.sh +351 -0
- package/tests/brainstorm-server/ws-protocol.test.js +392 -0
- package/tests/claude-code/README.md +158 -0
- package/tests/claude-code/analyze-token-usage.py +168 -0
- package/tests/claude-code/run-skill-tests.sh +187 -0
- package/tests/claude-code/test-document-review-system.sh +177 -0
- package/tests/claude-code/test-helpers.sh +202 -0
- package/tests/claude-code/test-subagent-driven-development-integration.sh +314 -0
- package/tests/claude-code/test-subagent-driven-development.sh +165 -0
- package/tests/explicit-skill-requests/prompts/action-oriented.txt +3 -0
- package/tests/explicit-skill-requests/prompts/after-planning-flow.txt +17 -0
- package/tests/explicit-skill-requests/prompts/claude-suggested-it.txt +11 -0
- package/tests/explicit-skill-requests/prompts/i-know-what-sdd-means.txt +8 -0
- package/tests/explicit-skill-requests/prompts/mid-conversation-execute-plan.txt +3 -0
- package/tests/explicit-skill-requests/prompts/please-use-brainstorming.txt +1 -0
- package/tests/explicit-skill-requests/prompts/skip-formalities.txt +3 -0
- package/tests/explicit-skill-requests/prompts/subagent-driven-development-please.txt +1 -0
- package/tests/explicit-skill-requests/prompts/use-systematic-debugging.txt +1 -0
- package/tests/explicit-skill-requests/run-all.sh +70 -0
- package/tests/explicit-skill-requests/run-claude-describes-sdd.sh +100 -0
- package/tests/explicit-skill-requests/run-extended-multiturn-test.sh +113 -0
- package/tests/explicit-skill-requests/run-haiku-test.sh +144 -0
- package/tests/explicit-skill-requests/run-multiturn-test.sh +143 -0
- package/tests/explicit-skill-requests/run-test.sh +136 -0
- package/tests/opencode/run-tests.sh +163 -0
- package/tests/opencode/setup.sh +73 -0
- package/tests/opencode/test-plugin-loading.sh +72 -0
- package/tests/opencode/test-priority.sh +198 -0
- package/tests/opencode/test-tools.sh +104 -0
- package/tests/skill-triggering/prompts/dispatching-parallel-agents.txt +8 -0
- package/tests/skill-triggering/prompts/executing-plans.txt +1 -0
- package/tests/skill-triggering/prompts/requesting-code-review.txt +3 -0
- package/tests/skill-triggering/prompts/systematic-debugging.txt +11 -0
- package/tests/skill-triggering/prompts/test-driven-development.txt +7 -0
- package/tests/skill-triggering/prompts/writing-plans.txt +10 -0
- package/tests/skill-triggering/run-all.sh +60 -0
- package/tests/skill-triggering/run-test.sh +88 -0
- package/tests/subagent-driven-dev/go-fractals/design.md +81 -0
- package/tests/subagent-driven-dev/go-fractals/plan.md +172 -0
- package/tests/subagent-driven-dev/go-fractals/scaffold.sh +45 -0
- package/tests/subagent-driven-dev/run-test.sh +106 -0
- package/tests/subagent-driven-dev/svelte-todo/design.md +70 -0
- package/tests/subagent-driven-dev/svelte-todo/plan.md +222 -0
- package/tests/subagent-driven-dev/svelte-todo/scaffold.sh +46 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the zero-dependency WebSocket protocol implementation.
|
|
3
|
+
*
|
|
4
|
+
* Tests the WebSocket frame encoding/decoding, handshake computation,
|
|
5
|
+
* and protocol-level behavior independent of the HTTP server.
|
|
6
|
+
*
|
|
7
|
+
* The module under test exports:
|
|
8
|
+
* - computeAcceptKey(clientKey) -> string
|
|
9
|
+
* - encodeFrame(opcode, payload) -> Buffer
|
|
10
|
+
* - decodeFrame(buffer) -> { opcode, payload, bytesConsumed } | null
|
|
11
|
+
* - OPCODES: { TEXT, CLOSE, PING, PONG }
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const assert = require('assert');
|
|
15
|
+
const crypto = require('crypto');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
// The module under test — will be the new zero-dep server file
|
|
19
|
+
const SERVER_PATH = path.join(__dirname, '../../skills/brainstorming/scripts/server.cjs');
|
|
20
|
+
let ws;
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
ws = require(SERVER_PATH);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
// Module doesn't exist yet (TDD — tests written before implementation)
|
|
26
|
+
console.error(`Cannot load ${SERVER_PATH}: ${e.message}`);
|
|
27
|
+
console.error('This is expected if running tests before implementation.');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function runTests() {
|
|
32
|
+
let passed = 0;
|
|
33
|
+
let failed = 0;
|
|
34
|
+
|
|
35
|
+
function test(name, fn) {
|
|
36
|
+
try {
|
|
37
|
+
fn();
|
|
38
|
+
console.log(` PASS: ${name}`);
|
|
39
|
+
passed++;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
console.log(` FAIL: ${name}`);
|
|
42
|
+
console.log(` ${e.message}`);
|
|
43
|
+
failed++;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ========== Handshake ==========
|
|
48
|
+
console.log('\n--- WebSocket Handshake ---');
|
|
49
|
+
|
|
50
|
+
test('computeAcceptKey produces correct RFC 6455 accept value', () => {
|
|
51
|
+
// RFC 6455 Section 4.2.2 example
|
|
52
|
+
// The magic GUID is "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
53
|
+
const clientKey = 'dGhlIHNhbXBsZSBub25jZQ==';
|
|
54
|
+
const expected = 's3pPLMBiTxaQ9kYGzzhZRbK+xOo=';
|
|
55
|
+
assert.strictEqual(ws.computeAcceptKey(clientKey), expected);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('computeAcceptKey produces valid base64 for random keys', () => {
|
|
59
|
+
for (let i = 0; i < 10; i++) {
|
|
60
|
+
const randomKey = crypto.randomBytes(16).toString('base64');
|
|
61
|
+
const result = ws.computeAcceptKey(randomKey);
|
|
62
|
+
// Result should be valid base64
|
|
63
|
+
assert.strictEqual(Buffer.from(result, 'base64').toString('base64'), result);
|
|
64
|
+
// SHA-1 output is 20 bytes, base64 encoded = 28 chars
|
|
65
|
+
assert.strictEqual(result.length, 28);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// ========== Frame Encoding ==========
|
|
70
|
+
console.log('\n--- Frame Encoding (server -> client) ---');
|
|
71
|
+
|
|
72
|
+
test('encodes small text frame (< 126 bytes)', () => {
|
|
73
|
+
const payload = 'Hello';
|
|
74
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, Buffer.from(payload));
|
|
75
|
+
// FIN bit + TEXT opcode = 0x81, length = 5
|
|
76
|
+
assert.strictEqual(frame[0], 0x81);
|
|
77
|
+
assert.strictEqual(frame[1], 5);
|
|
78
|
+
assert.strictEqual(frame.slice(2).toString(), 'Hello');
|
|
79
|
+
assert.strictEqual(frame.length, 7);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test('encodes empty text frame', () => {
|
|
83
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, Buffer.alloc(0));
|
|
84
|
+
assert.strictEqual(frame[0], 0x81);
|
|
85
|
+
assert.strictEqual(frame[1], 0);
|
|
86
|
+
assert.strictEqual(frame.length, 2);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('encodes medium text frame (126-65535 bytes)', () => {
|
|
90
|
+
const payload = Buffer.alloc(200, 0x41); // 200 'A's
|
|
91
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
92
|
+
assert.strictEqual(frame[0], 0x81);
|
|
93
|
+
assert.strictEqual(frame[1], 126); // extended length marker
|
|
94
|
+
assert.strictEqual(frame.readUInt16BE(2), 200);
|
|
95
|
+
assert.strictEqual(frame.slice(4).toString(), payload.toString());
|
|
96
|
+
assert.strictEqual(frame.length, 204);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test('encodes frame at exactly 126 bytes (boundary)', () => {
|
|
100
|
+
const payload = Buffer.alloc(126, 0x42);
|
|
101
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
102
|
+
assert.strictEqual(frame[1], 126); // extended length marker
|
|
103
|
+
assert.strictEqual(frame.readUInt16BE(2), 126);
|
|
104
|
+
assert.strictEqual(frame.length, 130);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('encodes frame at exactly 125 bytes (max small)', () => {
|
|
108
|
+
const payload = Buffer.alloc(125, 0x43);
|
|
109
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
110
|
+
assert.strictEqual(frame[1], 125);
|
|
111
|
+
assert.strictEqual(frame.length, 127);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('encodes large frame (> 65535 bytes)', () => {
|
|
115
|
+
const payload = Buffer.alloc(70000, 0x44);
|
|
116
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
117
|
+
assert.strictEqual(frame[0], 0x81);
|
|
118
|
+
assert.strictEqual(frame[1], 127); // 64-bit length marker
|
|
119
|
+
// 8-byte extended length at offset 2
|
|
120
|
+
const len = Number(frame.readBigUInt64BE(2));
|
|
121
|
+
assert.strictEqual(len, 70000);
|
|
122
|
+
assert.strictEqual(frame.length, 10 + 70000);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('encodes close frame', () => {
|
|
126
|
+
const frame = ws.encodeFrame(ws.OPCODES.CLOSE, Buffer.alloc(0));
|
|
127
|
+
assert.strictEqual(frame[0], 0x88); // FIN + CLOSE
|
|
128
|
+
assert.strictEqual(frame[1], 0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
test('encodes pong frame with payload', () => {
|
|
132
|
+
const payload = Buffer.from('ping-data');
|
|
133
|
+
const frame = ws.encodeFrame(ws.OPCODES.PONG, payload);
|
|
134
|
+
assert.strictEqual(frame[0], 0x8A); // FIN + PONG
|
|
135
|
+
assert.strictEqual(frame[1], payload.length);
|
|
136
|
+
assert.strictEqual(frame.slice(2).toString(), 'ping-data');
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('server frames are never masked (per RFC 6455)', () => {
|
|
140
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, Buffer.from('test'));
|
|
141
|
+
// Bit 7 of byte 1 is the mask bit — must be 0 for server frames
|
|
142
|
+
assert.strictEqual(frame[1] & 0x80, 0);
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// ========== Frame Decoding ==========
|
|
146
|
+
console.log('\n--- Frame Decoding (client -> server) ---');
|
|
147
|
+
|
|
148
|
+
// Helper: create a masked client frame
|
|
149
|
+
function makeClientFrame(opcode, payload, fin = true) {
|
|
150
|
+
const buf = Buffer.from(payload);
|
|
151
|
+
const mask = crypto.randomBytes(4);
|
|
152
|
+
const masked = Buffer.alloc(buf.length);
|
|
153
|
+
for (let i = 0; i < buf.length; i++) {
|
|
154
|
+
masked[i] = buf[i] ^ mask[i % 4];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
let header;
|
|
158
|
+
const finBit = fin ? 0x80 : 0x00;
|
|
159
|
+
if (buf.length < 126) {
|
|
160
|
+
header = Buffer.alloc(6);
|
|
161
|
+
header[0] = finBit | opcode;
|
|
162
|
+
header[1] = 0x80 | buf.length; // mask bit set
|
|
163
|
+
mask.copy(header, 2);
|
|
164
|
+
} else if (buf.length < 65536) {
|
|
165
|
+
header = Buffer.alloc(8);
|
|
166
|
+
header[0] = finBit | opcode;
|
|
167
|
+
header[1] = 0x80 | 126;
|
|
168
|
+
header.writeUInt16BE(buf.length, 2);
|
|
169
|
+
mask.copy(header, 4);
|
|
170
|
+
} else {
|
|
171
|
+
header = Buffer.alloc(14);
|
|
172
|
+
header[0] = finBit | opcode;
|
|
173
|
+
header[1] = 0x80 | 127;
|
|
174
|
+
header.writeBigUInt64BE(BigInt(buf.length), 2);
|
|
175
|
+
mask.copy(header, 10);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return Buffer.concat([header, masked]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
test('decodes small masked text frame', () => {
|
|
182
|
+
const frame = makeClientFrame(0x01, 'Hello');
|
|
183
|
+
const result = ws.decodeFrame(frame);
|
|
184
|
+
assert(result, 'Should return a result');
|
|
185
|
+
assert.strictEqual(result.opcode, ws.OPCODES.TEXT);
|
|
186
|
+
assert.strictEqual(result.payload.toString(), 'Hello');
|
|
187
|
+
assert.strictEqual(result.bytesConsumed, frame.length);
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
test('decodes empty masked text frame', () => {
|
|
191
|
+
const frame = makeClientFrame(0x01, '');
|
|
192
|
+
const result = ws.decodeFrame(frame);
|
|
193
|
+
assert(result, 'Should return a result');
|
|
194
|
+
assert.strictEqual(result.opcode, ws.OPCODES.TEXT);
|
|
195
|
+
assert.strictEqual(result.payload.length, 0);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('decodes medium masked text frame (126-65535 bytes)', () => {
|
|
199
|
+
const payload = 'A'.repeat(200);
|
|
200
|
+
const frame = makeClientFrame(0x01, payload);
|
|
201
|
+
const result = ws.decodeFrame(frame);
|
|
202
|
+
assert(result, 'Should return a result');
|
|
203
|
+
assert.strictEqual(result.payload.toString(), payload);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test('decodes large masked text frame (> 65535 bytes)', () => {
|
|
207
|
+
const payload = 'B'.repeat(70000);
|
|
208
|
+
const frame = makeClientFrame(0x01, payload);
|
|
209
|
+
const result = ws.decodeFrame(frame);
|
|
210
|
+
assert(result, 'Should return a result');
|
|
211
|
+
assert.strictEqual(result.payload.length, 70000);
|
|
212
|
+
assert.strictEqual(result.payload.toString(), payload);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
test('decodes masked close frame', () => {
|
|
216
|
+
const frame = makeClientFrame(0x08, '');
|
|
217
|
+
const result = ws.decodeFrame(frame);
|
|
218
|
+
assert(result, 'Should return a result');
|
|
219
|
+
assert.strictEqual(result.opcode, ws.OPCODES.CLOSE);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('decodes masked ping frame', () => {
|
|
223
|
+
const frame = makeClientFrame(0x09, 'ping!');
|
|
224
|
+
const result = ws.decodeFrame(frame);
|
|
225
|
+
assert(result, 'Should return a result');
|
|
226
|
+
assert.strictEqual(result.opcode, ws.OPCODES.PING);
|
|
227
|
+
assert.strictEqual(result.payload.toString(), 'ping!');
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('returns null for incomplete frame (not enough header bytes)', () => {
|
|
231
|
+
const result = ws.decodeFrame(Buffer.from([0x81]));
|
|
232
|
+
assert.strictEqual(result, null, 'Should return null for 1-byte buffer');
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test('returns null for incomplete frame (header ok, payload truncated)', () => {
|
|
236
|
+
// Create a valid frame then truncate it
|
|
237
|
+
const frame = makeClientFrame(0x01, 'Hello World');
|
|
238
|
+
const truncated = frame.slice(0, frame.length - 3);
|
|
239
|
+
const result = ws.decodeFrame(truncated);
|
|
240
|
+
assert.strictEqual(result, null, 'Should return null for truncated frame');
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
test('returns null for incomplete extended-length header', () => {
|
|
244
|
+
// Frame claiming 16-bit length but only 3 bytes total
|
|
245
|
+
const buf = Buffer.alloc(3);
|
|
246
|
+
buf[0] = 0x81;
|
|
247
|
+
buf[1] = 0x80 | 126; // masked, 16-bit extended
|
|
248
|
+
// Missing the 2 length bytes + mask
|
|
249
|
+
const result = ws.decodeFrame(buf);
|
|
250
|
+
assert.strictEqual(result, null);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('rejects unmasked client frame', () => {
|
|
254
|
+
// Server MUST reject unmasked client frames per RFC 6455 Section 5.1
|
|
255
|
+
const buf = Buffer.alloc(7);
|
|
256
|
+
buf[0] = 0x81; // FIN + TEXT
|
|
257
|
+
buf[1] = 5; // length 5, NO mask bit
|
|
258
|
+
Buffer.from('Hello').copy(buf, 2);
|
|
259
|
+
assert.throws(() => ws.decodeFrame(buf), /mask/i, 'Should reject unmasked client frame');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('handles multiple frames in a single buffer', () => {
|
|
263
|
+
const frame1 = makeClientFrame(0x01, 'first');
|
|
264
|
+
const frame2 = makeClientFrame(0x01, 'second');
|
|
265
|
+
const combined = Buffer.concat([frame1, frame2]);
|
|
266
|
+
|
|
267
|
+
const result1 = ws.decodeFrame(combined);
|
|
268
|
+
assert(result1, 'Should decode first frame');
|
|
269
|
+
assert.strictEqual(result1.payload.toString(), 'first');
|
|
270
|
+
assert.strictEqual(result1.bytesConsumed, frame1.length);
|
|
271
|
+
|
|
272
|
+
const result2 = ws.decodeFrame(combined.slice(result1.bytesConsumed));
|
|
273
|
+
assert(result2, 'Should decode second frame');
|
|
274
|
+
assert.strictEqual(result2.payload.toString(), 'second');
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('correctly unmasks with all mask byte values', () => {
|
|
278
|
+
// Use a known mask to verify unmasking arithmetic
|
|
279
|
+
const payload = Buffer.from('ABCDEFGH');
|
|
280
|
+
const mask = Buffer.from([0xFF, 0x00, 0xAA, 0x55]);
|
|
281
|
+
const masked = Buffer.alloc(payload.length);
|
|
282
|
+
for (let i = 0; i < payload.length; i++) {
|
|
283
|
+
masked[i] = payload[i] ^ mask[i % 4];
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Build frame manually
|
|
287
|
+
const header = Buffer.alloc(6);
|
|
288
|
+
header[0] = 0x81; // FIN + TEXT
|
|
289
|
+
header[1] = 0x80 | payload.length;
|
|
290
|
+
mask.copy(header, 2);
|
|
291
|
+
const frame = Buffer.concat([header, masked]);
|
|
292
|
+
|
|
293
|
+
const result = ws.decodeFrame(frame);
|
|
294
|
+
assert.strictEqual(result.payload.toString(), 'ABCDEFGH');
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
// ========== Frame Encoding Boundary at 65535/65536 ==========
|
|
298
|
+
console.log('\n--- Frame Size Boundaries ---');
|
|
299
|
+
|
|
300
|
+
test('encodes frame at exactly 65535 bytes (max 16-bit)', () => {
|
|
301
|
+
const payload = Buffer.alloc(65535, 0x45);
|
|
302
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
303
|
+
assert.strictEqual(frame[1], 126);
|
|
304
|
+
assert.strictEqual(frame.readUInt16BE(2), 65535);
|
|
305
|
+
assert.strictEqual(frame.length, 4 + 65535);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
test('encodes frame at exactly 65536 bytes (min 64-bit)', () => {
|
|
309
|
+
const payload = Buffer.alloc(65536, 0x46);
|
|
310
|
+
const frame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
311
|
+
assert.strictEqual(frame[1], 127);
|
|
312
|
+
assert.strictEqual(Number(frame.readBigUInt64BE(2)), 65536);
|
|
313
|
+
assert.strictEqual(frame.length, 10 + 65536);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
test('decodes frame at 65535 bytes boundary', () => {
|
|
317
|
+
const payload = 'X'.repeat(65535);
|
|
318
|
+
const frame = makeClientFrame(0x01, payload);
|
|
319
|
+
const result = ws.decodeFrame(frame);
|
|
320
|
+
assert(result);
|
|
321
|
+
assert.strictEqual(result.payload.length, 65535);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
test('decodes frame at 65536 bytes boundary', () => {
|
|
325
|
+
const payload = 'Y'.repeat(65536);
|
|
326
|
+
const frame = makeClientFrame(0x01, payload);
|
|
327
|
+
const result = ws.decodeFrame(frame);
|
|
328
|
+
assert(result);
|
|
329
|
+
assert.strictEqual(result.payload.length, 65536);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// ========== Close Frame with Status Code ==========
|
|
333
|
+
console.log('\n--- Close Frame Details ---');
|
|
334
|
+
|
|
335
|
+
test('decodes close frame with status code', () => {
|
|
336
|
+
// Close frame payload: 2-byte status code + optional reason
|
|
337
|
+
const statusBuf = Buffer.alloc(2);
|
|
338
|
+
statusBuf.writeUInt16BE(1000); // Normal closure
|
|
339
|
+
const frame = makeClientFrame(0x08, statusBuf);
|
|
340
|
+
const result = ws.decodeFrame(frame);
|
|
341
|
+
assert.strictEqual(result.opcode, ws.OPCODES.CLOSE);
|
|
342
|
+
assert.strictEqual(result.payload.readUInt16BE(0), 1000);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test('decodes close frame with status code and reason', () => {
|
|
346
|
+
const reason = 'Normal shutdown';
|
|
347
|
+
const payload = Buffer.alloc(2 + reason.length);
|
|
348
|
+
payload.writeUInt16BE(1000);
|
|
349
|
+
payload.write(reason, 2);
|
|
350
|
+
const frame = makeClientFrame(0x08, payload);
|
|
351
|
+
const result = ws.decodeFrame(frame);
|
|
352
|
+
assert.strictEqual(result.opcode, ws.OPCODES.CLOSE);
|
|
353
|
+
assert.strictEqual(result.payload.slice(2).toString(), reason);
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// ========== JSON Roundtrip ==========
|
|
357
|
+
console.log('\n--- JSON Message Roundtrip ---');
|
|
358
|
+
|
|
359
|
+
test('roundtrip encode/decode of JSON message', () => {
|
|
360
|
+
const msg = { type: 'reload' };
|
|
361
|
+
const payload = Buffer.from(JSON.stringify(msg));
|
|
362
|
+
const serverFrame = ws.encodeFrame(ws.OPCODES.TEXT, payload);
|
|
363
|
+
|
|
364
|
+
// Verify we can read what we encoded (unmasked server frame)
|
|
365
|
+
// Server frames don't go through decodeFrame (that expects masked),
|
|
366
|
+
// so just verify the payload bytes directly
|
|
367
|
+
let offset;
|
|
368
|
+
if (serverFrame[1] < 126) {
|
|
369
|
+
offset = 2;
|
|
370
|
+
} else if (serverFrame[1] === 126) {
|
|
371
|
+
offset = 4;
|
|
372
|
+
} else {
|
|
373
|
+
offset = 10;
|
|
374
|
+
}
|
|
375
|
+
const decoded = JSON.parse(serverFrame.slice(offset).toString());
|
|
376
|
+
assert.deepStrictEqual(decoded, msg);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
test('roundtrip masked client JSON message', () => {
|
|
380
|
+
const msg = { type: 'click', choice: 'a', text: 'Option A', timestamp: 1706000101 };
|
|
381
|
+
const frame = makeClientFrame(0x01, JSON.stringify(msg));
|
|
382
|
+
const result = ws.decodeFrame(frame);
|
|
383
|
+
const decoded = JSON.parse(result.payload.toString());
|
|
384
|
+
assert.deepStrictEqual(decoded, msg);
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// ========== Summary ==========
|
|
388
|
+
console.log(`\n--- Results: ${passed} passed, ${failed} failed ---`);
|
|
389
|
+
if (failed > 0) process.exit(1);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
runTests();
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# Claude Code Skills Tests
|
|
2
|
+
|
|
3
|
+
Automated tests for superpowers skills using Claude Code CLI.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This test suite verifies that skills are loaded correctly and Claude follows them as expected. Tests invoke Claude Code in headless mode (`claude -p`) and verify the behavior.
|
|
8
|
+
|
|
9
|
+
## Requirements
|
|
10
|
+
|
|
11
|
+
- Claude Code CLI installed and in PATH (`claude --version` should work)
|
|
12
|
+
- Local superpowers plugin installed (see main README for installation)
|
|
13
|
+
|
|
14
|
+
## Running Tests
|
|
15
|
+
|
|
16
|
+
### Run all fast tests (recommended):
|
|
17
|
+
```bash
|
|
18
|
+
./run-skill-tests.sh
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
### Run integration tests (slow, 10-30 minutes):
|
|
22
|
+
```bash
|
|
23
|
+
./run-skill-tests.sh --integration
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Run specific test:
|
|
27
|
+
```bash
|
|
28
|
+
./run-skill-tests.sh --test test-subagent-driven-development.sh
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Run with verbose output:
|
|
32
|
+
```bash
|
|
33
|
+
./run-skill-tests.sh --verbose
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Set custom timeout:
|
|
37
|
+
```bash
|
|
38
|
+
./run-skill-tests.sh --timeout 1800 # 30 minutes for integration tests
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Test Structure
|
|
42
|
+
|
|
43
|
+
### test-helpers.sh
|
|
44
|
+
Common functions for skills testing:
|
|
45
|
+
- `run_claude "prompt" [timeout]` - Run Claude with prompt
|
|
46
|
+
- `assert_contains output pattern name` - Verify pattern exists
|
|
47
|
+
- `assert_not_contains output pattern name` - Verify pattern absent
|
|
48
|
+
- `assert_count output pattern count name` - Verify exact count
|
|
49
|
+
- `assert_order output pattern_a pattern_b name` - Verify order
|
|
50
|
+
- `create_test_project` - Create temp test directory
|
|
51
|
+
- `create_test_plan project_dir` - Create sample plan file
|
|
52
|
+
|
|
53
|
+
### Test Files
|
|
54
|
+
|
|
55
|
+
Each test file:
|
|
56
|
+
1. Sources `test-helpers.sh`
|
|
57
|
+
2. Runs Claude Code with specific prompts
|
|
58
|
+
3. Verifies expected behavior using assertions
|
|
59
|
+
4. Returns 0 on success, non-zero on failure
|
|
60
|
+
|
|
61
|
+
## Example Test
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
#!/usr/bin/env bash
|
|
65
|
+
set -euo pipefail
|
|
66
|
+
|
|
67
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
68
|
+
source "$SCRIPT_DIR/test-helpers.sh"
|
|
69
|
+
|
|
70
|
+
echo "=== Test: My Skill ==="
|
|
71
|
+
|
|
72
|
+
# Ask Claude about the skill
|
|
73
|
+
output=$(run_claude "What does the my-skill skill do?" 30)
|
|
74
|
+
|
|
75
|
+
# Verify response
|
|
76
|
+
assert_contains "$output" "expected behavior" "Skill describes behavior"
|
|
77
|
+
|
|
78
|
+
echo "=== All tests passed ==="
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Current Tests
|
|
82
|
+
|
|
83
|
+
### Fast Tests (run by default)
|
|
84
|
+
|
|
85
|
+
#### test-subagent-driven-development.sh
|
|
86
|
+
Tests skill content and requirements (~2 minutes):
|
|
87
|
+
- Skill loading and accessibility
|
|
88
|
+
- Workflow ordering (spec compliance before code quality)
|
|
89
|
+
- Self-review requirements documented
|
|
90
|
+
- Plan reading efficiency documented
|
|
91
|
+
- Spec compliance reviewer skepticism documented
|
|
92
|
+
- Review loops documented
|
|
93
|
+
- Task context provision documented
|
|
94
|
+
|
|
95
|
+
### Integration Tests (use --integration flag)
|
|
96
|
+
|
|
97
|
+
#### test-subagent-driven-development-integration.sh
|
|
98
|
+
Full workflow execution test (~10-30 minutes):
|
|
99
|
+
- Creates real test project with Node.js setup
|
|
100
|
+
- Creates implementation plan with 2 tasks
|
|
101
|
+
- Executes plan using subagent-driven-development
|
|
102
|
+
- Verifies actual behaviors:
|
|
103
|
+
- Plan read once at start (not per task)
|
|
104
|
+
- Full task text provided in subagent prompts
|
|
105
|
+
- Subagents perform self-review before reporting
|
|
106
|
+
- Spec compliance review happens before code quality
|
|
107
|
+
- Spec reviewer reads code independently
|
|
108
|
+
- Working implementation is produced
|
|
109
|
+
- Tests pass
|
|
110
|
+
- Proper git commits created
|
|
111
|
+
|
|
112
|
+
**What it tests:**
|
|
113
|
+
- The workflow actually works end-to-end
|
|
114
|
+
- Our improvements are actually applied
|
|
115
|
+
- Subagents follow the skill correctly
|
|
116
|
+
- Final code is functional and tested
|
|
117
|
+
|
|
118
|
+
## Adding New Tests
|
|
119
|
+
|
|
120
|
+
1. Create new test file: `test-<skill-name>.sh`
|
|
121
|
+
2. Source test-helpers.sh
|
|
122
|
+
3. Write tests using `run_claude` and assertions
|
|
123
|
+
4. Add to test list in `run-skill-tests.sh`
|
|
124
|
+
5. Make executable: `chmod +x test-<skill-name>.sh`
|
|
125
|
+
|
|
126
|
+
## Timeout Considerations
|
|
127
|
+
|
|
128
|
+
- Default timeout: 5 minutes per test
|
|
129
|
+
- Claude Code may take time to respond
|
|
130
|
+
- Adjust with `--timeout` if needed
|
|
131
|
+
- Tests should be focused to avoid long runs
|
|
132
|
+
|
|
133
|
+
## Debugging Failed Tests
|
|
134
|
+
|
|
135
|
+
With `--verbose`, you'll see full Claude output:
|
|
136
|
+
```bash
|
|
137
|
+
./run-skill-tests.sh --verbose --test test-subagent-driven-development.sh
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Without verbose, only failures show output.
|
|
141
|
+
|
|
142
|
+
## CI/CD Integration
|
|
143
|
+
|
|
144
|
+
To run in CI:
|
|
145
|
+
```bash
|
|
146
|
+
# Run with explicit timeout for CI environments
|
|
147
|
+
./run-skill-tests.sh --timeout 900
|
|
148
|
+
|
|
149
|
+
# Exit code 0 = success, non-zero = failure
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Notes
|
|
153
|
+
|
|
154
|
+
- Tests verify skill *instructions*, not full execution
|
|
155
|
+
- Full workflow tests would be very slow
|
|
156
|
+
- Focus on verifying key skill requirements
|
|
157
|
+
- Tests should be deterministic
|
|
158
|
+
- Avoid testing implementation details
|