@tekyzinc/gsd-t 4.11.10 → 4.12.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.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,28 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.12.10] - 2026-06-27
6
+
7
+ ### Added — Python call-graph resolution (scip-python)
8
+
9
+ The precise call-graph now covers Python, not just TS/JS. `buildScipResolver` runs scip-python alongside scip-typescript and merges both resolution maps — a repo with both languages gets one unified call-graph.
10
+
11
+ - `bin/gsd-t-graph-scip-upgrade.cjs`: run scip-python when Python source is present; merge its `fileRefs` into the resolver. New `detectRepoLanguages()` skips an indexer when its language is absent (no wasted run). `runScipPython` now emits to a real `.gsd-t/index-python.scip` and passes `--project-name` + `--project-version 0.0.0` (scip-python crashes on an undefined version).
12
+ - The SCIP reader was already language-agnostic (Python symbols use the same `name().` descriptor form), so cross-file Python calls resolve with no reader change.
13
+ - `gsd-t install` / `gsd-t doctor` already covered scip-python (added in M95/M96).
14
+
15
+ Proven on BDS (35 .py): 125 → 1,331 resolved calls (1,206 Python); `who-calls` returns real Python callers. Fixture test resolves a cross-file `pkg.util.compute_total` call. Suite: 2521/2521 pass.
16
+
17
+ ## [4.11.11] - 2026-06-27
18
+
19
+ ### Fixed — SCIP resolution missed nested/monorepo tsconfig (call-graph empty on subdir-tsconfig projects)
20
+
21
+ `runScipTypescript` ran scip-typescript without `--infer-tsconfig`, so projects whose `tsconfig.json` lives in a subdirectory (`web/tsconfig.json`, monorepo packages) produced 0 resolved call edges — scip-typescript found no root tsconfig and skipped resolution.
22
+
23
+ - `bin/gsd-t-graph-scip-upgrade.cjs`: add `--infer-tsconfig` so scip-typescript discovers a nested tsconfig; bumped the timeout to 180s for larger inferred projects.
24
+
25
+ Proven on ClaudeWebCLI (tsconfig at `web/tsconfig.json`): 0 → 1,149 resolved call edges. Suite: 2519/2519 pass.
26
+
5
27
  ## [4.11.10] - 2026-06-27
6
28
 
