project-graph-mcp 1.0.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/AGENT_ROLE.md +126 -0
- package/AGENT_ROLE_MINIMAL.md +54 -0
- package/CONFIGURATION.md +188 -0
- package/LICENSE +21 -0
- package/README.md +279 -0
- package/package.json +46 -0
- package/references/symbiote-3x.md +834 -0
- package/rules/express-5.json +76 -0
- package/rules/fastify-5.json +75 -0
- package/rules/nestjs-10.json +88 -0
- package/rules/nextjs-15.json +87 -0
- package/rules/node-22.json +156 -0
- package/rules/react-18.json +87 -0
- package/rules/react-19.json +76 -0
- package/rules/symbiote-2x.json +158 -0
- package/rules/symbiote-3x.json +221 -0
- package/rules/typescript-5.json +69 -0
- package/rules/vue-3.json +79 -0
- package/src/cli-handlers.js +140 -0
- package/src/cli.js +83 -0
- package/src/complexity.js +223 -0
- package/src/custom-rules.js +583 -0
- package/src/dead-code.js +468 -0
- package/src/filters.js +226 -0
- package/src/framework-references.js +177 -0
- package/src/full-analysis.js +159 -0
- package/src/graph-builder.js +269 -0
- package/src/instructions.js +175 -0
- package/src/jsdoc-generator.js +214 -0
- package/src/large-files.js +162 -0
- package/src/mcp-server.js +375 -0
- package/src/outdated-patterns.js +295 -0
- package/src/parser.js +293 -0
- package/src/server.js +28 -0
- package/src/similar-functions.js +278 -0
- package/src/test-annotations.js +301 -0
- package/src/tool-defs.js +444 -0
- package/src/tools.js +240 -0
- package/src/undocumented.js +260 -0
- package/src/workspace.js +70 -0
- package/vendor/acorn.mjs +6145 -0
- package/vendor/walk.mjs +437 -0
package/src/tool-defs.js
ADDED
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tool Definitions
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export const TOOLS = [
|
|
6
|
+
// Graph Tools
|
|
7
|
+
{
|
|
8
|
+
name: 'get_skeleton',
|
|
9
|
+
description: 'Get compact minified project overview (10-50x smaller than source). Returns legend, stats, and node summaries.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
path: {
|
|
14
|
+
type: 'string',
|
|
15
|
+
description: 'Path to scan (e.g., "src/components")',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: ['path'],
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
name: 'get_focus_zone',
|
|
23
|
+
description: 'Get enriched context for recently modified files. Auto-detects from git or accepts explicit file list.',
|
|
24
|
+
inputSchema: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
path: { type: 'string' },
|
|
28
|
+
recentFiles: {
|
|
29
|
+
type: 'array',
|
|
30
|
+
items: { type: 'string' },
|
|
31
|
+
description: 'Explicit list of files to expand',
|
|
32
|
+
},
|
|
33
|
+
useGitDiff: {
|
|
34
|
+
type: 'boolean',
|
|
35
|
+
description: 'Auto-detect from git diff',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: 'expand',
|
|
42
|
+
description: 'Expand a minified symbol to full details. Use "SN" for class or "SN.tP" for specific method.',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
symbol: {
|
|
47
|
+
type: 'string',
|
|
48
|
+
description: 'Minified symbol (e.g., "SN" or "SN.tP")',
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
required: ['symbol'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'deps',
|
|
56
|
+
description: 'Get dependency tree for a symbol. Shows imports, usedBy, and calls.',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
symbol: { type: 'string' },
|
|
61
|
+
},
|
|
62
|
+
required: ['symbol'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'usages',
|
|
67
|
+
description: 'Find all usages of a symbol across the project.',
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
symbol: { type: 'string' },
|
|
72
|
+
},
|
|
73
|
+
required: ['symbol'],
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: 'invalidate_cache',
|
|
78
|
+
description: 'Invalidate the cached graph. Use after making code changes.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {},
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
// Test Checklist Tools
|
|
86
|
+
{
|
|
87
|
+
name: 'get_pending_tests',
|
|
88
|
+
description: 'Get list of pending browser tests from @test/@expect annotations.',
|
|
89
|
+
inputSchema: {
|
|
90
|
+
type: 'object',
|
|
91
|
+
properties: {
|
|
92
|
+
path: { type: 'string', description: 'Path to scan (e.g., "src/components")' },
|
|
93
|
+
},
|
|
94
|
+
required: ['path'],
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: 'mark_test_passed',
|
|
99
|
+
description: 'Mark a test step as passed. Use the test ID returned by get_pending_tests.',
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: 'object',
|
|
102
|
+
properties: {
|
|
103
|
+
testId: { type: 'string', description: 'Test ID (e.g., "togglePin.0")' },
|
|
104
|
+
},
|
|
105
|
+
required: ['testId'],
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'mark_test_failed',
|
|
110
|
+
description: 'Mark a test step as failed with a reason.',
|
|
111
|
+
inputSchema: {
|
|
112
|
+
type: 'object',
|
|
113
|
+
properties: {
|
|
114
|
+
testId: { type: 'string' },
|
|
115
|
+
reason: { type: 'string', description: 'Why the test failed' },
|
|
116
|
+
},
|
|
117
|
+
required: ['testId', 'reason'],
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: 'get_test_summary',
|
|
122
|
+
description: 'Get summary of test progress: passed, failed, pending counts.',
|
|
123
|
+
inputSchema: {
|
|
124
|
+
type: 'object',
|
|
125
|
+
properties: {
|
|
126
|
+
path: { type: 'string' },
|
|
127
|
+
},
|
|
128
|
+
required: ['path'],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'reset_test_state',
|
|
133
|
+
description: 'Reset all test progress to start fresh.',
|
|
134
|
+
inputSchema: {
|
|
135
|
+
type: 'object',
|
|
136
|
+
properties: {},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
|
|
140
|
+
// Filter Configuration Tools
|
|
141
|
+
{
|
|
142
|
+
name: 'get_filters',
|
|
143
|
+
description: 'Get current filter configuration (excluded dirs, patterns, gitignore status).',
|
|
144
|
+
inputSchema: {
|
|
145
|
+
type: 'object',
|
|
146
|
+
properties: {},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 'set_filters',
|
|
151
|
+
description: 'Update filter configuration. Pass only fields you want to change.',
|
|
152
|
+
inputSchema: {
|
|
153
|
+
type: 'object',
|
|
154
|
+
properties: {
|
|
155
|
+
excludeDirs: {
|
|
156
|
+
type: 'array',
|
|
157
|
+
items: { type: 'string' },
|
|
158
|
+
description: 'Directories to exclude (replaces current list)',
|
|
159
|
+
},
|
|
160
|
+
excludePatterns: {
|
|
161
|
+
type: 'array',
|
|
162
|
+
items: { type: 'string' },
|
|
163
|
+
description: 'File patterns to exclude (e.g., "*.test.js")',
|
|
164
|
+
},
|
|
165
|
+
includeHidden: {
|
|
166
|
+
type: 'boolean',
|
|
167
|
+
description: 'Include hidden directories (starting with .)',
|
|
168
|
+
},
|
|
169
|
+
useGitignore: {
|
|
170
|
+
type: 'boolean',
|
|
171
|
+
description: 'Parse and use .gitignore patterns',
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
name: 'add_excludes',
|
|
178
|
+
description: 'Add directories to exclude list without replacing.',
|
|
179
|
+
inputSchema: {
|
|
180
|
+
type: 'object',
|
|
181
|
+
properties: {
|
|
182
|
+
dirs: {
|
|
183
|
+
type: 'array',
|
|
184
|
+
items: { type: 'string' },
|
|
185
|
+
description: 'Directories to add to exclude list',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
required: ['dirs'],
|
|
189
|
+
},
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'remove_excludes',
|
|
193
|
+
description: 'Remove directories from exclude list.',
|
|
194
|
+
inputSchema: {
|
|
195
|
+
type: 'object',
|
|
196
|
+
properties: {
|
|
197
|
+
dirs: {
|
|
198
|
+
type: 'array',
|
|
199
|
+
items: { type: 'string' },
|
|
200
|
+
description: 'Directories to remove from exclude list',
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
required: ['dirs'],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: 'reset_filters',
|
|
208
|
+
description: 'Reset filters to default configuration.',
|
|
209
|
+
inputSchema: {
|
|
210
|
+
type: 'object',
|
|
211
|
+
properties: {},
|
|
212
|
+
},
|
|
213
|
+
},
|
|
214
|
+
|
|
215
|
+
// Guidelines
|
|
216
|
+
{
|
|
217
|
+
name: 'get_agent_instructions',
|
|
218
|
+
description: 'Get coding guidelines, architectural standards, and JSDoc rules for this project.',
|
|
219
|
+
inputSchema: {
|
|
220
|
+
type: 'object',
|
|
221
|
+
properties: {},
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
// Documentation Analysis
|
|
226
|
+
{
|
|
227
|
+
name: 'get_undocumented',
|
|
228
|
+
description: 'Find classes/functions missing JSDoc annotations. Use for documentation generation.',
|
|
229
|
+
inputSchema: {
|
|
230
|
+
type: 'object',
|
|
231
|
+
properties: {
|
|
232
|
+
path: {
|
|
233
|
+
type: 'string',
|
|
234
|
+
description: 'Path to scan (e.g., "src/components")',
|
|
235
|
+
},
|
|
236
|
+
level: {
|
|
237
|
+
type: 'string',
|
|
238
|
+
enum: ['tests', 'params', 'all'],
|
|
239
|
+
description: 'Strictness: tests (default) = @test/@expect, params = +@param/@returns, all = +description',
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
required: ['path'],
|
|
243
|
+
},
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
// Code Quality
|
|
247
|
+
{
|
|
248
|
+
name: 'get_dead_code',
|
|
249
|
+
description: 'Find unused functions, classes, exports, variables, and imports (dead code). Use for cleanup.',
|
|
250
|
+
inputSchema: {
|
|
251
|
+
type: 'object',
|
|
252
|
+
properties: {
|
|
253
|
+
path: {
|
|
254
|
+
type: 'string',
|
|
255
|
+
description: 'Path to scan (e.g., "src/")',
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
required: ['path'],
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: 'generate_jsdoc',
|
|
263
|
+
description: 'Generate JSDoc template for a file or specific function. Returns ready-to-use JSDoc blocks.',
|
|
264
|
+
inputSchema: {
|
|
265
|
+
type: 'object',
|
|
266
|
+
properties: {
|
|
267
|
+
path: {
|
|
268
|
+
type: 'string',
|
|
269
|
+
description: 'Path to JS file',
|
|
270
|
+
},
|
|
271
|
+
name: {
|
|
272
|
+
type: 'string',
|
|
273
|
+
description: 'Optional: specific function/method name',
|
|
274
|
+
},
|
|
275
|
+
},
|
|
276
|
+
required: ['path'],
|
|
277
|
+
},
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
name: 'get_similar_functions',
|
|
281
|
+
description: 'Find functionally similar functions (potential duplicates). Returns pairs with similarity score.',
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: 'object',
|
|
284
|
+
properties: {
|
|
285
|
+
path: {
|
|
286
|
+
type: 'string',
|
|
287
|
+
description: 'Path to scan (e.g., "src/")',
|
|
288
|
+
},
|
|
289
|
+
threshold: {
|
|
290
|
+
type: 'number',
|
|
291
|
+
description: 'Minimum similarity percentage (default: 60)',
|
|
292
|
+
},
|
|
293
|
+
},
|
|
294
|
+
required: ['path'],
|
|
295
|
+
},
|
|
296
|
+
},
|
|
297
|
+
{
|
|
298
|
+
name: 'get_complexity',
|
|
299
|
+
description: 'Analyze cyclomatic complexity of functions. Identifies high-complexity code needing refactoring.',
|
|
300
|
+
inputSchema: {
|
|
301
|
+
type: 'object',
|
|
302
|
+
properties: {
|
|
303
|
+
path: {
|
|
304
|
+
type: 'string',
|
|
305
|
+
description: 'Path to scan (e.g., "src/")',
|
|
306
|
+
},
|
|
307
|
+
minComplexity: {
|
|
308
|
+
type: 'number',
|
|
309
|
+
description: 'Minimum complexity to include (default: 1)',
|
|
310
|
+
},
|
|
311
|
+
onlyProblematic: {
|
|
312
|
+
type: 'boolean',
|
|
313
|
+
description: 'Only show high/critical items',
|
|
314
|
+
},
|
|
315
|
+
},
|
|
316
|
+
required: ['path'],
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
name: 'get_large_files',
|
|
321
|
+
description: 'Find files that may need splitting. Analyzes lines, functions, classes, and exports.',
|
|
322
|
+
inputSchema: {
|
|
323
|
+
type: 'object',
|
|
324
|
+
properties: {
|
|
325
|
+
path: {
|
|
326
|
+
type: 'string',
|
|
327
|
+
description: 'Path to scan (e.g., "src/")',
|
|
328
|
+
},
|
|
329
|
+
onlyProblematic: {
|
|
330
|
+
type: 'boolean',
|
|
331
|
+
description: 'Only show warning/critical files',
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
required: ['path'],
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
name: 'get_outdated_patterns',
|
|
339
|
+
description: 'Find legacy code patterns and redundant npm dependencies (now built into Node.js 18+).',
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: 'object',
|
|
342
|
+
properties: {
|
|
343
|
+
path: {
|
|
344
|
+
type: 'string',
|
|
345
|
+
description: 'Path to scan (e.g., "src/" or ".")',
|
|
346
|
+
},
|
|
347
|
+
codeOnly: {
|
|
348
|
+
type: 'boolean',
|
|
349
|
+
description: 'Only check code patterns',
|
|
350
|
+
},
|
|
351
|
+
depsOnly: {
|
|
352
|
+
type: 'boolean',
|
|
353
|
+
description: 'Only check package.json dependencies',
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
required: ['path'],
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
name: 'get_full_analysis',
|
|
361
|
+
description: 'Run ALL code quality checks at once. Returns combined report with Health Score (0-100).',
|
|
362
|
+
inputSchema: {
|
|
363
|
+
type: 'object',
|
|
364
|
+
properties: {
|
|
365
|
+
path: {
|
|
366
|
+
type: 'string',
|
|
367
|
+
description: 'Path to scan (e.g., "src/" or ".")',
|
|
368
|
+
},
|
|
369
|
+
includeItems: {
|
|
370
|
+
type: 'boolean',
|
|
371
|
+
description: 'Include individual items in report',
|
|
372
|
+
},
|
|
373
|
+
},
|
|
374
|
+
required: ['path'],
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
name: 'get_custom_rules',
|
|
379
|
+
description: 'List all custom code analysis rules. Rules are stored in JSON files in rules/ directory.',
|
|
380
|
+
inputSchema: {
|
|
381
|
+
type: 'object',
|
|
382
|
+
properties: {},
|
|
383
|
+
},
|
|
384
|
+
},
|
|
385
|
+
{
|
|
386
|
+
name: 'set_custom_rule',
|
|
387
|
+
description: 'Add or update a custom code analysis rule. Creates ruleset if it does not exist.',
|
|
388
|
+
inputSchema: {
|
|
389
|
+
type: 'object',
|
|
390
|
+
properties: {
|
|
391
|
+
ruleSet: {
|
|
392
|
+
type: 'string',
|
|
393
|
+
description: 'Name of ruleset (e.g., "symbiote", "react", "custom")',
|
|
394
|
+
},
|
|
395
|
+
rule: {
|
|
396
|
+
type: 'object',
|
|
397
|
+
description: 'Rule definition with id, name, description, pattern, patternType, replacement, severity, filePattern',
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
required: ['ruleSet', 'rule'],
|
|
401
|
+
},
|
|
402
|
+
},
|
|
403
|
+
{
|
|
404
|
+
name: 'check_custom_rules',
|
|
405
|
+
description: 'Run custom rules analysis on a directory. Returns violations found.',
|
|
406
|
+
inputSchema: {
|
|
407
|
+
type: 'object',
|
|
408
|
+
properties: {
|
|
409
|
+
path: {
|
|
410
|
+
type: 'string',
|
|
411
|
+
description: 'Path to scan',
|
|
412
|
+
},
|
|
413
|
+
ruleSet: {
|
|
414
|
+
type: 'string',
|
|
415
|
+
description: 'Optional: specific ruleset to use',
|
|
416
|
+
},
|
|
417
|
+
severity: {
|
|
418
|
+
type: 'string',
|
|
419
|
+
description: 'Optional: filter by severity (error/warning/info)',
|
|
420
|
+
},
|
|
421
|
+
},
|
|
422
|
+
required: ['path'],
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Framework References
|
|
427
|
+
{
|
|
428
|
+
name: 'get_framework_reference',
|
|
429
|
+
description: 'Get framework-specific AI reference documentation. Auto-detects framework from project or accepts explicit name. Returns full API reference, patterns, and common mistakes as agent context.',
|
|
430
|
+
inputSchema: {
|
|
431
|
+
type: 'object',
|
|
432
|
+
properties: {
|
|
433
|
+
framework: {
|
|
434
|
+
type: 'string',
|
|
435
|
+
description: 'Framework reference name (e.g., "symbiote-3x"). If omitted, auto-detects from path.',
|
|
436
|
+
},
|
|
437
|
+
path: {
|
|
438
|
+
type: 'string',
|
|
439
|
+
description: 'Project path for auto-detection (e.g., "src/")',
|
|
440
|
+
},
|
|
441
|
+
},
|
|
442
|
+
},
|
|
443
|
+
},
|
|
444
|
+
];
|
package/src/tools.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Tools for Project Graph
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { parseProject, parseFile } from './parser.js';
|
|
6
|
+
import { buildGraph, createSkeleton } from './graph-builder.js';
|
|
7
|
+
import { readFileSync } from 'fs';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
/** @type {import('./graph-builder.js').Graph|null} */
|
|
11
|
+
let cachedGraph = null;
|
|
12
|
+
|
|
13
|
+
/** @type {string|null} */
|
|
14
|
+
let cachedPath = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get or build graph with caching
|
|
18
|
+
* @param {string} path
|
|
19
|
+
* @returns {Promise<import('./graph-builder.js').Graph>}
|
|
20
|
+
*/
|
|
21
|
+
async function getGraph(path) {
|
|
22
|
+
if (cachedGraph && cachedPath === path) {
|
|
23
|
+
return cachedGraph;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const parsed = await parseProject(path);
|
|
27
|
+
cachedGraph = buildGraph(parsed);
|
|
28
|
+
cachedPath = path;
|
|
29
|
+
|
|
30
|
+
return cachedGraph;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Get compact project skeleton
|
|
35
|
+
* @param {string} path
|
|
36
|
+
* @returns {Promise<Object>}
|
|
37
|
+
*/
|
|
38
|
+
export async function getSkeleton(path) {
|
|
39
|
+
const graph = await getGraph(path);
|
|
40
|
+
return createSkeleton(graph);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Get enriched focus zone based on recent activity
|
|
45
|
+
* @param {Object} options
|
|
46
|
+
* @param {string[]} [options.recentFiles]
|
|
47
|
+
* @param {boolean} [options.useGitDiff]
|
|
48
|
+
* @returns {Promise<Object>}
|
|
49
|
+
*/
|
|
50
|
+
export async function getFocusZone(options = {}) {
|
|
51
|
+
const path = options.path || 'src/components';
|
|
52
|
+
const graph = await getGraph(path);
|
|
53
|
+
|
|
54
|
+
let focusFiles = options.recentFiles || [];
|
|
55
|
+
|
|
56
|
+
// Auto-detect from git diff
|
|
57
|
+
if (options.useGitDiff) {
|
|
58
|
+
try {
|
|
59
|
+
const diff = execSync('git diff --name-only HEAD~5', { encoding: 'utf-8' });
|
|
60
|
+
focusFiles = diff.split('\n').filter(f => f.endsWith('.js'));
|
|
61
|
+
} catch (e) {
|
|
62
|
+
// Git not available or not a repo
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const expanded = {};
|
|
67
|
+
|
|
68
|
+
for (const file of focusFiles) {
|
|
69
|
+
// Find classes in this file
|
|
70
|
+
const content = readFileSync(file, 'utf-8');
|
|
71
|
+
const parsed = await parseFile(content, file);
|
|
72
|
+
|
|
73
|
+
for (const cls of parsed.classes) {
|
|
74
|
+
const shortName = graph.legend[cls.name];
|
|
75
|
+
if (shortName && graph.nodes[shortName]) {
|
|
76
|
+
expanded[shortName] = {
|
|
77
|
+
...graph.nodes[shortName],
|
|
78
|
+
methods: cls.methods,
|
|
79
|
+
properties: cls.properties,
|
|
80
|
+
file: cls.file,
|
|
81
|
+
line: cls.line,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
focusFiles,
|
|
89
|
+
expanded,
|
|
90
|
+
expandable: Object.keys(graph.nodes).filter(k => !expanded[k]),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Expand a symbol to full details
|
|
96
|
+
* @param {string} symbol - Minified symbol like 'SN' or 'SN.tP'
|
|
97
|
+
* @returns {Promise<Object>}
|
|
98
|
+
*/
|
|
99
|
+
export async function expand(symbol) {
|
|
100
|
+
const path = cachedPath || 'src/components';
|
|
101
|
+
const graph = await getGraph(path);
|
|
102
|
+
|
|
103
|
+
const [nodeKey, methodKey] = symbol.split('.');
|
|
104
|
+
const fullName = graph.reverseLegend[nodeKey];
|
|
105
|
+
|
|
106
|
+
if (!fullName) {
|
|
107
|
+
return { error: `Unknown symbol: ${symbol}. Run get_skeleton on your project first, then use symbols from the L (Legend) field.` };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Find the source file
|
|
111
|
+
const parsed = await parseProject(path);
|
|
112
|
+
const cls = parsed.classes.find(c => c.name === fullName);
|
|
113
|
+
|
|
114
|
+
if (!cls) {
|
|
115
|
+
return { error: `Class not found: ${fullName}` };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// If method specified, extract method code
|
|
119
|
+
if (methodKey) {
|
|
120
|
+
const methodName = graph.reverseLegend[methodKey] || methodKey;
|
|
121
|
+
const content = readFileSync(cls.file, 'utf-8');
|
|
122
|
+
const methodCode = extractMethod(content, methodName);
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
symbol,
|
|
126
|
+
fullName: `${fullName}.${methodName}`,
|
|
127
|
+
file: cls.file,
|
|
128
|
+
line: cls.line, // TODO: get actual method line
|
|
129
|
+
code: methodCode,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Return full class info
|
|
134
|
+
return {
|
|
135
|
+
symbol,
|
|
136
|
+
fullName,
|
|
137
|
+
file: cls.file,
|
|
138
|
+
line: cls.line,
|
|
139
|
+
extends: cls.extends,
|
|
140
|
+
methods: cls.methods,
|
|
141
|
+
properties: cls.properties,
|
|
142
|
+
calls: cls.calls,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Get dependency tree for a symbol
|
|
148
|
+
* @param {string} symbol
|
|
149
|
+
* @returns {Promise<Object>}
|
|
150
|
+
*/
|
|
151
|
+
export async function deps(symbol) {
|
|
152
|
+
const path = cachedPath || 'src/components';
|
|
153
|
+
const graph = await getGraph(path);
|
|
154
|
+
|
|
155
|
+
const node = graph.nodes[symbol];
|
|
156
|
+
if (!node) {
|
|
157
|
+
return { error: `Unknown symbol: ${symbol}. Run get_skeleton on your project first, then use symbols from the L (Legend) field.` };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Find incoming edges (usedBy)
|
|
161
|
+
const usedBy = graph.edges
|
|
162
|
+
.filter(e => e[2].startsWith(symbol))
|
|
163
|
+
.map(e => e[0]);
|
|
164
|
+
|
|
165
|
+
// Find outgoing edges (calls)
|
|
166
|
+
const calls = graph.edges
|
|
167
|
+
.filter(e => e[0] === symbol)
|
|
168
|
+
.map(e => e[2]);
|
|
169
|
+
|
|
170
|
+
return {
|
|
171
|
+
symbol,
|
|
172
|
+
imports: node.i || [],
|
|
173
|
+
usedBy: [...new Set(usedBy)],
|
|
174
|
+
calls: [...new Set(calls)],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Find all usages of a symbol
|
|
180
|
+
* @param {string} symbol
|
|
181
|
+
* @returns {Promise<Array>}
|
|
182
|
+
*/
|
|
183
|
+
export async function usages(symbol) {
|
|
184
|
+
const path = cachedPath || 'src/components';
|
|
185
|
+
const graph = await getGraph(path);
|
|
186
|
+
const parsed = await parseProject(path);
|
|
187
|
+
|
|
188
|
+
const fullName = graph.reverseLegend[symbol] || symbol;
|
|
189
|
+
const results = [];
|
|
190
|
+
|
|
191
|
+
for (const cls of parsed.classes) {
|
|
192
|
+
if (cls.calls?.includes(fullName) || cls.calls?.some(c => c.includes(fullName))) {
|
|
193
|
+
results.push({
|
|
194
|
+
file: cls.file,
|
|
195
|
+
line: cls.line,
|
|
196
|
+
context: `${cls.name} calls ${fullName}`,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return results;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Extract method code from file content
|
|
206
|
+
* @param {string} content
|
|
207
|
+
* @param {string} methodName
|
|
208
|
+
* @returns {string}
|
|
209
|
+
*/
|
|
210
|
+
function extractMethod(content, methodName) {
|
|
211
|
+
const regex = new RegExp(`((?:\\/\\*\\*[\\s\\S]*?\\*\\/\\s*)?)(?:async\\s+)?${methodName}\\s*\\([^)]*\\)\\s*{`, 'g');
|
|
212
|
+
const match = regex.exec(content);
|
|
213
|
+
|
|
214
|
+
if (!match) return '';
|
|
215
|
+
|
|
216
|
+
const start = match.index;
|
|
217
|
+
let depth = 0;
|
|
218
|
+
let i = match.index + match[0].length - 1;
|
|
219
|
+
|
|
220
|
+
while (i < content.length) {
|
|
221
|
+
if (content[i] === '{') depth++;
|
|
222
|
+
else if (content[i] === '}') {
|
|
223
|
+
depth--;
|
|
224
|
+
if (depth === 0) {
|
|
225
|
+
return content.slice(start, i + 1);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
i++;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return content.slice(start);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Invalidate cache
|
|
236
|
+
*/
|
|
237
|
+
export function invalidateCache() {
|
|
238
|
+
cachedGraph = null;
|
|
239
|
+
cachedPath = null;
|
|
240
|
+
}
|