claude-crap 0.3.7 → 0.3.8
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 +25 -0
- package/dist/adapters/common.d.ts +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +1 -1
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/dart-analyzer.d.ts +41 -0
- package/dist/adapters/dart-analyzer.d.ts.map +1 -0
- package/dist/adapters/dart-analyzer.js +120 -0
- package/dist/adapters/dart-analyzer.js.map +1 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.d.ts.map +1 -1
- package/dist/adapters/index.js +4 -0
- package/dist/adapters/index.js.map +1 -1
- package/dist/crap-config.d.ts +2 -0
- package/dist/crap-config.d.ts.map +1 -1
- package/dist/crap-config.js +36 -28
- package/dist/crap-config.js.map +1 -1
- package/dist/dashboard/file-detail.d.ts.map +1 -1
- package/dist/dashboard/file-detail.js.map +1 -1
- package/dist/dashboard/server.d.ts +2 -0
- package/dist/dashboard/server.d.ts.map +1 -1
- package/dist/dashboard/server.js +7 -12
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +17 -2
- package/dist/index.js.map +1 -1
- package/dist/metrics/workspace-walker.d.ts +4 -1
- package/dist/metrics/workspace-walker.d.ts.map +1 -1
- package/dist/metrics/workspace-walker.js +12 -28
- package/dist/metrics/workspace-walker.js.map +1 -1
- package/dist/scanner/auto-scan.d.ts +1 -0
- package/dist/scanner/auto-scan.d.ts.map +1 -1
- package/dist/scanner/auto-scan.js +14 -5
- package/dist/scanner/auto-scan.js.map +1 -1
- package/dist/scanner/bootstrap.d.ts +1 -1
- package/dist/scanner/bootstrap.d.ts.map +1 -1
- package/dist/scanner/bootstrap.js +9 -0
- package/dist/scanner/bootstrap.js.map +1 -1
- package/dist/scanner/complexity-scanner.d.ts +2 -0
- package/dist/scanner/complexity-scanner.d.ts.map +1 -1
- package/dist/scanner/complexity-scanner.js +11 -26
- package/dist/scanner/complexity-scanner.js.map +1 -1
- package/dist/scanner/detector.d.ts +24 -4
- package/dist/scanner/detector.d.ts.map +1 -1
- package/dist/scanner/detector.js +105 -10
- package/dist/scanner/detector.js.map +1 -1
- package/dist/scanner/runner.d.ts +4 -1
- package/dist/scanner/runner.d.ts.map +1 -1
- package/dist/scanner/runner.js +12 -3
- package/dist/scanner/runner.js.map +1 -1
- package/dist/schemas/tool-schemas.d.ts +1 -1
- package/dist/schemas/tool-schemas.js +1 -1
- package/dist/schemas/tool-schemas.js.map +1 -1
- package/dist/shared/exclusions.d.ts +53 -0
- package/dist/shared/exclusions.d.ts.map +1 -0
- package/dist/shared/exclusions.js +126 -0
- package/dist/shared/exclusions.js.map +1 -0
- package/package.json +3 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/bundle/mcp-server.mjs +393 -141
- package/plugin/bundle/mcp-server.mjs.map +4 -4
- package/plugin/package-lock.json +15 -2
- package/plugin/package.json +2 -1
- package/scripts/bundle-plugin.mjs +2 -1
- package/src/adapters/common.ts +1 -1
- package/src/adapters/dart-analyzer.ts +161 -0
- package/src/adapters/index.ts +4 -0
- package/src/crap-config.ts +55 -18
- package/src/dashboard/file-detail.ts +0 -2
- package/src/dashboard/server.ts +9 -10
- package/src/index.ts +17 -2
- package/src/metrics/workspace-walker.ts +15 -27
- package/src/scanner/auto-scan.ts +17 -6
- package/src/scanner/bootstrap.ts +11 -0
- package/src/scanner/complexity-scanner.ts +15 -26
- package/src/scanner/detector.ts +114 -10
- package/src/scanner/runner.ts +12 -2
- package/src/schemas/tool-schemas.ts +1 -1
- package/src/shared/exclusions.ts +156 -0
- package/src/tests/adapters/dispatch.test.ts +2 -2
- package/src/tests/auto-scan.test.ts +2 -2
- package/src/tests/exclusions.test.ts +117 -0
- package/src/tests/scanner-detector.test.ts +31 -11
|
@@ -2977,7 +2977,7 @@ var require_compile = __commonJS({
|
|
|
2977
2977
|
const schOrFunc = root.refs[ref];
|
|
2978
2978
|
if (schOrFunc)
|
|
2979
2979
|
return schOrFunc;
|
|
2980
|
-
let _sch =
|
|
2980
|
+
let _sch = resolve7.call(this, root, ref);
|
|
2981
2981
|
if (_sch === void 0) {
|
|
2982
2982
|
const schema = (_a = root.localRefs) === null || _a === void 0 ? void 0 : _a[ref];
|
|
2983
2983
|
const { schemaId } = this.opts;
|
|
@@ -3004,7 +3004,7 @@ var require_compile = __commonJS({
|
|
|
3004
3004
|
function sameSchemaEnv(s1, s2) {
|
|
3005
3005
|
return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
|
|
3006
3006
|
}
|
|
3007
|
-
function
|
|
3007
|
+
function resolve7(root, ref) {
|
|
3008
3008
|
let sch;
|
|
3009
3009
|
while (typeof (sch = this.refs[ref]) == "string")
|
|
3010
3010
|
ref = sch;
|
|
@@ -3579,55 +3579,55 @@ var require_fast_uri = __commonJS({
|
|
|
3579
3579
|
}
|
|
3580
3580
|
return uri;
|
|
3581
3581
|
}
|
|
3582
|
-
function
|
|
3582
|
+
function resolve7(baseURI, relativeURI, options) {
|
|
3583
3583
|
const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
|
|
3584
3584
|
const resolved = resolveComponent(parse(baseURI, schemelessOptions), parse(relativeURI, schemelessOptions), schemelessOptions, true);
|
|
3585
3585
|
schemelessOptions.skipEscape = true;
|
|
3586
3586
|
return serialize(resolved, schemelessOptions);
|
|
3587
3587
|
}
|
|
3588
|
-
function resolveComponent(base,
|
|
3588
|
+
function resolveComponent(base, relative4, options, skipNormalization) {
|
|
3589
3589
|
const target = {};
|
|
3590
3590
|
if (!skipNormalization) {
|
|
3591
3591
|
base = parse(serialize(base, options), options);
|
|
3592
|
-
|
|
3592
|
+
relative4 = parse(serialize(relative4, options), options);
|
|
3593
3593
|
}
|
|
3594
3594
|
options = options || {};
|
|
3595
|
-
if (!options.tolerant &&
|
|
3596
|
-
target.scheme =
|
|
3597
|
-
target.userinfo =
|
|
3598
|
-
target.host =
|
|
3599
|
-
target.port =
|
|
3600
|
-
target.path = removeDotSegments(
|
|
3601
|
-
target.query =
|
|
3595
|
+
if (!options.tolerant && relative4.scheme) {
|
|
3596
|
+
target.scheme = relative4.scheme;
|
|
3597
|
+
target.userinfo = relative4.userinfo;
|
|
3598
|
+
target.host = relative4.host;
|
|
3599
|
+
target.port = relative4.port;
|
|
3600
|
+
target.path = removeDotSegments(relative4.path || "");
|
|
3601
|
+
target.query = relative4.query;
|
|
3602
3602
|
} else {
|
|
3603
|
-
if (
|
|
3604
|
-
target.userinfo =
|
|
3605
|
-
target.host =
|
|
3606
|
-
target.port =
|
|
3607
|
-
target.path = removeDotSegments(
|
|
3608
|
-
target.query =
|
|
3603
|
+
if (relative4.userinfo !== void 0 || relative4.host !== void 0 || relative4.port !== void 0) {
|
|
3604
|
+
target.userinfo = relative4.userinfo;
|
|
3605
|
+
target.host = relative4.host;
|
|
3606
|
+
target.port = relative4.port;
|
|
3607
|
+
target.path = removeDotSegments(relative4.path || "");
|
|
3608
|
+
target.query = relative4.query;
|
|
3609
3609
|
} else {
|
|
3610
|
-
if (!
|
|
3610
|
+
if (!relative4.path) {
|
|
3611
3611
|
target.path = base.path;
|
|
3612
|
-
if (
|
|
3613
|
-
target.query =
|
|
3612
|
+
if (relative4.query !== void 0) {
|
|
3613
|
+
target.query = relative4.query;
|
|
3614
3614
|
} else {
|
|
3615
3615
|
target.query = base.query;
|
|
3616
3616
|
}
|
|
3617
3617
|
} else {
|
|
3618
|
-
if (
|
|
3619
|
-
target.path = removeDotSegments(
|
|
3618
|
+
if (relative4.path[0] === "/") {
|
|
3619
|
+
target.path = removeDotSegments(relative4.path);
|
|
3620
3620
|
} else {
|
|
3621
3621
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
3622
|
-
target.path = "/" +
|
|
3622
|
+
target.path = "/" + relative4.path;
|
|
3623
3623
|
} else if (!base.path) {
|
|
3624
|
-
target.path =
|
|
3624
|
+
target.path = relative4.path;
|
|
3625
3625
|
} else {
|
|
3626
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
3626
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative4.path;
|
|
3627
3627
|
}
|
|
3628
3628
|
target.path = removeDotSegments(target.path);
|
|
3629
3629
|
}
|
|
3630
|
-
target.query =
|
|
3630
|
+
target.query = relative4.query;
|
|
3631
3631
|
}
|
|
3632
3632
|
target.userinfo = base.userinfo;
|
|
3633
3633
|
target.host = base.host;
|
|
@@ -3635,7 +3635,7 @@ var require_fast_uri = __commonJS({
|
|
|
3635
3635
|
}
|
|
3636
3636
|
target.scheme = base.scheme;
|
|
3637
3637
|
}
|
|
3638
|
-
target.fragment =
|
|
3638
|
+
target.fragment = relative4.fragment;
|
|
3639
3639
|
return target;
|
|
3640
3640
|
}
|
|
3641
3641
|
function equal(uriA, uriB, options) {
|
|
@@ -3806,7 +3806,7 @@ var require_fast_uri = __commonJS({
|
|
|
3806
3806
|
var fastUri = {
|
|
3807
3807
|
SCHEMES,
|
|
3808
3808
|
normalize,
|
|
3809
|
-
resolve:
|
|
3809
|
+
resolve: resolve7,
|
|
3810
3810
|
resolveComponent,
|
|
3811
3811
|
equal,
|
|
3812
3812
|
serialize,
|
|
@@ -6853,6 +6853,84 @@ function buildSarifResult3(opts) {
|
|
|
6853
6853
|
};
|
|
6854
6854
|
}
|
|
6855
6855
|
|
|
6856
|
+
// src/adapters/dart-analyzer.ts
|
|
6857
|
+
function mapSeverity3(dartSeverity) {
|
|
6858
|
+
switch (dartSeverity.toUpperCase()) {
|
|
6859
|
+
case "ERROR":
|
|
6860
|
+
return "error";
|
|
6861
|
+
case "WARNING":
|
|
6862
|
+
return "warning";
|
|
6863
|
+
case "INFO":
|
|
6864
|
+
return "note";
|
|
6865
|
+
default:
|
|
6866
|
+
return "warning";
|
|
6867
|
+
}
|
|
6868
|
+
}
|
|
6869
|
+
var EFFORT_BY_SEVERITY = {
|
|
6870
|
+
error: 30,
|
|
6871
|
+
warning: 15,
|
|
6872
|
+
note: 5,
|
|
6873
|
+
none: 0
|
|
6874
|
+
};
|
|
6875
|
+
function adaptDartAnalyzer(rawOutput) {
|
|
6876
|
+
let parsed;
|
|
6877
|
+
if (typeof rawOutput === "string") {
|
|
6878
|
+
try {
|
|
6879
|
+
parsed = JSON.parse(rawOutput);
|
|
6880
|
+
} catch {
|
|
6881
|
+
throw new Error("[dart-analyzer adapter] rawOutput is not valid JSON");
|
|
6882
|
+
}
|
|
6883
|
+
} else if (rawOutput && typeof rawOutput === "object" && "diagnostics" in rawOutput) {
|
|
6884
|
+
parsed = rawOutput;
|
|
6885
|
+
} else {
|
|
6886
|
+
throw new Error(
|
|
6887
|
+
"[dart-analyzer adapter] rawOutput must be a JSON string or an object with a 'diagnostics' array"
|
|
6888
|
+
);
|
|
6889
|
+
}
|
|
6890
|
+
if (!Array.isArray(parsed.diagnostics)) {
|
|
6891
|
+
throw new Error("[dart-analyzer adapter] 'diagnostics' must be an array");
|
|
6892
|
+
}
|
|
6893
|
+
const results = [];
|
|
6894
|
+
let totalEffortMinutes = 0;
|
|
6895
|
+
for (const diag of parsed.diagnostics) {
|
|
6896
|
+
const level = mapSeverity3(diag.severity);
|
|
6897
|
+
const effort = EFFORT_BY_SEVERITY[level] ?? estimateEffortMinutes(level);
|
|
6898
|
+
totalEffortMinutes += effort;
|
|
6899
|
+
results.push({
|
|
6900
|
+
ruleId: diag.code,
|
|
6901
|
+
level,
|
|
6902
|
+
message: {
|
|
6903
|
+
text: diag.problemMessage + (diag.correctionMessage ? ` ${diag.correctionMessage}` : "")
|
|
6904
|
+
},
|
|
6905
|
+
locations: [
|
|
6906
|
+
{
|
|
6907
|
+
physicalLocation: {
|
|
6908
|
+
artifactLocation: {
|
|
6909
|
+
uri: diag.location.file
|
|
6910
|
+
},
|
|
6911
|
+
region: {
|
|
6912
|
+
startLine: diag.location.range.start.line,
|
|
6913
|
+
startColumn: diag.location.range.start.column,
|
|
6914
|
+
endLine: diag.location.range.end.line,
|
|
6915
|
+
endColumn: diag.location.range.end.column
|
|
6916
|
+
}
|
|
6917
|
+
}
|
|
6918
|
+
}
|
|
6919
|
+
],
|
|
6920
|
+
properties: {
|
|
6921
|
+
effortMinutes: effort,
|
|
6922
|
+
...diag.documentation ? { helpUri: diag.documentation } : {}
|
|
6923
|
+
}
|
|
6924
|
+
});
|
|
6925
|
+
}
|
|
6926
|
+
return {
|
|
6927
|
+
document: wrapResultsInSarif("dart_analyze", "1.0.0", results),
|
|
6928
|
+
sourceTool: "dart_analyze",
|
|
6929
|
+
findingCount: parsed.diagnostics.length,
|
|
6930
|
+
totalEffortMinutes
|
|
6931
|
+
};
|
|
6932
|
+
}
|
|
6933
|
+
|
|
6856
6934
|
// src/adapters/index.ts
|
|
6857
6935
|
function adaptScannerOutput(scanner, rawOutput) {
|
|
6858
6936
|
switch (scanner) {
|
|
@@ -6864,6 +6942,8 @@ function adaptScannerOutput(scanner, rawOutput) {
|
|
|
6864
6942
|
return adaptBandit(rawOutput);
|
|
6865
6943
|
case "stryker":
|
|
6866
6944
|
return adaptStryker(rawOutput);
|
|
6945
|
+
case "dart_analyze":
|
|
6946
|
+
return adaptDartAnalyzer(rawOutput);
|
|
6867
6947
|
default: {
|
|
6868
6948
|
const exhaustive = scanner;
|
|
6869
6949
|
throw new Error(`[adapters] Unknown scanner: ${String(exhaustive)}`);
|
|
@@ -7250,6 +7330,100 @@ import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
|
7250
7330
|
import Fastify from "fastify";
|
|
7251
7331
|
import fastifyStatic from "@fastify/static";
|
|
7252
7332
|
|
|
7333
|
+
// src/shared/exclusions.ts
|
|
7334
|
+
import picomatch from "picomatch";
|
|
7335
|
+
var DEFAULT_SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
7336
|
+
// Package managers / vendored deps
|
|
7337
|
+
"node_modules",
|
|
7338
|
+
"vendor",
|
|
7339
|
+
// Version control
|
|
7340
|
+
".git",
|
|
7341
|
+
// Build outputs (general)
|
|
7342
|
+
"dist",
|
|
7343
|
+
"build",
|
|
7344
|
+
"bundle",
|
|
7345
|
+
"out",
|
|
7346
|
+
"target",
|
|
7347
|
+
"coverage",
|
|
7348
|
+
// Framework build outputs
|
|
7349
|
+
".next",
|
|
7350
|
+
// Next.js
|
|
7351
|
+
".nuxt",
|
|
7352
|
+
// Nuxt 2
|
|
7353
|
+
".output",
|
|
7354
|
+
// Nuxt 3
|
|
7355
|
+
".vercel",
|
|
7356
|
+
// Vercel
|
|
7357
|
+
".svelte-kit",
|
|
7358
|
+
// SvelteKit
|
|
7359
|
+
".astro",
|
|
7360
|
+
// Astro
|
|
7361
|
+
".angular",
|
|
7362
|
+
// Angular
|
|
7363
|
+
".turbo",
|
|
7364
|
+
// Turborepo
|
|
7365
|
+
".parcel-cache",
|
|
7366
|
+
// Parcel
|
|
7367
|
+
".expo",
|
|
7368
|
+
// Expo / React Native
|
|
7369
|
+
// Language-specific caches
|
|
7370
|
+
".venv",
|
|
7371
|
+
"venv",
|
|
7372
|
+
"__pycache__",
|
|
7373
|
+
".cache",
|
|
7374
|
+
".dart_tool",
|
|
7375
|
+
// Dart / Flutter
|
|
7376
|
+
".gradle",
|
|
7377
|
+
// Gradle
|
|
7378
|
+
// IDE state
|
|
7379
|
+
".idea",
|
|
7380
|
+
// Plugin state
|
|
7381
|
+
".claude-crap",
|
|
7382
|
+
".codesight"
|
|
7383
|
+
]);
|
|
7384
|
+
var DEFAULT_SKIP_PATTERNS = [
|
|
7385
|
+
"*.min.js",
|
|
7386
|
+
"*.min.css",
|
|
7387
|
+
"*.min.mjs",
|
|
7388
|
+
"*.min.cjs",
|
|
7389
|
+
"*.bundle.js",
|
|
7390
|
+
"*.chunk.js"
|
|
7391
|
+
];
|
|
7392
|
+
function createExclusionFilter(userExclusions) {
|
|
7393
|
+
const extraDirs = /* @__PURE__ */ new Set();
|
|
7394
|
+
const fileGlobs = [];
|
|
7395
|
+
for (const pattern of userExclusions ?? []) {
|
|
7396
|
+
if (pattern.endsWith("/")) {
|
|
7397
|
+
extraDirs.add(pattern.slice(0, -1));
|
|
7398
|
+
} else {
|
|
7399
|
+
fileGlobs.push(pattern);
|
|
7400
|
+
}
|
|
7401
|
+
}
|
|
7402
|
+
const defaultFileMatchers = DEFAULT_SKIP_PATTERNS.map(
|
|
7403
|
+
(p) => picomatch(p, { dot: true })
|
|
7404
|
+
);
|
|
7405
|
+
const userFileMatchers = fileGlobs.map(
|
|
7406
|
+
(p) => picomatch(p, { dot: true })
|
|
7407
|
+
);
|
|
7408
|
+
return {
|
|
7409
|
+
shouldSkipDir(dirName) {
|
|
7410
|
+
if (dirName.startsWith(".") && dirName !== ".claude-plugin") {
|
|
7411
|
+
return DEFAULT_SKIP_DIRS.has(dirName) || true;
|
|
7412
|
+
}
|
|
7413
|
+
return DEFAULT_SKIP_DIRS.has(dirName) || extraDirs.has(dirName);
|
|
7414
|
+
},
|
|
7415
|
+
shouldSkipFile(relativePath, fileName) {
|
|
7416
|
+
for (const matcher of defaultFileMatchers) {
|
|
7417
|
+
if (matcher(fileName)) return true;
|
|
7418
|
+
}
|
|
7419
|
+
for (const matcher of userFileMatchers) {
|
|
7420
|
+
if (matcher(relativePath) || matcher(fileName)) return true;
|
|
7421
|
+
}
|
|
7422
|
+
return false;
|
|
7423
|
+
}
|
|
7424
|
+
};
|
|
7425
|
+
}
|
|
7426
|
+
|
|
7253
7427
|
// src/metrics/tdr.ts
|
|
7254
7428
|
var RATING_ORDER = ["A", "B", "C", "D", "E"];
|
|
7255
7429
|
function ratingToRank(rating) {
|
|
@@ -7524,7 +7698,7 @@ async function startDashboard(options) {
|
|
|
7524
7698
|
root: publicRoot,
|
|
7525
7699
|
prefix: "/"
|
|
7526
7700
|
});
|
|
7527
|
-
fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.3.
|
|
7701
|
+
fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.3.8" }));
|
|
7528
7702
|
fastify.get("/api/score", async () => {
|
|
7529
7703
|
const stats = await workspaceStatsProvider();
|
|
7530
7704
|
const score = await buildScore(config, sarifStore, stats, urlOf(fastify, config));
|
|
@@ -7535,7 +7709,7 @@ async function startDashboard(options) {
|
|
|
7535
7709
|
if (!options.astEngine) {
|
|
7536
7710
|
return { threshold: config.cyclomaticMax, totalFunctions: 0, violationCount: 0, topFunctions: [] };
|
|
7537
7711
|
}
|
|
7538
|
-
return buildComplexityReport(config, options.astEngine, logger2);
|
|
7712
|
+
return buildComplexityReport(config, options.astEngine, logger2, options.exclude);
|
|
7539
7713
|
});
|
|
7540
7714
|
fastify.get("/api/file-detail", async (request, reply) => {
|
|
7541
7715
|
const { path: filePath } = request.query;
|
|
@@ -7681,24 +7855,9 @@ async function killStaleDashboard(pidFilePath, port, logger2) {
|
|
|
7681
7855
|
removePidFile(pidFilePath);
|
|
7682
7856
|
await new Promise((r) => setTimeout(r, 300));
|
|
7683
7857
|
}
|
|
7684
|
-
|
|
7685
|
-
"node_modules",
|
|
7686
|
-
".git",
|
|
7687
|
-
"dist",
|
|
7688
|
-
"build",
|
|
7689
|
-
"out",
|
|
7690
|
-
"target",
|
|
7691
|
-
".venv",
|
|
7692
|
-
"venv",
|
|
7693
|
-
"__pycache__",
|
|
7694
|
-
".cache",
|
|
7695
|
-
".next",
|
|
7696
|
-
".nuxt",
|
|
7697
|
-
".claude-crap",
|
|
7698
|
-
".codesight"
|
|
7699
|
-
]);
|
|
7700
|
-
async function buildComplexityReport(config, engine, logger2) {
|
|
7858
|
+
async function buildComplexityReport(config, engine, logger2, exclude) {
|
|
7701
7859
|
const threshold = config.cyclomaticMax;
|
|
7860
|
+
const filter = createExclusionFilter(exclude);
|
|
7702
7861
|
const allFunctions = [];
|
|
7703
7862
|
let totalFunctions = 0;
|
|
7704
7863
|
async function walk2(dir) {
|
|
@@ -7709,10 +7868,9 @@ async function buildComplexityReport(config, engine, logger2) {
|
|
|
7709
7868
|
return;
|
|
7710
7869
|
}
|
|
7711
7870
|
for (const entry of entries) {
|
|
7712
|
-
if (entry.name.startsWith(".") && entry.name !== ".claude-plugin") continue;
|
|
7713
7871
|
const full = join2(dir, entry.name);
|
|
7714
7872
|
if (entry.isDirectory()) {
|
|
7715
|
-
if (
|
|
7873
|
+
if (filter.shouldSkipDir(entry.name)) continue;
|
|
7716
7874
|
await walk2(full);
|
|
7717
7875
|
continue;
|
|
7718
7876
|
}
|
|
@@ -7791,23 +7949,7 @@ function computeCrap(input, threshold) {
|
|
|
7791
7949
|
|
|
7792
7950
|
// src/metrics/workspace-walker.ts
|
|
7793
7951
|
import { promises as fs4 } from "node:fs";
|
|
7794
|
-
import { join as join3 } from "node:path";
|
|
7795
|
-
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
7796
|
-
"node_modules",
|
|
7797
|
-
".git",
|
|
7798
|
-
"dist",
|
|
7799
|
-
"build",
|
|
7800
|
-
"out",
|
|
7801
|
-
"target",
|
|
7802
|
-
".venv",
|
|
7803
|
-
"venv",
|
|
7804
|
-
"__pycache__",
|
|
7805
|
-
".cache",
|
|
7806
|
-
".next",
|
|
7807
|
-
".nuxt",
|
|
7808
|
-
".claude-crap",
|
|
7809
|
-
".codesight"
|
|
7810
|
-
]);
|
|
7952
|
+
import { join as join3, relative } from "node:path";
|
|
7811
7953
|
var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
7812
7954
|
".ts",
|
|
7813
7955
|
".tsx",
|
|
@@ -7831,7 +7973,8 @@ var CODE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
7831
7973
|
".vue"
|
|
7832
7974
|
]);
|
|
7833
7975
|
var MAX_FILES_WALKED = 2e4;
|
|
7834
|
-
async function estimateWorkspaceLoc(workspaceRoot) {
|
|
7976
|
+
async function estimateWorkspaceLoc(workspaceRoot, options) {
|
|
7977
|
+
const filter = createExclusionFilter(options?.exclude);
|
|
7835
7978
|
let physicalLoc = 0;
|
|
7836
7979
|
let fileCount = 0;
|
|
7837
7980
|
let truncated = false;
|
|
@@ -7845,10 +7988,9 @@ async function estimateWorkspaceLoc(workspaceRoot) {
|
|
|
7845
7988
|
}
|
|
7846
7989
|
for (const entry of entries) {
|
|
7847
7990
|
if (truncated) return;
|
|
7848
|
-
if (entry.name.startsWith(".") && entry.name !== ".claude-plugin") continue;
|
|
7849
7991
|
const full = join3(dir, entry.name);
|
|
7850
7992
|
if (entry.isDirectory()) {
|
|
7851
|
-
if (
|
|
7993
|
+
if (filter.shouldSkipDir(entry.name)) continue;
|
|
7852
7994
|
await walk2(full);
|
|
7853
7995
|
continue;
|
|
7854
7996
|
}
|
|
@@ -7858,6 +8000,8 @@ async function estimateWorkspaceLoc(workspaceRoot) {
|
|
|
7858
8000
|
if (dot < 0) continue;
|
|
7859
8001
|
const ext = lower.substring(dot);
|
|
7860
8002
|
if (!CODE_EXTENSIONS.has(ext)) continue;
|
|
8003
|
+
const relPath = relative(workspaceRoot, full);
|
|
8004
|
+
if (filter.shouldSkipFile(relPath, entry.name)) continue;
|
|
7861
8005
|
fileCount += 1;
|
|
7862
8006
|
if (fileCount > MAX_FILES_WALKED) {
|
|
7863
8007
|
truncated = true;
|
|
@@ -8227,6 +8371,8 @@ var CrapConfigError = class extends Error {
|
|
|
8227
8371
|
}
|
|
8228
8372
|
};
|
|
8229
8373
|
function loadCrapConfig(options) {
|
|
8374
|
+
const fileResult = readFromFile(options.workspaceRoot);
|
|
8375
|
+
const exclude = fileResult?.exclude ?? [];
|
|
8230
8376
|
const envRaw = process.env["CLAUDE_CRAP_STRICTNESS"];
|
|
8231
8377
|
if (typeof envRaw === "string" && envRaw.trim() !== "") {
|
|
8232
8378
|
const normalized = envRaw.trim().toLowerCase();
|
|
@@ -8235,11 +8381,12 @@ function loadCrapConfig(options) {
|
|
|
8235
8381
|
`[crap-config] CLAUDE_CRAP_STRICTNESS="${envRaw}" is not a valid strictness. Expected one of: ${STRICTNESS_VALUES.join(", ")}.`
|
|
8236
8382
|
);
|
|
8237
8383
|
}
|
|
8238
|
-
return { strictness: normalized, strictnessSource: "env" };
|
|
8384
|
+
return { strictness: normalized, strictnessSource: "env", exclude };
|
|
8385
|
+
}
|
|
8386
|
+
if (fileResult?.strictness) {
|
|
8387
|
+
return { strictness: fileResult.strictness, strictnessSource: "file", exclude };
|
|
8239
8388
|
}
|
|
8240
|
-
|
|
8241
|
-
if (fromFile) return { strictness: fromFile, strictnessSource: "file" };
|
|
8242
|
-
return { strictness: DEFAULT_STRICTNESS, strictnessSource: "default" };
|
|
8389
|
+
return { strictness: DEFAULT_STRICTNESS, strictnessSource: "default", exclude };
|
|
8243
8390
|
}
|
|
8244
8391
|
function readFromFile(workspaceRoot) {
|
|
8245
8392
|
const filePath = join5(workspaceRoot, ".claude-crap.json");
|
|
@@ -8267,20 +8414,40 @@ function readFromFile(workspaceRoot) {
|
|
|
8267
8414
|
);
|
|
8268
8415
|
}
|
|
8269
8416
|
const doc = parsed;
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
|
|
8417
|
+
let strictness = null;
|
|
8418
|
+
if ("strictness" in doc) {
|
|
8419
|
+
const value = doc["strictness"];
|
|
8420
|
+
if (typeof value !== "string") {
|
|
8421
|
+
throw new CrapConfigError(
|
|
8422
|
+
`[crap-config] ${filePath}: 'strictness' must be a string, got ${typeof value}`
|
|
8423
|
+
);
|
|
8424
|
+
}
|
|
8425
|
+
const normalized = value.trim().toLowerCase();
|
|
8426
|
+
if (!isStrictness(normalized)) {
|
|
8427
|
+
throw new CrapConfigError(
|
|
8428
|
+
`[crap-config] ${filePath}: 'strictness' is "${value}"; expected one of ${STRICTNESS_VALUES.join(", ")}.`
|
|
8429
|
+
);
|
|
8430
|
+
}
|
|
8431
|
+
strictness = normalized;
|
|
8276
8432
|
}
|
|
8277
|
-
|
|
8278
|
-
if (
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8433
|
+
let exclude = [];
|
|
8434
|
+
if ("exclude" in doc) {
|
|
8435
|
+
const raw2 = doc["exclude"];
|
|
8436
|
+
if (!Array.isArray(raw2)) {
|
|
8437
|
+
throw new CrapConfigError(
|
|
8438
|
+
`[crap-config] ${filePath}: 'exclude' must be an array of strings`
|
|
8439
|
+
);
|
|
8440
|
+
}
|
|
8441
|
+
for (const item of raw2) {
|
|
8442
|
+
if (typeof item !== "string") {
|
|
8443
|
+
throw new CrapConfigError(
|
|
8444
|
+
`[crap-config] ${filePath}: every entry in 'exclude' must be a string, got ${typeof item}`
|
|
8445
|
+
);
|
|
8446
|
+
}
|
|
8447
|
+
}
|
|
8448
|
+
exclude = raw2;
|
|
8282
8449
|
}
|
|
8283
|
-
return
|
|
8450
|
+
return { strictness, exclude };
|
|
8284
8451
|
}
|
|
8285
8452
|
function isStrictness(value) {
|
|
8286
8453
|
return STRICTNESS_VALUES.includes(value);
|
|
@@ -8288,7 +8455,7 @@ function isStrictness(value) {
|
|
|
8288
8455
|
|
|
8289
8456
|
// src/tools/test-harness.ts
|
|
8290
8457
|
import { promises as fs6 } from "node:fs";
|
|
8291
|
-
import { basename, dirname as dirname4, extname, isAbsolute as isAbsolute3, join as join6, relative, resolve as resolve5, sep as sep2 } from "node:path";
|
|
8458
|
+
import { basename, dirname as dirname4, extname, isAbsolute as isAbsolute3, join as join6, relative as relative2, resolve as resolve5, sep as sep2 } from "node:path";
|
|
8292
8459
|
var TEST_SUFFIX_PATTERN = /\.(test|spec)\./;
|
|
8293
8460
|
function isTestFile(filePath) {
|
|
8294
8461
|
const base = basename(filePath);
|
|
@@ -8303,7 +8470,7 @@ function candidatePaths(workspaceRoot, filePath) {
|
|
|
8303
8470
|
const base = basename(absSource, ext);
|
|
8304
8471
|
const dir = dirname4(absSource);
|
|
8305
8472
|
const absWorkspace = resolve5(workspaceRoot);
|
|
8306
|
-
const relFromRoot =
|
|
8473
|
+
const relFromRoot = relative2(absWorkspace, absSource);
|
|
8307
8474
|
const relDir = dirname4(relFromRoot);
|
|
8308
8475
|
const candidates = /* @__PURE__ */ new Set();
|
|
8309
8476
|
candidates.add(join6(dir, `${base}.test${ext}`));
|
|
@@ -8355,8 +8522,8 @@ import { existsSync as existsSync5 } from "node:fs";
|
|
|
8355
8522
|
import { join as join11 } from "node:path";
|
|
8356
8523
|
|
|
8357
8524
|
// src/scanner/detector.ts
|
|
8358
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "node:fs";
|
|
8359
|
-
import { join as join7 } from "node:path";
|
|
8525
|
+
import { existsSync as existsSync2, readFileSync as readFileSync3, readdirSync } from "node:fs";
|
|
8526
|
+
import { join as join7, resolve as resolve6 } from "node:path";
|
|
8360
8527
|
import { execFile } from "node:child_process";
|
|
8361
8528
|
var SCANNER_SIGNALS = {
|
|
8362
8529
|
eslint: {
|
|
@@ -8405,6 +8572,14 @@ var SCANNER_SIGNALS = {
|
|
|
8405
8572
|
],
|
|
8406
8573
|
packageJsonKeys: ["@stryker-mutator/core"],
|
|
8407
8574
|
binaryNames: ["stryker"]
|
|
8575
|
+
},
|
|
8576
|
+
dart_analyze: {
|
|
8577
|
+
configFiles: [
|
|
8578
|
+
"analysis_options.yaml",
|
|
8579
|
+
"pubspec.yaml"
|
|
8580
|
+
],
|
|
8581
|
+
packageJsonKeys: [],
|
|
8582
|
+
binaryNames: ["dart"]
|
|
8408
8583
|
}
|
|
8409
8584
|
};
|
|
8410
8585
|
function probeConfigFiles(workspaceRoot, scanner) {
|
|
@@ -8435,14 +8610,14 @@ function probePackageJson(workspaceRoot, scanner) {
|
|
|
8435
8610
|
}
|
|
8436
8611
|
}
|
|
8437
8612
|
function probeBinary(binaryName) {
|
|
8438
|
-
return new Promise((
|
|
8613
|
+
return new Promise((resolve7) => {
|
|
8439
8614
|
execFile("which", [binaryName], { timeout: 5e3 }, (err) => {
|
|
8440
|
-
|
|
8615
|
+
resolve7(err === null);
|
|
8441
8616
|
});
|
|
8442
8617
|
});
|
|
8443
8618
|
}
|
|
8444
8619
|
async function detectScanners(workspaceRoot) {
|
|
8445
|
-
const scanners = ["eslint", "semgrep", "bandit", "stryker"];
|
|
8620
|
+
const scanners = ["eslint", "semgrep", "bandit", "stryker", "dart_analyze"];
|
|
8446
8621
|
const results = await Promise.all(
|
|
8447
8622
|
scanners.map(async (scanner) => {
|
|
8448
8623
|
const configProbe = probeConfigFiles(workspaceRoot, scanner);
|
|
@@ -8455,10 +8630,13 @@ async function detectScanners(workspaceRoot) {
|
|
|
8455
8630
|
};
|
|
8456
8631
|
}
|
|
8457
8632
|
if (probePackageJson(workspaceRoot, scanner)) {
|
|
8633
|
+
const binName = SCANNER_SIGNALS[scanner].binaryNames[0];
|
|
8634
|
+
const binPath = binName ? join7(workspaceRoot, "node_modules", ".bin", binName) : null;
|
|
8635
|
+
const installed = binPath !== null && existsSync2(binPath);
|
|
8458
8636
|
return {
|
|
8459
8637
|
scanner,
|
|
8460
|
-
available:
|
|
8461
|
-
reason: `found in package.json
|
|
8638
|
+
available: installed,
|
|
8639
|
+
reason: installed ? "found in package.json and installed" : `found in package.json but not installed (run \`npm install\`)`
|
|
8462
8640
|
};
|
|
8463
8641
|
}
|
|
8464
8642
|
const signals = SCANNER_SIGNALS[scanner];
|
|
@@ -8480,6 +8658,58 @@ async function detectScanners(workspaceRoot) {
|
|
|
8480
8658
|
);
|
|
8481
8659
|
return results;
|
|
8482
8660
|
}
|
|
8661
|
+
var MONOREPO_DIRS = ["apps", "packages", "libs", "modules", "services"];
|
|
8662
|
+
async function detectMonorepoScanners(workspaceRoot) {
|
|
8663
|
+
const subdirs = /* @__PURE__ */ new Set();
|
|
8664
|
+
try {
|
|
8665
|
+
const pkgPath = join7(workspaceRoot, "package.json");
|
|
8666
|
+
const raw = readFileSync3(pkgPath, "utf-8");
|
|
8667
|
+
const pkg = JSON.parse(raw);
|
|
8668
|
+
if (Array.isArray(pkg.workspaces)) {
|
|
8669
|
+
for (const ws of pkg.workspaces) {
|
|
8670
|
+
if (typeof ws === "string" && !ws.includes("*")) {
|
|
8671
|
+
const full = resolve6(workspaceRoot, ws);
|
|
8672
|
+
if (existsSync2(full)) subdirs.add(full);
|
|
8673
|
+
}
|
|
8674
|
+
}
|
|
8675
|
+
}
|
|
8676
|
+
} catch {
|
|
8677
|
+
}
|
|
8678
|
+
for (const dir of MONOREPO_DIRS) {
|
|
8679
|
+
const full = join7(workspaceRoot, dir);
|
|
8680
|
+
try {
|
|
8681
|
+
const entries = readdirSync(full, { withFileTypes: true });
|
|
8682
|
+
for (const entry of entries) {
|
|
8683
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
8684
|
+
subdirs.add(join7(full, entry.name));
|
|
8685
|
+
}
|
|
8686
|
+
}
|
|
8687
|
+
} catch {
|
|
8688
|
+
}
|
|
8689
|
+
}
|
|
8690
|
+
if (subdirs.size === 0) return [];
|
|
8691
|
+
const detections = [];
|
|
8692
|
+
const scanners = ["eslint", "semgrep", "bandit", "stryker", "dart_analyze"];
|
|
8693
|
+
for (const subdir of subdirs) {
|
|
8694
|
+
for (const scanner of scanners) {
|
|
8695
|
+
const configProbe = probeConfigFiles(subdir, scanner);
|
|
8696
|
+
if (!configProbe.found) continue;
|
|
8697
|
+
if (scanner === "dart_analyze") {
|
|
8698
|
+
const hasBinary = await probeBinary("dart");
|
|
8699
|
+
if (!hasBinary) continue;
|
|
8700
|
+
}
|
|
8701
|
+
const relDir = subdir.replace(workspaceRoot + "/", "");
|
|
8702
|
+
detections.push({
|
|
8703
|
+
scanner,
|
|
8704
|
+
available: true,
|
|
8705
|
+
reason: `config file found in ${relDir}/`,
|
|
8706
|
+
...configProbe.path ? { configPath: configProbe.path } : {},
|
|
8707
|
+
workingDir: subdir
|
|
8708
|
+
});
|
|
8709
|
+
}
|
|
8710
|
+
}
|
|
8711
|
+
return detections;
|
|
8712
|
+
}
|
|
8483
8713
|
|
|
8484
8714
|
// src/scanner/runner.ts
|
|
8485
8715
|
import { execFile as execFile2 } from "node:child_process";
|
|
@@ -8516,17 +8746,26 @@ function getScannerCommand(scanner, workspaceRoot) {
|
|
|
8516
8746
|
nonZeroIsNormal: false,
|
|
8517
8747
|
outputFile: join8(workspaceRoot, "reports", "mutation", "mutation.json")
|
|
8518
8748
|
};
|
|
8749
|
+
case "dart_analyze":
|
|
8750
|
+
return {
|
|
8751
|
+
command: "dart",
|
|
8752
|
+
args: ["analyze", "--format=json", "."],
|
|
8753
|
+
timeoutMs: 12e4,
|
|
8754
|
+
nonZeroIsNormal: true
|
|
8755
|
+
// exits 3 when findings exist
|
|
8756
|
+
};
|
|
8519
8757
|
}
|
|
8520
8758
|
}
|
|
8521
|
-
function runScanner(scanner, workspaceRoot) {
|
|
8759
|
+
function runScanner(scanner, workspaceRoot, options) {
|
|
8522
8760
|
const start = Date.now();
|
|
8523
|
-
const
|
|
8524
|
-
|
|
8761
|
+
const cwd = options?.workingDir ?? workspaceRoot;
|
|
8762
|
+
const cmd = getScannerCommand(scanner, cwd);
|
|
8763
|
+
return new Promise((resolve7) => {
|
|
8525
8764
|
execFile2(
|
|
8526
8765
|
cmd.command,
|
|
8527
8766
|
cmd.args,
|
|
8528
8767
|
{
|
|
8529
|
-
cwd
|
|
8768
|
+
cwd,
|
|
8530
8769
|
timeout: cmd.timeoutMs,
|
|
8531
8770
|
maxBuffer: 50 * 1024 * 1024,
|
|
8532
8771
|
// 50 MB — large codebases produce verbose output
|
|
@@ -8540,7 +8779,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8540
8779
|
if (cmd.outputFile && existsSync3(cmd.outputFile)) {
|
|
8541
8780
|
try {
|
|
8542
8781
|
const fileOutput = readFileSync4(cmd.outputFile, "utf-8");
|
|
8543
|
-
|
|
8782
|
+
resolve7({
|
|
8544
8783
|
scanner,
|
|
8545
8784
|
success: true,
|
|
8546
8785
|
rawOutput: fileOutput,
|
|
@@ -8550,7 +8789,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8550
8789
|
} catch {
|
|
8551
8790
|
}
|
|
8552
8791
|
}
|
|
8553
|
-
|
|
8792
|
+
resolve7({
|
|
8554
8793
|
scanner,
|
|
8555
8794
|
success: false,
|
|
8556
8795
|
rawOutput: "",
|
|
@@ -8563,7 +8802,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8563
8802
|
if (existsSync3(cmd.outputFile)) {
|
|
8564
8803
|
try {
|
|
8565
8804
|
const fileOutput = readFileSync4(cmd.outputFile, "utf-8");
|
|
8566
|
-
|
|
8805
|
+
resolve7({
|
|
8567
8806
|
scanner,
|
|
8568
8807
|
success: true,
|
|
8569
8808
|
rawOutput: fileOutput,
|
|
@@ -8571,7 +8810,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8571
8810
|
});
|
|
8572
8811
|
return;
|
|
8573
8812
|
} catch (readErr) {
|
|
8574
|
-
|
|
8813
|
+
resolve7({
|
|
8575
8814
|
scanner,
|
|
8576
8815
|
success: false,
|
|
8577
8816
|
rawOutput: "",
|
|
@@ -8581,7 +8820,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8581
8820
|
return;
|
|
8582
8821
|
}
|
|
8583
8822
|
}
|
|
8584
|
-
|
|
8823
|
+
resolve7({
|
|
8585
8824
|
scanner,
|
|
8586
8825
|
success: false,
|
|
8587
8826
|
rawOutput: "",
|
|
@@ -8592,7 +8831,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8592
8831
|
}
|
|
8593
8832
|
const output = stdout.trim();
|
|
8594
8833
|
if (!output) {
|
|
8595
|
-
|
|
8834
|
+
resolve7({
|
|
8596
8835
|
scanner,
|
|
8597
8836
|
success: true,
|
|
8598
8837
|
rawOutput: "[]",
|
|
@@ -8601,7 +8840,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8601
8840
|
});
|
|
8602
8841
|
return;
|
|
8603
8842
|
}
|
|
8604
|
-
|
|
8843
|
+
resolve7({
|
|
8605
8844
|
scanner,
|
|
8606
8845
|
success: true,
|
|
8607
8846
|
rawOutput: output,
|
|
@@ -8613,7 +8852,7 @@ function runScanner(scanner, workspaceRoot) {
|
|
|
8613
8852
|
}
|
|
8614
8853
|
|
|
8615
8854
|
// src/scanner/bootstrap.ts
|
|
8616
|
-
import { existsSync as existsSync4, writeFileSync as writeFileSync2, readdirSync } from "node:fs";
|
|
8855
|
+
import { existsSync as existsSync4, writeFileSync as writeFileSync2, readdirSync as readdirSync2 } from "node:fs";
|
|
8617
8856
|
import { join as join9 } from "node:path";
|
|
8618
8857
|
import { execFile as execFile3 } from "node:child_process";
|
|
8619
8858
|
function detectProjectType(workspaceRoot) {
|
|
@@ -8630,12 +8869,13 @@ function detectProjectType(workspaceRoot) {
|
|
|
8630
8869
|
}
|
|
8631
8870
|
if (has("Directory.Build.props")) return "csharp";
|
|
8632
8871
|
try {
|
|
8633
|
-
const entries =
|
|
8872
|
+
const entries = readdirSync2(workspaceRoot);
|
|
8634
8873
|
if (entries.some((e) => e.endsWith(".csproj") || e.endsWith(".sln"))) {
|
|
8635
8874
|
return "csharp";
|
|
8636
8875
|
}
|
|
8637
8876
|
} catch {
|
|
8638
8877
|
}
|
|
8878
|
+
if (has("pubspec.yaml")) return "dart";
|
|
8639
8879
|
return "unknown";
|
|
8640
8880
|
}
|
|
8641
8881
|
function generateEslintConfig(isTypeScript) {
|
|
@@ -8677,7 +8917,7 @@ export default [
|
|
|
8677
8917
|
`;
|
|
8678
8918
|
}
|
|
8679
8919
|
function npmInstall(workspaceRoot, packages) {
|
|
8680
|
-
return new Promise((
|
|
8920
|
+
return new Promise((resolve7) => {
|
|
8681
8921
|
execFile3(
|
|
8682
8922
|
"npm",
|
|
8683
8923
|
["install", "--save-dev", ...packages],
|
|
@@ -8688,14 +8928,14 @@ function npmInstall(workspaceRoot, packages) {
|
|
|
8688
8928
|
},
|
|
8689
8929
|
(err, stdout, stderr) => {
|
|
8690
8930
|
if (err) {
|
|
8691
|
-
|
|
8931
|
+
resolve7({
|
|
8692
8932
|
action: `npm install --save-dev ${packages.join(" ")}`,
|
|
8693
8933
|
success: false,
|
|
8694
8934
|
detail: stderr || err.message
|
|
8695
8935
|
});
|
|
8696
8936
|
return;
|
|
8697
8937
|
}
|
|
8698
|
-
|
|
8938
|
+
resolve7({
|
|
8699
8939
|
action: `npm install --save-dev ${packages.join(" ")}`,
|
|
8700
8940
|
success: true,
|
|
8701
8941
|
detail: `installed ${packages.join(", ")}`
|
|
@@ -8750,6 +8990,12 @@ function getRecommendation(projectType) {
|
|
|
8750
8990
|
canAutoInstall: false,
|
|
8751
8991
|
installInstructions: "brew install semgrep (or: pip install semgrep, pipx install semgrep)"
|
|
8752
8992
|
};
|
|
8993
|
+
case "dart":
|
|
8994
|
+
return {
|
|
8995
|
+
scanner: "dart_analyze",
|
|
8996
|
+
canAutoInstall: false,
|
|
8997
|
+
installInstructions: "Install the Dart SDK: https://dart.dev/get-dart (or Flutter SDK which includes Dart)"
|
|
8998
|
+
};
|
|
8753
8999
|
case "unknown":
|
|
8754
9000
|
return {
|
|
8755
9001
|
scanner: "semgrep",
|
|
@@ -8896,23 +9142,7 @@ function buildResult(projectType, steps, autoScanResult, recommendation) {
|
|
|
8896
9142
|
|
|
8897
9143
|
// src/scanner/complexity-scanner.ts
|
|
8898
9144
|
import { promises as fs7 } from "node:fs";
|
|
8899
|
-
import { join as join10, relative as
|
|
8900
|
-
var SKIP_DIRS3 = /* @__PURE__ */ new Set([
|
|
8901
|
-
"node_modules",
|
|
8902
|
-
".git",
|
|
8903
|
-
"dist",
|
|
8904
|
-
"build",
|
|
8905
|
-
"out",
|
|
8906
|
-
"target",
|
|
8907
|
-
".venv",
|
|
8908
|
-
"venv",
|
|
8909
|
-
"__pycache__",
|
|
8910
|
-
".cache",
|
|
8911
|
-
".next",
|
|
8912
|
-
".nuxt",
|
|
8913
|
-
".claude-crap",
|
|
8914
|
-
".codesight"
|
|
8915
|
-
]);
|
|
9145
|
+
import { join as join10, relative as relative3 } from "node:path";
|
|
8916
9146
|
var MAX_FILES = 2e4;
|
|
8917
9147
|
var RULE_ID = "complexity/cyclomatic-max";
|
|
8918
9148
|
var SOURCE_TOOL = "complexity";
|
|
@@ -8920,7 +9150,8 @@ async function scanComplexity(workspaceRoot, engine, sarifStore, config, logger2
|
|
|
8920
9150
|
const start = Date.now();
|
|
8921
9151
|
const threshold = config.cyclomaticMax;
|
|
8922
9152
|
const errorThreshold = threshold * 2;
|
|
8923
|
-
const
|
|
9153
|
+
const filter = createExclusionFilter(config.exclude);
|
|
9154
|
+
const files = await collectSourceFiles(workspaceRoot, filter);
|
|
8924
9155
|
logger2.info(
|
|
8925
9156
|
{ fileCount: files.length, threshold },
|
|
8926
9157
|
"complexity-scanner: starting analysis"
|
|
@@ -8939,7 +9170,7 @@ async function scanComplexity(workspaceRoot, engine, sarifStore, config, logger2
|
|
|
8939
9170
|
for (const fn of metrics.functions) {
|
|
8940
9171
|
if (fn.cyclomaticComplexity <= threshold) continue;
|
|
8941
9172
|
const level = fn.cyclomaticComplexity >= errorThreshold ? "error" : "warning";
|
|
8942
|
-
const relPath =
|
|
9173
|
+
const relPath = relative3(workspaceRoot, filePath);
|
|
8943
9174
|
sarifResults.push({
|
|
8944
9175
|
ruleId: RULE_ID,
|
|
8945
9176
|
level,
|
|
@@ -8990,7 +9221,7 @@ async function scanComplexity(workspaceRoot, engine, sarifStore, config, logger2
|
|
|
8990
9221
|
);
|
|
8991
9222
|
return { filesScanned, functionsAnalyzed, violations, durationMs };
|
|
8992
9223
|
}
|
|
8993
|
-
async function collectSourceFiles(workspaceRoot) {
|
|
9224
|
+
async function collectSourceFiles(workspaceRoot, filter) {
|
|
8994
9225
|
const files = [];
|
|
8995
9226
|
let truncated = false;
|
|
8996
9227
|
async function walk2(dir) {
|
|
@@ -9003,15 +9234,16 @@ async function collectSourceFiles(workspaceRoot) {
|
|
|
9003
9234
|
}
|
|
9004
9235
|
for (const entry of entries) {
|
|
9005
9236
|
if (truncated) return;
|
|
9006
|
-
if (entry.name.startsWith(".") && entry.name !== ".claude-plugin") continue;
|
|
9007
9237
|
const full = join10(dir, entry.name);
|
|
9008
9238
|
if (entry.isDirectory()) {
|
|
9009
|
-
if (
|
|
9239
|
+
if (filter.shouldSkipDir(entry.name)) continue;
|
|
9010
9240
|
await walk2(full);
|
|
9011
9241
|
continue;
|
|
9012
9242
|
}
|
|
9013
9243
|
if (!entry.isFile()) continue;
|
|
9014
9244
|
if (!detectLanguageFromPath(entry.name)) continue;
|
|
9245
|
+
const relPath = relative3(workspaceRoot, full);
|
|
9246
|
+
if (filter.shouldSkipFile(relPath, entry.name)) continue;
|
|
9015
9247
|
files.push(full);
|
|
9016
9248
|
if (files.length >= MAX_FILES) {
|
|
9017
9249
|
truncated = true;
|
|
@@ -9038,10 +9270,18 @@ function ingestScannerRun(scanner, rawOutput, sarifStore) {
|
|
|
9038
9270
|
async function autoScan(workspaceRoot, sarifStore, logger2, options) {
|
|
9039
9271
|
const start = Date.now();
|
|
9040
9272
|
const detected = await detectScanners(workspaceRoot);
|
|
9273
|
+
const monorepoDetected = await detectMonorepoScanners(workspaceRoot);
|
|
9274
|
+
const rootScannerSet = new Set(detected.filter((d) => d.available).map((d) => d.scanner));
|
|
9275
|
+
for (const md of monorepoDetected) {
|
|
9276
|
+
if (!rootScannerSet.has(md.scanner)) {
|
|
9277
|
+
detected.push(md);
|
|
9278
|
+
}
|
|
9279
|
+
}
|
|
9041
9280
|
const available = detected.filter((d) => d.available);
|
|
9042
9281
|
logger2.info(
|
|
9043
9282
|
{
|
|
9044
9283
|
detected: detected.map((d) => `${d.scanner}:${d.available}`),
|
|
9284
|
+
monorepo: monorepoDetected.length,
|
|
9045
9285
|
available: available.length
|
|
9046
9286
|
},
|
|
9047
9287
|
"auto-scan: detection complete"
|
|
@@ -9096,7 +9336,7 @@ async function autoScan(workspaceRoot, sarifStore, logger2, options) {
|
|
|
9096
9336
|
};
|
|
9097
9337
|
}
|
|
9098
9338
|
const runResults = await Promise.allSettled(
|
|
9099
|
-
available.map((d) => runScanner(d.scanner, workspaceRoot))
|
|
9339
|
+
available.map((d) => runScanner(d.scanner, workspaceRoot, d.workingDir ? { workingDir: d.workingDir } : void 0))
|
|
9100
9340
|
);
|
|
9101
9341
|
const results = [];
|
|
9102
9342
|
let totalFindings = 0;
|
|
@@ -9177,7 +9417,7 @@ async function autoScan(workspaceRoot, sarifStore, logger2, options) {
|
|
|
9177
9417
|
workspaceRoot,
|
|
9178
9418
|
options.engine,
|
|
9179
9419
|
sarifStore,
|
|
9180
|
-
{ cyclomaticMax: options.cyclomaticMax ?? 15 },
|
|
9420
|
+
{ cyclomaticMax: options.cyclomaticMax ?? 15, ...options.exclude ? { exclude: options.exclude } : {} },
|
|
9181
9421
|
logger2
|
|
9182
9422
|
);
|
|
9183
9423
|
totalFindings += complexityScan.violations;
|
|
@@ -9313,7 +9553,7 @@ var ingestScannerOutputSchema = {
|
|
|
9313
9553
|
properties: {
|
|
9314
9554
|
scanner: {
|
|
9315
9555
|
type: "string",
|
|
9316
|
-
enum: ["semgrep", "eslint", "bandit", "stryker"],
|
|
9556
|
+
enum: ["semgrep", "eslint", "bandit", "stryker", "dart_analyze"],
|
|
9317
9557
|
description: "Identifier of the producing scanner."
|
|
9318
9558
|
},
|
|
9319
9559
|
rawOutput: {
|
|
@@ -9373,6 +9613,15 @@ async function main() {
|
|
|
9373
9613
|
{ config: { ...config, pluginRoot: "<redacted>" } },
|
|
9374
9614
|
"claude-crap MCP server starting"
|
|
9375
9615
|
);
|
|
9616
|
+
let userExclusions = [];
|
|
9617
|
+
try {
|
|
9618
|
+
const crapConfig = loadCrapConfig({ workspaceRoot: config.pluginRoot });
|
|
9619
|
+
userExclusions = crapConfig.exclude;
|
|
9620
|
+
if (userExclusions.length > 0) {
|
|
9621
|
+
logger.info({ exclude: userExclusions }, "user exclusions loaded from .claude-crap.json");
|
|
9622
|
+
}
|
|
9623
|
+
} catch {
|
|
9624
|
+
}
|
|
9376
9625
|
const astEngine = new TreeSitterEngine();
|
|
9377
9626
|
const sarifStore = new SarifStore({
|
|
9378
9627
|
workspaceRoot: config.pluginRoot,
|
|
@@ -9388,9 +9637,10 @@ async function main() {
|
|
|
9388
9637
|
dashboard = await startDashboard({
|
|
9389
9638
|
config,
|
|
9390
9639
|
sarifStore,
|
|
9391
|
-
workspaceStatsProvider: () => estimateWorkspaceLoc(config.pluginRoot),
|
|
9640
|
+
workspaceStatsProvider: () => estimateWorkspaceLoc(config.pluginRoot, { exclude: userExclusions }),
|
|
9392
9641
|
logger,
|
|
9393
|
-
astEngine
|
|
9642
|
+
astEngine,
|
|
9643
|
+
exclude: userExclusions
|
|
9394
9644
|
});
|
|
9395
9645
|
} catch (err) {
|
|
9396
9646
|
logger.warn(
|
|
@@ -9560,7 +9810,7 @@ async function main() {
|
|
|
9560
9810
|
const typed = args ?? {};
|
|
9561
9811
|
const format = typed.format ?? "both";
|
|
9562
9812
|
try {
|
|
9563
|
-
const workspace = await estimateWorkspaceLoc(config.pluginRoot);
|
|
9813
|
+
const workspace = await estimateWorkspaceLoc(config.pluginRoot, { exclude: userExclusions });
|
|
9564
9814
|
const score = computeProjectScore({
|
|
9565
9815
|
workspaceRoot: config.pluginRoot,
|
|
9566
9816
|
minutesPerLoc: config.minutesPerLoc,
|
|
@@ -9783,7 +10033,8 @@ async function main() {
|
|
|
9783
10033
|
try {
|
|
9784
10034
|
const result = await autoScan(config.pluginRoot, sarifStore, logger, {
|
|
9785
10035
|
engine: astEngine,
|
|
9786
|
-
cyclomaticMax: config.cyclomaticMax
|
|
10036
|
+
cyclomaticMax: config.cyclomaticMax,
|
|
10037
|
+
exclude: userExclusions
|
|
9787
10038
|
});
|
|
9788
10039
|
const markdown = renderAutoScanMarkdown(result);
|
|
9789
10040
|
return {
|
|
@@ -9862,7 +10113,8 @@ async function main() {
|
|
|
9862
10113
|
logger.info("claude-crap MCP server ready (stdio)");
|
|
9863
10114
|
autoScan(config.pluginRoot, sarifStore, logger, {
|
|
9864
10115
|
engine: astEngine,
|
|
9865
|
-
cyclomaticMax: config.cyclomaticMax
|
|
10116
|
+
cyclomaticMax: config.cyclomaticMax,
|
|
10117
|
+
exclude: userExclusions
|
|
9866
10118
|
}).then((result) => {
|
|
9867
10119
|
const scanners = result.results.filter((r) => r.success).map((r) => r.scanner);
|
|
9868
10120
|
logger.info(
|