7
29
  ### Added — M97: the code graph is the default for ambient code-reading (grep-intercept), + 3 call-resolution fixes
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v4.11.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v4.12.10** - 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.
@@ -134,25 +134,65 @@ function runScipTypescript(projectRoot, outPath) {
134
134
  // M95: emit to a REAL file (outPath) so the index can be READ and call edges
135
135
  // resolved — not /dev/null. The old /dev/null path only proved invocability.
136
136
  const out = outPath || path.join(projectRoot, '.gsd-t', 'index.scip');
137
- const scip = spawnSync('scip-typescript', ['index', '--output', out, '.'], {
137
+ // --infer-tsconfig: resolve a tsconfig even when one isn't at the repo root
138
+ // (monorepos / nested layouts — e.g. web/tsconfig.json). Without it, projects
139
+ // whose tsconfig lives in a subdir got 0 resolved call edges. [RULE] scip-infer-nested-tsconfig
140
+ const scip = spawnSync('scip-typescript', ['index', '--infer-tsconfig', '--output', out, '.'], {
138
141
  cwd: projectRoot,
139
142
  encoding: 'utf8',
140
- timeout: 120_000,
143
+ timeout: 180_000,
141
144
  });
142
145
  if (scip.status === 0) return { ok: true, scipPath: out };
143
146
  return { ok: false, error: scip.stderr || `exit code ${scip.status}` };
144
147
  }
145
148
 
146
- function runScipPython(projectRoot) {
147
- const scip = spawnSync('scip-python', ['index', '.'], {
149
+ function runScipPython(projectRoot, outPath) {
150
+ // Emit to a REAL file (separate from the TS index) so it can be READ + merged.
151
+ // --project-name is required-ish for stable symbols; derive from the dir name.
152
+ const out = outPath || path.join(projectRoot, '.gsd-t', 'index-python.scip');
153
+ const projName = path.basename(projectRoot).replace(/[^A-Za-z0-9_-]/g, '-') || 'project';
154
+ // scip-python REQUIRES a project version — it crashes (makeModuleInit) when the
155
+ // version is undefined (no pyproject.toml). Pass an explicit fallback version.
156
+ const scip = spawnSync('scip-python',
157
+ ['index', '--project-name', projName, '--project-version', '0.0.0', '--output', out, '.'], {
148
158
  cwd: projectRoot,
149
159
  encoding: 'utf8',
150
- timeout: 120_000,
160
+ timeout: 180_000,
151
161
  });
152
- if (scip.status === 0) return { ok: true };
162
+ if (scip.status === 0) return { ok: true, scipPath: out };
153
163
  return { ok: false, error: scip.stderr || `exit code ${scip.status}` };
154
164
  }
155
165
 
166
+ // Cheap shallow language detection — does the repo contain TS/JS or Python source
167
+ // (excluding vendored/build dirs)? Used to skip an indexer when its language is
168
+ // absent. Walks at most a bounded depth to stay fast.
169
+ function detectRepoLanguages(repoRoot) {
170
+ const SKIP = new Set(['node_modules', '.git', 'dist', 'build', 'out', '.venv', 'venv',
171
+ 'site-packages', '__pycache__', '.next', 'coverage', 'Pods', '.dart_tool', 'vendor']);
172
+ let typescript = false, python = false;
173
+ function isSkip(name) {
174
+ if (SKIP.has(name)) return true;
175
+ return ['dist', 'build', 'out'].some(p => name.length > p.length && name.startsWith(p) &&
176
+ (name[p.length] === '-' || name[p.length] === '.' || name[p.length] === '_'));
177
+ }
178
+ function walk(dir, depth) {
179
+ if ((typescript && python) || depth > 6) return;
180
+ let entries;
181
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); } catch { return; }
182
+ for (const e of entries) {
183
+ if (typescript && python) return;
184
+ if (e.isDirectory()) { if (!isSkip(e.name)) walk(path.join(dir, e.name), depth + 1); }
185
+ else if (e.isFile()) {
186
+ const ext = path.extname(e.name).toLowerCase();
187
+ if (ext === '.ts' || ext === '.tsx' || ext === '.js' || ext === '.jsx' || ext === '.mjs' || ext === '.cjs') typescript = true;
188
+ else if (ext === '.py') python = true;
189
+ }
190
+ }
191
+ }
192
+ walk(repoRoot, 0);
193
+ return { typescript, python };
194
+ }
195
+
156
196
  function runScipRust(projectRoot) {
157
197
  // rust-analyzer + scip: run `scip` (the rust-analyzer scip bridge) if available
158
198
  // As with TypeScript, Phase-1 is tier-labelling only
@@ -215,32 +255,45 @@ function buildScipResolver(repoRoot, opts = {}) {
215
255
  const { readScipIndex } = require('./gsd-t-scip-reader.cjs');
216
256
  const avail = detectScip();
217
257
 
218
- // Phase-1 of M95: TypeScript/JavaScript only (scip-typescript). Python is a
219
- // sequenced follow-on. If TS SCIP is absent, the resolver is a no-op (floor).
220
- if (!avail.typescript) {
221
- return { ok: false, reason: 'scip-typescript-absent', resolveFileEdges: passthroughResolver };
258
+ // Detect which languages have source present (so we only run the relevant
259
+ // indexer). Cheap shallow check: does the repo contain .ts/.tsx/.js or .py?
260
+ const langs = detectRepoLanguages(repoRoot);
261
+
262
+ // Run each available+relevant indexer, merge their resolution maps. The reader
263
+ // is language-agnostic (Python symbols use the same `name().` descriptor form),
264
+ // so TS and Python refs merge into one fileRefs map keyed by repo-relative path.
265
+ const fileRefs = new Map(); // relPath → [{symbol, funcId, line}]
266
+ const ranIndexers = [];
267
+
268
+ function mergeRead(read) {
269
+ if (!read || !read.ok) return;
270
+ for (const [file, refs] of read.fileRefs) {
271
+ if (fileRefs.has(file)) fileRefs.get(file).push(...refs);
272
+ else fileRefs.set(file, refs.slice());
273
+ }
222
274
  }
223
275
 
224
- const scipPath = path.join(repoRoot, '.gsd-t', 'index.scip');
225
- let run;
226
- try {
227
- run = runScipTypescript(repoRoot, scipPath);
228
- } catch (e) {
229
- return { ok: false, reason: `scip-run-threw: ${e.message}`, resolveFileEdges: passthroughResolver };
276
+ if (avail.typescript && langs.typescript) {
277
+ try {
278
+ const run = runScipTypescript(repoRoot, path.join(repoRoot, '.gsd-t', 'index.scip'));
279
+ if (run && run.ok) { mergeRead(readScipIndex(run.scipPath)); ranIndexers.push('typescript'); }
280
+ } catch { /* degrade to floor for TS */ }
230
281
  }
231
- if (!run || !run.ok) {
232
- return { ok: false, reason: run ? run.error : 'scip-run-failed', resolveFileEdges: passthroughResolver };
282
+ if (avail.python && langs.python) {
283
+ try {
284
+ const run = runScipPython(repoRoot, path.join(repoRoot, '.gsd-t', 'index-python.scip'));
285
+ if (run && run.ok) { mergeRead(readScipIndex(run.scipPath)); ranIndexers.push('python'); }
286
+ } catch { /* degrade to floor for Python */ }
233
287
  }
234
288
 
235
- const read = readScipIndex(run.scipPath);
236
- if (!read.ok) {
237
- return { ok: false, reason: read.reason, resolveFileEdges: passthroughResolver };
289
+ if (!ranIndexers.length) {
290
+ // No applicable indexer present/ran → floor mode.
291
+ const reason = (!avail.typescript && !avail.python) ? 'scip-indexers-absent'
292
+ : (!langs.typescript && !langs.python) ? 'no-indexable-source'
293
+ : 'scip-run-failed';
294
+ return { ok: false, reason, resolveFileEdges: passthroughResolver };
238
295
  }
239
296
 
240
- // Build a fast per-file callee lookup: for a given test/impl file, the set of
241
- // funcIds it references (resolved). Used to rewrite UNRESOLVED# call edges.
242
- const fileRefs = read.fileRefs; // relPath → [{symbol, funcId, line}]
243
-
244
297
  /**
245
298
  * Resolve a single file's call edges. For each CALL edge whose dst is
246
299
  * UNRESOLVED#<name>, if SCIP found a reference to <name> in this file that
@@ -273,7 +326,7 @@ function buildScipResolver(repoRoot, opts = {}) {
273
326
  return { edges: out, resolved };
274
327
  }
275
328
 
276
- return { ok: true, scipPath: run.scipPath, resolveFileEdges };
329
+ return { ok: true, indexers: ranIndexers, scipPath: path.join(repoRoot, '.gsd-t', 'index.scip'), resolveFileEdges };
277
330
  }
278
331
 
279
332
  /** No-op resolver used when SCIP is unavailable (floor mode). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.11.10",
3
+ "version": "4.12.10",
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",