@tekyzinc/gsd-t 4.0.26 → 4.0.27

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,19 @@
2
2
 
3
3
  All notable changes to GSD-T are documented here. Updated with each release.
4
4
 
5
+ ## [4.0.27] - 2026-06-04 (M79 Diagram Quality - patch)
6
+
7
+ ### Fixed - scan-report diagrams were generic boilerplate, clashed with the dark theme, and one was actively misleading
8
+
9
+ The HTML scan report's six diagrams had three problems. (1) Four of them rendered a hardcoded "Tasks/Projects/Auth" template because `collectScanData` never populated the `services`/`layers`/`endpoints`/`states` inputs the generators read. (2) The database-schema diagram picked the wrong schema file on large repos (a 5-table video shim instead of the 417-table `src/lib/schema/index.ts`) and emitted `unknown` column types — a misleading diagram is worse than none. (3) All diagrams rendered on a white background with sharp corners and shrink-wrapped labels, clashing with the report's dark theme. The sequence diagram also failed to render because `validate & sanitize` broke the Mermaid sequence parser.
10
+
11
+ - `bin/scan-data-collector.js`: parse real `services`/`layers`/`endpoints` from `docs/architecture.md` and `states` from `docs/workflows.md` (strict transition-chain detection — prose noise yields `[]` so generators keep their good defaults).
12
+ - `bin/scan-diagrams-generators.js`: `genSystemArchitecture` draws up to 12 real feature domains with rounded `classDef`s; `genSequence` uses `validate and sanitize` (no `&` entity).
13
+ - `bin/scan-diagrams.js`: database-schema diagram suppressed by default via `SUPPRESSED_TYPES`; re-enable per call with `options.includeSchemaDiagram` once the schema extractor is fixed.
14
+ - `bin/scan-renderer.js`: shared `MERMAID_CONFIG` (dark `base` theme, rounded corners, node padding/spacing) applied via `mmdc -c`, plus `-b transparent` so diagrams blend into the dark panel.
15
+ - `test/m79-diagram-quality.test.js`: regression test (services extracted, schema suppressed + opt-in, sequence has no `&`, config injected).
16
+ - `test/scan.test.js`, `test/verify-gates.js`: updated to the 5-diagrams-by-default contract (schema via opt-in).
17
+
5
18
  ## [4.0.26] - 2026-06-03 (M78 Plain-English Grouped + Batched - patch)
6
19
 
7
20
  ### Fixed - plain-english companion was a flat ungrouped list + would stall on large registers
@@ -165,6 +165,55 @@ function parseQualityFindings(text) {
165
165
  return findings.slice(0, 3);
166
166
  }
167
167
 
