@tekyzinc/gsd-t 2.50.12 → 2.53.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 +24 -0
- package/README.md +379 -372
- package/bin/component-registry.js +250 -0
- package/bin/graph-cgc.js +510 -510
- package/bin/graph-indexer.js +147 -147
- package/bin/graph-overlay.js +195 -195
- package/bin/graph-parsers.js +327 -327
- package/bin/graph-query.js +453 -452
- package/bin/graph-store.js +154 -154
- package/bin/qa-calibrator.js +194 -0
- package/bin/scan-data-collector.js +153 -153
- package/bin/scan-diagrams-generators.js +187 -187
- package/bin/scan-diagrams.js +79 -79
- package/bin/scan-renderer.js +92 -92
- package/bin/scan-report-sections.js +121 -121
- package/bin/scan-report.js +184 -184
- package/bin/scan-schema-parsers.js +199 -199
- package/bin/scan-schema.js +103 -103
- package/bin/token-budget.js +246 -0
- package/commands/Claude-md.md +10 -10
- package/commands/branch.md +15 -15
- package/commands/checkin.md +45 -45
- package/commands/global-change.md +209 -209
- package/commands/gsd-t-audit.md +199 -0
- package/commands/gsd-t-backlog-add.md +94 -94
- package/commands/gsd-t-backlog-edit.md +111 -111
- package/commands/gsd-t-backlog-list.md +63 -63
- package/commands/gsd-t-backlog-move.md +94 -94
- package/commands/gsd-t-backlog-promote.md +123 -123
- package/commands/gsd-t-backlog-remove.md +86 -86
- package/commands/gsd-t-backlog-settings.md +158 -158
- package/commands/gsd-t-complete-milestone.md +528 -515
- package/commands/gsd-t-debug.md +506 -399
- package/commands/gsd-t-discuss.md +174 -174
- package/commands/gsd-t-execute.md +758 -634
- package/commands/gsd-t-feature.md +276 -276
- package/commands/gsd-t-health.md +142 -142
- package/commands/gsd-t-help.md +465 -457
- package/commands/gsd-t-impact.md +302 -302
- package/commands/gsd-t-init.md +320 -280
- package/commands/gsd-t-integrate.md +365 -249
- package/commands/gsd-t-milestone.md +87 -87
- package/commands/gsd-t-partition.md +442 -361
- package/commands/gsd-t-pause.md +82 -82
- package/commands/gsd-t-plan.md +345 -344
- package/commands/gsd-t-populate.md +111 -111
- package/commands/gsd-t-prd.md +326 -326
- package/commands/gsd-t-project.md +211 -211
- package/commands/gsd-t-promote-debt.md +123 -123
- package/commands/gsd-t-prompt.md +137 -137
- package/commands/gsd-t-qa.md +266 -266
- package/commands/gsd-t-quick.md +357 -234
- package/commands/gsd-t-reflect.md +134 -134
- package/commands/gsd-t-resume.md +72 -72
- package/commands/gsd-t-scan.md +615 -615
- package/commands/gsd-t-setup.md +76 -0
- package/commands/gsd-t-status.md +192 -166
- package/commands/gsd-t-test-sync.md +381 -381
- package/commands/gsd-t-triage-and-merge.md +171 -171
- package/commands/gsd-t-verify.md +382 -382
- package/commands/gsd-t-visualize.md +118 -118
- package/commands/gsd-t-wave.md +401 -378
- package/docs/GSD-T-README.md +425 -422
- package/docs/architecture.md +385 -369
- package/docs/harness-design-analysis.md +371 -0
- package/docs/infrastructure.md +205 -205
- package/docs/prd-graph-engine.md +398 -398
- package/docs/prd-gsd2-hybrid.md +559 -559
- package/docs/prd-harness-evolution.md +583 -0
- package/docs/requirements.md +14 -0
- package/docs/workflows.md +226 -226
- package/examples/.gsd-t/domains/example-domain/scope.md +13 -13
- package/package.json +40 -40
- package/scripts/gsd-t-auto-route.js +39 -39
- package/scripts/gsd-t-dashboard-mockup.html +1143 -1143
- package/scripts/gsd-t-dashboard-server.js +171 -171
- package/scripts/gsd-t-dashboard.html +262 -262
- package/scripts/gsd-t-event-writer.js +128 -128
- package/scripts/gsd-t-statusline.js +94 -94
- package/scripts/gsd-t-tools.js +175 -175
- package/templates/CLAUDE-global.md +639 -614
- package/templates/CLAUDE-project.md +24 -0
- package/templates/backlog-settings.md +18 -18
- package/templates/backlog.md +1 -1
- package/templates/progress.md +40 -40
- package/templates/shared-services-contract.md +60 -60
- package/templates/stacks/desktop.ini +2 -2
- package/bin/desktop.ini +0 -2
- package/commands/desktop.ini +0 -2
- package/docs/ci-examples/desktop.ini +0 -2
- package/docs/desktop.ini +0 -2
- package/examples/.gsd-t/contracts/desktop.ini +0 -2
- package/examples/.gsd-t/desktop.ini +0 -2
- package/examples/.gsd-t/domains/desktop.ini +0 -2
- package/examples/.gsd-t/domains/example-domain/desktop.ini +0 -2
- package/examples/desktop.ini +0 -2
- package/examples/rules/desktop.ini +0 -2
- package/scripts/desktop.ini +0 -2
- package/templates/desktop.ini +0 -2
package/bin/scan-renderer.js
CHANGED
|
@@ -1,92 +1,92 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const os = require('os');
|
|
5
|
-
const { execFileSync } = require('child_process');
|
|
6
|
-
const https = require('https');
|
|
7
|
-
|
|
8
|
-
const PLACEHOLDER_HTML = '<div class="diagram-placeholder">\n <p>Diagram unavailable — rendering tools not found</p>\n <p>Install: <code>npm install -g @mermaid-js/mermaid-cli</code></p>\n</div>';
|
|
9
|
-
|
|
10
|
-
function stripSvgDimensions(svgStr) {
|
|
11
|
-
return svgStr
|
|
12
|
-
.replace(/<svg([^>]*)\s+width="[^"]*"/, '<svg$1')
|
|
13
|
-
.replace(/<svg([^>]*)\s+height="[^"]*"/, '<svg$1');
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function makePlaceholder() {
|
|
17
|
-
return PLACEHOLDER_HTML;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function tryMmdc(mmdContent) {
|
|
21
|
-
const ts = Date.now();
|
|
22
|
-
const tmpIn = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.mmd');
|
|
23
|
-
const tmpOut = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.svg');
|
|
24
|
-
try {
|
|
25
|
-
fs.writeFileSync(tmpIn, mmdContent, 'utf8');
|
|
26
|
-
execFileSync('mmdc', ['-i', tmpIn, '-o', tmpOut, '-t', 'dark', '--quiet'], { timeout: 30000, stdio: 'pipe' });
|
|
27
|
-
const svg = fs.readFileSync(tmpOut, 'utf8');
|
|
28
|
-
return { svgContent: stripSvgDimensions(svg), rendered: true, rendererUsed: 'mermaid-cli' };
|
|
29
|
-
} catch { return null; }
|
|
30
|
-
finally {
|
|
31
|
-
try { fs.unlinkSync(tmpIn); } catch {}
|
|
32
|
-
try { fs.unlinkSync(tmpOut); } catch {}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function tryD2(mmdContent, type) {
|
|
37
|
-
if (type !== 'system-architecture' && type !== 'data-flow') return null;
|
|
38
|
-
const ts = Date.now();
|
|
39
|
-
const tmpIn = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.d2');
|
|
40
|
-
const tmpOut = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.svg');
|
|
41
|
-
try {
|
|
42
|
-
fs.writeFileSync(tmpIn, 'app -> db: query', 'utf8');
|
|
43
|
-
execFileSync('d2', [tmpIn, tmpOut, '--layout=dagre'], { timeout: 30000, stdio: 'pipe' });
|
|
44
|
-
const svg = fs.readFileSync(tmpOut, 'utf8');
|
|
45
|
-
return { svgContent: stripSvgDimensions(svg), rendered: true, rendererUsed: 'd2' };
|
|
46
|
-
} catch { return null; }
|
|
47
|
-
finally {
|
|
48
|
-
try { fs.unlinkSync(tmpIn); } catch {}
|
|
49
|
-
try { fs.unlinkSync(tmpOut); } catch {}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function tryKroki(mmdContent) {
|
|
54
|
-
return new Promise((resolve) => {
|
|
55
|
-
const body = JSON.stringify({ diagram_source: mmdContent });
|
|
56
|
-
const host = process.env.KROKI_HOST || 'kroki.io';
|
|
57
|
-
const options = {
|
|
58
|
-
hostname: host, port: 443,
|
|
59
|
-
path: '/mermaid/svg', method: 'POST',
|
|
60
|
-
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
61
|
-
timeout: 15000
|
|
62
|
-
};
|
|
63
|
-
const req = https.request(options, (res) => {
|
|
64
|
-
let data = '';
|
|
65
|
-
res.on('data', c => { data += c; });
|
|
66
|
-
res.on('end', () => {
|
|
67
|
-
if (data.trimStart().startsWith('<svg')) {
|
|
68
|
-
resolve({ svgContent: stripSvgDimensions(data), rendered: true, rendererUsed: 'kroki' });
|
|
69
|
-
} else { resolve(null); }
|
|
70
|
-
});
|
|
71
|
-
});
|
|
72
|
-
req.on('error', () => resolve(null));
|
|
73
|
-
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
74
|
-
req.write(body);
|
|
75
|
-
req.end();
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function renderDiagram(mmdContent, type, options) {
|
|
80
|
-
try {
|
|
81
|
-
const mmdc = tryMmdc(mmdContent);
|
|
82
|
-
if (mmdc) return mmdc;
|
|
83
|
-
const d2 = tryD2(mmdContent, type);
|
|
84
|
-
if (d2) return d2;
|
|
85
|
-
// tryKroki is async; skip in sync rendering path — Kroki available via async wrapper if needed
|
|
86
|
-
return { svgContent: makePlaceholder(), rendered: false, rendererUsed: 'placeholder' };
|
|
87
|
-
} catch {
|
|
88
|
-
return { svgContent: makePlaceholder(), rendered: false, rendererUsed: 'placeholder' };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
module.exports = { renderDiagram };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const { execFileSync } = require('child_process');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
|
|
8
|
+
const PLACEHOLDER_HTML = '<div class="diagram-placeholder">\n <p>Diagram unavailable — rendering tools not found</p>\n <p>Install: <code>npm install -g @mermaid-js/mermaid-cli</code></p>\n</div>';
|
|
9
|
+
|
|
10
|
+
function stripSvgDimensions(svgStr) {
|
|
11
|
+
return svgStr
|
|
12
|
+
.replace(/<svg([^>]*)\s+width="[^"]*"/, '<svg$1')
|
|
13
|
+
.replace(/<svg([^>]*)\s+height="[^"]*"/, '<svg$1');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function makePlaceholder() {
|
|
17
|
+
return PLACEHOLDER_HTML;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function tryMmdc(mmdContent) {
|
|
21
|
+
const ts = Date.now();
|
|
22
|
+
const tmpIn = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.mmd');
|
|
23
|
+
const tmpOut = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.svg');
|
|
24
|
+
try {
|
|
25
|
+
fs.writeFileSync(tmpIn, mmdContent, 'utf8');
|
|
26
|
+
execFileSync('mmdc', ['-i', tmpIn, '-o', tmpOut, '-t', 'dark', '--quiet'], { timeout: 30000, stdio: 'pipe' });
|
|
27
|
+
const svg = fs.readFileSync(tmpOut, 'utf8');
|
|
28
|
+
return { svgContent: stripSvgDimensions(svg), rendered: true, rendererUsed: 'mermaid-cli' };
|
|
29
|
+
} catch { return null; }
|
|
30
|
+
finally {
|
|
31
|
+
try { fs.unlinkSync(tmpIn); } catch {}
|
|
32
|
+
try { fs.unlinkSync(tmpOut); } catch {}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function tryD2(mmdContent, type) {
|
|
37
|
+
if (type !== 'system-architecture' && type !== 'data-flow') return null;
|
|
38
|
+
const ts = Date.now();
|
|
39
|
+
const tmpIn = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.d2');
|
|
40
|
+
const tmpOut = path.join(os.tmpdir(), 'gsd-scan-' + ts + '.svg');
|
|
41
|
+
try {
|
|
42
|
+
fs.writeFileSync(tmpIn, 'app -> db: query', 'utf8');
|
|
43
|
+
execFileSync('d2', [tmpIn, tmpOut, '--layout=dagre'], { timeout: 30000, stdio: 'pipe' });
|
|
44
|
+
const svg = fs.readFileSync(tmpOut, 'utf8');
|
|
45
|
+
return { svgContent: stripSvgDimensions(svg), rendered: true, rendererUsed: 'd2' };
|
|
46
|
+
} catch { return null; }
|
|
47
|
+
finally {
|
|
48
|
+
try { fs.unlinkSync(tmpIn); } catch {}
|
|
49
|
+
try { fs.unlinkSync(tmpOut); } catch {}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function tryKroki(mmdContent) {
|
|
54
|
+
return new Promise((resolve) => {
|
|
55
|
+
const body = JSON.stringify({ diagram_source: mmdContent });
|
|
56
|
+
const host = process.env.KROKI_HOST || 'kroki.io';
|
|
57
|
+
const options = {
|
|
58
|
+
hostname: host, port: 443,
|
|
59
|
+
path: '/mermaid/svg', method: 'POST',
|
|
60
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
|
|
61
|
+
timeout: 15000
|
|
62
|
+
};
|
|
63
|
+
const req = https.request(options, (res) => {
|
|
64
|
+
let data = '';
|
|
65
|
+
res.on('data', c => { data += c; });
|
|
66
|
+
res.on('end', () => {
|
|
67
|
+
if (data.trimStart().startsWith('<svg')) {
|
|
68
|
+
resolve({ svgContent: stripSvgDimensions(data), rendered: true, rendererUsed: 'kroki' });
|
|
69
|
+
} else { resolve(null); }
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
req.on('error', () => resolve(null));
|
|
73
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
74
|
+
req.write(body);
|
|
75
|
+
req.end();
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function renderDiagram(mmdContent, type, options) {
|
|
80
|
+
try {
|
|
81
|
+
const mmdc = tryMmdc(mmdContent);
|
|
82
|
+
if (mmdc) return mmdc;
|
|
83
|
+
const d2 = tryD2(mmdContent, type);
|
|
84
|
+
if (d2) return d2;
|
|
85
|
+
// tryKroki is async; skip in sync rendering path — Kroki available via async wrapper if needed
|
|
86
|
+
return { svgContent: makePlaceholder(), rendered: false, rendererUsed: 'placeholder' };
|
|
87
|
+
} catch {
|
|
88
|
+
return { svgContent: makePlaceholder(), rendered: false, rendererUsed: 'placeholder' };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
module.exports = { renderDiagram };
|
|
@@ -1,121 +1,121 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
function esc(s) {
|
|
4
|
-
return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
function buildMetricCards(d) {
|
|
8
|
-
d = d || {};
|
|
9
|
-
const loc = d.totalLoc > 999 ? (d.totalLoc / 1000).toFixed(1) + 'k' : (d.totalLoc || 0);
|
|
10
|
-
const cov = d.testCoverage || 'N/A';
|
|
11
|
-
const metrics = [
|
|
12
|
-
{ label: 'Files Scanned', value: d.filesScanned || 0, sub: 'across all components', bar: 'g' },
|
|
13
|
-
{ label: 'Lines of Code', value: loc, sub: 'source code', bar: '' },
|
|
14
|
-
{ label: 'Critical Issues', value: d.debtCritical || 0, sub: 'requires immediate fix', bar: 'r' },
|
|
15
|
-
{ label: 'High Issues', value: d.debtHigh || 0, sub: 'fix before next release', bar: 'o' },
|
|
16
|
-
{ label: 'Medium Issues', value: d.debtMedium || 0, sub: 'plan to address', bar: 'y' },
|
|
17
|
-
{ label: 'Test Coverage', value: cov, sub: 'passing tests', bar: 'g' }
|
|
18
|
-
];
|
|
19
|
-
const cards = metrics.map(m =>
|
|
20
|
-
'<div class="mc"><div class="mc-bar' + (m.bar ? ' ' + m.bar : '') + '"></div>' +
|
|
21
|
-
'<div class="mc-lbl">' + m.label + '</div>' +
|
|
22
|
-
'<div class="mc-val">' + m.value + '</div>' +
|
|
23
|
-
'<div class="mc-sub">' + m.sub + '</div></div>'
|
|
24
|
-
).join('');
|
|
25
|
-
return '<section id="summary"><div class="sl">Summary</div><div class="mxg">' + cards + '</div></section>';
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function buildDomainHealth(d) {
|
|
29
|
-
d = d || {};
|
|
30
|
-
const domains = d.domains || [];
|
|
31
|
-
if (!domains.length) {
|
|
32
|
-
return '<section id="domains"><div class="sl">Component Inventory</div>' +
|
|
33
|
-
'<p style="color:var(--muted2);font-size:12px">No component data available.</p></section>';
|
|
34
|
-
}
|
|
35
|
-
const rows = domains.map(item => {
|
|
36
|
-
const bigFile = parseInt(item.size) > 500;
|
|
37
|
-
const sizeColor = bigFile ? 'color:var(--amber)' : 'color:var(--blue)';
|
|
38
|
-
return '<tr>' +
|
|
39
|
-
'<td><strong>' + esc(item.name) + '</strong></td>' +
|
|
40
|
-
'<td style="font-family:\'Consolas\',monospace;font-size:10px;color:var(--muted2)">' + esc(item.filePath || '') + '</td>' +
|
|
41
|
-
'<td class="loc-cell" style="' + sizeColor + '">' + esc(item.size || '') + '</td>' +
|
|
42
|
-
'<td style="color:var(--muted2);font-size:11px">' + esc(item.purpose || '') + '</td>' +
|
|
43
|
-
'</tr>';
|
|
44
|
-
}).join('');
|
|
45
|
-
return '<section id="domains"><div class="sl">Component Inventory</div>' +
|
|
46
|
-
'<div class="tw"><table><thead><tr><th>Component</th><th>File(s)</th><th>Lines</th><th>Purpose</th></tr></thead>' +
|
|
47
|
-
'<tbody>' + rows + '</tbody></table></div></section>';
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function buildDiagramSection(d) {
|
|
51
|
-
const secId = 'diagram-' + d.type;
|
|
52
|
-
const cardTitle = esc(d.title + (d.typeBadge ? ' \u2014 ' + d.typeBadge : ''));
|
|
53
|
-
let diagramContent;
|
|
54
|
-
if (d.svgContent && !d.svgContent.includes('diagram-placeholder')) {
|
|
55
|
-
diagramContent = '<div style="width:100%;overflow:auto">' + d.svgContent + '</div>';
|
|
56
|
-
} else {
|
|
57
|
-
diagramContent = d.svgContent ||
|
|
58
|
-
'<div class="diagram-placeholder"><p>Diagram unavailable</p></div>';
|
|
59
|
-
}
|
|
60
|
-
return '<section id="' + secId + '" class="diagram-section">' +
|
|
61
|
-
'<div class="sl">' + esc(d.title) + '</div>' +
|
|
62
|
-
'<div class="dc" data-title="' + cardTitle + '">' +
|
|
63
|
-
'<div class="dc-h"><div class="dc-hl">' +
|
|
64
|
-
'<span class="dc-t">' + esc(d.title) + '</span>' +
|
|
65
|
-
'<span class="dc-tag">' + esc(d.typeBadge) + '</span>' +
|
|
66
|
-
'</div><button class="btn-exp" onclick="expandDiagram(this)">⛶<span class="lbl">Expand</span></button>' +
|
|
67
|
-
'</div>' +
|
|
68
|
-
'<div class="dc-b">' + diagramContent + '</div>' +
|
|
69
|
-
'<div class="dc-n">' + esc(d.note) + '</div>' +
|
|
70
|
-
'</div></section>';
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function buildTechDebt(d) {
|
|
74
|
-
d = d || {};
|
|
75
|
-
const items = d.techDebt || [];
|
|
76
|
-
if (!items.length) {
|
|
77
|
-
return '<section id="tech-debt"><div class="sl">Tech Debt Register</div>' +
|
|
78
|
-
'<p style="color:var(--muted2);font-size:12px">No open tech debt items.</p></section>';
|
|
79
|
-
}
|
|
80
|
-
const sevClass = { critical: 'c', high: 'h', medium: 'm', low: 'l', info: 'i' };
|
|
81
|
-
const rows = items.map(i => {
|
|
82
|
-
const sc = sevClass[(i.severity || '').toLowerCase()] || 'm';
|
|
83
|
-
return '<tr>' +
|
|
84
|
-
'<td><span class="bx ' + sc + '">' + esc(i.severity || '') + '</span></td>' +
|
|
85
|
-
'<td style="font-family:\'Consolas\',monospace;font-size:11px;color:var(--blue)">' + esc(i.domain || '') + '</td>' +
|
|
86
|
-
'<td>' + esc(i.issue || '') + '</td>' +
|
|
87
|
-
'<td><code>' + esc(i.location || '') + '</code></td>' +
|
|
88
|
-
'<td style="color:var(--muted2);font-size:11px">' + esc(i.effort || '') + '</td>' +
|
|
89
|
-
'</tr>';
|
|
90
|
-
}).join('');
|
|
91
|
-
return '<section id="tech-debt"><div class="sl">Tech Debt Register</div>' +
|
|
92
|
-
'<div class="tw"><table><thead><tr><th>Severity</th><th>Domain</th><th>Issue</th><th>Location</th><th>Effort</th></tr></thead>' +
|
|
93
|
-
'<tbody>' + rows + '</tbody></table></div></section>';
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const ICONS = {
|
|
97
|
-
security: '🛑', architecture: '⚡', reliability: '📊',
|
|
98
|
-
quality: '📄', performance: '⚠', strength: '✅', default: '📋'
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
function buildFindings(d) {
|
|
102
|
-
d = d || {};
|
|
103
|
-
const findings = d.findings || [];
|
|
104
|
-
if (!findings.length) {
|
|
105
|
-
return '<section id="findings"><div class="sl">Key Findings</div>' +
|
|
106
|
-
'<p style="color:var(--muted2);font-size:12px">No findings recorded.</p></section>';
|
|
107
|
-
}
|
|
108
|
-
const cards = findings.map(f => {
|
|
109
|
-
const cat = (f.category || '').toLowerCase();
|
|
110
|
-
const ico = ICONS[cat] || ICONS.default;
|
|
111
|
-
const rec = f.recommendation
|
|
112
|
-
? ' <strong style="color:var(--text)">Fix:</strong> ' + esc(f.recommendation) : '';
|
|
113
|
-
return '<div class="fi"><div class="ico">' + ico + '</div>' +
|
|
114
|
-
'<div><h4>' + esc(f.title || '') + '</h4>' +
|
|
115
|
-
'<p>' + esc(f.description || '') + rec + '</p>' +
|
|
116
|
-
'</div></div>';
|
|
117
|
-
}).join('');
|
|
118
|
-
return '<section id="findings"><div class="sl">Key Findings</div><div class="fl">' + cards + '</div></section>';
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
module.exports = { buildMetricCards, buildDomainHealth, buildDiagramSection, buildTechDebt, buildFindings };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function esc(s) {
|
|
4
|
+
return String(s || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function buildMetricCards(d) {
|
|
8
|
+
d = d || {};
|
|
9
|
+
const loc = d.totalLoc > 999 ? (d.totalLoc / 1000).toFixed(1) + 'k' : (d.totalLoc || 0);
|
|
10
|
+
const cov = d.testCoverage || 'N/A';
|
|
11
|
+
const metrics = [
|
|
12
|
+
{ label: 'Files Scanned', value: d.filesScanned || 0, sub: 'across all components', bar: 'g' },
|
|
13
|
+
{ label: 'Lines of Code', value: loc, sub: 'source code', bar: '' },
|
|
14
|
+
{ label: 'Critical Issues', value: d.debtCritical || 0, sub: 'requires immediate fix', bar: 'r' },
|
|
15
|
+
{ label: 'High Issues', value: d.debtHigh || 0, sub: 'fix before next release', bar: 'o' },
|
|
16
|
+
{ label: 'Medium Issues', value: d.debtMedium || 0, sub: 'plan to address', bar: 'y' },
|
|
17
|
+
{ label: 'Test Coverage', value: cov, sub: 'passing tests', bar: 'g' }
|
|
18
|
+
];
|
|
19
|
+
const cards = metrics.map(m =>
|
|
20
|
+
'<div class="mc"><div class="mc-bar' + (m.bar ? ' ' + m.bar : '') + '"></div>' +
|
|
21
|
+
'<div class="mc-lbl">' + m.label + '</div>' +
|
|
22
|
+
'<div class="mc-val">' + m.value + '</div>' +
|
|
23
|
+
'<div class="mc-sub">' + m.sub + '</div></div>'
|
|
24
|
+
).join('');
|
|
25
|
+
return '<section id="summary"><div class="sl">Summary</div><div class="mxg">' + cards + '</div></section>';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function buildDomainHealth(d) {
|
|
29
|
+
d = d || {};
|
|
30
|
+
const domains = d.domains || [];
|
|
31
|
+
if (!domains.length) {
|
|
32
|
+
return '<section id="domains"><div class="sl">Component Inventory</div>' +
|
|
33
|
+
'<p style="color:var(--muted2);font-size:12px">No component data available.</p></section>';
|
|
34
|
+
}
|
|
35
|
+
const rows = domains.map(item => {
|
|
36
|
+
const bigFile = parseInt(item.size) > 500;
|
|
37
|
+
const sizeColor = bigFile ? 'color:var(--amber)' : 'color:var(--blue)';
|
|
38
|
+
return '<tr>' +
|
|
39
|
+
'<td><strong>' + esc(item.name) + '</strong></td>' +
|
|
40
|
+
'<td style="font-family:\'Consolas\',monospace;font-size:10px;color:var(--muted2)">' + esc(item.filePath || '') + '</td>' +
|
|
41
|
+
'<td class="loc-cell" style="' + sizeColor + '">' + esc(item.size || '') + '</td>' +
|
|
42
|
+
'<td style="color:var(--muted2);font-size:11px">' + esc(item.purpose || '') + '</td>' +
|
|
43
|
+
'</tr>';
|
|
44
|
+
}).join('');
|
|
45
|
+
return '<section id="domains"><div class="sl">Component Inventory</div>' +
|
|
46
|
+
'<div class="tw"><table><thead><tr><th>Component</th><th>File(s)</th><th>Lines</th><th>Purpose</th></tr></thead>' +
|
|
47
|
+
'<tbody>' + rows + '</tbody></table></div></section>';
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function buildDiagramSection(d) {
|
|
51
|
+
const secId = 'diagram-' + d.type;
|
|
52
|
+
const cardTitle = esc(d.title + (d.typeBadge ? ' \u2014 ' + d.typeBadge : ''));
|
|
53
|
+
let diagramContent;
|
|
54
|
+
if (d.svgContent && !d.svgContent.includes('diagram-placeholder')) {
|
|
55
|
+
diagramContent = '<div style="width:100%;overflow:auto">' + d.svgContent + '</div>';
|
|
56
|
+
} else {
|
|
57
|
+
diagramContent = d.svgContent ||
|
|
58
|
+
'<div class="diagram-placeholder"><p>Diagram unavailable</p></div>';
|
|
59
|
+
}
|
|
60
|
+
return '<section id="' + secId + '" class="diagram-section">' +
|
|
61
|
+
'<div class="sl">' + esc(d.title) + '</div>' +
|
|
62
|
+
'<div class="dc" data-title="' + cardTitle + '">' +
|
|
63
|
+
'<div class="dc-h"><div class="dc-hl">' +
|
|
64
|
+
'<span class="dc-t">' + esc(d.title) + '</span>' +
|
|
65
|
+
'<span class="dc-tag">' + esc(d.typeBadge) + '</span>' +
|
|
66
|
+
'</div><button class="btn-exp" onclick="expandDiagram(this)">⛶<span class="lbl">Expand</span></button>' +
|
|
67
|
+
'</div>' +
|
|
68
|
+
'<div class="dc-b">' + diagramContent + '</div>' +
|
|
69
|
+
'<div class="dc-n">' + esc(d.note) + '</div>' +
|
|
70
|
+
'</div></section>';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function buildTechDebt(d) {
|
|
74
|
+
d = d || {};
|
|
75
|
+
const items = d.techDebt || [];
|
|
76
|
+
if (!items.length) {
|
|
77
|
+
return '<section id="tech-debt"><div class="sl">Tech Debt Register</div>' +
|
|
78
|
+
'<p style="color:var(--muted2);font-size:12px">No open tech debt items.</p></section>';
|
|
79
|
+
}
|
|
80
|
+
const sevClass = { critical: 'c', high: 'h', medium: 'm', low: 'l', info: 'i' };
|
|
81
|
+
const rows = items.map(i => {
|
|
82
|
+
const sc = sevClass[(i.severity || '').toLowerCase()] || 'm';
|
|
83
|
+
return '<tr>' +
|
|
84
|
+
'<td><span class="bx ' + sc + '">' + esc(i.severity || '') + '</span></td>' +
|
|
85
|
+
'<td style="font-family:\'Consolas\',monospace;font-size:11px;color:var(--blue)">' + esc(i.domain || '') + '</td>' +
|
|
86
|
+
'<td>' + esc(i.issue || '') + '</td>' +
|
|
87
|
+
'<td><code>' + esc(i.location || '') + '</code></td>' +
|
|
88
|
+
'<td style="color:var(--muted2);font-size:11px">' + esc(i.effort || '') + '</td>' +
|
|
89
|
+
'</tr>';
|
|
90
|
+
}).join('');
|
|
91
|
+
return '<section id="tech-debt"><div class="sl">Tech Debt Register</div>' +
|
|
92
|
+
'<div class="tw"><table><thead><tr><th>Severity</th><th>Domain</th><th>Issue</th><th>Location</th><th>Effort</th></tr></thead>' +
|
|
93
|
+
'<tbody>' + rows + '</tbody></table></div></section>';
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const ICONS = {
|
|
97
|
+
security: '🛑', architecture: '⚡', reliability: '📊',
|
|
98
|
+
quality: '📄', performance: '⚠', strength: '✅', default: '📋'
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
function buildFindings(d) {
|
|
102
|
+
d = d || {};
|
|
103
|
+
const findings = d.findings || [];
|
|
104
|
+
if (!findings.length) {
|
|
105
|
+
return '<section id="findings"><div class="sl">Key Findings</div>' +
|
|
106
|
+
'<p style="color:var(--muted2);font-size:12px">No findings recorded.</p></section>';
|
|
107
|
+
}
|
|
108
|
+
const cards = findings.map(f => {
|
|
109
|
+
const cat = (f.category || '').toLowerCase();
|
|
110
|
+
const ico = ICONS[cat] || ICONS.default;
|
|
111
|
+
const rec = f.recommendation
|
|
112
|
+
? ' <strong style="color:var(--text)">Fix:</strong> ' + esc(f.recommendation) : '';
|
|
113
|
+
return '<div class="fi"><div class="ico">' + ico + '</div>' +
|
|
114
|
+
'<div><h4>' + esc(f.title || '') + '</h4>' +
|
|
115
|
+
'<p>' + esc(f.description || '') + rec + '</p>' +
|
|
116
|
+
'</div></div>';
|
|
117
|
+
}).join('');
|
|
118
|
+
return '<section id="findings"><div class="sl">Key Findings</div><div class="fl">' + cards + '</div></section>';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
module.exports = { buildMetricCards, buildDomainHealth, buildDiagramSection, buildTechDebt, buildFindings };
|