preflight-mcp 0.1.2 → 0.1.4
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/README.md +49 -142
- package/README.zh-CN.md +141 -124
- package/dist/ast/treeSitter.js +588 -0
- package/dist/bundle/analysis.js +47 -0
- package/dist/bundle/context7.js +65 -36
- package/dist/bundle/facts.js +829 -0
- package/dist/bundle/github.js +34 -3
- package/dist/bundle/githubArchive.js +102 -29
- package/dist/bundle/overview.js +226 -48
- package/dist/bundle/service.js +250 -130
- package/dist/config.js +30 -3
- package/dist/context7/client.js +5 -2
- package/dist/evidence/dependencyGraph.js +1136 -0
- package/dist/http/server.js +109 -0
- package/dist/jobs/progressTracker.js +191 -0
- package/dist/search/sqliteFts.js +150 -10
- package/dist/server.js +340 -326
- package/dist/trace/service.js +108 -0
- package/dist/trace/store.js +170 -0
- package/package.json +4 -2
- package/dist/bundle/deepwiki.js +0 -206
|
@@ -0,0 +1,588 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import { Language, Parser } from 'web-tree-sitter';
|
|
4
|
+
const require = createRequire(import.meta.url);
|
|
5
|
+
let initPromise;
|
|
6
|
+
const languageCache = new Map();
|
|
7
|
+
function normalizeExt(p) {
|
|
8
|
+
return path.extname(p).toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
export function languageForFile(filePath) {
|
|
11
|
+
const ext = normalizeExt(filePath);
|
|
12
|
+
if (['.ts'].includes(ext))
|
|
13
|
+
return 'typescript';
|
|
14
|
+
if (['.tsx'].includes(ext))
|
|
15
|
+
return 'tsx';
|
|
16
|
+
if (['.js', '.jsx', '.mjs', '.cjs'].includes(ext))
|
|
17
|
+
return 'javascript';
|
|
18
|
+
if (ext === '.py')
|
|
19
|
+
return 'python';
|
|
20
|
+
if (ext === '.go')
|
|
21
|
+
return 'go';
|
|
22
|
+
if (ext === '.java')
|
|
23
|
+
return 'java';
|
|
24
|
+
if (ext === '.rs')
|
|
25
|
+
return 'rust';
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
async function ensureInit() {
|
|
29
|
+
initPromise ??= Parser.init();
|
|
30
|
+
await initPromise;
|
|
31
|
+
}
|
|
32
|
+
function wasmPathForLanguage(lang) {
|
|
33
|
+
const name = (() => {
|
|
34
|
+
switch (lang) {
|
|
35
|
+
case 'javascript':
|
|
36
|
+
return 'tree-sitter-javascript.wasm';
|
|
37
|
+
case 'typescript':
|
|
38
|
+
return 'tree-sitter-typescript.wasm';
|
|
39
|
+
case 'tsx':
|
|
40
|
+
return 'tree-sitter-tsx.wasm';
|
|
41
|
+
case 'python':
|
|
42
|
+
return 'tree-sitter-python.wasm';
|
|
43
|
+
case 'go':
|
|
44
|
+
return 'tree-sitter-go.wasm';
|
|
45
|
+
case 'java':
|
|
46
|
+
return 'tree-sitter-java.wasm';
|
|
47
|
+
case 'rust':
|
|
48
|
+
return 'tree-sitter-rust.wasm';
|
|
49
|
+
}
|
|
50
|
+
})();
|
|
51
|
+
return require.resolve(`@vscode/tree-sitter-wasm/wasm/${name}`);
|
|
52
|
+
}
|
|
53
|
+
async function loadLanguage(lang) {
|
|
54
|
+
const cached = languageCache.get(lang);
|
|
55
|
+
if (cached)
|
|
56
|
+
return cached;
|
|
57
|
+
const promise = (async () => {
|
|
58
|
+
await ensureInit();
|
|
59
|
+
const wasmPath = wasmPathForLanguage(lang);
|
|
60
|
+
return Language.load(wasmPath);
|
|
61
|
+
})();
|
|
62
|
+
languageCache.set(lang, promise);
|
|
63
|
+
return promise;
|
|
64
|
+
}
|
|
65
|
+
function rangeFromNode(n) {
|
|
66
|
+
return {
|
|
67
|
+
startLine: n.startPosition.row + 1,
|
|
68
|
+
startCol: n.startPosition.column + 1,
|
|
69
|
+
endLine: n.endPosition.row + 1,
|
|
70
|
+
endCol: n.endPosition.column + 1,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function firstStringFragment(node) {
|
|
74
|
+
const frags = node.descendantsOfType('string_fragment');
|
|
75
|
+
return frags[0] ?? null;
|
|
76
|
+
}
|
|
77
|
+
function firstOfTypes(node, types) {
|
|
78
|
+
for (const t of types) {
|
|
79
|
+
const found = node.descendantsOfType(t);
|
|
80
|
+
if (found[0])
|
|
81
|
+
return found[0];
|
|
82
|
+
}
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
function extractImportsJsTs(root, lang) {
|
|
86
|
+
const out = [];
|
|
87
|
+
for (const st of root.descendantsOfType(['import_statement', 'export_statement'])) {
|
|
88
|
+
const source = st.childForFieldName('source');
|
|
89
|
+
if (!source)
|
|
90
|
+
continue;
|
|
91
|
+
const frag = firstStringFragment(source);
|
|
92
|
+
if (!frag)
|
|
93
|
+
continue;
|
|
94
|
+
out.push({
|
|
95
|
+
language: lang,
|
|
96
|
+
kind: st.type === 'export_statement' ? 'exportFrom' : 'import',
|
|
97
|
+
module: frag.text,
|
|
98
|
+
range: rangeFromNode(frag),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
for (const call of root.descendantsOfType('call_expression')) {
|
|
102
|
+
const fn = call.childForFieldName('function');
|
|
103
|
+
if (!fn)
|
|
104
|
+
continue;
|
|
105
|
+
const args = call.childForFieldName('arguments');
|
|
106
|
+
const arg0 = args?.namedChild(0);
|
|
107
|
+
if (!arg0 || arg0.type !== 'string')
|
|
108
|
+
continue;
|
|
109
|
+
const frag = firstStringFragment(arg0);
|
|
110
|
+
if (!frag)
|
|
111
|
+
continue;
|
|
112
|
+
// Dynamic import: import("x")
|
|
113
|
+
if (fn.type === 'import') {
|
|
114
|
+
out.push({
|
|
115
|
+
language: lang,
|
|
116
|
+
kind: 'dynamicImport',
|
|
117
|
+
module: frag.text,
|
|
118
|
+
range: rangeFromNode(frag),
|
|
119
|
+
});
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
// CommonJS require: require("x")
|
|
123
|
+
if (fn.type === 'identifier' && fn.text === 'require') {
|
|
124
|
+
out.push({
|
|
125
|
+
language: lang,
|
|
126
|
+
kind: 'require',
|
|
127
|
+
module: frag.text,
|
|
128
|
+
range: rangeFromNode(frag),
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
function extractImportsPython(root) {
|
|
135
|
+
const out = [];
|
|
136
|
+
for (const st of root.descendantsOfType('import_statement')) {
|
|
137
|
+
const name = st.childForFieldName('name');
|
|
138
|
+
if (!name)
|
|
139
|
+
continue;
|
|
140
|
+
const dotted = name.type === 'aliased_import' ? name.childForFieldName('name') : name;
|
|
141
|
+
if (!dotted)
|
|
142
|
+
continue;
|
|
143
|
+
out.push({
|
|
144
|
+
language: 'python',
|
|
145
|
+
kind: 'pythonImport',
|
|
146
|
+
module: dotted.text,
|
|
147
|
+
range: rangeFromNode(dotted),
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
for (const st of root.descendantsOfType('import_from_statement')) {
|
|
151
|
+
const modName = st.childForFieldName('module_name');
|
|
152
|
+
if (!modName)
|
|
153
|
+
continue;
|
|
154
|
+
// For relative imports like `from . import foo`, tree-sitter gives module_name = `.` (relative_import)
|
|
155
|
+
// and then repeats `name:` fields for each imported identifier. Expand these into `.foo` so downstream
|
|
156
|
+
// consumers can resolve local imports.
|
|
157
|
+
if (modName.type === 'relative_import') {
|
|
158
|
+
const hasExplicitModule = modName.descendantsOfType('dotted_name')[0] != null;
|
|
159
|
+
if (!hasExplicitModule) {
|
|
160
|
+
const prefix = modName.text;
|
|
161
|
+
const names = st.childrenForFieldName('name');
|
|
162
|
+
let expanded = false;
|
|
163
|
+
for (const n of names) {
|
|
164
|
+
const base = n.type === 'aliased_import' ? n.childForFieldName('name') : n;
|
|
165
|
+
if (!base)
|
|
166
|
+
continue;
|
|
167
|
+
// `from . import *`
|
|
168
|
+
if (base.text === '*')
|
|
169
|
+
continue;
|
|
170
|
+
expanded = true;
|
|
171
|
+
out.push({
|
|
172
|
+
language: 'python',
|
|
173
|
+
kind: 'pythonFrom',
|
|
174
|
+
module: `${prefix}${base.text}`,
|
|
175
|
+
range: rangeFromNode(base),
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
if (expanded)
|
|
179
|
+
continue;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
out.push({
|
|
183
|
+
language: 'python',
|
|
184
|
+
kind: 'pythonFrom',
|
|
185
|
+
module: modName.text,
|
|
186
|
+
range: rangeFromNode(modName),
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return out;
|
|
190
|
+
}
|
|
191
|
+
function extractImportsGo(root) {
|
|
192
|
+
const out = [];
|
|
193
|
+
for (const spec of root.descendantsOfType('import_spec')) {
|
|
194
|
+
const p = spec.childForFieldName('path');
|
|
195
|
+
if (!p)
|
|
196
|
+
continue;
|
|
197
|
+
const content = firstOfTypes(p, [
|
|
198
|
+
'interpreted_string_literal_content',
|
|
199
|
+
'raw_string_literal_content',
|
|
200
|
+
]);
|
|
201
|
+
if (!content)
|
|
202
|
+
continue;
|
|
203
|
+
out.push({
|
|
204
|
+
language: 'go',
|
|
205
|
+
kind: 'goImport',
|
|
206
|
+
module: content.text,
|
|
207
|
+
range: rangeFromNode(content),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
}
|
|
212
|
+
function extractImportsJava(root) {
|
|
213
|
+
const out = [];
|
|
214
|
+
for (const decl of root.descendantsOfType('import_declaration')) {
|
|
215
|
+
const scoped = decl.namedChild(0);
|
|
216
|
+
if (!scoped || scoped.type !== 'scoped_identifier')
|
|
217
|
+
continue;
|
|
218
|
+
const asterisk = decl.descendantsOfType('asterisk')[0];
|
|
219
|
+
if (asterisk) {
|
|
220
|
+
out.push({
|
|
221
|
+
language: 'java',
|
|
222
|
+
kind: 'javaImport',
|
|
223
|
+
module: `${scoped.text}.*`,
|
|
224
|
+
range: {
|
|
225
|
+
startLine: scoped.startPosition.row + 1,
|
|
226
|
+
startCol: scoped.startPosition.column + 1,
|
|
227
|
+
endLine: asterisk.endPosition.row + 1,
|
|
228
|
+
endCol: asterisk.endPosition.column + 1,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
out.push({
|
|
234
|
+
language: 'java',
|
|
235
|
+
kind: 'javaImport',
|
|
236
|
+
module: scoped.text,
|
|
237
|
+
range: rangeFromNode(scoped),
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
function extractImportsRust(root) {
|
|
244
|
+
const out = [];
|
|
245
|
+
const unwrapUseAsClause = (n) => {
|
|
246
|
+
if (n.type !== 'use_as_clause')
|
|
247
|
+
return n;
|
|
248
|
+
return (n.childForFieldName('path') ?? n.namedChild(0) ?? n);
|
|
249
|
+
};
|
|
250
|
+
const rootNodeForUseListItem = (n) => {
|
|
251
|
+
const unwrapped = unwrapUseAsClause(n);
|
|
252
|
+
if (unwrapped.type === 'scoped_use_list') {
|
|
253
|
+
return unwrapped.childForFieldName('path') ?? null;
|
|
254
|
+
}
|
|
255
|
+
if (unwrapped.type === 'identifier' ||
|
|
256
|
+
unwrapped.type === 'scoped_identifier' ||
|
|
257
|
+
unwrapped.type === 'crate' ||
|
|
258
|
+
unwrapped.type === 'self' ||
|
|
259
|
+
unwrapped.type === 'super') {
|
|
260
|
+
return unwrapped;
|
|
261
|
+
}
|
|
262
|
+
return null;
|
|
263
|
+
};
|
|
264
|
+
for (const decl of root.descendantsOfType('use_declaration')) {
|
|
265
|
+
const arg = decl.childForFieldName('argument');
|
|
266
|
+
if (!arg)
|
|
267
|
+
continue;
|
|
268
|
+
if (arg.type === 'scoped_use_list') {
|
|
269
|
+
const p = arg.childForFieldName('path') ?? arg;
|
|
270
|
+
const list = arg.childForFieldName('list');
|
|
271
|
+
// Special-case: `use crate::{foo, bar};` / `use super::{foo, bar};` / `use self::{foo, bar};`
|
|
272
|
+
// These are very common for local module imports and should be expanded.
|
|
273
|
+
if (list && (p.type === 'crate' || p.type === 'super' || p.type === 'self')) {
|
|
274
|
+
for (const item of list.namedChildren) {
|
|
275
|
+
const rootNode = rootNodeForUseListItem(item);
|
|
276
|
+
if (!rootNode)
|
|
277
|
+
continue;
|
|
278
|
+
// `use crate::{self, foo};` doesn't add a meaningful module target for our purposes.
|
|
279
|
+
if (rootNode.type === 'self')
|
|
280
|
+
continue;
|
|
281
|
+
out.push({
|
|
282
|
+
language: 'rust',
|
|
283
|
+
kind: 'rustUse',
|
|
284
|
+
module: `${p.text}::${rootNode.text}`,
|
|
285
|
+
range: rangeFromNode(rootNode),
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
// Default behavior: treat the `path` as the imported module.
|
|
291
|
+
out.push({
|
|
292
|
+
language: 'rust',
|
|
293
|
+
kind: 'rustUse',
|
|
294
|
+
module: p.text,
|
|
295
|
+
range: rangeFromNode(p),
|
|
296
|
+
});
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
out.push({
|
|
300
|
+
language: 'rust',
|
|
301
|
+
kind: 'rustUse',
|
|
302
|
+
module: arg.text,
|
|
303
|
+
range: rangeFromNode(arg),
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
for (const decl of root.descendantsOfType('extern_crate_declaration')) {
|
|
307
|
+
const name = decl.childForFieldName('name');
|
|
308
|
+
if (!name)
|
|
309
|
+
continue;
|
|
310
|
+
out.push({
|
|
311
|
+
language: 'rust',
|
|
312
|
+
kind: 'rustExternCrate',
|
|
313
|
+
module: name.text,
|
|
314
|
+
range: rangeFromNode(name),
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
function extractImports(root, lang) {
|
|
320
|
+
switch (lang) {
|
|
321
|
+
case 'javascript':
|
|
322
|
+
case 'typescript':
|
|
323
|
+
case 'tsx':
|
|
324
|
+
return extractImportsJsTs(root, lang);
|
|
325
|
+
case 'python':
|
|
326
|
+
return extractImportsPython(root);
|
|
327
|
+
case 'go':
|
|
328
|
+
return extractImportsGo(root);
|
|
329
|
+
case 'java':
|
|
330
|
+
return extractImportsJava(root);
|
|
331
|
+
case 'rust':
|
|
332
|
+
return extractImportsRust(root);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function extractExportsJsTs(root) {
|
|
336
|
+
const out = new Set();
|
|
337
|
+
// ES module exports
|
|
338
|
+
for (const st of root.descendantsOfType('export_statement')) {
|
|
339
|
+
if (/^\s*export\s+default\b/.test(st.text)) {
|
|
340
|
+
out.add('default');
|
|
341
|
+
}
|
|
342
|
+
const clause = st.descendantsOfType('export_clause')[0];
|
|
343
|
+
if (clause) {
|
|
344
|
+
for (const spec of clause.descendantsOfType('export_specifier')) {
|
|
345
|
+
const alias = spec.childForFieldName('alias');
|
|
346
|
+
const name = (alias ?? spec.childForFieldName('name'));
|
|
347
|
+
if (name)
|
|
348
|
+
out.add(name.text);
|
|
349
|
+
}
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
// export function/class/type/interface/enum ...
|
|
353
|
+
const direct = st.namedChildren;
|
|
354
|
+
for (const child of direct) {
|
|
355
|
+
if (child.type === 'function_declaration' ||
|
|
356
|
+
child.type === 'class_declaration' ||
|
|
357
|
+
child.type === 'interface_declaration' ||
|
|
358
|
+
child.type === 'type_alias_declaration' ||
|
|
359
|
+
child.type === 'enum_declaration') {
|
|
360
|
+
const name = child.childForFieldName('name');
|
|
361
|
+
if (name)
|
|
362
|
+
out.add(name.text);
|
|
363
|
+
}
|
|
364
|
+
if (child.type === 'lexical_declaration' || child.type === 'variable_declaration') {
|
|
365
|
+
for (const decl of child.descendantsOfType('variable_declarator')) {
|
|
366
|
+
const name = decl.childForFieldName('name');
|
|
367
|
+
if (name && name.type === 'identifier')
|
|
368
|
+
out.add(name.text);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// CommonJS exports (best-effort): module.exports / exports.foo
|
|
374
|
+
for (const asn of root.descendantsOfType('assignment_expression')) {
|
|
375
|
+
const left = asn.childForFieldName('left');
|
|
376
|
+
if (!left)
|
|
377
|
+
continue;
|
|
378
|
+
// exports.foo = ...
|
|
379
|
+
if (left.type === 'member_expression') {
|
|
380
|
+
const obj = left.childForFieldName('object');
|
|
381
|
+
const prop = left.childForFieldName('property');
|
|
382
|
+
if (obj?.type === 'identifier' && obj.text === 'exports' && prop) {
|
|
383
|
+
out.add(prop.text);
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
// module.exports = ...
|
|
387
|
+
if (obj?.type === 'identifier' && obj.text === 'module' && prop && prop.text === 'exports') {
|
|
388
|
+
out.add('default');
|
|
389
|
+
continue;
|
|
390
|
+
}
|
|
391
|
+
// module.exports.foo = ...
|
|
392
|
+
if (obj?.type === 'member_expression' && prop) {
|
|
393
|
+
const obj2 = obj.childForFieldName('object');
|
|
394
|
+
const prop2 = obj.childForFieldName('property');
|
|
395
|
+
if (obj2?.type === 'identifier' && obj2.text === 'module' && prop2?.text === 'exports') {
|
|
396
|
+
out.add(prop.text);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
return Array.from(out);
|
|
402
|
+
}
|
|
403
|
+
function unquoteStringLiteral(raw) {
|
|
404
|
+
let t = raw.trim();
|
|
405
|
+
// Handles prefixes like r"..", f'..', etc.
|
|
406
|
+
t = t.replace(/^[rRuUbBfF]+/, '');
|
|
407
|
+
const q = t[0];
|
|
408
|
+
if (q !== '"' && q !== "'")
|
|
409
|
+
return null;
|
|
410
|
+
if (t.length < 2 || t[t.length - 1] !== q)
|
|
411
|
+
return null;
|
|
412
|
+
return t.slice(1, -1);
|
|
413
|
+
}
|
|
414
|
+
function extractExportsPython(root) {
|
|
415
|
+
const out = new Set();
|
|
416
|
+
// __all__ = ['a', 'b'] (literal only)
|
|
417
|
+
for (const asn of root.descendantsOfType(['assignment', 'assignment_statement'])) {
|
|
418
|
+
// Keep this conservative: only accept module-level assignments.
|
|
419
|
+
// NOTE: web-tree-sitter node wrappers are not guaranteed to be referentially stable,
|
|
420
|
+
// so avoid `=== root` checks.
|
|
421
|
+
const parent = asn.parent;
|
|
422
|
+
const grandParent = parent?.parent;
|
|
423
|
+
const isTopLevel = (parent !== null && parent.parent === null) ||
|
|
424
|
+
(grandParent !== null && grandParent?.parent === null);
|
|
425
|
+
if (!isTopLevel)
|
|
426
|
+
continue;
|
|
427
|
+
const left = asn.childForFieldName('left') ?? asn.namedChild(0);
|
|
428
|
+
if (!left || left.text !== '__all__')
|
|
429
|
+
continue;
|
|
430
|
+
const right = asn.childForFieldName('right') ?? asn.namedChild(asn.namedChildCount - 1);
|
|
431
|
+
let listOrTuple = null;
|
|
432
|
+
if (right) {
|
|
433
|
+
listOrTuple =
|
|
434
|
+
right.type === 'list' || right.type === 'tuple'
|
|
435
|
+
? right
|
|
436
|
+
: right.type === 'parenthesized_expression'
|
|
437
|
+
? (right.descendantsOfType('list')[0] ?? right.descendantsOfType('tuple')[0] ?? null)
|
|
438
|
+
: null;
|
|
439
|
+
}
|
|
440
|
+
// Fallback: different python grammars may not expose assignment fields consistently.
|
|
441
|
+
if (!listOrTuple) {
|
|
442
|
+
listOrTuple = firstOfTypes(asn, ['list', 'tuple']);
|
|
443
|
+
}
|
|
444
|
+
if (!listOrTuple)
|
|
445
|
+
continue;
|
|
446
|
+
for (const s of listOrTuple.descendantsOfType('string')) {
|
|
447
|
+
const inner = unquoteStringLiteral(s.text);
|
|
448
|
+
if (inner)
|
|
449
|
+
out.add(inner);
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Top-level defs/classes (heuristic for public API)
|
|
453
|
+
for (const child of root.namedChildren) {
|
|
454
|
+
if (child.type === 'function_definition' || child.type === 'class_definition') {
|
|
455
|
+
const name = child.childForFieldName('name');
|
|
456
|
+
const n = name?.text ?? '';
|
|
457
|
+
if (n && !n.startsWith('_'))
|
|
458
|
+
out.add(n);
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
return Array.from(out);
|
|
462
|
+
}
|
|
463
|
+
function extractExportsGo(root) {
|
|
464
|
+
const out = new Set();
|
|
465
|
+
const topLevel = root.namedChildren;
|
|
466
|
+
for (const n of topLevel) {
|
|
467
|
+
if (n.type === 'function_declaration' || n.type === 'method_declaration') {
|
|
468
|
+
const name = n.childForFieldName('name') ?? n.descendantsOfType('identifier')[0];
|
|
469
|
+
const t = name?.text ?? '';
|
|
470
|
+
if (t && /^[A-Z]/.test(t))
|
|
471
|
+
out.add(t);
|
|
472
|
+
}
|
|
473
|
+
if (n.type === 'type_declaration') {
|
|
474
|
+
for (const spec of n.descendantsOfType('type_spec')) {
|
|
475
|
+
const name = spec.childForFieldName('name') ?? spec.descendantsOfType('type_identifier')[0];
|
|
476
|
+
const t = name?.text ?? '';
|
|
477
|
+
if (t && /^[A-Z]/.test(t))
|
|
478
|
+
out.add(t);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
return Array.from(out);
|
|
483
|
+
}
|
|
484
|
+
function extractExportsJava(root) {
|
|
485
|
+
const out = new Set();
|
|
486
|
+
for (const n of root.namedChildren) {
|
|
487
|
+
if (n.type !== 'class_declaration' &&
|
|
488
|
+
n.type !== 'interface_declaration' &&
|
|
489
|
+
n.type !== 'enum_declaration' &&
|
|
490
|
+
n.type !== 'record_declaration') {
|
|
491
|
+
continue;
|
|
492
|
+
}
|
|
493
|
+
const mods = n.childForFieldName('modifiers') ?? n.namedChild(0);
|
|
494
|
+
if (mods && !mods.text.includes('public'))
|
|
495
|
+
continue;
|
|
496
|
+
const name = n.childForFieldName('name');
|
|
497
|
+
if (name)
|
|
498
|
+
out.add(name.text);
|
|
499
|
+
}
|
|
500
|
+
return Array.from(out);
|
|
501
|
+
}
|
|
502
|
+
function hasRustPubVisibility(n) {
|
|
503
|
+
const first = n.namedChild(0);
|
|
504
|
+
if (first?.type === 'visibility_modifier' && first.text.startsWith('pub'))
|
|
505
|
+
return true;
|
|
506
|
+
const vis = n.childForFieldName('visibility');
|
|
507
|
+
if (vis?.type === 'visibility_modifier' && vis.text.startsWith('pub'))
|
|
508
|
+
return true;
|
|
509
|
+
return false;
|
|
510
|
+
}
|
|
511
|
+
function extractExportsRust(root) {
|
|
512
|
+
const out = new Set();
|
|
513
|
+
const candidates = [
|
|
514
|
+
'function_item',
|
|
515
|
+
'struct_item',
|
|
516
|
+
'enum_item',
|
|
517
|
+
'trait_item',
|
|
518
|
+
'type_item',
|
|
519
|
+
'const_item',
|
|
520
|
+
'static_item',
|
|
521
|
+
'mod_item',
|
|
522
|
+
];
|
|
523
|
+
for (const n of root.namedChildren) {
|
|
524
|
+
if (!candidates.includes(n.type))
|
|
525
|
+
continue;
|
|
526
|
+
if (!hasRustPubVisibility(n))
|
|
527
|
+
continue;
|
|
528
|
+
const name = n.childForFieldName('name');
|
|
529
|
+
if (name)
|
|
530
|
+
out.add(name.text);
|
|
531
|
+
}
|
|
532
|
+
return Array.from(out);
|
|
533
|
+
}
|
|
534
|
+
function extractExports(root, lang) {
|
|
535
|
+
switch (lang) {
|
|
536
|
+
case 'javascript':
|
|
537
|
+
case 'typescript':
|
|
538
|
+
case 'tsx':
|
|
539
|
+
return extractExportsJsTs(root);
|
|
540
|
+
case 'python':
|
|
541
|
+
return extractExportsPython(root);
|
|
542
|
+
case 'go':
|
|
543
|
+
return extractExportsGo(root);
|
|
544
|
+
case 'java':
|
|
545
|
+
return extractExportsJava(root);
|
|
546
|
+
case 'rust':
|
|
547
|
+
return extractExportsRust(root);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
export async function extractModuleSyntaxWasm(filePath, normalizedContent) {
|
|
551
|
+
const lang = languageForFile(filePath);
|
|
552
|
+
if (!lang)
|
|
553
|
+
return null;
|
|
554
|
+
const language = await loadLanguage(lang);
|
|
555
|
+
const parser = new Parser();
|
|
556
|
+
try {
|
|
557
|
+
parser.setLanguage(language);
|
|
558
|
+
const tree = parser.parse(normalizedContent);
|
|
559
|
+
if (!tree)
|
|
560
|
+
return { language: lang, imports: [], exports: [] };
|
|
561
|
+
try {
|
|
562
|
+
const root = tree.rootNode;
|
|
563
|
+
return {
|
|
564
|
+
language: lang,
|
|
565
|
+
imports: extractImports(root, lang),
|
|
566
|
+
exports: extractExports(root, lang),
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
finally {
|
|
570
|
+
tree.delete();
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
finally {
|
|
574
|
+
parser.delete();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
export async function extractImportRefsWasm(filePath, normalizedContent) {
|
|
578
|
+
const res = await extractModuleSyntaxWasm(filePath, normalizedContent);
|
|
579
|
+
if (!res)
|
|
580
|
+
return null;
|
|
581
|
+
return { language: res.language, imports: res.imports };
|
|
582
|
+
}
|
|
583
|
+
export async function extractExportedSymbolsWasm(filePath, normalizedContent) {
|
|
584
|
+
const res = await extractModuleSyntaxWasm(filePath, normalizedContent);
|
|
585
|
+
if (!res)
|
|
586
|
+
return null;
|
|
587
|
+
return { language: res.language, exports: res.exports };
|
|
588
|
+
}
|
package/dist/bundle/analysis.js
CHANGED
|
@@ -12,6 +12,7 @@ export async function analyzeBundleStatic(params) {
|
|
|
12
12
|
const facts = await extractBundleFacts({
|
|
13
13
|
bundleRoot: params.bundleRoot,
|
|
14
14
|
repos: params.repos,
|
|
15
|
+
enablePhase2: params.mode === 'full', // Enable Phase 2 module analysis for 'full' mode
|
|
15
16
|
});
|
|
16
17
|
const factsPath = path.join(params.bundleRoot, 'analysis', 'FACTS.json');
|
|
17
18
|
await writeFacts(factsPath, facts);
|
|
@@ -87,5 +88,51 @@ export function generateQuickSummary(facts) {
|
|
|
87
88
|
if (facts.fileStructure.topLevelDirs.length > 0) {
|
|
88
89
|
sections.push(`- Top-level directories: ${facts.fileStructure.topLevelDirs.join(', ')}`);
|
|
89
90
|
}
|
|
91
|
+
sections.push('');
|
|
92
|
+
// Phase 2: Modules (if available)
|
|
93
|
+
if (facts.modules && facts.modules.length > 0) {
|
|
94
|
+
sections.push('## Module Analysis');
|
|
95
|
+
sections.push(`- Total modules: ${facts.modules.length}`);
|
|
96
|
+
const roleCount = facts.modules.reduce((acc, m) => {
|
|
97
|
+
acc[m.role] = (acc[m.role] || 0) + 1;
|
|
98
|
+
return acc;
|
|
99
|
+
}, {});
|
|
100
|
+
sections.push(`- Core modules: ${roleCount['core'] || 0}`);
|
|
101
|
+
sections.push(`- Utility modules: ${roleCount['utility'] || 0}`);
|
|
102
|
+
sections.push(`- Test modules: ${roleCount['test'] || 0}`);
|
|
103
|
+
sections.push(`- Config modules: ${roleCount['config'] || 0}`);
|
|
104
|
+
const coreModules = facts.modules.filter(m => m.role === 'core').slice(0, 5);
|
|
105
|
+
if (coreModules.length > 0) {
|
|
106
|
+
sections.push('\n### Top Core Modules');
|
|
107
|
+
for (const mod of coreModules) {
|
|
108
|
+
sections.push(`- ${mod.path} (${mod.exports.length} exports, ${mod.loc} LOC)`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
sections.push('');
|
|
112
|
+
}
|
|
113
|
+
// Phase 2: Architecture patterns (if available)
|
|
114
|
+
if (facts.patterns && facts.patterns.length > 0) {
|
|
115
|
+
sections.push('## Architecture Patterns');
|
|
116
|
+
sections.push(facts.patterns.map(p => `- ${p}`).join('\n'));
|
|
117
|
+
sections.push('');
|
|
118
|
+
}
|
|
119
|
+
// Phase 2: Tech stack (if available)
|
|
120
|
+
if (facts.techStack) {
|
|
121
|
+
sections.push('## Technology Stack');
|
|
122
|
+
sections.push(`- Language: ${facts.techStack.language}`);
|
|
123
|
+
if (facts.techStack.runtime) {
|
|
124
|
+
sections.push(`- Runtime: ${facts.techStack.runtime}`);
|
|
125
|
+
}
|
|
126
|
+
if (facts.techStack.packageManager) {
|
|
127
|
+
sections.push(`- Package Manager: ${facts.techStack.packageManager}`);
|
|
128
|
+
}
|
|
129
|
+
if (facts.techStack.buildTools) {
|
|
130
|
+
sections.push(`- Build Tools: ${facts.techStack.buildTools.join(', ')}`);
|
|
131
|
+
}
|
|
132
|
+
if (facts.techStack.testFrameworks) {
|
|
133
|
+
sections.push(`- Test Frameworks: ${facts.techStack.testFrameworks.join(', ')}`);
|
|
134
|
+
}
|
|
135
|
+
sections.push('');
|
|
136
|
+
}
|
|
90
137
|
return sections.join('\n') + '\n';
|
|
91
138
|
}
|