@tekyzinc/gsd-t 2.20.7 → 2.22.0

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/README.md CHANGED
@@ -18,7 +18,7 @@ A methodology for reliable, parallelizable development using Claude Code with op
18
18
  npx @tekyzinc/gsd-t install
19
19
  ```
20
20
 
21
- This installs 38 GSD-T commands + 3 utility commands to `~/.claude/commands/` and the global CLAUDE.md to `~/.claude/CLAUDE.md`. Works on Windows, Mac, and Linux.
21
+ This installs 39 GSD-T commands + 3 utility commands to `~/.claude/commands/` and the global CLAUDE.md to `~/.claude/CLAUDE.md`. Works on Windows, Mac, and Linux.
22
22
 
23
23
  ### Start Using It
24
24
 
@@ -129,6 +129,7 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
129
129
  | `/user:gsd-t-impact` | Analyze downstream effects | In wave |
130
130
  | `/user:gsd-t-execute` | Run tasks (solo or team) | In wave |
131
131
  | `/user:gsd-t-test-sync` | Sync tests with code changes | In wave |
132
+ | `/user:gsd-t-qa` | QA agent — test generation, execution, gap reporting | Auto-spawned |
132
133
  | `/user:gsd-t-integrate` | Wire domains together | In wave |
133
134
  | `/user:gsd-t-verify` | Run quality gates | In wave |
134
135
  | `/user:gsd-t-complete-milestone` | Archive + git tag | In wave |
@@ -145,6 +146,7 @@ This will replace changed command files, back up your CLAUDE.md if customized, a
145
146
  | `/user:gsd-t-log` | Sync progress Decision Log with recent git activity | Manual |
146
147
  | `/user:gsd-t-version-update` | Update GSD-T to latest version | Manual |
147
148
  | `/user:gsd-t-version-update-all` | Update GSD-T + all registered projects | Manual |
149
+ | `/user:gsd-t-triage-and-merge` | Auto-review, merge, and publish GitHub branches | Manual |
148
150
 
149
151
  ### Backlog Management
150
152
 
@@ -281,8 +283,9 @@ get-stuff-done-teams/
281
283
  ├── LICENSE
282
284
  ├── bin/
283
285
  │ └── gsd-t.js # CLI installer
284
- ├── commands/ # 41 slash commands
286
+ ├── commands/ # 42 slash commands
285
287
  │ ├── gsd-t-*.md # 38 GSD-T workflow commands
288
+ │ ├── gsd.md # GSD-T smart router
286
289
  │ ├── branch.md # Git branch helper
287
290
  │ ├── checkin.md # Auto-version + commit/push helper
288
291
  │ └── Claude-md.md # Reload CLAUDE.md directives
package/bin/gsd-t.js CHANGED
@@ -104,6 +104,10 @@ function ensureDir(dir) {
104
104
  fs.mkdirSync(dir, { recursive: true });
105
105
  return true;
106
106
  }
107
+ if (isSymlink(dir)) {
108
+ warn(`Refusing to use symlinked directory: ${dir}`);
109
+ return false;
110
+ }
107
111
  return false;
108
112
  }
109
113
 
@@ -133,7 +137,12 @@ function validateVersion(ver) {
133
137
 
134
138
  function validateProjectPath(p) {
135
139
  try {
136
- return path.isAbsolute(p) && fs.existsSync(p) && fs.statSync(p).isDirectory();
140
+ if (!path.isAbsolute(p) || !fs.existsSync(p)) return false;
141
+ const stat = fs.statSync(p);
142
+ if (!stat.isDirectory()) return false;
143
+ // On Unix, verify directory is owned by current user (defense-in-depth)
144
+ if (typeof process.getuid === "function" && stat.uid !== process.getuid()) return false;
145
+ return true;
137
146
  } catch {
138
147
  return false;
139
148
  }
@@ -381,19 +390,7 @@ function configureHeartbeatHooks(scriptPath) {
381
390
 
382
391
  // ─── Commands ────────────────────────────────────────────────────────────────
383
392
 
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
393
+ function installCommands(isUpdate) {
397
394
  heading("Slash Commands");
398
395
  const commandFiles = getCommandFiles();
399
396
  const gsdtCommands = getGsdtCommands();
@@ -406,7 +403,6 @@ function doInstall(opts = {}) {
406
403
  const dest = path.join(COMMANDS_DIR, file);
407
404
 
408
405
  if (isUpdate && fs.existsSync(dest)) {
409
- // Compare content — only overwrite if changed
410
406
  const srcContent = fs.readFileSync(src, "utf8");
411
407
  const destContent = fs.readFileSync(dest, "utf8");
412
408
  if (normalizeEol(srcContent) === normalizeEol(destContent)) {
@@ -423,8 +419,10 @@ function doInstall(opts = {}) {
423
419
  info(`${skipped} commands unchanged`);
424
420
  }
425
421
  success(`${gsdtCommands.length} GSD-T commands + ${utilityCommands.length} utilities ${isUpdate ? "updated" : "installed"} → ~/.claude/commands/`);
422
+ return { gsdtCommands, utilityCommands };
423
+ }
426
424
 
427
- // 3. Handle global CLAUDE.md
425
+ function installGlobalClaudeMd(isUpdate) {
428
426
  heading("Global CLAUDE.md");
429
427
  const globalSrc = path.join(PKG_TEMPLATES, "CLAUDE-global.md");
430
428
 
@@ -433,12 +431,10 @@ function doInstall(opts = {}) {
433
431
 
434
432
  if (existing.includes("GSD-T: Contract-Driven Development")) {
435
433
  if (isUpdate) {
436
- // Check if there are customizations (lines not in our template)
437
434
  const template = fs.readFileSync(globalSrc, "utf8");
438
435
  if (normalizeEol(existing) === normalizeEol(template)) {
439
436
  copyFile(globalSrc, GLOBAL_CLAUDE_MD, "CLAUDE.md updated (no customizations detected)");
440
437
  } else {
441
- // Backup and replace, warn about customizations
442
438
  const backupPath = GLOBAL_CLAUDE_MD + ".backup-" + Date.now();
443
439
  if (isSymlink(backupPath)) {
444
440
  warn("Skipping backup — target is a symlink");
@@ -454,7 +450,6 @@ function doInstall(opts = {}) {
454
450
  info("Run 'gsd-t update' to overwrite with latest version");
455
451
  }
456
452
  } else {
457
- // Existing CLAUDE.md without GSD-T — append
458
453
  if (isSymlink(GLOBAL_CLAUDE_MD)) {
459
454
  warn("Skipping CLAUDE.md append — target is a symlink");
460
455
  } else {
@@ -468,15 +463,26 @@ function doInstall(opts = {}) {
468
463
  } else {
469
464
  copyFile(globalSrc, GLOBAL_CLAUDE_MD, "CLAUDE.md installed → ~/.claude/CLAUDE.md");
470
465
  }
466
+ }
467
+
468
+ function doInstall(opts = {}) {
469
+ const isUpdate = opts.update || false;
470
+ const verb = isUpdate ? "Updating" : "Installing";
471
+
472
+ heading(`${verb} GSD-T ${versionLink()}`);
473
+ log("");
474
+
475
+ if (ensureDir(COMMANDS_DIR)) {
476
+ success("Created ~/.claude/commands/");
477
+ }
478
+
479
+ const { gsdtCommands, utilityCommands } = installCommands(isUpdate);
480
+ installGlobalClaudeMd(isUpdate);
471
481
 
472
- // 4. Install heartbeat script + hooks
473
482
  heading("Heartbeat (Real-time Events)");
474
483
  installHeartbeat();
475
-
476
- // 5. Save version
477
484
  saveInstalledVersion();
478
485
 
479
- // 6. Summary
480
486
  heading("Installation Complete!");
481
487
  log("");
482
488
  log(` Commands: ${gsdtCommands.length} GSD-T + ${utilityCommands.length} utility commands in ~/.claude/commands/`);
@@ -518,53 +524,35 @@ function doUpdate() {
518
524
  doInstall({ update: true });
519
525
  }
520
526
 
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
527
+ function initClaudeMd(projectDir, projectName, today) {
540
528
  const claudeMdPath = path.join(projectDir, "CLAUDE.md");
541
529
  if (isSymlink(claudeMdPath)) {
542
530
  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
- }
531
+ return;
532
+ }
533
+ try {
534
+ const template = fs.readFileSync(path.join(PKG_TEMPLATES, "CLAUDE-project.md"), "utf8");
535
+ const content = applyTokens(template, projectName, today);
536
+ fs.writeFileSync(claudeMdPath, content, { flag: "wx" });
537
+ success("CLAUDE.md created");
538
+ } catch (e) {
539
+ if (e.code === "EEXIST") {
540
+ const content = fs.readFileSync(claudeMdPath, "utf8");
541
+ if (content.includes("GSD-T Workflow")) {
542
+ info("CLAUDE.md already contains GSD-T section — skipping");
543
+ } else {
544
+ warn("CLAUDE.md exists but doesn't reference GSD-T");
545
+ info("Run /user:gsd-t-init inside Claude Code to add GSD-T section");
546
+ }
547
+ } else { throw e; }
560
548
  }
549
+ }
561
550
 
562
- // 2. Create docs/ with templates
551
+ function initDocs(projectDir, projectName, today) {
563
552
  const docsDir = path.join(projectDir, "docs");
564
553
  ensureDir(docsDir);
565
554
 
566
555
  const docTemplates = ["requirements.md", "architecture.md", "workflows.md", "infrastructure.md"];
567
-
568
556
  for (const file of docTemplates) {
569
557
  const destPath = path.join(docsDir, file);
570
558
  if (isSymlink(destPath)) {
@@ -581,8 +569,9 @@ function doInit(projectName) {
581
569
  else { throw e; }
582
570
  }
583
571
  }
572
+ }
584
573
 
585
- // 3. Create .gsd-t/ structure
574
+ function initGsdtDir(projectDir, projectName, today) {
586
575
  const gsdtDir = path.join(projectDir, ".gsd-t");
587
576
  const contractsDir = path.join(gsdtDir, "contracts");
588
577
  const domainsDir = path.join(gsdtDir, "domains");
@@ -590,7 +579,6 @@ function doInit(projectName) {
590
579
  ensureDir(contractsDir);
591
580
  ensureDir(domainsDir);
592
581
 
593
- // .gitkeep files so empty dirs are tracked
594
582
  for (const dir of [contractsDir, domainsDir]) {
595
583
  const gitkeep = path.join(dir, ".gitkeep");
596
584
  if (isSymlink(gitkeep)) continue;
@@ -618,8 +606,7 @@ function doInit(projectName) {
618
606
  }
619
607
 
620
608
  // Backlog files
621
- const backlogFiles = ["backlog.md", "backlog-settings.md"];
622
- for (const file of backlogFiles) {
609
+ for (const file of ["backlog.md", "backlog-settings.md"]) {
623
610
  const destPath = path.join(gsdtDir, file);
624
611
  if (isSymlink(destPath)) {
625
612
  warn(`Skipping .gsd-t/${file} — target is a symlink`);
@@ -634,13 +621,33 @@ function doInit(projectName) {
634
621
  else { throw e; }
635
622
  }
636
623
  }
624
+ }
625
+
626
+ function doInit(projectName) {
627
+ if (!projectName) {
628
+ projectName = path.basename(process.cwd());
629
+ }
630
+
631
+ if (!validateProjectName(projectName)) {
632
+ error(`Invalid project name: "${projectName}"`);
633
+ info("Project names must start with a letter or number and contain only letters, numbers, dots, hyphens, underscores, or spaces (max 101 chars)");
634
+ return;
635
+ }
636
+
637
+ heading(`Initializing GSD-T project: ${projectName}`);
638
+ log("");
639
+
640
+ const projectDir = process.cwd();
641
+ const today = new Date().toISOString().split("T")[0];
642
+
643
+ initClaudeMd(projectDir, projectName, today);
644
+ initDocs(projectDir, projectName, today);
645
+ initGsdtDir(projectDir, projectName, today);
637
646
 
638
- // 4. Register in project index
639
647
  if (registerProject(projectDir)) {
640
648
  success("Registered in ~/.claude/.gsd-t-projects");
641
649
  }
642
650
 
643
- // 5. Summary
644
651
  heading("Project Initialized!");
645
652
  log("");
646
653
  log(` ${projectDir}/`);
@@ -773,8 +780,10 @@ function doUninstall() {
773
780
  const commands = getInstalledCommands();
774
781
  let removed = 0;
775
782
  for (const file of commands) {
783
+ const fp = path.join(COMMANDS_DIR, file);
784
+ if (isSymlink(fp)) { warn(`Skipping symlink: ${file}`); continue; }
776
785
  try {
777
- fs.unlinkSync(path.join(COMMANDS_DIR, file));
786
+ fs.unlinkSync(fp);
778
787
  removed++;
779
788
  } catch (e) {
780
789
  error(`Failed to remove ${file}: ${e.message}`);
@@ -786,7 +795,7 @@ function doUninstall() {
786
795
 
787
796
  // Remove version file
788
797
  try {
789
- if (fs.existsSync(VERSION_FILE)) {
798
+ if (fs.existsSync(VERSION_FILE) && !isSymlink(VERSION_FILE)) {
790
799
  fs.unlinkSync(VERSION_FILE);
791
800
  }
792
801
  } catch (e) {
@@ -971,13 +980,8 @@ function doUpdateAll() {
971
980
  log("");
972
981
  }
973
982
 
974
- function doDoctor() {
975
- heading("GSD-T Doctor");
976
- log("");
977
-
983
+ function checkDoctorEnvironment() {
978
984
  let issues = 0;
979
-
980
- // 1. Node version
981
985
  const nodeVersion = parseInt(process.version.slice(1));
982
986
  if (nodeVersion >= 16) {
983
987
  success(`Node.js ${process.version}`);
@@ -985,8 +989,6 @@ function doDoctor() {
985
989
  error(`Node.js ${process.version} — requires >= 16`);
986
990
  issues++;
987
991
  }
988
-
989
- // 2. Claude Code installed?
990
992
  try {
991
993
  const claudeVersion = execFileSync("claude", ["--version"], { encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] }).trim();
992
994
  success(`Claude Code: ${claudeVersion}`);
@@ -995,8 +997,6 @@ function doDoctor() {
995
997
  info("Install with: npm install -g @anthropic-ai/claude-code");
996
998
  issues++;
997
999
  }
998
-
999
- // 3. ~/.claude/ exists?
1000
1000
  if (fs.existsSync(CLAUDE_DIR)) {
1001
1001
  success("~/.claude/ directory exists");
1002
1002
  } else {
@@ -1004,8 +1004,11 @@ function doDoctor() {
1004
1004
  info("Run 'npx @tekyzinc/gsd-t install' to create it");
1005
1005
  issues++;
1006
1006
  }
1007
+ return issues;
1008
+ }
1007
1009
 
1008
- // 4. Commands installed?
1010
+ function checkDoctorInstallation() {
1011
+ let issues = 0;
1009
1012
  const installed = getInstalledCommands();
1010
1013
  const expected = getCommandFiles();
1011
1014
  if (installed.length === expected.length) {
@@ -1019,8 +1022,6 @@ function doDoctor() {
1019
1022
  error("No GSD-T commands installed");
1020
1023
  issues++;
1021
1024
  }
1022
-
1023
- // 5. CLAUDE.md
1024
1025
  if (fs.existsSync(GLOBAL_CLAUDE_MD)) {
1025
1026
  const content = fs.readFileSync(GLOBAL_CLAUDE_MD, "utf8");
1026
1027
  if (content.includes("GSD-T")) {
@@ -1033,8 +1034,6 @@ function doDoctor() {
1033
1034
  error("No global CLAUDE.md");
1034
1035
  issues++;
1035
1036
  }
1036
-
1037
- // 6. settings.json valid?
1038
1037
  if (fs.existsSync(SETTINGS_JSON)) {
1039
1038
  try {
1040
1039
  JSON.parse(fs.readFileSync(SETTINGS_JSON, "utf8"));
@@ -1046,8 +1045,25 @@ function doDoctor() {
1046
1045
  } else {
1047
1046
  info("No settings.json (not required)");
1048
1047
  }
1048
+ let encodingIssues = 0;
1049
+ for (const file of installed) {
1050
+ const content = fs.readFileSync(path.join(COMMANDS_DIR, file), "utf8");
1051
+ if (content.includes("\u00e2\u20ac") || content.includes("\u00c3")) {
1052
+ encodingIssues++;
1053
+ }
1054
+ }
1055
+ if (encodingIssues > 0) {
1056
+ error(`${encodingIssues} command files have encoding issues (corrupted characters)`);
1057
+ info("Run 'npx @tekyzinc/gsd-t update' to replace with clean versions");
1058
+ issues++;
1059
+ } else if (installed.length > 0) {
1060
+ success("No encoding issues in command files");
1061
+ }
1062
+ return issues;
1063
+ }
1049
1064
 
1050
- // 7. Playwright check (current project)
1065
+ function checkDoctorProject() {
1066
+ let issues = 0;
1051
1067
  const cwd = process.cwd();
1052
1068
  if (hasPlaywright(cwd)) {
1053
1069
  success("Playwright configured");
@@ -1056,8 +1072,6 @@ function doDoctor() {
1056
1072
  info("Will be auto-installed when you run a GSD-T testing command");
1057
1073
  issues++;
1058
1074
  }
1059
-
1060
- // 8. Swagger/OpenAPI check (current project)
1061
1075
  if (hasApi(cwd)) {
1062
1076
  if (hasSwagger(cwd)) {
1063
1077
  success("Swagger/OpenAPI configured");
@@ -1069,24 +1083,16 @@ function doDoctor() {
1069
1083
  } else {
1070
1084
  info("No API framework detected (Swagger check skipped)");
1071
1085
  }
1086
+ return issues;
1087
+ }
1072
1088
 
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
1089
+ function doDoctor() {
1090
+ heading("GSD-T Doctor");
1091
+ log("");
1092
+ let issues = 0;
1093
+ issues += checkDoctorEnvironment();
1094
+ issues += checkDoctorInstallation();
1095
+ issues += checkDoctorProject();
1090
1096
  log("");
1091
1097
  if (issues === 0) {
1092
1098
  log(`${GREEN}${BOLD} All checks passed!${RESET}`);
@@ -1175,24 +1181,8 @@ function checkForUpdates() {
1175
1181
  } catch { /* timeout or network error — skip */ }
1176
1182
  } else if (isStale) {
1177
1183
  // 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], {
1184
+ const updateScript = path.join(__dirname, "..", "scripts", "npm-update-check.js");
1185
+ const child = cpSpawn(process.execPath, [updateScript, UPDATE_CHECK_FILE], {
1196
1186
  detached: true,
1197
1187
  stdio: "ignore",
1198
1188
  });
@@ -1213,7 +1203,8 @@ function showUpdateNotice(latest) {
1213
1203
  function doChangelog() {
1214
1204
  try {
1215
1205
  if (process.platform === "win32") {
1216
- // "start" is a cmd built-in must go through cmd.exe, but with safe array args
1206
+ // SAFETY: CHANGELOG_URL is a hardcoded constant (line 43). If it ever becomes
1207
+ // dynamic/user-provided, this cmd.exe call would need URL validation to prevent injection.
1217
1208
  execFileSync("cmd", ["/c", "start", "", CHANGELOG_URL], { stdio: "ignore" });
1218
1209
  } else {
1219
1210
  const openCmd = process.platform === "darwin" ? "open" : "xdg-open";
@@ -175,9 +175,20 @@ Before creating the git tag, verify all documentation is up to date:
175
175
 
176
176
  ### This is the LAST GATE before tagging — nothing should be undocumented.
177
177
 
178
- ## Step 7.6: Test Verification
178
+ ## Step 7.6: Spawn QA Agent and Test Verification
179
179
 
180
- Before creating the git tag, verify the milestone is truly complete:
180
+ Before creating the git tag, spawn the QA teammate for a final test audit:
181
+
182
+ ```
183
+ Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
184
+ Phase context: complete-milestone. Read .gsd-t/contracts/ for contract definitions.
185
+ Run final test audit before milestone archive. Verify all contract tests pass.
186
+ Report: final test pass/fail counts and any unresolved gaps.
187
+ ```
188
+
189
+ Wait for QA agent to complete. QA failure blocks milestone completion.
190
+
191
+ Then verify the milestone is truly complete:
181
192
 
182
193
  1. **Run the full test suite**: Execute ALL tests — unit, integration, and E2E
183
194
  2. **Run Playwright E2E** (if configured): Detect `playwright.config.*` or Playwright in dependencies. If present, run the full Playwright suite. If specs are missing or stale, invoke `gsd-t-test-sync` first.
@@ -40,6 +40,19 @@ The contract didn't specify something it should have. Symptoms:
40
40
 
41
41
  → Update the contract, then fix implementations on both sides.
42
42
 
43
+ ## Step 2.5: Spawn QA Agent
44
+
45
+ Spawn the QA teammate to handle regression testing:
46
+
47
+ ```
48
+ Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
49
+ Phase context: debug. Read .gsd-t/contracts/ for relevant contracts.
50
+ Write a regression test for the bug once root cause is identified.
51
+ Report: regression test status and related test coverage.
52
+ ```
53
+
54
+ QA failure blocks the commit.
55
+
43
56
  ## Step 3: Debug (Solo or Team)
44
57
 
45
58
  ### Solo Mode
@@ -19,6 +19,19 @@ Identify:
19
19
  - Which tasks are unblocked (no pending dependencies)
20
20
  - Which tasks are blocked (waiting on checkpoints)
21
21
 
22
+ ## Step 1.5: Spawn QA Agent
23
+
24
+ Spawn the QA teammate to handle testing alongside execution:
25
+
26
+ ```
27
+ Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
28
+ Phase context: execute. Read .gsd-t/contracts/ for contract definitions.
29
+ Run tests continuously as tasks complete. Write edge case tests for new code paths.
30
+ Report: test pass/fail status after each task completion.
31
+ ```
32
+
33
+ The QA agent runs in parallel — it writes and executes tests while you implement tasks. QA failure on any task blocks proceeding to the next task.
34
+
22
35
  ## Step 2: Choose Execution Mode
23
36
 
24
37
  ### Solo Mode (default)
@@ -79,6 +92,10 @@ Teammate assignments:
79
92
  - Teammate "{domain-1}": Execute .gsd-t/domains/{domain-1}/tasks.md
80
93
  - Teammate "{domain-2}": Execute .gsd-t/domains/{domain-2}/tasks.md
81
94
  - Teammate "{domain-3}": Execute .gsd-t/domains/{domain-3}/tasks.md
95
+ - Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
96
+ Phase context: execute. Read .gsd-t/contracts/ for contract definitions.
97
+ Run tests continuously as tasks complete. Write edge case tests.
98
+ Report: test pass/fail status after each task completion.
82
99
 
83
100
  Lead responsibilities:
84
101
  - Use delegate mode (Shift+Tab)
@@ -36,6 +36,7 @@ MILESTONE WORKFLOW [auto] = in wave
36
36
  impact [auto] Analyze downstream effects before execution
37
37
  execute [auto] Run tasks (solo or team mode)
38
38
  test-sync [auto] Sync tests with code changes
39
+ qa [auto] QA agent — test generation, execution, gap reporting
39
40
  integrate [auto] Wire domains together at boundaries
40
41
  verify [auto] Run quality gates
41
42
  complete-milestone [auto] Archive milestone + git tag
@@ -55,6 +56,7 @@ UTILITIES Manual
55
56
  log Sync progress Decision Log with recent git activity
56
57
  version-update Update GSD-T package to latest version
57
58
  version-update-all Update GSD-T package + all registered projects
59
+ triage-and-merge Auto-review, merge, and publish GitHub branches
58
60
 
59
61
  BACKLOG Manual
60
62
  ───────────────────────────────────────────────────────────────────────────────
@@ -236,6 +238,12 @@ Use these when user asks for help on a specific command:
236
238
  - **Creates**: `.gsd-t/test-coverage.md`, test tasks
237
239
  - **Use when**: After code changes, to maintain test health
238
240
 
241
+ ### qa
242
+ - **Summary**: QA agent — test generation, execution, and gap reporting
243
+ - **Auto-invoked**: Yes (spawned as teammate by partition, plan, execute, verify, quick, debug, integrate, complete-milestone)
244
+ - **Creates**: Contract test skeletons, acceptance tests, edge case tests, test audit reports
245
+ - **Use when**: Automatically spawned — never needs manual invocation. Standalone use for ad-hoc test audits.
246
+
239
247
  ### integrate
240
248
  - **Summary**: Wire domains together at their boundaries
241
249
  - **Auto-invoked**: Yes (in wave, after execute)
@@ -303,6 +311,13 @@ Use these when user asks for help on a specific command:
303
311
  - **Use when**: Progress.md Decision Log is behind — catches up by scanning git commits since the last logged entry
304
312
  - **Features**: Incremental updates, first-time full reconstruction from git history, groups same-day changes
305
313
 
314
+ ### triage-and-merge
315
+ - **Summary**: Auto-review unmerged GitHub branches, merge safe ones, and optionally publish
316
+ - **Auto-invoked**: No
317
+ - **Files**: Reads `CLAUDE.md`, `.gsd-t/progress.md`, `package.json`; updates `package.json`, `.gsd-t/progress.md`, `CHANGELOG.md`
318
+ - **Use when**: Collaborators have pushed branches and you want to batch-review, merge, and publish without manual per-branch ceremony
319
+ - **Features**: 3-tier impact scoring (auto-merge / review / skip), publish gate (auto in Level 3, prompted otherwise), conflict detection, sensitive file detection
320
+
306
321
  ### backlog-add
307
322
  - **Summary**: Capture a new backlog item with auto-categorization
308
323
  - **Auto-invoked**: No
@@ -88,6 +88,19 @@ Result: PASS
88
88
  Result: PARTIAL — needs pagination contract addition
89
89
  ```
