@tekyzinc/gsd-t 3.13.16 → 3.16.11

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 (54) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +1 -0
  3. package/bin/gsd-t-benchmark-orchestrator.js +437 -0
  4. package/bin/gsd-t-capture-lint.cjs +276 -0
  5. package/bin/gsd-t-completion-check.cjs +106 -0
  6. package/bin/gsd-t-orchestrator-config.cjs +64 -0
  7. package/bin/gsd-t-orchestrator-queue.cjs +180 -0
  8. package/bin/gsd-t-orchestrator-recover.cjs +231 -0
  9. package/bin/gsd-t-orchestrator-worker.cjs +219 -0
  10. package/bin/gsd-t-orchestrator.js +534 -0
  11. package/bin/gsd-t-stream-feed-client.cjs +151 -0
  12. package/bin/gsd-t-task-brief-compactor.cjs +89 -0
  13. package/bin/gsd-t-task-brief-template.cjs +96 -0
  14. package/bin/gsd-t-task-brief.js +249 -0
  15. package/bin/gsd-t-token-backfill.cjs +366 -0
  16. package/bin/gsd-t-token-capture.cjs +306 -0
  17. package/bin/gsd-t-token-dashboard.cjs +318 -0
  18. package/bin/gsd-t-token-regenerate-log.cjs +129 -0
  19. package/bin/gsd-t-transcript-tee.cjs +246 -0
  20. package/bin/gsd-t-unattended-heartbeat.cjs +188 -0
  21. package/bin/gsd-t-unattended-platform.cjs +191 -27
  22. package/bin/gsd-t-unattended-safety.cjs +8 -1
  23. package/bin/gsd-t-unattended.cjs +192 -31
  24. package/bin/gsd-t.js +329 -2
  25. package/bin/supervisor-pid-fingerprint.cjs +126 -0
  26. package/commands/gsd-t-debug.md +63 -51
  27. package/commands/gsd-t-design-decompose.md +2 -7
  28. package/commands/gsd-t-doc-ripple.md +20 -11
  29. package/commands/gsd-t-execute.md +82 -50
  30. package/commands/gsd-t-integrate.md +43 -16
  31. package/commands/gsd-t-plan.md +20 -7
  32. package/commands/gsd-t-prd.md +19 -12
  33. package/commands/gsd-t-quick.md +64 -29
  34. package/commands/gsd-t-resume.md +51 -4
  35. package/commands/gsd-t-unattended.md +19 -20
  36. package/commands/gsd-t-verify.md +48 -32
  37. package/commands/gsd-t-visualize.md +19 -17
  38. package/commands/gsd-t-wave.md +29 -27
  39. package/docs/architecture.md +16 -0
  40. package/docs/m40-benchmark-report.md +35 -0
  41. package/docs/requirements.md +20 -0
  42. package/package.json +1 -1
  43. package/scripts/gsd-t-dashboard-server.js +291 -4
  44. package/scripts/gsd-t-dashboard.html +31 -1
  45. package/scripts/gsd-t-design-review-server.js +3 -1
  46. package/scripts/gsd-t-stream-feed-server.js +428 -0
  47. package/scripts/gsd-t-stream-feed.html +1168 -0
  48. package/scripts/gsd-t-token-aggregator.js +373 -0
  49. package/scripts/gsd-t-transcript.html +422 -0
  50. package/scripts/hooks/gsd-t-in-session-probe.js +62 -0
  51. package/scripts/hooks/pre-commit-capture-lint +26 -0
  52. package/templates/CLAUDE-global.md +69 -0
  53. package/scripts/gsd-t-agent-dashboard-server.js +0 -424
  54. package/scripts/gsd-t-agent-dashboard.html +0 -1043
