projscan 0.5.0 → 0.6.0
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 +55 -33
- package/dist/core/ast.d.ts +34 -0
- package/dist/core/ast.js +270 -0
- package/dist/core/ast.js.map +1 -0
- package/dist/core/codeGraph.d.ts +36 -0
- package/dist/core/codeGraph.js +167 -0
- package/dist/core/codeGraph.js.map +1 -0
- package/dist/core/importGraph.d.ts +5 -15
- package/dist/core/importGraph.js +12 -110
- package/dist/core/importGraph.js.map +1 -1
- package/dist/core/indexCache.d.ts +12 -0
- package/dist/core/indexCache.js +97 -0
- package/dist/core/indexCache.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/server.js +12 -2
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tokenBudget.d.ts +25 -0
- package/dist/mcp/tokenBudget.js +89 -0
- package/dist/mcp/tokenBudget.js.map +1 -0
- package/dist/mcp/tools.js +170 -0
- package/dist/mcp/tools.js.map +1 -1
- package/dist/utils/banner.js +5 -5
- package/dist/utils/banner.js.map +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -6,9 +6,9 @@
|
|
|
6
6
|
[](https://github.com/abhiyoheswaran1/projscan/blob/main/LICENSE)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
|
|
9
|
-
**
|
|
9
|
+
**Agent-first code intelligence.** An MCP server that lets AI coding agents (Claude Code, Cursor, Windsurf) query your codebase — with a CLI for humans on the side.
|
|
10
10
|
|
|
11
|
-
[
|
|
11
|
+
[AI Agent Quick Start](#ai-agent-integration-mcp) · [CLI Quick Start](#quick-start) · [Commands](#commands) · [Full Guide](docs/GUIDE.md) · [Roadmap](docs/ROADMAP.md)
|
|
12
12
|
|
|
13
13
|
<img src="docs/hero.png" alt="projscan banner" width="600">
|
|
14
14
|
|
|
@@ -18,16 +18,13 @@
|
|
|
18
18
|
|
|
19
19
|
## Why?
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
AI coding agents are becoming the primary interface to code. Today, when you ask your agent *"which files implement auth?"* or *"what breaks if I bump React from 18 to 19?"* — it either guesses from names, or it shells out to grep and reads raw output not built for it.
|
|
22
22
|
|
|
23
|
-
-
|
|
24
|
-
- Is there linting? Testing? Formatting?
|
|
25
|
-
- What's the project structure?
|
|
26
|
-
- Are the dependencies healthy?
|
|
23
|
+
**projscan is the first code-intelligence tool built for agents, not for humans.** Your agent gets a fast, AST-accurate, context-budget-aware view of your codebase through 13 structured MCP tools. It can query the import graph, find symbol definitions, preview upgrades, rank hotspots — without loading the file tree into its context.
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
Humans get the same thing through the CLI.
|
|
29
26
|
|
|
30
|
-
**
|
|
27
|
+
**Everything is offline-first. Zero network calls. No API keys.**
|
|
31
28
|
|
|
32
29
|
```bash
|
|
33
30
|
npx projscan
|
|
@@ -388,27 +385,7 @@ Coverage is also automatically joined into `projscan hotspots` when one of those
|
|
|
388
385
|
|
|
389
386
|
## AI Agent Integration (MCP)
|
|
390
387
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
**Tools** (11):
|
|
394
|
-
- `projscan_analyze` — full project report
|
|
395
|
-
- `projscan_doctor` — health score + issues
|
|
396
|
-
- `projscan_hotspots` — risk-ranked files (with `limit`, `since` args)
|
|
397
|
-
- `projscan_file` — per-file risk + ownership + related issues
|
|
398
|
-
- `projscan_explain` — per-file purpose, imports, exports, smells
|
|
399
|
-
- `projscan_structure` — directory tree
|
|
400
|
-
- `projscan_dependencies` — package audit
|
|
401
|
-
- `projscan_outdated` — declared-vs-installed drift
|
|
402
|
-
- `projscan_audit` — npm audit, normalized
|
|
403
|
-
- `projscan_upgrade` — offline upgrade preview with CHANGELOG + importers
|
|
404
|
-
- `projscan_coverage` — coverage × hotspots, "scariest untested files"
|
|
405
|
-
|
|
406
|
-
**Prompts** (2, parameterized with live project data):
|
|
407
|
-
- `prioritize_refactoring` — ranked plan grounded in current hotspots
|
|
408
|
-
- `investigate_file` — senior-engineer brief for a specific file
|
|
409
|
-
|
|
410
|
-
**Resources** (3, readable on demand):
|
|
411
|
-
- `projscan://health` · `projscan://hotspots` · `projscan://structure`
|
|
388
|
+
**This is the primary way to use projscan.** `projscan mcp` starts an [MCP](https://modelcontextprotocol.io) server over stdio so AI coding agents can query your codebase with real structural accuracy — not regex, not grep.
|
|
412
389
|
|
|
413
390
|
### Claude Code
|
|
414
391
|
|
|
@@ -418,8 +395,6 @@ claude mcp add projscan -- npx projscan mcp
|
|
|
418
395
|
|
|
419
396
|
### Cursor / Windsurf / any MCP client
|
|
420
397
|
|
|
421
|
-
Add to your MCP config:
|
|
422
|
-
|
|
423
398
|
```json
|
|
424
399
|
{
|
|
425
400
|
"mcpServers": {
|
|
@@ -431,7 +406,54 @@ Add to your MCP config:
|
|
|
431
406
|
}
|
|
432
407
|
```
|
|
433
408
|
|
|
434
|
-
|
|
409
|
+
### What agents can ask
|
|
410
|
+
|
|
411
|
+
- *"Who imports `src/auth/jwt.ts`?"* → `projscan_graph { file, direction: "importers" }`
|
|
412
|
+
- *"Where is `runAudit` defined?"* → `projscan_search { query: "runAudit", scope: "symbols" }`
|
|
413
|
+
- *"Which files implement auth?"* → `projscan_search { query: "auth", scope: "content" }`
|
|
414
|
+
- *"What are the scariest untested files?"* → `projscan_coverage`
|
|
415
|
+
- *"What breaks if I bump chalk to 6?"* → `projscan_upgrade { package: "chalk" }`
|
|
416
|
+
- *"Where should I refactor first?"* → `projscan_hotspots`
|
|
417
|
+
|
|
418
|
+
### The 13 MCP tools
|
|
419
|
+
|
|
420
|
+
**Structural (0.6.0 — new, agent-native):**
|
|
421
|
+
- **`projscan_graph`** — query the AST-based code graph. Directions: `imports`, `exports`, `importers`, `symbol_defs`, `package_importers`. Millisecond responses on a warm cache.
|
|
422
|
+
- **`projscan_search`** — fast search across `symbols` (exported names), `files` (path substring), or `content` (source substring with line + excerpt). Replaces the temptation to shell out to grep.
|
|
423
|
+
|
|
424
|
+
**Analysis:**
|
|
425
|
+
- `projscan_analyze` — full project report
|
|
426
|
+
- `projscan_doctor` — health score + issues
|
|
427
|
+
- `projscan_hotspots` — risk-ranked files (churn × complexity × issues × ownership × coverage)
|
|
428
|
+
- `projscan_file` — per-file risk + ownership + related issues
|
|
429
|
+
- `projscan_explain` — per-file purpose, imports, exports, smells
|
|
430
|
+
- `projscan_structure` — directory tree
|
|
431
|
+
- `projscan_coverage` — scariest untested files (coverage × hotspots)
|
|
432
|
+
|
|
433
|
+
**Dependencies:**
|
|
434
|
+
- `projscan_dependencies` — declared deps, risks
|
|
435
|
+
- `projscan_outdated` — declared-vs-installed drift (offline)
|
|
436
|
+
- `projscan_audit` — normalized `npm audit`
|
|
437
|
+
- `projscan_upgrade` — upgrade preview (CHANGELOG + importers, offline)
|
|
438
|
+
|
|
439
|
+
### Context-window budgeting
|
|
440
|
+
|
|
441
|
+
**Every MCP tool accepts an optional `max_tokens` argument.** Set it and projscan serializes the result, and — if over budget — truncates the largest array field record-by-record until it fits. Responses include a `_budget` sidecar when truncated so your agent knows it got a partial view.
|
|
442
|
+
|
|
443
|
+
```json
|
|
444
|
+
{ "name": "projscan_hotspots", "arguments": { "limit": 100, "max_tokens": 800 } }
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
### Incremental index cache
|
|
448
|
+
|
|
449
|
+
projscan caches parsed ASTs at `.projscan-cache/graph.json` (auto-gitignored). First run populates it; subsequent runs re-parse only files whose `mtime` changed. Agent queries on a warm cache are milliseconds, not seconds.
|
|
450
|
+
|
|
451
|
+
### Prompts (2, parameterized with live project data)
|
|
452
|
+
- `prioritize_refactoring` — ranked plan grounded in current hotspots
|
|
453
|
+
- `investigate_file` — senior-engineer brief for a specific file
|
|
454
|
+
|
|
455
|
+
### Resources (3, readable on demand)
|
|
456
|
+
- `projscan://health` · `projscan://hotspots` · `projscan://structure`
|
|
435
457
|
|
|
436
458
|
## Use Cases
|
|
437
459
|
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export type SymbolKind = 'function' | 'class' | 'variable' | 'type' | 'interface' | 'enum' | 'default' | 'unknown';
|
|
2
|
+
export interface AstImport {
|
|
3
|
+
source: string;
|
|
4
|
+
kind: 'static' | 'dynamic' | 'require' | 'reexport';
|
|
5
|
+
specifiers: string[];
|
|
6
|
+
typeOnly: boolean;
|
|
7
|
+
line: number;
|
|
8
|
+
}
|
|
9
|
+
export interface AstExport {
|
|
10
|
+
name: string;
|
|
11
|
+
kind: SymbolKind;
|
|
12
|
+
typeOnly: boolean;
|
|
13
|
+
line: number;
|
|
14
|
+
}
|
|
15
|
+
export interface AstResult {
|
|
16
|
+
ok: boolean;
|
|
17
|
+
reason?: string;
|
|
18
|
+
imports: AstImport[];
|
|
19
|
+
exports: AstExport[];
|
|
20
|
+
callSites: string[];
|
|
21
|
+
lineCount: number;
|
|
22
|
+
}
|
|
23
|
+
/** Is this a file we should try to AST-parse at all? */
|
|
24
|
+
export declare function isParseable(filePath: string): boolean;
|
|
25
|
+
/**
|
|
26
|
+
* Parse a source file and extract imports, exports, and call sites.
|
|
27
|
+
*
|
|
28
|
+
* Uses @babel/parser with generous options so we accept real-world code:
|
|
29
|
+
* TypeScript, JSX, decorators, top-level await, class properties, etc.
|
|
30
|
+
*
|
|
31
|
+
* Failures return ok:false with a reason — callers decide whether to fall
|
|
32
|
+
* back to regex or skip the file. Never throws.
|
|
33
|
+
*/
|
|
34
|
+
export declare function parseSource(filePath: string, content: string): AstResult;
|
package/dist/core/ast.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import { parse } from '@babel/parser';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
const EMPTY = {
|
|
4
|
+
ok: false,
|
|
5
|
+
reason: 'unparsed',
|
|
6
|
+
imports: [],
|
|
7
|
+
exports: [],
|
|
8
|
+
callSites: [],
|
|
9
|
+
lineCount: 0,
|
|
10
|
+
};
|
|
11
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
12
|
+
'.ts',
|
|
13
|
+
'.tsx',
|
|
14
|
+
'.js',
|
|
15
|
+
'.jsx',
|
|
16
|
+
'.mjs',
|
|
17
|
+
'.cjs',
|
|
18
|
+
'.mts',
|
|
19
|
+
'.cts',
|
|
20
|
+
]);
|
|
21
|
+
/** Is this a file we should try to AST-parse at all? */
|
|
22
|
+
export function isParseable(filePath) {
|
|
23
|
+
return SOURCE_EXTENSIONS.has(path.extname(filePath).toLowerCase());
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse a source file and extract imports, exports, and call sites.
|
|
27
|
+
*
|
|
28
|
+
* Uses @babel/parser with generous options so we accept real-world code:
|
|
29
|
+
* TypeScript, JSX, decorators, top-level await, class properties, etc.
|
|
30
|
+
*
|
|
31
|
+
* Failures return ok:false with a reason — callers decide whether to fall
|
|
32
|
+
* back to regex or skip the file. Never throws.
|
|
33
|
+
*/
|
|
34
|
+
export function parseSource(filePath, content) {
|
|
35
|
+
if (!isParseable(filePath)) {
|
|
36
|
+
return { ...EMPTY, reason: 'non-source extension' };
|
|
37
|
+
}
|
|
38
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
39
|
+
const isTypeScript = ext === '.ts' || ext === '.tsx' || ext === '.mts' || ext === '.cts';
|
|
40
|
+
const isJSX = ext === '.tsx' || ext === '.jsx';
|
|
41
|
+
const plugins = [];
|
|
42
|
+
if (isTypeScript)
|
|
43
|
+
plugins.push('typescript');
|
|
44
|
+
if (isJSX)
|
|
45
|
+
plugins.push('jsx');
|
|
46
|
+
plugins.push('decorators-legacy', 'dynamicImport', 'topLevelAwait');
|
|
47
|
+
let ast;
|
|
48
|
+
try {
|
|
49
|
+
ast = parse(content, {
|
|
50
|
+
sourceType: 'module',
|
|
51
|
+
allowImportExportEverywhere: true,
|
|
52
|
+
allowAwaitOutsideFunction: true,
|
|
53
|
+
allowReturnOutsideFunction: true,
|
|
54
|
+
allowSuperOutsideMethod: true,
|
|
55
|
+
errorRecovery: true,
|
|
56
|
+
plugins,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
61
|
+
return { ...EMPTY, reason: `parse error: ${msg.slice(0, 120)}` };
|
|
62
|
+
}
|
|
63
|
+
const imports = [];
|
|
64
|
+
const exports = [];
|
|
65
|
+
const callSites = [];
|
|
66
|
+
for (const node of ast.program.body) {
|
|
67
|
+
visitTopLevel(node, imports, exports);
|
|
68
|
+
}
|
|
69
|
+
// Second pass: extract dynamic imports + call sites. Walk the whole tree
|
|
70
|
+
// (cheap — we already have the AST in memory).
|
|
71
|
+
walk(ast.program, (n) => {
|
|
72
|
+
if (n.type === 'CallExpression') {
|
|
73
|
+
const callee = n.callee;
|
|
74
|
+
if (callee.type === 'Identifier') {
|
|
75
|
+
callSites.push(callee.name);
|
|
76
|
+
}
|
|
77
|
+
else if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
|
|
78
|
+
callSites.push(callee.property.name);
|
|
79
|
+
}
|
|
80
|
+
else if (callee.type === 'Import' && n.arguments[0] && n.arguments[0].type === 'StringLiteral') {
|
|
81
|
+
imports.push({
|
|
82
|
+
source: n.arguments[0].value,
|
|
83
|
+
kind: 'dynamic',
|
|
84
|
+
specifiers: [],
|
|
85
|
+
typeOnly: false,
|
|
86
|
+
line: n.loc?.start.line ?? 0,
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// CommonJS require()
|
|
90
|
+
if (callee.type === 'Identifier' &&
|
|
91
|
+
callee.name === 'require' &&
|
|
92
|
+
n.arguments[0] &&
|
|
93
|
+
n.arguments[0].type === 'StringLiteral') {
|
|
94
|
+
imports.push({
|
|
95
|
+
source: n.arguments[0].value,
|
|
96
|
+
kind: 'require',
|
|
97
|
+
specifiers: [],
|
|
98
|
+
typeOnly: false,
|
|
99
|
+
line: n.loc?.start.line ?? 0,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
return {
|
|
105
|
+
ok: true,
|
|
106
|
+
imports,
|
|
107
|
+
exports,
|
|
108
|
+
callSites: [...new Set(callSites)],
|
|
109
|
+
lineCount: content ? content.split('\n').length : 0,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function visitTopLevel(node, imports, exports) {
|
|
113
|
+
switch (node.type) {
|
|
114
|
+
case 'ImportDeclaration': {
|
|
115
|
+
imports.push(importFromNode(node));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
case 'ExportNamedDeclaration': {
|
|
119
|
+
collectNamedExport(node, exports, imports);
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
case 'ExportDefaultDeclaration': {
|
|
123
|
+
exports.push({
|
|
124
|
+
name: 'default',
|
|
125
|
+
kind: 'default',
|
|
126
|
+
typeOnly: false,
|
|
127
|
+
line: node.loc?.start.line ?? 0,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
case 'ExportAllDeclaration': {
|
|
132
|
+
const source = node.source.value;
|
|
133
|
+
imports.push({
|
|
134
|
+
source,
|
|
135
|
+
kind: 'reexport',
|
|
136
|
+
specifiers: [],
|
|
137
|
+
typeOnly: Boolean(node.exportKind === 'type'),
|
|
138
|
+
line: node.loc?.start.line ?? 0,
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
default:
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
function importFromNode(node) {
|
|
147
|
+
const specifiers = node.specifiers.map((s) => {
|
|
148
|
+
if (s.type === 'ImportDefaultSpecifier')
|
|
149
|
+
return 'default';
|
|
150
|
+
if (s.type === 'ImportNamespaceSpecifier')
|
|
151
|
+
return '*';
|
|
152
|
+
if (s.type === 'ImportSpecifier') {
|
|
153
|
+
const imported = s.imported;
|
|
154
|
+
if (imported.type === 'Identifier')
|
|
155
|
+
return imported.name;
|
|
156
|
+
return imported.value;
|
|
157
|
+
}
|
|
158
|
+
return '';
|
|
159
|
+
});
|
|
160
|
+
return {
|
|
161
|
+
source: node.source.value,
|
|
162
|
+
kind: 'static',
|
|
163
|
+
specifiers: specifiers.filter(Boolean),
|
|
164
|
+
typeOnly: node.importKind === 'type',
|
|
165
|
+
line: node.loc?.start.line ?? 0,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
function collectNamedExport(node, exports, imports) {
|
|
169
|
+
// Re-export: export { X } from 'source'
|
|
170
|
+
if (node.source) {
|
|
171
|
+
imports.push({
|
|
172
|
+
source: node.source.value,
|
|
173
|
+
kind: 'reexport',
|
|
174
|
+
specifiers: node.specifiers.map((s) => {
|
|
175
|
+
if (s.type === 'ExportSpecifier') {
|
|
176
|
+
const exported = s.exported;
|
|
177
|
+
return exported.type === 'Identifier' ? exported.name : exported.value;
|
|
178
|
+
}
|
|
179
|
+
return '';
|
|
180
|
+
}).filter(Boolean),
|
|
181
|
+
typeOnly: node.exportKind === 'type',
|
|
182
|
+
line: node.loc?.start.line ?? 0,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
// Inline declaration: export function foo() {} / export const x = ... / etc.
|
|
186
|
+
if (node.declaration) {
|
|
187
|
+
const typeOnly = node.exportKind === 'type';
|
|
188
|
+
const line = node.declaration.loc?.start.line ?? node.loc?.start.line ?? 0;
|
|
189
|
+
switch (node.declaration.type) {
|
|
190
|
+
case 'FunctionDeclaration': {
|
|
191
|
+
const name = node.declaration.id?.name;
|
|
192
|
+
if (name)
|
|
193
|
+
exports.push({ name, kind: 'function', typeOnly, line });
|
|
194
|
+
return;
|
|
195
|
+
}
|
|
196
|
+
case 'ClassDeclaration': {
|
|
197
|
+
const name = node.declaration.id?.name;
|
|
198
|
+
if (name)
|
|
199
|
+
exports.push({ name, kind: 'class', typeOnly, line });
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
case 'VariableDeclaration': {
|
|
203
|
+
for (const decl of node.declaration.declarations) {
|
|
204
|
+
if (decl.id.type === 'Identifier') {
|
|
205
|
+
exports.push({ name: decl.id.name, kind: 'variable', typeOnly, line });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
case 'TSInterfaceDeclaration': {
|
|
211
|
+
const name = node.declaration.id.name;
|
|
212
|
+
exports.push({ name, kind: 'interface', typeOnly: true, line });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
case 'TSTypeAliasDeclaration': {
|
|
216
|
+
const name = node.declaration.id.name;
|
|
217
|
+
exports.push({ name, kind: 'type', typeOnly: true, line });
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
case 'TSEnumDeclaration': {
|
|
221
|
+
const name = node.declaration.id.name;
|
|
222
|
+
exports.push({ name, kind: 'enum', typeOnly, line });
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
default:
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
// Named re-export of local symbols: export { foo, bar }
|
|
230
|
+
for (const spec of node.specifiers) {
|
|
231
|
+
if (spec.type !== 'ExportSpecifier')
|
|
232
|
+
continue;
|
|
233
|
+
const exported = spec.exported;
|
|
234
|
+
const name = exported.type === 'Identifier' ? exported.name : exported.value;
|
|
235
|
+
exports.push({
|
|
236
|
+
name,
|
|
237
|
+
kind: 'unknown',
|
|
238
|
+
typeOnly: node.exportKind === 'type',
|
|
239
|
+
line: spec.loc?.start.line ?? node.loc?.start.line ?? 0,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Lightweight AST walker. We only care about recursing through node properties
|
|
245
|
+
* to find CallExpressions (for call sites + dynamic imports + require).
|
|
246
|
+
* Avoids the full babel-traverse dependency.
|
|
247
|
+
*/
|
|
248
|
+
function walk(node, visit) {
|
|
249
|
+
if (!node || typeof node !== 'object')
|
|
250
|
+
return;
|
|
251
|
+
visit(node);
|
|
252
|
+
for (const key of Object.keys(node)) {
|
|
253
|
+
if (key === 'loc' || key === 'range' || key === 'leadingComments' || key === 'trailingComments')
|
|
254
|
+
continue;
|
|
255
|
+
const child = node[key];
|
|
256
|
+
if (!child)
|
|
257
|
+
continue;
|
|
258
|
+
if (Array.isArray(child)) {
|
|
259
|
+
for (const item of child) {
|
|
260
|
+
if (item && typeof item === 'object' && 'type' in item) {
|
|
261
|
+
walk(item, visit);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
else if (typeof child === 'object' && 'type' in child) {
|
|
266
|
+
walk(child, visit);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
//# sourceMappingURL=ast.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ast.js","sourceRoot":"","sources":["../../src/core/ast.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { FileEntry } from '../types.js';
|
|
2
|
+
import { type AstImport, type AstExport } from './ast.js';
|
|
3
|
+
export interface GraphFile {
|
|
4
|
+
relativePath: string;
|
|
5
|
+
imports: AstImport[];
|
|
6
|
+
exports: AstExport[];
|
|
7
|
+
callSites: string[];
|
|
8
|
+
lineCount: number;
|
|
9
|
+
mtimeMs: number;
|
|
10
|
+
parseOk: boolean;
|
|
11
|
+
parseReason?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface CodeGraph {
|
|
14
|
+
/** per-file parse results, keyed by relativePath */
|
|
15
|
+
files: Map<string, GraphFile>;
|
|
16
|
+
/** package name → relativePaths that import it */
|
|
17
|
+
packageImporters: Map<string, Set<string>>;
|
|
18
|
+
/** relativePath → relativePaths that import it (local resolution) */
|
|
19
|
+
localImporters: Map<string, Set<string>>;
|
|
20
|
+
/** symbol name → relativePaths that export it */
|
|
21
|
+
symbolDefs: Map<string, Set<string>>;
|
|
22
|
+
/** scanned file count */
|
|
23
|
+
scannedFiles: number;
|
|
24
|
+
}
|
|
25
|
+
export declare function buildCodeGraph(rootPath: string, files: FileEntry[], previousGraph?: CodeGraph): Promise<CodeGraph>;
|
|
26
|
+
/**
|
|
27
|
+
* Convert an import specifier to a bare package name.
|
|
28
|
+
*/
|
|
29
|
+
export declare function toPackageName(specifier: string): string | null;
|
|
30
|
+
export declare function packagesUsed(graph: CodeGraph): Set<string>;
|
|
31
|
+
export declare function filesImportingPackage(graph: CodeGraph, pkg: string): string[];
|
|
32
|
+
export declare function filesImportingFile(graph: CodeGraph, relativePath: string): string[];
|
|
33
|
+
export declare function filesDefiningSymbol(graph: CodeGraph, name: string): string[];
|
|
34
|
+
export declare function importersOf(graph: CodeGraph, relativePath: string): string[];
|
|
35
|
+
export declare function exportsOf(graph: CodeGraph, relativePath: string): AstExport[];
|
|
36
|
+
export declare function importsOf(graph: CodeGraph, relativePath: string): AstImport[];
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { parseSource, isParseable } from './ast.js';
|
|
4
|
+
const NODE_BUILTINS = new Set([
|
|
5
|
+
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console', 'constants', 'crypto',
|
|
6
|
+
'dgram', 'dns', 'domain', 'events', 'fs', 'fs/promises', 'http', 'http2', 'https', 'inspector',
|
|
7
|
+
'module', 'net', 'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring', 'readline',
|
|
8
|
+
'repl', 'stream', 'string_decoder', 'sys', 'timers', 'tls', 'trace_events', 'tty', 'url', 'util',
|
|
9
|
+
'v8', 'vm', 'wasi', 'worker_threads', 'zlib',
|
|
10
|
+
]);
|
|
11
|
+
const RESOLUTION_EXTS = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
|
|
12
|
+
const MAX_FILE_SIZE = 1024 * 1024;
|
|
13
|
+
export async function buildCodeGraph(rootPath, files, previousGraph) {
|
|
14
|
+
const parseable = files.filter((f) => isParseable(f.relativePath) && f.sizeBytes <= MAX_FILE_SIZE);
|
|
15
|
+
const graphFiles = new Map();
|
|
16
|
+
const packageImporters = new Map();
|
|
17
|
+
const localImporters = new Map();
|
|
18
|
+
const symbolDefs = new Map();
|
|
19
|
+
// Parse each file (with mtime-based reuse if we have a previous graph)
|
|
20
|
+
await Promise.all(parseable.map(async (file) => {
|
|
21
|
+
const absolutePath = path.isAbsolute(file.absolutePath)
|
|
22
|
+
? file.absolutePath
|
|
23
|
+
: path.resolve(rootPath, file.relativePath);
|
|
24
|
+
let mtimeMs;
|
|
25
|
+
try {
|
|
26
|
+
const stat = await fs.stat(absolutePath);
|
|
27
|
+
mtimeMs = stat.mtimeMs;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const cached = previousGraph?.files.get(file.relativePath);
|
|
33
|
+
if (cached && cached.mtimeMs === mtimeMs) {
|
|
34
|
+
graphFiles.set(file.relativePath, cached);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
let content;
|
|
38
|
+
try {
|
|
39
|
+
content = await fs.readFile(absolutePath, 'utf-8');
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const result = parseSource(file.relativePath, content);
|
|
45
|
+
graphFiles.set(file.relativePath, {
|
|
46
|
+
relativePath: file.relativePath,
|
|
47
|
+
imports: result.imports,
|
|
48
|
+
exports: result.exports,
|
|
49
|
+
callSites: result.callSites,
|
|
50
|
+
lineCount: result.lineCount,
|
|
51
|
+
mtimeMs,
|
|
52
|
+
parseOk: result.ok,
|
|
53
|
+
parseReason: result.reason,
|
|
54
|
+
});
|
|
55
|
+
}));
|
|
56
|
+
// Build derived indexes after all parsing is done
|
|
57
|
+
for (const [importingFile, entry] of graphFiles) {
|
|
58
|
+
const importingDir = path.posix.dirname(importingFile);
|
|
59
|
+
for (const imp of entry.imports) {
|
|
60
|
+
const pkg = toPackageName(imp.source);
|
|
61
|
+
if (pkg) {
|
|
62
|
+
if (!packageImporters.has(pkg))
|
|
63
|
+
packageImporters.set(pkg, new Set());
|
|
64
|
+
packageImporters.get(pkg).add(importingFile);
|
|
65
|
+
}
|
|
66
|
+
else if (imp.source.startsWith('.') || imp.source.startsWith('/')) {
|
|
67
|
+
const resolved = resolveRelative(importingDir, imp.source, graphFiles);
|
|
68
|
+
if (resolved) {
|
|
69
|
+
if (!localImporters.has(resolved))
|
|
70
|
+
localImporters.set(resolved, new Set());
|
|
71
|
+
localImporters.get(resolved).add(importingFile);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
for (const exp of entry.exports) {
|
|
76
|
+
if (!exp.name)
|
|
77
|
+
continue;
|
|
78
|
+
if (!symbolDefs.has(exp.name))
|
|
79
|
+
symbolDefs.set(exp.name, new Set());
|
|
80
|
+
symbolDefs.get(exp.name).add(importingFile);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
files: graphFiles,
|
|
85
|
+
packageImporters,
|
|
86
|
+
localImporters,
|
|
87
|
+
symbolDefs,
|
|
88
|
+
scannedFiles: graphFiles.size,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Convert an import specifier to a bare package name.
|
|
93
|
+
*/
|
|
94
|
+
export function toPackageName(specifier) {
|
|
95
|
+
if (!specifier)
|
|
96
|
+
return null;
|
|
97
|
+
if (specifier.startsWith('.') || specifier.startsWith('/'))
|
|
98
|
+
return null;
|
|
99
|
+
if (specifier.startsWith('node:'))
|
|
100
|
+
return null;
|
|
101
|
+
if (NODE_BUILTINS.has(specifier))
|
|
102
|
+
return null;
|
|
103
|
+
if (specifier.startsWith('@')) {
|
|
104
|
+
const segments = specifier.split('/');
|
|
105
|
+
if (segments.length < 2)
|
|
106
|
+
return null;
|
|
107
|
+
return `${segments[0]}/${segments[1]}`;
|
|
108
|
+
}
|
|
109
|
+
return specifier.split('/')[0];
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Resolve a relative import to a file in the graph, or null if no match.
|
|
113
|
+
* Supports:
|
|
114
|
+
* - direct hit (./foo.ts → foo.ts)
|
|
115
|
+
* - extension inference (./foo → foo.ts)
|
|
116
|
+
* - barrel index (./foo → foo/index.ts)
|
|
117
|
+
* - .js that resolves to .ts under NodeNext
|
|
118
|
+
*/
|
|
119
|
+
function resolveRelative(importingDir, specifier, graphFiles) {
|
|
120
|
+
const base = path.posix.normalize(path.posix.join(importingDir, specifier));
|
|
121
|
+
if (graphFiles.has(base))
|
|
122
|
+
return base;
|
|
123
|
+
for (const ext of RESOLUTION_EXTS) {
|
|
124
|
+
if (graphFiles.has(base + ext))
|
|
125
|
+
return base + ext;
|
|
126
|
+
}
|
|
127
|
+
for (const ext of RESOLUTION_EXTS) {
|
|
128
|
+
const barrel = `${base}/index${ext}`;
|
|
129
|
+
if (graphFiles.has(barrel))
|
|
130
|
+
return barrel;
|
|
131
|
+
}
|
|
132
|
+
// .js → .ts fallback (NodeNext)
|
|
133
|
+
if (base.endsWith('.js')) {
|
|
134
|
+
const trimmed = base.slice(0, -3);
|
|
135
|
+
if (graphFiles.has(`${trimmed}.ts`))
|
|
136
|
+
return `${trimmed}.ts`;
|
|
137
|
+
if (graphFiles.has(`${trimmed}.tsx`))
|
|
138
|
+
return `${trimmed}.tsx`;
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
// ── Query API ──────────────────────────────────────────────
|
|
143
|
+
export function packagesUsed(graph) {
|
|
144
|
+
return new Set(graph.packageImporters.keys());
|
|
145
|
+
}
|
|
146
|
+
export function filesImportingPackage(graph, pkg) {
|
|
147
|
+
const set = graph.packageImporters.get(pkg);
|
|
148
|
+
return set ? [...set].sort() : [];
|
|
149
|
+
}
|
|
150
|
+
export function filesImportingFile(graph, relativePath) {
|
|
151
|
+
const set = graph.localImporters.get(relativePath);
|
|
152
|
+
return set ? [...set].sort() : [];
|
|
153
|
+
}
|
|
154
|
+
export function filesDefiningSymbol(graph, name) {
|
|
155
|
+
const set = graph.symbolDefs.get(name);
|
|
156
|
+
return set ? [...set].sort() : [];
|
|
157
|
+
}
|
|
158
|
+
export function importersOf(graph, relativePath) {
|
|
159
|
+
return filesImportingFile(graph, relativePath);
|
|
160
|
+
}
|
|
161
|
+
export function exportsOf(graph, relativePath) {
|
|
162
|
+
return graph.files.get(relativePath)?.exports ?? [];
|
|
163
|
+
}
|
|
164
|
+
export function importsOf(graph, relativePath) {
|
|
165
|
+
return graph.files.get(relativePath)?.imports ?? [];
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=codeGraph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codeGraph.js","sourceRoot":"","sources":["../../src/core/codeGraph.ts"],"names":[],"mappings":"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"}
|