@tekyzinc/gsd-t 4.0.25 → 4.0.26

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,14 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.0.26] - 2026-06-03 (M78 Plain-English Grouped + Batched - patch)
6
+
7
+ ### Fixed - plain-english companion was a flat ungrouped list + would stall on large registers
8
+
9
+ The non-technical companion (techdebt_in_plain_english.md) was generated by a SINGLE doc agent: it produced a flat list with no severity grouping, and would stall writing 300+ entries (the M75 register bug, unfixed for this doc). M78 gives it the register treatment: a dedicated Plain-English phase batches the (severity-sorted) findings, fans out bounded generator agents (shared concurrency gate), then assembles DETERMINISTICALLY with severity section headers (## Critical / High / Medium / Low) and chunk-writes. Removed from docTargets (no longer a single agent). +test/m78-plain-english-grouping.test.js (3 tests: grouped+complete+ordered, no mid-item chunk split, empty-severity omission). Also dropped the stale Render phase from meta.phases (removed back in M71). One-off: regrouped the existing Hilo plain-english doc by authoritative severity.
10
+
11
+ Suite: 1318 pass / 0 fail / 4 skip.
12
+
5
13
  ## [4.0.25] - 2026-06-03 (M77 HTML Report Reads Deep-Scan Table Format - patch)
6
14
 
7
15
  ### Fixed - scan report showed 0 critical/0 high on a 322-finding register
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.0.25",
3
+ "version": "4.0.26",
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",
@@ -31,8 +31,8 @@ export const meta = {
31
31
  { title: "Probe", detail: "volume probe → per-area slice list", model: "sonnet" },
32
32
  { title: "Deep Scan", detail: "pipeline: per-slice deep finder → single verify" },
33
33
  { title: "Synthesis", detail: "archive prior + write fresh register + git", model: "opus" },
34
- { title: "Document", detail: "living docs + 5 dimension files + plain-english (per-doc fan-out)" },
35
- { title: "Render", detail: "HTML scan report via gsd-t bin renderers (agent via Bash)" },
34
+ { title: "Document", detail: "living docs + 5 dimension files (per-doc fan-out)" },
35
+ { title: "Plain-English", detail: "non-technical companion: batched gen + severity-grouped chunked write" },
36
36
  ],
37
37
  };
38
38
 
@@ -645,8 +645,10 @@ const docTargets = [
645
645
  prompt: `Update or create \`${projectDir}/docs/requirements.md\`: functional requirements from routes/handlers/UI; technical from configs/package.json/runtime; non-functional from perf configs/rate limits/caching.` },
646
646
  { id: "readme", label: "README.md", merge: true,
647
647
  prompt: `Update or create \`${projectDir}/README.md\`: project name+description; tech stack+versions; getting-started/setup; brief architecture overview; link to docs/. If it exists, MERGE — preserve the user's structure/custom content.` },
648
- { id: "techdebt-plain-english", label: ".gsd-t/techdebt_in_plain_english.md", needsRegister: true,
649
- prompt: `Write \`${projectDir}/.gsd-t/techdebt_in_plain_english.md\` a NON-TECHNICAL companion to the register for a non-engineer (founder/PM/stakeholder). FIRST read \`${projectDir}/.gsd-t/techdebt.md\` (Read tool) to get the EXACT TD-NNN ids/order — it was just written and IS the source of truth (the findings JSON has no TD ids). Cover EVERY item, one entry each: \`### TD-NNN — <plain-English name>\`; **What it is** (no jargon; define any unavoidable term in parentheses); **Why it matters** (business/user consequence); **Real-world analogy** (a concrete everyday comparison that genuinely maps to THIS item); **Severity in plain terms** (CRITICAL/HIGH/MEDIUM/LOW → "fix before launch"/"schedule soon"/"clean up eventually"). Open with a 2-3 sentence plain-English health summary + headline counts.` },
648
+ // NOTE (M78): the plain-english companion is NOT in docTargets — a single agent
649
+ // stalls writing 300+ entries (the M75 register bug). It has its own dedicated phase
650
+ // below: batched generation (bounded fan-out) + deterministic severity-grouped,
651
+ // chunked write.
650
652
  ];
651
653
 
