@tekyzinc/gsd-t 2.71.20 → 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,14 @@
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
+
5
13
  ## [2.71.20] - 2026-04-08
6
14
 
7
15
  ### Fixed (orchestrator — reviewer crash false-pass + Ctrl+C + build logging)
@@ -368,6 +368,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
368
368
  openBrowser(`http://localhost:${reviewPort}/review`);
369
369
 
370
370
  // IRONCLAD GATE — JavaScript polling loop
371
+ let healthCheckCounter = 0;
371
372
  while (true) {
372
373
  if (fs.existsSync(signalPath)) {
373
374
  try {
@@ -376,6 +377,22 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
376
377
  return data;
377
378
  } catch { /* malformed — wait for rewrite */ }
378
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
+
379
396
  syncSleep(3000);
380
397
  }
381
398
  }
@@ -514,6 +531,7 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
514
531
  const devInfo = this.startDevServer(projectDir, devPort);
515
532
  const reviewInfo = this.startReviewServer(projectDir, devPort, reviewPort);
516
533
  this.pids = [devInfo.pid, reviewInfo.pid].filter(Boolean);
534
+ this._activeDevPort = devPort;
517
535
  }
518
536
 
519
537
  // Register cleanup on exit
@@ -595,6 +613,16 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
595
613
  // If issues found → spawn fixer Claude → re-measure → re-review until clean.
596
614
  const maxAutoReviewCycles = this.wf.defaults?.maxAutoReviewCycles || 4;
597
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
+
598
626
  let autoReviewCycle = 0;
599
627
  let autoReviewClean = false;
600
628
 
@@ -611,10 +639,18 @@ ${BOLD}Phases:${RESET} ${this.wf.phases.join(" → ")}
611
639
  // Parse reviewer output for issues
612
640
  let issues;
613
641
 
614
- // Treat reviewer crash (non-zero exit, very short duration) as a failed review, not a pass
615
- if (reviewResult.exitCode !== 0 && reviewResult.duration < 10) {
616
- warn(`Reviewer crashed (code ${reviewResult.exitCode}, ${reviewResult.duration}s) treating as review failure, will retry`);
617
- issues = [{ component: "ALL", severity: "critical", description: `Reviewer crashed with exit code ${reviewResult.exitCode} — review not performed` }];
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` }];
618
654
  } else {
619
655
  issues = this.wf.parseReviewResult
620
656
  ? this.wf.parseReviewResult(reviewResult.output, phase)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "2.71.20",
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",