@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 +13 -0
- package/bin/scan-data-collector.js +60 -1
- package/bin/scan-diagrams-generators.js +15 -7
- package/bin/scan-diagrams.js +9 -0
- package/bin/scan-renderer.js +32 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
-
'
|
|
9
|
-
'
|
|
10
|
-
'
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
161
|
+
API->>API: validate and sanitize
|
|
154
162
|
alt invalid input
|
|
155
163
|
API-->>Client: 400 Bad Request
|
|
156
164
|
else valid
|
package/bin/scan-diagrams.js
CHANGED
|
@@ -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';
|
package/bin/scan-renderer.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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",
|