@tekyzinc/gsd-t 5.11.32 → 5.12.11
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 +68 -0
- package/README.md +1 -1
- package/bin/gsd-t-pick-worktree.cjs +98 -7
- package/bin/gsd-t.js +1 -1
- package/commands/gsd-t-architect.md +26 -0
- package/package.json +1 -1
- package/templates/CLAUDE-global.md +3 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,74 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to GSD-T are documented here. Updated with each release.
|
|
4
4
|
|
|
5
|
+
## [5.12.11] - 2026-08-22
|
|
6
|
+
|
|
7
|
+
### Fixed — the architect could finish its work and leave nothing, silently
|
|
8
|
+
|
|
9
|
+
Five consecutive architect runs looked like an idle session. The event log
|
|
10
|
+
showed the opposite of a crash: the architect subagents spawned, worked for
|
|
11
|
+
minutes, and logged `subagent_complete` every time — yet no artifact was
|
|
12
|
+
written, and the session then sat silent.
|
|
13
|
+
|
|
14
|
+
Nothing in the command ever asked whether the subagent had answered. Step 4
|
|
15
|
+
spawned it and Step 6 assumed output existed, so a subagent that returned only
|
|
16
|
+
chat text — or nothing at all — produced a silent session with no error. That
|
|
17
|
+
is the shape the No-Fallback-Ever doctrine exists to forbid: a failure that
|
|
18
|
+
continues instead of stopping loudly.
|
|
19
|
+
|
|
20
|
+
- `commands/gsd-t-architect.md`: new step 4a halts on an empty or partial
|
|
21
|
+
return, naming what is missing, and explicitly bars substituting a
|
|
22
|
+
self-authored summary (that is not the fresh-context assessment the user
|
|
23
|
+
asked for). Step 6 now requires the artifact write to be PROVEN on disk
|
|
24
|
+
before presenting, halting with the missing path if it is not there.
|
|
25
|
+
- `.gsd-t/pseudocode/PseudoCode-SharedLocalConfig.md`: behaviour map for one
|
|
26
|
+
shared local settings file per project, linked into every worktree; gloss
|
|
27
|
+
placement fixed to satisfy the style gate.
|
|
28
|
+
|
|
29
|
+
The root cause of the empty return is not yet known — it lives in the session
|
|
30
|
+
transcript. These halts make the next occurrence report itself instead of
|
|
31
|
+
going quiet.
|
|
32
|
+
|
|
33
|
+
## [5.12.10] - 2026-08-19
|
|
34
|
+
|
|
35
|
+
### Added — naming an existing worktree walks you into it
|
|
36
|
+
|
|
37
|
+
Naming a worktree that already existed used to be refused outright. That
|
|
38
|
+
refused the ordinary case — your own worktree, from yesterday, with nobody in
|
|
39
|
+
it — and left no way back except quitting the session and starting one by
|
|
40
|
+
hand, which is the exact chore the picker exists to spare you.
|
|
41
|
+
|
|
42
|
+
`--name` now ENTERS a worktree when git confirms it as this repo's, on that
|
|
43
|
+
branch, with no interactive session in it. Two cases still STOP, because each
|
|
44
|
+
is a way of landing on somebody's uncommitted work: a directory git does not
|
|
45
|
+
know as that branch's worktree (a stray folder, or another branch's), and one
|
|
46
|
+
an interactive session already occupies (the M105 collision). The occupancy
|
|
47
|
+
check already existed on the reuse path and was simply never asked on the
|
|
48
|
+
naming path — no new mechanism was added.
|
|
49
|
+
|
|
50
|
+
A path bug surfaced while testing and is fixed: git reports symlink-resolved
|
|
51
|
+
paths while ours were used as typed, so on macOS `/var` vs `/private/var` made
|
|
52
|
+
one folder compare as two. Any repo under a symlinked path would have been
|
|
53
|
+
wrongly refused. An unresolvable path now HALTS rather than answering "not a
|
|
54
|
+
match".
|
|
55
|
+
|
|
56
|
+
- `bin/gsd-t-pick-worktree.cjs`: `create()` becomes `enterOrCreate()`; new
|
|
57
|
+
`isWorktreeOf()` asks the repo's own worktree register (not the directory,
|
|
58
|
+
which a foreign checkout would answer for); new `realPath()` compares
|
|
59
|
+
symlink-resolved paths and halts when one cannot be resolved; new `--list`
|
|
60
|
+
prints `free|busy<TAB><path>` per worktree.
|
|
61
|
+
- `bin/gsd-t.js`: flags pass through unchanged; comment notes `--list`.
|
|
62
|
+
- `templates/CLAUDE-global.md`: states the enter-vs-stop rule.
|
|
63
|
+
- `.gsd-t/pseudocode/PseudoCode-EnterExistingWorktree.md`: source-of-truth
|
|
64
|
+
behaviour map, style gate clean.
|
|
65
|
+
- `test/m111-pick-worktree.test.js`: the old "refuses a directory that already
|
|
66
|
+
exists" test narrows to the two cases that still refuse; adds coverage for
|
|
67
|
+
entering a free worktree, a stray directory, a worktree on another branch,
|
|
68
|
+
and both `--list` shapes.
|
|
69
|
+
|
|
70
|
+
The launcher (`cc()` in `~/.zshrc`) shows the worktree list above the name
|
|
71
|
+
prompt, so an existing one is picked by sight rather than recalled.
|
|
72
|
+
|
|
5
73
|
## [5.11.32] - 2026-08-14
|
|
6
74
|
|
|
7
75
|
### Fixed — a missing graph is now BUILT, and a "wired" claim has to prove itself
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# GSD-T: Contract-Driven Development for Claude Code
|
|
2
2
|
|
|
3
|
-
**v5.11
|
|
3
|
+
**v5.12.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
|
|
4
4
|
|
|
5
5
|
**Eliminates context rot** — task-level fresh dispatch (one subagent per task, ~10-20% context each) means compaction never triggers.
|
|
6
6
|
**Compaction-proof debug loops** — `gsd-t headless --debug-loop` runs test-fix-retest cycles as separate `claude -p` sessions. A JSONL debug ledger persists all hypothesis/fix/learning history across fresh sessions. Anti-repetition preamble injection prevents retrying failed hypotheses. Escalation tiers (sonnet → opus → human) and a hard iteration ceiling enforced externally.
|
|
@@ -12,12 +12,23 @@
|
|
|
12
12
|
* d=$(gsd-t-pick-worktree) && [ -n "$d" ] && cd "$d"
|
|
13
13
|
* claude
|
|
14
14
|
*
|
|
15
|
-
*
|
|
15
|
+
* Four modes:
|
|
16
16
|
*
|
|
17
17
|
* (no flags) pick a worktree, creating one if none is free
|
|
18
18
|
* --suggest say what WOULD happen, create nothing. Prints
|
|
19
19
|
* "reuse:<path>", "create", or nothing at all.
|
|
20
|
-
* --name <name>
|
|
20
|
+
* --name <name> go to the worktree on a branch called <name>, creating
|
|
21
|
+
* it if it isn't there yet
|
|
22
|
+
* --list list this project's worktrees, one per line, as
|
|
23
|
+
* "<free|busy>\t<path>". Creates nothing.
|
|
24
|
+
*
|
|
25
|
+
* --name naming a worktree that already exists ENTERS it. Refusing that was the
|
|
26
|
+
* old behaviour and it refused the ordinary case — your own worktree, from
|
|
27
|
+
* yesterday, with nobody in it — leaving no way back in except quitting and
|
|
28
|
+
* starting a session by hand, which is the thing this script exists to spare
|
|
29
|
+
* you. What the refusal genuinely protected is narrower: another live session
|
|
30
|
+
* sitting in that folder, or a directory git does not know as this branch's
|
|
31
|
+
* worktree. Both still stop.
|
|
21
32
|
*
|
|
22
33
|
* --suggest exists so the shell can ask for a name BEFORE anything is created.
|
|
23
34
|
* A branch named at session start, before the work is known, can only be a
|
|
@@ -64,6 +75,7 @@ function fail(message) { // could not decide — say why, change nothing
|
|
|
64
75
|
function main() {
|
|
65
76
|
const argv = process.argv.slice(2);
|
|
66
77
|
const suggest = argv.includes("--suggest");
|
|
78
|
+
const wantsList = argv.includes("--list");
|
|
67
79
|
const nameAt = argv.indexOf("--name");
|
|
68
80
|
const wanted = nameAt >= 0 ? argv[nameAt + 1] : null;
|
|
69
81
|
|
|
@@ -78,6 +90,17 @@ function main() {
|
|
|
78
90
|
|
|
79
91
|
const home = path.join(process.env.HOME, "Worktrees", path.basename(cwd));
|
|
80
92
|
|
|
93
|
+
// Report what is there so the prompt can show it before a name is typed.
|
|
94
|
+
// Nothing here is a path for the shell to cd into, so each line is labelled;
|
|
95
|
+
// the caller reads this one deliberately rather than as a bare directory.
|
|
96
|
+
if (wantsList) {
|
|
97
|
+
const busy = interactiveClaudeDirs();
|
|
98
|
+
for (const w of worktreesNewestFirst(home)) {
|
|
99
|
+
process.stdout.write(`${busy.has(w.path) ? "busy" : "free"}\t${w.path}\n`);
|
|
100
|
+
}
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
81
104
|
if (wanted) {
|
|
82
105
|
// Asking for the repo's own default branch means "work here, in the main
|
|
83
106
|
// checkout" — not "make a worktree called main", which git refuses anyway
|
|
@@ -94,7 +117,7 @@ function main() {
|
|
|
94
117
|
);
|
|
95
118
|
stay();
|
|
96
119
|
}
|
|
97
|
-
process.stdout.write(
|
|
120
|
+
process.stdout.write(enterOrCreate(cwd, home, branchNameFrom(wanted)).path + "\n");
|
|
98
121
|
return;
|
|
99
122
|
}
|
|
100
123
|
|
|
@@ -202,16 +225,30 @@ function worktreesNewestFirst(home) {
|
|
|
202
225
|
// timestamp, and a timestamp describes nothing — which is how this repo
|
|
203
226
|
// collected twenty `session-2026-08-08T23-10-16` branches. Callers with no name
|
|
204
227
|
// get "create" from --suggest and must come back with --name.
|
|
205
|
-
function
|
|
228
|
+
function enterOrCreate(repo, home, branch) {
|
|
206
229
|
if (!branch) {
|
|
207
230
|
fail("A worktree needs a branch name — run with --name <name>.");
|
|
208
231
|
}
|
|
209
232
|
const dest = path.join(home, branch);
|
|
210
233
|
|
|
211
|
-
//
|
|
212
|
-
//
|
|
234
|
+
// Already there: go in, provided it is genuinely this branch's worktree and
|
|
235
|
+
// nobody is working in it. Both conditions are checked before entering, since
|
|
236
|
+
// each is a way of landing on top of somebody's uncommitted work.
|
|
213
237
|
if (fs.existsSync(dest)) {
|
|
214
|
-
|
|
238
|
+
if (!isWorktreeOf(repo, dest, branch)) {
|
|
239
|
+
fail(
|
|
240
|
+
`${dest} exists but git does not know it as this repo's worktree for ` +
|
|
241
|
+
`"${branch}". Working there would sit on top of whatever is in it — ` +
|
|
242
|
+
`move it aside, or pick a different name.`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
if (interactiveClaudeDirs().has(dest)) {
|
|
246
|
+
fail(
|
|
247
|
+
`Another session is working in ${dest} right now. Two sessions in one ` +
|
|
248
|
+
`folder interleave each other's uncommitted work — pick a different name.`
|
|
249
|
+
);
|
|
250
|
+
}
|
|
251
|
+
return { path: dest };
|
|
215
252
|
}
|
|
216
253
|
|
|
217
254
|
fs.mkdirSync(home, { recursive: true });
|
|
@@ -235,6 +272,60 @@ function create(repo, home, branch) {
|
|
|
235
272
|
return { path: dest };
|
|
236
273
|
}
|
|
237
274
|
|
|
275
|
+
/**
|
|
276
|
+
* Does THIS repo know `dest` as its worktree for `branch`?
|
|
277
|
+
*
|
|
278
|
+
* Asked of the main repo rather than of the directory: a folder can be a
|
|
279
|
+
* perfectly valid git checkout of something else entirely, and it would answer
|
|
280
|
+
* "yes, I am a worktree on that branch" while belonging to another project.
|
|
281
|
+
* The repo's own register is the only place that settles ownership.
|
|
282
|
+
*
|
|
283
|
+
* A git that cannot answer is a stop, not a "probably fine" — the unanswered
|
|
284
|
+
* question is precisely whether somebody's work is already there.
|
|
285
|
+
*/
|
|
286
|
+
function isWorktreeOf(repo, dest, branch) {
|
|
287
|
+
const r = spawnSync("git", ["worktree", "list", "--porcelain"], {
|
|
288
|
+
cwd: repo, encoding: "utf8", timeout: 10000,
|
|
289
|
+
});
|
|
290
|
+
if (r.status !== 0) {
|
|
291
|
+
fail(
|
|
292
|
+
`Cannot read this repo's worktree list ` +
|
|
293
|
+
`(${String(r.stderr).trim() || "git failed"}), so it can't be confirmed ` +
|
|
294
|
+
`that ${dest} is yours to work in.`
|
|
295
|
+
);
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// Entries are blank-line separated; the lines that matter are "worktree
|
|
299
|
+
// <path>" and "branch refs/heads/<name>".
|
|
300
|
+
const target = realPath(dest);
|
|
301
|
+
for (const block of String(r.stdout).split(/\n\s*\n/)) {
|
|
302
|
+
const at = block.match(/^worktree (.+)$/m);
|
|
303
|
+
if (!at) continue;
|
|
304
|
+
if (realPath(at[1].trim()) !== target) continue;
|
|
305
|
+
const on = block.match(/^branch refs\/heads\/(.+)$/m);
|
|
306
|
+
return Boolean(on) && on[1].trim() === branch;
|
|
307
|
+
}
|
|
308
|
+
return false;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* The path with every symlink followed, so two spellings of one directory
|
|
313
|
+
* compare equal. git reports resolved paths; ours are as typed, and on macOS
|
|
314
|
+
* /var is a symlink to /private/var — so the same folder arrives under two
|
|
315
|
+
* names and a plain string compare calls them different places.
|
|
316
|
+
*
|
|
317
|
+
* A path that cannot be resolved stops the run. The comparison it feeds decides
|
|
318
|
+
* whether a directory is safe to work in, and an unresolvable path leaves that
|
|
319
|
+
* unanswered rather than answered "no".
|
|
320
|
+
*/
|
|
321
|
+
function realPath(p) {
|
|
322
|
+
try {
|
|
323
|
+
return fs.realpathSync(p);
|
|
324
|
+
} catch (e) {
|
|
325
|
+
fail(`Cannot resolve ${p} (${e.message}), so it can't be told apart from another directory.`);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
238
329
|
/**
|
|
239
330
|
* A worktree holds only what git tracks, so the secrets and the installed
|
|
240
331
|
* dependencies stay behind and the new folder is born unable to run. Carry the
|
package/bin/gsd-t.js
CHANGED
|
@@ -5658,7 +5658,7 @@ if (require.main === module) {
|
|
|
5658
5658
|
case "pick-worktree": {
|
|
5659
5659
|
const picker = path.join(PKG_ROOT, "bin", "gsd-t-pick-worktree.cjs");
|
|
5660
5660
|
try {
|
|
5661
|
-
// Pass the flags through — --suggest and --
|
|
5661
|
+
// Pass the flags through — --suggest, --name and --list live on the picker.
|
|
5662
5662
|
execFileSync(process.execPath, [picker, ...args.slice(1)], { stdio: "inherit" });
|
|
5663
5663
|
} catch (e) {
|
|
5664
5664
|
// The picker prints its own reason on stderr and exits non-zero when it
|
|
@@ -261,6 +261,26 @@ Graph note: if a code graph exists (`.gsd-t/graphDB/graph.db` — resolve via
|
|
|
261
261
|
reuse/caller queries (Stage 3 + Stage 5 duplication check). If absent, it greps/reads and says so
|
|
262
262
|
LOUDLY (reuse-detection is reduced — never a silent "nothing found").
|
|
263
263
|
|
|
264
|
+
### 4a — The subagent came back: CHECK IT, never assume
|
|
265
|
+
|
|
266
|
+
**A run that produces nothing must SAY SO. Ending the turn silently is the failure this step
|
|
267
|
+
exists to stop** — five consecutive architect runs once returned nothing, each looking like an
|
|
268
|
+
idle session, because no step ever asked whether the subagent had answered.
|
|
269
|
+
|
|
270
|
+
The moment the subagent returns, before any other work:
|
|
271
|
+
|
|
272
|
+
1. **Did it return anything at all?** An empty return, a return that is only a status line, or no
|
|
273
|
+
return (the subagent died on an API error) → **HALT**. Say plainly: the architect subagent
|
|
274
|
+
produced no assessment, name the target, and stop. Do NOT retry silently, do NOT write a
|
|
275
|
+
summary from your own reading of the code — a summary you wrote yourself is not the
|
|
276
|
+
fresh-context assessment the user asked for, and presenting it as one is worse than the
|
|
277
|
+
silence.
|
|
278
|
+
2. **Does it contain the required parts?** The Six-Stage answers and a `Simply Stated` lead. A
|
|
279
|
+
return that skips stages is a partial result → say which stages are missing, then HALT.
|
|
280
|
+
|
|
281
|
+
**Never end the turn without either an assessment or a stated reason there is none.** Silence is
|
|
282
|
+
indistinguishable from a hang, and the user cannot tell whether to wait or re-run.
|
|
283
|
+
|
|
264
284
|
---
|
|
265
285
|
|
|
266
286
|
## Step 5: The subagent runs the Six-Stage Pass (with EVIDENCE, never conviction)
|
|
@@ -366,6 +386,12 @@ the artifact to `.gsd-t/pseudocode/PseudoCode-<Target>.md`, then **self-check it
|
|
|
366
386
|
`gsd-t pseudocode-style --doc <the file>` — a non-zero exit means the style is wrong; fix it
|
|
367
387
|
before presenting. (The same gate is FAIL-blocking in verify.)
|
|
368
388
|
|
|
389
|
+
**Writing it is not optional, and the write must be PROVEN, not assumed.** Unless `--chat-only`,
|
|
390
|
+
confirm the file is on disk (`ls -la` it) before presenting anything. **A file that is not there
|
|
391
|
+
is a HALT** — say the assessment was produced but the artifact never landed, and name the path
|
|
392
|
+
that is missing. An assessment that exists only as chat text is lost the moment the session is
|
|
393
|
+
cleared, which is exactly how a run that did all its work still leaves nothing behind.
|
|
394
|
+
|
|
369
395
|
**B — Session summary** (always printed, even under `--build`):
|
|
370
396
|
- **Simply Stated** (REQUIRED FIRST LINE — the clarity gate) — the verdict + the single most
|
|
371
397
|
important finding in ONE clean, jargon-free, straight-line statement a smart non-specialist
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tekyzinc/gsd-t",
|
|
3
|
-
"version": "5.11
|
|
3
|
+
"version": "5.12.11",
|
|
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",
|
|
@@ -154,6 +154,9 @@ WHEN creating a worktree directly (git worktree add, isolation: "worktree", etc.
|
|
|
154
154
|
|
|
155
155
|
**A worktree is NOT usable until it is provisioned (M112).** `git worktree add` brings only what git TRACKS, so every ignored file stays behind — `.env` and its secrets, local settings, and the installed dependencies. The new folder looks complete and is not: tests fail on a missing module and the app cannot reach its database, both reading as broken code rather than a setup gap. `bin/gsd-t-worktree-provision.cjs` runs automatically inside `gsd-t-pick-worktree` and treats the three kinds of ignored file differently — **CARRY** local config and secrets (`.env*`, `.npmrc`, credentials; permissions preserved, so a 600 secret does not become world-readable), **SKIP** per-session `.gsd-t` state, build output and OS junk (copying another session's briefs/heartbeats is how two sessions come to believe they own the same work), and **INSTALL** dependencies from the worktree's own lockfile rather than copying or symlinking (a symlink makes a lockfile change in one tree silently alter the other). Anything it cannot read or copy is REPORTED, and a failed install HALTS — a worktree that came up short says so instead of looking ready. Creating a worktree by hand skips all of this, so prefer `gsd-t-pick-worktree --name <branch>`.
|
|
156
156
|
|
|
157
|
+
**Naming an existing worktree WALKS YOU INTO IT (M113).** `gsd-t pick-worktree --name <branch>` used to refuse whenever the folder was already there, which refused the ordinary case — your own worktree, from yesterday, nobody in it — and left no way back except quitting the session and starting one by hand. It now ENTERS a worktree git confirms as this repo's, on that branch, with no interactive session in it. Two cases still STOP, because each is a way of landing on somebody's uncommitted work: a directory git does not know as that branch's worktree (a stray folder, or another branch's), and a worktree an interactive session already occupies (the M105 collision). `gsd-t pick-worktree --list` prints one line per worktree as `free|busy<TAB><path>`, so the launcher can show what exists before asking for a name.
|
|
158
|
+
|
|
159
|
+
|
|
157
160
|
**The expected-branch rule governs the MAIN checkout only (M112).** A worktree exists precisely to be on its own branch, so `branch-guard` passes there and names the skip; a **detached HEAD in a worktree FAILS**, because commits made with no branch attached are easily lost. The rule is read from the project CLAUDE.md in either shape — a sentence (`Expected branch: main`) or a table row (`| Expected branch | main |`) — and when no rule is declared the check says `NOT CHECKED` rather than returning a bare pass.
|
|
158
161
|
|
|
159
162
|
# Destructive Action Guard (MANDATORY)
|