@tekyzinc/gsd-t 3.13.13 → 3.13.15
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 +47 -0
- package/bin/gsd-t-unattended.cjs +1 -1
- package/bin/gsd-t.js +50 -36
- package/bin/headless-exit-codes.cjs +49 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,53 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [3.13.15] - 2026-04-17
|
|
6
|
+
|
|
7
|
+
### Fixed — Self-protection guard now uses package-name identity + narrow `bin/*.cjs` gitignore rule
|
|
8
|
+
|
|
9
|
+
Two bugs in v3.13.14 surfaced when `gsd-t update-all` ran against GSD-T's own source repo from the globally-installed CLI:
|
|
10
|
+
|
|
11
|
+
**Bug 1 — Self-protection guard bypassed**: The v3.13.14 guard compared `realpathSync(projectBinDir)` against `realpathSync(PKG_ROOT/bin)`. When `update-all` runs from the globally-installed CLI (`/usr/local/lib/node_modules/@tekyzinc/gsd-t`), `PKG_ROOT` points there — NOT to the local GSD-T source at `/Users/…/projects/GSD-T`. The paths never match in the typical dogfood setup, so the guard returned `false` and the sweep ate the source `bin/gsd-t.js`.
|
|
12
|
+
|
|
13
|
+
**Fix**: identity is now by `package.json` name. The sweep reads `projectDir/package.json` and skips if `name === "@tekyzinc/gsd-t"`. Works regardless of whether `update-all` runs from the local source tree or the global install.
|
|
14
|
+
|
|
15
|
+
**Bug 2 — Gitignore rule overly broad**: `UNATTENDED_GITIGNORE_ENTRIES` included `bin/*.cjs`, which ignored every `.cjs` under `bin/` — contradicting the adjacent comment ("legitimate `.cjs` source files under `bin/` ARE tracked"). With the broad rule active, new source `.cjs` files (e.g., `bin/headless-exit-codes.cjs`) couldn't be committed without `--force`.
|
|
16
|
+
|
|
17
|
+
**Fix**: the gitignore entry narrows to exactly `bin/context-meter-state.cjs` — the single session-state artifact that was the original intent.
|
|
18
|
+
|
|
19
|
+
**Files**:
|
|
20
|
+
- `bin/gsd-t.js` — `isSourcePackage` now reads `package.json.name`; `UNATTENDED_GITIGNORE_ENTRIES` narrowed.
|
|
21
|
+
- `test/bin-gsd-t-resilience.test.js` — self-protection test reshaped: seeds a tmp `package.json` with `name: "@tekyzinc/gsd-t"` + a signature-matching stray, asserts the stray survives the sweep.
|
|
22
|
+
- `.gitignore` — deduped and restored to the narrow form.
|
|
23
|
+
|
|
24
|
+
**Tests**: 1240/1240 pass (unchanged count; existing self-protection test reshaped). E2E: N/A.
|
|
25
|
+
|
|
26
|
+
**Impact**: `gsd-t update-all` is now safe to run with GSD-T itself registered as a project, regardless of where the CLI is installed from. Legitimate `.cjs` source files in `bin/` are no longer blanket-ignored in downstream projects' `.gitignore`. bee-poc's supervisor, which started loading cleanly on v3.13.14, continues to load on v3.13.15 (this release is purely dogfood-protection + gitignore repair; no supervisor-behavior change).
|
|
27
|
+
|
|
28
|
+
## [3.13.14] - 2026-04-17
|
|
29
|
+
|
|
30
|
+
### Fixed — Supervisor no longer requires project-local `bin/gsd-t.js` + sweep self-protection
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
**Fix 1 — extract `mapHeadlessExitCode` to its own file** (`bin/headless-exit-codes.cjs`, new):
|
|
35
|
+
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`).
|
|
36
|
+
|
|
37
|
+
**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.
|
|
38
|
+
|
|
39
|
+
**Fix 3 — sweep self-protection** (`copyBinToolsToProject`):
|
|
40
|
+
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.
|
|
41
|
+
|
|
42
|
+
**Files**:
|
|
43
|
+
- `bin/headless-exit-codes.cjs` — new file, 50 lines, extracted helper with explanatory header.
|
|
44
|
+
- `bin/gsd-t-unattended.cjs` — line 31 now requires the new helper instead of `./gsd-t.js`.
|
|
45
|
+
- `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.
|
|
46
|
+
- `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).
|
|
47
|
+
|
|
48
|
+
**Tests**: 1240/1240 pass (+1 new self-protection test). E2E: N/A.
|
|
49
|
+
|
|
50
|
+
**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.
|
|
51
|
+
|
|
5
52
|
## [3.13.13] - 2026-04-17
|
|
6
53
|
|
|
7
54
|
### Fixed — Stray sweep now matches older-version installer artifacts
|
package/bin/gsd-t-unattended.cjs
CHANGED
|
@@ -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("./
|
|
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,46 @@ function copyBinToolsToProject(projectDir, projectName) {
|
|
|
2154
2161
|
}
|
|
2155
2162
|
}
|
|
2156
2163
|
}
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2164
|
+
// Self-protection: NEVER sweep GSD-T's own source repo. 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
|
+
// Identity is by package.json name, NOT by path — when update-all runs from
|
|
2169
|
+
// the globally-installed package, PKG_ROOT points to the global install and
|
|
2170
|
+
// realpath comparison against the local source always fails.
|
|
2171
|
+
const isSourcePackage = (() => {
|
|
2161
2172
|
try {
|
|
2162
|
-
const
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
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++;
|
|
2175
|
-
}
|
|
2173
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
2174
|
+
if (!fs.existsSync(pkgPath)) return false;
|
|
2175
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
2176
|
+
return pkg && pkg.name === "@tekyzinc/gsd-t";
|
|
2176
2177
|
} catch {
|
|
2177
|
-
|
|
2178
|
+
return false;
|
|
2179
|
+
}
|
|
2180
|
+
})();
|
|
2181
|
+
let cleaned = 0;
|
|
2182
|
+
if (!isSourcePackage) {
|
|
2183
|
+
for (const stray of DEPRECATED_BIN_STRAYS) {
|
|
2184
|
+
const strayPath = path.join(projectBinDir, stray);
|
|
2185
|
+
if (!fs.existsSync(strayPath)) continue;
|
|
2186
|
+
try {
|
|
2187
|
+
const strayContent = fs.readFileSync(strayPath, "utf8");
|
|
2188
|
+
const head = strayContent.slice(0, 400);
|
|
2189
|
+
// Signature-match any version this installer ever shipped. The marker
|
|
2190
|
+
// is unique enough to rule out user-owned files: node shebang + the
|
|
2191
|
+
// verbatim JSDoc header "GSD-T CLI Installer". Matches every historical
|
|
2192
|
+
// version of bin/gsd-t.js, not just the current one, so older strays
|
|
2193
|
+
// (e.g. v3.13.11 left behind on v3.13.12+) are still swept.
|
|
2194
|
+
const isOurs =
|
|
2195
|
+
head.startsWith("#!/usr/bin/env node") &&
|
|
2196
|
+
head.includes("GSD-T CLI Installer");
|
|
2197
|
+
if (isOurs) {
|
|
2198
|
+
fs.unlinkSync(strayPath);
|
|
2199
|
+
cleaned++;
|
|
2200
|
+
}
|
|
2201
|
+
} catch {
|
|
2202
|
+
// leave the file alone on any read error
|
|
2203
|
+
}
|
|
2178
2204
|
}
|
|
2179
2205
|
}
|
|
2180
2206
|
if (cleaned > 0) {
|
|
@@ -2303,7 +2329,7 @@ function ensureUnattendedConfig(projectDir, projectName) {
|
|
|
2303
2329
|
}
|
|
2304
2330
|
|
|
2305
2331
|
const UNATTENDED_GITIGNORE_ENTRIES = [
|
|
2306
|
-
"bin
|
|
2332
|
+
"bin/context-meter-state.cjs",
|
|
2307
2333
|
".gsd-t/.archive-migration-v1",
|
|
2308
2334
|
".gsd-t/.task-counter-retired-v1",
|
|
2309
2335
|
];
|
|
@@ -2852,22 +2878,10 @@ function buildHeadlessCmd(command, cmdArgs) {
|
|
|
2852
2878
|
* Exit codes: 0=success, 1=verify-fail, 2=context-budget-exceeded, 3=error,
|
|
2853
2879
|
* 4=blocked-needs-human, 5=command-dispatch-failed
|
|
2854
2880
|
*/
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
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
|
-
}
|
|
2881
|
+
// mapHeadlessExitCode now lives in ./headless-exit-codes.cjs — see re-export
|
|
2882
|
+
// near the top of this file. Kept out-of-line here so non-entry modules
|
|
2883
|
+
// (e.g. bin/gsd-t-unattended.cjs) can require the shared helper without
|
|
2884
|
+
// pulling in the full CLI at bin/gsd-t.js.
|
|
2871
2885
|
|
|
2872
2886
|
/**
|
|
2873
2887
|
* 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.
|
|
3
|
+
"version": "3.13.15",
|
|
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",
|