@tekyzinc/gsd-t 4.0.22 → 4.0.23

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.0.23] - 2026-06-03 (M76 ASCII-Clean Register Output — patch)
6
+
7
+ ### Fixed — register/docs rendered as mojibake in non-UTF-8 terminals
8
+
9
+ The scan register used emoji severity markers (🔴🟠🟡🟢) and em-dashes. In a non-UTF-8 terminal/pager these display as garbage boxes (`βPADCCH`/`πAPCCCH`). The file bytes were valid UTF-8 — purely a render problem — but the emoji added nothing and made the register unreadable in common viewers.
10
+
11
+ - `templates/workflows/gsd-t-scan.workflow.js`: added `ascii()` sanitizer (strips emoji/symbols, normalizes em/en dashes → `-`, smart quotes → ASCII, ellipsis → `...`). `fmtChunks` now emits plain-ASCII headers/tables and sanitizes every user-supplied field (finder text can contain these too). Document-phase agents instructed "ASCII ONLY".
12
+ - `test/m76-ascii-clean-register.test.js`: +5 tests (sanitizer behavior + a structural guard that fmtChunks' output literals contain no emoji/em-dash).
13
+ - One-off: cleaned the existing HiloAviation scan docs (techdebt.md + plain-english + 5 dimension files) — 0 emoji / 0 em-dashes remaining.
14
+
15
+ Suite: 1311 pass / 0 fail / 4 skip — zero regressions.
16
+
5
17
  ## [4.0.22] - 2026-06-02 (M75 Deterministic Chunked Register Write — patch)
6
18
 
7
19
  ### Fixed — synthesis no longer stalls writing a large register
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.0.22",
3
+ "version": "4.0.23",
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",
@@ -494,30 +494,43 @@ counts.total = finalFindings.length;
494
494
  // of a 466KB register truncates at ~165KB — verified). Chunk 0 is the header+summary
495
495
  // (Write/create); the rest are appends. Items are grouped so a chunk never splits an
496
496
  // item, and the severity-section heading rides with its first item's chunk.
497
+ // M76: register output must be ASCII-clean — no emoji, no em/en-dashes, no smart
498
+ // quotes. They render as mojibake boxes in non-UTF-8 terminals/pagers (the
499
+ // βPADCCH/πAPCCCH garbage a user saw). Finder text can also contain these, so sanitize
500
+ // every user-supplied field, not just our own template strings.
501
+ function ascii(s) {
502
+ return String(s == null ? "" : s)
503
+ .replace(/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2190}-\u{21FF}\u{2B00}-\u{2BFF}️]/gu, "") // emoji/symbols
504
+ .replace(/[—–]/g, "-") // em/en dash -> hyphen
505
+ .replace(/[‘’]/g, "'") // smart single quotes
506
+ .replace(/[“”]/g, '"') // smart double quotes
507
+ .replace(/…/g, "...") // ellipsis
508
+ .replace(/[ \t]+\n/g, "\n"); // tidy trailing whitespace
509
+ }
497
510
  function fmtChunks(today) {
498
- const sevHead = { CRITICAL: "🔴 Critical", HIGH: "🟠 High", MEDIUM: "🟡 Medium", LOW: "🟢 Low" };
511
+ const sevHead = { CRITICAL: "Critical", HIGH: "High", MEDIUM: "Medium", LOW: "Low" };
499
512
  const head = [];
500
- head.push(`# Tech Debt Register ${projectDir.split("/").pop()}`, "");
501
- if (scanNumber) head.push(`**Scan #${scanNumber}** Deep codebase scan (runtime-native, ${coverageComplete ? "full coverage" : "PARTIAL coverage"})`);
513
+ head.push(`# Tech Debt Register - ${projectDir.split("/").pop()}`, "");
514
+ if (scanNumber) head.push(`**Scan #${scanNumber}** - Deep codebase scan (runtime-native, ${coverageComplete ? "full coverage" : "PARTIAL coverage"})`);
502
515
  head.push(`**Date:** ${today}`);
503
- head.push(`**Slices run:** ${slices.length} | **Coverage:** ${coverageComplete ? `FULL all ${slices.length} slices succeeded` : `PARTIAL ${succeededCount}/${slices.length} succeeded`}`);
516
+ head.push(`**Slices run:** ${slices.length} | **Coverage:** ${coverageComplete ? `FULL - all ${slices.length} slices succeeded` : `PARTIAL - ${succeededCount}/${slices.length} succeeded`}`);
504
517
  head.push(`**Verified findings:** ${counts.total}`, "");
505
518
  head.push(`> Effort estimates use GSD-T-native units (domain / wave / spawn / token-spend). Never human-hours.`);
506
519
  head.push(`> TD numbering continues from the prior register (if any, archived). This scan begins at **TD-${tdStart}**.`, "");
507
- if (!coverageComplete) head.push(`> **PARTIAL COVERAGE ${failedSlices.length} of ${slices.length} codebase areas were NOT scanned this pass** (failed to return findings): ${failedSlices.join(", ")}. Findings UNDER-COUNT the real debt. Re-run (resume) for full coverage.`, "");
520
+ if (!coverageComplete) head.push(`> [!] **PARTIAL COVERAGE - ${failedSlices.length} of ${slices.length} codebase areas were NOT scanned this pass** (failed to return findings): ${ascii(failedSlices.join(", "))}. Findings UNDER-COUNT the real debt. Re-run (resume) for full coverage.`, "");
508
521
  head.push(`## Summary`, "", `| Severity | Count |`, `|----------|-------|`,
509
- `| 🔴 CRITICAL | ${counts.critical} |`, `| 🟠 HIGH | ${counts.high} |`,
510
- `| 🟡 MEDIUM | ${counts.medium} |`, `| 🟢 LOW | ${counts.low} |`,
522
+ `| CRITICAL | ${counts.critical} |`, `| HIGH | ${counts.high} |`,
523
+ `| MEDIUM | ${counts.medium} |`, `| LOW | ${counts.low} |`,
511
524
  `| **Total** | **${counts.total}** |`, "", "---", "");
512
525
 
513
526
  function itemMd(f, td) {
514
- const L = [`### TD-${td} ${f.title || "(untitled)"}`,
515
- `- **Area:** ${f.area || ""}`, `- **Severity:** ${f.severity}`, `- **Status:** OPEN`,
516
- `- **Location:** ${(f.files && f.files.length) ? f.files.join(", ") : ""}`];
517
- if (f.detail) L.push(`- **Description:** ${f.detail}`);
518
- if (f.impact) L.push(`- **Impact:** ${f.impact}`);
519
- if (f.recommendation) L.push(`- **Remediation:** ${f.recommendation}`);
520
- if (f.slice) L.push(`- **Found in slice:** ${f.slice}`);
527
+ const L = [`### TD-${td} - ${ascii(f.title) || "(untitled)"}`,
528
+ `- **Area:** ${ascii(f.area) || "(none)"}`, `- **Severity:** ${f.severity}`, `- **Status:** OPEN`,
529
+ `- **Location:** ${(f.files && f.files.length) ? ascii(f.files.join(", ")) : "(none)"}`];
530
+ if (f.detail) L.push(`- **Description:** ${ascii(f.detail)}`);
531
+ if (f.impact) L.push(`- **Impact:** ${ascii(f.impact)}`);
532
+ if (f.recommendation) L.push(`- **Remediation:** ${ascii(f.recommendation)}`);
533
+ if (f.slice) L.push(`- **Found in slice:** ${ascii(f.slice)}`);
521
534
  L.push("");
522
535
  return L.join("\n");
523
536
  }
@@ -646,7 +659,8 @@ const docResults = await parallel(
646
659
  d.prompt,
647
660
  ``,
648
661
  isLiving ? mergeNote : `Write the file fresh in the format described (use Bash \`mkdir -p\` for parent dirs if needed).`,
649
- `Read the actual code under the relevant slice paths for specifics don't summarize only from findings. Use Write/Edit to write the file, then return JSON per the schema (status "written"/"merged"/"skipped"/"failed"). Do NOT commit — the workflow handles git at the end.`,
662
+ `ASCII ONLY: do NOT use emoji, em-dashes (use " - "), en-dashes, smart quotes, or other non-ASCII punctuation they render as garbage in non-UTF-8 terminals. Plain ASCII markdown only.`,
663
+ `Read the actual code under the relevant slice paths for specifics - don't summarize only from findings. Use Write/Edit to write the file, then return JSON per the schema (status "written"/"merged"/"skipped"/"failed"). Do NOT commit - the workflow handles git at the end.`,
650
664
  ].filter(Boolean).join("\n");
651
665
  try {
652
666
  return await agent(prompt, { label: d.label, phase: "Document", schema: DOC_RESULT_SCHEMA, model: "sonnet" });