claude-crap 0.1.2 → 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/CHANGELOG.md +35 -0
- package/README.md +43 -23
- package/dist/index.js +79 -1
- package/dist/index.js.map +1 -1
- package/dist/scanner/auto-scan.d.ts +57 -0
- package/dist/scanner/auto-scan.d.ts.map +1 -0
- package/dist/scanner/auto-scan.js +138 -0
- package/dist/scanner/auto-scan.js.map +1 -0
- package/dist/scanner/detector.d.ts +53 -0
- package/dist/scanner/detector.d.ts.map +1 -0
- package/dist/scanner/detector.js +173 -0
- package/dist/scanner/detector.js.map +1 -0
- package/dist/scanner/index.d.ts +22 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +22 -0
- package/dist/scanner/index.js.map +1 -0
- package/dist/scanner/runner.d.ts +59 -0
- package/dist/scanner/runner.d.ts.map +1 -0
- package/dist/scanner/runner.js +159 -0
- package/dist/scanner/runner.js.map +1 -0
- package/dist/schemas/tool-schemas.d.ts +11 -0
- package/dist/schemas/tool-schemas.d.ts.map +1 -1
- package/dist/schemas/tool-schemas.js +11 -0
- package/dist/schemas/tool-schemas.js.map +1 -1
- package/package.json +5 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/bundle/mcp-server.mjs +452 -0
- package/plugin/bundle/mcp-server.mjs.map +4 -4
- package/plugin/package.json +1 -1
- package/src/index.ts +98 -0
- package/src/scanner/auto-scan.ts +212 -0
- package/src/scanner/detector.ts +224 -0
- package/src/scanner/index.ts +22 -0
- package/src/scanner/runner.ts +212 -0
- package/src/schemas/tool-schemas.ts +13 -0
- package/src/tests/auto-scan.test.ts +137 -0
- package/src/tests/integration/mcp-server.integration.test.ts +2 -1
- package/src/tests/scanner-detector.test.ts +181 -0
- package/src/tests/scanner-runner.test.ts +63 -0
|
@@ -8083,6 +8083,377 @@ function resolveWithinWorkspace(workspaceRoot, filePath) {
|
|
|
8083
8083
|
return candidate;
|
|
8084
8084
|
}
|
|
8085
8085
|
|
|
8086
|
+
// src/scanner/detector.ts
|
|
8087
|
+
import { existsSync, readFileSync as readFileSync2 } from "node:fs";
|
|
8088
|
+
import { join as join6 } from "node:path";
|
|
8089
|
+
import { execFile } from "node:child_process";
|
|
8090
|
+
var SCANNER_SIGNALS = {
|
|
8091
|
+
eslint: {
|
|
8092
|
+
configFiles: [
|
|
8093
|
+
"eslint.config.js",
|
|
8094
|
+
"eslint.config.mjs",
|
|
8095
|
+
"eslint.config.cjs",
|
|
8096
|
+
"eslint.config.ts",
|
|
8097
|
+
"eslint.config.mts",
|
|
8098
|
+
"eslint.config.cts",
|
|
8099
|
+
".eslintrc.js",
|
|
8100
|
+
".eslintrc.cjs",
|
|
8101
|
+
".eslintrc.yaml",
|
|
8102
|
+
".eslintrc.yml",
|
|
8103
|
+
".eslintrc.json"
|
|
8104
|
+
],
|
|
8105
|
+
packageJsonKeys: ["eslint"],
|
|
8106
|
+
binaryNames: ["eslint"]
|
|
8107
|
+
},
|
|
8108
|
+
semgrep: {
|
|
8109
|
+
configFiles: [
|
|
8110
|
+
".semgrep.yml",
|
|
8111
|
+
".semgrep.yaml",
|
|
8112
|
+
".semgrep.json"
|
|
8113
|
+
],
|
|
8114
|
+
packageJsonKeys: [],
|
|
8115
|
+
binaryNames: ["semgrep"]
|
|
8116
|
+
},
|
|
8117
|
+
bandit: {
|
|
8118
|
+
configFiles: [
|
|
8119
|
+
".bandit",
|
|
8120
|
+
"bandit.yaml",
|
|
8121
|
+
"bandit.yml"
|
|
8122
|
+
],
|
|
8123
|
+
packageJsonKeys: [],
|
|
8124
|
+
binaryNames: ["bandit"]
|
|
8125
|
+
},
|
|
8126
|
+
stryker: {
|
|
8127
|
+
configFiles: [
|
|
8128
|
+
"stryker.conf.js",
|
|
8129
|
+
"stryker.conf.mjs",
|
|
8130
|
+
"stryker.conf.cjs",
|
|
8131
|
+
"stryker.conf.json",
|
|
8132
|
+
".strykerrc",
|
|
8133
|
+
".strykerrc.json"
|
|
8134
|
+
],
|
|
8135
|
+
packageJsonKeys: ["@stryker-mutator/core"],
|
|
8136
|
+
binaryNames: ["stryker"]
|
|
8137
|
+
}
|
|
8138
|
+
};
|
|
8139
|
+
function probeConfigFiles(workspaceRoot, scanner) {
|
|
8140
|
+
const signals = SCANNER_SIGNALS[scanner];
|
|
8141
|
+
for (const file of signals.configFiles) {
|
|
8142
|
+
const fullPath = join6(workspaceRoot, file);
|
|
8143
|
+
if (existsSync(fullPath)) {
|
|
8144
|
+
return { found: true, path: fullPath };
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
return { found: false };
|
|
8148
|
+
}
|
|
8149
|
+
function probePackageJson(workspaceRoot, scanner) {
|
|
8150
|
+
const signals = SCANNER_SIGNALS[scanner];
|
|
8151
|
+
if (signals.packageJsonKeys.length === 0) return false;
|
|
8152
|
+
const pkgPath = join6(workspaceRoot, "package.json");
|
|
8153
|
+
if (!existsSync(pkgPath)) return false;
|
|
8154
|
+
try {
|
|
8155
|
+
const raw = readFileSync2(pkgPath, "utf-8");
|
|
8156
|
+
const pkg = JSON.parse(raw);
|
|
8157
|
+
const deps = {
|
|
8158
|
+
...typeof pkg.dependencies === "object" && pkg.dependencies !== null ? pkg.dependencies : {},
|
|
8159
|
+
...typeof pkg.devDependencies === "object" && pkg.devDependencies !== null ? pkg.devDependencies : {}
|
|
8160
|
+
};
|
|
8161
|
+
return signals.packageJsonKeys.some((key) => key in deps);
|
|
8162
|
+
} catch {
|
|
8163
|
+
return false;
|
|
8164
|
+
}
|
|
8165
|
+
}
|
|
8166
|
+
function probeBinary(binaryName) {
|
|
8167
|
+
return new Promise((resolve6) => {
|
|
8168
|
+
execFile("which", [binaryName], { timeout: 5e3 }, (err) => {
|
|
8169
|
+
resolve6(err === null);
|
|
8170
|
+
});
|
|
8171
|
+
});
|
|
8172
|
+
}
|
|
8173
|
+
async function detectScanners(workspaceRoot) {
|
|
8174
|
+
const scanners = ["eslint", "semgrep", "bandit", "stryker"];
|
|
8175
|
+
const results = await Promise.all(
|
|
8176
|
+
scanners.map(async (scanner) => {
|
|
8177
|
+
const configProbe = probeConfigFiles(workspaceRoot, scanner);
|
|
8178
|
+
if (configProbe.found && configProbe.path) {
|
|
8179
|
+
return {
|
|
8180
|
+
scanner,
|
|
8181
|
+
available: true,
|
|
8182
|
+
reason: `config file found: ${configProbe.path.replace(workspaceRoot + "/", "")}`,
|
|
8183
|
+
configPath: configProbe.path
|
|
8184
|
+
};
|
|
8185
|
+
}
|
|
8186
|
+
if (probePackageJson(workspaceRoot, scanner)) {
|
|
8187
|
+
return {
|
|
8188
|
+
scanner,
|
|
8189
|
+
available: true,
|
|
8190
|
+
reason: `found in package.json dependencies`
|
|
8191
|
+
};
|
|
8192
|
+
}
|
|
8193
|
+
const signals = SCANNER_SIGNALS[scanner];
|
|
8194
|
+
for (const bin of signals.binaryNames) {
|
|
8195
|
+
if (await probeBinary(bin)) {
|
|
8196
|
+
return {
|
|
8197
|
+
scanner,
|
|
8198
|
+
available: true,
|
|
8199
|
+
reason: `binary "${bin}" found on PATH`
|
|
8200
|
+
};
|
|
8201
|
+
}
|
|
8202
|
+
}
|
|
8203
|
+
return {
|
|
8204
|
+
scanner,
|
|
8205
|
+
available: false,
|
|
8206
|
+
reason: "no config file, package.json entry, or binary found"
|
|
8207
|
+
};
|
|
8208
|
+
})
|
|
8209
|
+
);
|
|
8210
|
+
return results;
|
|
8211
|
+
}
|
|
8212
|
+
|
|
8213
|
+
// src/scanner/runner.ts
|
|
8214
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
8215
|
+
import { readFileSync as readFileSync3, existsSync as existsSync2 } from "node:fs";
|
|
8216
|
+
import { join as join7 } from "node:path";
|
|
8217
|
+
function getScannerCommand(scanner, workspaceRoot) {
|
|
8218
|
+
switch (scanner) {
|
|
8219
|
+
case "eslint":
|
|
8220
|
+
return {
|
|
8221
|
+
command: "npx",
|
|
8222
|
+
args: ["eslint", "-f", "json", "."],
|
|
8223
|
+
timeoutMs: 12e4,
|
|
8224
|
+
nonZeroIsNormal: true
|
|
8225
|
+
};
|
|
8226
|
+
case "semgrep":
|
|
8227
|
+
return {
|
|
8228
|
+
command: "semgrep",
|
|
8229
|
+
args: ["--sarif", "--quiet", "."],
|
|
8230
|
+
timeoutMs: 12e4,
|
|
8231
|
+
nonZeroIsNormal: false
|
|
8232
|
+
};
|
|
8233
|
+
case "bandit":
|
|
8234
|
+
return {
|
|
8235
|
+
command: "bandit",
|
|
8236
|
+
args: ["-f", "json", "-r", ".", "-q"],
|
|
8237
|
+
timeoutMs: 12e4,
|
|
8238
|
+
nonZeroIsNormal: true
|
|
8239
|
+
};
|
|
8240
|
+
case "stryker":
|
|
8241
|
+
return {
|
|
8242
|
+
command: "npx",
|
|
8243
|
+
args: ["stryker", "run"],
|
|
8244
|
+
timeoutMs: 3e5,
|
|
8245
|
+
nonZeroIsNormal: false,
|
|
8246
|
+
outputFile: join7(workspaceRoot, "reports", "mutation", "mutation.json")
|
|
8247
|
+
};
|
|
8248
|
+
}
|
|
8249
|
+
}
|
|
8250
|
+
function runScanner(scanner, workspaceRoot) {
|
|
8251
|
+
const start = Date.now();
|
|
8252
|
+
const cmd = getScannerCommand(scanner, workspaceRoot);
|
|
8253
|
+
return new Promise((resolve6) => {
|
|
8254
|
+
execFile2(
|
|
8255
|
+
cmd.command,
|
|
8256
|
+
cmd.args,
|
|
8257
|
+
{
|
|
8258
|
+
cwd: workspaceRoot,
|
|
8259
|
+
timeout: cmd.timeoutMs,
|
|
8260
|
+
maxBuffer: 50 * 1024 * 1024,
|
|
8261
|
+
// 50 MB — large codebases produce verbose output
|
|
8262
|
+
env: { ...process.env, FORCE_COLOR: "0" }
|
|
8263
|
+
// suppress ANSI in output
|
|
8264
|
+
},
|
|
8265
|
+
(err, stdout, stderr) => {
|
|
8266
|
+
const durationMs = Date.now() - start;
|
|
8267
|
+
if (err && !cmd.nonZeroIsNormal) {
|
|
8268
|
+
if (cmd.outputFile && existsSync2(cmd.outputFile)) {
|
|
8269
|
+
try {
|
|
8270
|
+
const fileOutput = readFileSync3(cmd.outputFile, "utf-8");
|
|
8271
|
+
resolve6({
|
|
8272
|
+
scanner,
|
|
8273
|
+
success: true,
|
|
8274
|
+
rawOutput: fileOutput,
|
|
8275
|
+
durationMs
|
|
8276
|
+
});
|
|
8277
|
+
return;
|
|
8278
|
+
} catch {
|
|
8279
|
+
}
|
|
8280
|
+
}
|
|
8281
|
+
resolve6({
|
|
8282
|
+
scanner,
|
|
8283
|
+
success: false,
|
|
8284
|
+
rawOutput: "",
|
|
8285
|
+
error: stderr || err.message,
|
|
8286
|
+
durationMs
|
|
8287
|
+
});
|
|
8288
|
+
return;
|
|
8289
|
+
}
|
|
8290
|
+
if (cmd.outputFile) {
|
|
8291
|
+
if (existsSync2(cmd.outputFile)) {
|
|
8292
|
+
try {
|
|
8293
|
+
const fileOutput = readFileSync3(cmd.outputFile, "utf-8");
|
|
8294
|
+
resolve6({
|
|
8295
|
+
scanner,
|
|
8296
|
+
success: true,
|
|
8297
|
+
rawOutput: fileOutput,
|
|
8298
|
+
durationMs
|
|
8299
|
+
});
|
|
8300
|
+
return;
|
|
8301
|
+
} catch (readErr) {
|
|
8302
|
+
resolve6({
|
|
8303
|
+
scanner,
|
|
8304
|
+
success: false,
|
|
8305
|
+
rawOutput: "",
|
|
8306
|
+
error: `Failed to read output file: ${readErr.message}`,
|
|
8307
|
+
durationMs
|
|
8308
|
+
});
|
|
8309
|
+
return;
|
|
8310
|
+
}
|
|
8311
|
+
}
|
|
8312
|
+
resolve6({
|
|
8313
|
+
scanner,
|
|
8314
|
+
success: false,
|
|
8315
|
+
rawOutput: "",
|
|
8316
|
+
error: `Scanner completed but output file not found: ${cmd.outputFile}`,
|
|
8317
|
+
durationMs
|
|
8318
|
+
});
|
|
8319
|
+
return;
|
|
8320
|
+
}
|
|
8321
|
+
const output = stdout.trim();
|
|
8322
|
+
if (!output) {
|
|
8323
|
+
resolve6({
|
|
8324
|
+
scanner,
|
|
8325
|
+
success: true,
|
|
8326
|
+
rawOutput: "[]",
|
|
8327
|
+
// ESLint returns empty when no files match
|
|
8328
|
+
durationMs
|
|
8329
|
+
});
|
|
8330
|
+
return;
|
|
8331
|
+
}
|
|
8332
|
+
resolve6({
|
|
8333
|
+
scanner,
|
|
8334
|
+
success: true,
|
|
8335
|
+
rawOutput: output,
|
|
8336
|
+
durationMs
|
|
8337
|
+
});
|
|
8338
|
+
}
|
|
8339
|
+
);
|
|
8340
|
+
});
|
|
8341
|
+
}
|
|
8342
|
+
|
|
8343
|
+
// src/scanner/auto-scan.ts
|
|
8344
|
+
function ingestScannerRun(scanner, rawOutput, sarifStore) {
|
|
8345
|
+
let parsed;
|
|
8346
|
+
try {
|
|
8347
|
+
parsed = JSON.parse(rawOutput);
|
|
8348
|
+
} catch {
|
|
8349
|
+
parsed = rawOutput;
|
|
8350
|
+
}
|
|
8351
|
+
const adapted = adaptScannerOutput(scanner, parsed);
|
|
8352
|
+
const stats = sarifStore.ingestRun(adapted.document, adapted.sourceTool);
|
|
8353
|
+
return { accepted: stats.accepted };
|
|
8354
|
+
}
|
|
8355
|
+
async function autoScan(workspaceRoot, sarifStore, logger2) {
|
|
8356
|
+
const start = Date.now();
|
|
8357
|
+
const detected = await detectScanners(workspaceRoot);
|
|
8358
|
+
const available = detected.filter((d) => d.available);
|
|
8359
|
+
logger2.info(
|
|
8360
|
+
{
|
|
8361
|
+
detected: detected.map((d) => `${d.scanner}:${d.available}`),
|
|
8362
|
+
available: available.length
|
|
8363
|
+
},
|
|
8364
|
+
"auto-scan: detection complete"
|
|
8365
|
+
);
|
|
8366
|
+
if (available.length === 0) {
|
|
8367
|
+
return {
|
|
8368
|
+
detected,
|
|
8369
|
+
results: [],
|
|
8370
|
+
totalFindings: 0,
|
|
8371
|
+
totalDurationMs: Date.now() - start
|
|
8372
|
+
};
|
|
8373
|
+
}
|
|
8374
|
+
const runResults = await Promise.allSettled(
|
|
8375
|
+
available.map((d) => runScanner(d.scanner, workspaceRoot))
|
|
8376
|
+
);
|
|
8377
|
+
const results = [];
|
|
8378
|
+
let totalFindings = 0;
|
|
8379
|
+
let persistNeeded = false;
|
|
8380
|
+
for (let i = 0; i < available.length; i++) {
|
|
8381
|
+
const detection = available[i];
|
|
8382
|
+
const settled = runResults[i];
|
|
8383
|
+
if (settled.status === "rejected") {
|
|
8384
|
+
const error = String(settled.reason);
|
|
8385
|
+
logger2.warn(
|
|
8386
|
+
{ scanner: detection.scanner, error },
|
|
8387
|
+
"auto-scan: scanner execution rejected"
|
|
8388
|
+
);
|
|
8389
|
+
results.push({
|
|
8390
|
+
scanner: detection.scanner,
|
|
8391
|
+
success: false,
|
|
8392
|
+
findingsIngested: 0,
|
|
8393
|
+
durationMs: 0,
|
|
8394
|
+
error
|
|
8395
|
+
});
|
|
8396
|
+
continue;
|
|
8397
|
+
}
|
|
8398
|
+
const runResult = settled.value;
|
|
8399
|
+
if (!runResult.success) {
|
|
8400
|
+
logger2.warn(
|
|
8401
|
+
{ scanner: runResult.scanner, error: runResult.error },
|
|
8402
|
+
"auto-scan: scanner returned failure"
|
|
8403
|
+
);
|
|
8404
|
+
results.push({
|
|
8405
|
+
scanner: runResult.scanner,
|
|
8406
|
+
success: false,
|
|
8407
|
+
findingsIngested: 0,
|
|
8408
|
+
durationMs: runResult.durationMs,
|
|
8409
|
+
error: runResult.error ?? "unknown error"
|
|
8410
|
+
});
|
|
8411
|
+
continue;
|
|
8412
|
+
}
|
|
8413
|
+
try {
|
|
8414
|
+
const { accepted } = ingestScannerRun(
|
|
8415
|
+
runResult.scanner,
|
|
8416
|
+
runResult.rawOutput,
|
|
8417
|
+
sarifStore
|
|
8418
|
+
);
|
|
8419
|
+
totalFindings += accepted;
|
|
8420
|
+
persistNeeded = true;
|
|
8421
|
+
logger2.info(
|
|
8422
|
+
{ scanner: runResult.scanner, accepted, durationMs: runResult.durationMs },
|
|
8423
|
+
"auto-scan: scanner ingested"
|
|
8424
|
+
);
|
|
8425
|
+
results.push({
|
|
8426
|
+
scanner: runResult.scanner,
|
|
8427
|
+
success: true,
|
|
8428
|
+
findingsIngested: accepted,
|
|
8429
|
+
durationMs: runResult.durationMs
|
|
8430
|
+
});
|
|
8431
|
+
} catch (err) {
|
|
8432
|
+
const error = err.message;
|
|
8433
|
+
logger2.warn(
|
|
8434
|
+
{ scanner: runResult.scanner, error },
|
|
8435
|
+
"auto-scan: adapter/ingestion failed"
|
|
8436
|
+
);
|
|
8437
|
+
results.push({
|
|
8438
|
+
scanner: runResult.scanner,
|
|
8439
|
+
success: false,
|
|
8440
|
+
findingsIngested: 0,
|
|
8441
|
+
durationMs: runResult.durationMs,
|
|
8442
|
+
error
|
|
8443
|
+
});
|
|
8444
|
+
}
|
|
8445
|
+
}
|
|
8446
|
+
if (persistNeeded) {
|
|
8447
|
+
await sarifStore.persist();
|
|
8448
|
+
}
|
|
8449
|
+
return {
|
|
8450
|
+
detected,
|
|
8451
|
+
results,
|
|
8452
|
+
totalFindings,
|
|
8453
|
+
totalDurationMs: Date.now() - start
|
|
8454
|
+
};
|
|
8455
|
+
}
|
|
8456
|
+
|
|
8086
8457
|
// src/schemas/tool-schemas.ts
|
|
8087
8458
|
var computeCrapSchema = {
|
|
8088
8459
|
type: "object",
|
|
@@ -8210,6 +8581,13 @@ var ingestScannerOutputSchema = {
|
|
|
8210
8581
|
required: ["scanner", "rawOutput"],
|
|
8211
8582
|
additionalProperties: false
|
|
8212
8583
|
};
|
|
8584
|
+
var autoScanSchema = {
|
|
8585
|
+
type: "object",
|
|
8586
|
+
description: "Auto-detect available scanners (ESLint, Semgrep, Bandit, Stryker) in the workspace, execute them, and ingest findings into the SARIF store. Returns detection results, per-scanner execution stats, and total findings ingested. Call this to populate findings without manual scanner invocation.",
|
|
8587
|
+
properties: {},
|
|
8588
|
+
required: [],
|
|
8589
|
+
additionalProperties: false
|
|
8590
|
+
};
|
|
8213
8591
|
var ingestSarifSchema = {
|
|
8214
8592
|
type: "object",
|
|
8215
8593
|
description: "Ingest a raw SARIF 2.1.0 report produced by an external scanner (Semgrep, ESLint, Bandit, Stryker, etc.), deduplicate it against the internal store, and return the normalized document. The agent should call this once per scanner invocation, not once per finding.",
|
|
@@ -8329,6 +8707,11 @@ async function main() {
|
|
|
8329
8707
|
name: "score_project",
|
|
8330
8708
|
description: "Aggregate the project score across Maintainability, Reliability, Security and Overall, returning a chat-friendly Markdown summary, the structured JSON, the local dashboard URL, and the consolidated SARIF report path.",
|
|
8331
8709
|
inputSchema: scoreProjectSchema
|
|
8710
|
+
},
|
|
8711
|
+
{
|
|
8712
|
+
name: "auto_scan",
|
|
8713
|
+
description: "Auto-detect available scanners (ESLint, Semgrep, Bandit, Stryker) in the workspace, run them, and ingest findings into the SARIF store.",
|
|
8714
|
+
inputSchema: autoScanSchema
|
|
8332
8715
|
}
|
|
8333
8716
|
]
|
|
8334
8717
|
}));
|
|
@@ -8610,6 +8993,34 @@ async function main() {
|
|
|
8610
8993
|
};
|
|
8611
8994
|
}
|
|
8612
8995
|
}
|
|
8996
|
+
case "auto_scan": {
|
|
8997
|
+
logger.info({ tool: "auto_scan" }, "Tool call received");
|
|
8998
|
+
try {
|
|
8999
|
+
const result = await autoScan(config.pluginRoot, sarifStore, logger);
|
|
9000
|
+
const markdown = renderAutoScanMarkdown(result);
|
|
9001
|
+
return {
|
|
9002
|
+
content: [
|
|
9003
|
+
{ type: "text", text: markdown },
|
|
9004
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
9005
|
+
]
|
|
9006
|
+
};
|
|
9007
|
+
} catch (err) {
|
|
9008
|
+
logger.error({ err }, "auto_scan failed");
|
|
9009
|
+
return {
|
|
9010
|
+
content: [
|
|
9011
|
+
{
|
|
9012
|
+
type: "text",
|
|
9013
|
+
text: JSON.stringify(
|
|
9014
|
+
{ tool: "auto_scan", status: "error", message: err.message },
|
|
9015
|
+
null,
|
|
9016
|
+
2
|
|
9017
|
+
)
|
|
9018
|
+
}
|
|
9019
|
+
],
|
|
9020
|
+
isError: true
|
|
9021
|
+
};
|
|
9022
|
+
}
|
|
9023
|
+
}
|
|
8613
9024
|
default:
|
|
8614
9025
|
throw new Error(`[claude-crap] Unknown tool: ${name}`);
|
|
8615
9026
|
}
|
|
@@ -8661,6 +9072,47 @@ async function main() {
|
|
|
8661
9072
|
const transport = new StdioServerTransport();
|
|
8662
9073
|
await server.connect(transport);
|
|
8663
9074
|
logger.info("claude-crap MCP server ready (stdio)");
|
|
9075
|
+
autoScan(config.pluginRoot, sarifStore, logger).then((result) => {
|
|
9076
|
+
const scanners = result.results.filter((r) => r.success).map((r) => r.scanner);
|
|
9077
|
+
logger.info(
|
|
9078
|
+
{
|
|
9079
|
+
scannersRun: scanners,
|
|
9080
|
+
totalFindings: result.totalFindings,
|
|
9081
|
+
durationMs: result.totalDurationMs
|
|
9082
|
+
},
|
|
9083
|
+
"auto-scan completed"
|
|
9084
|
+
);
|
|
9085
|
+
}).catch((err) => {
|
|
9086
|
+
logger.warn(
|
|
9087
|
+
{ err: err.message },
|
|
9088
|
+
"auto-scan failed \u2014 continuing without it"
|
|
9089
|
+
);
|
|
9090
|
+
});
|
|
9091
|
+
}
|
|
9092
|
+
function renderAutoScanMarkdown(result) {
|
|
9093
|
+
const lines = ["## claude-crap :: auto-scan results\n"];
|
|
9094
|
+
lines.push("### Detected scanners\n");
|
|
9095
|
+
lines.push("| Scanner | Available | Reason |");
|
|
9096
|
+
lines.push("| ------- | :-------: | ------ |");
|
|
9097
|
+
for (const d of result.detected) {
|
|
9098
|
+
lines.push(`| ${d.scanner} | ${d.available ? "yes" : "no"} | ${d.reason} |`);
|
|
9099
|
+
}
|
|
9100
|
+
lines.push("");
|
|
9101
|
+
if (result.results.length > 0) {
|
|
9102
|
+
lines.push("### Execution results\n");
|
|
9103
|
+
lines.push("| Scanner | Status | Findings | Duration |");
|
|
9104
|
+
lines.push("| ------- | :----: | :------: | -------: |");
|
|
9105
|
+
for (const r of result.results) {
|
|
9106
|
+
const status = r.success ? "ok" : "failed";
|
|
9107
|
+
const duration = `${(r.durationMs / 1e3).toFixed(1)}s`;
|
|
9108
|
+
lines.push(`| ${r.scanner} | ${status} | ${r.findingsIngested} | ${duration} |`);
|
|
9109
|
+
}
|
|
9110
|
+
lines.push("");
|
|
9111
|
+
}
|
|
9112
|
+
lines.push(
|
|
9113
|
+
`**Total findings ingested:** ${result.totalFindings} in ${(result.totalDurationMs / 1e3).toFixed(1)}s`
|
|
9114
|
+
);
|
|
9115
|
+
return lines.join("\n");
|
|
8664
9116
|
}
|
|
8665
9117
|
function safeLoadStrictness(workspaceRoot, logger2) {
|
|
8666
9118
|
try {
|