projscan 0.1.13 → 0.2.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.
Files changed (38) hide show
  1. package/README.md +92 -9
  2. package/dist/cli/index.js +108 -144
  3. package/dist/cli/index.js.map +1 -1
  4. package/dist/core/fileInspector.d.ts +13 -0
  5. package/dist/core/fileInspector.js +205 -0
  6. package/dist/core/fileInspector.js.map +1 -0
  7. package/dist/core/hotspotAnalyzer.d.ts +16 -0
  8. package/dist/core/hotspotAnalyzer.js +342 -0
  9. package/dist/core/hotspotAnalyzer.js.map +1 -0
  10. package/dist/index.d.ts +7 -1
  11. package/dist/index.js +6 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/mcp/prompts.d.ts +14 -0
  14. package/dist/mcp/prompts.js +126 -0
  15. package/dist/mcp/prompts.js.map +1 -0
  16. package/dist/mcp/resources.d.ts +8 -0
  17. package/dist/mcp/resources.js +57 -0
  18. package/dist/mcp/resources.js.map +1 -0
  19. package/dist/mcp/server.d.ts +5 -0
  20. package/dist/mcp/server.js +205 -0
  21. package/dist/mcp/server.js.map +1 -0
  22. package/dist/mcp/tools.d.ts +9 -0
  23. package/dist/mcp/tools.js +176 -0
  24. package/dist/mcp/tools.js.map +1 -0
  25. package/dist/reporters/consoleReporter.d.ts +3 -1
  26. package/dist/reporters/consoleReporter.js +127 -0
  27. package/dist/reporters/consoleReporter.js.map +1 -1
  28. package/dist/reporters/jsonReporter.d.ts +3 -1
  29. package/dist/reporters/jsonReporter.js +6 -0
  30. package/dist/reporters/jsonReporter.js.map +1 -1
  31. package/dist/reporters/markdownReporter.d.ts +3 -1
  32. package/dist/reporters/markdownReporter.js +99 -0
  33. package/dist/reporters/markdownReporter.js.map +1 -1
  34. package/dist/types.d.ts +88 -0
  35. package/dist/utils/baseline.d.ts +4 -4
  36. package/dist/utils/baseline.js +71 -5
  37. package/dist/utils/baseline.js.map +1 -1
  38. package/package.json +2 -2
