@tekyzinc/gsd-t 2.20.4 → 2.20.6
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/CHANGELOG.md +19 -0
- package/bin/gsd-t.js +1309 -1163
- package/commands/Claude-md.md +2 -0
- package/commands/gsd-t-execute.md +15 -2
- package/commands/gsd-t-scan.md +16 -0
- package/commands/gsd-t-test-sync.md +11 -0
- package/commands/gsd-t-verify.md +11 -0
- package/package.json +39 -35
- package/scripts/gsd-t-heartbeat.js +198 -156
- package/templates/CLAUDE-global.md +34 -0
package/bin/gsd-t.js
CHANGED
|
@@ -1,1163 +1,1309 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GSD-T CLI Installer
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx @tekyzinc/gsd-t install — Install commands + global CLAUDE.md
|
|
8
|
-
* npx @tekyzinc/gsd-t update — Update commands + global CLAUDE.md (preserves customizations)
|
|
9
|
-
* npx @tekyzinc/gsd-t update-all — Update globally + all registered project CLAUDE.md files
|
|
10
|
-
* npx @tekyzinc/gsd-t init [name] — Initialize a new project with GSD-T structure (auto-registers)
|
|
11
|
-
* npx @tekyzinc/gsd-t register — Register current directory as a GSD-T project
|
|
12
|
-
* npx @tekyzinc/gsd-t status — Show what's installed and check for updates
|
|
13
|
-
* npx @tekyzinc/gsd-t uninstall — Remove GSD-T commands (leaves project files alone)
|
|
14
|
-
* npx @tekyzinc/gsd-t doctor — Diagnose common issues
|
|
15
|
-
* npx @tekyzinc/gsd-t changelog — Open changelog in the browser
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const fs = require("fs");
|
|
19
|
-
const path = require("path");
|
|
20
|
-
const os = require("os");
|
|
21
|
-
const {
|
|
22
|
-
|
|
23
|
-
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
24
|
-
|
|
25
|
-
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
26
|
-
const COMMANDS_DIR = path.join(CLAUDE_DIR, "commands");
|
|
27
|
-
const SCRIPTS_DIR = path.join(CLAUDE_DIR, "scripts");
|
|
28
|
-
const GLOBAL_CLAUDE_MD = path.join(CLAUDE_DIR, "CLAUDE.md");
|
|
29
|
-
const SETTINGS_JSON = path.join(CLAUDE_DIR, "settings.json");
|
|
30
|
-
const VERSION_FILE = path.join(CLAUDE_DIR, ".gsd-t-version");
|
|
31
|
-
const PROJECTS_FILE = path.join(CLAUDE_DIR, ".gsd-t-projects");
|
|
32
|
-
const UPDATE_CHECK_FILE = path.join(CLAUDE_DIR, ".gsd-t-update-check");
|
|
33
|
-
|
|
34
|
-
// Where our package files live (relative to this script)
|
|
35
|
-
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
36
|
-
const PKG_COMMANDS = path.join(PKG_ROOT, "commands");
|
|
37
|
-
const PKG_SCRIPTS = path.join(PKG_ROOT, "scripts");
|
|
38
|
-
const PKG_TEMPLATES = path.join(PKG_ROOT, "templates");
|
|
39
|
-
const PKG_EXAMPLES = path.join(PKG_ROOT, "examples");
|
|
40
|
-
|
|
41
|
-
// Read our version from package.json
|
|
42
|
-
const PKG_VERSION = require(path.join(PKG_ROOT, "package.json")).version;
|
|
43
|
-
const CHANGELOG_URL = "https://github.com/Tekyz-Inc/get-stuff-done-teams/blob/main/CHANGELOG.md";
|
|
44
|
-
|
|
45
|
-
//
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
function
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
fs.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (
|
|
229
|
-
warn("
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
if (
|
|
332
|
-
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
if (fs.existsSync(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
log(
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
heading(
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
const
|
|
570
|
-
if (
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
const
|
|
611
|
-
const
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
log(
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
""
|
|
728
|
-
|
|
729
|
-
""
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
if (
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
if (
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
}
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
}
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
}
|
|
984
|
-
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
//
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
}
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
}
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
}
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
log(
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
log(
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
//
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD-T CLI Installer
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* npx @tekyzinc/gsd-t install — Install commands + global CLAUDE.md
|
|
8
|
+
* npx @tekyzinc/gsd-t update — Update commands + global CLAUDE.md (preserves customizations)
|
|
9
|
+
* npx @tekyzinc/gsd-t update-all — Update globally + all registered project CLAUDE.md files
|
|
10
|
+
* npx @tekyzinc/gsd-t init [name] — Initialize a new project with GSD-T structure (auto-registers)
|
|
11
|
+
* npx @tekyzinc/gsd-t register — Register current directory as a GSD-T project
|
|
12
|
+
* npx @tekyzinc/gsd-t status — Show what's installed and check for updates
|
|
13
|
+
* npx @tekyzinc/gsd-t uninstall — Remove GSD-T commands (leaves project files alone)
|
|
14
|
+
* npx @tekyzinc/gsd-t doctor — Diagnose common issues
|
|
15
|
+
* npx @tekyzinc/gsd-t changelog — Open changelog in the browser
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require("fs");
|
|
19
|
+
const path = require("path");
|
|
20
|
+
const os = require("os");
|
|
21
|
+
const { execFileSync, spawn: cpSpawn } = require("child_process");
|
|
22
|
+
|
|
23
|
+
// ─── Configuration ───────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
const CLAUDE_DIR = path.join(os.homedir(), ".claude");
|
|
26
|
+
const COMMANDS_DIR = path.join(CLAUDE_DIR, "commands");
|
|
27
|
+
const SCRIPTS_DIR = path.join(CLAUDE_DIR, "scripts");
|
|
28
|
+
const GLOBAL_CLAUDE_MD = path.join(CLAUDE_DIR, "CLAUDE.md");
|
|
29
|
+
const SETTINGS_JSON = path.join(CLAUDE_DIR, "settings.json");
|
|
30
|
+
const VERSION_FILE = path.join(CLAUDE_DIR, ".gsd-t-version");
|
|
31
|
+
const PROJECTS_FILE = path.join(CLAUDE_DIR, ".gsd-t-projects");
|
|
32
|
+
const UPDATE_CHECK_FILE = path.join(CLAUDE_DIR, ".gsd-t-update-check");
|
|
33
|
+
|
|
34
|
+
// Where our package files live (relative to this script)
|
|
35
|
+
const PKG_ROOT = path.resolve(__dirname, "..");
|
|
36
|
+
const PKG_COMMANDS = path.join(PKG_ROOT, "commands");
|
|
37
|
+
const PKG_SCRIPTS = path.join(PKG_ROOT, "scripts");
|
|
38
|
+
const PKG_TEMPLATES = path.join(PKG_ROOT, "templates");
|
|
39
|
+
const PKG_EXAMPLES = path.join(PKG_ROOT, "examples");
|
|
40
|
+
|
|
41
|
+
// Read our version from package.json
|
|
42
|
+
const PKG_VERSION = require(path.join(PKG_ROOT, "package.json")).version;
|
|
43
|
+
const CHANGELOG_URL = "https://github.com/Tekyz-Inc/get-stuff-done-teams/blob/main/CHANGELOG.md";
|
|
44
|
+
|
|
45
|
+
// Destructive Action Guard — injected into project CLAUDE.md files by doUpdateAll
|
|
46
|
+
const GUARD_SECTION = [
|
|
47
|
+
"",
|
|
48
|
+
"",
|
|
49
|
+
"# Destructive Action Guard (MANDATORY)",
|
|
50
|
+
"",
|
|
51
|
+
"**NEVER perform destructive or structural changes without explicit user approval.** This applies at ALL autonomy levels.",
|
|
52
|
+
"",
|
|
53
|
+
"Before any of these actions, STOP and ask the user:",
|
|
54
|
+
"- DROP TABLE, DROP COLUMN, DROP INDEX, TRUNCATE, DELETE without WHERE",
|
|
55
|
+
"- Renaming or removing database tables or columns",
|
|
56
|
+
"- Schema migrations that lose data or break existing queries",
|
|
57
|
+
"- Replacing an existing architecture pattern (e.g., normalized → denormalized)",
|
|
58
|
+
"- Removing or replacing existing files/modules that contain working functionality",
|
|
59
|
+
"- Changing ORM models in ways that conflict with the existing database schema",
|
|
60
|
+
"- Removing API endpoints or changing response shapes that existing clients depend on",
|
|
61
|
+
"- Any change that would require other parts of the system to be rewritten",
|
|
62
|
+
"",
|
|
63
|
+
'**Rule: "Adapt new code to existing structures, not the other way around."**',
|
|
64
|
+
"",
|
|
65
|
+
].join("\n");
|
|
66
|
+
|
|
67
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
68
|
+
|
|
69
|
+
const BOLD = "\x1b[1m";
|
|
70
|
+
const GREEN = "\x1b[32m";
|
|
71
|
+
const YELLOW = "\x1b[33m";
|
|
72
|
+
const RED = "\x1b[31m";
|
|
73
|
+
const CYAN = "\x1b[36m";
|
|
74
|
+
const DIM = "\x1b[2m";
|
|
75
|
+
const RESET = "\x1b[0m";
|
|
76
|
+
|
|
77
|
+
function log(msg) {
|
|
78
|
+
console.log(msg);
|
|
79
|
+
}
|
|
80
|
+
function success(msg) {
|
|
81
|
+
console.log(`${GREEN} ✓${RESET} ${msg}`);
|
|
82
|
+
}
|
|
83
|
+
function warn(msg) {
|
|
84
|
+
console.log(`${YELLOW} ⚠${RESET} ${msg}`);
|
|
85
|
+
}
|
|
86
|
+
function error(msg) {
|
|
87
|
+
console.log(`${RED} ✗${RESET} ${msg}`);
|
|
88
|
+
}
|
|
89
|
+
function info(msg) {
|
|
90
|
+
console.log(`${CYAN} ℹ${RESET} ${msg}`);
|
|
91
|
+
}
|
|
92
|
+
function heading(msg) {
|
|
93
|
+
console.log(`\n${BOLD}${msg}${RESET}`);
|
|
94
|
+
}
|
|
95
|
+
function link(text, url) {
|
|
96
|
+
return `\x1b]8;;${url}\x07${text}\x1b]8;;\x07`;
|
|
97
|
+
}
|
|
98
|
+
function versionLink(ver) {
|
|
99
|
+
return link(`v${ver || PKG_VERSION}`, CHANGELOG_URL);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function ensureDir(dir) {
|
|
103
|
+
if (!fs.existsSync(dir)) {
|
|
104
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function isSymlink(filePath) {
|
|
111
|
+
try {
|
|
112
|
+
return fs.lstatSync(filePath).isSymbolicLink();
|
|
113
|
+
} catch {
|
|
114
|
+
return false; // File doesn't exist yet — safe to write
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function validateProjectName(name) {
|
|
119
|
+
return /^[a-zA-Z0-9][a-zA-Z0-9._\- ]{0,100}$/.test(name);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function applyTokens(content, projectName, date) {
|
|
123
|
+
return content.replace(/\{Project Name\}/g, projectName).replace(/\{Date\}/g, date);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function normalizeEol(str) {
|
|
127
|
+
return str.replace(/\r\n/g, "\n");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function validateVersion(ver) {
|
|
131
|
+
return /^\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$/.test(ver);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function validateProjectPath(p) {
|
|
135
|
+
try {
|
|
136
|
+
return path.isAbsolute(p) && fs.existsSync(p) && fs.statSync(p).isDirectory();
|
|
137
|
+
} catch {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function copyFile(src, dest, label) {
|
|
143
|
+
if (isSymlink(dest)) {
|
|
144
|
+
warn(`Skipping symlink target: ${dest}`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
fs.copyFileSync(src, dest);
|
|
149
|
+
success(label || path.basename(dest));
|
|
150
|
+
} catch (e) {
|
|
151
|
+
error(`Failed to copy ${label || path.basename(dest)}: ${e.message}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function hasPlaywright(projectDir) {
|
|
156
|
+
const configs = ["playwright.config.ts", "playwright.config.js", "playwright.config.mjs"];
|
|
157
|
+
return configs.some((f) => fs.existsSync(path.join(projectDir, f)));
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hasSwagger(projectDir) {
|
|
161
|
+
// Check for OpenAPI/Swagger spec files
|
|
162
|
+
const specFiles = [
|
|
163
|
+
"swagger.json", "swagger.yaml", "swagger.yml",
|
|
164
|
+
"openapi.json", "openapi.yaml", "openapi.yml",
|
|
165
|
+
];
|
|
166
|
+
if (specFiles.some((f) => fs.existsSync(path.join(projectDir, f)))) return true;
|
|
167
|
+
|
|
168
|
+
// Check package.json for swagger dependencies
|
|
169
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
170
|
+
if (fs.existsSync(pkgPath)) {
|
|
171
|
+
try {
|
|
172
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
173
|
+
const allDeps = Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.devDependencies || {}));
|
|
174
|
+
const swaggerPkgs = ["swagger-jsdoc", "swagger-ui-express", "@fastify/swagger", "@nestjs/swagger", "swagger-ui", "express-openapi-validator"];
|
|
175
|
+
if (swaggerPkgs.some((p) => allDeps.includes(p))) return true;
|
|
176
|
+
} catch { /* ignore */ }
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Check for Python FastAPI (has built-in OpenAPI)
|
|
180
|
+
const pyFiles = ["requirements.txt", "pyproject.toml"];
|
|
181
|
+
for (const f of pyFiles) {
|
|
182
|
+
const fp = path.join(projectDir, f);
|
|
183
|
+
if (fs.existsSync(fp)) {
|
|
184
|
+
try {
|
|
185
|
+
const content = fs.readFileSync(fp, "utf8");
|
|
186
|
+
if (content.includes("fastapi")) return true;
|
|
187
|
+
} catch { /* ignore */ }
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function hasApi(projectDir) {
|
|
195
|
+
// Quick check: does this project likely have API endpoints?
|
|
196
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
197
|
+
if (fs.existsSync(pkgPath)) {
|
|
198
|
+
try {
|
|
199
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
200
|
+
const allDeps = Object.keys(pkg.dependencies || {}).concat(Object.keys(pkg.devDependencies || {}));
|
|
201
|
+
const apiFrameworks = ["express", "fastify", "hono", "koa", "hapi", "@nestjs/core", "next"];
|
|
202
|
+
if (apiFrameworks.some((p) => allDeps.includes(p))) return true;
|
|
203
|
+
} catch { /* ignore */ }
|
|
204
|
+
}
|
|
205
|
+
// Check for Python API frameworks
|
|
206
|
+
const pyFiles = ["requirements.txt", "pyproject.toml"];
|
|
207
|
+
for (const f of pyFiles) {
|
|
208
|
+
const fp = path.join(projectDir, f);
|
|
209
|
+
if (fs.existsSync(fp)) {
|
|
210
|
+
try {
|
|
211
|
+
const content = fs.readFileSync(fp, "utf8");
|
|
212
|
+
if (content.includes("fastapi") || content.includes("flask") || content.includes("django")) return true;
|
|
213
|
+
} catch { /* ignore */ }
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return false;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function getInstalledVersion() {
|
|
220
|
+
try {
|
|
221
|
+
return fs.readFileSync(VERSION_FILE, "utf8").trim();
|
|
222
|
+
} catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function saveInstalledVersion() {
|
|
228
|
+
if (isSymlink(VERSION_FILE)) {
|
|
229
|
+
warn("Skipping version write — target is a symlink");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
fs.writeFileSync(VERSION_FILE, PKG_VERSION);
|
|
234
|
+
} catch (e) {
|
|
235
|
+
error(`Failed to save version file: ${e.message}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function getRegisteredProjects() {
|
|
240
|
+
try {
|
|
241
|
+
const content = fs.readFileSync(PROJECTS_FILE, "utf8").trim();
|
|
242
|
+
if (!content) return [];
|
|
243
|
+
const lines = content.split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
244
|
+
return lines.filter((p) => {
|
|
245
|
+
if (!validateProjectPath(p)) {
|
|
246
|
+
warn(`Skipping invalid project path: ${p}`);
|
|
247
|
+
return false;
|
|
248
|
+
}
|
|
249
|
+
return true;
|
|
250
|
+
});
|
|
251
|
+
} catch {
|
|
252
|
+
return [];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function registerProject(projectDir) {
|
|
257
|
+
const resolved = path.resolve(projectDir);
|
|
258
|
+
const projects = getRegisteredProjects();
|
|
259
|
+
if (projects.includes(resolved)) return false;
|
|
260
|
+
if (isSymlink(PROJECTS_FILE)) {
|
|
261
|
+
warn("Skipping project registration — target is a symlink");
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
try {
|
|
265
|
+
projects.push(resolved);
|
|
266
|
+
fs.writeFileSync(PROJECTS_FILE, projects.join("\n") + "\n");
|
|
267
|
+
return true;
|
|
268
|
+
} catch (e) {
|
|
269
|
+
error(`Failed to register project: ${e.message}`);
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function getCommandFiles() {
|
|
275
|
+
// All .md files in our commands/ directory (gsd-t-* plus utilities like branch, checkin, Claude-md)
|
|
276
|
+
return fs
|
|
277
|
+
.readdirSync(PKG_COMMANDS)
|
|
278
|
+
.filter((f) => f.endsWith(".md"));
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function getGsdtCommands() {
|
|
282
|
+
return getCommandFiles().filter((f) => f.startsWith("gsd-t-"));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function getUtilityCommands() {
|
|
286
|
+
return getCommandFiles().filter((f) => !f.startsWith("gsd-t-"));
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function getInstalledCommands() {
|
|
290
|
+
try {
|
|
291
|
+
const ourCommands = getCommandFiles();
|
|
292
|
+
return fs
|
|
293
|
+
.readdirSync(COMMANDS_DIR)
|
|
294
|
+
.filter((f) => ourCommands.includes(f));
|
|
295
|
+
} catch {
|
|
296
|
+
return [];
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ─── Heartbeat ──────────────────────────────────────────────────────────────
|
|
301
|
+
|
|
302
|
+
const HEARTBEAT_SCRIPT = "gsd-t-heartbeat.js";
|
|
303
|
+
const HEARTBEAT_HOOKS = [
|
|
304
|
+
"SessionStart", "PostToolUse", "SubagentStart", "SubagentStop",
|
|
305
|
+
"TaskCompleted", "TeammateIdle", "Notification", "Stop", "SessionEnd"
|
|
306
|
+
];
|
|
307
|
+
|
|
308
|
+
function installHeartbeat() {
|
|
309
|
+
ensureDir(SCRIPTS_DIR);
|
|
310
|
+
|
|
311
|
+
// Copy heartbeat script
|
|
312
|
+
const src = path.join(PKG_SCRIPTS, HEARTBEAT_SCRIPT);
|
|
313
|
+
const dest = path.join(SCRIPTS_DIR, HEARTBEAT_SCRIPT);
|
|
314
|
+
|
|
315
|
+
if (!fs.existsSync(src)) {
|
|
316
|
+
warn("Heartbeat script not found in package — skipping");
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const srcContent = fs.readFileSync(src, "utf8");
|
|
321
|
+
const destContent = fs.existsSync(dest) ? fs.readFileSync(dest, "utf8") : "";
|
|
322
|
+
|
|
323
|
+
if (normalizeEol(srcContent) !== normalizeEol(destContent)) {
|
|
324
|
+
copyFile(src, dest, HEARTBEAT_SCRIPT);
|
|
325
|
+
} else {
|
|
326
|
+
info("Heartbeat script unchanged");
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// Configure hooks in settings.json
|
|
330
|
+
const hooksAdded = configureHeartbeatHooks(dest);
|
|
331
|
+
if (hooksAdded > 0) {
|
|
332
|
+
success(`${hooksAdded} heartbeat hooks configured in settings.json`);
|
|
333
|
+
} else {
|
|
334
|
+
info("Heartbeat hooks already configured");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function configureHeartbeatHooks(scriptPath) {
|
|
339
|
+
let settings = {};
|
|
340
|
+
if (fs.existsSync(SETTINGS_JSON)) {
|
|
341
|
+
try {
|
|
342
|
+
settings = JSON.parse(fs.readFileSync(SETTINGS_JSON, "utf8"));
|
|
343
|
+
} catch {
|
|
344
|
+
warn("settings.json has invalid JSON — cannot configure hooks");
|
|
345
|
+
return 0;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (!settings.hooks) settings.hooks = {};
|
|
350
|
+
|
|
351
|
+
const cmd = `node "${scriptPath.replace(/\\/g, "\\\\")}"`;
|
|
352
|
+
let added = 0;
|
|
353
|
+
|
|
354
|
+
for (const event of HEARTBEAT_HOOKS) {
|
|
355
|
+
if (!settings.hooks[event]) settings.hooks[event] = [];
|
|
356
|
+
|
|
357
|
+
// Check if heartbeat hook already exists for this event
|
|
358
|
+
const hasHeartbeat = settings.hooks[event].some((entry) =>
|
|
359
|
+
entry.hooks && entry.hooks.some((h) => h.command && h.command.includes(HEARTBEAT_SCRIPT))
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
if (!hasHeartbeat) {
|
|
363
|
+
settings.hooks[event].push({
|
|
364
|
+
matcher: "",
|
|
365
|
+
hooks: [{ type: "command", command: cmd, async: true }],
|
|
366
|
+
});
|
|
367
|
+
added++;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (added > 0) {
|
|
372
|
+
if (isSymlink(SETTINGS_JSON)) {
|
|
373
|
+
warn("Skipping settings.json write — target is a symlink");
|
|
374
|
+
} else {
|
|
375
|
+
fs.writeFileSync(SETTINGS_JSON, JSON.stringify(settings, null, 2));
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return added;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─── Commands ────────────────────────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
function doInstall(opts = {}) {
|
|
385
|
+
const isUpdate = opts.update || false;
|
|
386
|
+
const verb = isUpdate ? "Updating" : "Installing";
|
|
387
|
+
|
|
388
|
+
heading(`${verb} GSD-T ${versionLink()}`);
|
|
389
|
+
log("");
|
|
390
|
+
|
|
391
|
+
// 1. Create ~/.claude/commands/ if needed
|
|
392
|
+
if (ensureDir(COMMANDS_DIR)) {
|
|
393
|
+
success("Created ~/.claude/commands/");
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// 2. Copy all command files
|
|
397
|
+
heading("Slash Commands");
|
|
398
|
+
const commandFiles = getCommandFiles();
|
|
399
|
+
const gsdtCommands = getGsdtCommands();
|
|
400
|
+
const utilityCommands = getUtilityCommands();
|
|
401
|
+
let installed = 0;
|
|
402
|
+
let skipped = 0;
|
|
403
|
+
|
|
404
|
+
for (const file of commandFiles) {
|
|
405
|
+
const src = path.join(PKG_COMMANDS, file);
|
|
406
|
+
const dest = path.join(COMMANDS_DIR, file);
|
|
407
|
+
|
|
408
|
+
if (isUpdate && fs.existsSync(dest)) {
|
|
409
|
+
// Compare content — only overwrite if changed
|
|
410
|
+
const srcContent = fs.readFileSync(src, "utf8");
|
|
411
|
+
const destContent = fs.readFileSync(dest, "utf8");
|
|
412
|
+
if (normalizeEol(srcContent) === normalizeEol(destContent)) {
|
|
413
|
+
skipped++;
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
copyFile(src, dest, file);
|
|
419
|
+
installed++;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (skipped > 0) {
|
|
423
|
+
info(`${skipped} commands unchanged`);
|
|
424
|
+
}
|
|
425
|
+
success(`${gsdtCommands.length} GSD-T commands + ${utilityCommands.length} utilities ${isUpdate ? "updated" : "installed"} → ~/.claude/commands/`);
|
|
426
|
+
|
|
427
|
+
// 3. Handle global CLAUDE.md
|
|
428
|
+
heading("Global CLAUDE.md");
|
|
429
|
+
const globalSrc = path.join(PKG_TEMPLATES, "CLAUDE-global.md");
|
|
430
|
+
|
|
431
|
+
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
|
432
|
+
const existing = fs.readFileSync(GLOBAL_CLAUDE_MD, "utf8");
|
|
433
|
+
|
|
434
|
+
if (existing.includes("GSD-T: Contract-Driven Development")) {
|
|
435
|
+
if (isUpdate) {
|
|
436
|
+
// Check if there are customizations (lines not in our template)
|
|
437
|
+
const template = fs.readFileSync(globalSrc, "utf8");
|
|
438
|
+
if (normalizeEol(existing) === normalizeEol(template)) {
|
|
439
|
+
copyFile(globalSrc, GLOBAL_CLAUDE_MD, "CLAUDE.md updated (no customizations detected)");
|
|
440
|
+
} else {
|
|
441
|
+
// Backup and replace, warn about customizations
|
|
442
|
+
const backupPath = GLOBAL_CLAUDE_MD + ".backup-" + Date.now();
|
|
443
|
+
if (isSymlink(backupPath)) {
|
|
444
|
+
warn("Skipping backup — target is a symlink");
|
|
445
|
+
} else {
|
|
446
|
+
fs.copyFileSync(GLOBAL_CLAUDE_MD, backupPath);
|
|
447
|
+
}
|
|
448
|
+
copyFile(globalSrc, GLOBAL_CLAUDE_MD, "CLAUDE.md updated");
|
|
449
|
+
warn(`Previous version backed up to ${path.basename(backupPath)}`);
|
|
450
|
+
info("Review the backup if you had custom additions to merge back in.");
|
|
451
|
+
}
|
|
452
|
+
} else {
|
|
453
|
+
info("CLAUDE.md already contains GSD-T config — skipping");
|
|
454
|
+
info("Run 'gsd-t update' to overwrite with latest version");
|
|
455
|
+
}
|
|
456
|
+
} else {
|
|
457
|
+
// Existing CLAUDE.md without GSD-T — append
|
|
458
|
+
if (isSymlink(GLOBAL_CLAUDE_MD)) {
|
|
459
|
+
warn("Skipping CLAUDE.md append — target is a symlink");
|
|
460
|
+
} else {
|
|
461
|
+
const gsdtContent = fs.readFileSync(globalSrc, "utf8");
|
|
462
|
+
const separator = "\n\n# ─── GSD-T Section (added by installer) ───\n\n";
|
|
463
|
+
fs.appendFileSync(GLOBAL_CLAUDE_MD, separator + gsdtContent);
|
|
464
|
+
success("GSD-T config appended to existing CLAUDE.md");
|
|
465
|
+
info("Your existing content was preserved.");
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
} else {
|
|
469
|
+
copyFile(globalSrc, GLOBAL_CLAUDE_MD, "CLAUDE.md installed → ~/.claude/CLAUDE.md");
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// 4. Install heartbeat script + hooks
|
|
473
|
+
heading("Heartbeat (Real-time Events)");
|
|
474
|
+
installHeartbeat();
|
|
475
|
+
|
|
476
|
+
// 5. Save version
|
|
477
|
+
saveInstalledVersion();
|
|
478
|
+
|
|
479
|
+
// 6. Summary
|
|
480
|
+
heading("Installation Complete!");
|
|
481
|
+
log("");
|
|
482
|
+
log(` Commands: ${gsdtCommands.length} GSD-T + ${utilityCommands.length} utility commands in ~/.claude/commands/`);
|
|
483
|
+
log(` Config: ~/.claude/CLAUDE.md`);
|
|
484
|
+
log(` Version: ${versionLink()}`);
|
|
485
|
+
log("");
|
|
486
|
+
log(`${BOLD}Quick Start:${RESET}`);
|
|
487
|
+
log(` ${DIM}$${RESET} cd your-project`);
|
|
488
|
+
log(` ${DIM}$${RESET} claude`);
|
|
489
|
+
log(` ${DIM}>${RESET} /user:gsd-t-init my-project`);
|
|
490
|
+
log(` ${DIM}>${RESET} /user:gsd-t-milestone "First Feature"`);
|
|
491
|
+
log(` ${DIM}>${RESET} /user:gsd-t-wave`);
|
|
492
|
+
log("");
|
|
493
|
+
log(`${BOLD}Other commands:${RESET}`);
|
|
494
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t status ${DIM}— check installation${RESET}`);
|
|
495
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t update ${DIM}— update to latest${RESET}`);
|
|
496
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t init myapp ${DIM}— scaffold a new project${RESET}`);
|
|
497
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t doctor ${DIM}— diagnose issues${RESET}`);
|
|
498
|
+
log("");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
function doUpdate() {
|
|
502
|
+
const installedVersion = getInstalledVersion();
|
|
503
|
+
|
|
504
|
+
if (installedVersion === PKG_VERSION) {
|
|
505
|
+
heading(`GSD-T ${versionLink()}`);
|
|
506
|
+
info("Already up to date!");
|
|
507
|
+
log("");
|
|
508
|
+
log(" To force a reinstall, run:");
|
|
509
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t install`);
|
|
510
|
+
log("");
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (installedVersion) {
|
|
515
|
+
heading(`Updating GSD-T: ${versionLink(installedVersion)} → ${versionLink()}`);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
doInstall({ update: true });
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function doInit(projectName) {
|
|
522
|
+
if (!projectName) {
|
|
523
|
+
// Use current directory name
|
|
524
|
+
projectName = path.basename(process.cwd());
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!validateProjectName(projectName)) {
|
|
528
|
+
error(`Invalid project name: "${projectName}"`);
|
|
529
|
+
info("Project names must start with a letter or number and contain only letters, numbers, dots, hyphens, underscores, or spaces (max 101 chars)");
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
heading(`Initializing GSD-T project: ${projectName}`);
|
|
534
|
+
log("");
|
|
535
|
+
|
|
536
|
+
const projectDir = process.cwd();
|
|
537
|
+
const today = new Date().toISOString().split("T")[0];
|
|
538
|
+
|
|
539
|
+
// 1. Create project CLAUDE.md
|
|
540
|
+
const claudeMdPath = path.join(projectDir, "CLAUDE.md");
|
|
541
|
+
if (isSymlink(claudeMdPath)) {
|
|
542
|
+
warn("Skipping CLAUDE.md — target is a symlink");
|
|
543
|
+
} else {
|
|
544
|
+
try {
|
|
545
|
+
const template = fs.readFileSync(path.join(PKG_TEMPLATES, "CLAUDE-project.md"), "utf8");
|
|
546
|
+
const content = applyTokens(template, projectName, today);
|
|
547
|
+
fs.writeFileSync(claudeMdPath, content, { flag: "wx" });
|
|
548
|
+
success("CLAUDE.md created");
|
|
549
|
+
} catch (e) {
|
|
550
|
+
if (e.code === "EEXIST") {
|
|
551
|
+
const content = fs.readFileSync(claudeMdPath, "utf8");
|
|
552
|
+
if (content.includes("GSD-T Workflow")) {
|
|
553
|
+
info("CLAUDE.md already contains GSD-T section — skipping");
|
|
554
|
+
} else {
|
|
555
|
+
warn("CLAUDE.md exists but doesn't reference GSD-T");
|
|
556
|
+
info("Run /user:gsd-t-init inside Claude Code to add GSD-T section");
|
|
557
|
+
}
|
|
558
|
+
} else { throw e; }
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
// 2. Create docs/ with templates
|
|
563
|
+
const docsDir = path.join(projectDir, "docs");
|
|
564
|
+
ensureDir(docsDir);
|
|
565
|
+
|
|
566
|
+
const docTemplates = ["requirements.md", "architecture.md", "workflows.md", "infrastructure.md"];
|
|
567
|
+
|
|
568
|
+
for (const file of docTemplates) {
|
|
569
|
+
const destPath = path.join(docsDir, file);
|
|
570
|
+
if (isSymlink(destPath)) {
|
|
571
|
+
warn(`Skipping docs/${file} — target is a symlink`);
|
|
572
|
+
continue;
|
|
573
|
+
}
|
|
574
|
+
try {
|
|
575
|
+
const template = fs.readFileSync(path.join(PKG_TEMPLATES, file), "utf8");
|
|
576
|
+
const content = applyTokens(template, projectName, today);
|
|
577
|
+
fs.writeFileSync(destPath, content, { flag: "wx" });
|
|
578
|
+
success(`docs/${file}`);
|
|
579
|
+
} catch (e) {
|
|
580
|
+
if (e.code === "EEXIST") { info(`docs/${file} already exists — skipping`); }
|
|
581
|
+
else { throw e; }
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// 3. Create .gsd-t/ structure
|
|
586
|
+
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
587
|
+
const contractsDir = path.join(gsdtDir, "contracts");
|
|
588
|
+
const domainsDir = path.join(gsdtDir, "domains");
|
|
589
|
+
|
|
590
|
+
ensureDir(contractsDir);
|
|
591
|
+
ensureDir(domainsDir);
|
|
592
|
+
|
|
593
|
+
// .gitkeep files so empty dirs are tracked
|
|
594
|
+
for (const dir of [contractsDir, domainsDir]) {
|
|
595
|
+
const gitkeep = path.join(dir, ".gitkeep");
|
|
596
|
+
if (isSymlink(gitkeep)) continue;
|
|
597
|
+
try {
|
|
598
|
+
fs.writeFileSync(gitkeep, "", { flag: "wx" });
|
|
599
|
+
} catch (e) {
|
|
600
|
+
if (e.code !== "EEXIST") throw e;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Progress file
|
|
605
|
+
const progressPath = path.join(gsdtDir, "progress.md");
|
|
606
|
+
if (isSymlink(progressPath)) {
|
|
607
|
+
warn("Skipping progress.md — target is a symlink");
|
|
608
|
+
} else {
|
|
609
|
+
try {
|
|
610
|
+
const template = fs.readFileSync(path.join(PKG_TEMPLATES, "progress.md"), "utf8");
|
|
611
|
+
const content = applyTokens(template, projectName, today);
|
|
612
|
+
fs.writeFileSync(progressPath, content, { flag: "wx" });
|
|
613
|
+
success(".gsd-t/progress.md");
|
|
614
|
+
} catch (e) {
|
|
615
|
+
if (e.code === "EEXIST") { info(".gsd-t/progress.md already exists — skipping"); }
|
|
616
|
+
else { throw e; }
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// Backlog files
|
|
621
|
+
const backlogFiles = ["backlog.md", "backlog-settings.md"];
|
|
622
|
+
for (const file of backlogFiles) {
|
|
623
|
+
const destPath = path.join(gsdtDir, file);
|
|
624
|
+
if (isSymlink(destPath)) {
|
|
625
|
+
warn(`Skipping .gsd-t/${file} — target is a symlink`);
|
|
626
|
+
continue;
|
|
627
|
+
}
|
|
628
|
+
try {
|
|
629
|
+
const content = fs.readFileSync(path.join(PKG_TEMPLATES, file), "utf8");
|
|
630
|
+
fs.writeFileSync(destPath, content, { flag: "wx" });
|
|
631
|
+
success(`.gsd-t/${file}`);
|
|
632
|
+
} catch (e) {
|
|
633
|
+
if (e.code === "EEXIST") { info(`.gsd-t/${file} already exists — skipping`); }
|
|
634
|
+
else { throw e; }
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// 4. Register in project index
|
|
639
|
+
if (registerProject(projectDir)) {
|
|
640
|
+
success("Registered in ~/.claude/.gsd-t-projects");
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// 5. Summary
|
|
644
|
+
heading("Project Initialized!");
|
|
645
|
+
log("");
|
|
646
|
+
log(` ${projectDir}/`);
|
|
647
|
+
log(` ├── CLAUDE.md`);
|
|
648
|
+
log(` ├── docs/`);
|
|
649
|
+
log(` │ ├── requirements.md`);
|
|
650
|
+
log(` │ ├── architecture.md`);
|
|
651
|
+
log(` │ ├── workflows.md`);
|
|
652
|
+
log(` │ └── infrastructure.md`);
|
|
653
|
+
log(` └── .gsd-t/`);
|
|
654
|
+
log(` ├── progress.md`);
|
|
655
|
+
log(` ├── backlog.md`);
|
|
656
|
+
log(` ├── backlog-settings.md`);
|
|
657
|
+
log(` ├── contracts/`);
|
|
658
|
+
log(` └── domains/`);
|
|
659
|
+
log("");
|
|
660
|
+
log(`${BOLD}Next steps:${RESET}`);
|
|
661
|
+
log(` 1. Edit CLAUDE.md — add project overview and tech stack`);
|
|
662
|
+
log(` 2. Start Claude Code: ${DIM}claude${RESET}`);
|
|
663
|
+
log(` 3. Run: ${DIM}/user:gsd-t-populate${RESET} ${DIM}(if existing codebase)${RESET}`);
|
|
664
|
+
log(` Or: ${DIM}/user:gsd-t-project${RESET} ${DIM}(if new project)${RESET}`);
|
|
665
|
+
log("");
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
function doStatus() {
|
|
669
|
+
heading("GSD-T Status");
|
|
670
|
+
log("");
|
|
671
|
+
|
|
672
|
+
// Installed version
|
|
673
|
+
const installedVersion = getInstalledVersion();
|
|
674
|
+
if (installedVersion) {
|
|
675
|
+
success(`Installed version: ${versionLink(installedVersion)}`);
|
|
676
|
+
if (installedVersion !== PKG_VERSION) {
|
|
677
|
+
warn(`Latest version: ${versionLink()}`);
|
|
678
|
+
info(`Run 'npx @tekyzinc/gsd-t update' to update`);
|
|
679
|
+
} else {
|
|
680
|
+
success(`Up to date (latest: ${versionLink()})`);
|
|
681
|
+
}
|
|
682
|
+
} else {
|
|
683
|
+
error("GSD-T not installed");
|
|
684
|
+
info("Run 'npx @tekyzinc/gsd-t install' to install");
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
// Commands
|
|
689
|
+
heading("Slash Commands");
|
|
690
|
+
const expected = getCommandFiles();
|
|
691
|
+
const installed = getInstalledCommands();
|
|
692
|
+
const gsdtExpected = getGsdtCommands();
|
|
693
|
+
const utilExpected = getUtilityCommands();
|
|
694
|
+
|
|
695
|
+
const missing = expected.filter((f) => !installed.includes(f));
|
|
696
|
+
const extra = installed.filter((f) => !expected.includes(f));
|
|
697
|
+
const present = expected.filter((f) => installed.includes(f));
|
|
698
|
+
|
|
699
|
+
log(` ${present.length}/${expected.length} commands installed (${gsdtExpected.length} GSD-T + ${utilExpected.length} utilities)`);
|
|
700
|
+
|
|
701
|
+
if (missing.length > 0) {
|
|
702
|
+
warn(`Missing: ${missing.join(", ")}`);
|
|
703
|
+
}
|
|
704
|
+
if (extra.length > 0) {
|
|
705
|
+
info(`Custom commands found: ${extra.join(", ")}`);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// Global CLAUDE.md
|
|
709
|
+
heading("Global Config");
|
|
710
|
+
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
|
711
|
+
const content = fs.readFileSync(GLOBAL_CLAUDE_MD, "utf8");
|
|
712
|
+
if (content.includes("GSD-T: Contract-Driven Development")) {
|
|
713
|
+
success("~/.claude/CLAUDE.md contains GSD-T config");
|
|
714
|
+
} else {
|
|
715
|
+
warn("~/.claude/CLAUDE.md exists but doesn't contain GSD-T section");
|
|
716
|
+
}
|
|
717
|
+
} else {
|
|
718
|
+
error("~/.claude/CLAUDE.md not found");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// Settings.json — teams
|
|
722
|
+
heading("Agent Teams");
|
|
723
|
+
if (fs.existsSync(SETTINGS_JSON)) {
|
|
724
|
+
try {
|
|
725
|
+
const settings = JSON.parse(fs.readFileSync(SETTINGS_JSON, "utf8"));
|
|
726
|
+
const teamsEnabled =
|
|
727
|
+
settings?.env?.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS === "1";
|
|
728
|
+
if (teamsEnabled) {
|
|
729
|
+
success("Agent Teams enabled in settings.json");
|
|
730
|
+
} else {
|
|
731
|
+
info("Agent Teams not enabled (optional — solo mode works fine)");
|
|
732
|
+
info('Add "CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS": "1" to env in settings.json');
|
|
733
|
+
}
|
|
734
|
+
} catch {
|
|
735
|
+
warn("settings.json exists but couldn't be parsed");
|
|
736
|
+
}
|
|
737
|
+
} else {
|
|
738
|
+
info("No settings.json found (Claude Code will use defaults)");
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// Current project
|
|
742
|
+
heading("Current Project");
|
|
743
|
+
const cwd = process.cwd();
|
|
744
|
+
const hasGsdT = fs.existsSync(path.join(cwd, ".gsd-t"));
|
|
745
|
+
const hasClaudeMd = fs.existsSync(path.join(cwd, "CLAUDE.md"));
|
|
746
|
+
|
|
747
|
+
if (hasGsdT) {
|
|
748
|
+
success(`.gsd-t/ found in ${cwd}`);
|
|
749
|
+
const progressPath = path.join(cwd, ".gsd-t", "progress.md");
|
|
750
|
+
if (fs.existsSync(progressPath)) {
|
|
751
|
+
const progress = fs.readFileSync(progressPath, "utf8");
|
|
752
|
+
const statusMatch = progress.match(/## Status:\s*(.+)/);
|
|
753
|
+
const milestoneMatch = progress.match(/## Project:\s*(.+)/);
|
|
754
|
+
if (milestoneMatch) info(`Project: ${milestoneMatch[1]}`);
|
|
755
|
+
if (statusMatch) info(`Status: ${statusMatch[1]}`);
|
|
756
|
+
}
|
|
757
|
+
} else if (hasClaudeMd) {
|
|
758
|
+
info("CLAUDE.md found but no .gsd-t/ directory");
|
|
759
|
+
info("Run /user:gsd-t-init inside Claude Code to set up");
|
|
760
|
+
} else {
|
|
761
|
+
info("Not in a GSD-T project directory");
|
|
762
|
+
info(`Run 'npx @tekyzinc/gsd-t init' to set up this directory`);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
log("");
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
function doUninstall() {
|
|
769
|
+
heading("Uninstalling GSD-T");
|
|
770
|
+
log("");
|
|
771
|
+
|
|
772
|
+
// Remove command files
|
|
773
|
+
const commands = getInstalledCommands();
|
|
774
|
+
let removed = 0;
|
|
775
|
+
for (const file of commands) {
|
|
776
|
+
try {
|
|
777
|
+
fs.unlinkSync(path.join(COMMANDS_DIR, file));
|
|
778
|
+
removed++;
|
|
779
|
+
} catch (e) {
|
|
780
|
+
error(`Failed to remove ${file}: ${e.message}`);
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (removed > 0) {
|
|
784
|
+
success(`Removed ${removed} slash commands from ~/.claude/commands/`);
|
|
785
|
+
}
|
|
786
|
+
|
|
787
|
+
// Remove version file
|
|
788
|
+
try {
|
|
789
|
+
if (fs.existsSync(VERSION_FILE)) {
|
|
790
|
+
fs.unlinkSync(VERSION_FILE);
|
|
791
|
+
}
|
|
792
|
+
} catch (e) {
|
|
793
|
+
error(`Failed to remove version file: ${e.message}`);
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Don't touch CLAUDE.md — too risky, may have customizations
|
|
797
|
+
warn("~/.claude/CLAUDE.md was NOT removed (may contain your customizations)");
|
|
798
|
+
info("Remove manually if desired: delete the GSD-T section from ~/.claude/CLAUDE.md");
|
|
799
|
+
|
|
800
|
+
// Don't touch project files
|
|
801
|
+
info("Project files (.gsd-t/, docs/, CLAUDE.md) were NOT removed");
|
|
802
|
+
|
|
803
|
+
heading("Uninstall Complete");
|
|
804
|
+
log("");
|
|
805
|
+
}
|
|
806
|
+
|
|
807
|
+
function updateProjectClaudeMd(claudeMd, projectName) {
|
|
808
|
+
const content = fs.readFileSync(claudeMd, "utf8");
|
|
809
|
+
if (content.includes("Destructive Action Guard")) return false;
|
|
810
|
+
|
|
811
|
+
let newContent;
|
|
812
|
+
const preCommitMatch = content.match(/\n(#{1,3} Pre-Commit Gate)/);
|
|
813
|
+
const dontDoMatch = content.match(/\n(#{1,3} Don't Do These Things)/);
|
|
814
|
+
|
|
815
|
+
if (preCommitMatch) {
|
|
816
|
+
newContent = content.replace(
|
|
817
|
+
"\n" + preCommitMatch[1],
|
|
818
|
+
GUARD_SECTION + "\n" + preCommitMatch[1]
|
|
819
|
+
);
|
|
820
|
+
} else if (dontDoMatch) {
|
|
821
|
+
newContent = content.replace(
|
|
822
|
+
"\n" + dontDoMatch[1],
|
|
823
|
+
GUARD_SECTION + "\n" + dontDoMatch[1]
|
|
824
|
+
);
|
|
825
|
+
} else {
|
|
826
|
+
newContent = content + GUARD_SECTION;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
if (isSymlink(claudeMd)) {
|
|
830
|
+
warn(`${projectName} — skipping CLAUDE.md write (symlink)`);
|
|
831
|
+
return false;
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
fs.writeFileSync(claudeMd, newContent);
|
|
835
|
+
success(`${projectName} — added Destructive Action Guard`);
|
|
836
|
+
return true;
|
|
837
|
+
} catch (e) {
|
|
838
|
+
error(`${projectName} — failed to update CLAUDE.md: ${e.message}`);
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
function createProjectChangelog(projectDir, projectName) {
|
|
844
|
+
const changelogPath = path.join(projectDir, "CHANGELOG.md");
|
|
845
|
+
if (isSymlink(changelogPath)) return false;
|
|
846
|
+
try {
|
|
847
|
+
const today = new Date().toISOString().split("T")[0];
|
|
848
|
+
const changelogContent = [
|
|
849
|
+
"# Changelog",
|
|
850
|
+
"",
|
|
851
|
+
"All notable changes to this project are documented here.",
|
|
852
|
+
"",
|
|
853
|
+
`## [0.1.0] - ${today}`,
|
|
854
|
+
"",
|
|
855
|
+
"### Added",
|
|
856
|
+
"- Initial changelog created by GSD-T",
|
|
857
|
+
"",
|
|
858
|
+
].join("\n");
|
|
859
|
+
fs.writeFileSync(changelogPath, changelogContent, { flag: "wx" });
|
|
860
|
+
success(`${projectName} — created CHANGELOG.md`);
|
|
861
|
+
return true;
|
|
862
|
+
} catch (e) {
|
|
863
|
+
if (e.code !== "EEXIST") throw e;
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
function checkProjectHealth(projects) {
|
|
869
|
+
heading("Project Health");
|
|
870
|
+
const playwrightMissing = [];
|
|
871
|
+
const swaggerMissing = [];
|
|
872
|
+
|
|
873
|
+
for (const projectDir of projects) {
|
|
874
|
+
if (!fs.existsSync(projectDir)) continue;
|
|
875
|
+
const name = path.basename(projectDir);
|
|
876
|
+
if (!hasPlaywright(projectDir)) playwrightMissing.push(name);
|
|
877
|
+
if (hasApi(projectDir) && !hasSwagger(projectDir)) swaggerMissing.push(name);
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (playwrightMissing.length === 0 && swaggerMissing.length === 0) {
|
|
881
|
+
success("All projects have Playwright and Swagger configured");
|
|
882
|
+
} else {
|
|
883
|
+
if (playwrightMissing.length > 0) {
|
|
884
|
+
warn(`Playwright missing: ${playwrightMissing.join(", ")}`);
|
|
885
|
+
info("Playwright will be auto-installed when you run a GSD-T command in each project");
|
|
886
|
+
}
|
|
887
|
+
if (swaggerMissing.length > 0) {
|
|
888
|
+
warn(`Swagger/OpenAPI missing (API detected): ${swaggerMissing.join(", ")}`);
|
|
889
|
+
info("Swagger will be auto-configured when an API endpoint is created or modified");
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return { playwrightMissing, swaggerMissing };
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function doUpdateAll() {
|
|
896
|
+
// First, run the normal global update
|
|
897
|
+
const installedVersion = getInstalledVersion();
|
|
898
|
+
if (installedVersion !== PKG_VERSION) {
|
|
899
|
+
doInstall({ update: true });
|
|
900
|
+
} else {
|
|
901
|
+
heading(`GSD-T ${versionLink()}`);
|
|
902
|
+
success("Global commands already up to date");
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Read project registry
|
|
906
|
+
heading("Updating registered projects...");
|
|
907
|
+
log("");
|
|
908
|
+
|
|
909
|
+
const projects = getRegisteredProjects();
|
|
910
|
+
|
|
911
|
+
if (projects.length === 0) {
|
|
912
|
+
info("No projects registered");
|
|
913
|
+
log("");
|
|
914
|
+
log(" Projects are registered automatically when you run:");
|
|
915
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t init`);
|
|
916
|
+
log("");
|
|
917
|
+
log(" Or register an existing project manually:");
|
|
918
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t register`);
|
|
919
|
+
log("");
|
|
920
|
+
return;
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
let updated = 0;
|
|
924
|
+
let skipped = 0;
|
|
925
|
+
let missing = 0;
|
|
926
|
+
|
|
927
|
+
for (const projectDir of projects) {
|
|
928
|
+
const projectName = path.basename(projectDir);
|
|
929
|
+
const claudeMd = path.join(projectDir, "CLAUDE.md");
|
|
930
|
+
|
|
931
|
+
if (!fs.existsSync(projectDir)) {
|
|
932
|
+
warn(`${projectName} — directory not found (${projectDir})`);
|
|
933
|
+
missing++;
|
|
934
|
+
continue;
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
if (!fs.existsSync(claudeMd)) {
|
|
938
|
+
warn(`${projectName} — no CLAUDE.md found`);
|
|
939
|
+
skipped++;
|
|
940
|
+
continue;
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
const guardAdded = updateProjectClaudeMd(claudeMd, projectName);
|
|
944
|
+
const changelogCreated = createProjectChangelog(projectDir, projectName);
|
|
945
|
+
|
|
946
|
+
if (guardAdded || changelogCreated) {
|
|
947
|
+
updated++;
|
|
948
|
+
} else {
|
|
949
|
+
info(`${projectName} — already up to date`);
|
|
950
|
+
skipped++;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const { playwrightMissing, swaggerMissing } = checkProjectHealth(projects);
|
|
955
|
+
|
|
956
|
+
// Summary
|
|
957
|
+
log("");
|
|
958
|
+
heading("Update All Complete");
|
|
959
|
+
log(` Projects registered: ${projects.length}`);
|
|
960
|
+
log(` Updated: ${updated}`);
|
|
961
|
+
log(` Already current: ${skipped}`);
|
|
962
|
+
if (missing > 0) {
|
|
963
|
+
log(` Not found: ${missing}`);
|
|
964
|
+
}
|
|
965
|
+
if (playwrightMissing.length > 0) {
|
|
966
|
+
log(` Missing Playwright: ${playwrightMissing.length}`);
|
|
967
|
+
}
|
|
968
|
+
if (swaggerMissing.length > 0) {
|
|
969
|
+
log(` Missing Swagger: ${swaggerMissing.length}`);
|
|
970
|
+
}
|
|
971
|
+
log("");
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
function doDoctor() {
|
|
975
|
+
heading("GSD-T Doctor");
|
|
976
|
+
log("");
|
|
977
|
+
|
|
978
|
+
let issues = 0;
|
|
979
|
+
|
|
980
|
+
// 1. Node version
|
|
981
|
+
const nodeVersion = parseInt(process.version.slice(1));
|
|
982
|
+
if (nodeVersion >= 16) {
|
|
983
|
+
success(`Node.js ${process.version}`);
|
|
984
|
+
} else {
|
|
985
|
+
error(`Node.js ${process.version} — requires >= 16`);
|
|
986
|
+
issues++;
|
|
987
|
+
}
|
|
988
|
+
|
|
989
|
+
// 2. Claude Code installed?
|
|
990
|
+
try {
|
|
991
|
+
const claudeVersion = execFileSync("claude", ["--version"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
|
|
992
|
+
success(`Claude Code: ${claudeVersion}`);
|
|
993
|
+
} catch {
|
|
994
|
+
warn("Claude Code CLI not found in PATH");
|
|
995
|
+
info("Install with: npm install -g @anthropic-ai/claude-code");
|
|
996
|
+
issues++;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// 3. ~/.claude/ exists?
|
|
1000
|
+
if (fs.existsSync(CLAUDE_DIR)) {
|
|
1001
|
+
success("~/.claude/ directory exists");
|
|
1002
|
+
} else {
|
|
1003
|
+
error("~/.claude/ directory not found");
|
|
1004
|
+
info("Run 'npx @tekyzinc/gsd-t install' to create it");
|
|
1005
|
+
issues++;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// 4. Commands installed?
|
|
1009
|
+
const installed = getInstalledCommands();
|
|
1010
|
+
const expected = getCommandFiles();
|
|
1011
|
+
if (installed.length === expected.length) {
|
|
1012
|
+
success(`All ${expected.length} commands installed`);
|
|
1013
|
+
} else if (installed.length > 0) {
|
|
1014
|
+
warn(`${installed.length}/${expected.length} commands installed`);
|
|
1015
|
+
const missing = expected.filter((f) => !installed.includes(f));
|
|
1016
|
+
info(`Missing: ${missing.join(", ")}`);
|
|
1017
|
+
issues++;
|
|
1018
|
+
} else {
|
|
1019
|
+
error("No GSD-T commands installed");
|
|
1020
|
+
issues++;
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// 5. CLAUDE.md
|
|
1024
|
+
if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
|
|
1025
|
+
const content = fs.readFileSync(GLOBAL_CLAUDE_MD, "utf8");
|
|
1026
|
+
if (content.includes("GSD-T")) {
|
|
1027
|
+
success("CLAUDE.md contains GSD-T config");
|
|
1028
|
+
} else {
|
|
1029
|
+
warn("CLAUDE.md exists but missing GSD-T section");
|
|
1030
|
+
issues++;
|
|
1031
|
+
}
|
|
1032
|
+
} else {
|
|
1033
|
+
error("No global CLAUDE.md");
|
|
1034
|
+
issues++;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// 6. settings.json valid?
|
|
1038
|
+
if (fs.existsSync(SETTINGS_JSON)) {
|
|
1039
|
+
try {
|
|
1040
|
+
JSON.parse(fs.readFileSync(SETTINGS_JSON, "utf8"));
|
|
1041
|
+
success("settings.json is valid JSON");
|
|
1042
|
+
} catch (e) {
|
|
1043
|
+
error(`settings.json has invalid JSON: ${e.message}`);
|
|
1044
|
+
issues++;
|
|
1045
|
+
}
|
|
1046
|
+
} else {
|
|
1047
|
+
info("No settings.json (not required)");
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// 7. Playwright check (current project)
|
|
1051
|
+
const cwd = process.cwd();
|
|
1052
|
+
if (hasPlaywright(cwd)) {
|
|
1053
|
+
success("Playwright configured");
|
|
1054
|
+
} else {
|
|
1055
|
+
warn("Playwright not configured in this project");
|
|
1056
|
+
info("Will be auto-installed when you run a GSD-T testing command");
|
|
1057
|
+
issues++;
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
// 8. Swagger/OpenAPI check (current project)
|
|
1061
|
+
if (hasApi(cwd)) {
|
|
1062
|
+
if (hasSwagger(cwd)) {
|
|
1063
|
+
success("Swagger/OpenAPI configured");
|
|
1064
|
+
} else {
|
|
1065
|
+
warn("API framework detected but no Swagger/OpenAPI spec found");
|
|
1066
|
+
info("Will be auto-configured when an API endpoint is created or modified");
|
|
1067
|
+
issues++;
|
|
1068
|
+
}
|
|
1069
|
+
} else {
|
|
1070
|
+
info("No API framework detected (Swagger check skipped)");
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// 9. Check for encoding issues in command files
|
|
1074
|
+
let encodingIssues = 0;
|
|
1075
|
+
for (const file of installed) {
|
|
1076
|
+
const content = fs.readFileSync(path.join(COMMANDS_DIR, file), "utf8");
|
|
1077
|
+
if (content.includes("â€") || content.includes("Ã")) {
|
|
1078
|
+
encodingIssues++;
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
if (encodingIssues > 0) {
|
|
1082
|
+
error(`${encodingIssues} command files have encoding issues (corrupted characters)`);
|
|
1083
|
+
info("Run 'npx @tekyzinc/gsd-t update' to replace with clean versions");
|
|
1084
|
+
issues++;
|
|
1085
|
+
} else if (installed.length > 0) {
|
|
1086
|
+
success("No encoding issues in command files");
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
// Summary
|
|
1090
|
+
log("");
|
|
1091
|
+
if (issues === 0) {
|
|
1092
|
+
log(`${GREEN}${BOLD} All checks passed!${RESET}`);
|
|
1093
|
+
} else {
|
|
1094
|
+
log(`${YELLOW}${BOLD} ${issues} issue${issues > 1 ? "s" : ""} found${RESET}`);
|
|
1095
|
+
}
|
|
1096
|
+
log("");
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
function doRegister() {
|
|
1100
|
+
const projectDir = process.cwd();
|
|
1101
|
+
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
1102
|
+
|
|
1103
|
+
if (!fs.existsSync(gsdtDir)) {
|
|
1104
|
+
error("Not a GSD-T project (no .gsd-t/ directory found)");
|
|
1105
|
+
info("Run 'npx @tekyzinc/gsd-t init' to initialize this project first");
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
if (registerProject(projectDir)) {
|
|
1110
|
+
success(`Registered: ${projectDir}`);
|
|
1111
|
+
} else {
|
|
1112
|
+
info("Already registered");
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Show all registered projects
|
|
1116
|
+
const projects = getRegisteredProjects();
|
|
1117
|
+
log("");
|
|
1118
|
+
heading("Registered Projects");
|
|
1119
|
+
for (const p of projects) {
|
|
1120
|
+
const exists = fs.existsSync(p);
|
|
1121
|
+
if (exists) {
|
|
1122
|
+
log(` ${GREEN}✓${RESET} ${p}`);
|
|
1123
|
+
} else {
|
|
1124
|
+
log(` ${RED}✗${RESET} ${p} ${DIM}(not found)${RESET}`);
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
log("");
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
function isNewerVersion(latest, current) {
|
|
1131
|
+
const l = latest.split(".").map(Number);
|
|
1132
|
+
const c = current.split(".").map(Number);
|
|
1133
|
+
for (let i = 0; i < 3; i++) {
|
|
1134
|
+
if ((l[i] || 0) > (c[i] || 0)) return true;
|
|
1135
|
+
if ((l[i] || 0) < (c[i] || 0)) return false;
|
|
1136
|
+
}
|
|
1137
|
+
return false;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
function checkForUpdates() {
|
|
1141
|
+
// Skip check for update/install/update-all (they handle it themselves)
|
|
1142
|
+
const skipCommands = ["install", "update", "update-all", "--version", "-v"];
|
|
1143
|
+
if (skipCommands.includes(command)) return;
|
|
1144
|
+
|
|
1145
|
+
// Read cache (sync, fast)
|
|
1146
|
+
let cached = null;
|
|
1147
|
+
try {
|
|
1148
|
+
if (fs.existsSync(UPDATE_CHECK_FILE)) {
|
|
1149
|
+
cached = JSON.parse(fs.readFileSync(UPDATE_CHECK_FILE, "utf8"));
|
|
1150
|
+
}
|
|
1151
|
+
} catch { /* ignore corrupt cache */ }
|
|
1152
|
+
|
|
1153
|
+
// Show notice from cache if a newer version is available
|
|
1154
|
+
if (cached && cached.latest && validateVersion(cached.latest) && isNewerVersion(cached.latest, PKG_VERSION)) {
|
|
1155
|
+
showUpdateNotice(cached.latest);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
const isStale = !cached || (Date.now() - cached.timestamp) > 3600000;
|
|
1159
|
+
|
|
1160
|
+
if (!cached && isStale) {
|
|
1161
|
+
// No cache at all — fetch synchronously so first run shows notification
|
|
1162
|
+
try {
|
|
1163
|
+
const fetchScript = "const h=require('https');h.get('https://registry.npmjs.org/@tekyzinc/gsd-t/latest',{timeout:5000},(r)=>{let d='';r.on('data',(c)=>d+=c);r.on('end',()=>{try{process.stdout.write(JSON.parse(d).version)}catch{}})}).on('error',()=>{})";
|
|
1164
|
+
const result = execFileSync(
|
|
1165
|
+
process.execPath, ["-e", fetchScript],
|
|
1166
|
+
{ timeout: 8000, encoding: "utf8" }
|
|
1167
|
+
).trim();
|
|
1168
|
+
if (result && validateVersion(result) && !isSymlink(UPDATE_CHECK_FILE)) {
|
|
1169
|
+
fs.writeFileSync(UPDATE_CHECK_FILE,
|
|
1170
|
+
JSON.stringify({ latest: result, timestamp: Date.now() }));
|
|
1171
|
+
if (isNewerVersion(result, PKG_VERSION)) {
|
|
1172
|
+
showUpdateNotice(result);
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
} catch { /* timeout or network error — skip */ }
|
|
1176
|
+
} else if (isStale) {
|
|
1177
|
+
// Cache exists but stale — refresh in background (non-blocking)
|
|
1178
|
+
const script = `
|
|
1179
|
+
const https = require("https");
|
|
1180
|
+
const fs = require("fs");
|
|
1181
|
+
https.get("https://registry.npmjs.org/@tekyzinc/gsd-t/latest",
|
|
1182
|
+
{ timeout: 5000 }, (res) => {
|
|
1183
|
+
let d = "";
|
|
1184
|
+
res.on("data", (c) => d += c);
|
|
1185
|
+
res.on("end", () => {
|
|
1186
|
+
try {
|
|
1187
|
+
const v = JSON.parse(d).version;
|
|
1188
|
+
fs.writeFileSync(${JSON.stringify(UPDATE_CHECK_FILE)},
|
|
1189
|
+
JSON.stringify({ latest: v, timestamp: Date.now() }));
|
|
1190
|
+
} catch {}
|
|
1191
|
+
});
|
|
1192
|
+
}).on("error", () => {});
|
|
1193
|
+
`.replace(/\n/g, "");
|
|
1194
|
+
const { spawn } = require("child_process");
|
|
1195
|
+
const child = spawn(process.execPath, ["-e", script], {
|
|
1196
|
+
detached: true,
|
|
1197
|
+
stdio: "ignore",
|
|
1198
|
+
});
|
|
1199
|
+
child.unref();
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function showUpdateNotice(latest) {
|
|
1204
|
+
log("");
|
|
1205
|
+
log(` ${YELLOW}╭──────────────────────────────────────────────╮${RESET}`);
|
|
1206
|
+
log(` ${YELLOW}│${RESET} Update available: ${DIM}${PKG_VERSION}${RESET} → ${GREEN}${latest}${RESET} ${YELLOW}│${RESET}`);
|
|
1207
|
+
log(` ${YELLOW}│${RESET} Run: ${CYAN}npm update -g @tekyzinc/gsd-t${RESET} ${YELLOW}│${RESET}`);
|
|
1208
|
+
log(` ${YELLOW}│${RESET} Then: ${CYAN}gsd-t update-all${RESET} ${YELLOW}│${RESET}`);
|
|
1209
|
+
log(` ${YELLOW}│${RESET} Changelog: ${CYAN}gsd-t changelog${RESET} ${YELLOW}│${RESET}`);
|
|
1210
|
+
log(` ${YELLOW}╰──────────────────────────────────────────────╯${RESET}`);
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
function doChangelog() {
|
|
1214
|
+
try {
|
|
1215
|
+
if (process.platform === "win32") {
|
|
1216
|
+
// "start" is a cmd built-in — must go through cmd.exe, but with safe array args
|
|
1217
|
+
execFileSync("cmd", ["/c", "start", "", CHANGELOG_URL], { stdio: "ignore" });
|
|
1218
|
+
} else {
|
|
1219
|
+
const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
|
|
1220
|
+
execFileSync(openCmd, [CHANGELOG_URL], { stdio: "ignore" });
|
|
1221
|
+
}
|
|
1222
|
+
success(`Opened changelog in browser`);
|
|
1223
|
+
} catch {
|
|
1224
|
+
// Fallback: print the URL
|
|
1225
|
+
log(`\n ${CHANGELOG_URL}\n`);
|
|
1226
|
+
}
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
function showHelp() {
|
|
1230
|
+
log("");
|
|
1231
|
+
log(`${BOLD}GSD-T${RESET} — Contract-Driven Development for Claude Code`);
|
|
1232
|
+
log("");
|
|
1233
|
+
log(`${BOLD}Usage:${RESET}`);
|
|
1234
|
+
log(` npx @tekyzinc/gsd-t ${CYAN}<command>${RESET} [options]`);
|
|
1235
|
+
log("");
|
|
1236
|
+
log(`${BOLD}Commands:${RESET}`);
|
|
1237
|
+
log(` ${CYAN}install${RESET} Install slash commands + global CLAUDE.md`);
|
|
1238
|
+
log(` ${CYAN}update${RESET} Update global commands + CLAUDE.md`);
|
|
1239
|
+
log(` ${CYAN}update-all${RESET} Update globally + all registered project CLAUDE.md files`);
|
|
1240
|
+
log(` ${CYAN}init${RESET} [name] Scaffold GSD-T project (auto-registers)`);
|
|
1241
|
+
log(` ${CYAN}register${RESET} Register current directory as a GSD-T project`);
|
|
1242
|
+
log(` ${CYAN}status${RESET} Show installation status + check for updates`);
|
|
1243
|
+
log(` ${CYAN}uninstall${RESET} Remove GSD-T commands (keeps project files)`);
|
|
1244
|
+
log(` ${CYAN}doctor${RESET} Diagnose common issues`);
|
|
1245
|
+
log(` ${CYAN}changelog${RESET} Open changelog in the browser`);
|
|
1246
|
+
log(` ${CYAN}help${RESET} Show this help`);
|
|
1247
|
+
log("");
|
|
1248
|
+
log(`${BOLD}Examples:${RESET}`);
|
|
1249
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t install`);
|
|
1250
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t init my-saas-app`);
|
|
1251
|
+
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t update`);
|
|
1252
|
+
log("");
|
|
1253
|
+
log(`${BOLD}After installing, use in Claude Code:${RESET}`);
|
|
1254
|
+
log(` ${DIM}>${RESET} /user:gsd-t-project "Build a task management app"`);
|
|
1255
|
+
log(` ${DIM}>${RESET} /user:gsd-t-wave`);
|
|
1256
|
+
log("");
|
|
1257
|
+
log(`${DIM}Docs: https://github.com/Tekyz-Inc/get-stuff-done-teams${RESET}`);
|
|
1258
|
+
log("");
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
// ─── Main ────────────────────────────────────────────────────────────────────
|
|
1262
|
+
|
|
1263
|
+
const args = process.argv.slice(2);
|
|
1264
|
+
const command = args[0] || "help";
|
|
1265
|
+
|
|
1266
|
+
switch (command) {
|
|
1267
|
+
case "install":
|
|
1268
|
+
doInstall();
|
|
1269
|
+
break;
|
|
1270
|
+
case "update":
|
|
1271
|
+
doUpdate();
|
|
1272
|
+
break;
|
|
1273
|
+
case "update-all":
|
|
1274
|
+
doUpdateAll();
|
|
1275
|
+
break;
|
|
1276
|
+
case "init":
|
|
1277
|
+
doInit(args[1]);
|
|
1278
|
+
break;
|
|
1279
|
+
case "register":
|
|
1280
|
+
doRegister();
|
|
1281
|
+
break;
|
|
1282
|
+
case "status":
|
|
1283
|
+
doStatus();
|
|
1284
|
+
break;
|
|
1285
|
+
case "uninstall":
|
|
1286
|
+
doUninstall();
|
|
1287
|
+
break;
|
|
1288
|
+
case "doctor":
|
|
1289
|
+
doDoctor();
|
|
1290
|
+
break;
|
|
1291
|
+
case "changelog":
|
|
1292
|
+
doChangelog();
|
|
1293
|
+
break;
|
|
1294
|
+
case "help":
|
|
1295
|
+
case "--help":
|
|
1296
|
+
case "-h":
|
|
1297
|
+
showHelp();
|
|
1298
|
+
break;
|
|
1299
|
+
case "--version":
|
|
1300
|
+
case "-v":
|
|
1301
|
+
log(PKG_VERSION);
|
|
1302
|
+
break;
|
|
1303
|
+
default:
|
|
1304
|
+
error(`Unknown command: ${command}`);
|
|
1305
|
+
showHelp();
|
|
1306
|
+
process.exit(1);
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
checkForUpdates();
|