90
90
 
91
+ ## Step 4.5: Spawn QA Agent
92
+
93
+ Spawn the QA teammate to verify contract compliance at domain boundaries:
94
+
95
+ ```
96
+ Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
97
+ Phase context: integrate. Read .gsd-t/contracts/ for all contract definitions.
98
+ Run contract compliance tests at every domain boundary.
99
+ Report: boundary-by-boundary test results.
100
+ ```
101
+
102
+ QA failure blocks integration completion.
103
+
91
104
  ## Step 5: Document Ripple
92
105
 
93
106
  Integration is where the real system takes shape. Verify documentation matches reality:
@@ -175,6 +175,19 @@ Before finalizing the partition:
175
175
  2. **Verify passing**: If any tests fail, assign them to the appropriate domain as pre-existing issues
176
176
  3. **Map tests to domains**: Note which test files belong to which domain — this informs task planning
177
177
 
178
+ ## Step 4.7: Spawn QA Agent
179
+
180
+ After contracts are written, spawn the QA teammate to generate contract test skeletons:
181
+
182
+ ```
183
+ Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
184
+ Phase context: partition. Read .gsd-t/contracts/ for contract definitions.
185
+ Generate contract test skeleton files for every contract.
186
+ Report: number of test files generated and total test cases.
187
+ ```
188
+
189
+ Wait for QA agent to complete before proceeding. QA failure blocks partition completion.
190
+
178
191
  ## Step 5: Validate
179
192
 
180
193
  Before finishing, verify:
@@ -118,6 +118,19 @@ Before finalizing the plan:
118
118
  2. **Verify passing**: Document any pre-existing failures — assign them to appropriate domain tasks
119
119
  3. **Include test tasks**: Ensure each domain's task list includes test creation/update tasks where acceptance criteria require verification
120
120
 
121
+ ## Step 4.7: Spawn QA Agent
122
+
123
+ After task lists are created, spawn the QA teammate to generate acceptance test scenarios:
124
+
125
+ ```
126
+ Teammate "qa": Read commands/gsd-t-qa.md for your full instructions.
127
+ Phase context: plan. Read .gsd-t/domains/*/tasks.md for task lists.
128
+ Generate acceptance test scenarios for tasks with user-facing deliverables.
129
+ Report: number of acceptance test scenarios generated.
130
+ ```
131
+
132
+ Wait for QA agent to complete before proceeding.
133
+
121
134
  ## Step 5: Update Progress
122
135
 
123
136
  Update `.gsd-t/progress.md`: