@tekyzinc/gsd-t 2.71.19 → 2.71.21

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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [2.71.21] - 2026-04-08
6
+
7
+ ### Fixed (orchestrator — timeout false-pass, review server health, stale cleanup)
8
+ - **Reviewer timeout/kill no longer treated as "pass"** — exit codes 143 (SIGTERM) and 137 (SIGKILL) are now always detected as failures regardless of duration. Previously, a reviewer that timed out at 300s was parsed as "0 issues = pass" because crash detection only checked duration < 10s.
9
+ - **Empty output with non-zero exit also caught** — any reviewer that exits non-zero with no output is treated as a failure, not a clean pass.
10
+ - **Review server health check during human review gate** — every ~30s the polling loop verifies port 3456 is alive. If the review server dies, it auto-restarts. Previously, a dead review server left the orchestrator stuck forever.
11
+ - **Stale auto-review cleanup** — old auto-review files from previous runs are cleared at the start of each phase's review cycle, preventing misleading results from prior orchestrator runs.
12
+
13
+ ## [2.71.20] - 2026-04-08
14
+
15
+ ### Fixed (orchestrator — reviewer crash false-pass + Ctrl+C + build logging)
16
+ - **Reviewer crash no longer treated as "pass"** — if the reviewer exits with non-zero code in under 10s, it's a crash, not a clean review. Retried on next cycle. Previously, empty output from a crashed reviewer was parsed as "0 issues = pass."
17
+ - **Ctrl+C now works** — replaced `Atomics.wait` with `sleep` command for synchronous polling. `Atomics.wait` blocks the event loop completely, preventing SIGINT.
18
+ - **Build output logging** — builder output written to `.gsd-t/design-review/build-logs/{phase}-build.log` for debugging.
19
+
5
20
  ## [2.71.18] - 2026-04-08
6
21
 
7
22
  ### Fixed (orchestrator — Claude permissions and timeouts)
@@ -52,10 +52,12 @@ function ensureDir(dir) {
52
52
  }
53
53
 
54
54
  function syncSleep(ms) {
55
+ // Use sleep command instead of Atomics.wait — Atomics blocks the event loop
56
+ // completely, preventing SIGINT (Ctrl+C) from being handled
55
57
  try {
56
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
58
+ execFileSync("sleep", [String(ms / 1000)], { stdio: "pipe" });
57
59
  } catch {
58
- try { execFileSync("sleep", [String(ms / 1000)], { stdio: "pipe" }); } catch { /* ignore */ }
60
+ // sleep interrupted by signal that's fine
59
61
  }
60
62
  }
61
63
 
@@ -366,6 +368,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
366
368
  openBrowser(`http://localhost:${reviewPort}/review`);
367
369
 
368
370
  // IRONCLAD GATE — JavaScript polling loop
371
+ let healthCheckCounter = 0;
369
372
  while (true) {
370
373
  if (fs.existsSync(signalPath)) {
371
374
  try {
@@ -374,6 +377,22 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
374
377
  return data;
375
378
  } catch { /* malformed — wait for rewrite */ }
376
379
  }
380
+
381
+ // Every 10 polls (~30s), verify review server is still alive
382
+ healthCheckCounter++;
383
+ if (healthCheckCounter % 10 === 0) {
384
+ if (!isPortInUse(reviewPort)) {
385
+ warn("Review server is down! Restarting...");
386
+ const devPort = this._activeDevPort || reviewPort - 1283; // fallback heuristic
387
+ const info = this.startReviewServer(projectDir, devPort, reviewPort);
388
+ if (info.pid || info.alreadyRunning) {
389
+ success("Review server restarted");
390
+ } else {
391
+ error("Failed to restart review server — please restart the orchestrator");
392
+ }
393
+ }
394
+ }
395
+
377
396
  syncSleep(3000);
378
397
  }
379
398
  }
@@ -512,6 +531,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
512
531
  const devInfo = this.startDevServer(projectDir, devPort);
513
532
  const reviewInfo = this.startReviewServer(projectDir, devPort, reviewPort);
514
533
  this.pids = [devInfo.pid, reviewInfo.pid].filter(Boolean);
534
+ this._activeDevPort = devPort;
515
535
  }
516
536
 
517
537
  // Register cleanup on exit
