project-graph-mcp 1.5.0 → 2.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/README.md +128 -8
- 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/ai-context.js +7 -0
- package/src/compact/compact.js +18 -0
- package/src/compact/compress.js +13 -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 +16 -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/ai-context.js
DELETED
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AI Context Boot — Single-call agent bootstrap
|
|
3
|
-
*
|
|
4
|
-
* Combines skeleton + doc-dialect + optional compressed files
|
|
5
|
-
* into one response for AI agent initialization.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { resolve, extname } from 'path';
|
|
9
|
-
import { getSkeleton, getGraph } from './tools.js';
|
|
10
|
-
import { getProjectDocs } from './doc-dialect.js';
|
|
11
|
-
import { compressFile } from './compress.js';
|
|
12
|
-
import { findJSFiles } from './parser.js';
|
|
13
|
-
|
|
14
|
-
/** Supported extensions for compression */
|
|
15
|
-
const COMPRESSIBLE = new Set(['.js', '.mjs', '.ts', '.tsx']);
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Estimate tokens for any value (string or object)
|
|
19
|
-
* @param {*} value
|
|
20
|
-
* @returns {number}
|
|
21
|
-
*/
|
|
22
|
-
function estimateTokens(value) {
|
|
23
|
-
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
24
|
-
return Math.ceil(str.length / 4);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Load complete AI context for agent bootstrap
|
|
29
|
-
* @param {string} dirPath - Project directory
|
|
30
|
-
* @param {Object} [options]
|
|
31
|
-
* @param {string[]} [options.includeFiles] - Specific files to include compressed
|
|
32
|
-
* @param {boolean} [options.includeDocs=true] - Include doc-dialect
|
|
33
|
-
* @param {boolean} [options.includeSkeleton=true] - Include skeleton
|
|
34
|
-
* @returns {Promise<{skeleton?: Object, docs?: string, files?: Object, totalTokens: number, vsOriginal: number, savings: string}>}
|
|
35
|
-
*/
|
|
36
|
-
export async function getAiContext(dirPath, options = {}) {
|
|
37
|
-
const {
|
|
38
|
-
includeFiles = [],
|
|
39
|
-
includeDocs = true,
|
|
40
|
-
includeSkeleton = true,
|
|
41
|
-
} = options;
|
|
42
|
-
|
|
43
|
-
const projectPath = resolve(dirPath);
|
|
44
|
-
const result = {};
|
|
45
|
-
let totalTokens = 0;
|
|
46
|
-
|
|
47
|
-
// 1. Skeleton
|
|
48
|
-
if (includeSkeleton) {
|
|
49
|
-
result.skeleton = await getSkeleton(projectPath);
|
|
50
|
-
totalTokens += estimateTokens(result.skeleton);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 2. Doc Dialect
|
|
54
|
-
if (includeDocs) {
|
|
55
|
-
const graph = await getGraph(projectPath);
|
|
56
|
-
result.docs = getProjectDocs(graph, projectPath);
|
|
57
|
-
totalTokens += estimateTokens(result.docs);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// 3. Compressed files
|
|
61
|
-
if (includeFiles.length > 0) {
|
|
62
|
-
result.files = {};
|
|
63
|
-
const allFiles = findJSFiles(projectPath);
|
|
64
|
-
|
|
65
|
-
for (const requestedFile of includeFiles) {
|
|
66
|
-
// Find matching file (by name or path)
|
|
67
|
-
const match = allFiles.find(f =>
|
|
68
|
-
f.endsWith(requestedFile) || f.endsWith('/' + requestedFile)
|
|
69
|
-
);
|
|
70
|
-
|
|
71
|
-
if (!match) {
|
|
72
|
-
result.files[requestedFile] = { error: `File not found: ${requestedFile}` };
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
const ext = extname(match).toLowerCase();
|
|
77
|
-
if (!COMPRESSIBLE.has(ext)) {
|
|
78
|
-
result.files[requestedFile] = { error: `Unsupported file type: ${ext}` };
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const compressed = await compressFile(match, { beautify: true, legend: true });
|
|
84
|
-
result.files[requestedFile] = compressed.code;
|
|
85
|
-
totalTokens += compressed.compressed;
|
|
86
|
-
} catch (e) {
|
|
87
|
-
result.files[requestedFile] = { error: e.message };
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// Estimate original size for savings calculation
|
|
93
|
-
const allFiles = findJSFiles(projectPath);
|
|
94
|
-
let vsOriginal = 0;
|
|
95
|
-
for (const file of allFiles) {
|
|
96
|
-
try {
|
|
97
|
-
const { readFileSync } = await import('fs');
|
|
98
|
-
vsOriginal += estimateTokens(readFileSync(file, 'utf-8'));
|
|
99
|
-
} catch {
|
|
100
|
-
// skip unreadable
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const savings = vsOriginal > 0
|
|
105
|
-
? Math.round((1 - totalTokens / vsOriginal) * 100)
|
|
106
|
-
: 0;
|
|
107
|
-
|
|
108
|
-
result.totalTokens = totalTokens;
|
|
109
|
-
result.vsOriginal = vsOriginal;
|
|
110
|
-
result.savings = `${savings}%`;
|
|
111
|
-
|
|
112
|
-
return result;
|
|
113
|
-
}
|
package/src/analysis-cache.js
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Analysis Cache Manager
|
|
3
|
-
* Persistent file-based cache in .context/.cache/ with dual hashing
|
|
4
|
-
*
|
|
5
|
-
* - sig: interface hash (function names, params, exports) → invalidates docs
|
|
6
|
-
* - contentHash: full source hash → invalidates body-dependent metrics
|
|
7
|
-
*
|
|
8
|
-
* Cacheable (per-file): complexity, undocumented, jsdocConsistency
|
|
9
|
-
* NOT cacheable (cross-file): deadCode, similarity
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from 'fs';
|
|
13
|
-
import { join, dirname } from 'path';
|
|
14
|
-
import { createHash } from 'crypto';
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* @typedef {Object} CacheEntry
|
|
18
|
-
* @property {string} sig - Interface hash
|
|
19
|
-
* @property {string} contentHash - Full content hash
|
|
20
|
-
* @property {Object} [complexity] - Cached complexity results
|
|
21
|
-
* @property {Array} [undocumented] - Cached undocumented items
|
|
22
|
-
* @property {Array} [jsdocIssues] - Cached JSDoc consistency issues
|
|
23
|
-
* @property {string} cachedAt - ISO timestamp
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Compute interface signature hash
|
|
28
|
-
* Matches @sig logic from doc-dialect.js
|
|
29
|
-
* @param {Object} fileData - Parsed file data with functions, classes, exports
|
|
30
|
-
* @returns {string} - 8-char hex hash
|
|
31
|
-
*/
|
|
32
|
-
export function computeSig(fileData) {
|
|
33
|
-
const parts = [];
|
|
34
|
-
|
|
35
|
-
// Function signatures
|
|
36
|
-
if (fileData.functions) {
|
|
37
|
-
for (const fn of fileData.functions) {
|
|
38
|
-
parts.push(`fn:${fn.name}:${fn.params?.length || 0}`);
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Class signatures
|
|
43
|
-
if (fileData.classes) {
|
|
44
|
-
for (const cls of fileData.classes) {
|
|
45
|
-
parts.push(`cls:${cls.name}`);
|
|
46
|
-
if (cls.methods) {
|
|
47
|
-
for (const m of cls.methods) {
|
|
48
|
-
parts.push(`m:${cls.name}.${m}`);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
// Exports
|
|
55
|
-
if (fileData.exports) {
|
|
56
|
-
for (const exp of fileData.exports) {
|
|
57
|
-
parts.push(`exp:${exp}`);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const hash = createHash('md5').update(parts.sort().join('|')).digest('hex');
|
|
62
|
-
return hash.slice(0, 8);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Compute full content hash (body-inclusive)
|
|
67
|
-
* Used for complexity and other body-dependent metrics
|
|
68
|
-
* @param {string} code - Source code
|
|
69
|
-
* @returns {string} - 8-char hex hash
|
|
70
|
-
*/
|
|
71
|
-
export function computeContentHash(code) {
|
|
72
|
-
return createHash('md5').update(code).digest('hex').slice(0, 8);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Get cache file path for a source file
|
|
77
|
-
* @param {string} contextDir - .context directory path
|
|
78
|
-
* @param {string} relPath - Relative path to source file (e.g., "src/parser.js")
|
|
79
|
-
* @returns {string} - Cache file path
|
|
80
|
-
*/
|
|
81
|
-
export function getCachePath(contextDir, relPath) {
|
|
82
|
-
// src/parser.js → .context/.cache/src/parser.json
|
|
83
|
-
const cacheName = relPath.replace(/\.[^.]+$/, '.json');
|
|
84
|
-
return join(contextDir, '.cache', cacheName);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Read cached analysis for a file
|
|
89
|
-
* @param {string} contextDir
|
|
90
|
-
* @param {string} relPath
|
|
91
|
-
* @returns {CacheEntry|null}
|
|
92
|
-
*/
|
|
93
|
-
export function readCache(contextDir, relPath) {
|
|
94
|
-
const cachePath = getCachePath(contextDir, relPath);
|
|
95
|
-
try {
|
|
96
|
-
if (!existsSync(cachePath)) return null;
|
|
97
|
-
return JSON.parse(readFileSync(cachePath, 'utf-8'));
|
|
98
|
-
} catch (e) {
|
|
99
|
-
return null;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Write cache entry for a file
|
|
105
|
-
* @param {string} contextDir
|
|
106
|
-
* @param {string} relPath
|
|
107
|
-
* @param {CacheEntry} data
|
|
108
|
-
*/
|
|
109
|
-
export function writeCache(contextDir, relPath, data) {
|
|
110
|
-
const cachePath = getCachePath(contextDir, relPath);
|
|
111
|
-
try {
|
|
112
|
-
mkdirSync(dirname(cachePath), { recursive: true });
|
|
113
|
-
writeFileSync(cachePath, JSON.stringify({
|
|
114
|
-
...data,
|
|
115
|
-
cachedAt: new Date().toISOString(),
|
|
116
|
-
}, null, 2));
|
|
117
|
-
} catch (e) {
|
|
118
|
-
// Cache write failure is non-fatal
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Check if cache is still valid
|
|
124
|
-
* @param {CacheEntry|null} cached
|
|
125
|
-
* @param {string} currentSig - Current interface hash
|
|
126
|
-
* @param {string} currentContentHash - Current content hash
|
|
127
|
-
* @param {'sig'|'content'} level - Which hash to check
|
|
128
|
-
* @returns {boolean}
|
|
129
|
-
*/
|
|
130
|
-
export function isCacheValid(cached, currentSig, currentContentHash, level = 'content') {
|
|
131
|
-
if (!cached) return false;
|
|
132
|
-
if (!cached.sig || !cached.contentHash) return false;
|
|
133
|
-
|
|
134
|
-
if (level === 'sig') {
|
|
135
|
-
return cached.sig === currentSig;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// For body-dependent metrics, both hashes must match
|
|
139
|
-
return cached.sig === currentSig && cached.contentHash === currentContentHash;
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
/**
|
|
143
|
-
* Invalidate all caches (e.g., after structural changes)
|
|
144
|
-
* @param {string} contextDir
|
|
145
|
-
*/
|
|
146
|
-
export function invalidateAllCaches(contextDir) {
|
|
147
|
-
const cacheDir = join(contextDir, '.cache');
|
|
148
|
-
try {
|
|
149
|
-
if (existsSync(cacheDir)) {
|
|
150
|
-
rmSync(cacheDir, { recursive: true, force: true });
|
|
151
|
-
}
|
|
152
|
-
} catch (e) {
|
|
153
|
-
// Non-fatal
|
|
154
|
-
}
|
|
155
|
-
}
|
package/src/cli-handlers.js
DELETED
|
@@ -1,271 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Command Handlers Registry
|
|
3
|
-
* Extracted from cli.js to reduce cyclomatic complexity
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { getSkeleton, getFocusZone, expand, deps, usages } from './tools.js';
|
|
7
|
-
import { getPendingTests, getTestSummary } from './test-annotations.js';
|
|
8
|
-
import { getFilters } from './filters.js';
|
|
9
|
-
import { getInstructions } from './instructions.js';
|
|
10
|
-
import { getUndocumentedSummary } from './undocumented.js';
|
|
11
|
-
import { getDeadCode } from './dead-code.js';
|
|
12
|
-
import { generateJSDoc } from './jsdoc-generator.js';
|
|
13
|
-
import { getSimilarFunctions } from './similar-functions.js';
|
|
14
|
-
import { getComplexity } from './complexity.js';
|
|
15
|
-
import { getLargeFiles } from './large-files.js';
|
|
16
|
-
import { getOutdatedPatterns } from './outdated-patterns.js';
|
|
17
|
-
import { getFullAnalysis } from './full-analysis.js';
|
|
18
|
-
import { compressFile } from './compress.js';
|
|
19
|
-
import { getProjectDocs, generateContextFiles } from './doc-dialect.js';
|
|
20
|
-
import { getGraph } from './tools.js';
|
|
21
|
-
import { parseProject } from './parser.js';
|
|
22
|
-
import { resolvePath } from './workspace.js';
|
|
23
|
-
import { checkJSDocConsistency } from './jsdoc-checker.js';
|
|
24
|
-
import { checkTypes } from './type-checker.js';
|
|
25
|
-
import { compactProject, expandProject } from './compact.js';
|
|
26
|
-
import { injectJSDoc, stripJSDoc, validateCtxContracts } from './ctx-to-jsdoc.js';
|
|
27
|
-
import { getConfig, setConfig, getModeDescription, getModeWorkflow } from './mode-config.js';
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Parse named argument from args array
|
|
31
|
-
* @param {string[]} args
|
|
32
|
-
* @param {string} name
|
|
33
|
-
* @returns {string|undefined}
|
|
34
|
-
*/
|
|
35
|
-
function getArg(args, name) {
|
|
36
|
-
const arg = args.find(a => a.startsWith(`--${name}=`));
|
|
37
|
-
return arg ? arg.split('=')[1] : undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Get path argument (first non-flag arg), resolved against workspace root
|
|
42
|
-
* @param {string[]} args
|
|
43
|
-
* @returns {string}
|
|
44
|
-
*/
|
|
45
|
-
function getPath(args) {
|
|
46
|
-
const raw = args.find(a => !a.startsWith('--')) || '.';
|
|
47
|
-
return resolvePath(raw);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* CLI command handlers registry
|
|
52
|
-
* Each handler returns a result or throws an error
|
|
53
|
-
*/
|
|
54
|
-
export const CLI_HANDLERS = {
|
|
55
|
-
skeleton: {
|
|
56
|
-
requiresArg: true,
|
|
57
|
-
argError: 'Path required: skeleton <path>',
|
|
58
|
-
handler: async (args) => getSkeleton(resolvePath(args[0])),
|
|
59
|
-
},
|
|
60
|
-
|
|
61
|
-
expand: {
|
|
62
|
-
requiresArg: true,
|
|
63
|
-
argError: 'Symbol required: expand <symbol>',
|
|
64
|
-
handler: async (args) => expand(args[0]),
|
|
65
|
-
},
|
|
66
|
-
|
|
67
|
-
deps: {
|
|
68
|
-
requiresArg: true,
|
|
69
|
-
argError: 'Symbol required: deps <symbol>',
|
|
70
|
-
handler: async (args) => deps(args[0]),
|
|
71
|
-
},
|
|
72
|
-
|
|
73
|
-
usages: {
|
|
74
|
-
requiresArg: true,
|
|
75
|
-
argError: 'Symbol required: usages <symbol>',
|
|
76
|
-
handler: async (args) => usages(args[0]),
|
|
77
|
-
},
|
|
78
|
-
|
|
79
|
-
pending: {
|
|
80
|
-
handler: async (args) => getPendingTests(getPath(args)),
|
|
81
|
-
},
|
|
82
|
-
|
|
83
|
-
summary: {
|
|
84
|
-
handler: async (args) => getTestSummary(getPath(args)),
|
|
85
|
-
},
|
|
86
|
-
|
|
87
|
-
filters: {
|
|
88
|
-
handler: async () => getFilters(),
|
|
89
|
-
},
|
|
90
|
-
|
|
91
|
-
instructions: {
|
|
92
|
-
rawOutput: true,
|
|
93
|
-
handler: async () => getInstructions(),
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
undocumented: {
|
|
97
|
-
handler: async (args) => {
|
|
98
|
-
const level = getArg(args, 'level') || 'tests';
|
|
99
|
-
return getUndocumentedSummary(getPath(args), level);
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
|
|
103
|
-
deadcode: {
|
|
104
|
-
handler: async (args) => getDeadCode(getPath(args)),
|
|
105
|
-
},
|
|
106
|
-
|
|
107
|
-
jsdoc: {
|
|
108
|
-
requiresArg: true,
|
|
109
|
-
argError: 'Usage: jsdoc <file>',
|
|
110
|
-
handler: async (args) => generateJSDoc(resolvePath(args[0])),
|
|
111
|
-
},
|
|
112
|
-
|
|
113
|
-
similar: {
|
|
114
|
-
handler: async (args) => {
|
|
115
|
-
const threshold = parseInt(getArg(args, 'threshold')) || 60;
|
|
116
|
-
return getSimilarFunctions(getPath(args), { threshold });
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
|
|
120
|
-
complexity: {
|
|
121
|
-
handler: async (args) => {
|
|
122
|
-
const minComplexity = parseInt(getArg(args, 'min')) || 1;
|
|
123
|
-
const onlyProblematic = args.includes('--problematic');
|
|
124
|
-
return getComplexity(getPath(args), { minComplexity, onlyProblematic });
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
largefiles: {
|
|
129
|
-
handler: async (args) => {
|
|
130
|
-
const onlyProblematic = args.includes('--problematic');
|
|
131
|
-
return getLargeFiles(getPath(args), { onlyProblematic });
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
|
|
135
|
-
outdated: {
|
|
136
|
-
handler: async (args) => {
|
|
137
|
-
const codeOnly = args.includes('--code');
|
|
138
|
-
const depsOnly = args.includes('--deps');
|
|
139
|
-
return getOutdatedPatterns(getPath(args), { codeOnly, depsOnly });
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
|
|
143
|
-
analyze: {
|
|
144
|
-
handler: async (args) => {
|
|
145
|
-
const includeItems = args.includes('--items');
|
|
146
|
-
return getFullAnalysis(getPath(args), { includeItems });
|
|
147
|
-
},
|
|
148
|
-
},
|
|
149
|
-
|
|
150
|
-
'jsdoc-check': {
|
|
151
|
-
handler: async (args) => checkJSDocConsistency(getPath(args)),
|
|
152
|
-
},
|
|
153
|
-
|
|
154
|
-
types: {
|
|
155
|
-
handler: async (args) => {
|
|
156
|
-
const maxDiagnostics = parseInt(getArg(args, 'max')) || 50;
|
|
157
|
-
return checkTypes(getPath(args), { maxDiagnostics });
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
|
|
161
|
-
compress: {
|
|
162
|
-
requiresArg: true,
|
|
163
|
-
argError: 'Usage: compress <file> [--no-beautify] [--no-legend]',
|
|
164
|
-
handler: async (args) => {
|
|
165
|
-
const beautify = !args.includes('--no-beautify');
|
|
166
|
-
const legend = !args.includes('--no-legend');
|
|
167
|
-
return compressFile(resolvePath(args[0]), { beautify, legend });
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
docs: {
|
|
172
|
-
requiresArg: true,
|
|
173
|
-
argError: 'Usage: docs <path> [--file=<filename>]',
|
|
174
|
-
handler: async (args) => {
|
|
175
|
-
const projectPath = resolvePath(args[0]);
|
|
176
|
-
const graph = await getGraph(projectPath);
|
|
177
|
-
const file = args.find(a => a.startsWith('--file='))?.split('=')[1];
|
|
178
|
-
return getProjectDocs(graph, projectPath, { file });
|
|
179
|
-
},
|
|
180
|
-
},
|
|
181
|
-
|
|
182
|
-
'generate-ctx': {
|
|
183
|
-
requiresArg: true,
|
|
184
|
-
argError: 'Usage: generate-ctx <path> [--overwrite] [--scope=focus|all]',
|
|
185
|
-
handler: async (args) => {
|
|
186
|
-
const projectPath = resolvePath(args[0]);
|
|
187
|
-
const graph = await getGraph(projectPath);
|
|
188
|
-
const parsed = await parseProject(projectPath);
|
|
189
|
-
const overwrite = args.includes('--overwrite');
|
|
190
|
-
const scope = args.find(a => a.startsWith('--scope='))?.split('=')[1] || 'all';
|
|
191
|
-
return generateContextFiles(graph, projectPath, parsed, { overwrite, scope });
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
|
|
195
|
-
compact: {
|
|
196
|
-
requiresArg: true,
|
|
197
|
-
argError: 'Usage: compact <path> [--dry-run]',
|
|
198
|
-
handler: async (args) => {
|
|
199
|
-
const projectPath = resolvePath(args[0]);
|
|
200
|
-
const dryRun = args.includes('--dry-run');
|
|
201
|
-
return compactProject(projectPath, { dryRun });
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
beautify: {
|
|
206
|
-
requiresArg: true,
|
|
207
|
-
argError: 'Usage: beautify <path> [--dry-run]',
|
|
208
|
-
handler: async (args) => {
|
|
209
|
-
const projectPath = resolvePath(args[0]);
|
|
210
|
-
const dryRun = args.includes('--dry-run');
|
|
211
|
-
return expandProject(projectPath, { dryRun });
|
|
212
|
-
},
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
'inject-jsdoc': {
|
|
216
|
-
requiresArg: true,
|
|
217
|
-
argError: 'Usage: inject-jsdoc <path> [--dry-run]',
|
|
218
|
-
handler: async (args) => {
|
|
219
|
-
const projectPath = resolvePath(args[0]);
|
|
220
|
-
const dryRun = args.includes('--dry-run');
|
|
221
|
-
return injectJSDoc(projectPath, { dryRun });
|
|
222
|
-
},
|
|
223
|
-
},
|
|
224
|
-
|
|
225
|
-
'strip-jsdoc': {
|
|
226
|
-
requiresArg: true,
|
|
227
|
-
argError: 'Usage: strip-jsdoc <path> [--dry-run]',
|
|
228
|
-
handler: async (args) => {
|
|
229
|
-
const projectPath = resolvePath(args[0]);
|
|
230
|
-
const dryRun = args.includes('--dry-run');
|
|
231
|
-
return stripJSDoc(projectPath, { dryRun });
|
|
232
|
-
},
|
|
233
|
-
},
|
|
234
|
-
|
|
235
|
-
'validate-ctx': {
|
|
236
|
-
requiresArg: true,
|
|
237
|
-
argError: 'Usage: validate-ctx <path> [--strict]',
|
|
238
|
-
handler: async (args) => {
|
|
239
|
-
const projectPath = resolvePath(args[0]);
|
|
240
|
-
const strict = args.includes('--strict');
|
|
241
|
-
return validateCtxContracts(projectPath, { strict });
|
|
242
|
-
},
|
|
243
|
-
},
|
|
244
|
-
|
|
245
|
-
mode: {
|
|
246
|
-
requiresArg: true,
|
|
247
|
-
argError: 'Usage: mode <path>',
|
|
248
|
-
handler: async (args) => {
|
|
249
|
-
const dir = resolvePath(args[0]);
|
|
250
|
-
const config = getConfig(dir);
|
|
251
|
-
return {
|
|
252
|
-
...config,
|
|
253
|
-
description: getModeDescription(config.mode),
|
|
254
|
-
workflow: getModeWorkflow(config.mode),
|
|
255
|
-
};
|
|
256
|
-
},
|
|
257
|
-
},
|
|
258
|
-
|
|
259
|
-
'set-mode': {
|
|
260
|
-
requiresArg: true,
|
|
261
|
-
argError: 'Usage: set-mode <path> <1|2|3>',
|
|
262
|
-
handler: async (args) => {
|
|
263
|
-
const dir = resolvePath(args[0]);
|
|
264
|
-
const mode = parseInt(args[1], 10);
|
|
265
|
-
if (!mode || ![1, 2, 3].includes(mode)) {
|
|
266
|
-
throw new Error('Mode must be 1, 2, or 3');
|
|
267
|
-
}
|
|
268
|
-
return setConfig(dir, { mode });
|
|
269
|
-
},
|
|
270
|
-
},
|
|
271
|
-
};
|
package/src/cli.js
DELETED
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* CLI Entry Point for Project Graph MCP
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { CLI_HANDLERS } from './cli-handlers.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Print CLI help
|
|
9
|
-
*/
|
|
10
|
-
export function printHelp() {
|
|
11
|
-
console.log(`
|
|
12
|
-
project-graph-mcp - MCP server for AI agents
|
|
13
|
-
|
|
14
|
-
Usage:
|
|
15
|
-
npx project-graph-mcp Start MCP stdio server
|
|
16
|
-
npx project-graph-mcp <command> [args] Run CLI command
|
|
17
|
-
|
|
18
|
-
Commands:
|
|
19
|
-
skeleton <path> Get compact project overview
|
|
20
|
-
expand <symbol> Expand minified symbol (e.g., SN, SN.togglePin)
|
|
21
|
-
deps <symbol> Get dependency tree
|
|
22
|
-
usages <symbol> Find all usages
|
|
23
|
-
pending <path> List pending .ctx.md test checklists
|
|
24
|
-
summary <path> Get test progress summary
|
|
25
|
-
undocumented <path> Find missing JSDoc (--level=tests|params|all)
|
|
26
|
-
deadcode <path> Find unused functions/classes
|
|
27
|
-
jsdoc <file> Generate JSDoc for file
|
|
28
|
-
similar <path> Find similar functions (--threshold=60)
|
|
29
|
-
complexity <path> Analyze cyclomatic complexity (--min=1)
|
|
30
|
-
largefiles <path> Find files needing split (--problematic)
|
|
31
|
-
outdated <path> Find legacy patterns & redundant deps
|
|
32
|
-
analyze <path> Run ALL checks with Health Score
|
|
33
|
-
jsdoc-check <path> Validate JSDoc ↔ function signatures
|
|
34
|
-
types <path> Run tsc type checking (--max=50)
|
|
35
|
-
compress <file> Compress JS file for AI (--no-beautify, --no-legend)
|
|
36
|
-
compact <path> Compact all JS files — strips comments/whitespace (--dry-run)
|
|
37
|
-
beautify <path> Beautify/expand all JS files — inverse of compact (--dry-run)
|
|
38
|
-
inject-jsdoc <path> Generate JSDoc from .ctx files and inject into source
|
|
39
|
-
strip-jsdoc <path> Strip all JSDoc blocks from source files
|
|
40
|
-
docs <path> Get project docs in doc-dialect format (--file=<name>)
|
|
41
|
-
generate-ctx <path> Generate .context/ docs (--overwrite --scope=focus)
|
|
42
|
-
validate-ctx <path> Validate .ctx contracts against source AST (--strict)
|
|
43
|
-
mode <path> Show current compact code mode and workflow
|
|
44
|
-
set-mode <path> <1|2|3> Set compact code mode (1=compact, 2=full, 3=IDE)
|
|
45
|
-
filters Show current filter configuration
|
|
46
|
-
instructions Show agent guidelines (JSDoc, Arch)
|
|
47
|
-
help Show this help
|
|
48
|
-
|
|
49
|
-
Examples:
|
|
50
|
-
npx project-graph-mcp skeleton src/components
|
|
51
|
-
npx project-graph-mcp expand SN
|
|
52
|
-
npx project-graph-mcp compact src/ --dry-run
|
|
53
|
-
`);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Run CLI command
|
|
58
|
-
* @param {string} command
|
|
59
|
-
* @param {string[]} args
|
|
60
|
-
*/
|
|
61
|
-
export async function runCLI(command, args) {
|
|
62
|
-
// Handle help commands
|
|
63
|
-
if (!command || command === 'help' || command === '--help' || command === '-h') {
|
|
64
|
-
printHelp();
|
|
65
|
-
return;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// Look up handler
|
|
69
|
-
const def = CLI_HANDLERS[command];
|
|
70
|
-
if (!def) {
|
|
71
|
-
console.error(`Unknown command: ${command}`);
|
|
72
|
-
console.error('Run with "help" for usage information');
|
|
73
|
-
process.exit(1);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// Validate required arg
|
|
77
|
-
if (def.requiresArg && !args[0]) {
|
|
78
|
-
console.error(def.argError || `Argument required for: ${command}`);
|
|
79
|
-
process.exit(1);
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const result = await def.handler(args);
|
|
84
|
-
|
|
85
|
-
// Handle raw output (like instructions)
|
|
86
|
-
if (def.rawOutput) {
|
|
87
|
-
console.log(result);
|
|
88
|
-
} else {
|
|
89
|
-
console.log(JSON.stringify(result, null, 2));
|
|
90
|
-
}
|
|
91
|
-
} catch (error) {
|
|
92
|
-
console.error('Error:', error.message);
|
|
93
|
-
process.exit(1);
|
|
94
|
-
}
|
|
95
|
-
}
|