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
package/dist/mcp/tools.js
CHANGED
|
@@ -1,595 +1,46 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import fs from 'node:fs/promises';
|
|
3
|
-
import { scanRepository } from '../core/repositoryScanner.js';
|
|
4
|
-
import { detectLanguages } from '../core/languageDetector.js';
|
|
5
|
-
import { detectFrameworks } from '../core/frameworkDetector.js';
|
|
6
|
-
import { analyzeDependencies } from '../core/dependencyAnalyzer.js';
|
|
7
|
-
import { collectIssues } from '../core/issueEngine.js';
|
|
8
|
-
import { analyzeHotspots } from '../core/hotspotAnalyzer.js';
|
|
9
|
-
import { detectOutdated } from '../core/outdatedDetector.js';
|
|
10
|
-
import { runAudit } from '../core/auditRunner.js';
|
|
11
|
-
import { previewUpgrade } from '../core/upgradePreview.js';
|
|
12
|
-
import { parseCoverage, coverageMap } from '../core/coverageParser.js';
|
|
13
|
-
import { joinCoverageWithHotspots } from '../core/coverageJoin.js';
|
|
14
|
-
import { buildCodeGraph, filesImportingFile, filesImportingPackage, filesDefiningSymbol, exportsOf, importsOf, } from '../core/codeGraph.js';
|
|
15
|
-
import { loadCachedGraph, saveCachedGraph } from '../core/indexCache.js';
|
|
16
|
-
import { buildSearchIndex, search as searchIndex, attachExcerpts, expandQuery } from '../core/searchIndex.js';
|
|
17
|
-
import { buildSemanticIndex, semanticSearch, reciprocalRankFusion, } from '../core/semanticSearch.js';
|
|
18
|
-
import { isSemanticAvailable } from '../core/embeddings.js';
|
|
19
|
-
import { paginate, listChecksum, readPageParams } from './pagination.js';
|
|
20
|
-
import { emitProgress } from './progress.js';
|
|
21
|
-
import { inspectFile, extractImports, extractExports, inferPurpose, detectFileIssues, } from '../core/fileInspector.js';
|
|
22
|
-
import { calculateScore } from '../utils/scoreCalculator.js';
|
|
23
1
|
/**
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
2
|
+
* MCP tool registry - barrel that aggregates the per-tool modules under
|
|
3
|
+
* `src/mcp/tools/`. New tools live in their own file under that directory and
|
|
4
|
+
* are added to the `tools` array below.
|
|
5
|
+
*
|
|
6
|
+
* The shape exposed here (`getToolDefinitions`, `getToolHandler`,
|
|
7
|
+
* `McpToolHandler`) is consumed by `src/mcp/server.ts`. Re-export `McpTool`
|
|
8
|
+
* + `McpToolHandler` so external callers don't need to know about the
|
|
9
|
+
* directory split.
|
|
28
10
|
*/
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (!hasPyManifest)
|
|
46
|
-
return false;
|
|
47
|
-
try {
|
|
48
|
-
await fs.access(path.join(rootPath, 'package.json'));
|
|
49
|
-
return false; // has JS manifest, not Python-dominated
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
return true;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
11
|
+
import { analyzeTool } from './tools/analyze.js';
|
|
12
|
+
import { doctorTool } from './tools/doctor.js';
|
|
13
|
+
import { hotspotsTool } from './tools/hotspots.js';
|
|
14
|
+
import { explainTool } from './tools/explain.js';
|
|
15
|
+
import { fileTool } from './tools/file.js';
|
|
16
|
+
import { structureTool } from './tools/structure.js';
|
|
17
|
+
import { dependenciesTool } from './tools/dependencies.js';
|
|
18
|
+
import { outdatedTool } from './tools/outdated.js';
|
|
19
|
+
import { auditTool } from './tools/audit.js';
|
|
20
|
+
import { upgradeTool } from './tools/upgrade.js';
|
|
21
|
+
import { coverageTool } from './tools/coverage.js';
|
|
22
|
+
import { graphTool } from './tools/graph.js';
|
|
23
|
+
import { couplingTool } from './tools/coupling.js';
|
|
24
|
+
import { workspacesTool } from './tools/workspaces.js';
|
|
25
|
+
import { prDiffTool } from './tools/prDiff.js';
|
|
26
|
+
import { searchTool } from './tools/search.js';
|
|
55
27
|
const tools = [
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const issues = await collectIssues(rootPath, scan.files);
|
|
73
|
-
emitProgress(4, 5, 'scoring');
|
|
74
|
-
const health = calculateScore(issues);
|
|
75
|
-
emitProgress(5, 5, 'done');
|
|
76
|
-
const report = {
|
|
77
|
-
projectName: path.basename(rootPath),
|
|
78
|
-
rootPath,
|
|
79
|
-
scan: { ...scan, files: [], directoryTree: scan.directoryTree },
|
|
80
|
-
languages,
|
|
81
|
-
frameworks,
|
|
82
|
-
dependencies,
|
|
83
|
-
issues,
|
|
84
|
-
timestamp: new Date().toISOString(),
|
|
85
|
-
health,
|
|
86
|
-
};
|
|
87
|
-
return report;
|
|
88
|
-
},
|
|
89
|
-
},
|
|
90
|
-
{
|
|
91
|
-
name: 'projscan_doctor',
|
|
92
|
-
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).',
|
|
93
|
-
inputSchema: {
|
|
94
|
-
type: 'object',
|
|
95
|
-
properties: {},
|
|
96
|
-
},
|
|
97
|
-
handler: async (_args, rootPath) => {
|
|
98
|
-
const scan = await scanRepository(rootPath);
|
|
99
|
-
const issues = await collectIssues(rootPath, scan.files);
|
|
100
|
-
const health = calculateScore(issues);
|
|
101
|
-
return {
|
|
102
|
-
health,
|
|
103
|
-
issues,
|
|
104
|
-
};
|
|
105
|
-
},
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
name: 'projscan_hotspots',
|
|
109
|
-
description: 'Rank files by risk using git churn × complexity × open issues. Returns the most dangerous files to touch. Supports cursor-based pagination: pass the `nextCursor` from a previous response back as `cursor` to fetch the next page.',
|
|
110
|
-
inputSchema: {
|
|
111
|
-
type: 'object',
|
|
112
|
-
properties: {
|
|
113
|
-
limit: {
|
|
114
|
-
type: 'number',
|
|
115
|
-
description: 'Cap on total hotspots ranked (default 100). For paging the returned set, use `page_size` + `cursor` instead.',
|
|
116
|
-
},
|
|
117
|
-
since: {
|
|
118
|
-
type: 'string',
|
|
119
|
-
description: 'Git history window. Examples: "12 months ago", "2024-01-01". Default: "12 months ago".',
|
|
120
|
-
},
|
|
121
|
-
cursor: {
|
|
122
|
-
type: 'string',
|
|
123
|
-
description: 'Opaque cursor from a previous response. Omit for the first page.',
|
|
124
|
-
},
|
|
125
|
-
page_size: {
|
|
126
|
-
type: 'number',
|
|
127
|
-
description: 'Items per page (default 50, max 500).',
|
|
128
|
-
},
|
|
129
|
-
max_tokens: {
|
|
130
|
-
type: 'number',
|
|
131
|
-
description: 'Cap response to roughly this many tokens.',
|
|
132
|
-
},
|
|
133
|
-
},
|
|
134
|
-
},
|
|
135
|
-
handler: async (args, rootPath) => {
|
|
136
|
-
emitProgress(0, 4, 'scanning repository');
|
|
137
|
-
const scan = await scanRepository(rootPath);
|
|
138
|
-
emitProgress(1, 4, 'collecting issues');
|
|
139
|
-
const issues = await collectIssues(rootPath, scan.files);
|
|
140
|
-
const limit = typeof args.limit === 'number' ? args.limit : 100;
|
|
141
|
-
const since = typeof args.since === 'string' ? args.since : undefined;
|
|
142
|
-
emitProgress(2, 4, 'analyzing git churn + risk');
|
|
143
|
-
const report = await analyzeHotspots(rootPath, scan.files, issues, { limit, since });
|
|
144
|
-
emitProgress(3, 4, 'paginating');
|
|
145
|
-
const page = paginate(report.hotspots, readPageParams(args), listChecksum(report.hotspots));
|
|
146
|
-
emitProgress(4, 4, 'done');
|
|
147
|
-
return {
|
|
148
|
-
available: report.available,
|
|
149
|
-
reason: report.reason,
|
|
150
|
-
window: report.window,
|
|
151
|
-
hotspots: page.items,
|
|
152
|
-
totalFilesRanked: report.totalFilesRanked,
|
|
153
|
-
nextCursor: page.nextCursor,
|
|
154
|
-
total: page.total,
|
|
155
|
-
};
|
|
156
|
-
},
|
|
157
|
-
},
|
|
158
|
-
{
|
|
159
|
-
name: 'projscan_explain',
|
|
160
|
-
description: 'Explain a single file: purpose, imports, exports, and potential issues. Useful for understanding unfamiliar code before editing.',
|
|
161
|
-
inputSchema: {
|
|
162
|
-
type: 'object',
|
|
163
|
-
properties: {
|
|
164
|
-
file: {
|
|
165
|
-
type: 'string',
|
|
166
|
-
description: 'Path to the file relative to the project root.',
|
|
167
|
-
},
|
|
168
|
-
},
|
|
169
|
-
required: ['file'],
|
|
170
|
-
},
|
|
171
|
-
handler: async (args, rootPath) => {
|
|
172
|
-
const rel = typeof args.file === 'string' ? args.file : '';
|
|
173
|
-
if (!rel)
|
|
174
|
-
throw new Error('file argument is required');
|
|
175
|
-
const absolutePath = path.resolve(rootPath, rel);
|
|
176
|
-
const resolvedRoot = path.resolve(rootPath);
|
|
177
|
-
if (!absolutePath.startsWith(resolvedRoot + path.sep) && absolutePath !== resolvedRoot) {
|
|
178
|
-
throw new Error('file must be inside the project root');
|
|
179
|
-
}
|
|
180
|
-
const content = await fs.readFile(absolutePath, 'utf-8');
|
|
181
|
-
return explainFile(absolutePath, content, rootPath);
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
{
|
|
185
|
-
name: 'projscan_file',
|
|
186
|
-
description: 'Drill into a single file: purpose, imports, exports, AND its churn/risk/ownership plus any related health issues. Use this after projscan_hotspots when deciding how to approach a specific risky file.',
|
|
187
|
-
inputSchema: {
|
|
188
|
-
type: 'object',
|
|
189
|
-
properties: {
|
|
190
|
-
file: {
|
|
191
|
-
type: 'string',
|
|
192
|
-
description: 'Path to the file relative to the project root.',
|
|
193
|
-
},
|
|
194
|
-
},
|
|
195
|
-
required: ['file'],
|
|
196
|
-
},
|
|
197
|
-
handler: async (args, rootPath) => {
|
|
198
|
-
const rel = typeof args.file === 'string' ? args.file : '';
|
|
199
|
-
if (!rel)
|
|
200
|
-
throw new Error('file argument is required');
|
|
201
|
-
return await inspectFile(rootPath, rel);
|
|
202
|
-
},
|
|
203
|
-
},
|
|
204
|
-
{
|
|
205
|
-
name: 'projscan_structure',
|
|
206
|
-
description: 'Return the project directory tree with file counts.',
|
|
207
|
-
inputSchema: {
|
|
208
|
-
type: 'object',
|
|
209
|
-
properties: {},
|
|
210
|
-
},
|
|
211
|
-
handler: async (_args, rootPath) => {
|
|
212
|
-
const scan = await scanRepository(rootPath);
|
|
213
|
-
return { structure: scan.directoryTree, totalFiles: scan.totalFiles };
|
|
214
|
-
},
|
|
215
|
-
},
|
|
216
|
-
{
|
|
217
|
-
name: 'projscan_dependencies',
|
|
218
|
-
description: 'Analyze package.json dependencies and return counts and risks (deprecated packages, wildcard versions, etc.).',
|
|
219
|
-
inputSchema: {
|
|
220
|
-
type: 'object',
|
|
221
|
-
properties: {},
|
|
222
|
-
},
|
|
223
|
-
handler: async (_args, rootPath) => {
|
|
224
|
-
const report = await analyzeDependencies(rootPath);
|
|
225
|
-
if (!report)
|
|
226
|
-
return { available: false, reason: 'No package.json found' };
|
|
227
|
-
return { available: true, ...report };
|
|
228
|
-
},
|
|
229
|
-
},
|
|
230
|
-
{
|
|
231
|
-
name: 'projscan_outdated',
|
|
232
|
-
description: 'Compare declared vs installed versions of every package. Reports drift (patch/minor/major). Offline - does not hit the npm registry. Supports cursor pagination.',
|
|
233
|
-
inputSchema: {
|
|
234
|
-
type: 'object',
|
|
235
|
-
properties: {
|
|
236
|
-
cursor: { type: 'string', description: 'Opaque cursor from a previous response.' },
|
|
237
|
-
page_size: { type: 'number', description: 'Items per page (default 50).' },
|
|
238
|
-
max_tokens: { type: 'number', description: 'Cap response size.' },
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
handler: async (args, rootPath) => {
|
|
242
|
-
const report = await detectOutdated(rootPath);
|
|
243
|
-
if (!report.available)
|
|
244
|
-
return report;
|
|
245
|
-
const page = paginate(report.packages, readPageParams(args), listChecksum(report.packages));
|
|
246
|
-
return {
|
|
247
|
-
available: true,
|
|
248
|
-
totalPackages: report.totalPackages,
|
|
249
|
-
packages: page.items,
|
|
250
|
-
total: page.total,
|
|
251
|
-
nextCursor: page.nextCursor,
|
|
252
|
-
};
|
|
253
|
-
},
|
|
254
|
-
},
|
|
255
|
-
{
|
|
256
|
-
name: 'projscan_audit',
|
|
257
|
-
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.',
|
|
258
|
-
inputSchema: {
|
|
259
|
-
type: 'object',
|
|
260
|
-
properties: {
|
|
261
|
-
cursor: { type: 'string', description: 'Opaque cursor from a previous response.' },
|
|
262
|
-
page_size: { type: 'number', description: 'Items per page (default 50).' },
|
|
263
|
-
max_tokens: { type: 'number', description: 'Cap response size.' },
|
|
264
|
-
},
|
|
265
|
-
},
|
|
266
|
-
handler: async (args, rootPath) => {
|
|
267
|
-
emitProgress(0, 2, 'running npm audit');
|
|
268
|
-
const report = await runAudit(rootPath);
|
|
269
|
-
if (!report.available)
|
|
270
|
-
return report;
|
|
271
|
-
emitProgress(1, 2, 'normalizing findings');
|
|
272
|
-
const page = paginate(report.findings, readPageParams(args), listChecksum(report.findings));
|
|
273
|
-
emitProgress(2, 2, 'done');
|
|
274
|
-
return {
|
|
275
|
-
available: true,
|
|
276
|
-
summary: report.summary,
|
|
277
|
-
findings: page.items,
|
|
278
|
-
total: page.total,
|
|
279
|
-
nextCursor: page.nextCursor,
|
|
280
|
-
};
|
|
281
|
-
},
|
|
282
|
-
},
|
|
283
|
-
{
|
|
284
|
-
name: 'projscan_upgrade',
|
|
285
|
-
description: 'Preview the impact of upgrading a package: semver drift, breaking-change markers from the local CHANGELOG, and the files in your repo that import it. Offline.',
|
|
286
|
-
inputSchema: {
|
|
287
|
-
type: 'object',
|
|
288
|
-
properties: {
|
|
289
|
-
package: {
|
|
290
|
-
type: 'string',
|
|
291
|
-
description: 'Name of the package to preview.',
|
|
292
|
-
},
|
|
293
|
-
},
|
|
294
|
-
required: ['package'],
|
|
295
|
-
},
|
|
296
|
-
handler: async (args, rootPath) => {
|
|
297
|
-
const pkgName = typeof args.package === 'string' ? args.package : '';
|
|
298
|
-
if (!pkgName)
|
|
299
|
-
throw new Error('package argument is required');
|
|
300
|
-
const scan = await scanRepository(rootPath);
|
|
301
|
-
// Python-dominated repos have no node_modules CHANGELOG to slice.
|
|
302
|
-
// Short-circuit with a clear reason rather than returning
|
|
303
|
-
// available:false with a misleading "not found" message.
|
|
304
|
-
if (await isPythonDominated(rootPath, scan.files)) {
|
|
305
|
-
return {
|
|
306
|
-
available: false,
|
|
307
|
-
reason: 'Upgrade preview is currently supported only for Node.js packages. Python support is planned for a future release.',
|
|
308
|
-
name: pkgName,
|
|
309
|
-
declared: null,
|
|
310
|
-
installed: null,
|
|
311
|
-
latest: null,
|
|
312
|
-
drift: 'unknown',
|
|
313
|
-
breakingMarkers: [],
|
|
314
|
-
importers: [],
|
|
315
|
-
};
|
|
316
|
-
}
|
|
317
|
-
return await previewUpgrade(rootPath, pkgName, scan.files);
|
|
318
|
-
},
|
|
319
|
-
},
|
|
320
|
-
{
|
|
321
|
-
name: 'projscan_coverage',
|
|
322
|
-
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.',
|
|
323
|
-
inputSchema: {
|
|
324
|
-
type: 'object',
|
|
325
|
-
properties: {
|
|
326
|
-
limit: {
|
|
327
|
-
type: 'number',
|
|
328
|
-
description: 'How many entries to return (default: 30, max: 200).',
|
|
329
|
-
},
|
|
330
|
-
max_tokens: {
|
|
331
|
-
type: 'number',
|
|
332
|
-
description: 'Cap the response size to roughly this many tokens (~4 chars/token). Truncates the entries array to fit.',
|
|
333
|
-
},
|
|
334
|
-
},
|
|
335
|
-
},
|
|
336
|
-
handler: async (args, rootPath) => {
|
|
337
|
-
const coverage = await parseCoverage(rootPath);
|
|
338
|
-
const scan = await scanRepository(rootPath);
|
|
339
|
-
const issues = await collectIssues(rootPath, scan.files);
|
|
340
|
-
const rawLimit = typeof args.limit === 'number' ? args.limit : 200;
|
|
341
|
-
const limit = Math.max(1, Math.min(500, rawLimit));
|
|
342
|
-
const hotspots = await analyzeHotspots(rootPath, scan.files, issues, {
|
|
343
|
-
limit,
|
|
344
|
-
coverage: coverage.available ? coverageMap(coverage) : undefined,
|
|
345
|
-
});
|
|
346
|
-
const joined = joinCoverageWithHotspots(hotspots, coverage);
|
|
347
|
-
if (!joined.available)
|
|
348
|
-
return joined;
|
|
349
|
-
const page = paginate(joined.entries, readPageParams(args), listChecksum(joined.entries));
|
|
350
|
-
return {
|
|
351
|
-
available: true,
|
|
352
|
-
coverageSource: joined.coverageSource,
|
|
353
|
-
coverageSourceFile: joined.coverageSourceFile,
|
|
354
|
-
entries: page.items,
|
|
355
|
-
total: page.total,
|
|
356
|
-
nextCursor: page.nextCursor,
|
|
357
|
-
};
|
|
358
|
-
},
|
|
359
|
-
},
|
|
360
|
-
{
|
|
361
|
-
name: 'projscan_graph',
|
|
362
|
-
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.',
|
|
363
|
-
inputSchema: {
|
|
364
|
-
type: 'object',
|
|
365
|
-
properties: {
|
|
366
|
-
file: {
|
|
367
|
-
type: 'string',
|
|
368
|
-
description: 'File path (relative to project root) to query.',
|
|
369
|
-
},
|
|
370
|
-
symbol: {
|
|
371
|
-
type: 'string',
|
|
372
|
-
description: 'Symbol name to query (e.g. a function or class). Use instead of `file` to find where a symbol is defined.',
|
|
373
|
-
},
|
|
374
|
-
direction: {
|
|
375
|
-
type: 'string',
|
|
376
|
-
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).',
|
|
377
|
-
enum: ['imports', 'exports', 'importers', 'symbol_defs', 'package_importers'],
|
|
378
|
-
},
|
|
379
|
-
limit: {
|
|
380
|
-
type: 'number',
|
|
381
|
-
description: 'Max entries returned (default 50).',
|
|
382
|
-
},
|
|
383
|
-
max_tokens: {
|
|
384
|
-
type: 'number',
|
|
385
|
-
description: 'Cap the response to roughly this many tokens.',
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
required: ['direction'],
|
|
389
|
-
},
|
|
390
|
-
handler: async (args, rootPath) => {
|
|
391
|
-
const scan = await scanRepository(rootPath);
|
|
392
|
-
const cached = await loadCachedGraph(rootPath);
|
|
393
|
-
const graph = await buildCodeGraph(rootPath, scan.files, cached);
|
|
394
|
-
await saveCachedGraph(rootPath, graph);
|
|
395
|
-
const direction = String(args.direction);
|
|
396
|
-
const file = typeof args.file === 'string' ? args.file : undefined;
|
|
397
|
-
const symbol = typeof args.symbol === 'string' ? args.symbol : undefined;
|
|
398
|
-
const limit = Math.max(1, Math.min(500, typeof args.limit === 'number' ? args.limit : 50));
|
|
399
|
-
switch (direction) {
|
|
400
|
-
case 'imports': {
|
|
401
|
-
if (!file)
|
|
402
|
-
throw new Error('file argument is required for direction=imports');
|
|
403
|
-
return { file, imports: importsOf(graph, file).slice(0, limit) };
|
|
404
|
-
}
|
|
405
|
-
case 'exports': {
|
|
406
|
-
if (!file)
|
|
407
|
-
throw new Error('file argument is required for direction=exports');
|
|
408
|
-
return { file, exports: exportsOf(graph, file).slice(0, limit) };
|
|
409
|
-
}
|
|
410
|
-
case 'importers': {
|
|
411
|
-
if (!file)
|
|
412
|
-
throw new Error('file argument is required for direction=importers');
|
|
413
|
-
return { file, importers: filesImportingFile(graph, file).slice(0, limit) };
|
|
414
|
-
}
|
|
415
|
-
case 'symbol_defs': {
|
|
416
|
-
if (!symbol)
|
|
417
|
-
throw new Error('symbol argument is required for direction=symbol_defs');
|
|
418
|
-
return { symbol, definedIn: filesDefiningSymbol(graph, symbol).slice(0, limit) };
|
|
419
|
-
}
|
|
420
|
-
case 'package_importers': {
|
|
421
|
-
const pkg = symbol ?? file;
|
|
422
|
-
if (!pkg)
|
|
423
|
-
throw new Error('symbol (or file) argument is required for direction=package_importers');
|
|
424
|
-
return { package: pkg, importers: filesImportingPackage(graph, pkg).slice(0, limit) };
|
|
425
|
-
}
|
|
426
|
-
default:
|
|
427
|
-
throw new Error(`unknown direction: ${direction}`);
|
|
428
|
-
}
|
|
429
|
-
},
|
|
430
|
-
},
|
|
431
|
-
{
|
|
432
|
-
name: 'projscan_search',
|
|
433
|
-
description: 'Ranked search across the project. Lexical (BM25) by default; optional semantic (vector) and hybrid (RRF fusion) modes available when the @xenova/transformers peer dependency is installed. Scope controls what to search: "auto"/"content" (ranked content matches with excerpts), "symbols" (exported names), "files" (path substring).',
|
|
434
|
-
inputSchema: {
|
|
435
|
-
type: 'object',
|
|
436
|
-
properties: {
|
|
437
|
-
query: {
|
|
438
|
-
type: 'string',
|
|
439
|
-
description: 'Search string. Multi-word queries are treated as OR across BM25 terms; semantic mode embeds the full query.',
|
|
440
|
-
},
|
|
441
|
-
scope: {
|
|
442
|
-
type: 'string',
|
|
443
|
-
description: 'What to search over: "auto" (= content), "symbols", "files", "content".',
|
|
444
|
-
enum: ['auto', 'symbols', 'files', 'content'],
|
|
445
|
-
},
|
|
446
|
-
mode: {
|
|
447
|
-
type: 'string',
|
|
448
|
-
description: '"lexical" (default, BM25) | "semantic" (embeddings, requires peer dep) | "hybrid" (BM25 + semantic via reciprocal rank fusion). Ignored for "symbols" and "files" scopes.',
|
|
449
|
-
enum: ['lexical', 'semantic', 'hybrid'],
|
|
450
|
-
},
|
|
451
|
-
limit: {
|
|
452
|
-
type: 'number',
|
|
453
|
-
description: 'Max matches returned (default 30).',
|
|
454
|
-
},
|
|
455
|
-
max_tokens: {
|
|
456
|
-
type: 'number',
|
|
457
|
-
description: 'Cap the response to roughly this many tokens.',
|
|
458
|
-
},
|
|
459
|
-
},
|
|
460
|
-
required: ['query'],
|
|
461
|
-
},
|
|
462
|
-
handler: async (args, rootPath) => {
|
|
463
|
-
const query = String(args.query ?? '').trim();
|
|
464
|
-
if (!query)
|
|
465
|
-
throw new Error('query argument is required and must be non-empty');
|
|
466
|
-
const scope = String(args.scope ?? 'auto');
|
|
467
|
-
const limit = Math.max(1, Math.min(500, typeof args.limit === 'number' ? args.limit : 30));
|
|
468
|
-
const scan = await scanRepository(rootPath);
|
|
469
|
-
const cached = await loadCachedGraph(rootPath);
|
|
470
|
-
const graph = await buildCodeGraph(rootPath, scan.files, cached);
|
|
471
|
-
await saveCachedGraph(rootPath, graph);
|
|
472
|
-
// Files scope - simple substring scan; ranking adds no value
|
|
473
|
-
if (scope === 'files') {
|
|
474
|
-
const q = query.toLowerCase();
|
|
475
|
-
const all = scan.files
|
|
476
|
-
.filter((f) => f.relativePath.toLowerCase().includes(q))
|
|
477
|
-
.map((f) => ({ file: f.relativePath, sizeBytes: f.sizeBytes }));
|
|
478
|
-
const page = paginate(all, readPageParams(args), listChecksum(all));
|
|
479
|
-
return { scope, query, matches: page.items, total: page.total, nextCursor: page.nextCursor };
|
|
480
|
-
}
|
|
481
|
-
// Symbols scope - walk the graph's export table; rank exact/prefix/substring
|
|
482
|
-
if (scope === 'symbols') {
|
|
483
|
-
const q = query.toLowerCase();
|
|
484
|
-
const rawMatches = [];
|
|
485
|
-
for (const [file, entry] of graph.files) {
|
|
486
|
-
for (const exp of entry.exports) {
|
|
487
|
-
const name = exp.name.toLowerCase();
|
|
488
|
-
if (!name.includes(q))
|
|
489
|
-
continue;
|
|
490
|
-
const rank = name === q ? 0 : name.startsWith(q) ? 1 : 2;
|
|
491
|
-
rawMatches.push({ symbol: exp.name, kind: exp.kind, file, line: exp.line, rank });
|
|
492
|
-
}
|
|
493
|
-
}
|
|
494
|
-
rawMatches.sort((a, b) => a.rank - b.rank);
|
|
495
|
-
const cleaned = rawMatches.map((m) => ({
|
|
496
|
-
symbol: m.symbol,
|
|
497
|
-
kind: m.kind,
|
|
498
|
-
file: m.file,
|
|
499
|
-
line: m.line,
|
|
500
|
-
}));
|
|
501
|
-
const page = paginate(cleaned, readPageParams(args), listChecksum(cleaned));
|
|
502
|
-
return { scope, query, matches: page.items, total: page.total, nextCursor: page.nextCursor };
|
|
503
|
-
}
|
|
504
|
-
// Content or auto scope - lexical BM25 by default, optionally semantic or hybrid
|
|
505
|
-
const mode = String(args.mode ?? 'lexical');
|
|
506
|
-
const index = await buildSearchIndex(rootPath, scan.files, graph);
|
|
507
|
-
const lexicalHits = searchIndex(index, query, { limit });
|
|
508
|
-
const tokens = expandQuery(query);
|
|
509
|
-
if (mode === 'lexical') {
|
|
510
|
-
const withExcerpts = await attachExcerpts(rootPath, lexicalHits, tokens);
|
|
511
|
-
const page = paginate(withExcerpts, readPageParams(args), listChecksum(withExcerpts));
|
|
512
|
-
return {
|
|
513
|
-
scope: scope === 'auto' ? 'content' : scope,
|
|
514
|
-
mode: 'lexical',
|
|
515
|
-
query,
|
|
516
|
-
queryTokens: tokens,
|
|
517
|
-
matches: page.items,
|
|
518
|
-
total: page.total,
|
|
519
|
-
nextCursor: page.nextCursor,
|
|
520
|
-
};
|
|
521
|
-
}
|
|
522
|
-
// Semantic or hybrid - both require the peer
|
|
523
|
-
const hasSemantic = await isSemanticAvailable();
|
|
524
|
-
if (!hasSemantic) {
|
|
525
|
-
return {
|
|
526
|
-
scope: scope === 'auto' ? 'content' : scope,
|
|
527
|
-
mode,
|
|
528
|
-
query,
|
|
529
|
-
error: 'Semantic search requires the optional peer dependency @xenova/transformers. Install it with: npm install @xenova/transformers',
|
|
530
|
-
available: false,
|
|
531
|
-
matches: [],
|
|
532
|
-
total: 0,
|
|
533
|
-
};
|
|
534
|
-
}
|
|
535
|
-
const semIndex = await buildSemanticIndex(rootPath, scan.files);
|
|
536
|
-
if (!semIndex) {
|
|
537
|
-
return {
|
|
538
|
-
scope: scope === 'auto' ? 'content' : scope,
|
|
539
|
-
mode,
|
|
540
|
-
query,
|
|
541
|
-
error: 'Semantic index build failed (peer loaded but model not usable).',
|
|
542
|
-
available: false,
|
|
543
|
-
matches: [],
|
|
544
|
-
total: 0,
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
const semHits = await semanticSearch(semIndex, query, { limit });
|
|
548
|
-
if (mode === 'semantic') {
|
|
549
|
-
const enriched = await attachExcerpts(rootPath, semHits.map((h) => ({
|
|
550
|
-
file: h.file,
|
|
551
|
-
score: h.score,
|
|
552
|
-
matched: [],
|
|
553
|
-
symbolMatch: false,
|
|
554
|
-
pathMatch: false,
|
|
555
|
-
excerpt: '',
|
|
556
|
-
line: 0,
|
|
557
|
-
})), tokens);
|
|
558
|
-
const page = paginate(enriched, readPageParams(args), listChecksum(enriched));
|
|
559
|
-
return {
|
|
560
|
-
scope: scope === 'auto' ? 'content' : scope,
|
|
561
|
-
mode: 'semantic',
|
|
562
|
-
query,
|
|
563
|
-
model: semIndex.model,
|
|
564
|
-
matches: page.items,
|
|
565
|
-
total: page.total,
|
|
566
|
-
nextCursor: page.nextCursor,
|
|
567
|
-
};
|
|
568
|
-
}
|
|
569
|
-
// Hybrid - reciprocal rank fusion
|
|
570
|
-
const fused = reciprocalRankFusion([lexicalHits, semHits]).slice(0, limit);
|
|
571
|
-
const enriched = await attachExcerpts(rootPath, fused.map((f) => ({
|
|
572
|
-
file: f.file,
|
|
573
|
-
score: f.score,
|
|
574
|
-
matched: [],
|
|
575
|
-
symbolMatch: false,
|
|
576
|
-
pathMatch: false,
|
|
577
|
-
excerpt: '',
|
|
578
|
-
line: 0,
|
|
579
|
-
})), tokens);
|
|
580
|
-
const page = paginate(enriched, readPageParams(args), listChecksum(enriched));
|
|
581
|
-
return {
|
|
582
|
-
scope: scope === 'auto' ? 'content' : scope,
|
|
583
|
-
mode: 'hybrid',
|
|
584
|
-
query,
|
|
585
|
-
queryTokens: tokens,
|
|
586
|
-
model: semIndex.model,
|
|
587
|
-
matches: page.items,
|
|
588
|
-
total: page.total,
|
|
589
|
-
nextCursor: page.nextCursor,
|
|
590
|
-
};
|
|
591
|
-
},
|
|
592
|
-
},
|
|
28
|
+
analyzeTool,
|
|
29
|
+
doctorTool,
|
|
30
|
+
hotspotsTool,
|
|
31
|
+
explainTool,
|
|
32
|
+
fileTool,
|
|
33
|
+
structureTool,
|
|
34
|
+
dependenciesTool,
|
|
35
|
+
outdatedTool,
|
|
36
|
+
auditTool,
|
|
37
|
+
upgradeTool,
|
|
38
|
+
coverageTool,
|
|
39
|
+
graphTool,
|
|
40
|
+
couplingTool,
|
|
41
|
+
workspacesTool,
|
|
42
|
+
prDiffTool,
|
|
43
|
+
searchTool,
|
|
593
44
|
];
|
|
594
45
|
export function getToolDefinitions() {
|
|
595
46
|
return tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
|
|
@@ -597,20 +48,4 @@ export function getToolDefinitions() {
|
|
|
597
48
|
export function getToolHandler(name) {
|
|
598
49
|
return tools.find((t) => t.name === name)?.handler;
|
|
599
50
|
}
|
|
600
|
-
// ── File Explanation (used by projscan_explain) ──────────
|
|
601
|
-
function explainFile(absolutePath, content, rootPath) {
|
|
602
|
-
const lines = content.split('\n');
|
|
603
|
-
const imports = extractImports(content);
|
|
604
|
-
const exports = extractExports(content);
|
|
605
|
-
const purpose = inferPurpose(absolutePath, exports);
|
|
606
|
-
const potentialIssues = detectFileIssues(content, lines.length);
|
|
607
|
-
return {
|
|
608
|
-
filePath: path.relative(rootPath, absolutePath),
|
|
609
|
-
purpose,
|
|
610
|
-
imports,
|
|
611
|
-
exports,
|
|
612
|
-
potentialIssues,
|
|
613
|
-
lineCount: lines.length,
|
|
614
|
-
};
|
|
615
|
-
}
|
|
616
51
|
//# sourceMappingURL=tools.js.map
|