@@ -593,6 +613,16 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
593
613
  // If issues found → spawn fixer Claude → re-measure → re-review until clean.
594
614
  const maxAutoReviewCycles = this.wf.defaults?.maxAutoReviewCycles || 4;
595
615
  if (this.wf.buildReviewPrompt) {
616
+ // Clear stale auto-review files from previous runs for this phase
617
+ const arDir = path.join(this.getReviewDir(projectDir), "auto-review");
618
+ if (fs.existsSync(arDir)) {
619
+ for (const f of fs.readdirSync(arDir)) {
620
+ if (f.startsWith(`${phase}-`)) {
621
+ try { fs.unlinkSync(path.join(arDir, f)); } catch { /* ignore */ }
622
+ }
623
+ }
624
+ }
625
+
596
626
  let autoReviewCycle = 0;
597
627
  let autoReviewClean = false;
598
628
 
@@ -607,14 +637,30 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
607
637
  const reviewResult = this.spawnClaude(projectDir, reviewPrompt, reviewTimeout);
608
638
 
609
639
  // Parse reviewer output for issues
610
- const issues = this.wf.parseReviewResult
611
- ? this.wf.parseReviewResult(reviewResult.output, phase)
612
- : this._parseDefaultReviewResult(reviewResult.output);
613
-
614
- if (reviewResult.exitCode === 0) {
615
- success(`Reviewer finished in ${reviewResult.duration}s`);
640
+ let issues;
641
+
642
+ // Treat reviewer failure as a failed review, not a pass:
643
+ // - crash: non-zero exit + very short duration (< 10s)
644
+ // - timeout/kill: exit codes 143 (SIGTERM) or 137 (SIGKILL)
645
+ // - empty output with non-zero exit: reviewer produced nothing useful
646
+ const isCrash = reviewResult.exitCode !== 0 && reviewResult.duration < 10;
647
+ const isKilled = [143, 137].includes(reviewResult.exitCode);
648
+ const isEmptyFail = reviewResult.exitCode !== 0 && !reviewResult.output.trim();
649
+
650
+ if (isCrash || isKilled || isEmptyFail) {
651
+ const reason = isCrash ? "crashed" : isKilled ? "killed/timed out" : "failed with no output";
652
+ warn(`Reviewer ${reason} (code ${reviewResult.exitCode}, ${reviewResult.duration}s) — treating as review failure, will retry`);
653
+ issues = [{ component: "ALL", severity: "critical", description: `Reviewer ${reason} with exit code ${reviewResult.exitCode} — review not performed` }];
616
654
  } else {
617
- warn(`Reviewer exited with code ${reviewResult.exitCode} after ${reviewResult.duration}s`);
655
+ issues = this.wf.parseReviewResult
656
+ ? this.wf.parseReviewResult(reviewResult.output, phase)
657
+ : this._parseDefaultReviewResult(reviewResult.output);
658
+
659
+ if (reviewResult.exitCode === 0) {
660
+ success(`Reviewer finished in ${reviewResult.duration}s`);
661
+ } else {
662
+ warn(`Reviewer exited with code ${reviewResult.exitCode} after ${reviewResult.duration}s`);
663
+ }
618
664
  }
619
665
 
620
666
  // Write review report
@@ -622,7 +668,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
622
668
  ensureDir(reportDir);
623
669
  fs.writeFileSync(
624
670
  path.join(reportDir, `${phase}-cycle-${autoReviewCycle}.json`),
625
- JSON.stringify({ cycle: autoReviewCycle, issues, output: reviewResult.output.slice(0, 5000) }, null, 2)
671
+ JSON.stringify({ cycle: autoReviewCycle, issues, exitCode: reviewResult.exitCode, duration: reviewResult.duration, output: reviewResult.output.slice(0, 5000) }, null, 2)
626
672
  );
627
673
 
628
674
  if (issues.length === 0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "2.71.19",
3
+ "version": "2.71.21",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 56 slash commands with headless CI/CD mode, graph-powered code analysis, real-time agent dashboard, execution intelligence, task telemetry, doc-ripple enforcement, backlog management, impact analysis, test sync, milestone archival, and PRD generation",
5
5
  "author": "Tekyz, Inc.",
6
6
  "license": "MIT",