@tekyzinc/gsd-t 3.13.13 → 3.13.14

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,30 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [3.13.14] - 2026-04-17
6
+
7
+ ### Fixed — Supervisor no longer requires project-local `bin/gsd-t.js` + sweep self-protection
8
+
9
+ v3.13.13 successfully swept bee-poc's stray `bin/gsd-t.js`, but exposed a second bug: `bin/gsd-t-unattended.cjs:31` still did `require("./gsd-t.js")` to pull in the `mapHeadlessExitCode` helper. With the stray removed, projects could no longer load the supervisor at all — the require chain now failed on `gsd-t.js` itself instead of `debug-ledger.js`. Three options were on the table (restore both files, resolve from global, or remove all stale copies); we picked the middle path via file extraction.
10
+
11
+ **Fix 1 — extract `mapHeadlessExitCode` to its own file** (`bin/headless-exit-codes.cjs`, new):
12
+ The exit-code contract helper (0=success, 1=verify fail, 2=context budget, 3=non-zero exit, 4=blocked, 5=unknown command) is now a standalone zero-dependency module. `bin/gsd-t-unattended.cjs:31` now does `require("./headless-exit-codes.cjs")` — no transitive dependency on the full CLI installer. `bin/gsd-t.js` still re-exports `mapHeadlessExitCode` for backward compatibility (top-of-file require + `module.exports`).
13
+
14
+ **Fix 2 — `headless-exit-codes.cjs` joins `PROJECT_BIN_TOOLS`**: `copyBinToolsToProject` now copies the new helper into every registered project's `bin/` on the next `update-all`, so the supervisor can load it locally without reaching into the global package.
15
+
16
+ **Fix 3 — sweep self-protection** (`copyBinToolsToProject`):
17
+ While dogfooding v3.13.13, the sweep ran against GSD-T's own source repo (which is registered as a project for eating-our-own-dogfood purposes), recognized the source `bin/gsd-t.js` as matching the installer signature (it IS the installer), and deleted it. The source was restored via `git restore`, but the sweep now carries a guard: if `realpathSync(projectBinDir) === realpathSync(PKG_ROOT/bin)`, skip the sweep entirely. Dogfooding the installer on itself no longer cannibalizes the source.
18
+
19
+ **Files**:
20
+ - `bin/headless-exit-codes.cjs` — new file, 50 lines, extracted helper with explanatory header.
21
+ - `bin/gsd-t-unattended.cjs` — line 31 now requires the new helper instead of `./gsd-t.js`.
22
+ - `bin/gsd-t.js` — top-of-file re-export of `mapHeadlessExitCode` (backward compat); original declaration replaced with a comment pointing to the extracted module; `PROJECT_BIN_TOOLS` gains `headless-exit-codes.cjs`; sweep logic gains the realpath equality guard.
23
+ - `test/bin-gsd-t-resilience.test.js` — new test: `copyBinToolsToProject refuses to sweep the source package's own bin/` (run sweep with `projectDir = PKG_ROOT`, assert `bin/gsd-t.js` still exists after).
24
+
25
+ **Tests**: 1240/1240 pass (+1 new self-protection test). E2E: N/A.
26
+
27
+ **Impact**: bee-poc's supervisor can now load after `gsd-t update-all` copies the new `headless-exit-codes.cjs` helper into `bin/`. No project needs a local `bin/gsd-t.js` for the supervisor to function. Running the installer against its own source repo no longer destroys the source.
28
+
5
29
  ## [3.13.13] - 2026-04-17
6
30
 
7
31
  ### Fixed — Stray sweep now matches older-version installer artifacts
@@ -28,7 +28,7 @@ const fs = require("fs");
28
28
  const path = require("path");
29
29
  const os = require("os");
30
30
  const { execSync, spawnSync } = require("child_process");
31
- const { mapHeadlessExitCode } = require("./gsd-t.js");
31
+ const { mapHeadlessExitCode } = require("./headless-exit-codes.cjs");
32
32
 
33
33
  // Safety rails (m36-safety-rails) — pure-function checks for pre-launch,
34
34
  // supervisor-init, pre-worker, and post-worker hook points per contract §12.
package/bin/gsd-t.js CHANGED
@@ -34,6 +34,12 @@ try {
34
34
  };
35
35
  }
36
36
 
