@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-indexer.js
CHANGED
|
@@ -1,147 +1,147 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
const store = require('./graph-store');
|
|
5
|
-
const { getParser } = require('./graph-parsers');
|
|
6
|
-
const { buildOverlay } = require('./graph-overlay');
|
|
7
|
-
|
|
8
|
-
const DEFAULT_EXCLUDE = [
|
|
9
|
-
'node_modules', '.git', 'dist', 'build',
|
|
10
|
-
'coverage', '.gsd-t', '.claude', '__pycache__'
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
const SUPPORTED_EXTS = [
|
|
14
|
-
'.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.py'
|
|
15
|
-
];
|
|
16
|
-
|
|
17
|
-
function walkFiles(dir, exclude) {
|
|
18
|
-
const results = [];
|
|
19
|
-
try {
|
|
20
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
21
|
-
for (const e of entries) {
|
|
22
|
-
if (exclude.includes(e.name)) continue;
|
|
23
|
-
if (e.name.startsWith('.')) continue;
|
|
24
|
-
const full = path.join(dir, e.name);
|
|
25
|
-
if (e.isDirectory()) {
|
|
26
|
-
results.push(...walkFiles(full, exclude));
|
|
27
|
-
} else if (e.isFile()) {
|
|
28
|
-
const ext = path.extname(e.name);
|
|
29
|
-
if (SUPPORTED_EXTS.includes(ext)) {
|
|
30
|
-
results.push(full);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
} catch { /* skip inaccessible dirs */ }
|
|
35
|
-
return results;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function indexProject(projectRoot, options = {}) {
|
|
39
|
-
const start = Date.now();
|
|
40
|
-
const force = options.force || false;
|
|
41
|
-
const exclude = options.exclude || DEFAULT_EXCLUDE;
|
|
42
|
-
const errors = [];
|
|
43
|
-
|
|
44
|
-
// Find source files
|
|
45
|
-
const allFiles = walkFiles(projectRoot, exclude);
|
|
46
|
-
|
|
47
|
-
// Check staleness
|
|
48
|
-
if (!force) {
|
|
49
|
-
const { stale, changedFiles } = store.isStale(
|
|
50
|
-
projectRoot, allFiles
|
|
51
|
-
);
|
|
52
|
-
if (!stale) {
|
|
53
|
-
const meta = store.readMeta(projectRoot);
|
|
54
|
-
return {
|
|
55
|
-
success: true,
|
|
56
|
-
entityCount: meta ? meta.entityCount : 0,
|
|
57
|
-
relationshipCount: meta ? meta.relationshipCount : 0,
|
|
58
|
-
duration: Date.now() - start,
|
|
59
|
-
errors: [],
|
|
60
|
-
filesProcessed: 0,
|
|
61
|
-
filesSkipped: allFiles.length
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
// Parse all files
|
|
67
|
-
const allEntities = [];
|
|
68
|
-
const allImports = [];
|
|
69
|
-
const allCalls = [];
|
|
70
|
-
const fileHashes = {};
|
|
71
|
-
let filesProcessed = 0;
|
|
72
|
-
let filesSkipped = 0;
|
|
73
|
-
|
|
74
|
-
for (const filePath of allFiles) {
|
|
75
|
-
const ext = path.extname(filePath);
|
|
76
|
-
const parser = getParser(ext);
|
|
77
|
-
if (!parser) { filesSkipped++; continue; }
|
|
78
|
-
|
|
79
|
-
const rel = path.relative(projectRoot, filePath)
|
|
80
|
-
.replace(/\\/g, '/');
|
|
81
|
-
try {
|
|
82
|
-
const content = fs.readFileSync(filePath, 'utf8');
|
|
83
|
-
fileHashes[rel] = store.hashContent(content);
|
|
84
|
-
const result = parser(content, rel);
|
|
85
|
-
allEntities.push(...result.entities);
|
|
86
|
-
allImports.push(...result.imports);
|
|
87
|
-
allCalls.push(...result.calls);
|
|
88
|
-
filesProcessed++;
|
|
89
|
-
} catch (e) {
|
|
90
|
-
errors.push(`Parse error in ${rel}: ${e.message}`);
|
|
91
|
-
filesSkipped++;
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// Build GSD-T overlay
|
|
96
|
-
const overlay = buildOverlay(projectRoot, allEntities);
|
|
97
|
-
|
|
98
|
-
// Resolve call edges (map callee names to entity IDs)
|
|
99
|
-
const resolvedCalls = [];
|
|
100
|
-
const entityByName = new Map();
|
|
101
|
-
for (const e of allEntities) {
|
|
102
|
-
if (!entityByName.has(e.name)) {
|
|
103
|
-
entityByName.set(e.name, e);
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const call of allCalls) {
|
|
107
|
-
const target = entityByName.get(call.callee);
|
|
108
|
-
if (target) {
|
|
109
|
-
resolvedCalls.push({
|
|
110
|
-
caller: call.caller,
|
|
111
|
-
callee: target.id,
|
|
112
|
-
line: call.line
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Write to storage
|
|
118
|
-
store.writeIndex(projectRoot, { entities: allEntities });
|
|
119
|
-
store.writeCalls(projectRoot, { edges: resolvedCalls });
|
|
120
|
-
store.writeImports(projectRoot, { edges: allImports });
|
|
121
|
-
store.writeContracts(projectRoot, overlay.contracts);
|
|
122
|
-
store.writeRequirements(projectRoot, overlay.requirements);
|
|
123
|
-
store.writeTests(projectRoot, overlay.tests);
|
|
124
|
-
store.writeSurfaces(projectRoot, overlay.surfaces);
|
|
125
|
-
|
|
126
|
-
const relCount = resolvedCalls.length + allImports.length;
|
|
127
|
-
store.writeMeta(projectRoot, {
|
|
128
|
-
lastIndexed: new Date().toISOString(),
|
|
129
|
-
provider: 'native',
|
|
130
|
-
entityCount: allEntities.length,
|
|
131
|
-
relationshipCount: relCount,
|
|
132
|
-
duration: Date.now() - start,
|
|
133
|
-
fileHashes
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
return {
|
|
137
|
-
success: true,
|
|
138
|
-
entityCount: allEntities.length,
|
|
139
|
-
relationshipCount: relCount,
|
|
140
|
-
duration: Date.now() - start,
|
|
141
|
-
errors,
|
|
142
|
-
filesProcessed,
|
|
143
|
-
filesSkipped
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
module.exports = { indexProject, walkFiles, DEFAULT_EXCLUDE };
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const store = require('./graph-store');
|
|
5
|
+
const { getParser } = require('./graph-parsers');
|
|
6
|
+
const { buildOverlay } = require('./graph-overlay');
|
|
7
|
+
|
|
8
|
+
const DEFAULT_EXCLUDE = [
|
|
9
|
+
'node_modules', '.git', 'dist', 'build',
|
|
10
|
+
'coverage', '.gsd-t', '.claude', '__pycache__'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const SUPPORTED_EXTS = [
|
|
14
|
+
'.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx', '.py'
|
|
15
|
+
];
|
|
16
|
+
|
|
17
|
+
function walkFiles(dir, exclude) {
|
|
18
|
+
const results = [];
|
|
19
|
+
try {
|
|
20
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
21
|
+
for (const e of entries) {
|
|
22
|
+
if (exclude.includes(e.name)) continue;
|
|
23
|
+
if (e.name.startsWith('.')) continue;
|
|
24
|
+
const full = path.join(dir, e.name);
|
|
25
|
+
if (e.isDirectory()) {
|
|
26
|
+
results.push(...walkFiles(full, exclude));
|
|
27
|
+
} else if (e.isFile()) {
|
|
28
|
+
const ext = path.extname(e.name);
|
|
29
|
+
if (SUPPORTED_EXTS.includes(ext)) {
|
|
30
|
+
results.push(full);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
} catch { /* skip inaccessible dirs */ }
|
|
35
|
+
return results;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function indexProject(projectRoot, options = {}) {
|
|
39
|
+
const start = Date.now();
|
|
40
|
+
const force = options.force || false;
|
|
41
|
+
const exclude = options.exclude || DEFAULT_EXCLUDE;
|
|
42
|
+
const errors = [];
|
|
43
|
+
|
|
44
|
+
// Find source files
|
|
45
|
+
const allFiles = walkFiles(projectRoot, exclude);
|
|
46
|
+
|
|
47
|
+
// Check staleness
|
|
48
|
+
if (!force) {
|
|
49
|
+
const { stale, changedFiles } = store.isStale(
|
|
50
|
+
projectRoot, allFiles
|
|
51
|
+
);
|
|
52
|
+
if (!stale) {
|
|
53
|
+
const meta = store.readMeta(projectRoot);
|
|
54
|
+
return {
|
|
55
|
+
success: true,
|
|
56
|
+
entityCount: meta ? meta.entityCount : 0,
|
|
57
|
+
relationshipCount: meta ? meta.relationshipCount : 0,
|
|
58
|
+
duration: Date.now() - start,
|
|
59
|
+
errors: [],
|
|
60
|
+
filesProcessed: 0,
|
|
61
|
+
filesSkipped: allFiles.length
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Parse all files
|
|
67
|
+
const allEntities = [];
|
|
68
|
+
const allImports = [];
|
|
69
|
+
const allCalls = [];
|
|
70
|
+
const fileHashes = {};
|
|
71
|
+
let filesProcessed = 0;
|
|
72
|
+
let filesSkipped = 0;
|
|
73
|
+
|
|
74
|
+
for (const filePath of allFiles) {
|
|
75
|
+
const ext = path.extname(filePath);
|
|
76
|
+
const parser = getParser(ext);
|
|
77
|
+
if (!parser) { filesSkipped++; continue; }
|
|
78
|
+
|
|
79
|
+
const rel = path.relative(projectRoot, filePath)
|
|
80
|
+
.replace(/\\/g, '/');
|
|
81
|
+
try {
|
|
82
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
83
|
+
fileHashes[rel] = store.hashContent(content);
|
|
84
|
+
const result = parser(content, rel);
|
|
85
|
+
allEntities.push(...result.entities);
|
|
86
|
+
allImports.push(...result.imports);
|
|
87
|
+
allCalls.push(...result.calls);
|
|
88
|
+
filesProcessed++;
|
|
89
|
+
} catch (e) {
|
|
90
|
+
errors.push(`Parse error in ${rel}: ${e.message}`);
|
|
91
|
+
filesSkipped++;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Build GSD-T overlay
|
|
96
|
+
const overlay = buildOverlay(projectRoot, allEntities);
|
|
97
|
+
|
|
98
|
+
// Resolve call edges (map callee names to entity IDs)
|
|
99
|
+
const resolvedCalls = [];
|
|
100
|
+
const entityByName = new Map();
|
|
101
|
+
for (const e of allEntities) {
|
|
102
|
+
if (!entityByName.has(e.name)) {
|
|
103
|
+
entityByName.set(e.name, e);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
for (const call of allCalls) {
|
|
107
|
+
const target = entityByName.get(call.callee);
|
|
108
|
+
if (target) {
|
|
109
|
+
resolvedCalls.push({
|
|
110
|
+
caller: call.caller,
|
|
111
|
+
callee: target.id,
|
|
112
|
+
line: call.line
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Write to storage
|
|
118
|
+
store.writeIndex(projectRoot, { entities: allEntities });
|
|
119
|
+
store.writeCalls(projectRoot, { edges: resolvedCalls });
|
|
120
|
+
store.writeImports(projectRoot, { edges: allImports });
|
|
121
|
+
store.writeContracts(projectRoot, overlay.contracts);
|
|
122
|
+
store.writeRequirements(projectRoot, overlay.requirements);
|
|
123
|
+
store.writeTests(projectRoot, overlay.tests);
|
|
124
|
+
store.writeSurfaces(projectRoot, overlay.surfaces);
|
|
125
|
+
|
|
126
|
+
const relCount = resolvedCalls.length + allImports.length;
|
|
127
|
+
store.writeMeta(projectRoot, {
|
|
128
|
+
lastIndexed: new Date().toISOString(),
|
|
129
|
+
provider: 'native',
|
|
130
|
+
entityCount: allEntities.length,
|
|
131
|
+
relationshipCount: relCount,
|
|
132
|
+
duration: Date.now() - start,
|
|
133
|
+
fileHashes
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
success: true,
|
|
138
|
+
entityCount: allEntities.length,
|
|
139
|
+
relationshipCount: relCount,
|
|
140
|
+
duration: Date.now() - start,
|
|
141
|
+
errors,
|
|
142
|
+
filesProcessed,
|
|
143
|
+
filesSkipped
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
module.exports = { indexProject, walkFiles, DEFAULT_EXCLUDE };
|
package/bin/graph-overlay.js
CHANGED
|
@@ -1,195 +1,195 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* GSD-T context mapper — enriches code entities with
|
|
7
|
-
* domain ownership, contract mapping, requirement traceability,
|
|
8
|
-
* test mapping, debt mapping, and surface detection.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
function readFileOrEmpty(filePath) {
|
|
12
|
-
try { return fs.readFileSync(filePath, 'utf8'); }
|
|
13
|
-
catch { return ''; }
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function listDirs(dirPath) {
|
|
17
|
-
try {
|
|
18
|
-
return fs.readdirSync(dirPath, { withFileTypes: true })
|
|
19
|
-
.filter(d => d.isDirectory())
|
|
20
|
-
.map(d => d.name);
|
|
21
|
-
} catch { return []; }
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function buildDomainMap(projectRoot) {
|
|
25
|
-
const domainsDir = path.join(projectRoot, '.gsd-t', 'domains');
|
|
26
|
-
const map = {};
|
|
27
|
-
for (const domain of listDirs(domainsDir)) {
|
|
28
|
-
const scopePath = path.join(domainsDir, domain, 'scope.md');
|
|
29
|
-
const content = readFileOrEmpty(scopePath);
|
|
30
|
-
const fileRefs = [];
|
|
31
|
-
for (const line of content.split('\n')) {
|
|
32
|
-
const m = line.match(/[-*]\s+`([^`]+)`/);
|
|
33
|
-
if (m) fileRefs.push(m[1].replace(/\s*\(.*\)/, '').trim());
|
|
34
|
-
}
|
|
35
|
-
map[domain] = fileRefs;
|
|
36
|
-
}
|
|
37
|
-
return map;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function mapDomains(entities, projectRoot) {
|
|
41
|
-
const domainMap = buildDomainMap(projectRoot);
|
|
42
|
-
for (const entity of entities) {
|
|
43
|
-
for (const [domain, files] of Object.entries(domainMap)) {
|
|
44
|
-
if (files.some(f => entity.file.includes(f))) {
|
|
45
|
-
entity.domain = domain;
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function mapContracts(entities, projectRoot) {
|
|
53
|
-
const contractsDir = path.join(
|
|
54
|
-
projectRoot, '.gsd-t', 'contracts'
|
|
55
|
-
);
|
|
56
|
-
const mappings = [];
|
|
57
|
-
try {
|
|
58
|
-
const files = fs.readdirSync(contractsDir)
|
|
59
|
-
.filter(f => f.endsWith('.md'));
|
|
60
|
-
for (const file of files) {
|
|
61
|
-
const content = readFileOrEmpty(
|
|
62
|
-
path.join(contractsDir, file)
|
|
63
|
-
);
|
|
64
|
-
for (const entity of entities) {
|
|
65
|
-
if (content.includes(entity.name)) {
|
|
66
|
-
mappings.push({
|
|
67
|
-
entity: entity.id,
|
|
68
|
-
contract: file,
|
|
69
|
-
section: ''
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
} catch { /* no contracts dir */ }
|
|
75
|
-
return { mappings };
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function mapRequirements(entities, projectRoot) {
|
|
79
|
-
const reqPath = path.join(
|
|
80
|
-
projectRoot, 'docs', 'requirements.md'
|
|
81
|
-
);
|
|
82
|
-
const content = readFileOrEmpty(reqPath);
|
|
83
|
-
const mappings = [];
|
|
84
|
-
if (!content) return { mappings };
|
|
85
|
-
|
|
86
|
-
const reqLines = content.split('\n');
|
|
87
|
-
for (const entity of entities) {
|
|
88
|
-
for (const line of reqLines) {
|
|
89
|
-
const reqMatch = line.match(/(REQ-\d+)/);
|
|
90
|
-
if (reqMatch && line.includes(entity.name)) {
|
|
91
|
-
mappings.push({
|
|
92
|
-
entity: entity.id,
|
|
93
|
-
requirement: reqMatch[1]
|
|
94
|
-
});
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
return { mappings };
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function mapTests(entities, projectRoot) {
|
|
102
|
-
const mappings = [];
|
|
103
|
-
const testDirs = ['test', 'tests', '__tests__', 'spec'];
|
|
104
|
-
for (const dir of testDirs) {
|
|
105
|
-
const testDir = path.join(projectRoot, dir);
|
|
106
|
-
try {
|
|
107
|
-
const files = fs.readdirSync(testDir)
|
|
108
|
-
.filter(f => /\.(test|spec)\.(js|ts|py)$/.test(f));
|
|
109
|
-
for (const testFile of files) {
|
|
110
|
-
const content = readFileOrEmpty(
|
|
111
|
-
path.join(testDir, testFile)
|
|
112
|
-
);
|
|
113
|
-
for (const entity of entities) {
|
|
114
|
-
if (content.includes(entity.name)) {
|
|
115
|
-
mappings.push({
|
|
116
|
-
entity: entity.id,
|
|
117
|
-
testFile: path.join(dir, testFile),
|
|
118
|
-
testName: ''
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
} catch { /* no test dir */ }
|
|
124
|
-
}
|
|
125
|
-
return { mappings };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function mapDebt(entities, projectRoot) {
|
|
129
|
-
const debtPath = path.join(
|
|
130
|
-
projectRoot, '.gsd-t', 'techdebt.md'
|
|
131
|
-
);
|
|
132
|
-
const content = readFileOrEmpty(debtPath);
|
|
133
|
-
const mappings = [];
|
|
134
|
-
if (!content) return mappings;
|
|
135
|
-
|
|
136
|
-
for (const entity of entities) {
|
|
137
|
-
const lines = content.split('\n');
|
|
138
|
-
for (const line of lines) {
|
|
139
|
-
const debtMatch = line.match(/(TD-\d+)/);
|
|
140
|
-
if (debtMatch && (
|
|
141
|
-
line.includes(entity.name) ||
|
|
142
|
-
line.includes(entity.file)
|
|
143
|
-
)) {
|
|
144
|
-
mappings.push({
|
|
145
|
-
id: debtMatch[1],
|
|
146
|
-
entity: entity.id,
|
|
147
|
-
description: line.trim()
|
|
148
|
-
});
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return mappings;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
function detectSurfaces(entities, projectRoot) {
|
|
156
|
-
const surfaceDirs = {
|
|
157
|
-
'web': ['web', 'frontend', 'client', 'webapp', 'www'],
|
|
158
|
-
'mobile': ['mobile', 'app', 'ios', 'android', 'react-native'],
|
|
159
|
-
'cli': ['cli', 'bin', 'commands'],
|
|
160
|
-
'api': ['api', 'server', 'backend', 'routes'],
|
|
161
|
-
'shared': ['shared', 'common', 'lib', 'utils', 'core']
|
|
162
|
-
};
|
|
163
|
-
|
|
164
|
-
const mappings = [];
|
|
165
|
-
for (const entity of entities) {
|
|
166
|
-
const surfaces = [];
|
|
167
|
-
for (const [surface, dirs] of Object.entries(surfaceDirs)) {
|
|
168
|
-
if (dirs.some(d => entity.file.startsWith(d + '/') ||
|
|
169
|
-
entity.file.startsWith(d + '\\'))) {
|
|
170
|
-
surfaces.push(surface);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
if (surfaces.length > 0) {
|
|
174
|
-
mappings.push({ entity: entity.id, surfaces });
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
return { mappings };
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
function buildOverlay(projectRoot, entities) {
|
|
181
|
-
mapDomains(entities, projectRoot);
|
|
182
|
-
const contracts = mapContracts(entities, projectRoot);
|
|
183
|
-
const requirements = mapRequirements(entities, projectRoot);
|
|
184
|
-
const tests = mapTests(entities, projectRoot);
|
|
185
|
-
const surfaces = detectSurfaces(entities, projectRoot);
|
|
186
|
-
const debt = mapDebt(entities, projectRoot);
|
|
187
|
-
|
|
188
|
-
return { contracts, requirements, tests, surfaces, debt };
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
module.exports = {
|
|
192
|
-
buildOverlay, buildDomainMap, mapDomains,
|
|
193
|
-
mapContracts, mapRequirements, mapTests,
|
|
194
|
-
mapDebt, detectSurfaces
|
|
195
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* GSD-T context mapper — enriches code entities with
|
|
7
|
+
* domain ownership, contract mapping, requirement traceability,
|
|
8
|
+
* test mapping, debt mapping, and surface detection.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
function readFileOrEmpty(filePath) {
|
|
12
|
+
try { return fs.readFileSync(filePath, 'utf8'); }
|
|
13
|
+
catch { return ''; }
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function listDirs(dirPath) {
|
|
17
|
+
try {
|
|
18
|
+
return fs.readdirSync(dirPath, { withFileTypes: true })
|
|
19
|
+
.filter(d => d.isDirectory())
|
|
20
|
+
.map(d => d.name);
|
|
21
|
+
} catch { return []; }
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function buildDomainMap(projectRoot) {
|
|
25
|
+
const domainsDir = path.join(projectRoot, '.gsd-t', 'domains');
|
|
26
|
+
const map = {};
|
|
27
|
+
for (const domain of listDirs(domainsDir)) {
|
|
28
|
+
const scopePath = path.join(domainsDir, domain, 'scope.md');
|
|
29
|
+
const content = readFileOrEmpty(scopePath);
|
|
30
|
+
const fileRefs = [];
|
|
31
|
+
for (const line of content.split('\n')) {
|
|
32
|
+
const m = line.match(/[-*]\s+`([^`]+)`/);
|
|
33
|
+
if (m) fileRefs.push(m[1].replace(/\s*\(.*\)/, '').trim());
|
|
34
|
+
}
|
|
35
|
+
map[domain] = fileRefs;
|
|
36
|
+
}
|
|
37
|
+
return map;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function mapDomains(entities, projectRoot) {
|
|
41
|
+
const domainMap = buildDomainMap(projectRoot);
|
|
42
|
+
for (const entity of entities) {
|
|
43
|
+
for (const [domain, files] of Object.entries(domainMap)) {
|
|
44
|
+
if (files.some(f => entity.file.includes(f))) {
|
|
45
|
+
entity.domain = domain;
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function mapContracts(entities, projectRoot) {
|
|
53
|
+
const contractsDir = path.join(
|
|
54
|
+
projectRoot, '.gsd-t', 'contracts'
|
|
55
|
+
);
|
|
56
|
+
const mappings = [];
|
|
57
|
+
try {
|
|
58
|
+
const files = fs.readdirSync(contractsDir)
|
|
59
|
+
.filter(f => f.endsWith('.md'));
|
|
60
|
+
for (const file of files) {
|
|
61
|
+
const content = readFileOrEmpty(
|
|
62
|
+
path.join(contractsDir, file)
|
|
63
|
+
);
|
|
64
|
+
for (const entity of entities) {
|
|
65
|
+
if (content.includes(entity.name)) {
|
|
66
|
+
mappings.push({
|
|
67
|
+
entity: entity.id,
|
|
68
|
+
contract: file,
|
|
69
|
+
section: ''
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
} catch { /* no contracts dir */ }
|
|
75
|
+
return { mappings };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function mapRequirements(entities, projectRoot) {
|
|
79
|
+
const reqPath = path.join(
|
|
80
|
+
projectRoot, 'docs', 'requirements.md'
|
|
81
|
+
);
|
|
82
|
+
const content = readFileOrEmpty(reqPath);
|
|
83
|
+
const mappings = [];
|
|
84
|
+
if (!content) return { mappings };
|
|
85
|
+
|
|
86
|
+
const reqLines = content.split('\n');
|
|
87
|
+
for (const entity of entities) {
|
|
88
|
+
for (const line of reqLines) {
|
|
89
|
+
const reqMatch = line.match(/(REQ-\d+)/);
|
|
90
|
+
if (reqMatch && line.includes(entity.name)) {
|
|
91
|
+
mappings.push({
|
|
92
|
+
entity: entity.id,
|
|
93
|
+
requirement: reqMatch[1]
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return { mappings };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function mapTests(entities, projectRoot) {
|
|
102
|
+
const mappings = [];
|
|
103
|
+
const testDirs = ['test', 'tests', '__tests__', 'spec'];
|
|
104
|
+
for (const dir of testDirs) {
|
|
105
|
+
const testDir = path.join(projectRoot, dir);
|
|
106
|
+
try {
|
|
107
|
+
const files = fs.readdirSync(testDir)
|
|
108
|
+
.filter(f => /\.(test|spec)\.(js|ts|py)$/.test(f));
|
|
109
|
+
for (const testFile of files) {
|
|
110
|
+
const content = readFileOrEmpty(
|
|
111
|
+
path.join(testDir, testFile)
|
|
112
|
+
);
|
|
113
|
+
for (const entity of entities) {
|
|
114
|
+
if (content.includes(entity.name)) {
|
|
115
|
+
mappings.push({
|
|
116
|
+
entity: entity.id,
|
|
117
|
+
testFile: path.join(dir, testFile),
|
|
118
|
+
testName: ''
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
} catch { /* no test dir */ }
|
|
124
|
+
}
|
|
125
|
+
return { mappings };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function mapDebt(entities, projectRoot) {
|
|
129
|
+
const debtPath = path.join(
|
|
130
|
+
projectRoot, '.gsd-t', 'techdebt.md'
|
|
131
|
+
);
|
|
132
|
+
const content = readFileOrEmpty(debtPath);
|
|
133
|
+
const mappings = [];
|
|
134
|
+
if (!content) return mappings;
|
|
135
|
+
|
|
136
|
+
for (const entity of entities) {
|
|
137
|
+
const lines = content.split('\n');
|
|
138
|
+
for (const line of lines) {
|
|
139
|
+
const debtMatch = line.match(/(TD-\d+)/);
|
|
140
|
+
if (debtMatch && (
|
|
141
|
+
line.includes(entity.name) ||
|
|
142
|
+
line.includes(entity.file)
|
|
143
|
+
)) {
|
|
144
|
+
mappings.push({
|
|
145
|
+
id: debtMatch[1],
|
|
146
|
+
entity: entity.id,
|
|
147
|
+
description: line.trim()
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return mappings;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function detectSurfaces(entities, projectRoot) {
|
|
156
|
+
const surfaceDirs = {
|
|
157
|
+
'web': ['web', 'frontend', 'client', 'webapp', 'www'],
|
|
158
|
+
'mobile': ['mobile', 'app', 'ios', 'android', 'react-native'],
|
|
159
|
+
'cli': ['cli', 'bin', 'commands'],
|
|
160
|
+
'api': ['api', 'server', 'backend', 'routes'],
|
|
161
|
+
'shared': ['shared', 'common', 'lib', 'utils', 'core']
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const mappings = [];
|
|
165
|
+
for (const entity of entities) {
|
|
166
|
+
const surfaces = [];
|
|
167
|
+
for (const [surface, dirs] of Object.entries(surfaceDirs)) {
|
|
168
|
+
if (dirs.some(d => entity.file.startsWith(d + '/') ||
|
|
169
|
+
entity.file.startsWith(d + '\\'))) {
|
|
170
|
+
surfaces.push(surface);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (surfaces.length > 0) {
|
|
174
|
+
mappings.push({ entity: entity.id, surfaces });
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return { mappings };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function buildOverlay(projectRoot, entities) {
|
|
181
|
+
mapDomains(entities, projectRoot);
|
|
182
|
+
const contracts = mapContracts(entities, projectRoot);
|
|
183
|
+
const requirements = mapRequirements(entities, projectRoot);
|
|
184
|
+
const tests = mapTests(entities, projectRoot);
|
|
185
|
+
const surfaces = detectSurfaces(entities, projectRoot);
|
|
186
|
+
const debt = mapDebt(entities, projectRoot);
|
|
187
|
+
|
|
188
|
+
return { contracts, requirements, tests, surfaces, debt };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = {
|
|
192
|
+
buildOverlay, buildDomainMap, mapDomains,
|
|
193
|
+
mapContracts, mapRequirements, mapTests,
|
|
194
|
+
mapDebt, detectSurfaces
|
|
195
|
+
};
|