@@ -0,0 +1,428 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * GSD-T Stream Feed Server (M40 D4)
4
+ *
5
+ * Ingests stream-json frames from D1 workers via chunked HTTP POST to /ingest,
6
+ * persists every frame to .gsd-t/stream-feed/YYYY-MM-DD.jsonl (persist-before-broadcast),
7
+ * rotates daily at UTC midnight, broadcasts over WebSocket at /feed (with ?from=N replay),
8
+ * enforces 127.0.0.1-only, kicks backpressured clients after 1000-frame buffer.
9
+ *
10
+ * Contract: .gsd-t/contracts/stream-json-sink-contract.md v1.x
11
+ * Zero external deps — node http + crypto + fs + net only. Hand-rolled WS framing.
12
+ */
13
+ 'use strict';
14
+
15
+ const http = require('http');
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const crypto = require('crypto');
19
+
20
+ const DEFAULT_PORT = 7842;
21
+ const BACKPRESSURE_LIMIT = 1000;
22
+ const WS_GUID = '258EAFA5-E914-47DA-95CA-C5AB0DC85B11';
23
+
24
+ // ── Frame store ────────────────────────────────────────────────────────────
25
+
26
+ function createStreamFeedServer(opts = {}) {
27
+ const projectDir = opts.projectDir || process.cwd();
28
+ const port = Number(opts.port || process.env.GSD_T_STREAM_FEED_PORT || DEFAULT_PORT);
29
+ const feedDir = path.join(projectDir, '.gsd-t', 'stream-feed');
30
+ try { fs.mkdirSync(feedDir, { recursive: true }); } catch { /* exists */ }
31
+
32
+ const clients = new Set();
33
+ const stats = { framesIngested: 0, framesBroadcast: 0, clientsConnected: 0, kicked: 0 };
34
+ const RECENT_BUFFER_MAX = opts.recentBufferMax || 10000;
35
+ const recentFrames = []; // In-memory mirror of today's JSONL for replay durability.
36
+
37
+ let currentDate = currentUtcDate();
38
+ let currentFile = path.join(feedDir, `${currentDate}.jsonl`);
39
+ let framesToday = countLines(currentFile);
40
+ let writeStream = fs.createWriteStream(currentFile, { flags: 'a' });
41
+ // Prime recent buffer from any pre-existing content so replay works from first connect.
42
+ try {
43
+ if (fs.existsSync(currentFile)) {
44
+ const existing = fs.readFileSync(currentFile, 'utf8').split('\n').filter(l => l.length > 0);
45
+ for (const l of existing.slice(-RECENT_BUFFER_MAX)) recentFrames.push(l);
46
+ }
47
+ } catch { /* noop */ }
48
+
49
+ function rotateIfNeeded() {
50
+ const d = currentUtcDate();
51
+ if (d === currentDate) return;
52
+ try { writeStream.end(); } catch { /* noop */ }
53
+ currentDate = d;
54
+ currentFile = path.join(feedDir, `${currentDate}.jsonl`);
55
+ framesToday = countLines(currentFile);
56
+ writeStream = fs.createWriteStream(currentFile, { flags: 'a' });
57
+ }
58
+
59
+ function persistFrame(line) {
60
+ rotateIfNeeded();
61
+ try {
62
+ writeStream.write(line + '\n');
63
+ framesToday += 1;
64
+ stats.framesIngested += 1;
65
+ recentFrames.push(line);
66
+ if (recentFrames.length > RECENT_BUFFER_MAX) recentFrames.shift();
67
+ return true;
68
+ } catch (e) {
69
+ process.stderr.write(`[stream-feed-server] persist failed: ${e.message}\n`);
70
+ return false;
71
+ }
72
+ }
73
+
74
+ function broadcast(line) {
75
+ for (const client of clients) {
76
+ if (client.sendBuffer.length >= BACKPRESSURE_LIMIT) {
77
+ kickClient(client, 'backpressure');
78
+ continue;
79
+ }
80
+ client.sendBuffer.push(line);
81
+ flushClient(client);
82
+ stats.framesBroadcast += 1;
83
+ }
84
+ }
85
+
86
+ function flushClient(client) {
87
+ if (client.flushing || client.closed) return;
88
+ client.flushing = true;
89
+ while (client.sendBuffer.length > 0 && !client.closed) {
90
+ const line = client.sendBuffer.shift();
91
+ const frame = encodeWsTextFrame(line);
92
+ try {
93
+ const ok = client.socket.write(frame);
94
+ if (!ok) {
95
+ client.socket.once('drain', () => { client.flushing = false; flushClient(client); });
96
+ return;
97
+ }
98
+ } catch {
99
+ client.closed = true;
100
+ clients.delete(client);
101
+ return;
102
+ }
103
+ }
104
+ client.flushing = false;
105
+ }
106
+
107
+ function kickClient(client, reason) {
108
+ if (client.closed) return;
109
+ const kick = JSON.stringify({ type: 'kicked', reason });
110
+ try {
111
+ client.socket.write(encodeWsTextFrame(kick));
112
+ client.socket.end(encodeWsCloseFrame(1001, reason));
113
+ } catch { /* socket dead */ }
114
+ client.closed = true;
115
+ client.sendBuffer.length = 0;
116
+ clients.delete(client);
117
+ stats.kicked += 1;
118
+ }
119
+
120
+ function replayToClient(client, fromLine) {
121
+ // Prefer in-memory buffer (survives write-stream lag). Fall back to file.
122
+ try {
123
+ let lines;
124
+ if (recentFrames.length > 0) {
125
+ lines = recentFrames;
126
+ } else if (fs.existsSync(currentFile)) {
127
+ const content = fs.readFileSync(currentFile, 'utf8');
128
+ lines = content.split('\n').filter(l => l.length > 0);
129
+ } else {
130
+ lines = [];
131
+ }
132
+ const start = Math.max(0, Math.min(fromLine || 0, lines.length));
133
+ for (let i = start; i < lines.length; i++) {
134
+ client.sendBuffer.push(lines[i]);
135
+ if (client.sendBuffer.length >= BACKPRESSURE_LIMIT) {
136
+ kickClient(client, 'backpressure');
137
+ return;
138
+ }
139
+ }
140
+ flushClient(client);
141
+ } catch (e) {
142
+ process.stderr.write(`[stream-feed-server] replay failed: ${e.message}\n`);
143
+ }
144
+ }
145
+
146
+ const server = http.createServer((req, res) => {
147
+ const u = parseUrl(req.url);
148
+
149
+ if (req.method === 'POST' && u.pathname === '/ingest') {
150
+ ingestStream(req, res, { persistFrame, broadcast });
151
+ return;
152
+ }
153
+ if (req.method === 'GET' && u.pathname === '/status') {
154
+ const body = JSON.stringify({
155
+ status: 'ok', port, currentFile, framesToday,
156
+ clients: clients.size, stats,
157
+ });
158
+ res.writeHead(200, { 'Content-Type': 'application/json' });
159
+ res.end(body);
160
+ return;
161
+ }
162
+ if (req.method === 'GET' && u.pathname === '/') {
163
+ res.writeHead(200, { 'Content-Type': 'text/plain' });
164
+ res.end(`GSD-T Stream Feed Server\nport=${port}\nframesToday=${framesToday}\nclients=${clients.size}\n`);
165
+ return;
166
+ }
167
+
168
+ res.writeHead(404);
169
+ res.end('not found');
170
+ });
171
+
172
+ // Enforce 127.0.0.1-only at the socket level.
173
+ server.on('connection', (socket) => {
174
+ const addr = socket.remoteAddress;
175
+ if (!isLoopback(addr)) {
176
+ try { socket.destroy(); } catch { /* noop */ }
177
+ }
178
+ });
179
+
180
+ // WebSocket upgrade
181
+ server.on('upgrade', (req, socket, head) => {
182
+ const addr = socket.remoteAddress;
183
+ if (!isLoopback(addr)) {
184
+ try { socket.destroy(); } catch { /* noop */ }
185
+ return;
186
+ }
187
+ const u = parseUrl(req.url);
188
+ if (u.pathname !== '/feed') {
189
+ socket.write('HTTP/1.1 404 Not Found\r\n\r\n');
190
+ socket.destroy();
191
+ return;
192
+ }
193
+ const key = req.headers['sec-websocket-key'];
194
+ if (!key) { socket.destroy(); return; }
195
+ const accept = crypto.createHash('sha1').update(key + WS_GUID).digest('base64');
196
+ const headers = [
197
+ 'HTTP/1.1 101 Switching Protocols',
198
+ 'Upgrade: websocket',
199
+ 'Connection: Upgrade',
200
+ `Sec-WebSocket-Accept: ${accept}`,
201
+ '\r\n',
202
+ ].join('\r\n');
203
+ socket.write(headers);
204
+
205
+ const client = { socket, sendBuffer: [], flushing: false, closed: false };
206
+ clients.add(client);
207
+ stats.clientsConnected += 1;
208
+
209
+ const cleanupClient = () => {
210
+ if (client.closed) return;
211
+ client.closed = true;
212
+ clients.delete(client);
213
+ try { socket.destroy(); } catch { /* noop */ }
214
+ };
215
+ socket.on('close', cleanupClient);
216
+ socket.on('error', cleanupClient);
217
+ socket.on('end', cleanupClient);
218
+ socket.on('data', (buf) => {
219
+ const frames = decodeWsFrames(buf);
220
+ for (const f of frames) {
221
+ if (f.opcode === 0x8) { // close
222
+ client.closed = true;
223
+ try { socket.end(); } catch { /* noop */ }
224
+ clients.delete(client);
225
+ }
226
+ // Ping/pong/other opcodes ignored (no app-level protocol).
227
+ }
228
+ });
229
+
230
+ const fromLine = parseInt(u.query.from, 10);
231
+ if (!Number.isNaN(fromLine)) {
232
+ replayToClient(client, fromLine);
233
+ } else {
234
+ replayToClient(client, 0);
235
+ }
236
+ });
237
+
238
+ function stop() {
239
+ return new Promise((resolve) => {
240
+ for (const client of clients) {
241
+ try { client.socket.destroy(); } catch { /* noop */ }
242
+ }
243
+ clients.clear();
244
+ try { writeStream.end(); } catch { /* noop */ }
245
+ server.close(() => resolve());
246
+ });
247
+ }
248
+
249
+ return {
250
+ server, port, projectDir,
251
+ listen(cb) { server.listen(port, '127.0.0.1', cb); },
252
+ stop,
253
+ stats,
254
+ _internal: { clients, persistFrame, broadcast, rotateIfNeeded, replayToClient, recentFrames },
255
+ };
256
+ }
257
+
258
+ // ── POST /ingest handler ──────────────────────────────────────────────────
259
+
260
+ function ingestStream(req, res, { persistFrame, broadcast }) {
261
+ let buf = '';
262
+ req.on('data', (chunk) => {
263
+ buf += chunk.toString('utf8');
264
+ let idx;
265
+ while ((idx = buf.indexOf('\n')) !== -1) {
266
+ const line = buf.slice(0, idx).trim();
267
+ buf = buf.slice(idx + 1);
268
+ if (line.length === 0) continue;
269
+ // Validate it's a JSON line; reject malformed but don't crash.
270
+ try { JSON.parse(line); } catch { continue; }
271
+ const ok = persistFrame(line);
272
+ if (ok) broadcast(line);
273
+ }
274
+ });
275
+ req.on('end', () => {
276
+ if (buf.trim().length > 0) {
277
+ const line = buf.trim();
278
+ try { JSON.parse(line); persistFrame(line); broadcast(line); } catch { /* drop */ }
279
+ }
280
+ res.writeHead(200, { 'Content-Type': 'application/json' });
281
+ res.end(JSON.stringify({ ok: true }));
282
+ });
283
+ req.on('error', () => {
284
+ try { res.writeHead(400); res.end('ingest error'); } catch { /* noop */ }
285
+ });
286
+ }
287
+
288
+ // ── Helpers ───────────────────────────────────────────────────────────────
289
+
290
+ function currentUtcDate() {
291
+ return new Date().toISOString().slice(0, 10);
292
+ }
293
+
294
+ function countLines(file) {
295
+ try {
296
+ const content = fs.readFileSync(file, 'utf8');
297
+ return content.split('\n').filter(l => l.length > 0).length;
298
+ } catch { return 0; }
299
+ }
300
+
301
+ function parseUrl(u) {
302
+ const q = {};
303
+ const [pathname, qs] = u.split('?');
304
+ if (qs) {
305
+ for (const pair of qs.split('&')) {
306
+ const [k, v] = pair.split('=');
307
+ q[decodeURIComponent(k || '')] = decodeURIComponent(v || '');
308
+ }
309
+ }
310
+ return { pathname, query: q };
311
+ }
312
+
313
+ function isLoopback(addr) {
314
+ if (!addr) return false;
315
+ return addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';
316
+ }
317
+
318
+ // ── WebSocket framing (hand-rolled, RFC 6455) ─────────────────────────────
319
+
320
+ function encodeWsTextFrame(str) {
321
+ const data = Buffer.from(str, 'utf8');
322
+ const len = data.length;
323
+ let header;
324
+ if (len < 126) {
325
+ header = Buffer.alloc(2);
326
+ header[0] = 0x81; // FIN + text
327
+ header[1] = len;
328
+ } else if (len < 65536) {
329
+ header = Buffer.alloc(4);
330
+ header[0] = 0x81;
331
+ header[1] = 126;
332
+ header.writeUInt16BE(len, 2);
333
+ } else {
334
+ header = Buffer.alloc(10);
335
+ header[0] = 0x81;
336
+ header[1] = 127;
337
+ header.writeUInt32BE(0, 2);
338
+ header.writeUInt32BE(len, 6);
339
+ }
340
+ return Buffer.concat([header, data]);
341
+ }
342
+
343
+ function encodeWsCloseFrame(code, reason) {
344
+ const r = Buffer.from(reason || '', 'utf8');
345
+ const body = Buffer.alloc(2 + r.length);
346
+ body.writeUInt16BE(code || 1000, 0);
347
+ r.copy(body, 2);
348
+ const header = Buffer.alloc(2);
349
+ header[0] = 0x88;
350
+ header[1] = body.length;
351
+ return Buffer.concat([header, body]);
352
+ }
353
+
354
+ function decodeWsFrames(buf) {
355
+ const frames = [];
356
+ let i = 0;
357
+ while (i < buf.length) {
358
+ if (buf.length - i < 2) break;
359
+ const b0 = buf[i];
360
+ const b1 = buf[i + 1];
361
+ const opcode = b0 & 0x0f;
362
+ const masked = (b1 & 0x80) !== 0;
363
+ let len = b1 & 0x7f;
364
+ let offset = i + 2;
365
+ if (len === 126) {
366
+ if (buf.length - offset < 2) break;
367
+ len = buf.readUInt16BE(offset); offset += 2;
368
+ } else if (len === 127) {
369
+ if (buf.length - offset < 8) break;
370
+ offset += 4;
371
+ len = buf.readUInt32BE(offset); offset += 4;
372
+ }
373
+ let maskKey;
374
+ if (masked) {
375
+ if (buf.length - offset < 4) break;
376
+ maskKey = buf.slice(offset, offset + 4);
377
+ offset += 4;
378
+ }
379
+ if (buf.length - offset < len) break;
380
+ let payload = buf.slice(offset, offset + len);
381
+ if (masked && maskKey) {
382
+ const out = Buffer.alloc(payload.length);
383
+ for (let j = 0; j < payload.length; j++) out[j] = payload[j] ^ maskKey[j % 4];
384
+ payload = out;
385
+ }
386
+ frames.push({ opcode, payload });
387
+ i = offset + len;
388
+ }
389
+ return frames;
390
+ }
391
+
392
+ module.exports = {
393
+ createStreamFeedServer,
394
+ _testing: {
395
+ encodeWsTextFrame,
396
+ decodeWsFrames,
397
+ isLoopback,
398
+ parseUrl,
399
+ currentUtcDate,
400
+ countLines,
401
+ },
402
+ };
403
+
404
+ // ── CLI ───────────────────────────────────────────────────────────────────
405
+
406
+ if (require.main === module) {
407
+ const args = process.argv.slice(2);
408
+ const opts = {};
409
+ for (let i = 0; i < args.length; i++) {
410
+ if (args[i] === '--port') opts.port = Number(args[++i]);
411
+ else if (args[i] === '--project-dir') opts.projectDir = args[++i];
412
+ }
413
+ const srv = createStreamFeedServer(opts);
414
+ srv.listen(() => {
415
+ process.stdout.write(`[stream-feed-server] listening on http://127.0.0.1:${srv.port}\n`);
416
+ process.stdout.write(`[stream-feed-server] project: ${srv.projectDir}\n`);
417
+ });
418
+ let shuttingDown = false;
419
+ async function shutdown(sig) {
420
+ if (shuttingDown) return;
421
+ shuttingDown = true;
422
+ process.stderr.write(`[stream-feed-server] ${sig} — shutting down\n`);
423
+ await srv.stop();
424
+ process.exit(0);
425
+ }
426
+ process.on('SIGINT', () => shutdown('SIGINT'));
427
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
428
+ }