@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
|
@@ -1,153 +1,153 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
function read(filePath) {
|
|
6
|
-
try { return fs.readFileSync(filePath, 'utf8'); } catch { return ''; }
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
function parseDebtSummary(text) {
|
|
10
|
-
const n = (pattern) => { const m = text.match(pattern); return m ? parseInt(m[1], 10) : 0; };
|
|
11
|
-
return {
|
|
12
|
-
debtCritical: n(/Critical items?:\s*(\d+)/i),
|
|
13
|
-
debtHigh: n(/High priority:\s*(\d+)/i),
|
|
14
|
-
debtMedium: n(/Medium priority:\s*(\d+)/i)
|
|
15
|
-
};
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function parseTestCoverage(text) {
|
|
19
|
-
const total = text.match(/Total tests\s*\|\s*(\d+)/i);
|
|
20
|
-
const passing = text.match(/Passing\s*\|\s*(\d+)/i);
|
|
21
|
-
if (total && passing) return parseInt(passing[1], 10) + '/' + parseInt(total[1], 10);
|
|
22
|
-
return 'N/A';
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function parseFilesAndLoc(text) {
|
|
26
|
-
const m = text.match(/\|\s*\*?\*?(?:Grand\s+)?Total[^|]*\*?\*?\s*\|\s*\*?\*?(\d+)\s+files?\*?\*?\s*\|\s*\*?\*?([\d,]+)[^|]*\*?\*?\s*\|/i);
|
|
27
|
-
if (m) return { filesScanned: parseInt(m[1], 10), totalLoc: parseInt(m[2].replace(/,/g, ''), 10) };
|
|
28
|
-
return { filesScanned: 0, totalLoc: 0 };
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function parseComponents(text) {
|
|
32
|
-
const sec = text.match(/## Component Inventory([\s\S]*?)(?=\n## |\n---|\n#[^#]|$)/);
|
|
33
|
-
if (!sec) return [];
|
|
34
|
-
return sec[1].split('\n')
|
|
35
|
-
.filter(l => /^\|/.test(l) && !/---/.test(l) && !/Component.*File/i.test(l))
|
|
36
|
-
.map(row => {
|
|
37
|
-
const cols = row.split('|').map(c => c.trim().replace(/\*\*/g, '').replace(/`/g, '')).filter(Boolean);
|
|
38
|
-
if (cols.length < 3) return null;
|
|
39
|
-
const name = cols[0];
|
|
40
|
-
if (!name || /^total/i.test(name)) return null;
|
|
41
|
-
return { name, filePath: cols[1] || '', size: cols[2] || '', purpose: cols[3] || '', files: 1, healthScore: 80 };
|
|
42
|
-
})
|
|
43
|
-
.filter(Boolean);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function parseSeverityMap(text) {
|
|
47
|
-
const map = {};
|
|
48
|
-
const high = text.match(/High priority:[^\n]*\(([^)]+)\)/i);
|
|
49
|
-
if (high) {
|
|
50
|
-
high[1].split(',').forEach(part => { const m = part.trim().match(/TD-\d+/); if (m) map[m[0]] = 'high'; });
|
|
51
|
-
}
|
|
52
|
-
const med = text.match(/Medium priority:[^\n]*\(([^)]+)\)/i);
|
|
53
|
-
if (med) {
|
|
54
|
-
med[1].split(',').forEach(part => {
|
|
55
|
-
const r = part.trim().match(/TD-(\d+)[–\-](\d+)/);
|
|
56
|
-
if (r) {
|
|
57
|
-
for (let i = parseInt(r[1]); i <= parseInt(r[2]); i++) {
|
|
58
|
-
map['TD-' + String(i).padStart(3, '0')] = 'medium';
|
|
59
|
-
}
|
|
60
|
-
} else { const m = part.trim().match(/TD-\d+/); if (m) map[m[0]] = 'medium'; }
|
|
61
|
-
});
|
|
62
|
-
}
|
|
63
|
-
return map;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function parseTechDebtItems(qualText, debtText) {
|
|
67
|
-
if (!qualText) return [];
|
|
68
|
-
const severityMap = parseSeverityMap(debtText || '');
|
|
69
|
-
const tableMatch = qualText.match(/\| ID \| Title \| Status \|([\s\S]*?)(?=\n---|\n## |$)/i);
|
|
70
|
-
if (!tableMatch) return [];
|
|
71
|
-
return tableMatch[1].split('\n')
|
|
72
|
-
.filter(l => /^\|/.test(l) && !/---/.test(l) && !/\| ID \|/i.test(l))
|
|
73
|
-
.map(row => {
|
|
74
|
-
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
75
|
-
if (cols.length < 3) return null;
|
|
76
|
-
const id = cols[0]; const title = cols[1];
|
|
77
|
-
if (!cols[2].toUpperCase().includes('OPEN')) return null;
|
|
78
|
-
return { severity: severityMap[id] || 'low', domain: id, issue: title, location: '', effort: '' };
|
|
79
|
-
})
|
|
80
|
-
.filter(Boolean).slice(0, 20);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function parseSecurityFindings(text) {
|
|
84
|
-
if (!text) return [];
|
|
85
|
-
const findings = [];
|
|
86
|
-
for (const sec of text.split(/\n### /).slice(1)) {
|
|
87
|
-
const titleLine = sec.split('\n')[0];
|
|
88
|
-
if (!/SEC-[HM]\d+/.test(titleLine)) continue;
|
|
89
|
-
const idM = titleLine.match(/(SEC-[HM]\d+)/);
|
|
90
|
-
const nameM = titleLine.match(/SEC-[HM]\d+:\s*(.+?)(?:\s+[-–][-–]|\s*$)/);
|
|
91
|
-
const detM = sec.match(/- \*\*Details\*\*:\s*(.+?)(?=\n-|\n\n|$)/s);
|
|
92
|
-
const fixM = sec.match(/- \*\*Fix\*\*:\s*(.+?)(?=\n-|\n\n|$)/s);
|
|
93
|
-
findings.push({
|
|
94
|
-
category: /SEC-H/.test(titleLine) ? 'Security' : 'Security',
|
|
95
|
-
severity: /SEC-H/.test(titleLine) ? 'high' : 'medium',
|
|
96
|
-
title: (idM ? idM[1] : '') + (nameM ? ': ' + nameM[1].trim() : ''),
|
|
97
|
-
description: detM ? detM[1].trim().replace(/\n/g, ' ') : '',
|
|
98
|
-
recommendation: fixM ? fixM[1].trim().replace(/\n/g, ' ') : ''
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
return findings;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function parseQualityFindings(text) {
|
|
105
|
-
if (!text) return [];
|
|
106
|
-
const findings = [];
|
|
107
|
-
for (const sec of text.split(/\n### /).slice(1)) {
|
|
108
|
-
const titleLine = sec.split('\n')[0];
|
|
109
|
-
const idM = titleLine.match(/((?:DC|TCG|TD)-[A-Z\-\d]+)/);
|
|
110
|
-
const nameM = titleLine.match(/(?:DC|TCG|TD)-[A-Z\-\d]+:\s*(.+?)(?:\s*$)/);
|
|
111
|
-
const locM = sec.match(/^`([^`]+)`/m);
|
|
112
|
-
const detM = sec.match(/\n(.+?)\n- \*\*Impact\*\*/s);
|
|
113
|
-
const sugM = sec.match(/- \*\*Suggestion\*\*:\s*(.+?)(?=\n-|\n\n|$)/s);
|
|
114
|
-
if (!idM) continue;
|
|
115
|
-
findings.push({
|
|
116
|
-
category: 'Quality',
|
|
117
|
-
severity: 'medium',
|
|
118
|
-
title: (idM ? idM[1] : '') + (nameM ? ': ' + nameM[1].trim() : ''),
|
|
119
|
-
description: locM ? locM[1] : (detM ? detM[1].trim() : ''),
|
|
120
|
-
recommendation: sugM ? sugM[1].trim().replace(/\n/g, ' ') : 'Review and schedule remediation'
|
|
121
|
-
});
|
|
122
|
-
}
|
|
123
|
-
return findings.slice(0, 3);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
function collectScanData(projectRoot) {
|
|
127
|
-
const scanDir = path.join(projectRoot, '.gsd-t', 'scan');
|
|
128
|
-
const rs = (f) => read(path.join(scanDir, f));
|
|
129
|
-
const rr = (f) => read(path.join(projectRoot, f));
|
|
130
|
-
|
|
131
|
-
const archText = rs('architecture.md');
|
|
132
|
-
const testText = rs('test-baseline.md');
|
|
133
|
-
const secText = rs('security.md');
|
|
134
|
-
const qualText = rs('quality.md');
|
|
135
|
-
const debtText = rr('.gsd-t/techdebt.md');
|
|
136
|
-
|
|
137
|
-
let projectName = path.basename(projectRoot);
|
|
138
|
-
try { projectName = JSON.parse(rr('package.json')).name || projectName; } catch {}
|
|
139
|
-
|
|
140
|
-
const { filesScanned, totalLoc } = parseFilesAndLoc(archText);
|
|
141
|
-
const { debtCritical, debtHigh, debtMedium } = parseDebtSummary(debtText);
|
|
142
|
-
const testCoverage = parseTestCoverage(testText);
|
|
143
|
-
const domains = parseComponents(archText);
|
|
144
|
-
const techDebt = parseTechDebtItems(qualText, debtText);
|
|
145
|
-
const secFinds = parseSecurityFindings(secText);
|
|
146
|
-
const qualFinds = parseQualityFindings(qualText);
|
|
147
|
-
const findings = secFinds.concat(qualFinds).slice(0, 10);
|
|
148
|
-
|
|
149
|
-
return { projectName, filesScanned, totalLoc, debtCritical, debtHigh, debtMedium,
|
|
150
|
-
testCoverage, domains, techDebt, findings };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
module.exports = { collectScanData };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
function read(filePath) {
|
|
6
|
+
try { return fs.readFileSync(filePath, 'utf8'); } catch { return ''; }
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function parseDebtSummary(text) {
|
|
10
|
+
const n = (pattern) => { const m = text.match(pattern); return m ? parseInt(m[1], 10) : 0; };
|
|
11
|
+
return {
|
|
12
|
+
debtCritical: n(/Critical items?:\s*(\d+)/i),
|
|
13
|
+
debtHigh: n(/High priority:\s*(\d+)/i),
|
|
14
|
+
debtMedium: n(/Medium priority:\s*(\d+)/i)
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function parseTestCoverage(text) {
|
|
19
|
+
const total = text.match(/Total tests\s*\|\s*(\d+)/i);
|
|
20
|
+
const passing = text.match(/Passing\s*\|\s*(\d+)/i);
|
|
21
|
+
if (total && passing) return parseInt(passing[1], 10) + '/' + parseInt(total[1], 10);
|
|
22
|
+
return 'N/A';
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseFilesAndLoc(text) {
|
|
26
|
+
const m = text.match(/\|\s*\*?\*?(?:Grand\s+)?Total[^|]*\*?\*?\s*\|\s*\*?\*?(\d+)\s+files?\*?\*?\s*\|\s*\*?\*?([\d,]+)[^|]*\*?\*?\s*\|/i);
|
|
27
|
+
if (m) return { filesScanned: parseInt(m[1], 10), totalLoc: parseInt(m[2].replace(/,/g, ''), 10) };
|
|
28
|
+
return { filesScanned: 0, totalLoc: 0 };
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseComponents(text) {
|
|
32
|
+
const sec = text.match(/## Component Inventory([\s\S]*?)(?=\n## |\n---|\n#[^#]|$)/);
|
|
33
|
+
if (!sec) return [];
|
|
34
|
+
return sec[1].split('\n')
|
|
35
|
+
.filter(l => /^\|/.test(l) && !/---/.test(l) && !/Component.*File/i.test(l))
|
|
36
|
+
.map(row => {
|
|
37
|
+
const cols = row.split('|').map(c => c.trim().replace(/\*\*/g, '').replace(/`/g, '')).filter(Boolean);
|
|
38
|
+
if (cols.length < 3) return null;
|
|
39
|
+
const name = cols[0];
|
|
40
|
+
if (!name || /^total/i.test(name)) return null;
|
|
41
|
+
return { name, filePath: cols[1] || '', size: cols[2] || '', purpose: cols[3] || '', files: 1, healthScore: 80 };
|
|
42
|
+
})
|
|
43
|
+
.filter(Boolean);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function parseSeverityMap(text) {
|
|
47
|
+
const map = {};
|
|
48
|
+
const high = text.match(/High priority:[^\n]*\(([^)]+)\)/i);
|
|
49
|
+
if (high) {
|
|
50
|
+
high[1].split(',').forEach(part => { const m = part.trim().match(/TD-\d+/); if (m) map[m[0]] = 'high'; });
|
|
51
|
+
}
|
|
52
|
+
const med = text.match(/Medium priority:[^\n]*\(([^)]+)\)/i);
|
|
53
|
+
if (med) {
|
|
54
|
+
med[1].split(',').forEach(part => {
|
|
55
|
+
const r = part.trim().match(/TD-(\d+)[–\-](\d+)/);
|
|
56
|
+
if (r) {
|
|
57
|
+
for (let i = parseInt(r[1]); i <= parseInt(r[2]); i++) {
|
|
58
|
+
map['TD-' + String(i).padStart(3, '0')] = 'medium';
|
|
59
|
+
}
|
|
60
|
+
} else { const m = part.trim().match(/TD-\d+/); if (m) map[m[0]] = 'medium'; }
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function parseTechDebtItems(qualText, debtText) {
|
|
67
|
+
if (!qualText) return [];
|
|
68
|
+
const severityMap = parseSeverityMap(debtText || '');
|
|
69
|
+
const tableMatch = qualText.match(/\| ID \| Title \| Status \|([\s\S]*?)(?=\n---|\n## |$)/i);
|
|
70
|
+
if (!tableMatch) return [];
|
|
71
|
+
return tableMatch[1].split('\n')
|
|
72
|
+
.filter(l => /^\|/.test(l) && !/---/.test(l) && !/\| ID \|/i.test(l))
|
|
73
|
+
.map(row => {
|
|
74
|
+
const cols = row.split('|').map(c => c.trim()).filter(Boolean);
|
|
75
|
+
if (cols.length < 3) return null;
|
|
76
|
+
const id = cols[0]; const title = cols[1];
|
|
77
|
+
if (!cols[2].toUpperCase().includes('OPEN')) return null;
|
|
78
|
+
return { severity: severityMap[id] || 'low', domain: id, issue: title, location: '', effort: '' };
|
|
79
|
+
})
|
|
80
|
+
.filter(Boolean).slice(0, 20);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function parseSecurityFindings(text) {
|
|
84
|
+
if (!text) return [];
|
|
85
|
+
const findings = [];
|
|
86
|
+
for (const sec of text.split(/\n### /).slice(1)) {
|
|
87
|
+
const titleLine = sec.split('\n')[0];
|
|
88
|
+
if (!/SEC-[HM]\d+/.test(titleLine)) continue;
|
|
89
|
+
const idM = titleLine.match(/(SEC-[HM]\d+)/);
|
|
90
|
+
const nameM = titleLine.match(/SEC-[HM]\d+:\s*(.+?)(?:\s+[-–][-–]|\s*$)/);
|
|
91
|
+
const detM = sec.match(/- \*\*Details\*\*:\s*(.+?)(?=\n-|\n\n|$)/s);
|
|
92
|
+
const fixM = sec.match(/- \*\*Fix\*\*:\s*(.+?)(?=\n-|\n\n|$)/s);
|
|
93
|
+
findings.push({
|
|
94
|
+
category: /SEC-H/.test(titleLine) ? 'Security' : 'Security',
|
|
95
|
+
severity: /SEC-H/.test(titleLine) ? 'high' : 'medium',
|
|
96
|
+
title: (idM ? idM[1] : '') + (nameM ? ': ' + nameM[1].trim() : ''),
|
|
97
|
+
description: detM ? detM[1].trim().replace(/\n/g, ' ') : '',
|
|
98
|
+
recommendation: fixM ? fixM[1].trim().replace(/\n/g, ' ') : ''
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return findings;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseQualityFindings(text) {
|
|
105
|
+
if (!text) return [];
|
|
106
|
+
const findings = [];
|
|
107
|
+
for (const sec of text.split(/\n### /).slice(1)) {
|
|
108
|
+
const titleLine = sec.split('\n')[0];
|
|
109
|
+
const idM = titleLine.match(/((?:DC|TCG|TD)-[A-Z\-\d]+)/);
|
|
110
|
+
const nameM = titleLine.match(/(?:DC|TCG|TD)-[A-Z\-\d]+:\s*(.+?)(?:\s*$)/);
|
|
111
|
+
const locM = sec.match(/^`([^`]+)`/m);
|
|
112
|
+
const detM = sec.match(/\n(.+?)\n- \*\*Impact\*\*/s);
|
|
113
|
+
const sugM = sec.match(/- \*\*Suggestion\*\*:\s*(.+?)(?=\n-|\n\n|$)/s);
|
|
114
|
+
if (!idM) continue;
|
|
115
|
+
findings.push({
|
|
116
|
+
category: 'Quality',
|
|
117
|
+
severity: 'medium',
|
|
118
|
+
title: (idM ? idM[1] : '') + (nameM ? ': ' + nameM[1].trim() : ''),
|
|
119
|
+
description: locM ? locM[1] : (detM ? detM[1].trim() : ''),
|
|
120
|
+
recommendation: sugM ? sugM[1].trim().replace(/\n/g, ' ') : 'Review and schedule remediation'
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
return findings.slice(0, 3);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function collectScanData(projectRoot) {
|
|
127
|
+
const scanDir = path.join(projectRoot, '.gsd-t', 'scan');
|
|
128
|
+
const rs = (f) => read(path.join(scanDir, f));
|
|
129
|
+
const rr = (f) => read(path.join(projectRoot, f));
|
|
130
|
+
|
|
131
|
+
const archText = rs('architecture.md');
|
|
132
|
+
const testText = rs('test-baseline.md');
|
|
133
|
+
const secText = rs('security.md');
|
|
134
|
+
const qualText = rs('quality.md');
|
|
135
|
+
const debtText = rr('.gsd-t/techdebt.md');
|
|
136
|
+
|
|
137
|
+
let projectName = path.basename(projectRoot);
|
|
138
|
+
try { projectName = JSON.parse(rr('package.json')).name || projectName; } catch {}
|
|
139
|
+
|
|
140
|
+
const { filesScanned, totalLoc } = parseFilesAndLoc(archText);
|
|
141
|
+
const { debtCritical, debtHigh, debtMedium } = parseDebtSummary(debtText);
|
|
142
|
+
const testCoverage = parseTestCoverage(testText);
|
|
143
|
+
const domains = parseComponents(archText);
|
|
144
|
+
const techDebt = parseTechDebtItems(qualText, debtText);
|
|
145
|
+
const secFinds = parseSecurityFindings(secText);
|
|
146
|
+
const qualFinds = parseQualityFindings(qualText);
|
|
147
|
+
const findings = secFinds.concat(qualFinds).slice(0, 10);
|
|
148
|
+
|
|
149
|
+
return { projectName, filesScanned, totalLoc, debtCritical, debtHigh, debtMedium,
|
|
150
|
+
testCoverage, domains, techDebt, findings };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
module.exports = { collectScanData };
|