37
+ // Shared headless exit-code helper — lives in its own file so non-entry
38
+ // modules (e.g. bin/gsd-t-unattended.cjs) can require it without pulling
39
+ // in the full CLI. See commentary near the original declaration site and
40
+ // in headless-exit-codes.cjs itself.
41
+ const { mapHeadlessExitCode } = require(path.join(__dirname, "headless-exit-codes.cjs"));
42
+
37
43
  // ─── Configuration ───────────────────────────────────────────────────────────
38
44
 
39
45
  const CLAUDE_DIR = path.join(os.homedir(), ".claude");
@@ -2110,6 +2116,7 @@ const PROJECT_BIN_TOOLS = [
2110
2116
  "context-meter-config.cjs", "token-budget.cjs",
2111
2117
  "gsd-t-unattended.cjs", "gsd-t-unattended-platform.cjs", "gsd-t-unattended-safety.cjs",
2112
2118
  "handoff-lock.cjs", "headless-auto-spawn.cjs",
2119
+ "headless-exit-codes.cjs",
2113
2120
  ];
2114
2121
 
2115
2122
  // Files that older versions of this installer copied into project bin/ but
@@ -2154,27 +2161,41 @@ function copyBinToolsToProject(projectDir, projectName) {
2154
2161
  }
2155
2162
  }
2156
2163
  }
2164
+ // Self-protection: NEVER sweep the package's own bin/. Without this guard,
2165
+ // running `gsd-t update-all` with the GSD-T source repo itself registered
2166
+ // as a project (legitimate during development) would signature-match
2167
+ // bin/gsd-t.js — which IS the installer — and delete the source file.
2168
+ // Resolve both paths to handle symlinks / relative path quirks.
2169
+ const resolvedProjectBin = fs.realpathSync.native
2170
+ ? (() => { try { return fs.realpathSync(projectBinDir); } catch { return projectBinDir; } })()
2171
+ : projectBinDir;
2172
+ const resolvedPkgBin = (() => {
2173
+ try { return fs.realpathSync(path.join(PKG_ROOT, "bin")); } catch { return path.join(PKG_ROOT, "bin"); }
2174
+ })();
2175
+ const isSourcePackage = resolvedProjectBin === resolvedPkgBin;
2157
2176
  let cleaned = 0;
