@tekyzinc/gsd-t 4.9.14 → 4.10.10

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.
Files changed (51) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +1 -1
  3. package/bin/gsd-t-competition-judge.cjs +7 -1
  4. package/bin/gsd-t-context-brief-kinds/impact.cjs +91 -4
  5. package/bin/gsd-t-context-brief-kinds/partition.cjs +95 -0
  6. package/bin/gsd-t-context-brief-kinds/plan.cjs +110 -4
  7. package/bin/gsd-t-file-disjointness.cjs +319 -7
  8. package/bin/gsd-t-graph-anti-grep-lint.cjs +515 -0
  9. package/bin/gsd-t-graph-edge-extract.cjs +612 -0
  10. package/bin/gsd-t-graph-freshness.cjs +506 -0
  11. package/bin/gsd-t-graph-index.cjs +540 -0
  12. package/bin/gsd-t-graph-k1-sqlite-stream.cjs +251 -0
  13. package/bin/gsd-t-graph-query-cli.cjs +1182 -0
  14. package/bin/gsd-t-graph-scip-upgrade.cjs +440 -0
  15. package/bin/gsd-t-graph-store-bakeoff.cjs +889 -0
  16. package/bin/gsd-t-graph-synthetic-gen.cjs +304 -0
  17. package/bin/gsd-t-graph-ts-throughput.cjs +587 -0
  18. package/bin/gsd-t-scip-reader.cjs +167 -0
  19. package/bin/gsd-t.js +166 -48
  20. package/commands/gsd-t-debug.md +10 -0
  21. package/commands/gsd-t-design-build.md +10 -0
  22. package/commands/gsd-t-execute.md +15 -0
  23. package/commands/gsd-t-feature.md +15 -7
  24. package/commands/gsd-t-gap-analysis.md +17 -7
  25. package/commands/gsd-t-impact.md +25 -0
  26. package/commands/gsd-t-integrate.md +25 -0
  27. package/commands/gsd-t-partition.md +18 -0
  28. package/commands/gsd-t-plan.md +16 -0
  29. package/commands/gsd-t-populate.md +16 -5
  30. package/commands/gsd-t-prd.md +18 -0
  31. package/commands/gsd-t-project.md +19 -0
  32. package/commands/gsd-t-promote-debt.md +16 -5
  33. package/commands/gsd-t-qa.md +20 -8
  34. package/commands/gsd-t-quick.md +10 -0
  35. package/commands/gsd-t-scan.md +21 -3
  36. package/commands/gsd-t-test-sync.md +10 -0
  37. package/commands/gsd-t-verify.md +25 -0
  38. package/commands/gsd-t-wave.md +8 -0
  39. package/package.json +10 -2
  40. package/templates/workflows/gsd-t-debug.workflow.js +81 -0
  41. package/templates/workflows/gsd-t-integrate.workflow.js +47 -1
  42. package/templates/workflows/gsd-t-phase.workflow.js +84 -0
  43. package/templates/workflows/gsd-t-quick.workflow.js +64 -0
  44. package/templates/workflows/gsd-t-scan.workflow.js +200 -9
  45. package/templates/workflows/gsd-t-verify.workflow.js +50 -1
  46. package/bin/graph-cgc.js +0 -510
  47. package/bin/graph-indexer.js +0 -147
  48. package/bin/graph-overlay.js +0 -195
  49. package/bin/graph-parsers.js +0 -327
  50. package/bin/graph-query.js +0 -453
  51. package/bin/graph-store.js +0 -154
