@tekyzinc/gsd-t 5.17.11 → 5.17.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 CHANGED
@@ -2,6 +2,62 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [5.17.13] - 2026-09-03
6
+
7
+ ### Fixed — the verify gate failed projects for GSD-T's own gaps (TD-395), and a lost finalizer crashed the phase workflow
8
+
9
+ Reported from TimeTracking: verify could not reach Red Team / QA because the gate
10
+ ahead of the triad failed on things the project could not fix. Two of them were ours.
11
+
12
+ - `bin/gsd-t.js`: `gsd-t-logging-envelope-check.cjs` joins `PROJECT_BIN_TOOLS`. The
13
+ verify gate reaches it via `__dirname` inside the project's own `bin/`, so it existed
14
+ on the author's machine and in no project (5th propagation gap of this class).
15
+ - `test/verify-gate-tools-propagated.test.js`: every `__dirname` tool the gate dispatches
16
+ to must be in the propagation set — structural, with a negative case. Closes the class.
17
+ - `bin/gsd-t-graph-use-gate.cjs`: a workflow stamps its WIRED claim under its own name
18
+ (`phase`, `verify`) while its workers' Bash-run queries land under the query CLI's default
19
+ label `cli`, which the gate exempted. TimeTracking's ledger: 302 queries under `cli`,
20
+ gate FAILED as zero. A query from an id with no wiring claim of its own now counts as
21
+ evidence for every claimant whose claim precedes it; a query before the claim is not
22
+ evidence; another claimant's queries are never borrowed. 4 tests.
23
+ - `templates/workflows/gsd-t-verify.workflow.js`: verify stamped its claim at Preflight and
24
+ ran its own gate immediately after — a fresh claim with zero queries, failing by
25
+ construction every run. The stamp now lands after the gate, before the first structural
26
+ query.
27
+ - `templates/workflows/gsd-t-phase.workflow.js`: a finalizer agent lost to an API 529
28
+ resolved to null and the run died with a TypeError at `result.competition = …`. It now
29
+ ends with `status: "failed"` and a resume hint (a halt with a message, not a fallback).
30
+
31
+ Not a GSD-T change: the gate's Playwright step runs `npx playwright test` exactly as the
32
+ project's own `npm test` does; a config that throws without `TEST_DATABASE_URL` must load
33
+ `.env.local` itself.
34
+
35
+ Also in this release: **M115 Test-Plan-First Requirements Interrogation** is DEFINED and
36
+ PARTITIONED (5 domains, 3 risk-first waves) with its A1 blind-replay answer key secured at
37
+ `test/fixtures/m115-blind-replay/`. Nothing of M115 is built yet.
38
+
39
+ ## [5.17.12] - 2026-09-02
40
+
41
+ ### Fixed — an import written `.js` never resolved to its `.ts` source, so `who-imports` answered "nothing imports this"
42
+
43
+ `who-imports src/content/dom-observer.ts` in binvoice returned 0 importers with
44
+ coverage "complete" while five real importers sat in the edges table. TypeScript
45
+ ESM requires an import to be WRITTEN `./dom-observer.js` even though the file on
46
+ disk is `dom-observer.ts`. The resolver only ever APPENDED extensions
47
+ (`x.js`, `x.js.ts`, …) and never SWAPPED one, so the specifier could not reach
48
+ its source. 817 of binvoice's 2,376 import edges (34%) were unresolvable this way,
49
+ each reading as "safe to delete."
50
+
51
+ - `bin/gsd-t-graph-query-cli.cjs`: after the append pass, try the swap
52
+ (`.js`→`.ts`/`.tsx`, `.mjs`→`.mts`, `.cjs`→`.cts`) on both the relative and
53
+ alias-expanded routes. A real `.js` on disk still wins; a package specifier
54
+ stays as written. `[RULE] query-resolves-js-specifier-to-ts-source`.
55
+ - `test/m114-nested-tsconfig-graph.test.js`: three end-to-end tests that index a
56
+ tiny repo and query it through the real CLI — `.js`→`.ts` resolves, a real
57
+ `.js` beats the swap, an external package is not rewritten.
58
+
59
+ Re-run `gsd-t graph index` is NOT required — resolution happens at query time.
60
+
5
61
  ## [5.17.11] - 2026-09-01
