projscan 0.1.11 → 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.
- package/README.md +141 -32
- package/dist/cli/index.js +108 -144
- package/dist/cli/index.js.map +1 -1
- package/dist/core/fileInspector.d.ts +13 -0
- package/dist/core/fileInspector.js +205 -0
- package/dist/core/fileInspector.js.map +1 -0
- package/dist/core/hotspotAnalyzer.d.ts +16 -0
- package/dist/core/hotspotAnalyzer.js +342 -0
- package/dist/core/hotspotAnalyzer.js.map +1 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/mcp/prompts.d.ts +14 -0
- package/dist/mcp/prompts.js +126 -0
- package/dist/mcp/prompts.js.map +1 -0
- package/dist/mcp/resources.d.ts +8 -0
- package/dist/mcp/resources.js +57 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/server.d.ts +5 -0
- package/dist/mcp/server.js +205 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +9 -0
- package/dist/mcp/tools.js +176 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/reporters/consoleReporter.d.ts +3 -1
- package/dist/reporters/consoleReporter.js +127 -0
- package/dist/reporters/consoleReporter.js.map +1 -1
- package/dist/reporters/jsonReporter.d.ts +3 -1
- package/dist/reporters/jsonReporter.js +6 -0
- package/dist/reporters/jsonReporter.js.map +1 -1
- package/dist/reporters/markdownReporter.d.ts +3 -1
- package/dist/reporters/markdownReporter.js +99 -0
- package/dist/reporters/markdownReporter.js.map +1 -1
- package/dist/types.d.ts +88 -0
- package/dist/utils/baseline.d.ts +4 -4
- package/dist/utils/baseline.js +71 -5
- package/dist/utils/baseline.js.map +1 -1
- package/package.json +2 -2
|
@@ -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;
|
|
@@ -156,6 +156,37 @@ export function reportDiff(diff) {
|
|
|
156
156
|
if (diff.resolvedIssues.length === 0 && diff.newIssues.length === 0) {
|
|
157
157
|
console.log(`\n ${chalk.dim('No change in issues.')}`);
|
|
158
158
|
}
|
|
159
|
+
if (diff.hotspotDiff) {
|
|
160
|
+
const hd = diff.hotspotDiff;
|
|
161
|
+
const total = hd.rose.length + hd.fell.length + hd.appeared.length + hd.resolved.length;
|
|
162
|
+
if (total > 0) {
|
|
163
|
+
console.log(header('Hotspot Changes'));
|
|
164
|
+
if (hd.rose.length > 0) {
|
|
165
|
+
console.log(`\n ${chalk.red('▲')} Worsening (${hd.rose.length}):`);
|
|
166
|
+
for (const delta of hd.rose.slice(0, 10)) {
|
|
167
|
+
console.log(` ${chalk.red('+' + delta.scoreDelta.toFixed(1))} ${delta.relativePath} ${chalk.dim(`${delta.beforeScore?.toFixed(1)} → ${delta.afterScore?.toFixed(1)}`)}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (hd.appeared.length > 0) {
|
|
171
|
+
console.log(`\n ${chalk.yellow('●')} Newly risky (${hd.appeared.length}):`);
|
|
172
|
+
for (const delta of hd.appeared.slice(0, 10)) {
|
|
173
|
+
console.log(` ${chalk.yellow(delta.afterScore?.toFixed(1) ?? '?')} ${delta.relativePath}`);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
if (hd.fell.length > 0) {
|
|
177
|
+
console.log(`\n ${chalk.green('▼')} Improving (${hd.fell.length}):`);
|
|
178
|
+
for (const delta of hd.fell.slice(0, 10)) {
|
|
179
|
+
console.log(` ${chalk.green(delta.scoreDelta.toFixed(1))} ${delta.relativePath} ${chalk.dim(`${delta.beforeScore?.toFixed(1)} → ${delta.afterScore?.toFixed(1)}`)}`);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (hd.resolved.length > 0) {
|
|
183
|
+
console.log(`\n ${chalk.green('✓')} No longer tracked (${hd.resolved.length}):`);
|
|
184
|
+
for (const delta of hd.resolved.slice(0, 5)) {
|
|
185
|
+
console.log(` ${chalk.green('—')} ${delta.relativePath}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
159
190
|
console.log(`\n Baseline: ${chalk.dim(diff.before.timestamp)}`);
|
|
160
191
|
console.log('');
|
|
161
192
|
}
|
|
@@ -279,4 +310,100 @@ export function reportDependencies(report) {
|
|
|
279
310
|
}
|
|
280
311
|
console.log('');
|
|
281
312
|
}
|
|
313
|
+
// ── Report: hotspots ──────────────────────────────────────
|
|
314
|
+
export function reportHotspots(report) {
|
|
315
|
+
console.log(header('Project Hotspots'));
|
|
316
|
+
if (!report.available) {
|
|
317
|
+
console.log(`\n ${chalk.yellow('⚠')} ${report.reason ?? 'Hotspot analysis unavailable.'}\n`);
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
if (report.hotspots.length === 0) {
|
|
321
|
+
console.log(`\n ${chalk.green('✓')} No hotspots detected.`);
|
|
322
|
+
console.log(chalk.dim(` Scanned ${report.window.commitsScanned} commit${report.window.commitsScanned === 1 ? '' : 's'} since ${report.window.since}.\n`));
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
console.log(chalk.dim(`\n ${report.window.commitsScanned} commit${report.window.commitsScanned === 1 ? '' : 's'} since ${report.window.since} · ${report.totalFilesRanked} file${report.totalFilesRanked === 1 ? '' : 's'} ranked\n`));
|
|
326
|
+
const maxScore = report.hotspots[0]?.riskScore ?? 1;
|
|
327
|
+
for (let i = 0; i < report.hotspots.length; i++) {
|
|
328
|
+
const h = report.hotspots[i];
|
|
329
|
+
const rank = chalk.bold(String(i + 1).padStart(2, ' ') + '.');
|
|
330
|
+
const scoreLabel = chalk.bold(h.riskScore.toFixed(1).padStart(5, ' '));
|
|
331
|
+
const barPct = Math.min(100, Math.round((h.riskScore / maxScore) * 100));
|
|
332
|
+
console.log(` ${rank} ${scoreLabel} ${bar(barPct, 14)} ${chalk.cyan(h.relativePath)}`);
|
|
333
|
+
const reasonStr = h.reasons.length > 0 ? h.reasons.join(', ') : 'ranked by risk';
|
|
334
|
+
console.log(` ${chalk.dim(reasonStr)}`);
|
|
335
|
+
}
|
|
336
|
+
console.log(chalk.dim(`\n Tip: run ${chalk.bold.cyan('projscan file <file>')} to drill into a hotspot.\n`));
|
|
337
|
+
}
|
|
338
|
+
// ── Report: file (drill-down) ─────────────────────────────
|
|
339
|
+
export function reportFileInspection(insp) {
|
|
340
|
+
console.log(header('File Report'));
|
|
341
|
+
if (!insp.exists) {
|
|
342
|
+
console.log(`\n ${chalk.red('✗')} ${insp.reason ?? 'File unavailable.'}\n`);
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
console.log(`\n ${chalk.bold('File:')} ${insp.relativePath}`);
|
|
346
|
+
console.log(` ${chalk.bold('Purpose:')} ${insp.purpose}`);
|
|
347
|
+
console.log(` ${chalk.bold('Lines:')} ${insp.lineCount}`);
|
|
348
|
+
console.log(` ${chalk.bold('Size:')} ${formatSize(insp.sizeBytes)}`);
|
|
349
|
+
if (insp.hotspot) {
|
|
350
|
+
const h = insp.hotspot;
|
|
351
|
+
console.log(header('Risk'));
|
|
352
|
+
console.log(` ${chalk.bold('Risk Score:')} ${chalk.bold(h.riskScore.toFixed(1))}`);
|
|
353
|
+
console.log(` ${chalk.bold('Commits:')} ${h.churn}`);
|
|
354
|
+
console.log(` ${chalk.bold('Authors:')} ${h.distinctAuthors}${h.primaryAuthor ? ` (primary: ${formatAuthorEmail(h.primaryAuthor)}, ${Math.round(h.primaryAuthorShare * 100)}%)` : ''}`);
|
|
355
|
+
if (h.daysSinceLastChange !== null) {
|
|
356
|
+
console.log(` ${chalk.bold('Last change:')} ${h.daysSinceLastChange} days ago`);
|
|
357
|
+
}
|
|
358
|
+
if (h.busFactorOne) {
|
|
359
|
+
console.log(` ${chalk.red('⚠')} Bus factor 1 — only one author has touched this.`);
|
|
360
|
+
}
|
|
361
|
+
if (h.reasons.length > 0) {
|
|
362
|
+
console.log(` ${chalk.dim(h.reasons.join(', '))}`);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
console.log(chalk.dim('\n No hotspot data (file is untouched in git window or outside analysis scope).'));
|
|
367
|
+
}
|
|
368
|
+
if (insp.issues.length > 0) {
|
|
369
|
+
console.log(header('Related Issues'));
|
|
370
|
+
for (const issue of insp.issues) {
|
|
371
|
+
console.log(` ${severityIcon(issue.severity)} ${issue.title}`);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (insp.potentialIssues.length > 0) {
|
|
375
|
+
console.log(header('Potential Issues'));
|
|
376
|
+
for (const issue of insp.potentialIssues) {
|
|
377
|
+
console.log(` ${chalk.yellow('⚠')} ${issue}`);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (insp.imports.length > 0) {
|
|
381
|
+
console.log(header('Dependencies'));
|
|
382
|
+
for (const imp of insp.imports.slice(0, 20)) {
|
|
383
|
+
const prefix = imp.isRelative ? chalk.dim('(local)') : chalk.cyan('(package)');
|
|
384
|
+
console.log(` ${prefix} ${imp.source}`);
|
|
385
|
+
}
|
|
386
|
+
if (insp.imports.length > 20) {
|
|
387
|
+
console.log(chalk.dim(` ... and ${insp.imports.length - 20} more`));
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (insp.exports.length > 0) {
|
|
391
|
+
console.log(header('Exports'));
|
|
392
|
+
for (const exp of insp.exports) {
|
|
393
|
+
console.log(` ${chalk.dim('•')} ${chalk.bold(exp.name)} ${chalk.dim(`(${exp.type})`)}`);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
console.log('');
|
|
397
|
+
}
|
|
398
|
+
function formatSize(bytes) {
|
|
399
|
+
if (bytes < 1024)
|
|
400
|
+
return `${bytes} B`;
|
|
401
|
+
if (bytes < 1024 * 1024)
|
|
402
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
403
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
404
|
+
}
|
|
405
|
+
function formatAuthorEmail(email) {
|
|
406
|
+
const at = email.indexOf('@');
|
|
407
|
+
return at > 0 ? email.slice(0, at) : email;
|
|
408
|
+
}
|
|
282
409
|
//# sourceMappingURL=consoleReporter.js.map
|