@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/graph-store.js
CHANGED
|
@@ -1,154 +1,154 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const crypto = require('crypto');
|
|
5
|
-
|
|
6
|
-
const GRAPH_DIR = '.gsd-t/graph';
|
|
7
|
-
const FILES = {
|
|
8
|
-
index: 'index.json',
|
|
9
|
-
calls: 'calls.json',
|
|
10
|
-
imports: 'imports.json',
|
|
11
|
-
contracts: 'contracts.json',
|
|
12
|
-
requirements: 'requirements.json',
|
|
13
|
-
tests: 'tests.json',
|
|
14
|
-
surfaces: 'surfaces.json',
|
|
15
|
-
meta: 'meta.json'
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
function getGraphDir(projectRoot) {
|
|
19
|
-
return path.join(projectRoot, GRAPH_DIR);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function isSymlink(p) {
|
|
23
|
-
try { return fs.lstatSync(p).isSymbolicLink(); } catch { return false; }
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function ensureDir(projectRoot) {
|
|
27
|
-
const dir = getGraphDir(projectRoot);
|
|
28
|
-
if (isSymlink(dir)) throw new Error('Graph directory is a symlink: ' + dir);
|
|
29
|
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
30
|
-
return dir;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function readFile(projectRoot, fileName) {
|
|
34
|
-
try {
|
|
35
|
-
const fp = path.join(getGraphDir(projectRoot), fileName);
|
|
36
|
-
if (isSymlink(fp)) return null;
|
|
37
|
-
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
38
|
-
} catch { return null; }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function writeFile(projectRoot, fileName, data) {
|
|
42
|
-
const dir = ensureDir(projectRoot);
|
|
43
|
-
const fp = path.join(dir, fileName);
|
|
44
|
-
if (isSymlink(fp)) throw new Error('Graph file is a symlink: ' + fp);
|
|
45
|
-
fs.writeFileSync(fp, JSON.stringify(data, null, 2), 'utf8');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function readIndex(root) {
|
|
49
|
-
return readFile(root, FILES.index) || { entities: [] };
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function readCalls(root) {
|
|
53
|
-
return readFile(root, FILES.calls) || { edges: [] };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function readImports(root) {
|
|
57
|
-
return readFile(root, FILES.imports) || { edges: [] };
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function readContracts(root) {
|
|
61
|
-
return readFile(root, FILES.contracts) || { mappings: [] };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function readRequirements(root) {
|
|
65
|
-
return readFile(root, FILES.requirements) || { mappings: [] };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function readTests(root) {
|
|
69
|
-
return readFile(root, FILES.tests) || { mappings: [] };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function readSurfaces(root) {
|
|
73
|
-
return readFile(root, FILES.surfaces) || { mappings: [] };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function readMeta(root) {
|
|
77
|
-
return readFile(root, FILES.meta);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function writeIndex(root, data) {
|
|
81
|
-
writeFile(root, FILES.index, data);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
function writeCalls(root, data) {
|
|
85
|
-
writeFile(root, FILES.calls, data);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function writeImports(root, data) {
|
|
89
|
-
writeFile(root, FILES.imports, data);
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function writeContracts(root, data) {
|
|
93
|
-
writeFile(root, FILES.contracts, data);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function writeRequirements(root, data) {
|
|
97
|
-
writeFile(root, FILES.requirements, data);
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
function writeTests(root, data) {
|
|
101
|
-
writeFile(root, FILES.tests, data);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function writeSurfaces(root, data) {
|
|
105
|
-
writeFile(root, FILES.surfaces, data);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function writeMeta(root, data) {
|
|
109
|
-
writeFile(root, FILES.meta, data);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function hashContent(content) {
|
|
113
|
-
return crypto.createHash('md5').update(content).digest('hex');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function hashFile(filePath) {
|
|
117
|
-
try {
|
|
118
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
119
|
-
return hashContent(content);
|
|
120
|
-
} catch { return null; }
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isStale(root, sourceFiles) {
|
|
124
|
-
const meta = readMeta(root);
|
|
125
|
-
if (!meta || !meta.fileHashes) {
|
|
126
|
-
return { stale: true, changedFiles: sourceFiles };
|
|
127
|
-
}
|
|
128
|
-
const changed = [];
|
|
129
|
-
for (const f of sourceFiles) {
|
|
130
|
-
const hash = hashFile(f);
|
|
131
|
-
const rel = path.relative(root, f);
|
|
132
|
-
if (hash !== meta.fileHashes[rel]) changed.push(f);
|
|
133
|
-
}
|
|
134
|
-
return { stale: changed.length > 0, changedFiles: changed };
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
function clear(root) {
|
|
138
|
-
const dir = getGraphDir(root);
|
|
139
|
-
if (!fs.existsSync(dir)) return;
|
|
140
|
-
for (const name of Object.values(FILES)) {
|
|
141
|
-
const fp = path.join(dir, name);
|
|
142
|
-
try { fs.unlinkSync(fp); } catch { /* ignore */ }
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
module.exports = {
|
|
147
|
-
getGraphDir, ensureDir,
|
|
148
|
-
readIndex, readCalls, readImports, readContracts,
|
|
149
|
-
readRequirements, readTests, readSurfaces, readMeta,
|
|
150
|
-
writeIndex, writeCalls, writeImports, writeContracts,
|
|
151
|
-
writeRequirements, writeTests, writeSurfaces, writeMeta,
|
|
152
|
-
hashContent, hashFile, isStale, clear,
|
|
153
|
-
FILES, GRAPH_DIR
|
|
154
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const crypto = require('crypto');
|
|
5
|
+
|
|
6
|
+
const GRAPH_DIR = '.gsd-t/graph';
|
|
7
|
+
const FILES = {
|
|
8
|
+
index: 'index.json',
|
|
9
|
+
calls: 'calls.json',
|
|
10
|
+
imports: 'imports.json',
|
|
11
|
+
contracts: 'contracts.json',
|
|
12
|
+
requirements: 'requirements.json',
|
|
13
|
+
tests: 'tests.json',
|
|
14
|
+
surfaces: 'surfaces.json',
|
|
15
|
+
meta: 'meta.json'
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function getGraphDir(projectRoot) {
|
|
19
|
+
return path.join(projectRoot, GRAPH_DIR);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function isSymlink(p) {
|
|
23
|
+
try { return fs.lstatSync(p).isSymbolicLink(); } catch { return false; }
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function ensureDir(projectRoot) {
|
|
27
|
+
const dir = getGraphDir(projectRoot);
|
|
28
|
+
if (isSymlink(dir)) throw new Error('Graph directory is a symlink: ' + dir);
|
|
29
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
30
|
+
return dir;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function readFile(projectRoot, fileName) {
|
|
34
|
+
try {
|
|
35
|
+
const fp = path.join(getGraphDir(projectRoot), fileName);
|
|
36
|
+
if (isSymlink(fp)) return null;
|
|
37
|
+
return JSON.parse(fs.readFileSync(fp, 'utf8'));
|
|
38
|
+
} catch { return null; }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function writeFile(projectRoot, fileName, data) {
|
|
42
|
+
const dir = ensureDir(projectRoot);
|
|
43
|
+
const fp = path.join(dir, fileName);
|
|
44
|
+
if (isSymlink(fp)) throw new Error('Graph file is a symlink: ' + fp);
|
|
45
|
+
fs.writeFileSync(fp, JSON.stringify(data, null, 2), 'utf8');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function readIndex(root) {
|
|
49
|
+
return readFile(root, FILES.index) || { entities: [] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function readCalls(root) {
|
|
53
|
+
return readFile(root, FILES.calls) || { edges: [] };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function readImports(root) {
|
|
57
|
+
return readFile(root, FILES.imports) || { edges: [] };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function readContracts(root) {
|
|
61
|
+
return readFile(root, FILES.contracts) || { mappings: [] };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function readRequirements(root) {
|
|
65
|
+
return readFile(root, FILES.requirements) || { mappings: [] };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function readTests(root) {
|
|
69
|
+
return readFile(root, FILES.tests) || { mappings: [] };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function readSurfaces(root) {
|
|
73
|
+
return readFile(root, FILES.surfaces) || { mappings: [] };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function readMeta(root) {
|
|
77
|
+
return readFile(root, FILES.meta);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function writeIndex(root, data) {
|
|
81
|
+
writeFile(root, FILES.index, data);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function writeCalls(root, data) {
|
|
85
|
+
writeFile(root, FILES.calls, data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function writeImports(root, data) {
|
|
89
|
+
writeFile(root, FILES.imports, data);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function writeContracts(root, data) {
|
|
93
|
+
writeFile(root, FILES.contracts, data);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function writeRequirements(root, data) {
|
|
97
|
+
writeFile(root, FILES.requirements, data);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function writeTests(root, data) {
|
|
101
|
+
writeFile(root, FILES.tests, data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function writeSurfaces(root, data) {
|
|
105
|
+
writeFile(root, FILES.surfaces, data);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function writeMeta(root, data) {
|
|
109
|
+
writeFile(root, FILES.meta, data);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function hashContent(content) {
|
|
113
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function hashFile(filePath) {
|
|
117
|
+
try {
|
|
118
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
119
|
+
return hashContent(content);
|
|
120
|
+
} catch { return null; }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isStale(root, sourceFiles) {
|
|
124
|
+
const meta = readMeta(root);
|
|
125
|
+
if (!meta || !meta.fileHashes) {
|
|
126
|
+
return { stale: true, changedFiles: sourceFiles };
|
|
127
|
+
}
|
|
128
|
+
const changed = [];
|
|
129
|
+
for (const f of sourceFiles) {
|
|
130
|
+
const hash = hashFile(f);
|
|
131
|
+
const rel = path.relative(root, f);
|
|
132
|
+
if (hash !== meta.fileHashes[rel]) changed.push(f);
|
|
133
|
+
}
|
|
134
|
+
return { stale: changed.length > 0, changedFiles: changed };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function clear(root) {
|
|
138
|
+
const dir = getGraphDir(root);
|
|
139
|
+
if (!fs.existsSync(dir)) return;
|
|
140
|
+
for (const name of Object.values(FILES)) {
|
|
141
|
+
const fp = path.join(dir, name);
|
|
142
|
+
try { fs.unlinkSync(fp); } catch { /* ignore */ }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
module.exports = {
|
|
147
|
+
getGraphDir, ensureDir,
|
|
148
|
+
readIndex, readCalls, readImports, readContracts,
|
|
149
|
+
readRequirements, readTests, readSurfaces, readMeta,
|
|
150
|
+
writeIndex, writeCalls, writeImports, writeContracts,
|
|
151
|
+
writeRequirements, writeTests, writeSurfaces, writeMeta,
|
|
152
|
+
hashContent, hashFile, isStale, clear,
|
|
153
|
+
FILES, GRAPH_DIR
|
|
154
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* GSD-T QA Calibrator — Self-calibrating miss-rate tracking and QA injection
|
|
5
|
+
*
|
|
6
|
+
* Reads/writes .gsd-t/metrics/qa-miss-log.jsonl to track categories where
|
|
7
|
+
* Red Team finds bugs QA missed. Computes miss rates and generates targeted
|
|
8
|
+
* QA prompt injections for weak spots.
|
|
9
|
+
*
|
|
10
|
+
* Zero external dependencies (Node.js built-ins only).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require("fs");
|
|
14
|
+
const path = require("path");
|
|
15
|
+
|
|
16
|
+
// ── Constants ────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
const CATEGORIES = [
|
|
19
|
+
"contract-violation", "boundary-input", "state-transition",
|
|
20
|
+
"error-path", "missing-flow", "regression", "e2e-gap",
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const WEAK_SPOT_THRESHOLD = 0.30;
|
|
24
|
+
const PERSISTENT_MILESTONE_COUNT = 3;
|
|
25
|
+
|
|
26
|
+
// ── Exports ──────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
module.exports = {
|
|
29
|
+
logMiss, getCategoryMissRates, getWeakSpots,
|
|
30
|
+
generateQAInjection, getPersistentWeakSpots,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// ── logMiss ──────────────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Log a QA miss (Red Team found, QA missed)
|
|
37
|
+
* @param {object} miss - miss record matching schema
|
|
38
|
+
* @param {string} [projectDir]
|
|
39
|
+
*/
|
|
40
|
+
function logMiss(miss, projectDir) {
|
|
41
|
+
const fp = missLogPath(projectDir);
|
|
42
|
+
ensureDir(path.dirname(fp));
|
|
43
|
+
const record = { ts: new Date().toISOString(), ...miss };
|
|
44
|
+
fs.appendFileSync(fp, JSON.stringify(record) + "\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── getCategoryMissRates ─────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Compute per-category miss rates across recent milestones.
|
|
51
|
+
* missRate = category misses / total misses in window (relative share).
|
|
52
|
+
* @param {number} [windowSize=5]
|
|
53
|
+
* @param {string} [projectDir]
|
|
54
|
+
* @returns {object[]} [{ category, missRate, totalFindings, qaMissed }]
|
|
55
|
+
*/
|
|
56
|
+
function getCategoryMissRates(windowSize, projectDir) {
|
|
57
|
+
const win = typeof windowSize === "number" ? windowSize : 5;
|
|
58
|
+
const records = loadMissLog(projectDir);
|
|
59
|
+
const milestones = getRecentMilestones(records, win);
|
|
60
|
+
const inWindow = records.filter((r) => milestones.includes(r.milestone));
|
|
61
|
+
const total = inWindow.length;
|
|
62
|
+
return CATEGORIES.map((cat) => {
|
|
63
|
+
const catRecs = inWindow.filter((r) => r.category === cat);
|
|
64
|
+
const qaMissed = catRecs.length;
|
|
65
|
+
const totalFindings = qaMissed;
|
|
66
|
+
const missRate = total > 0 ? qaMissed / total : 0;
|
|
67
|
+
return { category: cat, missRate, totalFindings, qaMissed };
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── getWeakSpots ─────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Get current weak spots (categories with >30% miss rate)
|
|
75
|
+
* @param {number} [windowSize=5]
|
|
76
|
+
* @param {string} [projectDir]
|
|
77
|
+
* @returns {object[]} [{ category, missRate, recentExamples: string[] }]
|
|
78
|
+
*/
|
|
79
|
+
function getWeakSpots(windowSize, projectDir) {
|
|
80
|
+
const win = typeof windowSize === "number" ? windowSize : 5;
|
|
81
|
+
const records = loadMissLog(projectDir);
|
|
82
|
+
const milestones = getRecentMilestones(records, win);
|
|
83
|
+
const inWindow = records.filter((r) => milestones.includes(r.milestone));
|
|
84
|
+
const total = inWindow.length;
|
|
85
|
+
return CATEGORIES
|
|
86
|
+
.map((cat) => {
|
|
87
|
+
const catRecs = inWindow.filter((r) => r.category === cat);
|
|
88
|
+
const missRate = total > 0 ? catRecs.length / total : 0;
|
|
89
|
+
const recentExamples = catRecs.slice(-3).map((r) => r.description || r.task || "");
|
|
90
|
+
return { category: cat, missRate, recentExamples };
|
|
91
|
+
})
|
|
92
|
+
.filter((s) => s.missRate > WEAK_SPOT_THRESHOLD);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ── generateQAInjection ──────────────────────────────────────────────────────
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Generate QA prompt injection text for weak spots
|
|
99
|
+
* @param {number} [windowSize=5]
|
|
100
|
+
* @param {string} [projectDir]
|
|
101
|
+
* @returns {string} markdown text to inject, or "" if no weak spots
|
|
102
|
+
*/
|
|
103
|
+
function generateQAInjection(windowSize, projectDir) {
|
|
104
|
+
const spots = getWeakSpots(windowSize, projectDir);
|
|
105
|
+
if (spots.length === 0) return "";
|
|
106
|
+
const lines = spots.map((s) => {
|
|
107
|
+
const pct = Math.round(s.missRate * 100);
|
|
108
|
+
const label = categoryLabel(s.category);
|
|
109
|
+
const examples = s.recentExamples.filter(Boolean).slice(0, 2).join(", ");
|
|
110
|
+
const exStr = examples ? `: Recent misses: ${examples}` : "";
|
|
111
|
+
return `- **${s.category}** (${pct}% miss rate): ${label}${exStr}`;
|
|
112
|
+
});
|
|
113
|
+
return [
|
|
114
|
+
"## QA PRIORITY FOCUS AREAS (auto-calibrated)",
|
|
115
|
+
"",
|
|
116
|
+
"Your historical miss rate for these categories is elevated. Pay EXTRA attention:",
|
|
117
|
+
"",
|
|
118
|
+
...lines,
|
|
119
|
+
"",
|
|
120
|
+
"These are the areas where Red Team most often finds bugs you missed. Proving them clean is high-value.",
|
|
121
|
+
].join("\n");
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ── getPersistentWeakSpots ───────────────────────────────────────────────────
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Check if a weak spot should generate a permanent rule engine patch
|
|
128
|
+
* @param {string} [projectDir]
|
|
129
|
+
* @returns {object[]} categories with >30% miss rate for 3+ consecutive milestones
|
|
130
|
+
*/
|
|
131
|
+
function getPersistentWeakSpots(projectDir) {
|
|
132
|
+
const records = loadMissLog(projectDir);
|
|
133
|
+
const milestones = getRecentMilestones(records, Infinity);
|
|
134
|
+
if (milestones.length < PERSISTENT_MILESTONE_COUNT) return [];
|
|
135
|
+
return CATEGORIES.filter((cat) => {
|
|
136
|
+
let consecutive = 0;
|
|
137
|
+
let maxConsecutive = 0;
|
|
138
|
+
for (const ms of milestones) {
|
|
139
|
+
const msRecs = records.filter((r) => r.milestone === ms);
|
|
140
|
+
const catRecs = msRecs.filter((r) => r.category === cat);
|
|
141
|
+
const rate = msRecs.length > 0 ? catRecs.length / msRecs.length : 0;
|
|
142
|
+
if (rate > WEAK_SPOT_THRESHOLD) {
|
|
143
|
+
consecutive++;
|
|
144
|
+
maxConsecutive = Math.max(maxConsecutive, consecutive);
|
|
145
|
+
} else {
|
|
146
|
+
consecutive = 0;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return maxConsecutive >= PERSISTENT_MILESTONE_COUNT;
|
|
150
|
+
}).map((cat) => {
|
|
151
|
+
const catRecs = records.filter((r) => r.category === cat);
|
|
152
|
+
const total = records.length;
|
|
153
|
+
return { category: cat, missRate: total > 0 ? catRecs.length / total : 0 };
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// ── Helpers ──────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
function missLogPath(d) {
|
|
160
|
+
return path.join(d || process.cwd(), ".gsd-t", "metrics", "qa-miss-log.jsonl");
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function loadMissLog(projectDir) {
|
|
164
|
+
const fp = missLogPath(projectDir);
|
|
165
|
+
if (!fs.existsSync(fp)) return [];
|
|
166
|
+
const content = fs.readFileSync(fp, "utf8").trim();
|
|
167
|
+
if (!content) return [];
|
|
168
|
+
return content.split("\n").map(safeParse).filter(Boolean);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function getRecentMilestones(records, windowSize) {
|
|
172
|
+
const seen = [];
|
|
173
|
+
for (const r of records) {
|
|
174
|
+
if (r.milestone && !seen.includes(r.milestone)) seen.push(r.milestone);
|
|
175
|
+
}
|
|
176
|
+
return windowSize === Infinity ? seen : seen.slice(-windowSize);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
function categoryLabel(cat) {
|
|
181
|
+
const labels = {
|
|
182
|
+
"contract-violation": "Code doesn't match contract interface definition",
|
|
183
|
+
"boundary-input": "Edge cases and boundary values",
|
|
184
|
+
"state-transition": "Invalid state changes and missing state guards",
|
|
185
|
+
"error-path": "Error handling completeness",
|
|
186
|
+
"missing-flow": "Required user flows and code paths",
|
|
187
|
+
"regression": "Previously working functionality",
|
|
188
|
+
"e2e-gap": "End-to-end scenario coverage",
|
|
189
|
+
};
|
|
190
|
+
return labels[cat] || cat;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function safeParse(l) { try { return JSON.parse(l); } catch { return null; } }
|
|
194
|
+
function ensureDir(d) { if (!fs.existsSync(d)) fs.mkdirSync(d, { recursive: true }); }
|