6
62
 
7
63
  ### Fixed — every arrow-function export was invisible to the code graph
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v5.17.11** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v5.17.13** - 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.
@@ -525,6 +525,25 @@ function loadSqliteStore(dbPath) {
525
525
  // expanded alias is ALREADY project-relative and must not be — joining it to
526
526
  // the importer's directory would produce `src/app/src/lib/db`. So the two
527
527
  // take different routes to the same place: add the extension either way.
528
+ // [RULE] query-resolves-js-specifier-to-ts-source
529
+ //
530
+ // binvoice, 2026-09-02: `who-imports src/content/dom-observer.ts` returned 0
531
+ // importers with coverage "complete" while five real importers sat in the
532
+ // edges table. TypeScript ESM requires an import to be WRITTEN `.js` even
533
+ // though the file on disk is `.ts` — so the edge says `./dom-observer.js`
534
+ // and no amount of appending extensions reaches `dom-observer.ts`. It has to
535
+ // SWAP the extension, not append to it. 817 of binvoice's 2,376 import edges
536
+ // (34%) were unresolvable this way, each answering "nothing imports this" —
537
+ // which reads as safe to delete.
538
+ const jsToTsCandidates = (p) => {
539
+ const m = /\.(js|jsx|mjs|cjs)$/.exec(p);
540
+ if (!m) return [];
541
+ const stem = p.slice(0, -m[0].length);
542
+ // .js->.ts/.tsx, .jsx->.tsx, .mjs->.mts, .cjs->.cts — the source files a
543
+ // bundler would have compiled INTO the specifier that was written.
544
+ return [stem + ".ts", stem + ".tsx", stem + ".mts", stem + ".cts"];
545
+ };
546
+
528
547
  const resolveDst = (srcFile, dst) => {
529
548
  if (typeof dst !== "string" || !dst) return dst;
530
549
 
@@ -536,6 +555,10 @@ function loadSqliteStore(dbPath) {
536
555
  for (const ext of EXTS) {
537
556
  if (fileIds.has(base + ext)) return base + ext;
538
557
  }
558
+ // An alias-expanded path can carry the same written-.js convention.
559
+ for (const cand of jsToTsCandidates(base)) {
560
+ if (fileIds.has(cand)) return cand;
561
+ }
539
562
  return dst; // genuinely external — keep it exactly as written
540
563
  }
541
564
 
@@ -544,6 +567,11 @@ function loadSqliteStore(dbPath) {
544
567
  for (const ext of EXTS) {
545
568
  if (fileIds.has(base + ext)) return base + ext;
546
569
  }
570
+ // TypeScript ESM: the import is written `.js`, the source is `.ts`. Swap,
571
+ // don't append. [RULE] query-resolves-js-specifier-to-ts-source
572
+ for (const cand of jsToTsCandidates(base)) {
573
+ if (fileIds.has(cand)) return cand;
574
+ }
547
575
  return base; // no indexed match (external/missing) — keep the resolved path
548
576
  };
549
577
 
@@ -94,7 +94,7 @@ function readLedger(files, sinceTs) {
94
94
 
95
95
  const ensure = (id) => {
96
96
  if (!consumers[id]) {
97
- consumers[id] = { consumer: id, queryCount: 0, wiringModes: [], firstWiringTs: null };
97
+ consumers[id] = { consumer: id, queryCount: 0, queryTs: [], wiringModes: [], firstWiringTs: null };
98
98
  }
99
99
  return consumers[id];
100
100
  };
@@ -122,7 +122,9 @@ function readLedger(files, sinceTs) {
122
122
  const kind = String(ev.kind || '');
123
123
 
124
124
  if (kind === EVIDENCE_KIND) {
125
- ensure(id).queryCount++;
125
+ const c = ensure(id);
126
+ c.queryCount++;
127
+ c.queryTs.push(typeof ev.ts === 'string' ? ev.ts : '');
126
128
  } else if (kind === WIRING_KIND) {
127
129
  const c = ensure(id);
128
130
  c.wiringModes.push(String(ev.graphWiringMode || ''));
@@ -143,24 +145,45 @@ function evaluate(consumers) {
143
145
  const violations = [];
144
146
  const checked = [];
145
147
 
148
+ // [RULE] graph-use-attributes-unlabelled-queries-by-time
149
+ // A workflow stamps its WIRED claim under its own name ("phase", "verify", …),
150
+ // but the graph queries its workers run through Bash carry the query CLI's
151
+ // default label ("cli") — nothing in the worker's shell exports
152
+ // GSDT_GRAPH_CONSUMER. TimeTracking (2026-09-03, TD-395): 302 queries under
153
+ // "cli", the three WIRED claims under "phase"/"verify", and the gate FAILED a
154
+ // run that had consulted the graph 302 times — attribution, not absence.
155
+ // So a query logged by an id that made NO wiring claim of its own (cli, a hook)
156
+ // counts as evidence for every claimant whose WIRED claim PRECEDES it. A query
157
+ // before the claim is not evidence: nothing had claimed anything yet.
158
+ const unlabelledQueryTs = [];
159
+ for (const id of Object.keys(consumers)) {
160
+ const c = consumers[id];
161
+ if (c.wiringModes.length === 0) unlabelledQueryTs.push(...(c.queryTs || []));
162
+ }
163
+
146
164
  for (const id of Object.keys(consumers)) {
147
165
  if (NON_CONSUMER_IDS.has(id)) continue;
148
166
  const c = consumers[id];
149
167
  if (c.wiringModes.length === 0) continue; // never declared; nothing claimed, nothing to prove
150
168
 
151
169
  const claimedWired = c.wiringModes.some((m) => m.toLowerCase() === 'wired');
152
- checked.push({ consumer: id, wiringModes: c.wiringModes, queryCount: c.queryCount });
170
+ const since = c.firstWiringTs || '';
171
+ const attributed = unlabelledQueryTs.filter((ts) => ts && ts >= since).length;
172
+ const evidenceCount = c.queryCount + attributed;
173
+ checked.push({ consumer: id, wiringModes: c.wiringModes, queryCount: c.queryCount, attributedQueryCount: attributed });
153
174
 
154
- if (claimedWired && c.queryCount === 0) {
175
+ if (claimedWired && evidenceCount === 0) {
155
176
  violations.push({
156
177
  consumer: id,
157
178
  wiringMode: 'WIRED',
158
179
  queryCount: 0,
180
+ attributedQueryCount: 0,
159
181
  firstWiringTs: c.firstWiringTs,
160
182
  evidence:
161
183
  `consumer "${id}" logged graphWiringMode=WIRED but issued 0 graph queries ` +
162
- `(kind:"query") in this window a WIRED claim with no query evidence means ` +
163
- `the structural question was answered some other way.`,
184
+ `(kind:"query") in this window, and no unlabelled query (cli / hook) was logged after ` +
185
+ `the claim a WIRED claim with no query evidence means the structural question ` +
186
+ `was answered some other way.`,
164
187
  });
165
188
  }
166
189
  }
package/bin/gsd-t.js CHANGED
@@ -3467,6 +3467,11 @@ const PROJECT_BIN_TOOLS = [
3467
3467
  "cli-preflight.cjs", "parallel-cli.cjs", "parallel-cli-tee.cjs",
3468
3468
  "gsd-t-context-brief.cjs",
3469
3469
  "gsd-t-verify-gate.cjs", "gsd-t-verify-gate-judge.cjs",
3470
+ // M100 D3 — trace+audit envelope gate. verify-gate dispatches to it via __dirname,
3471
+ // so a project with the gate but not this file fails `logging-envelope` on every
3472
+ // verify (TimeTracking TD-395, 2026-09-03 — the 5th propagation gap of this class;
3473
+ // test/verify-gate-tools-propagated.test.js now asserts the whole set structurally).
3474
+ "gsd-t-logging-envelope-check.cjs",
3470
3475
  // v5.4.10 — integer-identity primary-key gate. gsd-t-verify-gate.cjs dispatches to
3471
3476
  // it via an absolute path in the Track 2 plan, so a project that has the verify gate
3472
3477
  // but NOT this file gets an ENOENT on every verify. Ships alongside the gate itself.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "5.17.11",
3
+ "version": "5.17.13",
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",
@@ -1320,6 +1320,13 @@ if (!competitionOn) {
1320
1320
  ].filter(Boolean).join("\n"),
1321
1321
  { label: `${phaseName}:finalize`, phase: "Finalize", schema: FINALIZE_SCHEMA, model: "opus" }
1322
1322
  ).catch((e) => ({ status: "failed", artifacts: [], summary: `finalizer error: ${e && e.message}` }));
1323
+ // A finalizer that RESOLVES to nothing (API 529 overload observed 2026-09-02, run
1324
+ // wf_66339f4d-545) used to crash the whole run with a TypeError at
1325
+ // `result.competition = ...` — a stack trace instead of a status. This is a HALT,
1326
+ // not a fallback: the run still ends failed, it just says so in the envelope.
1327
+ if (!result || typeof result !== "object") {
1328
+ result = { status: "failed", artifacts: [], summary: "finalizer returned no result (transient API error or empty agent return) — resume the run with resumeFromRunId; cached stages replay, only finalize re-runs" };
1329
+ }
1323
1330
 
1324
1331
  // Re-validate the FINALIZED partition (Invariant 4). If salvage reintroduced an
1325
1332
  // overlap, the finalized graft is invalid → block completion with a clear reason.
@@ -301,8 +301,6 @@ if (!pre.ok) {
301
301
  return { status: "failed", reason: "preflight-failed", preflight: pre.envelope };
302
302
  }
303
303
  const brief = await generateBrief(projectDir, { kind: "verify", milestone, id: `verify-${(milestone || "m").toLowerCase()}` });
304
- // M99 D2: persist graphWiringMode for the verify consumer. [RULE] wiring-mode-three-states
305
- await persistWiringMode("Preflight");
306
304
 
307
305
  phase("Verify-Gate");
308
306
  const vg = await runVerifyGate(projectDir);
@@ -316,6 +314,14 @@ if (!vg.ok) {
316
314
  }
317
315
  log(`verify-gate green`);
318
316
 
317
+ // M99 D2: persist graphWiringMode for the verify consumer. [RULE] wiring-mode-three-states
318
+ // Stamped AFTER the verify-gate, not before it (TimeTracking TD-395, 2026-09-03): the
319
+ // gate's graph-use check judges every WIRED claim on the queries that FOLLOW it, and
320
+ // verify's first graph query (dead-code, just below) comes after the gate. Stamping at
321
+ // Preflight made every verify run fail its own gate — a fresh claim, zero queries, by
322
+ // construction. Now the claim precedes the structural queries and the NEXT run judges it.
323
+ await persistWiringMode("Verify-Gate");
324
+
319
325
  // ─── M94-D10-T5: Graph Structural Slice — dead-code + dangling (ADDITIVE, announced-degradation) ──
320
326
  // [RULE] qa-verify-use-orphan-dangling-verbs — query dead-code + dangling structurally.
321
327
  // [RULE] verify-integrate-graph-additive-announced-not-hard-fail — PRE-MORTEM Finding 3