claude-crap 0.3.6 → 0.3.7
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/dist/dashboard/file-detail.d.ts +77 -0
- package/dist/dashboard/file-detail.d.ts.map +1 -0
- package/dist/dashboard/file-detail.js +120 -0
- package/dist/dashboard/file-detail.js.map +1 -0
- package/dist/dashboard/server.d.ts +3 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +108 -1
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +19 -2
- package/dist/index.js.map +1 -1
- package/dist/scanner/auto-scan.d.ts +8 -1
- package/dist/scanner/auto-scan.d.ts.map +1 -1
- package/dist/scanner/auto-scan.js +14 -1
- package/dist/scanner/auto-scan.js.map +1 -1
- package/dist/scanner/complexity-scanner.d.ts +54 -0
- package/dist/scanner/complexity-scanner.d.ts.map +1 -0
- package/dist/scanner/complexity-scanner.js +176 -0
- package/dist/scanner/complexity-scanner.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/bundle/dashboard/public/index.html +432 -12
- package/plugin/bundle/mcp-server.mjs +429 -71
- package/plugin/bundle/mcp-server.mjs.map +4 -4
- package/plugin/package-lock.json +2 -2
- package/plugin/package.json +1 -1
- package/src/dashboard/file-detail.ts +197 -0
- package/src/dashboard/public/index.html +432 -12
- package/src/dashboard/server.ts +141 -1
- package/src/index.ts +20 -2
- package/src/scanner/auto-scan.ts +26 -0
- package/src/scanner/complexity-scanner.ts +233 -0
- package/src/tests/complexity-scanner.test.ts +263 -0
- package/src/tests/file-detail-api.test.ts +258 -0
package/plugin/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-crap-plugin",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-crap-plugin",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.7",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@fastify/static": "^8.0.3",
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
package/plugin/package.json
CHANGED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File detail builder for the dashboard.
|
|
3
|
+
*
|
|
4
|
+
* Given a workspace-relative file path, this module produces a rich
|
|
5
|
+
* detail payload combining source code, per-function AST metrics, and
|
|
6
|
+
* SARIF findings filtered to that file. The dashboard uses this to
|
|
7
|
+
* render a ReportGenerator-style annotated code view.
|
|
8
|
+
*
|
|
9
|
+
* The builder is extracted into its own module (rather than inlined in
|
|
10
|
+
* `server.ts`) so that:
|
|
11
|
+
* - The logic is unit-testable without booting the HTTP server.
|
|
12
|
+
* - The types are importable by both the Fastify route and tests.
|
|
13
|
+
*
|
|
14
|
+
* @module dashboard/file-detail
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { promises as fs } from "node:fs";
|
|
18
|
+
import { join } from "node:path";
|
|
19
|
+
|
|
20
|
+
import { resolveWithinWorkspace } from "../workspace-guard.js";
|
|
21
|
+
import { detectLanguageFromPath, type SupportedLanguage } from "../ast/language-config.js";
|
|
22
|
+
import type { TreeSitterEngine, FunctionMetrics } from "../ast/tree-sitter-engine.js";
|
|
23
|
+
import type { SarifStore, IngestedFinding } from "../sarif/sarif-store.js";
|
|
24
|
+
|
|
25
|
+
// ── Types ─────────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/** Per-function entry in the detail response. */
|
|
28
|
+
export interface FileDetailFunction {
|
|
29
|
+
readonly name: string;
|
|
30
|
+
readonly startLine: number;
|
|
31
|
+
readonly endLine: number;
|
|
32
|
+
readonly cyclomaticComplexity: number;
|
|
33
|
+
readonly lineCount: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Per-finding entry in the detail response. */
|
|
37
|
+
export interface FileDetailFinding {
|
|
38
|
+
readonly ruleId: string;
|
|
39
|
+
readonly level: string;
|
|
40
|
+
readonly message: string;
|
|
41
|
+
readonly sourceTool: string;
|
|
42
|
+
readonly startLine: number;
|
|
43
|
+
readonly startColumn: number;
|
|
44
|
+
readonly endLine: number;
|
|
45
|
+
readonly endColumn: number;
|
|
46
|
+
readonly effortMinutes: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Summary statistics for the file. */
|
|
50
|
+
export interface FileDetailSummary {
|
|
51
|
+
readonly totalFindings: number;
|
|
52
|
+
readonly errorCount: number;
|
|
53
|
+
readonly warningCount: number;
|
|
54
|
+
readonly noteCount: number;
|
|
55
|
+
readonly totalEffortMinutes: number;
|
|
56
|
+
readonly avgComplexity: number;
|
|
57
|
+
readonly maxComplexity: number;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Full response payload for the file detail endpoint. */
|
|
61
|
+
export interface FileDetailResponse {
|
|
62
|
+
readonly filePath: string;
|
|
63
|
+
readonly language: SupportedLanguage | null;
|
|
64
|
+
readonly physicalLoc: number;
|
|
65
|
+
readonly logicalLoc: number;
|
|
66
|
+
readonly cyclomaticMax: number;
|
|
67
|
+
readonly sourceLines: string[];
|
|
68
|
+
readonly functions: FileDetailFunction[];
|
|
69
|
+
readonly findings: FileDetailFinding[];
|
|
70
|
+
readonly summary: FileDetailSummary;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Input accepted by {@link buildFileDetail}. */
|
|
74
|
+
export interface BuildFileDetailInput {
|
|
75
|
+
readonly relativePath: string;
|
|
76
|
+
readonly workspaceRoot: string;
|
|
77
|
+
readonly astEngine?: TreeSitterEngine | undefined;
|
|
78
|
+
readonly sarifStore: SarifStore;
|
|
79
|
+
readonly cyclomaticMax: number;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ── Builder ───────────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Build the file detail payload. Pure function aside from the file
|
|
86
|
+
* read and the tree-sitter analysis (both deterministic for a given
|
|
87
|
+
* file).
|
|
88
|
+
*
|
|
89
|
+
* @throws When the file does not exist or the path escapes the workspace.
|
|
90
|
+
*/
|
|
91
|
+
export async function buildFileDetail(
|
|
92
|
+
input: BuildFileDetailInput,
|
|
93
|
+
): Promise<FileDetailResponse> {
|
|
94
|
+
const { relativePath, workspaceRoot, astEngine, sarifStore, cyclomaticMax } = input;
|
|
95
|
+
|
|
96
|
+
// 1. Guard against path traversal
|
|
97
|
+
const absolutePath = resolveWithinWorkspace(workspaceRoot, relativePath);
|
|
98
|
+
|
|
99
|
+
// 2. Read source
|
|
100
|
+
const source = await fs.readFile(absolutePath, "utf8");
|
|
101
|
+
const sourceLines = source.split(/\r?\n/);
|
|
102
|
+
// Remove trailing empty line from files ending with \n
|
|
103
|
+
if (sourceLines.length > 0 && sourceLines[sourceLines.length - 1] === "") {
|
|
104
|
+
sourceLines.pop();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const physicalLoc = sourceLines.length;
|
|
108
|
+
let logicalLoc = 0;
|
|
109
|
+
for (const line of sourceLines) {
|
|
110
|
+
if (line.trim().length > 0) logicalLoc += 1;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 3. AST analysis (if language is supported)
|
|
114
|
+
const language = detectLanguageFromPath(relativePath);
|
|
115
|
+
let functions: FileDetailFunction[] = [];
|
|
116
|
+
|
|
117
|
+
if (language && astEngine) {
|
|
118
|
+
try {
|
|
119
|
+
const metrics = await astEngine.analyzeFile({
|
|
120
|
+
filePath: absolutePath,
|
|
121
|
+
language,
|
|
122
|
+
});
|
|
123
|
+
functions = metrics.functions.map((fn: FunctionMetrics) => ({
|
|
124
|
+
name: fn.name,
|
|
125
|
+
startLine: fn.startLine,
|
|
126
|
+
endLine: fn.endLine,
|
|
127
|
+
cyclomaticComplexity: fn.cyclomaticComplexity,
|
|
128
|
+
lineCount: fn.lineCount,
|
|
129
|
+
}));
|
|
130
|
+
} catch {
|
|
131
|
+
// Analysis failure is non-fatal — return empty functions
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// 4. Filter SARIF findings for this file
|
|
136
|
+
const allFindings = sarifStore.list();
|
|
137
|
+
const fileFindings = allFindings.filter(
|
|
138
|
+
(f: IngestedFinding) => f.location.uri === relativePath,
|
|
139
|
+
);
|
|
140
|
+
|
|
141
|
+
const findings: FileDetailFinding[] = fileFindings.map((f: IngestedFinding) => ({
|
|
142
|
+
ruleId: f.ruleId,
|
|
143
|
+
level: f.level,
|
|
144
|
+
message: f.message,
|
|
145
|
+
sourceTool: f.sourceTool,
|
|
146
|
+
startLine: f.location.startLine,
|
|
147
|
+
startColumn: f.location.startColumn,
|
|
148
|
+
endLine: f.location.endLine ?? f.location.startLine,
|
|
149
|
+
endColumn: f.location.endColumn ?? 0,
|
|
150
|
+
effortMinutes:
|
|
151
|
+
typeof f.properties?.effortMinutes === "number"
|
|
152
|
+
? f.properties.effortMinutes
|
|
153
|
+
: 0,
|
|
154
|
+
}));
|
|
155
|
+
|
|
156
|
+
// 5. Build summary
|
|
157
|
+
let errorCount = 0;
|
|
158
|
+
let warningCount = 0;
|
|
159
|
+
let noteCount = 0;
|
|
160
|
+
let totalEffortMinutes = 0;
|
|
161
|
+
|
|
162
|
+
for (const f of findings) {
|
|
163
|
+
if (f.level === "error") errorCount += 1;
|
|
164
|
+
else if (f.level === "warning") warningCount += 1;
|
|
165
|
+
else if (f.level === "note") noteCount += 1;
|
|
166
|
+
totalEffortMinutes += f.effortMinutes;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const complexities = functions.map((f) => f.cyclomaticComplexity);
|
|
170
|
+
const maxComplexity = complexities.length > 0 ? Math.max(...complexities) : 0;
|
|
171
|
+
const avgComplexity =
|
|
172
|
+
complexities.length > 0
|
|
173
|
+
? Math.round(
|
|
174
|
+
(complexities.reduce((a, b) => a + b, 0) / complexities.length) * 100,
|
|
175
|
+
) / 100
|
|
176
|
+
: 0;
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
filePath: relativePath,
|
|
180
|
+
language,
|
|
181
|
+
physicalLoc,
|
|
182
|
+
logicalLoc,
|
|
183
|
+
cyclomaticMax,
|
|
184
|
+
sourceLines,
|
|
185
|
+
functions,
|
|
186
|
+
findings,
|
|
187
|
+
summary: {
|
|
188
|
+
totalFindings: findings.length,
|
|
189
|
+
errorCount,
|
|
190
|
+
warningCount,
|
|
191
|
+
noteCount,
|
|
192
|
+
totalEffortMinutes,
|
|
193
|
+
avgComplexity,
|
|
194
|
+
maxComplexity,
|
|
195
|
+
},
|
|
196
|
+
};
|
|
197
|
+
}
|