@@ -0,0 +1,167 @@
1
+ /**
2
+ * gsd-t-scip-reader.cjs
3
+ *
4
+ * M95 — Read a SCIP index (`index.scip`, a protobuf emitted by scip-typescript /
5
+ * scip-python) and turn it into the data the graph needs to RESOLVE call edges:
6
+ *
7
+ * 1. symbolToDef : Map<scipSymbol, funcId> — where each symbol is DEFINED
8
+ * 2. fileRefs : Map<relPath, Array<{symbol, funcId, line}>> — every
9
+ * reference occurrence in that file, already resolved to the def funcId
10
+ *
11
+ * A funcId is the graph's canonical `relPath#funcName` (line-suffix dropped here;
12
+ * the indexer keys call edges on `relPath#name`-style ids — see resolveCallEdges).
13
+ *
14
+ * Why this exists: before M95 the SCIP "upgrade" only RAN scip-typescript and
15
+ * relabelled the tree-sitter edges 'compiler-accurate' without reading the output.
16
+ * This module reads the output so call targets actually resolve across files.
17
+ *
18
+ * The SCIP protobuf decoder is the one bundled inside scip-typescript
19
+ * (`dist/src/scip.js`). scip-typescript is a GSD-T install requirement (M95), so
20
+ * depending on its bundled decoder is legitimate. We locate it across install
21
+ * layouts and FAIL LOUD if absent (never silently degrade).
22
+ *
23
+ * [RULE] scip-resolution-reads-real-output — edges are derived from the .scip
24
+ * protobuf, never relabelled-in-place.
25
+ * [RULE] scip-symbol-to-funcid-deterministic — same .scip → same funcId map.
26
+ */
27
+
28
+ 'use strict';
29
+
30
+ const fs = require('node:fs');
31
+ const path = require('node:path');
32
+ const { execSync } = require('node:child_process');
33
+
34
+ // ── Locate scip-typescript's bundled SCIP protobuf decoder ───────────────────
35
+ let _scipProto = null;
36
+
37
+ /**
38
+ * Load the bundled `scip.js` protobuf namespace from scip-typescript.
39
+ * Searches: local node_modules → global npm root → NODE_PATH.
40
+ * @returns {object|null} the `scip` namespace (Index, Document, …) or null.
41
+ */
42
+ function loadScipProto() {
43
+ if (_scipProto) return _scipProto;
44
+
45
+ const candidates = [];
46
+ // 1. local node_modules (if scip-typescript is a project dep)
47
+ try {
48
+ candidates.push(require.resolve('@sourcegraph/scip-typescript/dist/src/scip.js'));
49
+ } catch { /* not local */ }
50
+ // 2. global npm root
51
+ try {
52
+ const groot = execSync('npm root -g', { encoding: 'utf8' }).trim();
53
+ candidates.push(path.join(groot, '@sourcegraph/scip-typescript/dist/src/scip.js'));
54
+ } catch { /* npm not reachable */ }
55
+
56
+ for (const c of candidates) {
57
+ try {
58
+ if (fs.existsSync(c)) {
59
+ const mod = require(c);
60
+ _scipProto = mod.scip || mod;
61
+ if (_scipProto && _scipProto.Index) return _scipProto;
62
+ }
63
+ } catch { /* try next */ }
64
+ }
65
+ return null;
66
+ }
67
+
68
+ // ── SCIP symbol → function name ──────────────────────────────────────────────
69
+ // A SCIP symbol looks like:
70
+ // "scip-typescript npm <pkg> <ver> src/`calc.ts`/computeTotal()."
71
+ // The trailing descriptor after the last '/' identifies the entity:
72
+ // "computeTotal()." → a method/function descriptor (ends with "().")
73
+ // "computeTotal()(x)" → a parameter (we ignore params for call resolution)
74
+ // We extract the bare name from a function/method descriptor.
75
+
76
+ /**
77
+ * Extract the function name from a SCIP symbol, or null if the symbol is not a
78
+ * callable (parameter, type, module, etc.).
79
+ * @param {string} symbol
80
+ * @returns {string|null}
81
+ */
82
+ function funcNameFromSymbol(symbol) {
83
+ if (!symbol || typeof symbol !== 'string') return null;
84
+ // Parameters look like "name().(param)" — skip (they contain "(...)" after "().")
85
+ // Methods/functions end with "()." possibly preceded by the name.
86
+ // Take the final descriptor (after the last unescaped '/').
87
+ const lastSlash = symbol.lastIndexOf('/');
88
+ const descriptor = lastSlash === -1 ? symbol : symbol.slice(lastSlash + 1);
89
+ // Method/function descriptor: "<name>()." — capture <name>
90
+ const m = descriptor.match(/^([A-Za-z_$][\w$]*)\(\)\.$/);
91
+ return m ? m[1] : null;
92
+ }
93
+
94
+ // ── Read a SCIP index into resolution maps ───────────────────────────────────
95
+
96
+ const SYMBOL_ROLE_DEFINITION = 0x1; // SymbolRole.Definition bit
97
+
98
+ /**
99
+ * Decode a `.scip` file and build resolution maps.
100
+ *
101
+ * @param {string} scipPath absolute path to an index.scip
102
+ * @returns {{ ok: true, symbolToDef: Map<string,string>,
103
+ * fileRefs: Map<string, Array<{symbol:string, funcId:string, line:number}>> }
104
+ * | { ok: false, reason: string }}
105
+ */
106
+ function readScipIndex(scipPath) {
107
+ const proto = loadScipProto();
108
+ if (!proto) {
109
+ return { ok: false, reason: 'scip-decoder-unavailable' };
110
+ }
111
+ if (!fs.existsSync(scipPath)) {
112
+ return { ok: false, reason: 'scip-file-missing' };
113
+ }
114
+
115
+ let obj;
116
+ try {
117
+ const buf = fs.readFileSync(scipPath);
118
+ obj = proto.Index.deserialize(buf).toObject();
119
+ } catch (e) {
120
+ return { ok: false, reason: `scip-decode-failed: ${e.message}` };
121
+ }
122
+
123
+ const symbolToDef = new Map(); // scipSymbol → funcId (relPath#name)
124
+ const fileRefs = new Map(); // relPath → [{symbol, line}]
125
+
126
+ const docs = obj.documents || [];
127
+
128
+ // First pass: collect every DEFINITION occurrence → symbol → funcId.
129
+ for (const doc of docs) {
130
+ const relPath = doc.relative_path;
131
+ if (!relPath) continue;
132
+ for (const occ of doc.occurrences || []) {
133
+ const isDef = (occ.symbol_roles & SYMBOL_ROLE_DEFINITION) !== 0;
134
+ if (!isDef) continue;
135
+ const name = funcNameFromSymbol(occ.symbol);
136
+ if (!name) continue;
137
+ // funcId = the graph's file#name key
138
+ symbolToDef.set(occ.symbol, `${relPath}#${name}`);
139
+ }
140
+ }
141
+
142
+ // Second pass: collect every REFERENCE occurrence per file, resolved to the def.
143
+ for (const doc of docs) {
144
+ const relPath = doc.relative_path;
145
+ if (!relPath) continue;
146
+ const refs = [];
147
+ for (const occ of doc.occurrences || []) {
148
+ const isDef = (occ.symbol_roles & SYMBOL_ROLE_DEFINITION) !== 0;
149
+ if (isDef) continue; // refs only
150
+ const name = funcNameFromSymbol(occ.symbol);
151
+ if (!name) continue; // only callable references
152
+ const funcId = symbolToDef.get(occ.symbol); // resolve to the def
153
+ if (!funcId) continue; // external/unresolvable → skip (stays floor)
154
+ const line = Array.isArray(occ.range) ? occ.range[0] : null;
155
+ refs.push({ symbol: occ.symbol, funcId, line });
156
+ }
157
+ if (refs.length) fileRefs.set(relPath, refs);
158
+ }
159
+
160
+ return { ok: true, symbolToDef, fileRefs };
161
+ }
162
+
163
+ module.exports = {
164
+ loadScipProto,
165
+ funcNameFromSymbol,
166
+ readScipIndex,
167
+ };
package/bin/gsd-t.js CHANGED
@@ -1501,6 +1501,41 @@ function installCgc() {
1501
1501
  }
