project-graph-mcp 1.5.0 → 2.1.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 +171 -31
- package/docs/img/explorer-compact.jpg +0 -0
- package/docs/img/explorer-expanded.jpg +0 -0
- package/package.json +12 -8
- package/src/.project-graph-cache.json +1 -1
- package/src/analysis/analysis-cache.js +7 -0
- package/src/analysis/complexity.js +14 -0
- package/src/analysis/custom-rules.js +36 -0
- package/src/analysis/db-analysis.js +9 -0
- package/src/analysis/dead-code.js +19 -0
- package/src/analysis/full-analysis.js +18 -0
- package/src/analysis/jsdoc-checker.js +24 -0
- package/src/analysis/jsdoc-generator.js +10 -0
- package/src/analysis/large-files.js +11 -0
- package/src/analysis/outdated-patterns.js +12 -0
- package/src/analysis/similar-functions.js +16 -0
- package/src/analysis/test-annotations.js +21 -0
- package/src/analysis/type-checker.js +8 -0
- package/src/analysis/undocumented.js +14 -0
- package/src/cli/cli-handlers.js +4 -0
- package/src/cli/cli.js +5 -0
- package/src/compact/.project-graph-cache.json +1 -0
- package/src/compact/ai-context.js +7 -0
- package/src/compact/compact-migrate.js +17 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +14 -0
- package/src/compact/ctx-to-jsdoc.js +29 -0
- package/src/compact/doc-dialect.js +30 -0
- package/src/compact/expand.js +37 -0
- package/src/compact/framework-references.js +5 -0
- package/src/compact/instructions.js +3 -0
- package/src/compact/mode-config.js +8 -0
- package/src/compact/validate-pipeline.js +9 -0
- package/src/core/event-bus.js +9 -0
- package/src/core/filters.js +14 -0
- package/src/core/graph-builder.js +12 -0
- package/src/core/parser.js +31 -0
- package/src/core/workspace.js +8 -0
- package/src/lang/lang-go.js +17 -0
- package/src/lang/lang-python.js +12 -0
- package/src/lang/lang-sql.js +23 -0
- package/src/lang/lang-typescript.js +9 -0
- package/src/lang/lang-utils.js +4 -0
- package/src/mcp/mcp-server.js +17 -0
- package/src/mcp/tool-defs.js +3 -0
- package/src/mcp/tools.js +25 -0
- package/src/network/backend-lifecycle.js +19 -0
- package/src/network/backend.js +5 -0
- package/src/network/local-gateway.js +23 -0
- package/src/network/mdns.js +13 -0
- package/src/network/server.js +10 -0
- package/src/network/web-server.js +34 -0
- package/web/.project-graph-cache.json +1 -0
- package/web/app.js +17 -0
- package/web/components/code-block.js +3 -0
- package/web/components/quick-open.js +5 -0
- package/web/dashboard-state.js +3 -0
- package/web/dashboard.html +27 -0
- package/web/dashboard.js +8 -0
- package/web/highlight.js +13 -0
- package/web/index.html +35 -0
- package/web/panels/ActionBoard/ActionBoard.css.js +1 -0
- package/web/panels/ActionBoard/ActionBoard.js +4 -0
- package/web/panels/ActionBoard/ActionBoard.tpl.js +1 -0
- package/web/panels/EventItem/EventItem.css.js +1 -0
- package/web/panels/EventItem/EventItem.js +4 -0
- package/web/panels/EventItem/EventItem.tpl.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.css.js +1 -0
- package/web/panels/ProjectItem/ProjectItem.js +5 -0
- package/web/panels/ProjectItem/ProjectItem.tpl.js +1 -0
- package/web/panels/ProjectList/ProjectList.css.js +1 -0
- package/web/panels/ProjectList/ProjectList.js +4 -0
- package/web/panels/ProjectList/ProjectList.tpl.js +1 -0
- package/web/panels/SettingsPanel/.project-graph-cache.json +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.css.js +1 -0
- package/web/panels/SettingsPanel/SettingsPanel.js +7 -0
- package/web/panels/SettingsPanel/SettingsPanel.tpl.js +1 -0
- package/web/panels/code-viewer.js +5 -0
- package/web/panels/ctx-panel.js +4 -0
- package/web/panels/dep-graph.js +6 -0
- package/web/panels/file-tree.js +188 -0
- package/web/panels/health-panel.js +3 -0
- package/web/panels/live-monitor.js +3 -0
- package/web/state.js +17 -0
- package/web/style.css +157 -0
- package/references/symbiote-3x.md +0 -834
- package/src/ai-context.js +0 -113
- package/src/analysis-cache.js +0 -155
- package/src/cli-handlers.js +0 -271
- package/src/cli.js +0 -95
- package/src/compact.js +0 -207
- package/src/complexity.js +0 -237
- package/src/compress.js +0 -319
- package/src/ctx-to-jsdoc.js +0 -514
- package/src/custom-rules.js +0 -584
- package/src/db-analysis.js +0 -194
- package/src/dead-code.js +0 -468
- package/src/doc-dialect.js +0 -716
- package/src/filters.js +0 -227
- package/src/framework-references.js +0 -177
- package/src/full-analysis.js +0 -470
- package/src/graph-builder.js +0 -299
- package/src/instructions.js +0 -73
- package/src/jsdoc-checker.js +0 -351
- package/src/jsdoc-generator.js +0 -203
- package/src/lang-go.js +0 -285
- package/src/lang-python.js +0 -197
- package/src/lang-sql.js +0 -309
- package/src/lang-typescript.js +0 -190
- package/src/lang-utils.js +0 -124
- package/src/large-files.js +0 -163
- package/src/mcp-server.js +0 -675
- package/src/mode-config.js +0 -127
- package/src/outdated-patterns.js +0 -296
- package/src/parser.js +0 -662
- package/src/server.js +0 -28
- package/src/similar-functions.js +0 -279
- package/src/test-annotations.js +0 -323
- package/src/tool-defs.js +0 -793
- package/src/tools.js +0 -470
- package/src/type-checker.js +0 -188
- package/src/undocumented.js +0 -259
- package/src/workspace.js +0 -70
- /package/{AGENT_ROLE.md → docs/examples/AGENT_ROLE.md} +0 -0
- /package/{AGENT_ROLE_MINIMAL.md → docs/examples/AGENT_ROLE_MINIMAL.md} +0 -0
package/src/undocumented.js
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Undocumented Code Finder (AST-based)
|
|
3
|
-
* Finds methods/functions missing JSDoc annotations using Acorn AST parser
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { readFileSync, readdirSync, statSync } from 'fs';
|
|
7
|
-
import { join, relative, resolve } from 'path';
|
|
8
|
-
import { parse } from '../vendor/acorn.mjs';
|
|
9
|
-
import * as walk from '../vendor/walk.mjs';
|
|
10
|
-
import { shouldExcludeDir, shouldExcludeFile, parseGitignore } from './filters.js';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* @typedef {Object} UndocumentedItem
|
|
14
|
-
* @property {string} name - ClassName.methodName or functionName
|
|
15
|
-
* @property {string} type - 'method' | 'function' | 'class'
|
|
16
|
-
* @property {string} file
|
|
17
|
-
* @property {number} line
|
|
18
|
-
* @property {string} reason - What's missing
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Find all JS files in directory
|
|
23
|
-
* @param {string} dir
|
|
24
|
-
* @param {string} rootDir
|
|
25
|
-
* @returns {string[]}
|
|
26
|
-
*/
|
|
27
|
-
function findJSFiles(dir, rootDir = dir) {
|
|
28
|
-
if (dir === rootDir) {
|
|
29
|
-
parseGitignore(rootDir);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
const files = [];
|
|
33
|
-
|
|
34
|
-
try {
|
|
35
|
-
for (const entry of readdirSync(dir)) {
|
|
36
|
-
const fullPath = join(dir, entry);
|
|
37
|
-
const relativePath = relative(rootDir, fullPath);
|
|
38
|
-
const stat = statSync(fullPath);
|
|
39
|
-
|
|
40
|
-
if (stat.isDirectory()) {
|
|
41
|
-
if (!shouldExcludeDir(entry, relativePath)) {
|
|
42
|
-
files.push(...findJSFiles(fullPath, rootDir));
|
|
43
|
-
}
|
|
44
|
-
} else if (entry.endsWith('.js') && !entry.endsWith('.css.js') && !entry.endsWith('.tpl.js')) {
|
|
45
|
-
if (!shouldExcludeFile(entry, relativePath)) {
|
|
46
|
-
files.push(fullPath);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
} catch (e) {
|
|
51
|
-
// Directory not found
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
return files;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Extract JSDoc comments from code with their positions
|
|
59
|
-
* @param {string} code
|
|
60
|
-
* @returns {Array<{text: string, endLine: number}>}
|
|
61
|
-
*/
|
|
62
|
-
function extractComments(code) {
|
|
63
|
-
const comments = [];
|
|
64
|
-
const regex = /\/\*\*[\s\S]*?\*\//g;
|
|
65
|
-
let match;
|
|
66
|
-
|
|
67
|
-
while ((match = regex.exec(code)) !== null) {
|
|
68
|
-
const endLine = code.slice(0, match.index + match[0].length).split('\n').length;
|
|
69
|
-
comments.push({ text: match[0], endLine });
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return comments;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Find JSDoc comment before a target line
|
|
77
|
-
* @param {Array<{text: string, endLine: number}>} comments - Extracted JSDoc comments
|
|
78
|
-
* @param {number} targetLine - Line number to search before
|
|
79
|
-
* @returns {string|null}
|
|
80
|
-
*/
|
|
81
|
-
function findJSDocBefore(comments, targetLine) {
|
|
82
|
-
for (const comment of comments) {
|
|
83
|
-
const gap = targetLine - comment.endLine;
|
|
84
|
-
if (gap >= 0 && gap <= 2) {
|
|
85
|
-
return comment.text;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return null;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Check what's missing from JSDoc based on level
|
|
93
|
-
* @param {string|null} jsdoc
|
|
94
|
-
* @param {'tests'|'params'|'all'} level
|
|
95
|
-
* @returns {string[]}
|
|
96
|
-
*/
|
|
97
|
-
function checkMissing(jsdoc, level) {
|
|
98
|
-
const missing = [];
|
|
99
|
-
|
|
100
|
-
if (!jsdoc) {
|
|
101
|
-
if (level === 'all') missing.push('description');
|
|
102
|
-
if (level === 'params' || level === 'all') missing.push('@param', '@returns');
|
|
103
|
-
return missing;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (level === 'params' || level === 'all') {
|
|
107
|
-
if (!jsdoc.includes('@param')) missing.push('@param');
|
|
108
|
-
if (!jsdoc.includes('@returns') && !jsdoc.includes('@return')) missing.push('@returns');
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return missing;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** Skip list for methods */
|
|
115
|
-
const SKIP_METHODS = [
|
|
116
|
-
'constructor', 'connectedCallback', 'disconnectedCallback',
|
|
117
|
-
'attributeChangedCallback', 'renderCallback',
|
|
118
|
-
];
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Parse file using AST and find undocumented items (per-file export for cache integration)
|
|
122
|
-
* @param {string} code
|
|
123
|
-
* @param {string} filePath
|
|
124
|
-
* @param {'tests'|'params'|'all'} level
|
|
125
|
-
* @returns {UndocumentedItem[]}
|
|
126
|
-
*/
|
|
127
|
-
export function checkUndocumentedFile(code, filePath, level) {
|
|
128
|
-
const results = [];
|
|
129
|
-
|
|
130
|
-
let ast;
|
|
131
|
-
try {
|
|
132
|
-
ast = parse(code, { ecmaVersion: 'latest', sourceType: 'module', locations: true });
|
|
133
|
-
} catch (e) {
|
|
134
|
-
return results;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
const comments = extractComments(code);
|
|
138
|
-
|
|
139
|
-
walk.simple(ast, {
|
|
140
|
-
ClassDeclaration(node) {
|
|
141
|
-
const className = node.id?.name || 'Anonymous';
|
|
142
|
-
|
|
143
|
-
// Check class itself (only for 'all' level)
|
|
144
|
-
if (level === 'all') {
|
|
145
|
-
const classJsdoc = findJSDocBefore(comments, node.loc.start.line);
|
|
146
|
-
if (!classJsdoc) {
|
|
147
|
-
results.push({
|
|
148
|
-
name: className,
|
|
149
|
-
type: 'class',
|
|
150
|
-
file: filePath,
|
|
151
|
-
line: node.loc.start.line,
|
|
152
|
-
reason: 'No JSDoc',
|
|
153
|
-
});
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Check methods
|
|
158
|
-
for (const element of node.body.body) {
|
|
159
|
-
if (element.type === 'MethodDefinition') {
|
|
160
|
-
const methodName = element.key.name || element.key.value;
|
|
161
|
-
|
|
162
|
-
// Skip: constructor, private, getters/setters, lifecycle
|
|
163
|
-
if (element.kind === 'get' || element.kind === 'set') continue;
|
|
164
|
-
if (methodName?.startsWith('_')) continue;
|
|
165
|
-
if (SKIP_METHODS.includes(methodName)) continue;
|
|
166
|
-
|
|
167
|
-
const jsdoc = findJSDocBefore(comments, element.loc.start.line);
|
|
168
|
-
const missing = checkMissing(jsdoc, level);
|
|
169
|
-
|
|
170
|
-
if (missing.length > 0) {
|
|
171
|
-
results.push({
|
|
172
|
-
name: `${className}.${methodName}`,
|
|
173
|
-
type: 'method',
|
|
174
|
-
file: filePath,
|
|
175
|
-
line: element.loc.start.line,
|
|
176
|
-
reason: missing.join(', '),
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
},
|
|
182
|
-
|
|
183
|
-
FunctionDeclaration(node) {
|
|
184
|
-
if (!node.id) return;
|
|
185
|
-
const funcName = node.id.name;
|
|
186
|
-
|
|
187
|
-
// Skip private functions
|
|
188
|
-
if (funcName.startsWith('_')) return;
|
|
189
|
-
|
|
190
|
-
const jsdoc = findJSDocBefore(comments, node.loc.start.line);
|
|
191
|
-
const missing = checkMissing(jsdoc, level);
|
|
192
|
-
|
|
193
|
-
if (missing.length > 0) {
|
|
194
|
-
results.push({
|
|
195
|
-
name: funcName,
|
|
196
|
-
type: 'function',
|
|
197
|
-
file: filePath,
|
|
198
|
-
line: node.loc.start.line,
|
|
199
|
-
reason: missing.join(', '),
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
},
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return results;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Get undocumented items from directory
|
|
210
|
-
* @param {string} dir
|
|
211
|
-
* @param {'tests'|'params'|'all'} level
|
|
212
|
-
* @returns {UndocumentedItem[]}
|
|
213
|
-
*/
|
|
214
|
-
export function getUndocumented(dir, level = 'tests') {
|
|
215
|
-
const resolvedDir = resolve(dir);
|
|
216
|
-
const files = findJSFiles(dir);
|
|
217
|
-
const results = [];
|
|
218
|
-
|
|
219
|
-
for (const file of files) {
|
|
220
|
-
let content;
|
|
221
|
-
try {
|
|
222
|
-
content = readFileSync(file, 'utf-8');
|
|
223
|
-
} catch (e) {
|
|
224
|
-
continue; // File deleted between findJSFiles and read
|
|
225
|
-
}
|
|
226
|
-
const items = checkUndocumentedFile(content, relative(resolvedDir, file), level);
|
|
227
|
-
results.push(...items);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
return results;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Get summary of undocumented items
|
|
235
|
-
* @param {string} dir
|
|
236
|
-
* @param {'tests'|'params'|'all'} level
|
|
237
|
-
* @returns {Object}
|
|
238
|
-
*/
|
|
239
|
-
export function getUndocumentedSummary(dir, level = 'tests') {
|
|
240
|
-
const items = getUndocumented(dir, level);
|
|
241
|
-
|
|
242
|
-
const byType = {
|
|
243
|
-
class: items.filter(i => i.type === 'class').length,
|
|
244
|
-
function: items.filter(i => i.type === 'function').length,
|
|
245
|
-
method: items.filter(i => i.type === 'method').length,
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const byReason = {};
|
|
249
|
-
for (const item of items) {
|
|
250
|
-
byReason[item.reason] = (byReason[item.reason] || 0) + 1;
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
return {
|
|
254
|
-
total: items.length,
|
|
255
|
-
byType,
|
|
256
|
-
byReason,
|
|
257
|
-
items: items.slice(0, 20),
|
|
258
|
-
};
|
|
259
|
-
}
|
package/src/workspace.js
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Workspace Root Resolution
|
|
3
|
-
*
|
|
4
|
-
* Resolves relative paths against the correct workspace root.
|
|
5
|
-
* Priority: MCP initialize roots → --workspace arg → PROJECT_ROOT env → process.cwd()
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { resolve, isAbsolute } from 'path';
|
|
9
|
-
|
|
10
|
-
/** @type {string|null} */
|
|
11
|
-
let workspaceRoot = null;
|
|
12
|
-
|
|
13
|
-
// Auto-detect --workspace arg at module load
|
|
14
|
-
const wsArg = process.argv.find(a => a.startsWith('--workspace='));
|
|
15
|
-
if (wsArg) {
|
|
16
|
-
workspaceRoot = wsArg.split('=')[1];
|
|
17
|
-
console.error(`[project-graph] Workspace from arg: ${workspaceRoot}`);
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Set workspace root from MCP initialize roots
|
|
22
|
-
* @param {Array<{uri: string, name?: string}>} roots
|
|
23
|
-
*/
|
|
24
|
-
export function setRoots(roots) {
|
|
25
|
-
if (roots && roots.length > 0) {
|
|
26
|
-
let uri = roots[0].uri;
|
|
27
|
-
// Strip file:// protocol if present
|
|
28
|
-
if (uri.startsWith('file://')) {
|
|
29
|
-
uri = uri.slice(7);
|
|
30
|
-
}
|
|
31
|
-
workspaceRoot = uri;
|
|
32
|
-
console.error(`[project-graph] Workspace root: ${workspaceRoot}`);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Get current workspace root
|
|
38
|
-
* @returns {string}
|
|
39
|
-
*/
|
|
40
|
-
export function getWorkspaceRoot() {
|
|
41
|
-
if (workspaceRoot) {
|
|
42
|
-
return workspaceRoot;
|
|
43
|
-
}
|
|
44
|
-
if (process.env.PROJECT_ROOT) {
|
|
45
|
-
return process.env.PROJECT_ROOT;
|
|
46
|
-
}
|
|
47
|
-
return process.cwd();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Resolve a path argument against workspace root.
|
|
52
|
-
* Absolute paths are returned as-is.
|
|
53
|
-
* Relative paths are resolved against the workspace root.
|
|
54
|
-
* @param {string} inputPath
|
|
55
|
-
* @returns {string}
|
|
56
|
-
*/
|
|
57
|
-
export function resolvePath(inputPath) {
|
|
58
|
-
if (!inputPath) {
|
|
59
|
-
return getWorkspaceRoot();
|
|
60
|
-
}
|
|
61
|
-
const root = getWorkspaceRoot();
|
|
62
|
-
const resolved = isAbsolute(inputPath) ? inputPath : resolve(root, inputPath);
|
|
63
|
-
|
|
64
|
-
// Prevent path traversal — resolved path must stay within workspace
|
|
65
|
-
if (!resolved.startsWith(root)) {
|
|
66
|
-
throw new Error(`Path traversal blocked: '${inputPath}' resolves outside workspace root '${root}'`);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
return resolved;
|
|
70
|
-
}
|
|
File without changes
|
|
File without changes
|