@tekyzinc/gsd-t 3.12.10 → 3.12.13
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 +110 -26
- package/README.md +76 -76
- package/bin/design-orchestrator.js +1 -1
- package/bin/gsd-t-unattended.cjs +56 -2
- package/bin/gsd-t-unattended.js +1 -1
- package/bin/gsd-t.js +198 -17
- package/bin/headless-auto-spawn.cjs +58 -2
- package/commands/gsd-t-backlog-promote.md +6 -6
- package/commands/gsd-t-complete-milestone.md +7 -7
- package/commands/gsd-t-design-audit.md +3 -3
- package/commands/gsd-t-design-build.md +1 -1
- package/commands/gsd-t-design-decompose.md +4 -4
- package/commands/gsd-t-execute.md +1 -1
- package/commands/gsd-t-feature.md +3 -3
- package/commands/gsd-t-gap-analysis.md +3 -3
- package/commands/gsd-t-health.md +3 -3
- package/commands/gsd-t-help.md +10 -10
- package/commands/gsd-t-impact.md +3 -3
- package/commands/gsd-t-init-scan-setup.md +5 -5
- package/commands/gsd-t-init.md +4 -4
- package/commands/gsd-t-log.md +1 -1
- package/commands/gsd-t-milestone.md +2 -2
- package/commands/gsd-t-pause.md +2 -2
- package/commands/gsd-t-prd.md +2 -2
- package/commands/gsd-t-project.md +1 -1
- package/commands/gsd-t-resume.md +4 -4
- package/commands/gsd-t-scan.md +3 -3
- package/commands/gsd-t-setup.md +2 -2
- package/commands/gsd-t-test-sync.md +1 -1
- package/commands/gsd-t-unattended-watch.md +5 -5
- package/commands/gsd-t-unattended.md +9 -9
- package/commands/gsd-t-wave.md +4 -4
- package/commands/gsd.md +17 -17
- package/docs/GSD-T-README.md +68 -68
- package/docs/architecture.md +8 -8
- package/docs/context-budget-recovery-plan.md +2 -2
- package/docs/infrastructure.md +7 -7
- package/docs/methodology.md +1 -1
- package/docs/neo4j-setup.md +2 -2
- package/docs/prd-gsd2-hybrid.md +1 -1
- package/docs/prd-harness-evolution.md +1 -1
- package/docs/requirements.md +2 -2
- package/docs/unattended-config.md +1 -1
- package/docs/unattended-windows-caveats.md +1 -1
- package/docs/workflows.md +1 -1
- package/package.json +1 -1
- package/scripts/context-meter/threshold.test.js +2 -2
- package/scripts/gsd-t-auto-route.js +1 -1
- package/scripts/gsd-t-context-meter.e2e.test.js +1 -1
- package/scripts/gsd-t-context-meter.test.js +1 -1
- package/scripts/gsd-t-event-writer.js +8 -2
- package/scripts/gsd-t-update-check.js +1 -1
- package/templates/CLAUDE-global.md +18 -163
- package/templates/stacks/_markdown.md +32 -0
- package/templates/stacks/design-to-code.md +1 -1
package/bin/gsd-t-unattended.cjs
CHANGED
|
@@ -498,7 +498,7 @@ function doUnattended(argv, deps) {
|
|
|
498
498
|
console.error(
|
|
499
499
|
"[gsd-t-unattended] --watch is incompatible with unattended.\n" +
|
|
500
500
|
"Unattended supervisor is detached by definition.\n" +
|
|
501
|
-
"Run /
|
|
501
|
+
"Run /gsd-t-unattended-watch from your interactive session to see live activity.",
|
|
502
502
|
);
|
|
503
503
|
return {
|
|
504
504
|
ok: false,
|
|
@@ -939,6 +939,17 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
939
939
|
// Append the full worker output to run.log (never truncate).
|
|
940
940
|
_appendRunLog(dir, state.iter, workerEnd, exitCode, stdout, stderr);
|
|
941
941
|
|
|
942
|
+
// Append to token-log.md (Fix 1, v3.12.12) — supervisor workers write rows
|
|
943
|
+
// so the log captures headless/unattended activity, not just interactive spawns.
|
|
944
|
+
_appendTokenLog(projectDir, {
|
|
945
|
+
dtStart: workerStart.toISOString().slice(0, 16).replace("T", " "),
|
|
946
|
+
dtEnd: workerEnd.toISOString().slice(0, 16).replace("T", " "),
|
|
947
|
+
command: "gsd-t-resume",
|
|
948
|
+
durationS: Math.round(elapsedMs / 1000),
|
|
949
|
+
exitCode,
|
|
950
|
+
iter: state.iter,
|
|
951
|
+
});
|
|
952
|
+
|
|
942
953
|
// Post-spawn state update
|
|
943
954
|
state.lastExit = exitCode;
|
|
944
955
|
state.lastWorkerFinishedAt = workerEnd.toISOString();
|
|
@@ -1060,6 +1071,41 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
1060
1071
|
return state;
|
|
1061
1072
|
}
|
|
1062
1073
|
|
|
1074
|
+
// ── _appendTokenLog (Fix 1, v3.12.12) ───────────────────────────────────────
|
|
1075
|
+
|
|
1076
|
+
const _TOKEN_LOG_HEADER =
|
|
1077
|
+
"| Datetime-start | Datetime-end | Command | Step | Model | Duration(s) | Notes | Domain | Task | Ctx% |\n" +
|
|
1078
|
+
"|---|---|---|---|---|---|---|---|---|---|\n";
|
|
1079
|
+
|
|
1080
|
+
/**
|
|
1081
|
+
* Append one row to {projectDir}/.gsd-t/token-log.md for a supervisor worker
|
|
1082
|
+
* iteration. Matches the schema used by interactive command-file observability.
|
|
1083
|
+
*/
|
|
1084
|
+
function _appendTokenLog(projectDir, entry) {
|
|
1085
|
+
try {
|
|
1086
|
+
const logPath = path.join(projectDir, ".gsd-t", "token-log.md");
|
|
1087
|
+
const note = entry.exitCode === 0
|
|
1088
|
+
? `supervisor iter=${entry.iter}: ok`
|
|
1089
|
+
: `supervisor iter=${entry.iter}: exit ${entry.exitCode}`;
|
|
1090
|
+
const row =
|
|
1091
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | supervisor-iter-${entry.iter} | unknown | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
1092
|
+
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
1093
|
+
if (!fs.existsSync(gsdtDir)) fs.mkdirSync(gsdtDir, { recursive: true });
|
|
1094
|
+
if (!fs.existsSync(logPath)) {
|
|
1095
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${_TOKEN_LOG_HEADER}${row}`);
|
|
1096
|
+
} else {
|
|
1097
|
+
const existing = fs.readFileSync(logPath, "utf8");
|
|
1098
|
+
if (!existing.includes("| Datetime-start |")) {
|
|
1099
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${_TOKEN_LOG_HEADER}${existing}${row}`);
|
|
1100
|
+
} else {
|
|
1101
|
+
fs.appendFileSync(logPath, row);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
} catch (_) {
|
|
1105
|
+
/* best-effort — never halt the supervisor loop */
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1063
1109
|
// ── _spawnWorker ────────────────────────────────────────────────────────────
|
|
1064
1110
|
|
|
1065
1111
|
/**
|
|
@@ -1073,7 +1119,15 @@ function runMainLoop(state, dir, opts, deps, ctx) {
|
|
|
1073
1119
|
*/
|
|
1074
1120
|
function _spawnWorker(state, opts) {
|
|
1075
1121
|
const bin = (state && state.claudeBin) || resolveClaudePath();
|
|
1076
|
-
|
|
1122
|
+
// Inject command/phase so event-stream tool_call entries are tagged in worker
|
|
1123
|
+
// contexts (Fix 2, v3.12.12). Supervisor always runs gsd-t-resume workers;
|
|
1124
|
+
// phase is inferred from state when available.
|
|
1125
|
+
const workerEnv = {
|
|
1126
|
+
...process.env,
|
|
1127
|
+
GSD_T_UNATTENDED_WORKER: "1",
|
|
1128
|
+
GSD_T_COMMAND: "gsd-t-resume",
|
|
1129
|
+
GSD_T_PHASE: (state && state.phase) || "execute",
|
|
1130
|
+
};
|
|
1077
1131
|
const res = platformSpawnWorker(opts.cwd, opts.timeout, {
|
|
1078
1132
|
bin,
|
|
1079
1133
|
args: [
|
package/bin/gsd-t-unattended.js
CHANGED
|
@@ -499,7 +499,7 @@ function doUnattended(argv, deps) {
|
|
|
499
499
|
console.error(
|
|
500
500
|
"[gsd-t-unattended] --watch is incompatible with unattended.\n" +
|
|
501
501
|
"Unattended supervisor is detached by definition.\n" +
|
|
502
|
-
"Run /
|
|
502
|
+
"Run /gsd-t-unattended-watch from your interactive session to see live activity.",
|
|
503
503
|
);
|
|
504
504
|
return {
|
|
505
505
|
ok: false,
|
package/bin/gsd-t.js
CHANGED
|
@@ -388,8 +388,14 @@ const CONTEXT_METER_GITIGNORE_ENTRIES = [
|
|
|
388
388
|
".gsd-t/context-meter.log",
|
|
389
389
|
];
|
|
390
390
|
const CONTEXT_METER_HOOK_MARKER = "gsd-t-context-meter";
|
|
391
|
+
// Canonical global hook — runs the script from the globally-installed npm package.
|
|
392
|
+
// Guarded so it silently exits 0 if the package is not present (non-GSD-T projects).
|
|
391
393
|
const CONTEXT_METER_HOOK_COMMAND =
|
|
392
|
-
'node "$
|
|
394
|
+
'bash -c \'[ -f "$(npm root -g)/@tekyzinc/gsd-t/scripts/gsd-t-context-meter.js" ] && node "$(npm root -g)/@tekyzinc/gsd-t/scripts/gsd-t-context-meter.js" || true\'';
|
|
395
|
+
// Legacy command patterns that must be migrated on install/update/init.
|
|
396
|
+
const CONTEXT_METER_STALE_PATTERNS = [
|
|
397
|
+
/node\s+"?\$CLAUDE_PROJECT_DIR\/scripts\/gsd-t-context-meter\.js"?/,
|
|
398
|
+
];
|
|
393
399
|
|
|
394
400
|
// Append entries to {projectDir}/.gitignore. Each entry added only if absent.
|
|
395
401
|
// Idempotent. Returns true if any entries were added, false otherwise.
|
|
@@ -544,7 +550,9 @@ function installContextMeter(projectDir) {
|
|
|
544
550
|
|
|
545
551
|
// Register the Context Meter PostToolUse hook in ~/.claude/settings.json.
|
|
546
552
|
// Idempotent — if an existing hook references CONTEXT_METER_HOOK_MARKER the
|
|
547
|
-
// command string is refreshed in-place
|
|
553
|
+
// command string is refreshed/migrated in-place to the canonical form.
|
|
554
|
+
// Stale entries matching CONTEXT_METER_STALE_PATTERNS are migrated on the spot.
|
|
555
|
+
// All other settings/hooks are preserved.
|
|
548
556
|
// Returns { installed: bool, action: "added"|"updated"|"noop" }.
|
|
549
557
|
function configureContextMeterHooks(settingsPath) {
|
|
550
558
|
const targetPath = settingsPath || SETTINGS_JSON;
|
|
@@ -570,13 +578,17 @@ function configureContextMeterHooks(settingsPath) {
|
|
|
570
578
|
for (const entry of settings.hooks.PostToolUse) {
|
|
571
579
|
if (!entry || !Array.isArray(entry.hooks)) continue;
|
|
572
580
|
for (const h of entry.hooks) {
|
|
573
|
-
if (h
|
|
581
|
+
if (!h || typeof h.command !== "string") continue;
|
|
582
|
+
const isCurrentCanonical = h.command === cmd;
|
|
583
|
+
const isMarkerMatch = h.command.includes(CONTEXT_METER_HOOK_MARKER);
|
|
584
|
+
const isStaleMatch = !isCurrentCanonical &&
|
|
585
|
+
CONTEXT_METER_STALE_PATTERNS.some((re) => re.test(h.command));
|
|
586
|
+
|
|
587
|
+
if (isCurrentCanonical || isMarkerMatch || isStaleMatch) {
|
|
574
588
|
found = true;
|
|
575
|
-
if (
|
|
589
|
+
if (!isCurrentCanonical) {
|
|
576
590
|
h.command = cmd;
|
|
577
591
|
action = "updated";
|
|
578
|
-
} else if (action === "noop") {
|
|
579
|
-
action = "noop";
|
|
580
592
|
}
|
|
581
593
|
}
|
|
582
594
|
}
|
|
@@ -607,6 +619,46 @@ function configureContextMeterHooks(settingsPath) {
|
|
|
607
619
|
return { installed: true, action };
|
|
608
620
|
}
|
|
609
621
|
|
|
622
|
+
// Remove any context meter PostToolUse hooks from settings.json.
|
|
623
|
+
// Used during uninstall. Leaves all other hooks intact.
|
|
624
|
+
function removeContextMeterHook(settingsPath) {
|
|
625
|
+
const targetPath = settingsPath || SETTINGS_JSON;
|
|
626
|
+
if (!fs.existsSync(targetPath)) return false;
|
|
627
|
+
let settings;
|
|
628
|
+
try {
|
|
629
|
+
settings = JSON.parse(fs.readFileSync(targetPath, "utf8"));
|
|
630
|
+
if (!settings || typeof settings !== "object") return false;
|
|
631
|
+
} catch {
|
|
632
|
+
warn("settings.json has invalid JSON — cannot remove context meter hook");
|
|
633
|
+
return false;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (!settings.hooks || !Array.isArray(settings.hooks.PostToolUse)) return false;
|
|
637
|
+
|
|
638
|
+
const before = settings.hooks.PostToolUse.length;
|
|
639
|
+
settings.hooks.PostToolUse = settings.hooks.PostToolUse.filter((entry) => {
|
|
640
|
+
if (!entry || !Array.isArray(entry.hooks)) return true;
|
|
641
|
+
// Keep the entry only if NONE of its hooks reference the context meter
|
|
642
|
+
return !entry.hooks.some(
|
|
643
|
+
(h) => h && typeof h.command === "string" && h.command.includes(CONTEXT_METER_HOOK_MARKER)
|
|
644
|
+
);
|
|
645
|
+
});
|
|
646
|
+
const removed = before - settings.hooks.PostToolUse.length;
|
|
647
|
+
if (removed === 0) return false;
|
|
648
|
+
|
|
649
|
+
if (isSymlink(targetPath)) {
|
|
650
|
+
warn("Skipping settings.json write — target is a symlink");
|
|
651
|
+
return false;
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
fs.writeFileSync(targetPath, JSON.stringify(settings, null, 2));
|
|
655
|
+
return true;
|
|
656
|
+
} catch (e) {
|
|
657
|
+
warn(`Failed to write settings.json: ${e.message}`);
|
|
658
|
+
return false;
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
|
|
610
662
|
// Interactive prompt for the Anthropic API key env var.
|
|
611
663
|
// Skips if not a TTY or if the env var is already set.
|
|
612
664
|
// Never writes the key anywhere — just prints the export command for the user
|
|
@@ -1242,9 +1294,9 @@ function showInstallSummary(gsdtCount, utilCount) {
|
|
|
1242
1294
|
log(`${BOLD}Quick Start:${RESET}`);
|
|
1243
1295
|
log(` ${DIM}$${RESET} cd your-project`);
|
|
1244
1296
|
log(` ${DIM}$${RESET} claude`);
|
|
1245
|
-
log(` ${DIM}>${RESET} /
|
|
1246
|
-
log(` ${DIM}>${RESET} /
|
|
1247
|
-
log(` ${DIM}>${RESET} /
|
|
1297
|
+
log(` ${DIM}>${RESET} /gsd-t-init my-project`);
|
|
1298
|
+
log(` ${DIM}>${RESET} /gsd-t-milestone "First Feature"`);
|
|
1299
|
+
log(` ${DIM}>${RESET} /gsd-t-wave`);
|
|
1248
1300
|
log("");
|
|
1249
1301
|
log(`${BOLD}Other commands:${RESET}`);
|
|
1250
1302
|
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t status ${DIM}— check installation${RESET}`);
|
|
@@ -1292,7 +1344,7 @@ function initClaudeMd(projectDir, projectName, today) {
|
|
|
1292
1344
|
info("CLAUDE.md already contains GSD-T section — skipping");
|
|
1293
1345
|
} else {
|
|
1294
1346
|
warn("CLAUDE.md exists but doesn't reference GSD-T");
|
|
1295
|
-
info("Run /
|
|
1347
|
+
info("Run /gsd-t-init inside Claude Code to add GSD-T section");
|
|
1296
1348
|
}
|
|
1297
1349
|
} else { throw e; }
|
|
1298
1350
|
}
|
|
@@ -1461,8 +1513,8 @@ function showInitTree(projectDir) {
|
|
|
1461
1513
|
log(`${BOLD}Next steps:${RESET}`);
|
|
1462
1514
|
log(` 1. Edit CLAUDE.md — add project overview and tech stack`);
|
|
1463
1515
|
log(` 2. Start Claude Code: ${DIM}claude${RESET}`);
|
|
1464
|
-
log(` 3. Run: ${DIM}/
|
|
1465
|
-
log(` Or: ${DIM}/
|
|
1516
|
+
log(` 3. Run: ${DIM}/gsd-t-populate${RESET} ${DIM}(if existing codebase)${RESET}`);
|
|
1517
|
+
log(` Or: ${DIM}/gsd-t-project${RESET} ${DIM}(if new project)${RESET}`);
|
|
1466
1518
|
log("");
|
|
1467
1519
|
}
|
|
1468
1520
|
|
|
@@ -1635,7 +1687,7 @@ function showStatusProject() {
|
|
|
1635
1687
|
}
|
|
1636
1688
|
} else if (hasClaudeMd) {
|
|
1637
1689
|
info("CLAUDE.md found but no .gsd-t/ directory");
|
|
1638
|
-
info("Run /
|
|
1690
|
+
info("Run /gsd-t-init inside Claude Code to set up");
|
|
1639
1691
|
} else {
|
|
1640
1692
|
info("Not in a GSD-T project directory");
|
|
1641
1693
|
info(`Run 'npx @tekyzinc/gsd-t init' to set up this directory`);
|
|
@@ -1649,6 +1701,11 @@ function doUninstall() {
|
|
|
1649
1701
|
removeInstalledCommands();
|
|
1650
1702
|
removeVersionFile();
|
|
1651
1703
|
|
|
1704
|
+
// Remove context meter PostToolUse hook from settings.json
|
|
1705
|
+
if (removeContextMeterHook(SETTINGS_JSON)) {
|
|
1706
|
+
success("Context meter PostToolUse hook removed from settings.json");
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1652
1709
|
warn("~/.claude/CLAUDE.md was NOT removed (may contain your customizations)");
|
|
1653
1710
|
info("Remove manually if desired: delete the GSD-T section from ~/.claude/CLAUDE.md");
|
|
1654
1711
|
info("Project files (.gsd-t/, docs/, CLAUDE.md) were NOT removed");
|
|
@@ -1888,6 +1945,19 @@ function exportUniversalRulesForNpm() {
|
|
|
1888
1945
|
}
|
|
1889
1946
|
|
|
1890
1947
|
async function doUpdateAll() {
|
|
1948
|
+
// Step 1: Upgrade the globally-installed npm package FIRST. Without this,
|
|
1949
|
+
// `update-all` would only propagate command files — but the global `gsd-t`
|
|
1950
|
+
// binary itself could stay pinned to an older version (e.g., user stuck
|
|
1951
|
+
// on v3.11.11 while npm registry has v3.12.12). See CHANGELOG v3.12.13.
|
|
1952
|
+
// Guard with an env flag to prevent re-exec loops.
|
|
1953
|
+
if (!process.env.GSDT_POST_UPGRADE) {
|
|
1954
|
+
const upgraded = await upgradeGlobalBinary();
|
|
1955
|
+
if (upgraded.reexec) {
|
|
1956
|
+
reexecUpdateAll();
|
|
1957
|
+
return;
|
|
1958
|
+
}
|
|
1959
|
+
}
|
|
1960
|
+
|
|
1891
1961
|
await updateGlobalCommands();
|
|
1892
1962
|
heading("Updating registered projects...");
|
|
1893
1963
|
log("");
|
|
@@ -1912,6 +1982,65 @@ async function doUpdateAll() {
|
|
|
1912
1982
|
showUpdateAllSummary(projects.length, counts, playwrightMissing, swaggerMissing, syncCount);
|
|
1913
1983
|
}
|
|
1914
1984
|
|
|
1985
|
+
// Upgrade the globally-installed @tekyzinc/gsd-t to @latest. Returns
|
|
1986
|
+
// { upgraded: bool, reexec: bool, error?: string }.
|
|
1987
|
+
// - reexec=true when the on-disk version after `npm install -g` is newer than
|
|
1988
|
+
// the currently-running PKG_VERSION, meaning we need to hand off to the
|
|
1989
|
+
// freshly-installed binary so the new code drives propagation.
|
|
1990
|
+
// - reexec=false when already at latest, or when the install failed (we
|
|
1991
|
+
// continue with the current binary and still propagate command files).
|
|
1992
|
+
async function upgradeGlobalBinary() {
|
|
1993
|
+
heading("Upgrading global @tekyzinc/gsd-t to latest...");
|
|
1994
|
+
try {
|
|
1995
|
+
execFileSync("npm", ["install", "-g", "@tekyzinc/gsd-t@latest"], {
|
|
1996
|
+
stdio: "inherit",
|
|
1997
|
+
env: process.env,
|
|
1998
|
+
});
|
|
1999
|
+
} catch (e) {
|
|
2000
|
+
warn(`Global npm install failed: ${e.message || e}`);
|
|
2001
|
+
info("Continuing with current binary — command files will still be propagated.");
|
|
2002
|
+
return { upgraded: false, reexec: false, error: String(e.message || e) };
|
|
2003
|
+
}
|
|
2004
|
+
|
|
2005
|
+
// Read the freshly-installed global package's version. If it's newer than
|
|
2006
|
+
// the currently-running process, signal a re-exec.
|
|
2007
|
+
let newVersion = null;
|
|
2008
|
+
try {
|
|
2009
|
+
const prefix = execFileSync("npm", ["prefix", "-g"], { encoding: "utf8" }).trim();
|
|
2010
|
+
const globalPkgJson = path.join(prefix, "lib", "node_modules", "@tekyzinc", "gsd-t", "package.json");
|
|
2011
|
+
if (fs.existsSync(globalPkgJson)) {
|
|
2012
|
+
newVersion = JSON.parse(fs.readFileSync(globalPkgJson, "utf8")).version;
|
|
2013
|
+
}
|
|
2014
|
+
} catch {
|
|
2015
|
+
// Best-effort; fall through.
|
|
2016
|
+
}
|
|
2017
|
+
|
|
2018
|
+
if (newVersion && newVersion !== PKG_VERSION) {
|
|
2019
|
+
success(`Global binary upgraded: v${PKG_VERSION} → v${newVersion}`);
|
|
2020
|
+
info("Handing off to the newly-installed binary for propagation...");
|
|
2021
|
+
return { upgraded: true, reexec: true };
|
|
2022
|
+
}
|
|
2023
|
+
success(`Global binary already at latest (v${PKG_VERSION})`);
|
|
2024
|
+
return { upgraded: true, reexec: false };
|
|
2025
|
+
}
|
|
2026
|
+
|
|
2027
|
+
// Hand execution to the newly-installed global `gsd-t update-all`. Sets the
|
|
2028
|
+
// GSDT_POST_UPGRADE env flag so the child does not recurse into another
|
|
2029
|
+
// upgrade attempt.
|
|
2030
|
+
function reexecUpdateAll() {
|
|
2031
|
+
const env = Object.assign({}, process.env, { GSDT_POST_UPGRADE: "1" });
|
|
2032
|
+
try {
|
|
2033
|
+
execFileSync("gsd-t", ["update-all"], { stdio: "inherit", env });
|
|
2034
|
+
} catch (e) {
|
|
2035
|
+
// Surface the child's exit code; execFileSync throws with .status on
|
|
2036
|
+
// non-zero exit. Fall through to re-throw so the caller exits cleanly.
|
|
2037
|
+
if (e && typeof e.status === "number") {
|
|
2038
|
+
process.exit(e.status);
|
|
2039
|
+
}
|
|
2040
|
+
throw e;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
|
|
1915
2044
|
async function updateGlobalCommands() {
|
|
1916
2045
|
if (getInstalledVersion() !== PKG_VERSION) {
|
|
1917
2046
|
await doInstall({ update: true });
|
|
@@ -2600,6 +2729,39 @@ function doGraph(args) {
|
|
|
2600
2729
|
}
|
|
2601
2730
|
}
|
|
2602
2731
|
|
|
2732
|
+
// ─── Token-Log Writer (Fix 1, v3.12.12) ─────────────────────────────────────
|
|
2733
|
+
|
|
2734
|
+
const _TL_HEADER =
|
|
2735
|
+
"| Datetime-start | Datetime-end | Command | Step | Model | Duration(s) | Notes | Domain | Task | Ctx% |\n" +
|
|
2736
|
+
"|---|---|---|---|---|---|---|---|---|---|\n";
|
|
2737
|
+
|
|
2738
|
+
/**
|
|
2739
|
+
* Append one row to {projectDir}/.gsd-t/token-log.md for a headless exec
|
|
2740
|
+
* invocation. Best-effort — never throws.
|
|
2741
|
+
*/
|
|
2742
|
+
function appendHeadlessTokenLog(projectDir, entry) {
|
|
2743
|
+
try {
|
|
2744
|
+
const logPath = path.join(projectDir, ".gsd-t", "token-log.md");
|
|
2745
|
+
const note = entry.exitCode === 0 ? "headless exec: ok" : `headless exec: exit ${entry.exitCode}`;
|
|
2746
|
+
const row =
|
|
2747
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | headless | unknown | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
2748
|
+
const gsdtDir = path.join(projectDir, ".gsd-t");
|
|
2749
|
+
if (!fs.existsSync(gsdtDir)) fs.mkdirSync(gsdtDir, { recursive: true });
|
|
2750
|
+
if (!fs.existsSync(logPath)) {
|
|
2751
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${_TL_HEADER}${row}`);
|
|
2752
|
+
} else {
|
|
2753
|
+
const existing = fs.readFileSync(logPath, "utf8");
|
|
2754
|
+
if (!existing.includes("| Datetime-start |")) {
|
|
2755
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${_TL_HEADER}${existing}${row}`);
|
|
2756
|
+
} else {
|
|
2757
|
+
fs.appendFileSync(logPath, row);
|
|
2758
|
+
}
|
|
2759
|
+
}
|
|
2760
|
+
} catch (_) {
|
|
2761
|
+
/* best-effort */
|
|
2762
|
+
}
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2603
2765
|
// ─── Headless Mode ────────────────────────────────────────────────────────────
|
|
2604
2766
|
|
|
2605
2767
|
/**
|
|
@@ -2628,7 +2790,7 @@ function parseHeadlessFlags(args) {
|
|
|
2628
2790
|
* Build the claude -p invocation string for a GSD-T command.
|
|
2629
2791
|
*
|
|
2630
2792
|
* Non-interactive `claude -p` mode requires the bare `/gsd-t-X` form — the
|
|
2631
|
-
* `/
|
|
2793
|
+
* `/gsd-t-X` namespace prefix is rejected as "Unknown command" even
|
|
2632
2794
|
* though interactive mode accepts both. Verified by M36 Phase 0 Spike A
|
|
2633
2795
|
* (2026-04-15). See .gsd-t/M36-spike-findings.md.
|
|
2634
2796
|
*/
|
|
@@ -2717,12 +2879,20 @@ function doHeadlessExec(command, cmdArgs, flags) {
|
|
|
2717
2879
|
let output = "";
|
|
2718
2880
|
let processExitCode = 0;
|
|
2719
2881
|
|
|
2882
|
+
// Inject command/phase env vars so worker event-stream entries are tagged
|
|
2883
|
+
// (Fix 2, v3.12.12).
|
|
2884
|
+
const workerEnv = Object.assign({}, process.env, {
|
|
2885
|
+
GSD_T_COMMAND: `gsd-t-${command}`,
|
|
2886
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || "execute",
|
|
2887
|
+
});
|
|
2888
|
+
|
|
2720
2889
|
try {
|
|
2721
2890
|
const result = execFileSync("claude", ["-p", "--dangerously-skip-permissions", prompt], {
|
|
2722
2891
|
encoding: "utf8",
|
|
2723
2892
|
timeout: timeoutMs,
|
|
2724
2893
|
stdio: ["pipe", "pipe", "pipe"],
|
|
2725
|
-
cwd: process.cwd()
|
|
2894
|
+
cwd: process.cwd(),
|
|
2895
|
+
env: workerEnv,
|
|
2726
2896
|
});
|
|
2727
2897
|
output = result;
|
|
2728
2898
|
} catch (e) {
|
|
@@ -2738,6 +2908,16 @@ function doHeadlessExec(command, cmdArgs, flags) {
|
|
|
2738
2908
|
const gsdtExitCode = mapHeadlessExitCode(processExitCode, output);
|
|
2739
2909
|
const duration = Date.now() - startTime;
|
|
2740
2910
|
|
|
2911
|
+
// Append to token-log.md (Fix 1, v3.12.12) — headless exec writes a row so
|
|
2912
|
+
// `gsd-t headless <command>` spawns are visible in the log.
|
|
2913
|
+
appendHeadlessTokenLog(process.cwd(), {
|
|
2914
|
+
dtStart: new Date(startTime).toISOString().slice(0, 16).replace("T", " "),
|
|
2915
|
+
dtEnd: new Date(startTime + duration).toISOString().slice(0, 16).replace("T", " "),
|
|
2916
|
+
command: `gsd-t-${command}`,
|
|
2917
|
+
durationS: Math.round(duration / 1000),
|
|
2918
|
+
exitCode: gsdtExitCode,
|
|
2919
|
+
});
|
|
2920
|
+
|
|
2741
2921
|
// Write log file if requested
|
|
2742
2922
|
if (logMode && logFile) {
|
|
2743
2923
|
try {
|
|
@@ -3270,8 +3450,8 @@ function showHelp() {
|
|
|
3270
3450
|
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t init my-saas-app`);
|
|
3271
3451
|
log(` ${DIM}$${RESET} npx @tekyzinc/gsd-t update\n`);
|
|
3272
3452
|
log(`${BOLD}After installing, use in Claude Code:${RESET}`);
|
|
3273
|
-
log(` ${DIM}>${RESET} /
|
|
3274
|
-
log(` ${DIM}>${RESET} /
|
|
3453
|
+
log(` ${DIM}>${RESET} /gsd-t-project "Build a task management app"`);
|
|
3454
|
+
log(` ${DIM}>${RESET} /gsd-t-wave\n`);
|
|
3275
3455
|
log(`${DIM}Docs: https://github.com/Tekyz-Inc/get-stuff-done-teams${RESET}\n`);
|
|
3276
3456
|
}
|
|
3277
3457
|
|
|
@@ -3360,6 +3540,7 @@ module.exports = {
|
|
|
3360
3540
|
ensureGitignoreEntries,
|
|
3361
3541
|
installContextMeter,
|
|
3362
3542
|
configureContextMeterHooks,
|
|
3543
|
+
removeContextMeterHook,
|
|
3363
3544
|
promptForApiKeyIfMissing,
|
|
3364
3545
|
resolveApiKeyEnvVar,
|
|
3365
3546
|
runTaskCounterRetirementMigration,
|
|
@@ -135,11 +135,18 @@ function autoSpawnHeadless(opts) {
|
|
|
135
135
|
const gsdtCli = path.join(projectDir, "bin", "gsd-t.js");
|
|
136
136
|
const childArgs = [gsdtCli, "headless", stripGsdtPrefix(command), ...args, "--log"];
|
|
137
137
|
|
|
138
|
+
// Inject command/phase into worker env so event-stream entries are tagged
|
|
139
|
+
// (Fix 2, v3.12.12). GSD_T_PHASE defaults to "execute" for primary spawns.
|
|
140
|
+
const workerEnv = Object.assign({}, process.env, {
|
|
141
|
+
GSD_T_COMMAND: command,
|
|
142
|
+
GSD_T_PHASE: process.env.GSD_T_PHASE || "execute",
|
|
143
|
+
});
|
|
144
|
+
|
|
138
145
|
const child = spawn("node", childArgs, {
|
|
139
146
|
cwd: projectDir,
|
|
140
147
|
detached: true,
|
|
141
148
|
stdio: ["ignore", logFd, logFd],
|
|
142
|
-
env:
|
|
149
|
+
env: workerEnv,
|
|
143
150
|
});
|
|
144
151
|
|
|
145
152
|
child.unref();
|
|
@@ -291,6 +298,7 @@ function installCompletionWatcher(opts) {
|
|
|
291
298
|
const POLL_MS = 2000;
|
|
292
299
|
const MAX_WAIT_MS = 60 * 60 * 1000; // 1 hour safety cap
|
|
293
300
|
const startMs = Date.now();
|
|
301
|
+
const dtStart = new Date(startTimestamp).toLocaleString("sv-SE", { hour12: false }).slice(0, 16);
|
|
294
302
|
|
|
295
303
|
const timer = setInterval(() => {
|
|
296
304
|
let alive = false;
|
|
@@ -305,9 +313,20 @@ function installCompletionWatcher(opts) {
|
|
|
305
313
|
// Exit code is unknown from a signal-based probe. Best-effort: read
|
|
306
314
|
// the log's last lines to guess, otherwise default to 0.
|
|
307
315
|
const exitCode = guessExitCodeFromLog(projectDir, id);
|
|
316
|
+
const endTimestamp = new Date().toISOString();
|
|
308
317
|
markSessionCompleted(projectDir, id, {
|
|
309
318
|
exitCode,
|
|
310
|
-
endTimestamp
|
|
319
|
+
endTimestamp,
|
|
320
|
+
});
|
|
321
|
+
// Append token-log row (Fix 1, v3.12.12)
|
|
322
|
+
const dtEnd = new Date(endTimestamp).toLocaleString("sv-SE", { hour12: false }).slice(0, 16);
|
|
323
|
+
const durationS = Math.round((Date.now() - startMs) / 1000);
|
|
324
|
+
appendTokenLog(projectDir, {
|
|
325
|
+
dtStart,
|
|
326
|
+
dtEnd,
|
|
327
|
+
command: extractCommand(id),
|
|
328
|
+
durationS,
|
|
329
|
+
exitCode,
|
|
311
330
|
});
|
|
312
331
|
fireMacNotification({ id, command: extractCommand(id), startTimestamp });
|
|
313
332
|
} else if (Date.now() - startMs > MAX_WAIT_MS) {
|
|
@@ -356,6 +375,43 @@ function fireMacNotification({ id, command }) {
|
|
|
356
375
|
}
|
|
357
376
|
}
|
|
358
377
|
|
|
378
|
+
// ── Token-Log Writer (Fix 1, v3.12.12) ───────────────────────────────────────
|
|
379
|
+
|
|
380
|
+
const TOKEN_LOG_HEADER =
|
|
381
|
+
"| Datetime-start | Datetime-end | Command | Step | Model | Duration(s) | Notes | Domain | Task | Ctx% |\n" +
|
|
382
|
+
"|---|---|---|---|---|---|---|---|---|---|\n";
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Append one row to {projectDir}/.gsd-t/token-log.md matching the schema used
|
|
386
|
+
* by interactive command-file observability blocks.
|
|
387
|
+
*
|
|
388
|
+
* @param {string} projectDir
|
|
389
|
+
* @param {{ dtStart: string, dtEnd: string, command: string, durationS: number, exitCode: number }} entry
|
|
390
|
+
*/
|
|
391
|
+
function appendTokenLog(projectDir, entry) {
|
|
392
|
+
try {
|
|
393
|
+
const logPath = path.join(projectDir, ".gsd-t", "token-log.md");
|
|
394
|
+
const note = entry.exitCode === 0 ? "headless spawn: ok" : `headless spawn: exit ${entry.exitCode}`;
|
|
395
|
+
const row =
|
|
396
|
+
`| ${entry.dtStart} | ${entry.dtEnd} | ${entry.command} | headless | unknown | ${entry.durationS}s | ${note} | - | - | unknown |\n`;
|
|
397
|
+
if (!fs.existsSync(logPath)) {
|
|
398
|
+
// Create with header
|
|
399
|
+
ensureDir(path.dirname(logPath));
|
|
400
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${TOKEN_LOG_HEADER}${row}`);
|
|
401
|
+
} else {
|
|
402
|
+
// Check if header row exists; if not prepend it (migration for files created before this fix)
|
|
403
|
+
const existing = fs.readFileSync(logPath, "utf8");
|
|
404
|
+
if (!existing.includes("| Datetime-start |")) {
|
|
405
|
+
fs.writeFileSync(logPath, `# GSD-T Token Log\n\n${TOKEN_LOG_HEADER}${existing}${row}`);
|
|
406
|
+
} else {
|
|
407
|
+
fs.appendFileSync(logPath, row);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
} catch (_) {
|
|
411
|
+
/* best-effort — never halt the completion watcher */
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
359
415
|
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
360
416
|
|
|
361
417
|
function ensureDir(d) {
|
|
@@ -13,14 +13,14 @@ Parse $ARGUMENTS to extract:
|
|
|
13
13
|
- `<position>` — the entry number to promote
|
|
14
14
|
|
|
15
15
|
If no position is provided, show an error:
|
|
16
|
-
"Usage: `/
|
|
16
|
+
"Usage: `/gsd-t-backlog-promote <position>`"
|
|
17
17
|
|
|
18
18
|
## Step 2: Find and Display Entry
|
|
19
19
|
|
|
20
20
|
Find the entry at the specified position in `.gsd-t/backlog.md`.
|
|
21
21
|
|
|
22
22
|
If the position doesn't exist, show an error:
|
|
23
|
-
"No backlog entry at position {position}. Run `/
|
|
23
|
+
"No backlog entry at position {position}. Run `/gsd-t-backlog-list` to see available entries."
|
|
24
24
|
|
|
25
25
|
Display the entry to the user:
|
|
26
26
|
```
|
|
@@ -88,10 +88,10 @@ After classification is confirmed:
|
|
|
88
88
|
|
|
89
89
|
Based on the classification, present the command for the user to invoke:
|
|
90
90
|
|
|
91
|
-
- **Milestone**: "Run `/
|
|
92
|
-
- **Quick**: "Run `/
|
|
93
|
-
- **Debug**: "Run `/
|
|
94
|
-
- **Feature**: "Run `/
|
|
91
|
+
- **Milestone**: "Run `/gsd-t-milestone {refined description}`"
|
|
92
|
+
- **Quick**: "Run `/gsd-t-quick {refined description}`"
|
|
93
|
+
- **Debug**: "Run `/gsd-t-debug {refined description}`"
|
|
94
|
+
- **Feature**: "Run `/gsd-t-feature {refined description}`"
|
|
95
95
|
|
|
96
96
|
Display the full command with the refined description ready to copy.
|
|
97
97
|
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
You are finalizing a completed milestone. Your job is to archive the milestone documentation, create a git tag, and prepare for the next milestone.
|
|
4
4
|
|
|
5
5
|
This command is:
|
|
6
|
-
- **Auto-invoked** by `/
|
|
7
|
-
- **Auto-invoked** by `/
|
|
6
|
+
- **Auto-invoked** by `/gsd-t-verify` (Step 8) after all quality gates pass — at ALL autonomy levels
|
|
7
|
+
- **Auto-invoked** by `/gsd-t-wave` as part of the VERIFY+COMPLETE phase
|
|
8
8
|
- **Standalone** when user wants to manually close a milestone
|
|
9
9
|
|
|
10
10
|
## Step 1: Verify Completion
|
|
@@ -14,7 +14,7 @@ Read:
|
|
|
14
14
|
2. `.gsd-t/verify-report.md` — confirm all checks passed
|
|
15
15
|
|
|
16
16
|
If status is not VERIFIED:
|
|
17
|
-
"⚠️ Milestone not yet verified. Run `/
|
|
17
|
+
"⚠️ Milestone not yet verified. Run `/gsd-t-verify` first, or use `--force` to complete anyway."
|
|
18
18
|
|
|
19
19
|
If `--force` flag provided, proceed with warning in archive.
|
|
20
20
|
|
|
@@ -76,7 +76,7 @@ Refer to `.gsd-t/contracts/goal-backward-contract.md` for the full verification
|
|
|
76
76
|
{findings table}
|
|
77
77
|
|
|
78
78
|
Options:
|
|
79
|
-
1. Fix the findings and re-run /
|
|
79
|
+
1. Fix the findings and re-run /gsd-t-verify
|
|
80
80
|
2. Override with explicit acknowledgment: re-run this command with --force-goal-backward
|
|
81
81
|
|
|
82
82
|
Proceed with option 1 (recommended) or acknowledge to force completion?
|
|
@@ -493,8 +493,8 @@ Summary:
|
|
|
493
493
|
|
|
494
494
|
Next steps:
|
|
495
495
|
- Push tags: git push origin v{version}
|
|
496
|
-
- Start next milestone: /
|
|
497
|
-
- Or view roadmap: /
|
|
496
|
+
- Start next milestone: /gsd-t-milestone "{next name}"
|
|
497
|
+
- Or view roadmap: /gsd-t-status
|
|
498
498
|
```
|
|
499
499
|
|
|
500
500
|
## Step 13: Update Roadmap (if exists)
|
|
@@ -510,7 +510,7 @@ If `.gsd-t/roadmap.md` exists:
|
|
|
510
510
|
"Cannot complete — verification found issues. Address them first or use `--force`."
|
|
511
511
|
|
|
512
512
|
### If no milestone active:
|
|
513
|
-
"No active milestone to complete. Run `/
|
|
513
|
+
"No active milestone to complete. Run `/gsd-t-status` to see state."
|
|
514
514
|
|
|
515
515
|
### If git operations fail:
|
|
516
516
|
- Still create archive
|
|
@@ -400,7 +400,7 @@ If ANY CRITICAL or HIGH deviations were found, automatically prompt the fix work
|
|
|
400
400
|
The audit report at `.gsd-t/design-audit-{page-name}-{YYYY-MM-DD}.md`
|
|
401
401
|
has the exact Figma values for each deviation.
|
|
402
402
|
|
|
403
|
-
`/
|
|
403
|
+
`/gsd-t-quick fix all CRITICAL and HIGH deviations from .gsd-t/design-audit-{page-name}-{YYYY-MM-DD}.md — use the Figma values in the report as the source of truth`
|
|
404
404
|
|
|
405
405
|
───────────────────────────────────────────────────────────────
|
|
406
406
|
```
|
|
@@ -414,7 +414,7 @@ If ONLY MEDIUM or LOW deviations remain, show:
|
|
|
414
414
|
|
|
415
415
|
**{N} MEDIUM + {N} LOW deviations.** These are minor — fix if you want pixel-perfect.
|
|
416
416
|
|
|
417
|
-
`/
|
|
417
|
+
`/gsd-t-quick fix MEDIUM and LOW deviations from .gsd-t/design-audit-{page-name}-{YYYY-MM-DD}.md`
|
|
418
418
|
|
|
419
419
|
───────────────────────────────────────────────────────────────
|
|
420
420
|
```
|
|
@@ -427,7 +427,7 @@ After fixes are applied, **re-run the audit automatically** to verify. Loop unti
|
|
|
427
427
|
|
|
428
428
|
## Rules
|
|
429
429
|
|
|
430
|
-
- **You write ZERO code during the audit phase (Steps 1-5).** Report only. Code changes happen in Step 6 via `/
|
|
430
|
+
- **You write ZERO code during the audit phase (Steps 1-5).** Report only. Code changes happen in Step 6 via `/gsd-t-quick`.
|
|
431
431
|
- **You do NOT "look close" at anything.** Every property gets an exact value from Figma and an exact value from the build. They match or they don't.
|
|
432
432
|
- **You do NOT skip widgets.** Every widget in the Figma AND every widget in the build gets audited.
|
|
433
433
|
- **You MUST call `get_design_context` per widget node — NOT `get_screenshot`.** `get_design_context` returns structured code, component properties, and design tokens. `get_screenshot` returns only a visual image that you cannot extract exact values from. Using `get_screenshot` for widget extraction defeats the entire purpose of structured comparison — you end up eyeballing instead of measuring. The ONLY acceptable use of `get_screenshot` is for the built page (Step 2) where you need to see what was actually rendered. For Figma source data, ALWAYS use `get_design_context`.
|
|
@@ -40,7 +40,7 @@ Pass any of these as `$ARGUMENTS`:
|
|
|
40
40
|
## Prerequisites
|
|
41
41
|
|
|
42
42
|
- Design contracts must exist in `.gsd-t/contracts/design/` with an `INDEX.md`
|
|
43
|
-
- If no contracts exist, run `/
|
|
43
|
+
- If no contracts exist, run `/gsd-t-design-decompose` first
|
|
44
44
|
|
|
45
45
|
## Why a JS Orchestrator?
|
|
46
46
|
|
|
@@ -471,7 +471,7 @@ This domain owns the following design contracts:
|
|
|
471
471
|
- Pages: (none — pages owned by page-assembly domain)
|
|
472
472
|
```
|
|
473
473
|
|
|
474
|
-
If `.gsd-t/domains/` does NOT exist yet, suggest the user run `/
|
|
474
|
+
If `.gsd-t/domains/` does NOT exist yet, suggest the user run `/gsd-t-partition` next, with a note that design contracts should be partitioned into domains:
|
|
475
475
|
- **design-system domain** owns element contracts
|
|
476
476
|
- **widgets domain** owns widget contracts
|
|
477
477
|
- **pages domain** owns page assembly + routing
|
|
@@ -494,11 +494,11 @@ Display:
|
|
|
494
494
|
|
|
495
495
|
**Design Build** — build UI from contracts with tiered review gates (elements → widgets → pages)
|
|
496
496
|
|
|
497
|
-
`/
|
|
497
|
+
`/gsd-t-design-build`
|
|
498
498
|
|
|
499
499
|
**Also available:**
|
|
500
|
-
- `/
|
|
501
|
-
- `/
|
|
500
|
+
- `/gsd-t-partition` — if you need domain boundaries before building
|
|
501
|
+
- `/gsd-t-plan` — if you need task lists before building
|
|
502
502
|
|
|
503
503
|
───────────────────────────────────────────────────────────────
|
|
504
504
|
```
|