@tekyzinc/gsd-t 5.11.28 → 5.11.30

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,84 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [5.11.30] - 2026-08-11
6
+
7
+ ### Fixed — the scan crashed before any finder ran: `slices is not defined`
8
+
9
+ The Atos scan died after 4 agents (preflight, probe, graph-wiring) with a
10
+ JavaScript reference error. Nothing was written; no finder ever started.
11
+
12
+ 618: slices = budgetPlan.slices; // never declared at this scope
13
+
14
+ v5.11.26 added that assignment to a bare name. v5.11.27 then added a
15
+ `const slices` INSIDE a helper function — a different scope entirely, which made
16
+ the name look declared to anyone skimming the file. The workflow sandbox runs in
17
+ strict mode, where assigning to an undeclared name throws.
18
+
19
+ `slices` is now declared where the code that uses it runs, starting as the
20
+ probe's own carve so the budget-failed branch needs no assignment at all.
21
+
22
+ **Nothing caught this, which is the more important half.** `node --check` parses
23
+ an undeclared assignment happily — it is legal syntax, and only strict mode makes
24
+ it an error, at runtime. The sandbox lint checks banned requires and `args`
25
+ handling, not scope. No test executes this path, because the workflow only runs
26
+ against a real project.
27
+
28
+ - `templates/workflows/gsd-t-scan.workflow.js`: `let slices = rawSlices` at the
29
+ scope that runs them.
30
+ - `test/m112-workflow-undeclared-assignment.test.js`: a static check over every
31
+ workflow for an assignment to a name its scope never declares, with a
32
+ function-body map so an `if`/`else` block is not mistaken for a nested scope.
33
+
34
+ The check was itself wrong twice before it worked, and the meta-test is what
35
+ caught it: the first version tested a hand-written sample with the offending line
36
+ at column zero and passed while missing the real bug, which is indented two
37
+ spaces inside an `if`. Verified against the actual shipped file — it reports
38
+ line 618 there and passes the fixed one.
39
+
40
+ ## [5.11.29] - 2026-08-11
41
+
42
+ ### Fixed — 20 of 27 projects had no usable code graph, and nothing said so
43
+
44
+ A binvoice session reached for the graph, found none, and read an 827-file
45
+ project by grep. `update-all` had reported that project as current for months,
46
+ because it was: every file GSD-T ships was in place. The graph is not a shipped
47
+ file — it is built state, created only when someone runs `gsd-t graph index` by
48
+ hand, and no propagation step creates it.
49
+
50
+ Checking for it exposed a second, larger failure. M99 moved the store from
51
+ `.gsd-t/graph.db` to `.gsd-t/graphDB/graph.db` — it changed where the code LOOKS
52
+ without moving what was already there. 18 projects still held a real, populated
53
+ index at the old path that every tool walked straight past.
54
+
55
+ never built 2 (binvoice, newman)
56
+ at the old path 18 — a full index, invisible to the tooling
57
+ working 7
58
+
59
+ **Absence is now repaired, not reported.** Per David's rule: a missing graph is
60
+ BUILT, a stale one is UPDATED, a misplaced one is MOVED, and grep is reserved
61
+ for content that cannot be indexed at all (`.md`, `.sql`, `.json`, config,
62
+ prose). The prior contract said HALT on an absent graph — correct 20 times here,
63
+ and it would have repaired nothing.
64
+
65
+ The rule now also governs plain conversational sessions. The binvoice failure
66
+ was not a wired command; it was ordinary work, and the contract only ever
67
+ covered `/gsd-t-*` commands.
68
+
69
+ - `bin/gsd-t.js`: `graphState()` tells the two faults apart; `migrateLegacyGraph()`
70
+ moves a pre-M99 store (the resolver's own migration, already written for M99
71
+ and never called on existing projects); `buildGraph()` indexes from scratch and
72
+ verifies a store actually landed rather than trusting the indexer's exit code.
73
+ A failed repair is reported per project and never counted as fixed.
74
+ - `.gsd-t/contracts/graph-consumer-wiring-contract.md`: FAIL-LOUD now repairs
75
+ before halting.
76
+ - `~/.claude/CLAUDE.md` + `templates/CLAUDE-global.md`: the rule reaches every
77
+ session, not only wired commands.
78
+ - `test/m112-graph-health-check.test.js`: 11 regressions.
79
+
80
+ Run once here: 18 stores moved, 2 graphs built (binvoice 639 files/56,822 edges,
81
+ newman 610 files/27,582 edges), all verified answering.
82
+
5
83
  ## [5.11.28] - 2026-08-11
