projscan 0.10.0 → 0.12.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 +56 -19
- package/dist/analyzers/unusedDependencyCheck.js +69 -17
- package/dist/analyzers/unusedDependencyCheck.js.map +1 -1
- package/dist/cli/_shared.d.ts +16 -0
- package/dist/cli/_shared.js +210 -0
- package/dist/cli/_shared.js.map +1 -0
- package/dist/cli/commands/analyze.d.ts +1 -0
- package/dist/cli/commands/analyze.js +87 -0
- package/dist/cli/commands/analyze.js.map +1 -0
- package/dist/cli/commands/audit.d.ts +1 -0
- package/dist/cli/commands/audit.js +47 -0
- package/dist/cli/commands/audit.js.map +1 -0
- package/dist/cli/commands/badge.d.ts +1 -0
- package/dist/cli/commands/badge.js +45 -0
- package/dist/cli/commands/badge.js.map +1 -0
- package/dist/cli/commands/ci.d.ts +1 -0
- package/dist/cli/commands/ci.js +57 -0
- package/dist/cli/commands/ci.js.map +1 -0
- package/dist/cli/commands/coupling.d.ts +1 -0
- package/dist/cli/commands/coupling.js +83 -0
- package/dist/cli/commands/coupling.js.map +1 -0
- package/dist/cli/commands/coverage.d.ts +1 -0
- package/dist/cli/commands/coverage.js +63 -0
- package/dist/cli/commands/coverage.js.map +1 -0
- package/dist/cli/commands/dependencies.d.ts +1 -0
- package/dist/cli/commands/dependencies.js +45 -0
- package/dist/cli/commands/dependencies.js.map +1 -0
- package/dist/cli/commands/diagram.d.ts +1 -0
- package/dist/cli/commands/diagram.js +45 -0
- package/dist/cli/commands/diagram.js.map +1 -0
- package/dist/cli/commands/diff.d.ts +1 -0
- package/dist/cli/commands/diff.js +70 -0
- package/dist/cli/commands/diff.js.map +1 -0
- package/dist/cli/commands/doctor.d.ts +1 -0
- package/dist/cli/commands/doctor.js +62 -0
- package/dist/cli/commands/doctor.js.map +1 -0
- package/dist/cli/commands/explain.d.ts +1 -0
- package/dist/cli/commands/explain.js +42 -0
- package/dist/cli/commands/explain.js.map +1 -0
- package/dist/cli/commands/file.d.ts +1 -0
- package/dist/cli/commands/file.js +45 -0
- package/dist/cli/commands/file.js.map +1 -0
- package/dist/cli/commands/fix.d.ts +1 -0
- package/dist/cli/commands/fix.js +70 -0
- package/dist/cli/commands/fix.js.map +1 -0
- package/dist/cli/commands/help.d.ts +1 -0
- package/dist/cli/commands/help.js +11 -0
- package/dist/cli/commands/help.js.map +1 -0
- package/dist/cli/commands/hotspots.d.ts +1 -0
- package/dist/cli/commands/hotspots.js +74 -0
- package/dist/cli/commands/hotspots.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +21 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/outdated.d.ts +1 -0
- package/dist/cli/commands/outdated.js +51 -0
- package/dist/cli/commands/outdated.js.map +1 -0
- package/dist/cli/commands/prDiff.d.ts +1 -0
- package/dist/cli/commands/prDiff.js +59 -0
- package/dist/cli/commands/prDiff.js.map +1 -0
- package/dist/cli/commands/search.d.ts +1 -0
- package/dist/cli/commands/search.js +233 -0
- package/dist/cli/commands/search.js.map +1 -0
- package/dist/cli/commands/structure.d.ts +1 -0
- package/dist/cli/commands/structure.js +58 -0
- package/dist/cli/commands/structure.js.map +1 -0
- package/dist/cli/commands/upgrade.d.ts +1 -0
- package/dist/cli/commands/upgrade.js +44 -0
- package/dist/cli/commands/upgrade.js.map +1 -0
- package/dist/cli/commands/workspaces.d.ts +1 -0
- package/dist/cli/commands/workspaces.js +35 -0
- package/dist/cli/commands/workspaces.js.map +1 -0
- package/dist/cli/index.js +45 -1132
- package/dist/cli/index.js.map +1 -1
- package/dist/core/ast.d.ts +2 -0
- package/dist/core/ast.js +35 -2
- package/dist/core/ast.js.map +1 -1
- package/dist/core/codeGraph.d.ts +2 -0
- package/dist/core/codeGraph.js +2 -0
- package/dist/core/codeGraph.js.map +1 -1
- package/dist/core/couplingAnalyzer.d.ts +18 -0
- package/dist/core/couplingAnalyzer.js +174 -0
- package/dist/core/couplingAnalyzer.js.map +1 -0
- package/dist/core/fileInspector.d.ts +1 -1
- package/dist/core/fileInspector.js +31 -1
- package/dist/core/fileInspector.js.map +1 -1
- package/dist/core/hotspotAnalyzer.d.ts +13 -0
- package/dist/core/hotspotAnalyzer.js +29 -6
- package/dist/core/hotspotAnalyzer.js.map +1 -1
- package/dist/core/indexCache.js +6 -3
- package/dist/core/indexCache.js.map +1 -1
- package/dist/core/languages/LanguageAdapter.d.ts +1 -1
- package/dist/core/languages/goAdapter.d.ts +2 -0
- package/dist/core/languages/goAdapter.js +138 -0
- package/dist/core/languages/goAdapter.js.map +1 -0
- package/dist/core/languages/goCallSites.d.ts +20 -0
- package/dist/core/languages/goCallSites.js +42 -0
- package/dist/core/languages/goCallSites.js.map +1 -0
- package/dist/core/languages/goCyclomatic.d.ts +21 -0
- package/dist/core/languages/goCyclomatic.js +55 -0
- package/dist/core/languages/goCyclomatic.js.map +1 -0
- package/dist/core/languages/goExports.d.ts +26 -0
- package/dist/core/languages/goExports.js +89 -0
- package/dist/core/languages/goExports.js.map +1 -0
- package/dist/core/languages/goImports.d.ts +26 -0
- package/dist/core/languages/goImports.js +64 -0
- package/dist/core/languages/goImports.js.map +1 -0
- package/dist/core/languages/goManifests.d.ts +19 -0
- package/dist/core/languages/goManifests.js +56 -0
- package/dist/core/languages/goManifests.js.map +1 -0
- package/dist/core/languages/javaAdapter.d.ts +2 -0
- package/dist/core/languages/javaAdapter.js +148 -0
- package/dist/core/languages/javaAdapter.js.map +1 -0
- package/dist/core/languages/javaCallSites.d.ts +16 -0
- package/dist/core/languages/javaCallSites.js +45 -0
- package/dist/core/languages/javaCallSites.js.map +1 -0
- package/dist/core/languages/javaCyclomatic.d.ts +21 -0
- package/dist/core/languages/javaCyclomatic.js +49 -0
- package/dist/core/languages/javaCyclomatic.js.map +1 -0
- package/dist/core/languages/javaExports.d.ts +25 -0
- package/dist/core/languages/javaExports.js +80 -0
- package/dist/core/languages/javaExports.js.map +1 -0
- package/dist/core/languages/javaImports.d.ts +25 -0
- package/dist/core/languages/javaImports.js +49 -0
- package/dist/core/languages/javaImports.js.map +1 -0
- package/dist/core/languages/javaManifests.d.ts +25 -0
- package/dist/core/languages/javaManifests.js +86 -0
- package/dist/core/languages/javaManifests.js.map +1 -0
- package/dist/core/languages/pythonAdapter.js +8 -1
- package/dist/core/languages/pythonAdapter.js.map +1 -1
- package/dist/core/languages/pythonCallSites.d.ts +19 -0
- package/dist/core/languages/pythonCallSites.js +40 -0
- package/dist/core/languages/pythonCallSites.js.map +1 -0
- package/dist/core/languages/pythonCyclomatic.d.ts +18 -0
- package/dist/core/languages/pythonCyclomatic.js +45 -0
- package/dist/core/languages/pythonCyclomatic.js.map +1 -0
- package/dist/core/languages/registry.js +4 -1
- package/dist/core/languages/registry.js.map +1 -1
- package/dist/core/languages/rubyAdapter.d.ts +2 -0
- package/dist/core/languages/rubyAdapter.js +131 -0
- package/dist/core/languages/rubyAdapter.js.map +1 -0
- package/dist/core/languages/rubyCallSites.d.ts +16 -0
- package/dist/core/languages/rubyCallSites.js +34 -0
- package/dist/core/languages/rubyCallSites.js.map +1 -0
- package/dist/core/languages/rubyCyclomatic.d.ts +19 -0
- package/dist/core/languages/rubyCyclomatic.js +47 -0
- package/dist/core/languages/rubyCyclomatic.js.map +1 -0
- package/dist/core/languages/rubyExports.d.ts +24 -0
- package/dist/core/languages/rubyExports.js +53 -0
- package/dist/core/languages/rubyExports.js.map +1 -0
- package/dist/core/languages/rubyImports.d.ts +12 -0
- package/dist/core/languages/rubyImports.js +75 -0
- package/dist/core/languages/rubyImports.js.map +1 -0
- package/dist/core/languages/rubyManifests.d.ts +20 -0
- package/dist/core/languages/rubyManifests.js +55 -0
- package/dist/core/languages/rubyManifests.js.map +1 -0
- package/dist/core/languages/treeSitterLoader.js +4 -1
- package/dist/core/languages/treeSitterLoader.js.map +1 -1
- package/dist/core/monorepo.d.ts +20 -0
- package/dist/core/monorepo.js +270 -0
- package/dist/core/monorepo.js.map +1 -0
- package/dist/core/outdatedDetector.d.ts +13 -2
- package/dist/core/outdatedDetector.js +86 -16
- package/dist/core/outdatedDetector.js.map +1 -1
- package/dist/core/prDiff.d.ts +43 -0
- package/dist/core/prDiff.js +298 -0
- package/dist/core/prDiff.js.map +1 -0
- package/dist/core/telemetry.d.ts +90 -0
- package/dist/core/telemetry.js +199 -0
- package/dist/core/telemetry.js.map +1 -0
- package/dist/grammars/tree-sitter-go.wasm +0 -0
- package/dist/grammars/tree-sitter-java.wasm +0 -0
- package/dist/grammars/tree-sitter-ruby.wasm +0 -0
- package/dist/mcp/tools/_shared.d.ts +24 -0
- package/dist/mcp/tools/_shared.js +82 -0
- package/dist/mcp/tools/_shared.js.map +1 -0
- package/dist/mcp/tools/analyze.d.ts +2 -0
- package/dist/mcp/tools/analyze.js +55 -0
- package/dist/mcp/tools/analyze.js.map +1 -0
- package/dist/mcp/tools/audit.d.ts +2 -0
- package/dist/mcp/tools/audit.js +32 -0
- package/dist/mcp/tools/audit.js.map +1 -0
- package/dist/mcp/tools/coupling.d.ts +2 -0
- package/dist/mcp/tools/coupling.js +67 -0
- package/dist/mcp/tools/coupling.js.map +1 -0
- package/dist/mcp/tools/coverage.d.ts +2 -0
- package/dist/mcp/tools/coverage.js +53 -0
- package/dist/mcp/tools/coverage.js.map +1 -0
- package/dist/mcp/tools/dependencies.d.ts +2 -0
- package/dist/mcp/tools/dependencies.js +16 -0
- package/dist/mcp/tools/dependencies.js.map +1 -0
- package/dist/mcp/tools/doctor.d.ts +2 -0
- package/dist/mcp/tools/doctor.js +30 -0
- package/dist/mcp/tools/doctor.js.map +1 -0
- package/dist/mcp/tools/explain.d.ts +2 -0
- package/dist/mcp/tools/explain.js +30 -0
- package/dist/mcp/tools/explain.js.map +1 -0
- package/dist/mcp/tools/file.d.ts +2 -0
- package/dist/mcp/tools/file.js +22 -0
- package/dist/mcp/tools/file.js.map +1 -0
- package/dist/mcp/tools/graph.d.ts +2 -0
- package/dist/mcp/tools/graph.js +69 -0
- package/dist/mcp/tools/graph.js.map +1 -0
- package/dist/mcp/tools/hotspots.d.ts +2 -0
- package/dist/mcp/tools/hotspots.js +64 -0
- package/dist/mcp/tools/hotspots.js.map +1 -0
- package/dist/mcp/tools/outdated.d.ts +2 -0
- package/dist/mcp/tools/outdated.js +36 -0
- package/dist/mcp/tools/outdated.js.map +1 -0
- package/dist/mcp/tools/prDiff.d.ts +2 -0
- package/dist/mcp/tools/prDiff.js +38 -0
- package/dist/mcp/tools/prDiff.js.map +1 -0
- package/dist/mcp/tools/search.d.ts +2 -0
- package/dist/mcp/tools/search.js +167 -0
- package/dist/mcp/tools/search.js.map +1 -0
- package/dist/mcp/tools/structure.d.ts +2 -0
- package/dist/mcp/tools/structure.js +34 -0
- package/dist/mcp/tools/structure.js.map +1 -0
- package/dist/mcp/tools/upgrade.d.ts +2 -0
- package/dist/mcp/tools/upgrade.js +38 -0
- package/dist/mcp/tools/upgrade.js.map +1 -0
- package/dist/mcp/tools/workspaces.d.ts +2 -0
- package/dist/mcp/tools/workspaces.js +13 -0
- package/dist/mcp/tools/workspaces.js.map +1 -0
- package/dist/mcp/tools.d.ts +12 -6
- package/dist/mcp/tools.js +40 -605
- package/dist/mcp/tools.js.map +1 -1
- package/dist/reporters/consoleReporter.d.ts +4 -1
- package/dist/reporters/consoleReporter.js +113 -0
- package/dist/reporters/consoleReporter.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +4 -1
- package/dist/reporters/jsonReporter.js +9 -0
- package/dist/reporters/jsonReporter.js.map +1 -1
- package/dist/reporters/markdownReporter.d.ts +4 -1
- package/dist/reporters/markdownReporter.js +103 -3
- package/dist/reporters/markdownReporter.js.map +1 -1
- package/dist/tool-manifest.json +358 -0
- package/dist/types.d.ts +115 -0
- package/dist/utils/cache.d.ts +3 -0
- package/dist/utils/cache.js +51 -0
- package/dist/utils/cache.js.map +1 -0
- package/package.json +7 -3
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { extractImports, extractExports, inferPurpose, detectFileIssues, } from '../../core/fileInspector.js';
|
|
4
|
+
import { detectWorkspaces } from '../../core/monorepo.js';
|
|
5
|
+
/**
|
|
6
|
+
* A repo is "Python-dominated" if it has a pyproject.toml OR setup.py AND
|
|
7
|
+
* either no node_modules directory or no package.json.
|
|
8
|
+
*/
|
|
9
|
+
export async function isPythonDominated(rootPath, files) {
|
|
10
|
+
const hasPython = files.some((f) => f.extension === '.py' || f.extension === '.pyw');
|
|
11
|
+
if (!hasPython)
|
|
12
|
+
return false;
|
|
13
|
+
const manifests = ['pyproject.toml', 'setup.py', 'setup.cfg'];
|
|
14
|
+
let hasPyManifest = false;
|
|
15
|
+
for (const m of manifests) {
|
|
16
|
+
try {
|
|
17
|
+
await fs.access(path.join(rootPath, m));
|
|
18
|
+
hasPyManifest = true;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// next
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!hasPyManifest)
|
|
26
|
+
return false;
|
|
27
|
+
try {
|
|
28
|
+
await fs.access(path.join(rootPath, 'package.json'));
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Resolve the `package` arg to a (file -> boolean) filter, or null when
|
|
37
|
+
* scoping wasn't requested.
|
|
38
|
+
*/
|
|
39
|
+
export async function resolvePackageFilter(rootPath, args) {
|
|
40
|
+
const name = typeof args.package === 'string' && args.package.length > 0 ? args.package : null;
|
|
41
|
+
if (!name)
|
|
42
|
+
return null;
|
|
43
|
+
const ws = await detectWorkspaces(rootPath);
|
|
44
|
+
const pkg = ws.packages.find((p) => p.name === name);
|
|
45
|
+
if (!pkg)
|
|
46
|
+
return () => false;
|
|
47
|
+
if (pkg.isRoot)
|
|
48
|
+
return () => true;
|
|
49
|
+
const prefix = pkg.relativePath + '/';
|
|
50
|
+
return (file) => file === pkg.relativePath || file.startsWith(prefix);
|
|
51
|
+
}
|
|
52
|
+
export const PACKAGE_ARG_SCHEMA = {
|
|
53
|
+
type: 'string',
|
|
54
|
+
description: 'Optional. Workspace package name (from projscan_workspaces) to scope results to one package only.',
|
|
55
|
+
};
|
|
56
|
+
/** Walk a DirectoryNode tree to find the node whose `path` equals targetPath. */
|
|
57
|
+
export function sliceTree(node, targetPath) {
|
|
58
|
+
if (node.path === targetPath)
|
|
59
|
+
return node;
|
|
60
|
+
for (const child of node.children) {
|
|
61
|
+
const hit = sliceTree(child, targetPath);
|
|
62
|
+
if (hit)
|
|
63
|
+
return hit;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
export function explainFile(absolutePath, content, rootPath) {
|
|
68
|
+
const lines = content.split('\n');
|
|
69
|
+
const imports = extractImports(content);
|
|
70
|
+
const exports = extractExports(content);
|
|
71
|
+
const purpose = inferPurpose(absolutePath, exports);
|
|
72
|
+
const potentialIssues = detectFileIssues(content, lines.length);
|
|
73
|
+
return {
|
|
74
|
+
filePath: path.relative(rootPath, absolutePath),
|
|
75
|
+
purpose,
|
|
76
|
+
imports,
|
|
77
|
+
exports,
|
|
78
|
+
potentialIssues,
|
|
79
|
+
lineCount: lines.length,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=_shared.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"_shared.js","sourceRoot":"","sources":["../../../src/mcp/tools/_shared.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
3
|
+
import { detectLanguages } from '../../core/languageDetector.js';
|
|
4
|
+
import { detectFrameworks } from '../../core/frameworkDetector.js';
|
|
5
|
+
import { analyzeDependencies } from '../../core/dependencyAnalyzer.js';
|
|
6
|
+
import { collectIssues } from '../../core/issueEngine.js';
|
|
7
|
+
import { calculateScore } from '../../utils/scoreCalculator.js';
|
|
8
|
+
import { emitProgress } from '../progress.js';
|
|
9
|
+
import { PACKAGE_ARG_SCHEMA, resolvePackageFilter } from './_shared.js';
|
|
10
|
+
export const analyzeTool = {
|
|
11
|
+
name: 'projscan_analyze',
|
|
12
|
+
description: 'Run a full projscan analysis of the project: languages, frameworks, dependencies, issues, and health score. Use this to understand a codebase before making changes.',
|
|
13
|
+
inputSchema: {
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
package: PACKAGE_ARG_SCHEMA,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
handler: async (args, rootPath) => {
|
|
20
|
+
emitProgress(0, 5, 'scanning repository');
|
|
21
|
+
const scan = await scanRepository(rootPath);
|
|
22
|
+
emitProgress(1, 5, 'detecting languages + frameworks');
|
|
23
|
+
const languages = detectLanguages(scan.files);
|
|
24
|
+
const frameworks = await detectFrameworks(rootPath, scan.files);
|
|
25
|
+
emitProgress(2, 5, 'analyzing dependencies');
|
|
26
|
+
const dependencies = await analyzeDependencies(rootPath);
|
|
27
|
+
emitProgress(3, 5, 'running analyzers');
|
|
28
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
29
|
+
const passes = await resolvePackageFilter(rootPath, args);
|
|
30
|
+
if (passes) {
|
|
31
|
+
issues = issues.filter((i) => {
|
|
32
|
+
const locs = i.locations ?? [];
|
|
33
|
+
if (locs.length === 0)
|
|
34
|
+
return false;
|
|
35
|
+
return locs.some((l) => l.file && passes(l.file));
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
emitProgress(4, 5, 'scoring');
|
|
39
|
+
const health = calculateScore(issues);
|
|
40
|
+
emitProgress(5, 5, 'done');
|
|
41
|
+
const report = {
|
|
42
|
+
projectName: path.basename(rootPath),
|
|
43
|
+
rootPath,
|
|
44
|
+
scan: { ...scan, files: [], directoryTree: scan.directoryTree },
|
|
45
|
+
languages,
|
|
46
|
+
frameworks,
|
|
47
|
+
dependencies,
|
|
48
|
+
issues,
|
|
49
|
+
timestamp: new Date().toISOString(),
|
|
50
|
+
health,
|
|
51
|
+
};
|
|
52
|
+
return report;
|
|
53
|
+
},
|
|
54
|
+
};
|
|
55
|
+
//# sourceMappingURL=analyze.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze.js","sourceRoot":"","sources":["../../../src/mcp/tools/analyze.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AACjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iCAAiC,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AACvE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAgB,MAAM,cAAc,CAAC;AAGtF,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,sKAAsK;IACxK,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,OAAO,EAAE,kBAAkB;SAC5B;KACF;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,qBAAqB,CAAC,CAAC;QAC1C,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC5C,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,kCAAkC,CAAC,CAAC;QACvD,MAAM,SAAS,GAAG,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC9C,MAAM,UAAU,GAAG,MAAM,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QAChE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,wBAAwB,CAAC,CAAC;QAC7C,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACzD,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACxC,IAAI,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC1D,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;gBAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACpC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;QACL,CAAC;QACD,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,SAAS,CAAC,CAAC;QAC9B,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACtC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAE3B,MAAM,MAAM,GAA+C;YACzD,WAAW,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;YACpC,QAAQ;YACR,IAAI,EAAE,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,aAAa,EAAE,IAAI,CAAC,aAAa,EAAE;YAC/D,SAAS;YACT,UAAU;YACV,YAAY;YACZ,MAAM;YACN,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,MAAM;SACP,CAAC;QACF,OAAO,MAAM,CAAC;IAChB,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { runAudit } from '../../core/auditRunner.js';
|
|
2
|
+
import { paginate, listChecksum, readPageParams } from '../pagination.js';
|
|
3
|
+
import { emitProgress } from '../progress.js';
|
|
4
|
+
export const auditTool = {
|
|
5
|
+
name: 'projscan_audit',
|
|
6
|
+
description: 'Run `npm audit` and return a normalized summary of vulnerabilities (critical / high / moderate / low / info). Requires package-lock.json. Supports cursor pagination on the findings array.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
cursor: { type: 'string', description: 'Opaque cursor from a previous response.' },
|
|
11
|
+
page_size: { type: 'number', description: 'Items per page (default 50).' },
|
|
12
|
+
max_tokens: { type: 'number', description: 'Cap response size.' },
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
handler: async (args, rootPath) => {
|
|
16
|
+
emitProgress(0, 2, 'running npm audit');
|
|
17
|
+
const report = await runAudit(rootPath);
|
|
18
|
+
if (!report.available)
|
|
19
|
+
return report;
|
|
20
|
+
emitProgress(1, 2, 'normalizing findings');
|
|
21
|
+
const page = paginate(report.findings, readPageParams(args), listChecksum(report.findings));
|
|
22
|
+
emitProgress(2, 2, 'done');
|
|
23
|
+
return {
|
|
24
|
+
available: true,
|
|
25
|
+
summary: report.summary,
|
|
26
|
+
findings: page.items,
|
|
27
|
+
total: page.total,
|
|
28
|
+
nextCursor: page.nextCursor,
|
|
29
|
+
};
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/mcp/tools/audit.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAG9C,MAAM,CAAC,MAAM,SAAS,GAAY;IAChC,IAAI,EAAE,gBAAgB;IACtB,WAAW,EACT,6LAA6L;IAC/L,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,yCAAyC,EAAE;YAClF,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,8BAA8B,EAAE;YAC1E,UAAU,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,EAAE;SAClE;KACF;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,mBAAmB,CAAC,CAAC;QACxC,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACxC,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,OAAO,MAAM,CAAC;QACrC,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,sBAAsB,CAAC,CAAC;QAC3C,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,QAAQ,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC5F,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3B,OAAO;YACL,SAAS,EAAE,IAAI;YACf,OAAO,EAAE,MAAM,CAAC,OAAO;YACvB,QAAQ,EAAE,IAAI,CAAC,KAAK;YACpB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
2
|
+
import { buildCodeGraph } from '../../core/codeGraph.js';
|
|
3
|
+
import { loadCachedGraph, saveCachedGraph } from '../../core/indexCache.js';
|
|
4
|
+
import { computeCoupling, filterCoupling } from '../../core/couplingAnalyzer.js';
|
|
5
|
+
import { detectWorkspaces, filterFilesByPackage } from '../../core/monorepo.js';
|
|
6
|
+
import { paginate, listChecksum, readPageParams } from '../pagination.js';
|
|
7
|
+
import { emitProgress } from '../progress.js';
|
|
8
|
+
export const couplingTool = {
|
|
9
|
+
name: 'projscan_coupling',
|
|
10
|
+
description: 'Per-file coupling metrics (fan-in, fan-out, instability) and circular-import cycles, derived from the AST code graph. Use `direction` to focus the result: "all" returns every file sorted by fan-in; "high_fan_in" / "high_fan_out" sort accordingly; "cycles_only" returns just the files participating in import cycles. Cycles are reported separately as strongly-connected components of size >= 2.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
file: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: "Optional. When set, the response includes only this file's coupling row (cycles list still returned in full).",
|
|
17
|
+
},
|
|
18
|
+
direction: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'Filter/sort applied to `files`. Default "all".',
|
|
21
|
+
enum: ['all', 'high_fan_in', 'high_fan_out', 'cycles_only'],
|
|
22
|
+
},
|
|
23
|
+
limit: { type: 'number', description: 'Max file rows returned (default 25, max 500).' },
|
|
24
|
+
max_tokens: { type: 'number', description: 'Cap the response to roughly this many tokens.' },
|
|
25
|
+
package: {
|
|
26
|
+
type: 'string',
|
|
27
|
+
description: 'Optional. Workspace package name (from projscan_workspaces) to scope coupling rows to one package only.',
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
handler: async (args, rootPath) => {
|
|
32
|
+
emitProgress(0, 3, 'building code graph');
|
|
33
|
+
const scan = await scanRepository(rootPath);
|
|
34
|
+
const cached = await loadCachedGraph(rootPath);
|
|
35
|
+
const graph = await buildCodeGraph(rootPath, scan.files, cached);
|
|
36
|
+
await saveCachedGraph(rootPath, graph);
|
|
37
|
+
emitProgress(1, 3, 'computing coupling + cycles');
|
|
38
|
+
const ws = await detectWorkspaces(rootPath);
|
|
39
|
+
const report = computeCoupling(graph, ws);
|
|
40
|
+
const direction = (typeof args.direction === 'string' ? args.direction : 'all');
|
|
41
|
+
const limit = Math.max(1, Math.min(500, typeof args.limit === 'number' ? args.limit : 25));
|
|
42
|
+
const file = typeof args.file === 'string' ? args.file : undefined;
|
|
43
|
+
let files = filterCoupling(report, direction);
|
|
44
|
+
if (file)
|
|
45
|
+
files = files.filter((f) => f.relativePath === file);
|
|
46
|
+
if (typeof args.package === 'string' && args.package.length > 0) {
|
|
47
|
+
const ws2 = await detectWorkspaces(rootPath);
|
|
48
|
+
const allowed = new Set(filterFilesByPackage(ws2, args.package, files.map((f) => f.relativePath)));
|
|
49
|
+
files = files.filter((f) => allowed.has(f.relativePath));
|
|
50
|
+
}
|
|
51
|
+
files = files.slice(0, limit);
|
|
52
|
+
emitProgress(2, 3, 'paginating');
|
|
53
|
+
const page = paginate(files, readPageParams(args), listChecksum(files));
|
|
54
|
+
emitProgress(3, 3, 'done');
|
|
55
|
+
return {
|
|
56
|
+
files: page.items,
|
|
57
|
+
cycles: report.cycles,
|
|
58
|
+
crossPackageEdges: report.crossPackageEdges,
|
|
59
|
+
totalFiles: report.totalFiles,
|
|
60
|
+
totalCycles: report.totalCycles,
|
|
61
|
+
totalCrossPackageEdges: report.totalCrossPackageEdges,
|
|
62
|
+
nextCursor: page.nextCursor,
|
|
63
|
+
total: page.total,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
//# sourceMappingURL=coupling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coupling.js","sourceRoot":"","sources":["../../../src/mcp/tools/coupling.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
2
|
+
import { collectIssues } from '../../core/issueEngine.js';
|
|
3
|
+
import { analyzeHotspots } from '../../core/hotspotAnalyzer.js';
|
|
4
|
+
import { parseCoverage, coverageMap } from '../../core/coverageParser.js';
|
|
5
|
+
import { joinCoverageWithHotspots } from '../../core/coverageJoin.js';
|
|
6
|
+
import { paginate, listChecksum, readPageParams } from '../pagination.js';
|
|
7
|
+
import { PACKAGE_ARG_SCHEMA, resolvePackageFilter } from './_shared.js';
|
|
8
|
+
export const coverageTool = {
|
|
9
|
+
name: 'projscan_coverage',
|
|
10
|
+
description: 'Join test coverage with hotspot risk. Returns files ranked by "risk × uncovered fraction" - the scariest untested files. Requires a coverage file at coverage/lcov.info, coverage/coverage-final.json, or coverage/coverage-summary.json.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
limit: {
|
|
15
|
+
type: 'number',
|
|
16
|
+
description: 'How many entries to return (default: 30, max: 200).',
|
|
17
|
+
},
|
|
18
|
+
max_tokens: {
|
|
19
|
+
type: 'number',
|
|
20
|
+
description: 'Cap the response size to roughly this many tokens (~4 chars/token). Truncates the entries array to fit.',
|
|
21
|
+
},
|
|
22
|
+
package: PACKAGE_ARG_SCHEMA,
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
handler: async (args, rootPath) => {
|
|
26
|
+
const coverage = await parseCoverage(rootPath);
|
|
27
|
+
const scan = await scanRepository(rootPath);
|
|
28
|
+
const issues = await collectIssues(rootPath, scan.files);
|
|
29
|
+
const rawLimit = typeof args.limit === 'number' ? args.limit : 200;
|
|
30
|
+
const limit = Math.max(1, Math.min(500, rawLimit));
|
|
31
|
+
const hotspots = await analyzeHotspots(rootPath, scan.files, issues, {
|
|
32
|
+
limit,
|
|
33
|
+
coverage: coverage.available ? coverageMap(coverage) : undefined,
|
|
34
|
+
});
|
|
35
|
+
const joined = joinCoverageWithHotspots(hotspots, coverage);
|
|
36
|
+
if (!joined.available)
|
|
37
|
+
return joined;
|
|
38
|
+
const passes = await resolvePackageFilter(rootPath, args);
|
|
39
|
+
const filteredEntries = passes
|
|
40
|
+
? joined.entries.filter((e) => passes(e.relativePath))
|
|
41
|
+
: joined.entries;
|
|
42
|
+
const page = paginate(filteredEntries, readPageParams(args), listChecksum(filteredEntries));
|
|
43
|
+
return {
|
|
44
|
+
available: true,
|
|
45
|
+
coverageSource: joined.coverageSource,
|
|
46
|
+
coverageSourceFile: joined.coverageSourceFile,
|
|
47
|
+
entries: page.items,
|
|
48
|
+
total: page.total,
|
|
49
|
+
nextCursor: page.nextCursor,
|
|
50
|
+
};
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=coverage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"coverage.js","sourceRoot":"","sources":["../../../src/mcp/tools/coverage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,4BAA4B,CAAC;AACtE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAgB,MAAM,cAAc,CAAC;AAEtF,MAAM,CAAC,MAAM,YAAY,GAAY;IACnC,IAAI,EAAE,mBAAmB;IACzB,WAAW,EACT,2OAA2O;IAC7O,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,KAAK,EAAE;gBACL,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,qDAAqD;aACnE;YACD,UAAU,EAAE;gBACV,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,yGAAyG;aACvH;YACD,OAAO,EAAE,kBAAkB;SAC5B;KACF;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChC,MAAM,QAAQ,GAAG,MAAM,aAAa,CAAC,QAAQ,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC5C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACzD,MAAM,QAAQ,GAAG,OAAO,IAAI,CAAC,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC;QACnE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;QACnD,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE;YACnE,KAAK;YACL,QAAQ,EAAE,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,SAAS;SACjE,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QAC5D,IAAI,CAAC,MAAM,CAAC,SAAS;YAAE,OAAO,MAAM,CAAC;QACrC,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC1D,MAAM,eAAe,GAAG,MAAM;YAC5B,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC;YACtD,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACnB,MAAM,IAAI,GAAG,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,IAAI,CAAC,EAAE,YAAY,CAAC,eAAe,CAAC,CAAC,CAAC;QAC5F,OAAO;YACL,SAAS,EAAE,IAAI;YACf,cAAc,EAAE,MAAM,CAAC,cAAc;YACrC,kBAAkB,EAAE,MAAM,CAAC,kBAAkB;YAC7C,OAAO,EAAE,IAAI,CAAC,KAAK;YACnB,KAAK,EAAE,IAAI,CAAC,KAAK;YACjB,UAAU,EAAE,IAAI,CAAC,UAAU;SAC5B,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { analyzeDependencies } from '../../core/dependencyAnalyzer.js';
|
|
2
|
+
export const dependenciesTool = {
|
|
3
|
+
name: 'projscan_dependencies',
|
|
4
|
+
description: 'Analyze package.json dependencies and return counts and risks (deprecated packages, wildcard versions, etc.).',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {},
|
|
8
|
+
},
|
|
9
|
+
handler: async (_args, rootPath) => {
|
|
10
|
+
const report = await analyzeDependencies(rootPath);
|
|
11
|
+
if (!report)
|
|
12
|
+
return { available: false, reason: 'No package.json found' };
|
|
13
|
+
return { available: true, ...report };
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=dependencies.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependencies.js","sourceRoot":"","sources":["../../../src/mcp/tools/dependencies.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,kCAAkC,CAAC;AAGvE,MAAM,CAAC,MAAM,gBAAgB,GAAY;IACvC,IAAI,EAAE,uBAAuB;IAC7B,WAAW,EAAE,+GAA+G;IAC5H,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE,EAAE;KACf;IACD,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE;QACjC,MAAM,MAAM,GAAG,MAAM,mBAAmB,CAAC,QAAQ,CAAC,CAAC;QACnD,IAAI,CAAC,MAAM;YAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,CAAC;QAC1E,OAAO,EAAE,SAAS,EAAE,IAAI,EAAE,GAAG,MAAM,EAAE,CAAC;IACxC,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
2
|
+
import { collectIssues } from '../../core/issueEngine.js';
|
|
3
|
+
import { calculateScore } from '../../utils/scoreCalculator.js';
|
|
4
|
+
import { PACKAGE_ARG_SCHEMA, resolvePackageFilter } from './_shared.js';
|
|
5
|
+
export const doctorTool = {
|
|
6
|
+
name: 'projscan_doctor',
|
|
7
|
+
description: 'Run a health check on the project. Returns a 0-100 score, letter grade, and the list of issues (linting, formatting, tests, security, architecture).',
|
|
8
|
+
inputSchema: {
|
|
9
|
+
type: 'object',
|
|
10
|
+
properties: {
|
|
11
|
+
package: PACKAGE_ARG_SCHEMA,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
handler: async (args, rootPath) => {
|
|
15
|
+
const scan = await scanRepository(rootPath);
|
|
16
|
+
let issues = await collectIssues(rootPath, scan.files);
|
|
17
|
+
const passes = await resolvePackageFilter(rootPath, args);
|
|
18
|
+
if (passes) {
|
|
19
|
+
issues = issues.filter((i) => {
|
|
20
|
+
const locs = i.locations ?? [];
|
|
21
|
+
if (locs.length === 0)
|
|
22
|
+
return false;
|
|
23
|
+
return locs.some((l) => l.file && passes(l.file));
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
const health = calculateScore(issues);
|
|
27
|
+
return { health, issues };
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=doctor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../../src/mcp/tools/doctor.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,OAAO,EAAE,cAAc,EAAE,MAAM,gCAAgC,CAAC;AAChE,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAgB,MAAM,cAAc,CAAC;AAEtF,MAAM,CAAC,MAAM,UAAU,GAAY;IACjC,IAAI,EAAE,iBAAiB;IACvB,WAAW,EACT,sJAAsJ;IACxJ,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,OAAO,EAAE,kBAAkB;SAC5B;KACF;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACvD,MAAM,MAAM,GAAG,MAAM,oBAAoB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC;QAC1D,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE;gBAC3B,MAAM,IAAI,GAAG,CAAC,CAAC,SAAS,IAAI,EAAE,CAAC;gBAC/B,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAC;gBACpC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;YACpD,CAAC,CAAC,CAAC;QACL,CAAC;QACD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;QACtC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;IAC5B,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { explainFile } from './_shared.js';
|
|
4
|
+
export const explainTool = {
|
|
5
|
+
name: 'projscan_explain',
|
|
6
|
+
description: 'Explain a single file: purpose, imports, exports, and potential issues. Useful for understanding unfamiliar code before editing.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
file: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'Path to the file relative to the project root.',
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
required: ['file'],
|
|
16
|
+
},
|
|
17
|
+
handler: async (args, rootPath) => {
|
|
18
|
+
const rel = typeof args.file === 'string' ? args.file : '';
|
|
19
|
+
if (!rel)
|
|
20
|
+
throw new Error('file argument is required');
|
|
21
|
+
const absolutePath = path.resolve(rootPath, rel);
|
|
22
|
+
const resolvedRoot = path.resolve(rootPath);
|
|
23
|
+
if (!absolutePath.startsWith(resolvedRoot + path.sep) && absolutePath !== resolvedRoot) {
|
|
24
|
+
throw new Error('file must be inside the project root');
|
|
25
|
+
}
|
|
26
|
+
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
27
|
+
return explainFile(absolutePath, content, rootPath);
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=explain.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"explain.js","sourceRoot":"","sources":["../../../src/mcp/tools/explain.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAClC,OAAO,EAAE,WAAW,EAAgB,MAAM,cAAc,CAAC;AAEzD,MAAM,CAAC,MAAM,WAAW,GAAY;IAClC,IAAI,EAAE,kBAAkB;IACxB,WAAW,EACT,kIAAkI;IACpI,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,gDAAgD;aAC9D;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACnB;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChC,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACvD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACjD,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC5C,IAAI,CAAC,YAAY,CAAC,UAAU,CAAC,YAAY,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,YAAY,KAAK,YAAY,EAAE,CAAC;YACvF,MAAM,IAAI,KAAK,CAAC,sCAAsC,CAAC,CAAC;QAC1D,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,EAAE,CAAC,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACzD,OAAO,WAAW,CAAC,YAAY,EAAE,OAAO,EAAE,QAAQ,CAAC,CAAC;IACtD,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { inspectFile } from '../../core/fileInspector.js';
|
|
2
|
+
export const fileTool = {
|
|
3
|
+
name: 'projscan_file',
|
|
4
|
+
description: 'Drill into a single file: purpose, imports, exports, churn/risk/ownership, related health issues, AST cyclomatic complexity, and coupling (fan-in / fan-out). Use this after projscan_hotspots when deciding how to approach a specific risky file.',
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: 'object',
|
|
7
|
+
properties: {
|
|
8
|
+
file: {
|
|
9
|
+
type: 'string',
|
|
10
|
+
description: 'Path to the file relative to the project root.',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
required: ['file'],
|
|
14
|
+
},
|
|
15
|
+
handler: async (args, rootPath) => {
|
|
16
|
+
const rel = typeof args.file === 'string' ? args.file : '';
|
|
17
|
+
if (!rel)
|
|
18
|
+
throw new Error('file argument is required');
|
|
19
|
+
return await inspectFile(rootPath, rel);
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
//# sourceMappingURL=file.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file.js","sourceRoot":"","sources":["../../../src/mcp/tools/file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,6BAA6B,CAAC;AAG1D,MAAM,CAAC,MAAM,QAAQ,GAAY;IAC/B,IAAI,EAAE,eAAe;IACrB,WAAW,EACT,qPAAqP;IACvP,WAAW,EAAE;QACX,IAAI,EAAE,QAAQ;QACd,UAAU,EAAE;YACV,IAAI,EAAE;gBACJ,IAAI,EAAE,QAAQ;gBACd,WAAW,EAAE,gDAAgD;aAC9D;SACF;QACD,QAAQ,EAAE,CAAC,MAAM,CAAC;KACnB;IACD,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE;QAChC,MAAM,GAAG,GAAG,OAAO,IAAI,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QAC3D,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QACvD,OAAO,MAAM,WAAW,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;IAC1C,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
2
|
+
import { buildCodeGraph, filesImportingFile, filesImportingPackage, filesDefiningSymbol, exportsOf, importsOf, } from '../../core/codeGraph.js';
|
|
3
|
+
import { loadCachedGraph, saveCachedGraph } from '../../core/indexCache.js';
|
|
4
|
+
export const graphTool = {
|
|
5
|
+
name: 'projscan_graph',
|
|
6
|
+
description: 'Query the AST-based code graph directly. Returns imports, exports, importers, or symbol definitions for a file or symbol. Agents should prefer this over analyze/doctor/explain for targeted structural questions - it is much cheaper and more accurate.',
|
|
7
|
+
inputSchema: {
|
|
8
|
+
type: 'object',
|
|
9
|
+
properties: {
|
|
10
|
+
file: {
|
|
11
|
+
type: 'string',
|
|
12
|
+
description: 'File path (relative to project root) to query.',
|
|
13
|
+
},
|
|
14
|
+
symbol: {
|
|
15
|
+
type: 'string',
|
|
16
|
+
description: 'Symbol name to query (e.g. a function or class). Use instead of `file` to find where a symbol is defined.',
|
|
17
|
+
},
|
|
18
|
+
direction: {
|
|
19
|
+
type: 'string',
|
|
20
|
+
description: 'What to return: "imports" (what the file imports), "exports" (what the file exports), "importers" (who imports the file), "symbol_defs" (files defining the symbol), "package_importers" (files importing a package by name).',
|
|
21
|
+
enum: ['imports', 'exports', 'importers', 'symbol_defs', 'package_importers'],
|
|
22
|
+
},
|
|
23
|
+
limit: { type: 'number', description: 'Max entries returned (default 50).' },
|
|
24
|
+
max_tokens: { type: 'number', description: 'Cap the response to roughly this many tokens.' },
|
|
25
|
+
},
|
|
26
|
+
required: ['direction'],
|
|
27
|
+
},
|
|
28
|
+
handler: async (args, rootPath) => {
|
|
29
|
+
const scan = await scanRepository(rootPath);
|
|
30
|
+
const cached = await loadCachedGraph(rootPath);
|
|
31
|
+
const graph = await buildCodeGraph(rootPath, scan.files, cached);
|
|
32
|
+
await saveCachedGraph(rootPath, graph);
|
|
33
|
+
const direction = String(args.direction);
|
|
34
|
+
const file = typeof args.file === 'string' ? args.file : undefined;
|
|
35
|
+
const symbol = typeof args.symbol === 'string' ? args.symbol : undefined;
|
|
36
|
+
const limit = Math.max(1, Math.min(500, typeof args.limit === 'number' ? args.limit : 50));
|
|
37
|
+
switch (direction) {
|
|
38
|
+
case 'imports': {
|
|
39
|
+
if (!file)
|
|
40
|
+
throw new Error('file argument is required for direction=imports');
|
|
41
|
+
return { file, imports: importsOf(graph, file).slice(0, limit) };
|
|
42
|
+
}
|
|
43
|
+
case 'exports': {
|
|
44
|
+
if (!file)
|
|
45
|
+
throw new Error('file argument is required for direction=exports');
|
|
46
|
+
return { file, exports: exportsOf(graph, file).slice(0, limit) };
|
|
47
|
+
}
|
|
48
|
+
case 'importers': {
|
|
49
|
+
if (!file)
|
|
50
|
+
throw new Error('file argument is required for direction=importers');
|
|
51
|
+
return { file, importers: filesImportingFile(graph, file).slice(0, limit) };
|
|
52
|
+
}
|
|
53
|
+
case 'symbol_defs': {
|
|
54
|
+
if (!symbol)
|
|
55
|
+
throw new Error('symbol argument is required for direction=symbol_defs');
|
|
56
|
+
return { symbol, definedIn: filesDefiningSymbol(graph, symbol).slice(0, limit) };
|
|
57
|
+
}
|
|
58
|
+
case 'package_importers': {
|
|
59
|
+
const pkg = symbol ?? file;
|
|
60
|
+
if (!pkg)
|
|
61
|
+
throw new Error('symbol (or file) argument is required for direction=package_importers');
|
|
62
|
+
return { package: pkg, importers: filesImportingPackage(graph, pkg).slice(0, limit) };
|
|
63
|
+
}
|
|
64
|
+
default:
|
|
65
|
+
throw new Error(`unknown direction: ${direction}`);
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"graph.js","sourceRoot":"","sources":["../../../src/mcp/tools/graph.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { scanRepository } from '../../core/repositoryScanner.js';
|
|
2
|
+
import { collectIssues } from '../../core/issueEngine.js';
|
|
3
|
+
import { analyzeHotspots } from '../../core/hotspotAnalyzer.js';
|
|
4
|
+
import { buildCodeGraph } from '../../core/codeGraph.js';
|
|
5
|
+
import { loadCachedGraph, saveCachedGraph } from '../../core/indexCache.js';
|
|
6
|
+
import { detectWorkspaces, filterFilesByPackage } from '../../core/monorepo.js';
|
|
7
|
+
import { paginate, listChecksum, readPageParams } from '../pagination.js';
|
|
8
|
+
import { emitProgress } from '../progress.js';
|
|
9
|
+
export const hotspotsTool = {
|
|
10
|
+
name: 'projscan_hotspots',
|
|
11
|
+
description: 'Rank files by risk using git churn × AST cyclomatic complexity × open issues. Returns the most dangerous files to touch. Each hotspot includes `cyclomaticComplexity` (null for non-AST languages, where line count is used as fallback). Supports cursor-based pagination: pass the `nextCursor` from a previous response back as `cursor` to fetch the next page.',
|
|
12
|
+
inputSchema: {
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
limit: {
|
|
16
|
+
type: 'number',
|
|
17
|
+
description: 'Cap on total hotspots ranked (default 100). For paging the returned set, use `page_size` + `cursor` instead.',
|
|
18
|
+
},
|
|
19
|
+
since: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
description: 'Git history window. Examples: "12 months ago", "2024-01-01". Default: "12 months ago".',
|
|
22
|
+
},
|
|
23
|
+
cursor: { type: 'string', description: 'Opaque cursor from a previous response. Omit for the first page.' },
|
|
24
|
+
page_size: { type: 'number', description: 'Items per page (default 50, max 500).' },
|
|
25
|
+
max_tokens: { type: 'number', description: 'Cap response to roughly this many tokens.' },
|
|
26
|
+
package: {
|
|
27
|
+
type: 'string',
|
|
28
|
+
description: 'Optional. Workspace package name (from projscan_workspaces) to scope hotspots to one package only.',
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
handler: async (args, rootPath) => {
|
|
33
|
+
emitProgress(0, 5, 'scanning repository');
|
|
34
|
+
const scan = await scanRepository(rootPath);
|
|
35
|
+
emitProgress(1, 5, 'collecting issues');
|
|
36
|
+
const issues = await collectIssues(rootPath, scan.files);
|
|
37
|
+
const limit = typeof args.limit === 'number' ? args.limit : 100;
|
|
38
|
+
const since = typeof args.since === 'string' ? args.since : undefined;
|
|
39
|
+
emitProgress(2, 5, 'building code graph');
|
|
40
|
+
const cached = await loadCachedGraph(rootPath);
|
|
41
|
+
const graph = await buildCodeGraph(rootPath, scan.files, cached);
|
|
42
|
+
await saveCachedGraph(rootPath, graph);
|
|
43
|
+
emitProgress(3, 5, 'analyzing git churn + risk');
|
|
44
|
+
const report = await analyzeHotspots(rootPath, scan.files, issues, { limit, since, graph });
|
|
45
|
+
if (typeof args.package === 'string' && args.package.length > 0) {
|
|
46
|
+
const ws = await detectWorkspaces(rootPath);
|
|
47
|
+
const allowed = new Set(filterFilesByPackage(ws, args.package, report.hotspots.map((h) => h.relativePath)));
|
|
48
|
+
report.hotspots = report.hotspots.filter((h) => allowed.has(h.relativePath));
|
|
49
|
+
}
|
|
50
|
+
emitProgress(4, 5, 'paginating');
|
|
51
|
+
const page = paginate(report.hotspots, readPageParams(args), listChecksum(report.hotspots));
|
|
52
|
+
emitProgress(5, 5, 'done');
|
|
53
|
+
return {
|
|
54
|
+
available: report.available,
|
|
55
|
+
reason: report.reason,
|
|
56
|
+
window: report.window,
|
|
57
|
+
hotspots: page.items,
|
|
58
|
+
totalFilesRanked: report.totalFilesRanked,
|
|
59
|
+
nextCursor: page.nextCursor,
|
|
60
|
+
total: page.total,
|
|
61
|
+
};
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
//# sourceMappingURL=hotspots.js.map
|