@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 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.32** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
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
- * Three modes:
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> create a worktree on a branch called <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(create(cwd, home, branchNameFrom(wanted)).path + "\n");
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 create(repo, home, branch) {
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
- // Reusing a directory that already holds a different branch's work would put
212
- // this session on top of it. Say so instead.
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
- fail(`${dest} already exists. Pick a different name, or start there directly.`);
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 --name live on the picker.
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.32",
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)