168
+ // M79: derive the diagram inputs (services/layers/endpoints/states) from the DEEP
169
+ // living docs (docs/architecture.md, docs/workflows.md) so the architecture/data-flow
170
+ // diagrams reflect the project's REAL feature domains instead of falling back to the
171
+ // generic "Tasks/Projects" templates. Falls back to scan/architecture.md, then [].
172
+ function parseServices(docArch) {
173
+ if (!docArch) return [];
174
+ // Top-level numbered domain sections: "## 5. Multi-Tenant School and Location Model"
175
+ const svc = [];
176
+ for (const m of docArch.matchAll(/^##\s+\d+\.\s+(.+?)\s*$/gm)) {
177
+ const name = m[1].trim();
178
+ // skip meta sections that aren't feature domains
179
+ if (/^(system overview|technology stack|application structure|table of contents)/i.test(name)) continue;
180
+ svc.push(name);
181
+ }
182
+ return svc;
183
+ }
184
+ function parseLayers(docArch) {
185
+ if (!docArch) return [];
186
+ // "### Controller Layer", "## 4. Authentication and Session Layer", etc.
187
+ const layers = [];
188
+ for (const m of docArch.matchAll(/^#{2,3}\s+(?:\d+\.\s+)?(.+?\bLayer)\s*$/gmi)) {
189
+ const n = m[1].trim(); if (!layers.includes(n)) layers.push(n);
190
+ }
191
+ return layers;
192
+ }
193
+ function parseEndpoints(docArch) {
194
+ if (!docArch) return [];
195
+ // real route lines like `GET /feature-flags/resolve/:flagKey` or `POST /api/...`
196
+ const eps = [];
197
+ for (const m of docArch.matchAll(/\b(GET|POST|PUT|PATCH|DELETE)\s+(\/[A-Za-z0-9_\-\/:{}.]+)/g)) {
198
+ const e = m[1] + ' ' + m[2]; if (!eps.includes(e)) eps.push(e);
199
+ }
200
+ return eps;
201
+ }
202
+ function parseStates(docWorkflows) {
203
+ if (!docWorkflows) return [];
204
+ // Only accept REAL state transitions written as "Draft -> Open" / "Draft → Open"
205
+ // where BOTH sides look like status enum values (short, capitalized, no spaces).
206
+ // If we can't find a genuine transition chain, return [] so the generator keeps
207
+ // its (good) default state machine rather than rendering doc-prose noise. (M79:
208
+ // a loose match previously pulled "Domains/Source/Schedule" from prose headers.)
209
+ const isState = (w) => /^[A-Z][A-Za-z]{1,14}$/.test(w) && /(Draft|Open|Progress|Review|Done|Block|Cancel|Pending|Active|Closed|Approved|Rejected|Queued|Failed|Complete)/i.test(w);
210
+ const states = [];
211
+ for (const m of docWorkflows.matchAll(/\b([A-Z][A-Za-z]+)\s*(?:->|→)\s*([A-Z][A-Za-z]+)\b/g)) {
212
+ if (isState(m[1]) && isState(m[2])) for (const s of [m[1], m[2]]) if (!states.includes(s)) states.push(s);
213
+ }
214
+ return states.length >= 3 ? states : [];
215
+ }
216
+
168
217
  function collectScanData(projectRoot) {
169
218
  const scanDir = path.join(projectRoot, '.gsd-t', 'scan');
170
219
  const rs = (f) => read(path.join(scanDir, f));
@@ -175,6 +224,9 @@ function collectScanData(projectRoot) {
175
224
  const secText = rs('security.md');
176
225
  const qualText = rs('quality.md');
177
226
  const debtText = rr('.gsd-t/techdebt.md');
227
+ // Deep living docs — the real architecture knowledge (M79).
228
+ const docArch = rr('docs/architecture.md');
229
+ const docFlow = rr('docs/workflows.md');
178
230
 
179
231
  let projectName = path.basename(projectRoot);
180
232
  try { projectName = JSON.parse(rr('package.json')).name || projectName; } catch {}
@@ -188,8 +240,15 @@ function collectScanData(projectRoot) {
188
240
  const qualFinds = parseQualityFindings(qualText);
189
241
  const findings = secFinds.concat(qualFinds).slice(0, 10);
190
242
 
243
+ // Diagram inputs from the deep docs (M79).
244
+ const services = parseServices(docArch);
245
+ const layers = parseLayers(docArch);
246
+ const endpoints = parseEndpoints(docArch);
247
+ const states = parseStates(docFlow);
248
+
191
249
  return { projectName, filesScanned, totalLoc, debtCritical, debtHigh, debtMedium,
192
- testCoverage, domains, techDebt, findings };
250
+ testCoverage, domains, techDebt, findings,
251
+ services, layers, endpoints, states };
193
252
  }
194
253
 
195
254
  module.exports = { collectScanData };
@@ -2,14 +2,22 @@
2
2
 
3
3
  function genSystemArchitecture(analysisData) {
4
4
  try {
5
- const services = (analysisData.services || []).slice(0, 4);
5
+ // M79: when real feature domains were extracted from docs/architecture.md, draw
6
+ // them as the system's services (User -> Backend -> each domain). Show up to 12.
7
+ const services = (analysisData.services || []).slice(0, 12);
6
8
  if (services.length >= 2) {
7
9
  const lines = ['graph TB',
8
- ' classDef user fill:#0d2035,stroke:#06b6d4,color:#a5f3fc',
9
- ' classDef svc fill:#1a0f3a,stroke:#7c3aed,color:#ddd6fe',
10
- ' classDef db fill:#0a2318,stroke:#10b981,color:#a7f3d0',
11
- ' User(["👤 User"]):::user'];
12
- services.forEach((s, i) => { lines.push(' S' + i + '["' + s + '"]:::svc'); lines.push(' User --> S' + i); });
10
+ ' User(["👤 User"]):::user',
11
+ ' API["⚡ Backend / API"]:::core',
12
+ ' User -->|"HTTPS"| API'];
13
+ services.forEach((s, i) => {
14
+ const label = String(s).replace(/"/g, "'");
15
+ lines.push(' S' + i + '["' + label + '"]:::svc');
16
+ lines.push(' API --> S' + i);
17
+ });
18
+ lines.push(' classDef user fill:#0d2035,stroke:#22d3ee,color:#a5f3fc,rx:8,ry:8');
19
+ lines.push(' classDef core fill:#1a0f3a,stroke:#a78bfa,color:#ede9fe,rx:8,ry:8');
20
+ lines.push(' classDef svc fill:#0f1d3a,stroke:#60a5fa,color:#bfdbfe,rx:8,ry:8');
13
21
  return lines.join('\n');
14
22
  }
15
23
  return `graph TB
@@ -150,7 +158,7 @@ function genSequence(analysisData) {
150
158
  participant Queue
151
159
  User->>Client: Submit form
152
160
  Client->>API: ${ep}
153
- API->>API: validate & sanitize
161
+ API->>API: validate and sanitize
154
162
  alt invalid input
155
163
  API-->>Client: 400 Bad Request
156
164
  else valid
@@ -42,11 +42,20 @@ function buildPlaceholder(def, mmd) {
42
42
  };
43
43
  }
44
44
 
45
+ // M79: the database-schema diagram is SUPPRESSED by default. The schema extractor
46
+ // picks the wrong file on large repos (grabbed a 5-table video shim instead of the
47
+ // 417-table src/lib/schema/index.ts) and emits `unknown` column types — a misleading
48
+ // diagram is worse than none. Re-enable per-call with options.includeSchemaDiagram
49
+ // once parseDrizzle/detectOrm are fixed to use the real schema.
50
+ const SUPPRESSED_TYPES = new Set(['database-schema']);
51
+
45
52
  function generateDiagrams(analysisData, schemaData, options) {
46
53
  try {
47
54
  const { renderDiagram } = require('./scan-renderer');
55
+ const opts = options || {};
48
56
  const results = [];
49
57
  for (const def of DIAGRAM_DEFS) {
58
+ if (SUPPRESSED_TYPES.has(def.type) && !opts.includeSchemaDiagram) continue;
50
59
  try {
51
60
  const mmd = def.gen(analysisData, schemaData);
52
61
  const isDbSchema = def.type === 'database-schema';
@@ -17,19 +17,50 @@ function makePlaceholder() {
17
17
  return PLACEHOLDER_HTML;
18
18
  }
19
19
 
20
+ // M79: shared Mermaid config — coherent dark palette that sits well on the report's
21
+ // dark page, ROUNDED node corners, and generous PADDING/spacing so labels (e.g.
22
+ // "assign", "resolve") aren't shrink-wrapped against the box edges. Applied to every
23
+ // diagram via mmdc's -c flag so all diagrams share one consistent look.
24
+ const MERMAID_CONFIG = {
25
+ theme: 'base',
26
+ themeVariables: {
27
+ background: '#0a0f1e',
28
+ primaryColor: '#172554', // node fill (deep blue, reads on dark)
29
+ primaryBorderColor: '#3b82f6',
30
+ primaryTextColor: '#e2e8f0',
31
+ lineColor: '#64748b',
32
+ secondaryColor: '#1e1b4b',
33
+ tertiaryColor: '#0a2318',
34
+ fontFamily: 'ui-sans-serif, system-ui, -apple-system, Segoe UI, sans-serif',
35
+ fontSize: '15px',
36
+ clusterBkg: '#0b1220',
37
+ clusterBorder: '#1e3a5f',
38
+ nodeBorder: '#3b82f6',
39
+ edgeLabelBackground: '#0b1220',
40
+ },
41
+ flowchart: { curve: 'basis', padding: 18, nodeSpacing: 55, rankSpacing: 70, htmlLabels: true, useMaxWidth: true },
42
+ state: { padding: 16, nodeSpacing: 55, rankSpacing: 70 },
43
+ sequence: { useMaxWidth: true, boxMargin: 12 },
44
+ };
45
+
20
46
  function tryMmdc(mmdContent) {
21
47
  const ts = Date.now();
22
48
  const tmpIn = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.mmd');
23
49
  const tmpOut = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.svg');
50
+ const tmpCfg = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.config.json');
24
51
  try {
25
52
  fs.writeFileSync(tmpIn, mmdContent, 'utf8');
26
- execFileSync('mmdc', ['-i', tmpIn, '-o', tmpOut, '-t', 'dark', '--quiet'], { timeout: 30000, stdio: 'pipe' });
53
+ fs.writeFileSync(tmpCfg, JSON.stringify(MERMAID_CONFIG), 'utf8');
54
+ // -b transparent so the diagram blends into the report's dark panel instead of a
55
+ // white box; -c applies the shared theme/padding/rounded config.
56
+ execFileSync('mmdc', ['-i', tmpIn, '-o', tmpOut, '-c', tmpCfg, '-b', 'transparent', '--quiet'], { timeout: 30000, stdio: 'pipe' });
27
57
  const svg = fs.readFileSync(tmpOut, 'utf8');
28
58
  return { svgContent: stripSvgDimensions(svg), rendered: true, rendererUsed: 'mermaid-cli' };
29
59
  } catch { return null; }
30
60
  finally {
31
61
  try { fs.unlinkSync(tmpIn); } catch {}
32
62
  try { fs.unlinkSync(tmpOut); } catch {}
63
+ try { fs.unlinkSync(tmpCfg); } catch {}
33
64
  }
34
65
  }
35
66
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tekyzinc/gsd-t",
3
- "version": "4.0.26",
3
+ "version": "4.0.27",
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",