6
84
 
7
85
  ### Fixed — the graph stored the alias edges, then could not find them (two bugs)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v5.11.28** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v5.11.30** - 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.
package/bin/gsd-t.js CHANGED
@@ -2917,10 +2917,129 @@ function createProjectChangelog(projectDir, projectName) {
2917
2917
  }
2918
2918
  }
2919
2919
 
2920
+ /**
2921
+ * Does this project have a built code graph, and does it have code worth one?
2922
+ *
2923
+ * [RULE] health-reports-missing-graph-never-assumes-built
2924
+ *
2925
+ * binvoice, 2026-08-11: a session reached for the graph, found nothing, and fell
2926
+ * back to grep — in an 827-file project. The tooling was installed and
2927
+ * propagated like everywhere else; the index had simply never been built.
2928
+ * Nothing builds it automatically, so a project has a graph only if someone once
2929
+ * ran `gsd-t graph index` there by hand.
2930
+ *
2931
+ * Nothing ever said so. `update-all` reported the project as current, because it
2932
+ * was — every file it ships was in place. The gap lived in state no propagation
2933
+ * step creates, and stayed invisible until a session hit it mid-task.
2934
+ *
2935
+ * Reported, never auto-built: indexing a large repo is slow, and doing it to 33
2936
+ * of them inside an update would be a surprise nobody asked for.
2937
+ */
2938
+ function graphState(projectDir) {
2939
+ // Where the store lives is the resolver's answer alone. If it cannot answer,
2940
+ // this check HALTS the caller rather than reporting a project as healthy — a
2941
+ // silent "graph fine" here is exactly the blindness being fixed.
2942
+ const resolver = require("./gsd-t-graph-store-resolver.cjs");
2943
+ const storePath = resolver.resolveStorePath(projectDir);
2944
+ if (storePath && fs.existsSync(storePath)) return { missing: false, files: 0 };
2945
+
2946
+ // A graph at the pre-M99 location (.gsd-t/graph.db) EXISTS — it is just where
2947
+ // the resolver no longer looks. Telling the user to build one would be wrong
2948
+ // twice over: the work is already done, and the real problem (a store the
2949
+ // tooling cannot find) would go unnamed.
2950
+ const legacyPath = resolver.resolveLegacyStorePath(projectDir);
2951
+ if (legacyPath && fs.existsSync(legacyPath)) return { missing: false, files: 0, legacy: true };
2952
+
2953
+ let n = 0;
2954
+ const SKIP = new Set(["node_modules", ".git", ".next", "dist", "build", ".venv", "venv", "out", "coverage"]);
2955
+ const EXT = new Set([".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".py"]);
2956
+ const walk = (dir, depth) => {
2957
+ if (depth > 6) return;
2958
+ if (n > 400) return; // enough to answer "is this a real codebase?"
2959
+ let entries;
2960
+ try {
2961
+ entries = fs.readdirSync(dir, { withFileTypes: true });
2962
+ } catch (_e) {
2963
+ // An unreadable subdirectory makes the count a floor, not a lie: the
2964
+ // result is only ever compared against a low threshold, and a miscount
2965
+ // downward can only omit a project from a report, never invent one.
2966
+ return;
2967
+ }
2968
+ for (const e of entries) {
2969
+ if (e.isDirectory()) {
2970
+ if (SKIP.has(e.name)) continue;
2971
+ if (e.name.startsWith(".")) continue;
2972
+ walk(path.join(dir, e.name), depth + 1);
2973
+ continue;
2974
+ }
2975
+ if (EXT.has(path.extname(e.name))) n++;
2976
+ }
2977
+ };
2978
+ walk(projectDir, 0);
2979
+
2980
+ // A project with almost no source has nothing for a graph to map; calling that
2981
+ // "missing" would be noise on every docs-only or config-only repo.
2982
+ return { missing: n >= 25, files: n };
2983
+ }
2984
+
2985
+ /**
2986
+ * Move a graph that sits at the pre-M99 path into the one the tooling reads.
2987
+ *
2988
+ * The store already holds a full index — M99 changed where every tool LOOKS
2989
+ * without moving what was there, so the graph was present and unreachable. The
2990
+ * resolver's own migration does the move, checkpoints the write-ahead log, and
2991
+ * verifies the result is readable.
2992
+ */
2993
+ function migrateLegacyGraph(projectDir) {
2994
+ try {
2995
+ const r = require("./gsd-t-graph-store-resolver.cjs").migrateGraphStore(projectDir);
2996
+ if (r && r.migrated) return { ok: true };
2997
+ return { ok: false, err: (r && r.reason) || "migration reported no result" };
2998
+ } catch (e) {
2999
+ return { ok: false, err: e.message || String(e) };
3000
+ }
3001
+ }
3002
+
3003
+ /**
3004
+ * Build a project's code graph from scratch.
3005
+ *
3006
+ * Slow on a large repo, so the caller announces it before this runs. A failure
3007
+ * is returned, never swallowed: a project left without a graph must say so, so
3008
+ * the next session halts instead of quietly grepping.
3009
+ */
3010
+ function buildGraph(projectDir) {
3011
+ try {
3012
+ const idx = path.join(__dirname, "gsd-t-graph-index.cjs");
3013
+ const r = require("child_process").spawnSync(process.execPath, [idx], {
3014
+ cwd: projectDir,
3015
+ encoding: "utf8",
3016
+ timeout: 15 * 60 * 1000,
3017
+ stdio: ["ignore", "pipe", "pipe"],
3018
+ });
3019
+ if (r.error) return { ok: false, err: r.error.message };
3020
+ if (r.status !== 0) {
3021
+ const tail = String(r.stderr || r.stdout || "").trim().split("\n").slice(-2).join(" ");
3022
+ return { ok: false, err: `exit ${r.status}${tail ? ` — ${tail}` : ""}` };
3023
+ }
3024
+ // The indexer reporting success is not proof a store landed. Verify the file
3025
+ // the tooling will actually read.
3026
+ const storePath = require("./gsd-t-graph-store-resolver.cjs").resolveStorePath(projectDir);
3027
+ if (!fs.existsSync(storePath)) return { ok: false, err: "indexer reported success but no store was written" };
3028
+ return { ok: true };
3029
+ } catch (e) {
3030
+ return { ok: false, err: e.message || String(e) };
3031
+ }
3032
+ }
3033
+
2920
3034
  async function checkProjectHealth(projects) {
2921
3035
  heading("Project Health");
2922
3036
  const playwrightMissing = [];
2923
3037
  const swaggerMissing = [];
3038
+ const graphMissing = [];
3039
+ const graphLegacy = [];
3040
+ const graphMigrated = [];
3041
+ const graphBuilt = [];
3042
+ const graphRepairFailed = [];
2924
3043
  const playwrightAutoInstalled = [];
2925
3044
  const playwrightInstallFailed = [];
2926
3045
 
@@ -2929,6 +3048,42 @@ async function checkProjectHealth(projects) {
2929
3048
  const name = path.basename(projectDir);
2930
3049
  if (!hasPlaywright(projectDir)) playwrightMissing.push(name);
2931
3050
  if (hasApi(projectDir) && !hasSwagger(projectDir)) swaggerMissing.push(name);
3051
+ const g = graphState(projectDir);
3052
+ if (g.missing) graphMissing.push({ name, dir: projectDir, files: g.files });
3053
+ if (g.legacy) graphLegacy.push({ name, dir: projectDir });
3054
+ }
3055
+
3056
+ // [RULE] graph-missing-is-built-not-reported
3057
+ //
3058
+ // A missing graph is not a status to report — it is work to do. Every session
3059
+ // in a project without one silently falls back to grep, which reads a fraction
3060
+ // of what the graph knows and answers a different question. Reporting it and
3061
+ // moving on leaves that in place until a human notices, which for binvoice
3062
+ // (827 files) meant months.
3063
+ //
3064
+ // Two different faults, two different repairs:
3065
+ //
3066
+ // · at the old path — the store EXISTS and holds a full index. M99 changed
3067
+ // where the tooling looks without moving what was there, so 18 projects
3068
+ // were reading past a perfectly good graph. This is a file move plus a
3069
+ // write-ahead-log checkpoint, and takes no time at all.
3070
+ // · never built — there is nothing to move. Indexing is genuinely slow
3071
+ // on a large repo, so this is announced before it runs rather than
3072
+ // appearing as an unexplained pause.
3073
+ //
3074
+ // A repair that fails is REPORTED and the project is left as it was. It is not
3075
+ // retried differently and never half-applied: a graph that came up short would
3076
+ // answer confidently with partial data, which is worse than not having one.
3077
+ for (const { name, dir } of graphLegacy) {
3078
+ const r = migrateLegacyGraph(dir);
3079
+ if (r.ok) graphMigrated.push(name);
3080
+ else graphRepairFailed.push({ name, what: "move", err: r.err });
3081
+ }
3082
+ for (const { name, dir, files } of graphMissing) {
3083
+ log(` building code graph for ${name} (${files}+ source files) — first build, this takes a while`);
3084
+ const r = buildGraph(dir);
3085
+ if (r.ok) graphBuilt.push(name);
3086
+ else graphRepairFailed.push({ name, what: "build", err: r.err });
2932
3087
  }
2933
3088
 
2934
3089
  // M50 D1: auto-install Playwright for any UI project that's missing it.
@@ -2947,6 +3102,19 @@ async function checkProjectHealth(projects) {
2947
3102
  }
2948
3103
  }
2949
3104
 
3105
+ if (graphMigrated.length > 0) {
3106
+ success(`Code graph moved to where the tooling reads it: ${graphMigrated.join(", ")}`);
3107
+ }
3108
+ if (graphBuilt.length > 0) {
3109
+ success(`Code graph built: ${graphBuilt.join(", ")}`);
3110
+ }
3111
+ if (graphRepairFailed.length > 0) {
3112
+ for (const f of graphRepairFailed) {
3113
+ warn(` ${f.name} — graph ${f.what} FAILED: ${f.err}`);
3114
+ }
3115
+ info("Those projects have no usable graph. Fix before working in them — a session there cannot answer structural questions.");
3116
+ }
3117
+
2950
3118
  if (playwrightMissing.length === 0 && swaggerMissing.length === 0) {
2951
3119
  success("All projects have Playwright and Swagger configured");
2952
3120
  } else {
@@ -2978,6 +3146,11 @@ async function checkProjectHealth(projects) {
2978
3146
  return {
2979
3147
  playwrightMissing,
2980
3148
  swaggerMissing,
3149
+ graphMissing,
3150
+ graphLegacy,
3151
+ graphMigrated,
3152
+ graphBuilt,
3153
+ graphRepairFailed,
2981
3154
  playwrightAutoInstalled,
2982
3155
  playwrightInstallFailed,
2983
3156
  };
@@ -3155,6 +3328,7 @@ async function doUpdateAll() {
3155
3328
  playwrightMissing,
3156
3329
  swaggerMissing,
3157
3330
  playwrightAutoInstalled,
3331
+ graphMissing,
3158
3332
  } = await checkProjectHealth(projects);
3159
3333
  showUpdateAllSummary(
3160
3334
  projects.length,
@@ -3163,6 +3337,7 @@ async function doUpdateAll() {
3163
3337
  swaggerMissing,
3164
3338
  syncCount,
3165
3339
  playwrightAutoInstalled,
3340
+ graphMissing,
3166
3341
  );
3167
3342
  }
3168
3343
 
@@ -3693,6 +3868,7 @@ function showUpdateAllSummary(
3693
3868
  swaggerMissing,
3694
3869
  syncCount,
3695
3870
  playwrightAutoInstalled,
3871
+ graphMissing,
3696
3872
  ) {
3697
3873
  log("");
3698
3874
  heading("Update All Complete");
@@ -3706,6 +3882,9 @@ function showUpdateAllSummary(
3706
3882
  log(` Auto-installed Playwright in: ${playwrightAutoInstalled.length} project(s)`);
3707
3883
  }
3708
3884
  if (swaggerMissing.length > 0) log(` Missing Swagger: ${swaggerMissing.length}`);
3885
+ if (Array.isArray(graphMissing) && graphMissing.length > 0) {
3886
+ log(` No code graph: ${graphMissing.length}`);
3887
+ }
3709
3888
  if (syncCount > 0) log(` Global rules synced: ${syncCount}`);
3710
3889
  log("");
3711
3890
  }
@@ -5336,6 +5515,7 @@ function showHelp() {
5336
5515
  // ─── Exports (for testing) ───────────────────────────────────────────────────
5337
5516
 
5338
5517
  module.exports = {
5518
+ graphState,
5339
5519
  validateProjectName,
5340
5520
  applyTokens,
5341
5521
  normalizeEol,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "5.11.28",
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",
3
+ "version": "5.11.30",
4
+ "description": "GSD-T: Contract-Driven Development for Claude Code \u2014 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",
7
7
  "repository": {
@@ -310,6 +310,25 @@ When any GSD-T command creates or modifies an API endpoint:
310
310
 
311
311
  This applies during: `gsd-t-execute`, `gsd-t-quick`, `gsd-t-integrate`, `gsd-t-wave`, and any command that touches API code.
312
312
 
313
+ ## Code Graph — build it, never grep around it (MANDATORY)
314
+
315
+ **Read code structure through the graph. If the graph is missing, BUILD it. If it is out of date, UPDATE it. Grep only where the answer cannot be indexed.**
316
+
317
+ ```
318
+ NEED A STRUCTURAL ANSWER? (what imports this, who calls this, what breaks if I change it)
319
+ ├── Graph present and fresh? → query it
320
+ ├── Graph missing? → `gsd-t graph index` — BUILD IT, then query
321
+ ├── Graph stale? → re-index the touched set, then query
322
+ ├── Graph at the old path? → it EXISTS; move it (`gsd-t graph index`), then query
323
+ └── Build/repair FAILED? → HALT and say so. Never answer the structural question by grep.
324
+ ```
325
+
326
+ **Grep is correct ONLY where the content cannot be indexed** — `.md`, `.sql`, `.json`, `.sh`, config, prose, comments. For anything about code structure, grep is not a weaker answer, it is a **different and wrong one**: it matches text, the question is about relationships.
327
+
328
+ **This governs plain conversational work, not just `/gsd-t-*` commands.** The failure that produced this rule (binvoice, 2026-08-11) was an ordinary session: it reached for the graph, found none, grepped an 827-file project, and nothing objected. Checking the graph's existence before reasoning about code is the first move, not a fallback.
329
+
330
+ **Absence is a repairable condition, not a stop sign.** When this was checked across the machine, 20 of 27 registered projects had no usable graph — 2 never built, 18 holding a real index at a path the tooling stopped reading after the store moved. Every one of those sessions had been grepping. `gsd-t update-all` now repairs both automatically and reports any it could not.
331
+
313
332
  ## Prime Rule
314
333
  KEEP GOING. Only stop for:
315
334
  1. Unrecoverable errors after 2 fix attempts (delegate to `gsd-t headless --debug-loop` first — only stop if exit code 4)
@@ -602,6 +602,21 @@ const budgetPlan = await runCli(
602
602
  "slice-budget"
603
603
  );
604
604
 
605
+ // What the finders will actually run. Declared HERE, at the scope that uses it.
606
+ //
607
+ // [RULE] slices-declared-at-the-scope-that-runs-them
608
+ //
609
+ // v5.11.26 assigned to a bare `slices` that was never declared anywhere, and
610
+ // v5.11.27 added a `const slices` inside probePlaceholderFaults() — a different
611
+ // scope entirely. The workflow sandbox runs in strict mode, so the assignment
612
+ // below threw `slices is not defined` and killed the Atos scan after 4 agents,
613
+ // before a single finder ran. Nothing had checked it: `node --check` parses an
614
+ // undeclared assignment happily, and no test executed this path.
615
+ //
616
+ // It starts as the probe's own slices, so the failure branch below needs no
617
+ // assignment — an unmeasured plan still runs what the probe carved.
618
+ let slices = rawSlices;
619
+
605
620
  if (budgetPlan && budgetPlan.ok && Array.isArray(budgetPlan.slices) && budgetPlan.slices.length) {
606
621
  const a = budgetPlan.after || {};
607
622
  if (budgetPlan.slices.length > rawSlices.length) {