@tekyzinc/gsd-t 4.14.10 → 4.15.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,16 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.15.10] - 2026-06-30
6
+
7
+ ### Added — scan builds the graph index when absent + repo-labeled `share/` export
8
+
9
+ Two scan improvements, both prompted by a real hilo-figma-atos scan that grep-fell-back and produced same-named docs across projects.
10
+
11
+ - **Scan auto-builds the graph index when absent** (`templates/workflows/gsd-t-scan.workflow.js`). The wired graph path was documented "build index if absent, then query" but the BUILD step was never wired — `GRAPH_BUILD_SCHEMA` was defined and unused. So on any project without a pre-built index (the common case), scan probed `graph status`, saw not-ok, and silently grep-fell-back — the graph was NEVER used. Observed on hilo-figma-atos (a 30M-token scan grep-fell-back purely because the index had never been built). Fix: the wired branch now runs `gsd-t graph index` (new `runCliBuild` helper), re-probes, and only falls back if the build itself fails. This is the headline value of the whole graph arc — scan now engages the graph on first run, not only when a human pre-built it.
12
+ - **Repo-labeled `share/` export + dated archive (#47)** (`templates/workflows/gsd-t-scan.workflow.js`, `commands/gsd-t-scan.md`). The LAST scan step COPIES the living docs + scan reports into `share/<file>-<repo>.md` (e.g. `share/architecture-hilo-figma-atos.md`) so files shared across projects are distinguishable. INTERNAL files keep their fixed names (`.gsd-t/techdebt.md`, `docs/architecture.md`) so all internal tooling (promote-debt, gap-analysis, complete-milestone, …) is untouched — zero blast radius. Prior scan outputs are archived to `.gsd-t/scan/archive/<name>-YYYYMMDD-HHMM.md` for diffing new-vs-prior.
13
+ - `test/m94-d6-scan-consumer.test.js`: regression tests for both (build-when-absent + re-probe; share/ export + fixed internal names + dated archive).
14
+
5
15
  ## [4.14.10] - 2026-06-30
6
16
 
7
17
  ### Added — M99: Graph Observability & Consolidation
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # GSD-T: Contract-Driven Development for Claude Code
2
2
 
3
- **v4.14.10** - A methodology for reliable, parallelizable development using Claude Code with optional Agent Teams support.
3
+ **v4.15.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.
@@ -110,11 +110,11 @@ Present a summary: headline volume totals, findings by severity, the top critica
110
110
 
111
111
  The scan Workflow updates ALL of these deterministically (no manual follow-on):
112
112
 
113
- - `.gsd-t/techdebt.md` — fresh register (synthesis; prior one archived to `.gsd-t/techdebt_YYYY-MM-DD.md`)
113
+ - `.gsd-t/techdebt.md` — fresh register (synthesis; **the prior register + all prior dimension files are archived with a datetime stamp to `.gsd-t/scan/archive/<name>-YYYYMMDD-HHMM.md`** so you can diff new-vs-prior — #47)
114
114
  - `.gsd-t/techdebt_in_plain_english.md` — non-technical companion to the register: every TD item in layman's terms, why it matters, and a real-world analogy (document phase)
115
115
  - `.gsd-t/scan/{architecture,security,quality,business-rules,contract-drift}.md` — dimension analysis files (document phase)
116
- - `docs/architecture.md`, `docs/workflows.md`, `docs/infrastructure.md`, `docs/requirements.md`, `README.md` — living docs, **merged not overwritten** (document phase)
117
- - HTML scan report via `bin/scan-report.js` (render phase)
116
+ - `docs/architecture.md`, `docs/workflows.md`, `docs/infrastructure.md`, `docs/requirements.md`, `README.md` — living docs, **merged not overwritten** (document phase). These keep their fixed names (GSD-T reads them by hardcoded path).
117
+ - **`share/` — team-shareable, repo-name-labeled COPIES (#47):** the LAST scan step copies the living docs + scan reports into `share/` with the project's repo name suffixed (`share/architecture-<repo>.md`, `share/techdebt-<repo>.md`, …). Originals untouched. This solves "which file is for which project?" when sharing across repos — your team reads from `share/`. Regenerated each scan.
118
118
 
119
119
  The document phase + render commit these via git on the feature branch (no push). The lead agent should still add a `.gsd-t/progress.md` Decision Log entry with the scan summary stats.
120
120
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.14.10",
3
+ "version": "4.15.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",
@@ -88,6 +88,7 @@ const PREFLIGHT_SCHEMA = {
88
88
  properties: {
89
89
  ok: { type: "boolean" },
90
90
  branch: { type: "string" },
91
+ repoName: { type: "string", description: "the project directory's basename (e.g. 'hilo-figma-atos') — used to suffix shared scan/doc files" },
91
92
  priorRegisterExists: { type: "boolean" },
92
93
  priorMaxTd: { type: "integer", description: "highest TD-NNN in the prior register, 0 if none" },
93
94
  notes: { type: "string" },
@@ -287,6 +288,29 @@ async function runCli(verb, target, label) {
287
288
  return r || { ok: false, reason: "graph-unavailable", via: "error" };
288
289
  }
289
290
 
291
+ // Build the graph index (`gsd-t graph index`) when it is absent. The wired path
292
+ // is documented "build index if absent, then query" but the BUILD step was never
293
+ // wired — so on any project without a pre-built index (the common case), scan
294
+ // silently grep-fell-back and the graph was NEVER used (observed on hilo-figma-atos
295
+ // 2026-06-30: a 30M-token scan grep-fell-back because the index was never built).
296
+ // `graph index` is a longer command than the verb queries → bigger timeout.
297
+ // [RULE] scan-builds-index-when-absent
298
+ async function runCliBuild() {
299
+ const prompt = [
300
+ `Build the GSD-T code-graph index for the project at \`${projectDir}\`, then report. Steps:`,
301
+ `1. If \`${projectDir}/bin/gsd-t.js\` exists, run: \`node ${projectDir}/bin/gsd-t.js graph index\` (via="local"). Otherwise run: \`gsd-t graph index\` (via="global"). Use cwd \`${projectDir}\`. This may take up to a few minutes on a large repo — wait for it to finish. Do NOT redirect stderr.`,
302
+ `2. Set ok=true if the command exits 0 (the index built). Set ok=false with a short reason + first ~200 chars of stderr otherwise.`,
303
+ `Do NOT do any other work. ONLY run this one build command and report the structured result.`,
304
+ ].join("\n");
305
+ const r = await agent(prompt, {
306
+ label: "graph:index-build",
307
+ phase: "Graph-Wiring",
308
+ model: "haiku",
309
+ schema: GRAPH_BUILD_SCHEMA,
310
+ }).catch((e) => ({ ok: false, reason: `agent-error: ${e && e.message}` }));
311
+ return r || { ok: false, reason: "build-failed" };
312
+ }
313
+
290
314
  // M99 D2: persist a kind:'wiring' ledger line for this workflow.
291
315
  // M81 sandbox: all I/O through agent() Bash; no require/fs in the sandbox.
292
316
  // Uses the `gsd-t graph wiring-log` CLI shim (avoids embedding require() in strings).
@@ -312,8 +336,9 @@ const pre = await agent(
312
336
  `You are the preflight check for a GSD-T deep scan of the project at \`${projectDir}\`.`,
313
337
  `Using Bash/Read tools, determine:`,
314
338
  `1. The current git branch (\`git -C ${projectDir} rev-parse --abbrev-ref HEAD\`; if not a git repo, report branch "(no-git)").`,
315
- `2. Whether \`${projectDir}/.gsd-t/techdebt.md\` exists (priorRegisterExists).`,
316
- `3. If it exists, the HIGHEST TD-NNN number in it (grep \`### TD-\`, parse the max integer; priorMaxTd). If absent, priorMaxTd=0.`,
339
+ `2. The repo name = the basename of the resolved project dir (\`basename "$(cd ${projectDir} && pwd)"\`) — e.g. "hilo-figma-atos". Report as repoName (used to label the team-shared share/ copies).`,
340
+ `3. Whether \`${projectDir}/.gsd-t/techdebt.md\` exists (priorRegisterExists).`,
341
+ `4. If it exists, the HIGHEST TD-NNN number in it (grep \`### TD-\`, parse the max integer; priorMaxTd). If absent, priorMaxTd=0.`,
317
342
  `Set ok=true unless something makes scanning impossible (e.g. projectDir does not exist). Return JSON per the schema.`,
318
343
  ].join("\n"),
319
344
  { label: "preflight", phase: "Preflight", schema: PREFLIGHT_SCHEMA, model: "haiku" }
@@ -323,7 +348,12 @@ if (!pre || !pre.ok) {
323
348
  return { status: "failed", reason: "preflight-failed", preflight: pre };
324
349
  }
325
350
  const tdStart = (pre.priorRegisterExists ? (pre.priorMaxTd || 0) : 0) + 1;
326
- log(`preflight ok branch=${pre.branch}, priorRegister=${pre.priorRegisterExists}, TD numbering starts at TD-${tdStart}`);
351
+ // #47: repo-name suffix for the TEAM-SHARED copies only. INTERNAL files keep their
352
+ // fixed names (.gsd-t/techdebt.md, .gsd-t/scan/<dim>.md, docs/*.md) so all internal
353
+ // tooling (promote-debt, gap-analysis, complete-milestone, …) keeps working — zero
354
+ // blast radius. The repo suffix appears ONLY on the share/ exports at the end.
355
+ const repoName = (pre.repoName && /^[A-Za-z0-9._-]+$/.test(pre.repoName)) ? pre.repoName : "project";
356
+ log(`preflight ok — branch=${pre.branch}, repo=${repoName}, priorRegister=${pre.priorRegisterExists}, TD numbering starts at TD-${tdStart}`);
327
357
 
328
358
  // Volume probe — an agent measures the codebase (its own Bash) and carves slices.
329
359
  phase("Probe");
@@ -376,12 +406,26 @@ if (graphMode === "disabled") {
376
406
  } else {
377
407
  // graphMode === "wired": build index if absent, then query structural slice.
378
408
  // Step 1: check if index exists and is queryable.
379
- const statusResult = await runCli("status", null, "status");
409
+ let statusResult = await runCli("status", null, "status");
410
+ // Step 1b: if the index is absent/unqueryable, BUILD it once, then re-probe.
411
+ // This is the previously-missing build step — without it scan grep-fell-back on
412
+ // every project lacking a pre-built index (hilo-figma-atos 2026-06-30).
413
+ // [RULE] scan-builds-index-when-absent
380
414
  if (!statusResult || !statusResult.ok) {
381
- // Graph unavailable — announce fallback, continue with intact grep-mode scan.
382
- // [RULE] parser-fail-disables-loud-never-silent (from graph-query-cli-contract)
415
+ log(`graph-wiring: index not queryable (${(statusResult && statusResult.reason) || "graph-unavailable"})building it now (gsd-t graph index)...`);
416
+ const buildResult = await runCliBuild();
417
+ if (buildResult && buildResult.ok) {
418
+ log(`graph-wiring: index build OK — re-probing status.`);
419
+ statusResult = await runCli("status", null, "status");
420
+ } else {
421
+ log(`graph-wiring: index build FAILED [${(buildResult && buildResult.reason) || "build-failed"}] — falling back to grep-mode.`);
422
+ }
423
+ }
424
+ if (!statusResult || !statusResult.ok) {
425
+ // Graph still unavailable after a build attempt — announce fallback, continue
426
+ // with intact grep-mode scan. [RULE] parser-fail-disables-loud-never-silent
383
427
  graphWiringMode = "fallback-announced";
384
- log(`⚠ GRAPH-FALLBACK (ANNOUNCED): graph status probe returned not-ok [reason=${(statusResult && statusResult.reason) || "graph-unavailable"}, via=${(statusResult && statusResult.via) || "?"}] — scan continues in full grep-mode (today's architecture, intact). Structural findings from LLM reconstruction only. Build the index (gsd-t graph build) to enable graph-wired accuracy.`);
428
+ log(`⚠ GRAPH-FALLBACK (ANNOUNCED): graph unavailable after build attempt [reason=${(statusResult && statusResult.reason) || "graph-unavailable"}, via=${(statusResult && statusResult.via) || "?"}] — scan continues in full grep-mode (today's architecture, intact). Structural findings from LLM reconstruction only.`);
385
429
  await persistWiringMode("fallback-announced"); // M99 D2 [RULE] wiring-mode-three-states
386
430
  } else {
387
431
  // Step 2: query the structural slice (dead-code + dangling + clusters).
@@ -799,10 +843,15 @@ const today = (typeof todayAgent === "string" && /\d{4}-\d{2}-\d{2}/.test(todayA
799
843
 
800
844
  let archivePath = "";
801
845
  if (pre.priorRegisterExists) {
846
+ // #47: archive the prior register + ALL prior dimension files into
847
+ // .gsd-t/scan/archive/ with a DATETIME stamp so the user can diff new-vs-prior.
848
+ // Handles BOTH the new suffixed names and any legacy unsuffixed leftovers.
802
849
  const arch = await agent(
803
850
  [
804
- `Archive the existing tech-debt register in \`${projectDir}\` via Bash, then report the archive path.`,
805
- `Rename \`${projectDir}/.gsd-t/techdebt.md\` to \`${projectDir}/.gsd-t/techdebt_${today}.md\`; if that exists, append _2/_3. Use \`git mv\` if a git repo else \`mv\`. Reply with ONLY the resulting archive filename.`,
851
+ `Archive the existing scan outputs in \`${projectDir}\` into a dated archive folder so the user can diff new-vs-prior, then report the archive dir. Steps (via Bash):`,
852
+ `1. STAMP="$(date +%Y%m%d-%H%M)". ARCH="${projectDir}/.gsd-t/scan/archive". mkdir -p "$ARCH".`,
853
+ `2. Move each existing scan output into "$ARCH" with the STAMP appended before .md. For EACH that exists, move it: \`${projectDir}/.gsd-t/techdebt.md\`, \`${projectDir}/.gsd-t/techdebt_in_plain_english.md\`, and \`${projectDir}/.gsd-t/scan/<dim>.md\` for each dim in {architecture,security,quality,business-rules,contract-drift}. Target name: same basename + "-$STAMP.md" (e.g. techdebt-20260630-1542.md). Use \`git mv\` if a git repo else \`mv\`. Skip any that don't exist (no error).`,
854
+ `3. Reply with ONLY the archive dir path "$ARCH".`,
806
855
  ].join("\n"),
807
856
  { label: "synthesis:archive", phase: "Synthesis", model: "haiku" }
808
857
  ).catch(() => null);
@@ -815,7 +864,7 @@ const { chunks, lastTd } = fmtChunks(today);
815
864
  // each subsequent chunk is APPENDED. Done SEQUENTIALLY so the file builds in order and
816
865
  // each agent's prompt+output stays small enough to pass intact. The register path is
817
866
  // passed to each chunk; only the chunk content varies.
818
- const regPath = `${projectDir}/.gsd-t/techdebt.md`;
867
+ const regPath = `${projectDir}/.gsd-t/techdebt.md`; // internal fixed name (shared copy suffixed in share/)
819
868
  let chunkOk = 0;
820
869
  for (let ci = 0; ci < chunks.length; ci++) {
821
870
  const isFirst = ci === 0;
@@ -933,7 +982,7 @@ log(`document phase: ${docsOk.length}/${docTargets.length} written/merged${docsF
933
982
  // out bounded generator agents (each writes its batch's entries via the shared gate),
934
983
  // then ASSEMBLE deterministically with severity section headers, and chunk-write.
935
984
  phase("Plain-English");
936
- const peTarget = `${projectDir}/.gsd-t/techdebt_in_plain_english.md`;
985
+ const peTarget = `${projectDir}/.gsd-t/techdebt_in_plain_english.md`; // internal fixed name (shared copy suffixed in share/)
937
986
  const sevLabel = { CRITICAL: "fix before launch", HIGH: "fix soon", MEDIUM: "schedule", LOW: "clean up eventually" };
938
987
  // Attach the deterministic TD number (matches the register: severity-sorted, tdStart+).
939
988
  const peItems = finalFindings.map((f, i) => ({
@@ -1023,11 +1072,27 @@ const peComplete = peActual === peExpectedEntries;
1023
1072
  if (!peComplete) log(`⚠ plain-english INCOMPLETE: wrote ${peActual}/${peExpectedEntries} entries (writer said ${typeof peWriteRes === "string" ? peWriteRes.trim().slice(0, 20) : JSON.stringify(peWriteRes).slice(0, 40)})`);
1024
1073
  log(`plain-english: ${peExpectedEntries}/${peItems.length} entries, grouped by severity, ${peChunks.length} chunks, on-disk count ${peActual ?? "?"} (${peComplete ? "COMPLETE" : "INCOMPLETE"})${peFailed ? `; ${peFailed} gen batch(es) failed` : ""}`);
1025
1074
 
1026
- // Commit the docs + dimension files + plain-english via a small agent (Bash git).
1075
+ // #47: EXPORT-COPY the living docs into a share/ folder with the repo-name suffix
1076
+ // the LAST scan step. Originals stay at their fixed docs/ names (GSD-T reads them by
1077
+ // hardcoded path under the No-Re-Research rule); the share/ copies are the team-shareable,
1078
+ // project-labeled set. Regenerated each scan (overwrite). The scan reports themselves are
1079
+ // already suffixed in place (techdebt-<repo>.md, scan/<dim>-<repo>.md).
1080
+ const shareAgent = await agent(
1081
+ [
1082
+ `Create a shareable, repo-labeled copy of this project's living docs in \`${projectDir}\` via Bash. Steps:`,
1083
+ `1. \`mkdir -p ${projectDir}/share\`.`,
1084
+ `2. COPY (do NOT move — keep the original at its fixed name) each of these that EXISTS into \`${projectDir}/share/\` with the repo name "${repoName}" suffixed before .md. Living docs: \`docs/architecture.md\`→\`share/architecture-${repoName}.md\`, \`docs/requirements.md\`→\`share/requirements-${repoName}.md\`, \`docs/workflows.md\`→\`share/workflows-${repoName}.md\`, \`docs/infrastructure.md\`→\`share/infrastructure-${repoName}.md\`, \`README.md\`→\`share/README-${repoName}.md\`. Scan reports: \`.gsd-t/techdebt.md\`→\`share/techdebt-${repoName}.md\`, \`.gsd-t/techdebt_in_plain_english.md\`→\`share/techdebt_in_plain_english-${repoName}.md\`, and each \`.gsd-t/scan/<dim>.md\`→\`share/<dim>-${repoName}.md\` for dim in {architecture,security,quality,business-rules,contract-drift} (NOTE these scan dimension files share basenames with the living-doc architecture — keep them distinct: prefer \`share/scan-<dim>-${repoName}.md\` for the .gsd-t/scan/ ones to avoid colliding with docs/architecture). Overwrite existing share/ files (always fresh). Skip any source that doesn't exist (no error).`,
1085
+ `3. Reply with ONLY the count of files copied.`,
1086
+ ].join("\n"),
1087
+ { label: "share-export", phase: "Document", model: "haiku" }
1088
+ ).catch(() => null);
1089
+ log(`share/ export: ${typeof shareAgent === "string" ? shareAgent.trim().slice(0, 40) : "done"} → ${projectDir}/share/`);
1090
+
1091
+ // Commit the docs + dimension files + plain-english + share/ via a small agent (Bash git).
1027
1092
  const commitAgent = await agent(
1028
1093
  [
1029
1094
  `Commit the GSD-T scan's generated documents in \`${projectDir}\` via Bash git, IF it is a git repo (else report skipped).`,
1030
- `Stage: \`.gsd-t/scan\`, \`.gsd-t/techdebt_in_plain_english.md\`, \`docs\`, \`README.md\` (do NOT stage \`.gsd-t/scan/.doc-backup\` if present). Commit message: "scan: deep document cross-population (${docsOk.length} docs) + dimension files". Do NOT push. Return JSON per the schema (status "rendered" if committed, "skipped" if not a git repo / nothing to commit, "failed" on error; outputPath optional).`,
1095
+ `Stage: \`.gsd-t/scan\`, \`.gsd-t/techdebt.md\`, \`.gsd-t/techdebt_in_plain_english.md\`, \`share\`, \`docs\`, \`README.md\` (do NOT stage \`.gsd-t/scan/.doc-backup\` if present; \`.gsd-t/scan/archive\` MAY be staged — the dated history is worth keeping). Commit message: "scan: deep document cross-population (${docsOk.length} docs) + dimension files + share/ export". Do NOT push. Return JSON per the schema (status "rendered" if committed, "skipped" if not a git repo / nothing to commit, "failed" on error; outputPath optional).`,
1031
1096
  ].join("\n"),
1032
1097
  { label: "commit-docs", phase: "Document", schema: RENDER_SCHEMA, model: "haiku" }
1033
1098
  ).catch((e) => ({ status: "failed", notes: String(e && e.message) }));