2158
- for (const stray of DEPRECATED_BIN_STRAYS) {
2159
- const strayPath = path.join(projectBinDir, stray);
2160
- if (!fs.existsSync(strayPath)) continue;
2161
- try {
2162
- const strayContent = fs.readFileSync(strayPath, "utf8");
2163
- const head = strayContent.slice(0, 400);
2164
- // Signature-match any version this installer ever shipped. The marker
2165
- // is unique enough to rule out user-owned files: node shebang + the
2166
- // verbatim JSDoc header "GSD-T CLI Installer". Matches every historical
2167
- // version of bin/gsd-t.js, not just the current one, so older strays
2168
- // (e.g. v3.13.11 left behind on v3.13.12+) are still swept.
2169
- const isOurs =
2170
- head.startsWith("#!/usr/bin/env node") &&
2171
- head.includes("GSD-T CLI Installer");
2172
- if (isOurs) {
2173
- fs.unlinkSync(strayPath);
2174
- cleaned++;
2177
+ if (!isSourcePackage) {
2178
+ for (const stray of DEPRECATED_BIN_STRAYS) {
2179
+ const strayPath = path.join(projectBinDir, stray);
2180
+ if (!fs.existsSync(strayPath)) continue;
2181
+ try {
2182
+ const strayContent = fs.readFileSync(strayPath, "utf8");
2183
+ const head = strayContent.slice(0, 400);
2184
+ // Signature-match any version this installer ever shipped. The marker
2185
+ // is unique enough to rule out user-owned files: node shebang + the
2186
+ // verbatim JSDoc header "GSD-T CLI Installer". Matches every historical
2187
+ // version of bin/gsd-t.js, not just the current one, so older strays
2188
+ // (e.g. v3.13.11 left behind on v3.13.12+) are still swept.
2189
+ const isOurs =
2190
+ head.startsWith("#!/usr/bin/env node") &&
2191
+ head.includes("GSD-T CLI Installer");
2192
+ if (isOurs) {
2193
+ fs.unlinkSync(strayPath);
2194
+ cleaned++;
2195
+ }
2196
+ } catch {
2197
+ // leave the file alone on any read error
2175
2198
  }
2176
- } catch {
2177
- // leave the file alone on any read error
2178
2199
  }
2179
2200
  }
2180
2201
  if (cleaned > 0) {
@@ -2852,22 +2873,10 @@ function buildHeadlessCmd(command, cmdArgs) {
2852
2873
  * Exit codes: 0=success, 1=verify-fail, 2=context-budget-exceeded, 3=error,
2853
2874
  * 4=blocked-needs-human, 5=command-dispatch-failed
2854
2875
  */
2855
- function mapHeadlessExitCode(processExitCode, output) {
2856
- if (processExitCode !== 0 && processExitCode !== null) return 3;
2857
- const raw = output || "";
2858
- const lower = raw.toLowerCase();
2859
- // Command dispatch failure — `claude -p` prints "Unknown command: /X"
2860
- // to stdout and still exits 0. Without this sentinel, a mistyped or
2861
- // namespace-prefixed slash command silently reports success. (M36 Phase 0.)
2862
- if (/^unknown command:/im.test(raw)) return 5;
2863
- if (lower.includes("context budget exceeded") || lower.includes("context window exceeded") ||
2864
- lower.includes("budget exceeded") || lower.includes("token limit")) return 2;
2865
- if (lower.includes("blocked") && (lower.includes("needs human") || lower.includes("need human") ||
2866
- lower.includes("human input") || lower.includes("human approval"))) return 4;
2867
- if (lower.includes("verification failed") || lower.includes("verify failed") ||
2868
- lower.includes("quality gate failed") || lower.includes("tests failed")) return 1;
2869
- return 0;
2870
- }
2876
+ // mapHeadlessExitCode now lives in ./headless-exit-codes.cjs — see re-export
2877
+ // near the top of this file. Kept out-of-line here so non-entry modules
2878
+ // (e.g. bin/gsd-t-unattended.cjs) can require the shared helper without
2879
+ // pulling in the full CLI at bin/gsd-t.js.
2871
2880
 
2872
2881
  /**
2873
2882
  * Generate a headless log file path.
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Shared helper: map a `claude -p` process exit code + output to the
3
+ * GSD-T headless exit-code contract.
4
+ *
5
+ * Lives in its own file so non-entry modules (e.g. gsd-t-unattended.cjs)
6
+ * can require it without pulling in the full CLI at bin/gsd-t.js. That
7
+ * decoupling lets PROJECT_BIN_TOOLS ship the supervisor without also
8
+ * vendoring the CLI itself — projects resolve `gsd-t` from the global
9
+ * install, not from a project-local copy.
10
+ *
11
+ * Exit codes (contract):
12
+ * 0 — success
13
+ * 1 — verification/test failure
14
+ * 2 — context budget exceeded
15
+ * 3 — non-zero process exit (other)
16
+ * 4 — blocked / needs human
17
+ * 5 — unknown slash command (claude -p exited 0 but rejected the prompt)
18
+ */
19
+
20
+ "use strict";
21
+
22
+ function mapHeadlessExitCode(processExitCode, output) {
23
+ if (processExitCode !== 0 && processExitCode !== null) return 3;
24
+ const raw = output || "";
25
+ const lower = raw.toLowerCase();
26
+ if (/^unknown command:/im.test(raw)) return 5;
27
+ if (
28
+ lower.includes("context budget exceeded") ||
29
+ lower.includes("context window exceeded") ||
30
+ lower.includes("budget exceeded") ||
31
+ lower.includes("token limit")
32
+ ) return 2;
33
+ if (
34
+ lower.includes("blocked") &&
35
+ (lower.includes("needs human") ||
36
+ lower.includes("need human") ||
37
+ lower.includes("human input") ||
38
+ lower.includes("human approval"))
39
+ ) return 4;
40
+ if (
41
+ lower.includes("verification failed") ||
42
+ lower.includes("verify failed") ||
43
+ lower.includes("quality gate failed") ||
44
+ lower.includes("tests failed")
45
+ ) return 1;
46
+ return 0;
47
+ }
48
+
49
+ module.exports = { mapHeadlessExitCode };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "3.13.13",
3
+ "version": "3.13.14",
4
4
  "description": "GSD-T: Contract-Driven Development for Claude Code — 54 slash commands with headless-by-default workflow spawning, unattended supervisor relay with event stream, graph-powered code analysis, real-time agent dashboard, 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",