1502
1502
  }
1503
1503
 
1504
+ // M95: install the SCIP indexers that give the code graph compiler-accurate
1505
+ // call-edge resolution. These are a GSD-T requirement (not "optional only to get
1506
+ // better") — the precise call graph (who-calls / test-impl / blast-radius across
1507
+ // imports) depends on them. Node-only npm packages (Apache-2.0 / MIT). Fail-loud
1508
+ // but non-blocking: if the global install needs sudo, warn with the manual
1509
+ // command rather than aborting the whole GSD-T install.
1510
+ function installScipIndexers() {
1511
+ const indexers = [
1512
+ { bin: "scip-typescript", pkg: "@sourcegraph/scip-typescript", langs: "TS/JS" },
1513
+ { bin: "scip-python", pkg: "@sourcegraph/scip-python", langs: "Python" },
1514
+ ];
1515
+ for (const { bin, pkg, langs } of indexers) {
1516
+ let present = false;
1517
+ try {
1518
+ execFileSync("which", [bin], { encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"] });
1519
+ present = true;
1520
+ } catch { /* not present */ }
1521
+
1522
+ if (present) {
1523
+ info(`${bin} present (${langs} compiler-accurate edges)`);
1524
+ continue;
1525
+ }
1526
+ info(`Installing ${bin} (${langs} compiler-accurate code graph)...`);
1527
+ try {
1528
+ execFileSync("npm", ["install", "-g", pkg], {
1529
+ encoding: "utf8", timeout: 180000, stdio: ["pipe", "pipe", "pipe"],
1530
+ });
1531
+ success(`${bin} installed`);
1532
+ } catch (e) {
1533
+ warn(`${bin} install failed — code graph will use tree-sitter floor (approximate) for ${langs}`);
1534
+ info(`To enable compiler-accurate ${langs} edges: npm install -g ${pkg}`);
1535
+ }
1536
+ }
1537
+ }
1538
+
1504
1539
  // ─── Commands ────────────────────────────────────────────────────────────────
1505
1540
 
1506
1541
  // Shared templates that slash-command prompts reference by predictable path.
@@ -1689,6 +1724,9 @@ async function doInstall(opts = {}) {
1689
1724
  heading("Graph Engine (CGC)");
1690
1725
  installCgc();
1691
1726
 
1727
+ heading("SCIP Indexers (Compiler-Accurate Code Graph — M95)");
1728
+ installScipIndexers();
1729
+
1692
1730
  saveInstalledVersion();
1693
1731
 
1694
1732
  showInstallSummary(gsdtCommands.length, utilityCommands.length);
@@ -2636,6 +2674,14 @@ const PROJECT_BIN_TOOLS = [
2636
2674
  // M93 — jargon-gloss lint for written docs (the file surface the brevity-guard
2637
2675
  // Stop hook can't reach). Propagated so a project's doc checks can invoke it.
2638
2676
  "gsd-t-jargon-lint.cjs",
2677
+ // M94/M95 — code-graph runtime. The wired consumers (execute/wave disjointness,
2678
+ // debug, quick, impact, plan, scan) look for `bin/gsd-t-graph-query-cli.cjs` IN
2679
+ // THE PROJECT and fall back to grep when it is absent. Propagating the query CLI
2680
+ // plus its require-chain (indexer, freshness, edge-extract, SCIP upgrade + reader)
2681
+ // is what actually makes a project graph-aware instead of grep-dependent.
2682
+ // [[project_code_graph_universal_consumer]] [[feedback_graph_is_architectural_anchor]]
2683
+ "gsd-t-graph-query-cli.cjs", "gsd-t-graph-index.cjs", "gsd-t-graph-freshness.cjs",
2684
+ "gsd-t-graph-edge-extract.cjs", "gsd-t-graph-scip-upgrade.cjs", "gsd-t-scip-reader.cjs",
2639
2685
  ];
2640
2686
 
2641
2687
  // Files that older versions of this installer copied into project bin/ but
@@ -3114,6 +3160,31 @@ function checkDoctorCgc() {
3114
3160
  return issues;
3115
3161
  }
3116
3162
 
3163
+ // M95: report SCIP indexer presence. SCIP gives the code graph compiler-accurate
3164
+ // call-edge resolution (who-calls / test-impl / blast-radius across imports).
3165
+ // Absence is a WARN (graph still works at tree-sitter floor), not a hard error.
3166
+ function checkDoctorScip() {
3167
+ let issues = 0;
3168
+ heading("SCIP Indexers (Compiler-Accurate Code Graph)");
3169
+ const indexers = [
3170
+ { bin: "scip-typescript", langs: "TS/JS", pkg: "@sourcegraph/scip-typescript" },
3171
+ { bin: "scip-python", langs: "Python", pkg: "@sourcegraph/scip-python" },
3172
+ ];
3173
+ for (const { bin, langs, pkg } of indexers) {
3174
+ try {
3175
+ const ver = execFileSync(bin, ["--version"], {
3176
+ encoding: "utf8", timeout: 3000, stdio: ["pipe", "pipe", "pipe"],
3177
+ }).trim();
3178
+ success(`${bin} ${ver} (${langs} compiler-accurate)`);
3179
+ } catch {
3180
+ warn(`${bin} not installed — ${langs} edges fall back to tree-sitter floor (approximate)`);
3181
+ info(`Run: npm install -g ${pkg} (or reinstall GSD-T)`);
3182
+ issues++;
3183
+ }
3184
+ }
3185
+ return issues;
3186
+ }
3187
+
3117
3188
  // Verify context meter wiring: hook registration, hook script presence,
3118
3189
  // config validity, and a local estimation dry-run.
3119
3190
  // Returns number of issues (RED results). Mirrors checkDoctorCgc shape.
@@ -3355,6 +3426,7 @@ async function doDoctor(opts) {
3355
3426
  issues += checkDoctorInstallation();
3356
3427
  issues += await checkDoctorProject(opts);
3357
3428
  issues += checkDoctorCgc();
3429
+ issues += checkDoctorScip();
3358
3430
  // M61 D1: Context Meter retired (token-budget.cjs + context-meter hook deleted).
3359
3431
  // checkDoctorContextMeter would emit phantom errors for the deleted subsystem
3360
3432
  // and tell the user to reinstall it. Native /context replaces the meter.
@@ -3506,68 +3578,111 @@ function doChangelog() {
3506
3578
  }
3507
3579
 
3508
3580
  // ─── Graph ──────────────────────────────────────────────────────────────────
3581
+ // M94 Fix-1: the M20–M21 dead engine (graph-indexer / graph-store / graph-query)
3582
+ // is REMOVED. All entity-graph verbs (status / who-imports / who-calls / blast-radius
3583
+ // / index / query) now delegate to the M94 D5 query CLI
3584
+ // (bin/gsd-t-graph-query-cli.cjs). [RULE] graph-status-live
3585
+ // [RULE] graph-rewire-no-dangling-export
3586
+ //
3587
+ // Preserved intact: `gsd-t graph --output json|table` and `gsd-t graph tasks`
3588
+ // which print the M44 task-DAG (bin/gsd-t-task-graph.cjs) — a SEPARATE working
3589
+ // feature unrelated to the codebase entity graph. (Destructive Action Guard.)
3590
+
3591
+ /**
3592
+ * Delegate a verb to the D5 graph query CLI.
3593
+ * Returns the JSON envelope (already parsed) or { ok: false, reason: '...' }.
3594
+ * Prints human-readable output when interactive=true (the default).
3595
+ *
3596
+ * [RULE] graph-status-live — NEVER hits the dead M20–M21 "No graph index found" path.
3597
+ */
3598
+ function _graphQueryCli(verbAndArgs) {
3599
+ const { spawnSync } = require("child_process");
3600
+ const cliPath = require("path").join(__dirname, "gsd-t-graph-query-cli.cjs");
3601
+ const result = spawnSync(process.execPath, [cliPath].concat(verbAndArgs), {
3602
+ encoding: "utf8",
3603
+ cwd: process.cwd(),
3604
+ timeout: 30000,
3605
+ });
3606
+ if (result.error) {
3607
+ return { ok: false, reason: "graph-unavailable", detail: result.error.message };
3608
+ }
3609
+ const stdout = (result.stdout || "").trim();
3610
+ if (!stdout) {
3611
+ return { ok: false, reason: "graph-unavailable", detail: result.stderr || "no output" };
3612
+ }
3613
+ try {
3614
+ return JSON.parse(stdout);
3615
+ } catch {
3616
+ return { ok: false, reason: "graph-unavailable", detail: stdout };
3617
+ }
3618
+ }
3509
3619
 
3620
+ /**
3621
+ * gsd-t graph index
3622
+ * Builds the codebase entity graph via D3 (bin/gsd-t-graph-index.cjs).
3623
+ * [RULE] graph-rewire-no-dangling-export
3624
+ */
3510
3625
  function doGraphIndex() {
3511
3626
  heading("GSD-T Graph — Index");
3512
- const root = process.cwd();
3513
- const gq = require("./graph-indexer");
3514
- const result = gq.indexProject(root, { force: true });
3515
- if (result.success) {
3516
- success(`Indexed ${result.entityCount} entities, ${result.relationshipCount} relationships`);
3517
- info(`Files processed: ${result.filesProcessed}, skipped: ${result.filesSkipped}`);
3518
- info(`Duration: ${result.duration}ms`);
3519
- if (result.errors.length > 0) {
3520
- warn(`Parse errors: ${result.errors.length}`);
3521
- result.errors.forEach(e => log(` ${DIM}${e}${RESET}`));
3522
- }
3523
- } else {
3524
- error("Indexing failed");
3627
+ const { spawnSync } = require("child_process");
3628
+ const idxPath = require("path").join(__dirname, "gsd-t-graph-index.cjs");
3629
+ const result = spawnSync(process.execPath, [idxPath, "build", "--repo", process.cwd()], {
3630
+ encoding: "utf8",
3631
+ cwd: process.cwd(),
3632
+ stdio: ["ignore", "inherit", "inherit"],
3633
+ timeout: 300000,
3634
+ });
3635
+ if (result.status !== 0 && result.status !== null) {
3636
+ error("Graph index build failed — see output above");
3525
3637
  }
3526
3638
  }
3527
3639
 
3640
+ /**
3641
+ * gsd-t graph status
3642
+ * Queries live index state via D5 CLI.
3643
+ * [RULE] graph-status-live — NEVER returns "No graph index found" from the dead path.
3644
+ * [RULE] graph-rewire-no-dangling-export
3645
+ */
3528
3646
  function doGraphStatus() {
3529
3647
  heading("GSD-T Graph — Status");
3530
- const root = process.cwd();
3531
- const store = require("./graph-store");
3532
- const meta = store.readMeta(root);
3533
- if (!meta) {
3534
- warn("No graph index found. Run: gsd-t graph index");
3648
+ const envelope = _graphQueryCli(["status"]);
3649
+ if (!envelope.ok) {
3650
+ warn(`Graph index unavailable: ${envelope.reason || "graph-unavailable"}`);
3651
+ info("Run: gsd-t graph index");
3652
+ return;
3653
+ }
3654
+ // envelope from D5 CLI: { ok, verb:"status", indexed, storeSize, fileCount, tier, ... }
3655
+ if (envelope.indexed === false) {
3656
+ warn("No graph index found.");
3657
+ info("Run: gsd-t graph index");
3535
3658
  return;
3536
3659
  }
3537
- success(`Provider: ${meta.provider}`);
3538
- info(`Entities: ${meta.entityCount}`);
3539
- info(`Relationships: ${meta.relationshipCount}`);
3540
- info(`Last indexed: ${meta.lastIndexed}`);
3541
- info(`Duration: ${meta.duration}ms`);
3542
- const fileCount = Object.keys(meta.fileHashes || {}).length;
3543
- info(`Files tracked: ${fileCount}`);
3660
+ success(`Graph index: ${envelope.fileCount || 0} files`);
3661
+ if (envelope.tier) info(`Tier: ${envelope.tier}`);
3662
+ if (envelope.storeSize !== undefined) info(`Store size: ${envelope.storeSize} bytes`);
3663
+ if (envelope.detail) log(JSON.stringify(envelope, null, 2));
3544
3664
  }
3545
3665
 
3666
+ /**
3667
+ * gsd-t graph query <verb> [args...]
3668
+ * Delegates the verb directly to the D5 CLI.
3669
+ * [RULE] graph-rewire-no-dangling-export
3670
+ */
3546
3671
  function doGraphQuery(args) {
3547
- const root = process.cwd();
3548
- const gq = require("./graph-query");
3549
- const type = args[0];
3550
- if (!type) {
3551
- error("Usage: gsd-t graph query <type> [params...]");
3552
- info("Types: getEntity, getEntities, getCallers, getCallees,");
3553
- info(" findDeadCode, findDuplicates, findCircularDeps,");
3554
- info(" getDomainBoundaryViolations, getIndexStatus");
3672
+ const verb = args[0];
3673
+ if (!verb) {
3674
+ error("Usage: gsd-t graph query <verb> [target]");
3675
+ info("Verbs: status, who-imports, who-calls, blast-radius, cluster, dead-code, orphan, dangling, test-impl");
3555
3676
  return;
3556
3677
  }
3557
- const params = {};
3558
- for (let i = 1; i < args.length; i++) {
3559
- const [k, v] = args[i].split("=");
3560
- if (k && v) params[k] = v;
3561
- }
3562
- const result = gq.query(type, params, root);
3563
- log(JSON.stringify(result, null, 2));
3678
+ const envelope = _graphQueryCli([verb].concat(args.slice(1)));
3679
+ log(JSON.stringify(envelope, null, 2));
3564
3680
  }
3565
3681
 
3566
3682
  function doGraph(args) {
3567
3683
  // M44 D1-T4: `gsd-t graph --output json|table` prints the task-graph DAG
3568
- // (parsed from .gsd-t/domains/*/tasks.md) for debugging. The pre-existing
3569
- // `index|status|query` subcommands are the codebase entity graph (graph-
3570
- // indexer) and remain unchanged.
3684
+ // (parsed from .gsd-t/domains/*/tasks.md). This is a SEPARATE working feature
3685
+ // (Destructive Action Guard preserved intact). [RULE] graph-rewire-no-dangling-export
3571
3686
  const outIdx = args.indexOf("--output");
3572
3687
  if (outIdx !== -1) {
3573
3688
  const fmt = args[outIdx + 1] || "json";
@@ -3575,13 +3690,16 @@ function doGraph(args) {
3575
3690
  }
3576
3691
  const sub = args[0] || "status";
3577
3692
  switch (sub) {
3578
- case "index": doGraphIndex(); break;
3579
- case "status": doGraphStatus(); break;
3580
- case "query": doGraphQuery(args.slice(1)); break;
3581
- case "tasks": doGraphTaskOutput(args[1] || "table"); break;
3693
+ case "index": doGraphIndex(); break;
3694
+ case "status": doGraphStatus(); break;
3695
+ case "query": doGraphQuery(args.slice(1)); break;
3696
+ case "who-imports": { const e = _graphQueryCli(["who-imports", args[1] || ""]); log(JSON.stringify(e, null, 2)); break; }
3697
+ case "who-calls": { const e = _graphQueryCli(["who-calls", args[1] || ""]); log(JSON.stringify(e, null, 2)); break; }
3698
+ case "blast-radius": { const e = _graphQueryCli(["blast-radius", args[1] || ""]); log(JSON.stringify(e, null, 2)); break; }
3699
+ case "tasks": doGraphTaskOutput(args[1] || "table"); break;
3582
3700
  default:
3583
3701
  error(`Unknown graph subcommand: ${sub}`);
3584
- info("Usage: gsd-t graph [index|status|query|tasks]");
3702
+ info("Usage: gsd-t graph [index|status|query|who-imports|who-calls|blast-radius|tasks]");
3585
3703
  info(" gsd-t graph --output json|table (task DAG)");
3586
3704
  }
3587
3705
  }
@@ -90,6 +90,16 @@ The Workflow runs up to 2 cycles. Cycle 2 receives Cycle 1's failed hypothesis t
90
90
  - `complete` → re-run verify (`/gsd-t-verify`); auto-advance.
91
91
  - `needs-human` → present root-cause hypothesis + nextStepsIfNotResolved to the user. Do NOT spawn a third cycle; that's the Prime Rule.
92
92
 
93
+ ## Graph-Enhanced Debugging — READER + WRITER Pattern (M94-D11)
94
+
95
+ Debug applies the **WRITER pattern** from `graph-consumer-wiring-contract.md`:
96
+
97
+ **READER half (localization):** Before the fix agent reasons over code, the debug workflow queries `blast-radius` and `who-calls` to localize the bug's call chain — which callers reach the failing function, what is downstream of the changed symbol. This replaces grep-reconstructed call-chain discovery. The structural slice is injected into the debug agent's context.
98
+
99
+ **WRITER half (re-index after fix):** After the fix lands, the workflow triggers a re-index of the edited files so downstream graph queries see fresh edges (`graph-freshness-contract.md` D4 surface — `freshness_check_on_query` over the touched set). `[RULE] debug-reader-and-writer-both`.
100
+
101
+ **FAIL-LOUD on graph-unavailable:** On `{ok:false, reason:"graph-unavailable"}`, the debug workflow surfaces `"graph unavailable — fix it (gsd-t graph status)"` and halts the graph-query step. It does NOT fall back to grep for the structural question. The existing debug-loop logic (2-cycle cap, loop-ledger halt) is NOT disrupted — the graph query is additive, injected before the fix agent receives context.
102
+
93
103
  ## Contract-Boundary Debugging
94
104
 
95
105
  If the bug is at a domain contract boundary (one domain calls another via API/schema/component contract), the debug cycle agent is instructed to:
@@ -2,6 +2,16 @@
2
2
 
3
3
  This command delegates to the **JavaScript orchestrator** for ironclad flow control. Do NOT attempt to run the build pipeline inline — the orchestrator handles Claude spawning, measurement, review gates, and feedback processing deterministically.
4
4
 
5
+ ## Graph-Enhanced Design Build — WRITER Pattern (M94-D11)
6
+
7
+ Design-build applies the **WRITER pattern** from `graph-consumer-wiring-contract.md`:
8
+
9
+ **READER half (who-imports / cluster):** Before generating code, the build pipeline queries `gsd-t graph who-imports` on the target design-contract files to discover which existing components import the target (impact of the generated code on existing consumers), and `gsd-t graph cluster` to surface tightly-coupled file groups relevant to the component's context. This replaces grep/raw-read for the dependency structure question. The structural slice is injected into the code-generation agent's context. `[RULE] design-build-writer-pattern`.
10
+
11
+ **WRITER half:** After each tier (elements → widgets → pages) generates files, the pipeline triggers a re-index of the generated files (`freshness_check_on_query` from `graph-freshness-contract.md` D4 surface) so the next tier's `who-imports` / `cluster` query sees the new edges from the generated components. `[RULE] design-build-writer-pattern`.
12
+
13
+ **FAIL-LOUD on graph-unavailable:** On `{ok:false, reason:"graph-unavailable"}`, the structural query surfaces `"graph unavailable — fix it (gsd-t graph status)"` — the pipeline does NOT fall back to grep for the structural question. `[RULE] consumer-structural-grep-removed`.
14
+
5
15
  ## Step 1: Launch the Orchestrator
6
16
 
7
17
  ```bash
@@ -57,6 +57,21 @@ The Workflow returns:
57
57
  - `status === "verify-failed"`: domains completed but verify-gate found regressions. Invoke `gsd-t-debug` (or its Workflow) before retry.
58
58
  - `status === "failed"`: a domain reported `failed`, integrate failed, or preflight blocked. Read `domainResults` for the blocking entry.
59
59
 
60
+ ## Graph-Aware Disjointness (SAFETY-CRITICAL — M94-D11)
61
+
62
+ The file-disjointness check (`bin/gsd-t-file-disjointness.cjs`) is **graph-aware** as of M94. It uses `gsd-t graph blast-radius` and `gsd-t graph who-imports` to detect **transitive dependency overlap** — two domains whose touched files share a transitive dependency (one's files import a module that the other's files also import) are NOT disjoint even if their declared `Touches` lists have no literal overlap.
63
+
64
+ **FAIL-LOUD halt on graph-unavailable:**
65
+ On `graph-unavailable`, the disjointness check **FAILS LOUD and HALTS** — it does NOT fan out on a grep-reconstructed disjointness guess. A grep-reconstructed disjointness guess risks a concurrent-edit conflict (two agents editing overlapping code). This is the safety-critical invariant: `[RULE] execute-disjointness-fail-loud-halts-never-grep-guess`.
66
+
67
+ **Bootstrap escape hatch (fresh repo or parser regression):**
68
+ `graph-unavailable` in a fresh repo (no index yet built) or after a parser regression must not permanently brick parallel execution. When the graph is genuinely unavailable:
69
+ - The HALT message clearly states `graph-unavailable` (not `graph-says-non-disjoint`) and surfaces the remediation: `gsd-t graph status` and `gsd-t graph build`.
70
+ - The operator may pass `--disjointness-fallback=touches-only` to fall back to the literal-Touches-overlap check with a **loud announced WARNING** (never silent). This escape is NOT applicable to `graph-says-non-disjoint` verdicts — a real non-disjoint block stays absolute.
71
+ - `[RULE] disjointness-bootstrap-escape-not-a-no-recourse-brick`
72
+
73
+ **WRITER half:** After each domain's edits land, the workflow triggers a freshness pass over the touched set so downstream graph queries see fresh edges (`graph-freshness-contract.md` D4 surface). `[RULE] execute-disjointness-graph-aware-dependency-overlap`.
74
+
60
75
  ## Document Ripple
61
76
 
62
77
  After execute completes, ensure these are updated in the same commit chain:
@@ -52,18 +52,26 @@ Build a mental model of: "How does this codebase work today?"
52
52
 
53
53
  Note what you find — this informs Step 3's Multi-Consumer Check and Step 4's milestone ordering.
54
54
 
55
- ## Step 1.5: Graph-Enhanced Blast Radius Analysis
55
+ ## Step 1.5: Graph Structural Slice — blast-radius + who-imports (M94-D10)
56
56
 
57
57
  ```bash
58
- node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-id "${GSD_T_PARENT_AGENT_ID:-null}" --command gsd-t-feature --step 1 --step-label ".5: Graph-Enhanced Blast Radius Analysis" 2>/dev/null || true
58
+ node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-id "${GSD_T_PARENT_AGENT_ID:-null}" --command gsd-t-feature --step 1 --step-label ".5: Graph Structural Slice" 2>/dev/null || true
59
59
  ```
60
60
 
61
- If `.gsd-t/graph/meta.json` exists (graph index is available):
62
- 1. Query `getSurfaceConsumers` and `getCallers` on functions likely affected by the feature to calculate blast radius across all consumer surfaces
63
- 2. Query `getTransitiveCallers` for deep impact chains that may not be obvious from architecture docs alone
64
- 3. Feed these findings into the Impact Analysis in Step 3
61
+ **[RULE] plan-feature-gapanalysis-use-graph-not-grep** the feature agent MUST use the graph
62
+ CLI for blast-radius and who-imports questions (new-feature impact on existing code), NOT
63
+ grep/raw-read to reconstruct dependents.
65
64
 
66
- If graph is not available, skip this step.
65
+ The phase Workflow (`gsd-t-phase.workflow.js`) automatically queries `gsd-t graph blast-radius`
66
+ for the feature phase and injects the pre-computed blast-radius slice into the agent context.
67
+ Use this slice to identify which existing code is in the blast radius of the new feature's
68
+ touch points — catching impact via call edges that a grep over import patterns would MISS.
69
+
70
+ **On `graph-unavailable`:** the phase Workflow surfaces a LOUD message and the feature agent
71
+ FAILS LOUD — it does NOT silently skip this step or fall back to grep for the structural
72
+ blast-radius / who-imports question. Run `gsd-t graph status` to diagnose.
73
+
74
+ Graph consumer manifest row: `commands/gsd-t-feature.md | templates/workflows/gsd-t-phase.workflow.js | reader | blast-radius,who-imports | grep-reconstructed blast-radius/dependent discovery`
67
75
 
68
76
  ## Step 2: Understand the Feature
69
77
 
@@ -32,18 +32,28 @@ Read (if they exist):
32
32
  3. `docs/requirements.md` — existing requirements
33
33
  4. `docs/architecture.md` — system structure
34
34
 
35
- ## Step 1.5: Graph-Enhanced Code Mapping
35
+ ## Step 1.5: Graph Structural Slice — who-imports + dead-code (M94-D10)
36
36
 
37
37
  ```bash
38
- node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-id "${GSD_T_PARENT_AGENT_ID:-null}" --command gsd-t-gap-analysis --step 1 --step-label ".5: Graph-Enhanced Code Mapping" 2>/dev/null || true
38
+ node scripts/gsd-t-watch-state.js advance --agent-id "$GSD_T_AGENT_ID" --parent-id "${GSD_T_PARENT_AGENT_ID:-null}" --command gsd-t-gap-analysis --step 1 --step-label ".5: Graph Structural Slice" 2>/dev/null || true
39
39
  ```
40
40
 
41
- If `.gsd-t/graph/meta.json` exists (graph index is available):
42
- 1. Query `getRequirementFor` to pre-map requirements to code entities provides evidence for classification in Step 4
43
- 2. Query `findDeadCode` to identify unreachable code that may indicate implemented-but-disconnected features (potential gap indicators)
44
- 3. Feed these findings into the classification step to improve evidence quality
41
+ **[RULE] plan-feature-gapanalysis-use-graph-not-grep** the gap-analysis agent MUST use the
42
+ graph CLI for dead-code and who-imports questions (requirements-vs-code coverage gaps), NOT
43
+ grep/raw-read to reconstruct the dependency or dead-code picture.
45
44
 
46
- If graph is not available, skip this step.
45
+ The phase Workflow (`gsd-t-phase.workflow.js`) automatically queries `gsd-t graph dead-code`
46
+ for the gap-analysis phase and injects the pre-computed dead-code slice into the agent context.
47
+ Use this slice to:
48
+ 1. Identify implemented-but-disconnected code (dead-code entries that may indicate a gap — a
49
+ feature was implemented but never wired into a consumer).
50
+ 2. Identify which requirements map to code entities via real import/call edges (`who-imports`).
51
+
52
+ **On `graph-unavailable`:** the phase Workflow surfaces a LOUD message and the gap-analysis
53
+ agent FAILS LOUD — it does NOT silently skip this step or fall back to grep for the structural
54
+ dead-code / who-imports question. Run `gsd-t graph status` to diagnose.
55
+
56
+ Graph consumer manifest row: `commands/gsd-t-gap-analysis.md | templates/workflows/gsd-t-phase.workflow.js | reader | who-imports,dead-code | grep-reconstructed dead-code/dependency discovery`
47
57
 
48
58
  ## Step 2: Parse Requirements
49
59
 
@@ -14,6 +14,31 @@ The agent analyzes the downstream effects of proposed changes: what might break,
14
14
 
15
15
  Read `.gsd-t/progress.md`, the relevant domain `tasks.md`, and `docs/architecture.md`/`.gsd-t/contracts/` for the surfaces in scope.
16
16
 
17
+ ## Step 1.5: Graph Structural Slice — blast-radius (M94-D10)
18
+
19
+ **[RULE] impact-uses-blast-radius-not-grep** — the impact agent MUST use the graph CLI
20
+ to answer the downstream-effect (blast-radius) question, NOT grep/raw-read to reconstruct
21
+ dependents.
22
+
23
+ The phase Workflow (`gsd-t-phase.workflow.js`) automatically queries `gsd-t graph blast-radius`
24
+ for the impact phase and injects the pre-computed blast-radius slice into the agent context
25
+ before reasoning begins. The agent receives the structural answer and MUST use it — no grep
26
+ reconstruction of import/call dependents is permitted.
27
+
28
+ **On `graph-unavailable`:** the phase Workflow surfaces a LOUD message:
29
+ ```
30
+ ⚠ graph unavailable — structural blast-radius slice NOT injected (fix it: gsd-t graph status).
31
+ NO grep fallback — structural question unanswered.
32
+ ```
33
+ The impact agent FAILS LOUD on graph-unavailable — it does NOT silently fall back to grep for
34
+ the structural blast-radius question. Run `gsd-t graph status` to diagnose.
35
+
36
+ The `blast-radius` verb returns the downstream impact set as the UNION of the import-graph
37
+ and call-graph reverse-reachable sets from the target (transitive). This catches dependents
38
+ reachable only via call edges that a grep over import patterns would MISS.
39
+
40
+ Graph consumer manifest row: `commands/gsd-t-impact.md | templates/workflows/gsd-t-phase.workflow.js | reader | blast-radius | grep-reconstructed dependent set`
41
+
17
42
  ## Step 2: Resolve the active model profile (M86 — invoke-time injection)
18
43
 
19
44
  Before calling the Workflow, resolve the active model profile to build the `overrides` map:
@@ -25,6 +25,31 @@ It does NOT re-do work that domain workers already did, and does NOT run the ful
25
25
 
26
26
  Read `.gsd-t/progress.md` and verify all required domain workers have completed (status `complete` in their tasks.md). Read `.gsd-t/contracts/m{NN}-integration-points.md` for the shared-file matrix.
27
27
 
28
+ ## Step 1.5: Graph Structural Slice — who-imports + blast-radius (M94-D10, ADDITIVE)
29
+
30
+ **[RULE] integrate-uses-graph-for-wiring-verification** — the integrate Workflow MUST use
31
+ the graph CLI `who-imports` and `blast-radius` verbs to verify cross-domain wiring (real
32
+ import/call edges across the seam), NOT LLM-reconstructed wiring by reading files.
33
+
34
+ **[RULE] verify-integrate-graph-additive-announced-not-hard-fail** — integrate's graph query
35
+ degrades ANNOUNCED on graph-unavailable, NOT as a hard-fail of the entire integrate run.
36
+
37
+ The integrate Workflow (`gsd-t-integrate.workflow.js`) queries `gsd-t graph who-imports` and
38
+ `gsd-t graph blast-radius` to verify that domains are actually wired (real edges exist across
39
+ the cross-domain seam). The integrate agent receives this slice and uses it to confirm
40
+ wiring — rather than reading files and inferring the connection.
41
+
42
+ **On `graph-unavailable`:** the integrate Workflow records a WARNING and CONTINUES its
43
+ cross-domain wire-up work:
44
+ ```
45
+ ⚠ graph unavailable — structural wiring-check skipped, fix it (gsd-t graph status)
46
+ ```
47
+ It does NOT hard-fail integrate solely due to graph unavailability — integrate must always be
48
+ able to run. This is the bootstrap carve-out (PRE-MORTEM Finding 3). Integrate does NOT
49
+ silently grep for the structural wiring question.
50
+
51
+ Graph consumer manifest row (lint-exempt per verify+integrate carve-out): `commands/gsd-t-integrate.md | templates/workflows/gsd-t-integrate.workflow.js | reader | who-imports,blast-radius | LLM-read-reconstructed cross-domain wiring verification`
52
+
28
53
  ## Step 2: Invoke the integrate Workflow
29
54
 
30
55
  Call the `Workflow` tool with: