@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-parsers.js
CHANGED
|
@@ -1,327 +1,327 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Language-specific parsers for extracting code entities.
|
|
5
|
-
* Zero external dependencies — regex-based only.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// --- JS/TS Parser ---
|
|
9
|
-
|
|
10
|
-
const JS_FUNC_PATTERNS = [
|
|
11
|
-
// function declarations: function name(, async function name(
|
|
12
|
-
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
|
|
13
|
-
// arrow/const: const name = (, const name = function(, const name = async (
|
|
14
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function\s*)?\(/gm,
|
|
15
|
-
// arrow with =>: const name = (...) =>
|
|
16
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/gm,
|
|
17
|
-
];
|
|
18
|
-
|
|
19
|
-
const JS_CLASS_PATTERN = /^\s*(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm;
|
|
20
|
-
|
|
21
|
-
const JS_METHOD_PATTERN = /^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/gm;
|
|
22
|
-
|
|
23
|
-
const JS_IMPORT_PATTERNS = [
|
|
24
|
-
// import { x, y } from 'module'
|
|
25
|
-
/^\s*import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/gm,
|
|
26
|
-
// import x from 'module'
|
|
27
|
-
/^\s*import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/gm,
|
|
28
|
-
// import * as x from 'module'
|
|
29
|
-
/^\s*import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/gm,
|
|
30
|
-
// const x = require('module')
|
|
31
|
-
/^\s*(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(\s*['"]([^'"]+)['"]\s*\)/gm,
|
|
32
|
-
];
|
|
33
|
-
|
|
34
|
-
const JS_EXPORT_PATTERNS = [
|
|
35
|
-
// module.exports = { ... } or module.exports = name
|
|
36
|
-
/^\s*module\.exports\s*=\s*/gm,
|
|
37
|
-
// export default
|
|
38
|
-
/^\s*export\s+default\s+/gm,
|
|
39
|
-
// export { x, y }
|
|
40
|
-
/^\s*export\s+\{([^}]+)\}/gm,
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
function parseJavaScript(content, filePath) {
|
|
44
|
-
const entities = [];
|
|
45
|
-
const imports = [];
|
|
46
|
-
const calls = [];
|
|
47
|
-
const lines = content.split('\n');
|
|
48
|
-
const exportedNames = new Set();
|
|
49
|
-
let currentClass = null;
|
|
50
|
-
|
|
51
|
-
// Collect exported names
|
|
52
|
-
for (const line of lines) {
|
|
53
|
-
if (/^\s*export\s+/.test(line)) {
|
|
54
|
-
const m = line.match(/(?:function|class|const|let|var)\s+(\w+)/);
|
|
55
|
-
if (m) exportedNames.add(m[1]);
|
|
56
|
-
}
|
|
57
|
-
const exportMatch = line.match(/^\s*export\s+\{([^}]+)\}/);
|
|
58
|
-
if (exportMatch) {
|
|
59
|
-
exportMatch[1].split(',').forEach(n => {
|
|
60
|
-
const name = n.trim().split(/\s+as\s+/)[0].trim();
|
|
61
|
-
if (name) exportedNames.add(name);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
if (/module\.exports/.test(line)) {
|
|
65
|
-
const m = line.match(/module\.exports\s*=\s*\{\s*([^}]+)\}/);
|
|
66
|
-
if (m) {
|
|
67
|
-
m[1].split(',').forEach(n => {
|
|
68
|
-
const name = n.trim().split(':')[0].trim();
|
|
69
|
-
if (name) exportedNames.add(name);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Extract entities line by line
|
|
76
|
-
for (let i = 0; i < lines.length; i++) {
|
|
77
|
-
const line = lines[i];
|
|
78
|
-
const lineNum = i + 1;
|
|
79
|
-
|
|
80
|
-
// Class declarations
|
|
81
|
-
const classMatch = line.match(
|
|
82
|
-
/^\s*(?:export\s+)?(?:default\s+)?class\s+(\w+)/
|
|
83
|
-
);
|
|
84
|
-
if (classMatch) {
|
|
85
|
-
currentClass = classMatch[1];
|
|
86
|
-
entities.push({
|
|
87
|
-
id: `${filePath}:${lineNum}:${classMatch[1]}`,
|
|
88
|
-
name: classMatch[1],
|
|
89
|
-
type: 'class',
|
|
90
|
-
file: filePath,
|
|
91
|
-
line: lineNum,
|
|
92
|
-
domain: null,
|
|
93
|
-
exported: exportedNames.has(classMatch[1]) ||
|
|
94
|
-
/export/.test(line)
|
|
95
|
-
});
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
// Method declarations (inside class)
|
|
100
|
-
if (currentClass) {
|
|
101
|
-
const methodMatch = line.match(
|
|
102
|
-
/^\s+(?:static\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/
|
|
103
|
-
);
|
|
104
|
-
if (methodMatch && methodMatch[1] !== 'constructor') {
|
|
105
|
-
entities.push({
|
|
106
|
-
id: `${filePath}:${lineNum}:${methodMatch[1]}`,
|
|
107
|
-
name: methodMatch[1],
|
|
108
|
-
type: 'method',
|
|
109
|
-
file: filePath,
|
|
110
|
-
line: lineNum,
|
|
111
|
-
domain: null,
|
|
112
|
-
exported: exportedNames.has(currentClass)
|
|
113
|
-
});
|
|
114
|
-
continue;
|
|
115
|
-
}
|
|
116
|
-
// Detect end of class
|
|
117
|
-
if (/^}/.test(line)) currentClass = null;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
// Function declarations
|
|
121
|
-
const funcMatch = line.match(
|
|
122
|
-
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/
|
|
123
|
-
);
|
|
124
|
-
if (funcMatch) {
|
|
125
|
-
entities.push({
|
|
126
|
-
id: `${filePath}:${lineNum}:${funcMatch[1]}`,
|
|
127
|
-
name: funcMatch[1],
|
|
128
|
-
type: 'function',
|
|
129
|
-
file: filePath,
|
|
130
|
-
line: lineNum,
|
|
131
|
-
domain: null,
|
|
132
|
-
exported: exportedNames.has(funcMatch[1]) ||
|
|
133
|
-
/export/.test(line)
|
|
134
|
-
});
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Arrow/const functions
|
|
139
|
-
const arrowMatch = line.match(
|
|
140
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function\s*)?\(/
|
|
141
|
-
) || line.match(
|
|
142
|
-
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/
|
|
143
|
-
);
|
|
144
|
-
if (arrowMatch) {
|
|
145
|
-
entities.push({
|
|
146
|
-
id: `${filePath}:${lineNum}:${arrowMatch[1]}`,
|
|
147
|
-
name: arrowMatch[1],
|
|
148
|
-
type: 'function',
|
|
149
|
-
file: filePath,
|
|
150
|
-
line: lineNum,
|
|
151
|
-
domain: null,
|
|
152
|
-
exported: exportedNames.has(arrowMatch[1]) ||
|
|
153
|
-
/export/.test(line)
|
|
154
|
-
});
|
|
155
|
-
continue;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Import statements
|
|
159
|
-
const esImportNamed = line.match(
|
|
160
|
-
/^\s*import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/
|
|
161
|
-
);
|
|
162
|
-
if (esImportNamed) {
|
|
163
|
-
const names = esImportNamed[1].split(',').map(n =>
|
|
164
|
-
n.trim().split(/\s+as\s+/)[0].trim()
|
|
165
|
-
).filter(Boolean);
|
|
166
|
-
imports.push({
|
|
167
|
-
source: filePath,
|
|
168
|
-
target: esImportNamed[2],
|
|
169
|
-
names,
|
|
170
|
-
line: lineNum
|
|
171
|
-
});
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
const esImportDefault = line.match(
|
|
176
|
-
/^\s*import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/
|
|
177
|
-
);
|
|
178
|
-
if (esImportDefault) {
|
|
179
|
-
imports.push({
|
|
180
|
-
source: filePath,
|
|
181
|
-
target: esImportDefault[2],
|
|
182
|
-
names: [esImportDefault[1]],
|
|
183
|
-
line: lineNum
|
|
184
|
-
});
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
const esImportStar = line.match(
|
|
189
|
-
/^\s*import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/
|
|
190
|
-
);
|
|
191
|
-
if (esImportStar) {
|
|
192
|
-
imports.push({
|
|
193
|
-
source: filePath,
|
|
194
|
-
target: esImportStar[2],
|
|
195
|
-
names: [esImportStar[1]],
|
|
196
|
-
line: lineNum
|
|
197
|
-
});
|
|
198
|
-
continue;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const requireMatch = line.match(
|
|
202
|
-
/^\s*(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(\s*['"]([^'"]+)['"]\s*\)/
|
|
203
|
-
);
|
|
204
|
-
if (requireMatch) {
|
|
205
|
-
const names = requireMatch[1]
|
|
206
|
-
? requireMatch[1].split(',').map(n =>
|
|
207
|
-
n.trim().split(':')[0].trim()
|
|
208
|
-
).filter(Boolean)
|
|
209
|
-
: [requireMatch[2]];
|
|
210
|
-
imports.push({
|
|
211
|
-
source: filePath,
|
|
212
|
-
target: requireMatch[3],
|
|
213
|
-
names,
|
|
214
|
-
line: lineNum
|
|
215
|
-
});
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Extract calls (best-effort: known entity names followed by '(')
|
|
220
|
-
const entityNames = new Set(entities.map(e => e.name));
|
|
221
|
-
for (let i = 0; i < lines.length; i++) {
|
|
222
|
-
const line = lines[i];
|
|
223
|
-
// Skip declarations, imports, comments
|
|
224
|
-
if (/^\s*(\/\/|\/\*|import |const |let |var |function |class |export )/.test(line)) continue;
|
|
225
|
-
for (const name of entityNames) {
|
|
226
|
-
const re = new RegExp(`\\b${name}\\s*\\(`, 'g');
|
|
227
|
-
if (re.test(line)) {
|
|
228
|
-
calls.push({ caller: `${filePath}:${i + 1}:_caller`, callee: name, line: i + 1 });
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
return { entities, imports, calls };
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// --- Python Parser ---
|
|
237
|
-
|
|
238
|
-
function parsePython(content, filePath) {
|
|
239
|
-
const entities = [];
|
|
240
|
-
const imports = [];
|
|
241
|
-
const calls = [];
|
|
242
|
-
const lines = content.split('\n');
|
|
243
|
-
let currentClass = null;
|
|
244
|
-
|
|
245
|
-
for (let i = 0; i < lines.length; i++) {
|
|
246
|
-
const line = lines[i];
|
|
247
|
-
const lineNum = i + 1;
|
|
248
|
-
|
|
249
|
-
// Class declaration
|
|
250
|
-
const classMatch = line.match(/^class\s+(\w+)\s*[:(]/);
|
|
251
|
-
if (classMatch) {
|
|
252
|
-
currentClass = classMatch[1];
|
|
253
|
-
entities.push({
|
|
254
|
-
id: `${filePath}:${lineNum}:${classMatch[1]}`,
|
|
255
|
-
name: classMatch[1],
|
|
256
|
-
type: 'class',
|
|
257
|
-
file: filePath,
|
|
258
|
-
line: lineNum,
|
|
259
|
-
domain: null,
|
|
260
|
-
exported: true
|
|
261
|
-
});
|
|
262
|
-
continue;
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
// Function/method declaration
|
|
266
|
-
const defMatch = line.match(/^(\s*)def\s+(\w+)\s*\(/);
|
|
267
|
-
if (defMatch) {
|
|
268
|
-
const indent = defMatch[1].length;
|
|
269
|
-
const isMethod = indent > 0 && currentClass;
|
|
270
|
-
if (!defMatch[2].startsWith('_') || defMatch[2] === '__init__') {
|
|
271
|
-
entities.push({
|
|
272
|
-
id: `${filePath}:${lineNum}:${defMatch[2]}`,
|
|
273
|
-
name: defMatch[2],
|
|
274
|
-
type: isMethod ? 'method' : 'function',
|
|
275
|
-
file: filePath,
|
|
276
|
-
line: lineNum,
|
|
277
|
-
domain: null,
|
|
278
|
-
exported: !defMatch[2].startsWith('_')
|
|
279
|
-
});
|
|
280
|
-
}
|
|
281
|
-
continue;
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// Reset class context on unindented non-empty line
|
|
285
|
-
if (currentClass && /^\S/.test(line) && line.trim()) {
|
|
286
|
-
currentClass = null;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// import x, import x as y
|
|
290
|
-
const importMatch = line.match(/^import\s+([\w.]+)/);
|
|
291
|
-
if (importMatch) {
|
|
292
|
-
imports.push({
|
|
293
|
-
source: filePath,
|
|
294
|
-
target: importMatch[1],
|
|
295
|
-
names: [importMatch[1].split('.').pop()],
|
|
296
|
-
line: lineNum
|
|
297
|
-
});
|
|
298
|
-
continue;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
// from x import y, z
|
|
302
|
-
const fromMatch = line.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
|
|
303
|
-
if (fromMatch) {
|
|
304
|
-
const names = fromMatch[2].split(',').map(n =>
|
|
305
|
-
n.trim().split(/\s+as\s+/)[0].trim()
|
|
306
|
-
).filter(Boolean);
|
|
307
|
-
imports.push({
|
|
308
|
-
source: filePath,
|
|
309
|
-
target: fromMatch[1],
|
|
310
|
-
names,
|
|
311
|
-
line: lineNum
|
|
312
|
-
});
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
return { entities, imports, calls };
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
function getParser(ext) {
|
|
320
|
-
if (['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx'].includes(ext)) {
|
|
321
|
-
return parseJavaScript;
|
|
322
|
-
}
|
|
323
|
-
if (ext === '.py') return parsePython;
|
|
324
|
-
return null;
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
module.exports = { parseJavaScript, parsePython, getParser };
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Language-specific parsers for extracting code entities.
|
|
5
|
+
* Zero external dependencies — regex-based only.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// --- JS/TS Parser ---
|
|
9
|
+
|
|
10
|
+
const JS_FUNC_PATTERNS = [
|
|
11
|
+
// function declarations: function name(, async function name(
|
|
12
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/gm,
|
|
13
|
+
// arrow/const: const name = (, const name = function(, const name = async (
|
|
14
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function\s*)?\(/gm,
|
|
15
|
+
// arrow with =>: const name = (...) =>
|
|
16
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/gm,
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
const JS_CLASS_PATTERN = /^\s*(?:export\s+)?(?:default\s+)?class\s+(\w+)/gm;
|
|
20
|
+
|
|
21
|
+
const JS_METHOD_PATTERN = /^\s+(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/gm;
|
|
22
|
+
|
|
23
|
+
const JS_IMPORT_PATTERNS = [
|
|
24
|
+
// import { x, y } from 'module'
|
|
25
|
+
/^\s*import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/gm,
|
|
26
|
+
// import x from 'module'
|
|
27
|
+
/^\s*import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/gm,
|
|
28
|
+
// import * as x from 'module'
|
|
29
|
+
/^\s*import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/gm,
|
|
30
|
+
// const x = require('module')
|
|
31
|
+
/^\s*(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(\s*['"]([^'"]+)['"]\s*\)/gm,
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
const JS_EXPORT_PATTERNS = [
|
|
35
|
+
// module.exports = { ... } or module.exports = name
|
|
36
|
+
/^\s*module\.exports\s*=\s*/gm,
|
|
37
|
+
// export default
|
|
38
|
+
/^\s*export\s+default\s+/gm,
|
|
39
|
+
// export { x, y }
|
|
40
|
+
/^\s*export\s+\{([^}]+)\}/gm,
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
function parseJavaScript(content, filePath) {
|
|
44
|
+
const entities = [];
|
|
45
|
+
const imports = [];
|
|
46
|
+
const calls = [];
|
|
47
|
+
const lines = content.split('\n');
|
|
48
|
+
const exportedNames = new Set();
|
|
49
|
+
let currentClass = null;
|
|
50
|
+
|
|
51
|
+
// Collect exported names
|
|
52
|
+
for (const line of lines) {
|
|
53
|
+
if (/^\s*export\s+/.test(line)) {
|
|
54
|
+
const m = line.match(/(?:function|class|const|let|var)\s+(\w+)/);
|
|
55
|
+
if (m) exportedNames.add(m[1]);
|
|
56
|
+
}
|
|
57
|
+
const exportMatch = line.match(/^\s*export\s+\{([^}]+)\}/);
|
|
58
|
+
if (exportMatch) {
|
|
59
|
+
exportMatch[1].split(',').forEach(n => {
|
|
60
|
+
const name = n.trim().split(/\s+as\s+/)[0].trim();
|
|
61
|
+
if (name) exportedNames.add(name);
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (/module\.exports/.test(line)) {
|
|
65
|
+
const m = line.match(/module\.exports\s*=\s*\{\s*([^}]+)\}/);
|
|
66
|
+
if (m) {
|
|
67
|
+
m[1].split(',').forEach(n => {
|
|
68
|
+
const name = n.trim().split(':')[0].trim();
|
|
69
|
+
if (name) exportedNames.add(name);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Extract entities line by line
|
|
76
|
+
for (let i = 0; i < lines.length; i++) {
|
|
77
|
+
const line = lines[i];
|
|
78
|
+
const lineNum = i + 1;
|
|
79
|
+
|
|
80
|
+
// Class declarations
|
|
81
|
+
const classMatch = line.match(
|
|
82
|
+
/^\s*(?:export\s+)?(?:default\s+)?class\s+(\w+)/
|
|
83
|
+
);
|
|
84
|
+
if (classMatch) {
|
|
85
|
+
currentClass = classMatch[1];
|
|
86
|
+
entities.push({
|
|
87
|
+
id: `${filePath}:${lineNum}:${classMatch[1]}`,
|
|
88
|
+
name: classMatch[1],
|
|
89
|
+
type: 'class',
|
|
90
|
+
file: filePath,
|
|
91
|
+
line: lineNum,
|
|
92
|
+
domain: null,
|
|
93
|
+
exported: exportedNames.has(classMatch[1]) ||
|
|
94
|
+
/export/.test(line)
|
|
95
|
+
});
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Method declarations (inside class)
|
|
100
|
+
if (currentClass) {
|
|
101
|
+
const methodMatch = line.match(
|
|
102
|
+
/^\s+(?:static\s+)?(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/
|
|
103
|
+
);
|
|
104
|
+
if (methodMatch && methodMatch[1] !== 'constructor') {
|
|
105
|
+
entities.push({
|
|
106
|
+
id: `${filePath}:${lineNum}:${methodMatch[1]}`,
|
|
107
|
+
name: methodMatch[1],
|
|
108
|
+
type: 'method',
|
|
109
|
+
file: filePath,
|
|
110
|
+
line: lineNum,
|
|
111
|
+
domain: null,
|
|
112
|
+
exported: exportedNames.has(currentClass)
|
|
113
|
+
});
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
// Detect end of class
|
|
117
|
+
if (/^}/.test(line)) currentClass = null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Function declarations
|
|
121
|
+
const funcMatch = line.match(
|
|
122
|
+
/^\s*(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\(/
|
|
123
|
+
);
|
|
124
|
+
if (funcMatch) {
|
|
125
|
+
entities.push({
|
|
126
|
+
id: `${filePath}:${lineNum}:${funcMatch[1]}`,
|
|
127
|
+
name: funcMatch[1],
|
|
128
|
+
type: 'function',
|
|
129
|
+
file: filePath,
|
|
130
|
+
line: lineNum,
|
|
131
|
+
domain: null,
|
|
132
|
+
exported: exportedNames.has(funcMatch[1]) ||
|
|
133
|
+
/export/.test(line)
|
|
134
|
+
});
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Arrow/const functions
|
|
139
|
+
const arrowMatch = line.match(
|
|
140
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?(?:function\s*)?\(/
|
|
141
|
+
) || line.match(
|
|
142
|
+
/^\s*(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?\([^)]*\)\s*=>/
|
|
143
|
+
);
|
|
144
|
+
if (arrowMatch) {
|
|
145
|
+
entities.push({
|
|
146
|
+
id: `${filePath}:${lineNum}:${arrowMatch[1]}`,
|
|
147
|
+
name: arrowMatch[1],
|
|
148
|
+
type: 'function',
|
|
149
|
+
file: filePath,
|
|
150
|
+
line: lineNum,
|
|
151
|
+
domain: null,
|
|
152
|
+
exported: exportedNames.has(arrowMatch[1]) ||
|
|
153
|
+
/export/.test(line)
|
|
154
|
+
});
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Import statements
|
|
159
|
+
const esImportNamed = line.match(
|
|
160
|
+
/^\s*import\s+\{([^}]+)\}\s+from\s+['"]([^'"]+)['"]/
|
|
161
|
+
);
|
|
162
|
+
if (esImportNamed) {
|
|
163
|
+
const names = esImportNamed[1].split(',').map(n =>
|
|
164
|
+
n.trim().split(/\s+as\s+/)[0].trim()
|
|
165
|
+
).filter(Boolean);
|
|
166
|
+
imports.push({
|
|
167
|
+
source: filePath,
|
|
168
|
+
target: esImportNamed[2],
|
|
169
|
+
names,
|
|
170
|
+
line: lineNum
|
|
171
|
+
});
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const esImportDefault = line.match(
|
|
176
|
+
/^\s*import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/
|
|
177
|
+
);
|
|
178
|
+
if (esImportDefault) {
|
|
179
|
+
imports.push({
|
|
180
|
+
source: filePath,
|
|
181
|
+
target: esImportDefault[2],
|
|
182
|
+
names: [esImportDefault[1]],
|
|
183
|
+
line: lineNum
|
|
184
|
+
});
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const esImportStar = line.match(
|
|
189
|
+
/^\s*import\s+\*\s+as\s+(\w+)\s+from\s+['"]([^'"]+)['"]/
|
|
190
|
+
);
|
|
191
|
+
if (esImportStar) {
|
|
192
|
+
imports.push({
|
|
193
|
+
source: filePath,
|
|
194
|
+
target: esImportStar[2],
|
|
195
|
+
names: [esImportStar[1]],
|
|
196
|
+
line: lineNum
|
|
197
|
+
});
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const requireMatch = line.match(
|
|
202
|
+
/^\s*(?:const|let|var)\s+(?:\{([^}]+)\}|(\w+))\s*=\s*require\(\s*['"]([^'"]+)['"]\s*\)/
|
|
203
|
+
);
|
|
204
|
+
if (requireMatch) {
|
|
205
|
+
const names = requireMatch[1]
|
|
206
|
+
? requireMatch[1].split(',').map(n =>
|
|
207
|
+
n.trim().split(':')[0].trim()
|
|
208
|
+
).filter(Boolean)
|
|
209
|
+
: [requireMatch[2]];
|
|
210
|
+
imports.push({
|
|
211
|
+
source: filePath,
|
|
212
|
+
target: requireMatch[3],
|
|
213
|
+
names,
|
|
214
|
+
line: lineNum
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Extract calls (best-effort: known entity names followed by '(')
|
|
220
|
+
const entityNames = new Set(entities.map(e => e.name));
|
|
221
|
+
for (let i = 0; i < lines.length; i++) {
|
|
222
|
+
const line = lines[i];
|
|
223
|
+
// Skip declarations, imports, comments
|
|
224
|
+
if (/^\s*(\/\/|\/\*|import |const |let |var |function |class |export )/.test(line)) continue;
|
|
225
|
+
for (const name of entityNames) {
|
|
226
|
+
const re = new RegExp(`\\b${name}\\s*\\(`, 'g');
|
|
227
|
+
if (re.test(line)) {
|
|
228
|
+
calls.push({ caller: `${filePath}:${i + 1}:_caller`, callee: name, line: i + 1 });
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return { entities, imports, calls };
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// --- Python Parser ---
|
|
237
|
+
|
|
238
|
+
function parsePython(content, filePath) {
|
|
239
|
+
const entities = [];
|
|
240
|
+
const imports = [];
|
|
241
|
+
const calls = [];
|
|
242
|
+
const lines = content.split('\n');
|
|
243
|
+
let currentClass = null;
|
|
244
|
+
|
|
245
|
+
for (let i = 0; i < lines.length; i++) {
|
|
246
|
+
const line = lines[i];
|
|
247
|
+
const lineNum = i + 1;
|
|
248
|
+
|
|
249
|
+
// Class declaration
|
|
250
|
+
const classMatch = line.match(/^class\s+(\w+)\s*[:(]/);
|
|
251
|
+
if (classMatch) {
|
|
252
|
+
currentClass = classMatch[1];
|
|
253
|
+
entities.push({
|
|
254
|
+
id: `${filePath}:${lineNum}:${classMatch[1]}`,
|
|
255
|
+
name: classMatch[1],
|
|
256
|
+
type: 'class',
|
|
257
|
+
file: filePath,
|
|
258
|
+
line: lineNum,
|
|
259
|
+
domain: null,
|
|
260
|
+
exported: true
|
|
261
|
+
});
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Function/method declaration
|
|
266
|
+
const defMatch = line.match(/^(\s*)def\s+(\w+)\s*\(/);
|
|
267
|
+
if (defMatch) {
|
|
268
|
+
const indent = defMatch[1].length;
|
|
269
|
+
const isMethod = indent > 0 && currentClass;
|
|
270
|
+
if (!defMatch[2].startsWith('_') || defMatch[2] === '__init__') {
|
|
271
|
+
entities.push({
|
|
272
|
+
id: `${filePath}:${lineNum}:${defMatch[2]}`,
|
|
273
|
+
name: defMatch[2],
|
|
274
|
+
type: isMethod ? 'method' : 'function',
|
|
275
|
+
file: filePath,
|
|
276
|
+
line: lineNum,
|
|
277
|
+
domain: null,
|
|
278
|
+
exported: !defMatch[2].startsWith('_')
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
continue;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Reset class context on unindented non-empty line
|
|
285
|
+
if (currentClass && /^\S/.test(line) && line.trim()) {
|
|
286
|
+
currentClass = null;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// import x, import x as y
|
|
290
|
+
const importMatch = line.match(/^import\s+([\w.]+)/);
|
|
291
|
+
if (importMatch) {
|
|
292
|
+
imports.push({
|
|
293
|
+
source: filePath,
|
|
294
|
+
target: importMatch[1],
|
|
295
|
+
names: [importMatch[1].split('.').pop()],
|
|
296
|
+
line: lineNum
|
|
297
|
+
});
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// from x import y, z
|
|
302
|
+
const fromMatch = line.match(/^from\s+([\w.]+)\s+import\s+(.+)/);
|
|
303
|
+
if (fromMatch) {
|
|
304
|
+
const names = fromMatch[2].split(',').map(n =>
|
|
305
|
+
n.trim().split(/\s+as\s+/)[0].trim()
|
|
306
|
+
).filter(Boolean);
|
|
307
|
+
imports.push({
|
|
308
|
+
source: filePath,
|
|
309
|
+
target: fromMatch[1],
|
|
310
|
+
names,
|
|
311
|
+
line: lineNum
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return { entities, imports, calls };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function getParser(ext) {
|
|
320
|
+
if (['.js', '.mjs', '.cjs', '.ts', '.tsx', '.jsx'].includes(ext)) {
|
|
321
|
+
return parseJavaScript;
|
|
322
|
+
}
|
|
323
|
+
if (ext === '.py') return parsePython;
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
module.exports = { parseJavaScript, parsePython, getParser };
|