@@ -0,0 +1,126 @@
1
+ import { scanRepository } from '../core/repositoryScanner.js';
2
+ import { collectIssues } from '../core/issueEngine.js';
3
+ import { analyzeHotspots } from '../core/hotspotAnalyzer.js';
4
+ import { inspectFile } from '../core/fileInspector.js';
5
+ import { calculateScore } from '../utils/scoreCalculator.js';
6
+ const promptDefinitions = [
7
+ {
8
+ name: 'prioritize_refactoring',
9
+ description: "Produce a ranked refactoring plan grounded in this project's current churn-weighted hotspots and open health issues.",
10
+ arguments: [
11
+ {
12
+ name: 'limit',
13
+ description: 'How many hotspots to include (default: 10)',
14
+ required: false,
15
+ },
16
+ ],
17
+ },
18
+ {
19
+ name: 'investigate_file',
20
+ description: "Produce a senior-engineer investigation of a specific file, grounded in its churn, ownership, related issues, and structure.",
21
+ arguments: [
22
+ {
23
+ name: 'file',
24
+ description: 'Path to the file (relative to project root)',
25
+ required: true,
26
+ },
27
+ ],
28
+ },
29
+ ];
30
+ export function getPromptDefinitions() {
31
+ return promptDefinitions;
32
+ }
33
+ export async function getPrompt(name, args, rootPath) {
34
+ switch (name) {
35
+ case 'prioritize_refactoring':
36
+ return await prioritizeRefactoringPrompt(args, rootPath);
37
+ case 'investigate_file':
38
+ return await investigateFilePrompt(args, rootPath);
39
+ default:
40
+ throw new Error(`Unknown prompt: ${name}`);
41
+ }
42
+ }
43
+ async function prioritizeRefactoringPrompt(args, rootPath) {
44
+ const limit = coerceLimit(args.limit, 10);
45
+ const scan = await scanRepository(rootPath);
46
+ const issues = await collectIssues(rootPath, scan.files);
47
+ const hotspots = await analyzeHotspots(rootPath, scan.files, issues, { limit });
48
+ const { score, grade } = calculateScore(issues);
49
+ const hotspotLines = hotspots.available && hotspots.hotspots.length > 0
50
+ ? hotspots.hotspots
51
+ .map((h, i) => {
52
+ const reasons = h.reasons.length > 0 ? h.reasons.join(', ') : 'ranked by risk';
53
+ const ownership = h.busFactorOne && h.primaryAuthor
54
+ ? ` [BUS FACTOR 1: ${h.primaryAuthor}]`
55
+ : '';
56
+ return `${i + 1}. ${h.relativePath} — risk ${h.riskScore.toFixed(1)} (${reasons})${ownership}`;
57
+ })
58
+ .join('\n')
59
+ : '(no hotspots available — project may not be a git repository)';
60
+ const topIssues = issues
61
+ .slice(0, 15)
62
+ .map((issue) => `- [${issue.severity}] ${issue.title}`)
63
+ .join('\n');
64
+ const text = [
65
+ 'You are a senior engineer reviewing a codebase. Produce a concrete, prioritized refactoring plan.',
66
+ '',
67
+ `Current health: ${grade} (${score}/100). Issues: ${issues.length}.`,
68
+ '',
69
+ 'Top hotspots (ranked by churn × complexity × open issues × recency):',
70
+ hotspotLines,
71
+ '',
72
+ 'Top health issues:',
73
+ topIssues || '(none)',
74
+ '',
75
+ 'For each of the top 3 hotspots, output:',
76
+ '1. Why it is risky (in one sentence, citing the evidence above)',
77
+ '2. A specific refactoring or investigation action',
78
+ '3. Estimated effort (S / M / L)',
79
+ '',
80
+ 'Then propose an ordering that maximizes risk reduction per unit of effort.',
81
+ ].join('\n');
82
+ return {
83
+ description: 'Prioritized refactoring plan grounded in live project data',
84
+ messages: [
85
+ { role: 'user', content: { type: 'text', text } },
86
+ ],
87
+ };
88
+ }
89
+ async function investigateFilePrompt(args, rootPath) {
90
+ const file = typeof args.file === 'string' ? args.file : '';
91
+ if (!file)
92
+ throw new Error('investigate_file requires a "file" argument');
93
+ const insp = await inspectFile(rootPath, file);
94
+ const body = JSON.stringify(insp, null, 2);
95
+ const text = [
96
+ `You are a senior engineer investigating \`${file}\`.`,
97
+ '',
98
+ "Here is the file report from projscan (purpose, risk score, ownership, related health issues, imports, exports):",
99
+ '',
100
+ '```json',
101
+ body,
102
+ '```',
103
+ '',
104
+ 'Explain in order:',
105
+ '1. What this file does and how it fits in the codebase.',
106
+ '2. What is risky about it right now (cite evidence from the report).',
107
+ '3. Concrete next actions — questions to ask, tests to add, or refactors to attempt.',
108
+ '4. Who to involve (based on ownership, if available).',
109
+ ].join('\n');
110
+ return {
111
+ description: `Investigation brief for ${file}`,
112
+ messages: [{ role: 'user', content: { type: 'text', text } }],
113
+ };
114
+ }
115
+ function coerceLimit(value, fallback) {
116
+ if (typeof value === 'number' && Number.isFinite(value)) {
117
+ return Math.max(1, Math.min(100, Math.floor(value)));
118
+ }
119
+ if (typeof value === 'string') {
120
+ const parsed = parseInt(value, 10);
121
+ if (Number.isFinite(parsed))
122
+ return Math.max(1, Math.min(100, parsed));
123
+ }
124
+ return fallback;
125
+ }
126
+ //# sourceMappingURL=prompts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.js","sourceRoot":"","sources":["../../src/mcp/prompts.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ import type { McpResourceDefinition } from '../types.js';
2
+ export interface McpResourceContent {
3
+ uri: string;
4
+ mimeType: string;
5
+ text: string;
6
+ }
7
+ export declare function getResourceDefinitions(): McpResourceDefinition[];
8
+ export declare function readResource(uri: string, rootPath: string): Promise<McpResourceContent>;
@@ -0,0 +1,57 @@
1
+ import { scanRepository } from '../core/repositoryScanner.js';
2
+ import { collectIssues } from '../core/issueEngine.js';
3
+ import { analyzeHotspots } from '../core/hotspotAnalyzer.js';
4
+ import { calculateScore } from '../utils/scoreCalculator.js';
5
+ const resourceDefinitions = [
6
+ {
7
+ uri: 'projscan://health',
8
+ name: 'Project Health Report',
9
+ description: 'Live projscan doctor output: score, grade, and open issues.',
10
+ mimeType: 'application/json',
11
+ },
12
+ {
13
+ uri: 'projscan://hotspots',
14
+ name: 'Risk Hotspots',
15
+ description: 'Files ranked by git-churn × complexity × open issues × recency. Includes ownership data.',
16
+ mimeType: 'application/json',
17
+ },
18
+ {
19
+ uri: 'projscan://structure',
20
+ name: 'Project Directory Tree',
21
+ description: 'File/directory layout of the project with counts.',
22
+ mimeType: 'application/json',
23
+ },
24
+ ];
25
+ export function getResourceDefinitions() {
26
+ return resourceDefinitions;
27
+ }
28
+ export async function readResource(uri, rootPath) {
29
+ switch (uri) {
30
+ case 'projscan://health': {
31
+ const scan = await scanRepository(rootPath);
32
+ const issues = await collectIssues(rootPath, scan.files);
33
+ const health = calculateScore(issues);
34
+ return jsonResource(uri, { health, issues });
35
+ }
36
+ case 'projscan://hotspots': {
37
+ const scan = await scanRepository(rootPath);
38
+ const issues = await collectIssues(rootPath, scan.files);
39
+ const hotspots = await analyzeHotspots(rootPath, scan.files, issues, { limit: 20 });
40
+ return jsonResource(uri, hotspots);
41
+ }
42
+ case 'projscan://structure': {
43
+ const scan = await scanRepository(rootPath);
44
+ return jsonResource(uri, { structure: scan.directoryTree, totalFiles: scan.totalFiles });
45
+ }
46
+ default:
47
+ throw new Error(`Unknown resource URI: ${uri}`);
48
+ }
49
+ }
50
+ function jsonResource(uri, data) {
51
+ return {
52
+ uri,
53
+ mimeType: 'application/json',
54
+ text: JSON.stringify(data, null, 2),
55
+ };
56
+ }
57
+ //# sourceMappingURL=resources.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.js","sourceRoot":"","sources":["../../src/mcp/resources.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAQ7D,MAAM,mBAAmB,GAA4B;IACnD;QACE,GAAG,EAAE,mBAAmB;QACxB,IAAI,EAAE,uBAAuB;QAC7B,WAAW,EAAE,6DAA6D;QAC1E,QAAQ,EAAE,kBAAkB;KAC7B;IACD;QACE,GAAG,EAAE,qBAAqB;QAC1B,IAAI,EAAE,eAAe;QACrB,WAAW,EACT,0FAA0F;QAC5F,QAAQ,EAAE,kBAAkB;KAC7B;IACD;QACE,GAAG,EAAE,sBAAsB;QAC3B,IAAI,EAAE,wBAAwB;QAC9B,WAAW,EAAE,mDAAmD;QAChE,QAAQ,EAAE,kBAAkB;KAC7B;CACF,CAAC;AAEF,MAAM,UAAU,sBAAsB;IACpC,OAAO,mBAAmB,CAAC;AAC7B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAW,EAAE,QAAgB;IAC9D,QAAQ,GAAG,EAAE,CAAC;QACZ,KAAK,mBAAmB,CAAC,CAAC,CAAC;YACzB,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,cAAc,CAAC,MAAM,CAAC,CAAC;YACtC,OAAO,YAAY,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,CAAC;QACD,KAAK,qBAAqB,CAAC,CAAC,CAAC;YAC3B,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;YAC5C,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;YACzD,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,QAAQ,EAAE,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;YACpF,OAAO,YAAY,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;QACrC,CAAC;QACD,KAAK,sBAAsB,CAAC,CAAC,CAAC;YAC5B,MAAM,IAAI,GAAG,MAAM,cAAc,CAAC,QAAQ,CAAC,CAAC;YAC5C,OAAO,YAAY,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,CAAC,aAAa,EAAE,UAAU,EAAE,IAAI,CAAC,UAAU,EAAE,CAAC,CAAC;QAC3F,CAAC;QACD;YACE,MAAM,IAAI,KAAK,CAAC,yBAAyB,GAAG,EAAE,CAAC,CAAC;IACpD,CAAC;AACH,CAAC;AAED,SAAS,YAAY,CAAC,GAAW,EAAE,IAAa;IAC9C,OAAO;QACL,GAAG;QACH,QAAQ,EAAE,kBAAkB;QAC5B,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;KACpC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,5 @@
1
+ export interface McpServerHandle {
2
+ handleMessage(line: string): Promise<string | null>;
3
+ }
4
+ export declare function createMcpServer(rootPath: string): McpServerHandle;
5
+ export declare function runMcpServer(rootPath: string): Promise<void>;
@@ -0,0 +1,205 @@
1
+ import readline from 'node:readline';
2
+ import { readFileSync } from 'node:fs';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { getToolDefinitions, getToolHandler } from './tools.js';
6
+ import { getPromptDefinitions, getPrompt } from './prompts.js';
7
+ import { getResourceDefinitions, readResource } from './resources.js';
8
+ const PROTOCOL_VERSION = '2024-11-05';
9
+ const JSONRPC_ERROR = {
10
+ ParseError: -32700,
11
+ InvalidRequest: -32600,
12
+ MethodNotFound: -32601,
13
+ InvalidParams: -32602,
14
+ InternalError: -32603,
15
+ };
16
+ function readPackageVersion() {
17
+ try {
18
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
19
+ const pkg = JSON.parse(readFileSync(path.resolve(__dirname, '../../package.json'), 'utf-8'));
20
+ return String(pkg.version ?? '0.0.0');
21
+ }
22
+ catch {
23
+ return '0.0.0';
24
+ }
25
+ }
26
+ export function createMcpServer(rootPath) {
27
+ const serverVersion = readPackageVersion();
28
+ let initialized = false;
29
+ async function dispatch(request) {
30
+ const id = request.id ?? null;
31
+ // Notifications (no id) — no response expected.
32
+ const isNotification = request.id === undefined || request.id === null;
33
+ try {
34
+ switch (request.method) {
35
+ case 'initialize': {
36
+ const params = (request.params ?? {});
37
+ initialized = true;
38
+ return ok(id, {
39
+ protocolVersion: params.protocolVersion ?? PROTOCOL_VERSION,
40
+ serverInfo: {
41
+ name: 'projscan',
42
+ version: serverVersion,
43
+ },
44
+ capabilities: {
45
+ tools: {},
46
+ prompts: {},
47
+ resources: {},
48
+ },
49
+ });
50
+ }
51
+ case 'notifications/initialized':
52
+ case 'initialized':
53
+ return null;
54
+ case 'ping':
55
+ return ok(id, {});
56
+ case 'shutdown':
57
+ return ok(id, null);
58
+ case 'tools/list': {
59
+ return ok(id, { tools: getToolDefinitions() });
60
+ }
61
+ case 'tools/call': {
62
+ const params = (request.params ?? {});
63
+ const name = params.name;
64
+ if (!name) {
65
+ return fail(id, JSONRPC_ERROR.InvalidParams, 'Missing tool name');
66
+ }
67
+ const handler = getToolHandler(name);
68
+ if (!handler) {
69
+ return fail(id, JSONRPC_ERROR.MethodNotFound, `Unknown tool: ${name}`);
70
+ }
71
+ try {
72
+ const result = await handler(params.arguments ?? {}, rootPath);
73
+ return ok(id, {
74
+ content: [
75
+ {
76
+ type: 'text',
77
+ text: safeStringify(result),
78
+ },
79
+ ],
80
+ isError: false,
81
+ });
82
+ }
83
+ catch (err) {
84
+ const message = err instanceof Error ? err.message : String(err);
85
+ return ok(id, {
86
+ content: [{ type: 'text', text: `Error: ${message}` }],
87
+ isError: true,
88
+ });
89
+ }
90
+ }
91
+ case 'prompts/list': {
92
+ return ok(id, { prompts: getPromptDefinitions() });
93
+ }
94
+ case 'prompts/get': {
95
+ const params = (request.params ?? {});
96
+ if (!params.name) {
97
+ return fail(id, JSONRPC_ERROR.InvalidParams, 'Missing prompt name');
98
+ }
99
+ try {
100
+ const result = await getPrompt(params.name, params.arguments ?? {}, rootPath);
101
+ return ok(id, result);
102
+ }
103
+ catch (err) {
104
+ const message = err instanceof Error ? err.message : String(err);
105
+ return fail(id, JSONRPC_ERROR.InvalidParams, message);
106
+ }
107
+ }
108
+ case 'resources/list': {
109
+ return ok(id, { resources: getResourceDefinitions() });
110
+ }
111
+ case 'resources/read': {
112
+ const params = (request.params ?? {});
113
+ if (!params.uri) {
114
+ return fail(id, JSONRPC_ERROR.InvalidParams, 'Missing resource uri');
115
+ }
116
+ try {
117
+ const content = await readResource(params.uri, rootPath);
118
+ return ok(id, { contents: [content] });
119
+ }
120
+ catch (err) {
121
+ const message = err instanceof Error ? err.message : String(err);
122
+ return fail(id, JSONRPC_ERROR.InvalidParams, message);
123
+ }
124
+ }
125
+ default:
126
+ if (isNotification)
127
+ return null;
128
+ return fail(id, JSONRPC_ERROR.MethodNotFound, `Method not found: ${request.method}`);
129
+ }
130
+ }
131
+ catch (err) {
132
+ if (isNotification)
133
+ return null;
134
+ const message = err instanceof Error ? err.message : String(err);
135
+ return fail(id, JSONRPC_ERROR.InternalError, message);
136
+ }
137
+ finally {
138
+ void initialized;
139
+ }
140
+ }
141
+ async function handleMessage(line) {
142
+ const trimmed = line.trim();
143
+ if (!trimmed)
144
+ return null;
145
+ let request;
146
+ try {
147
+ request = JSON.parse(trimmed);
148
+ }
149
+ catch {
150
+ return JSON.stringify(fail(null, JSONRPC_ERROR.ParseError, 'Invalid JSON'));
151
+ }
152
+ if (!request || typeof request !== 'object' || request.jsonrpc !== '2.0' || typeof request.method !== 'string') {
153
+ return JSON.stringify(fail(request?.id ?? null, JSONRPC_ERROR.InvalidRequest, 'Invalid JSON-RPC request'));
154
+ }
155
+ const response = await dispatch(request);
156
+ if (!response)
157
+ return null;
158
+ return JSON.stringify(response);
159
+ }
160
+ return { handleMessage };
161
+ }
162
+ function ok(id, result) {
163
+ return { jsonrpc: '2.0', id, result };
164
+ }
165
+ function fail(id, code, message, data) {
166
+ return {
167
+ jsonrpc: '2.0',
168
+ id,
169
+ error: { code, message, data },
170
+ };
171
+ }
172
+ function safeStringify(value) {
173
+ try {
174
+ return JSON.stringify(value, null, 2);
175
+ }
176
+ catch {
177
+ return String(value);
178
+ }
179
+ }
180
+ export async function runMcpServer(rootPath) {
181
+ const server = createMcpServer(rootPath);
182
+ process.stderr.write(`[projscan-mcp] listening on stdio (root=${rootPath})\n`);
183
+ const rl = readline.createInterface({
184
+ input: process.stdin,
185
+ crlfDelay: Infinity,
186
+ });
187
+ rl.on('line', (line) => {
188
+ server
189
+ .handleMessage(line)
190
+ .then((response) => {
191
+ if (response !== null) {
192
+ process.stdout.write(response + '\n');
193
+ }
194
+ })
195
+ .catch((err) => {
196
+ const message = err instanceof Error ? err.message : String(err);
197
+ process.stderr.write(`[projscan-mcp] error: ${message}\n`);
198
+ });
199
+ });
200
+ await new Promise((resolve) => {
201
+ rl.on('close', resolve);
202
+ process.stdin.on('end', resolve);
203
+ });
204
+ }
205
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import type { McpToolDefinition } from '../types.js';
2
+ export interface McpToolHandler {
3
+ (args: Record<string, unknown>, rootPath: string): Promise<unknown>;
4
+ }
5
+ export interface McpTool extends McpToolDefinition {
6
+ handler: McpToolHandler;
7
+ }
8
+ export declare function getToolDefinitions(): McpToolDefinition[];
9
+ export declare function getToolHandler(name: string): McpToolHandler | undefined;
@@ -0,0 +1,176 @@
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 { inspectFile, extractImports, extractExports, inferPurpose, detectFileIssues, } from '../core/fileInspector.js';
10
+ import { calculateScore } from '../utils/scoreCalculator.js';
11
+ const tools = [
12
+ {
13
+ name: 'projscan_analyze',
14
+ 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.',
15
+ inputSchema: {
16
+ type: 'object',
17
+ properties: {},
18
+ },
19
+ handler: async (_args, rootPath) => {
20
+ const scan = await scanRepository(rootPath);
21
+ const languages = detectLanguages(scan.files);
22
+ const frameworks = await detectFrameworks(rootPath, scan.files);
23
+ const dependencies = await analyzeDependencies(rootPath);
24
+ const issues = await collectIssues(rootPath, scan.files);
25
+ const health = calculateScore(issues);
26
+ const report = {
27
+ projectName: path.basename(rootPath),
28
+ rootPath,
29
+ scan: { ...scan, files: [], directoryTree: scan.directoryTree },
30
+ languages,
31
+ frameworks,
32
+ dependencies,
33
+ issues,
34
+ timestamp: new Date().toISOString(),
35
+ health,
36
+ };
37
+ return report;
38
+ },
39
+ },
40
+ {
41
+ name: 'projscan_doctor',
42
+ 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).',
43
+ inputSchema: {
44
+ type: 'object',
45
+ properties: {},
46
+ },
47
+ handler: async (_args, rootPath) => {
48
+ const scan = await scanRepository(rootPath);
49
+ const issues = await collectIssues(rootPath, scan.files);
50
+ const health = calculateScore(issues);
51
+ return {
52
+ health,
53
+ issues,
54
+ };
55
+ },
56
+ },
57
+ {
58
+ name: 'projscan_hotspots',
59
+ description: 'Rank files by risk using git churn × complexity × open issues. Returns the most dangerous files to touch. Use this to decide where to focus refactoring, testing, or review effort.',
60
+ inputSchema: {
61
+ type: 'object',
62
+ properties: {
63
+ limit: {
64
+ type: 'number',
65
+ description: 'How many hotspots to return (default: 10, max: 100).',
66
+ },
67
+ since: {
68
+ type: 'string',
69
+ description: 'Git history window. Examples: "12 months ago", "2024-01-01". Default: "12 months ago".',
70
+ },
71
+ },
72
+ },
73
+ handler: async (args, rootPath) => {
74
+ const scan = await scanRepository(rootPath);
75
+ const issues = await collectIssues(rootPath, scan.files);
76
+ const limit = typeof args.limit === 'number' ? args.limit : undefined;
77
+ const since = typeof args.since === 'string' ? args.since : undefined;
78
+ return await analyzeHotspots(rootPath, scan.files, issues, { limit, since });
79
+ },
80
+ },
81
+ {
82
+ name: 'projscan_explain',
83
+ description: 'Explain a single file: purpose, imports, exports, and potential issues. Useful for understanding unfamiliar code before editing.',
84
+ inputSchema: {
85
+ type: 'object',
86
+ properties: {
87
+ file: {
88
+ type: 'string',
89
+ description: 'Path to the file relative to the project root.',
90
+ },
91
+ },
92
+ required: ['file'],
93
+ },
94
+ handler: async (args, rootPath) => {
95
+ const rel = typeof args.file === 'string' ? args.file : '';
96
+ if (!rel)
97
+ throw new Error('file argument is required');
98
+ const absolutePath = path.resolve(rootPath, rel);
99
+ const resolvedRoot = path.resolve(rootPath);
100
+ if (!absolutePath.startsWith(resolvedRoot + path.sep) && absolutePath !== resolvedRoot) {
101
+ throw new Error('file must be inside the project root');
102
+ }
103
+ const content = await fs.readFile(absolutePath, 'utf-8');
104
+ return explainFile(absolutePath, content, rootPath);
105
+ },
106
+ },
107
+ {
108
+ name: 'projscan_file',
109
+ 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.',
110
+ inputSchema: {
111
+ type: 'object',
112
+ properties: {
113
+ file: {
114
+ type: 'string',
115
+ description: 'Path to the file relative to the project root.',
116
+ },
117
+ },
118
+ required: ['file'],
119
+ },
120
+ handler: async (args, rootPath) => {
121
+ const rel = typeof args.file === 'string' ? args.file : '';
122
+ if (!rel)
123
+ throw new Error('file argument is required');
124
+ return await inspectFile(rootPath, rel);
125
+ },
126
+ },
127
+ {
128
+ name: 'projscan_structure',
129
+ description: 'Return the project directory tree with file counts.',
130
+ inputSchema: {
131
+ type: 'object',
132
+ properties: {},
133
+ },
134
+ handler: async (_args, rootPath) => {
135
+ const scan = await scanRepository(rootPath);
136
+ return { structure: scan.directoryTree, totalFiles: scan.totalFiles };
137
+ },
138
+ },
139
+ {
140
+ name: 'projscan_dependencies',
141
+ description: 'Analyze package.json dependencies and return counts and risks (deprecated packages, wildcard versions, etc.).',
142
+ inputSchema: {
143
+ type: 'object',
144
+ properties: {},
145
+ },
146
+ handler: async (_args, rootPath) => {
147
+ const report = await analyzeDependencies(rootPath);
148
+ if (!report)
149
+ return { available: false, reason: 'No package.json found' };
150
+ return { available: true, ...report };
151
+ },
152
+ },
153
+ ];
154
+ export function getToolDefinitions() {
155
+ return tools.map(({ name, description, inputSchema }) => ({ name, description, inputSchema }));
156
+ }
157
+ export function getToolHandler(name) {
158
+ return tools.find((t) => t.name === name)?.handler;
159
+ }
160
+ // ── File Explanation (used by projscan_explain) ──────────
161
+ function explainFile(absolutePath, content, rootPath) {
162
+ const lines = content.split('\n');
163
+ const imports = extractImports(content);
164
+ const exports = extractExports(content);
165
+ const purpose = inferPurpose(absolutePath, exports);
166
+ const potentialIssues = detectFileIssues(content, lines.length);
167
+ return {
168
+ filePath: path.relative(rootPath, absolutePath),
169
+ purpose,
170
+ imports,
171
+ exports,
172
+ potentialIssues,
173
+ lineCount: lines.length,
174
+ };
175
+ }
176
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/mcp/tools.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import type { AnalysisReport, Issue, Fix, FixResult, FileExplanation, ArchitectureLayer, DirectoryNode, DependencyReport, DiffResult } from '../types.js';
1
+ import type { AnalysisReport, Issue, Fix, FixResult, FileExplanation, FileInspection, ArchitectureLayer, DirectoryNode, DependencyReport, DiffResult, HotspotReport } from '../types.js';
2
2
  export declare function reportAnalysis(report: AnalysisReport): void;
3
3
  export declare function reportHealth(issues: Issue[], scanTimeMs?: number): void;
4
4
  export declare function reportCi(issues: Issue[], threshold: number): void;
@@ -9,3 +9,5 @@ export declare function reportExplanation(explanation: FileExplanation): void;
9
9
  export declare function reportDiagram(layers: ArchitectureLayer[]): void;
10
10
  export declare function reportStructure(tree: DirectoryNode, projectName?: string): void;
11
11
  export declare function reportDependencies(report: DependencyReport): void;
12
+ export declare function reportHotspots(report: HotspotReport): void;
13
+ export declare function reportFileInspection(insp: FileInspection): void;