652
654
  const docResults = await parallel(
@@ -674,6 +676,85 @@ const docsOk = docResults.filter(Boolean).filter((r) => r.status === "written" |
674
676
  const docsFailed = docResults.filter(Boolean).filter((r) => r.status === "failed");
675
677
  log(`document phase: ${docsOk.length}/${docTargets.length} written/merged${docsFailed.length ? `; ${docsFailed.length} failed (non-fatal): ${docsFailed.map((d) => d.doc).join(", ")}` : ""}`);
676
678
 
679
+ // ─── Plain-English phase (M78) ───────────────────────────────────────────────
680
+ // The non-technical companion can be 300+ entries — a single agent stalls writing
681
+ // it (the M75 register bug). So: batch the (already severity-sorted) findings, fan
682
+ // out bounded generator agents (each writes its batch's entries via the shared gate),
683
+ // then ASSEMBLE deterministically with severity section headers, and chunk-write.
684
+ phase("Plain-English");
685
+ const peTarget = `${projectDir}/.gsd-t/techdebt_in_plain_english.md`;
686
+ const sevLabel = { CRITICAL: "fix before launch", HIGH: "fix soon", MEDIUM: "schedule", LOW: "clean up eventually" };
687
+ // Attach the deterministic TD number (matches the register: severity-sorted, tdStart+).
688
+ const peItems = finalFindings.map((f, i) => ({
689
+ td: tdStart + i, severity: f.severity, title: ascii(f.title),
690
+ area: ascii(f.area), detail: ascii(f.detail || f.impact || "").slice(0, 500),
691
+ }));
692
+ const PE_BATCH = 36;
693
+ const peBatches = [];
694
+ for (let i = 0; i < peItems.length; i += PE_BATCH) peBatches.push(peItems.slice(i, i + PE_BATCH));
695
+ const PE_SCHEMA = { type: "object", required: ["entries"], additionalProperties: false,
696
+ properties: { entries: { type: "array", items: { type: "object", required: ["td", "markdown"], properties: { td: { type: "integer" }, markdown: { type: "string" } } } } } };
697
+
698
+ const peResults = await parallel(peBatches.map((batch, bi) => async () => {
699
+ const prompt = [
700
+ `Write NON-TECHNICAL ("plain English") companion entries for a tech-debt register, for a non-engineer stakeholder (founder/PM).`,
701
+ `For EACH finding below, produce one entry. Return JSON {entries:[{td, markdown}]} where markdown is EXACTLY:`,
702
+ `### TD-<td> - <plain-English name, no jargon>`,
703
+ `**What it is.** <1-2 sentences, no jargon; define any unavoidable term in parentheses>`,
704
+ `**Why it matters.** <business/user consequence>`,
705
+ `**Real-world analogy.** <a concrete everyday comparison that genuinely maps to THIS issue>`,
706
+ `**Severity.** <the plain-urgency phrase given per item>`,
707
+ `Keep the td number EXACTLY. ASCII punctuation only (hyphens, straight quotes — NO em-dashes/smart-quotes/ellipsis). No preamble.`,
708
+ ``,
709
+ `Findings (batch ${bi + 1}/${peBatches.length}):`,
710
+ "```json",
711
+ JSON.stringify(batch.map((it) => ({ ...it, severityPhrase: sevLabel[it.severity] || "review" }))),
712
+ "```",
713
+ ].join("\n");
714
+ try {
715
+ const r = await gatedAgent(prompt, { label: `plain-english ${bi + 1}/${peBatches.length}`, phase: "Plain-English", schema: PE_SCHEMA, model: "sonnet" });
716
+ return { bi, entries: (r && Array.isArray(r.entries)) ? r.entries : [] };
717
+ } catch (e) { return { bi, entries: [], failed: true }; }
718
+ }));
719
+ // Map td -> entry markdown, then assemble grouped by severity (deterministic headers).
720
+ const peByTd = {};
721
+ for (const r of peResults) for (const e of (r.entries || [])) if (e && e.td != null) peByTd[e.td] = ascii(e.markdown || "");
722
+ const peFailed = peResults.filter((r) => r.failed).length;
723
+ const peGroups = { CRITICAL: [], HIGH: [], MEDIUM: [], LOW: [] };
724
+ for (const it of peItems) { const md = peByTd[it.td]; if (md && peGroups[it.severity]) peGroups[it.severity].push(md.trim()); }
725
+ const peSevHead = { CRITICAL: "## 🔴 Critical", HIGH: "## 🟠 High", MEDIUM: "## 🟡 Medium", LOW: "## 🟢 Low" };
726
+ const peHeader = [
727
+ `# Tech Debt - Plain English`, "",
728
+ `> Non-technical companion to .gsd-t/techdebt.md (Scan${scanNumber ? " #" + scanNumber : ""}, ${peItems.length} findings). One entry per item: what it is, why it matters, a real-world analogy, plain-urgency severity. Grouped by severity.`, "", "---",
729
+ ].join("\n");
730
+ // Build chunks: header, then per-severity section (header + its entries), sub-split ≤30KB.
731
+ const peChunks = [peHeader];
732
+ for (const sev of ["CRITICAL", "HIGH", "MEDIUM", "LOW"]) {
733
+ const items = peGroups[sev];
734
+ if (!items.length) continue;
735
+ let buf = `\n${peSevHead[sev]} (${items.length})\n\n`;
736
+ for (const md of items) {
737
+ const piece = md + "\n\n";
738
+ if (buf.length + piece.length > 30000) { peChunks.push(buf); buf = ""; }
739
+ buf += piece;
740
+ }
741
+ if (buf.trim()) peChunks.push(buf);
742
+ }
743
+ let peWrote = 0;
744
+ for (let ci = 0; ci < peChunks.length; ci++) {
745
+ const first = ci === 0;
746
+ const res = await gatedAgent(
747
+ [
748
+ first ? `Create the file \`${peTarget}\` (overwrite) with EXACTLY the content between markers, using the Write tool. Verbatim.`
749
+ : `APPEND EXACTLY the content between markers to the END of \`${peTarget}\` (do not overwrite; append) via a Bash heredoc \`cat >> ${peTarget} <<'GSDTEOF'\` … \`GSDTEOF\`. Verbatim.`,
750
+ `Reply ONLY "OK".`, "", "<<<C>>>", peChunks[ci], "<<<END>>>",
751
+ ].join("\n"),
752
+ { label: `plain-english write ${ci + 1}/${peChunks.length}`, phase: "Plain-English", model: "haiku" }
753
+ ).catch((e) => ({ _e: String(e && e.message) }));
754
+ if (typeof res === "string" && /ok/i.test(res)) peWrote++;
755
+ }
756
+ log(`plain-english: ${Object.values(peGroups).reduce((a, b) => a + b.length, 0)}/${peItems.length} entries, grouped by severity, ${peWrote}/${peChunks.length} chunks written${peFailed ? `; ${peFailed} gen batch(es) failed` : ""}`);
757
+
677
758
  // Commit the docs + dimension files + plain-english via a small agent (Bash git).
678
759
  const commitAgent = await agent(
679
760
  [