@tekyzinc/gsd-t 4.11.11 → 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,18 @@
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
+
5
17
  ## [4.11.11] - 2026-06-27
6
18
 
7
19
  ### Fixed — SCIP resolution missed nested/monorepo tsconfig (call-graph empty on subdir-tsconfig projects)
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v4.11.11** - 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.
@@ -146,16 +146,53 @@ function runScipTypescript(projectRoot, outPath) {
146
146
  return { ok: false, error: scip.stderr || `exit code ${scip.status}` };
147
147
  }
148
148
 
149
- function runScipPython(projectRoot) {
150
- 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, '.'], {
151
158
  cwd: projectRoot,
152
159
  encoding: 'utf8',
153
- timeout: 120_000,
160
+ timeout: 180_000,
154
161
  });
155
- if (scip.status === 0) return { ok: true };
162
+ if (scip.status === 0) return { ok: true, scipPath: out };
156
163
  return { ok: false, error: scip.stderr || `exit code ${scip.status}` };
157
164
  }
158
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
+
159
196
  function runScipRust(projectRoot) {
160
197
  // rust-analyzer + scip: run `scip` (the rust-analyzer scip bridge) if available
161
198
  // As with TypeScript, Phase-1 is tier-labelling only
@@ -218,32 +255,45 @@ function buildScipResolver(repoRoot, opts = {}) {
218
255
  const { readScipIndex } = require('./gsd-t-scip-reader.cjs');
219
256
  const avail = detectScip();
220
257
 
221
- // Phase-1 of M95: TypeScript/JavaScript only (scip-typescript). Python is a
222
- // sequenced follow-on. If TS SCIP is absent, the resolver is a no-op (floor).
223
- if (!avail.typescript) {
224
- 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
+ }
225
274
  }
226
275
 
227
- const scipPath = path.join(repoRoot, '.gsd-t', 'index.scip');
228
- let run;
229
- try {
230
- run = runScipTypescript(repoRoot, scipPath);
231
- } catch (e) {
232
- 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 */ }
233
281
  }
234
- if (!run || !run.ok) {
235
- 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 */ }
236
287
  }
237
288
 
238
- const read = readScipIndex(run.scipPath);
239
- if (!read.ok) {
240
- 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 };
241
295
  }
242
296
 
243
- // Build a fast per-file callee lookup: for a given test/impl file, the set of
244
- // funcIds it references (resolved). Used to rewrite UNRESOLVED# call edges.
245
- const fileRefs = read.fileRefs; // relPath → [{symbol, funcId, line}]
246
-
247
297
  /**
248
298
  * Resolve a single file's call edges. For each CALL edge whose dst is
249
299
  * UNRESOLVED#<name>, if SCIP found a reference to <name> in this file that
@@ -276,7 +326,7 @@ function buildScipResolver(repoRoot, opts = {}) {
276
326
  return { edges: out, resolved };
277
327
  }
278
328
 
279
- return { ok: true, scipPath: run.scipPath, resolveFileEdges };
329
+ return { ok: true, indexers: ranIndexers, scipPath: path.join(repoRoot, '.gsd-t', 'index.scip'), resolveFileEdges };
280
330
  }
281
331
 
282
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.11",
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",