claude-crap 0.3.0 → 0.3.3

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.
@@ -7414,16 +7414,18 @@ async function startDashboard(options) {
7414
7414
  });
7415
7415
  await fastify.register(fastifyStatic, {
7416
7416
  root: publicRoot,
7417
- prefix: "/",
7418
- decorateReply: false
7417
+ prefix: "/"
7419
7418
  });
7420
- fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.1.0" }));
7419
+ fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.3.2" }));
7421
7420
  fastify.get("/api/score", async () => {
7422
7421
  const stats = await workspaceStatsProvider();
7423
7422
  const score = await buildScore(config, sarifStore, stats, urlOf(fastify, config));
7424
7423
  return score;
7425
7424
  });
7426
7425
  fastify.get("/api/sarif", async () => sarifStore.toSarifDocument());
7426
+ fastify.get("/", async (_request, reply) => {
7427
+ return reply.sendFile("index.html");
7428
+ });
7427
7429
  await fastify.listen({ port: config.dashboardPort, host: "127.0.0.1" });
7428
7430
  const url = `http://127.0.0.1:${config.dashboardPort}`;
7429
7431
  logger2.info({ url, publicRoot }, "claude-crap dashboard listening");
@@ -8083,6 +8085,10 @@ function resolveWithinWorkspace(workspaceRoot, filePath) {
8083
8085
  return candidate;
8084
8086
  }
8085
8087
 
8088
+ // src/scanner/auto-scan.ts
8089
+ import { existsSync as existsSync4 } from "node:fs";
8090
+ import { join as join9 } from "node:path";
8091
+
8086
8092
  // src/scanner/detector.ts
8087
8093
  import { existsSync, readFileSync as readFileSync2 } from "node:fs";
8088
8094
  import { join as join6 } from "node:path";
@@ -8264,7 +8270,8 @@ function runScanner(scanner, workspaceRoot) {
8264
8270
  },
8265
8271
  (err, stdout, stderr) => {
8266
8272
  const durationMs = Date.now() - start;
8267
- if (err && !cmd.nonZeroIsNormal) {
8273
+ const isFatalError = cmd.nonZeroIsNormal && err && (!stdout?.trim() || stderr?.includes("Oops!") || stderr?.includes("couldn't find"));
8274
+ if (err && (!cmd.nonZeroIsNormal || isFatalError)) {
8268
8275
  if (cmd.outputFile && existsSync2(cmd.outputFile)) {
8269
8276
  try {
8270
8277
  const fileOutput = readFileSync3(cmd.outputFile, "utf-8");
@@ -8340,120 +8347,6 @@ function runScanner(scanner, workspaceRoot) {
8340
8347
  });
8341
8348
  }
8342
8349
 
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
-
8457
8350
  // src/scanner/bootstrap.ts
8458
8351
  import { existsSync as existsSync3, writeFileSync, readdirSync } from "node:fs";
8459
8352
  import { join as join8 } from "node:path";
@@ -8489,7 +8382,14 @@ export default tseslint.config(
8489
8382
  js.configs.recommended,
8490
8383
  ...tseslint.configs.recommended,
8491
8384
  {
8492
- ignores: ["dist/", "node_modules/", "coverage/"],
8385
+ ignores: [
8386
+ "dist/",
8387
+ "node_modules/",
8388
+ "coverage/",
8389
+ "**/bundle/",
8390
+ "**/vendor/",
8391
+ "**/*.min.js",
8392
+ ],
8493
8393
  },
8494
8394
  );
8495
8395
  `;
@@ -8499,7 +8399,14 @@ export default tseslint.config(
8499
8399
  export default [
8500
8400
  js.configs.recommended,
8501
8401
  {
8502
- ignores: ["dist/", "node_modules/", "coverage/"],
8402
+ ignores: [
8403
+ "dist/",
8404
+ "node_modules/",
8405
+ "coverage/",
8406
+ "**/bundle/",
8407
+ "**/vendor/",
8408
+ "**/*.min.js",
8409
+ ],
8503
8410
  },
8504
8411
  ];
8505
8412
  `;
@@ -8589,7 +8496,8 @@ function getRecommendation(projectType) {
8589
8496
  async function bootstrapScanner(workspaceRoot, sarifStore, logger2) {
8590
8497
  const detections = await detectScanners(workspaceRoot);
8591
8498
  const available = detections.filter((d) => d.available);
8592
- if (available.length > 0) {
8499
+ const eslintNeedsConfig = available.some((d) => d.scanner === "eslint") && !detections.some((d) => d.scanner === "eslint" && d.configPath);
8500
+ if (available.length > 0 && !eslintNeedsConfig) {
8593
8501
  const existingScanners = available.map((d) => d.scanner);
8594
8502
  logger2.info(
8595
8503
  { existingScanners },
@@ -8614,13 +8522,23 @@ async function bootstrapScanner(workspaceRoot, sarifStore, logger2) {
8614
8522
  );
8615
8523
  if (recommendation.canAutoInstall) {
8616
8524
  const isTypeScript = projectType === "typescript";
8617
- const packages = isTypeScript ? ["eslint", "@eslint/js", "typescript-eslint"] : ["eslint", "@eslint/js"];
8618
- const installStep = await npmInstall(workspaceRoot, packages);
8619
- steps.push(installStep);
8620
- if (installStep.success) {
8621
- const configStep = writeEslintConfigFile(workspaceRoot, isTypeScript);
8622
- steps.push(configStep);
8525
+ const eslintAlreadyInstalled = available.some((d) => d.scanner === "eslint");
8526
+ if (!eslintAlreadyInstalled) {
8527
+ const packages = isTypeScript ? ["eslint", "@eslint/js", "typescript-eslint"] : ["eslint", "@eslint/js"];
8528
+ const installStep = await npmInstall(workspaceRoot, packages);
8529
+ steps.push(installStep);
8530
+ if (!installStep.success) {
8531
+ return buildResult(projectType, steps, null);
8532
+ }
8533
+ } else {
8534
+ steps.push({
8535
+ action: "npm install eslint",
8536
+ success: true,
8537
+ detail: "eslint already in package.json \u2014 skipped install"
8538
+ });
8623
8539
  }
8540
+ const configStep = writeEslintConfigFile(workspaceRoot, isTypeScript);
8541
+ steps.push(configStep);
8624
8542
  } else {
8625
8543
  steps.push({
8626
8544
  action: `suggest ${recommendation.scanner} install`,
@@ -8632,25 +8550,73 @@ async function bootstrapScanner(workspaceRoot, sarifStore, logger2) {
8632
8550
  let autoScanResult = null;
8633
8551
  if (installSucceeded && recommendation.canAutoInstall) {
8634
8552
  try {
8635
- autoScanResult = await autoScan(workspaceRoot, sarifStore, logger2);
8553
+ const scanStart = Date.now();
8554
+ const postDetections = await detectScanners(workspaceRoot);
8555
+ const postAvailable = postDetections.filter((d) => d.available);
8556
+ const scanResults = [];
8557
+ let scanFindings = 0;
8558
+ const settled = await Promise.allSettled(
8559
+ postAvailable.map((d) => runScanner(d.scanner, workspaceRoot))
8560
+ );
8561
+ for (let i = 0; i < postAvailable.length; i++) {
8562
+ const det = postAvailable[i];
8563
+ const res = settled[i];
8564
+ if (res.status === "rejected" || !res.value.success) {
8565
+ scanResults.push({
8566
+ scanner: det.scanner,
8567
+ success: false,
8568
+ findingsIngested: 0,
8569
+ durationMs: res.status === "fulfilled" ? res.value.durationMs : 0,
8570
+ error: res.status === "rejected" ? String(res.reason) : res.value.error ?? "unknown error"
8571
+ });
8572
+ continue;
8573
+ }
8574
+ const runResult = res.value;
8575
+ let parsed;
8576
+ try {
8577
+ parsed = JSON.parse(runResult.rawOutput);
8578
+ } catch {
8579
+ parsed = runResult.rawOutput;
8580
+ }
8581
+ const adapted = adaptScannerOutput(runResult.scanner, parsed);
8582
+ const stats = sarifStore.ingestRun(adapted.document, adapted.sourceTool);
8583
+ scanFindings += stats.accepted;
8584
+ scanResults.push({
8585
+ scanner: runResult.scanner,
8586
+ success: true,
8587
+ findingsIngested: stats.accepted,
8588
+ durationMs: runResult.durationMs
8589
+ });
8590
+ }
8591
+ if (scanFindings > 0) await sarifStore.persist();
8592
+ autoScanResult = {
8593
+ detected: postDetections,
8594
+ results: scanResults,
8595
+ totalFindings: scanFindings,
8596
+ totalDurationMs: Date.now() - scanStart
8597
+ };
8636
8598
  } catch (err) {
8637
8599
  logger2.warn(
8638
8600
  { err: err.message },
8639
- "bootstrap: auto_scan after install failed"
8601
+ "bootstrap: scan after install failed"
8640
8602
  );
8641
8603
  }
8642
8604
  }
8605
+ return buildResult(projectType, steps, autoScanResult, recommendation);
8606
+ }
8607
+ function buildResult(projectType, steps, autoScanResult, recommendation) {
8608
+ const success = steps.every((s) => s.success);
8643
8609
  const findings = autoScanResult?.totalFindings ?? 0;
8644
- const scannerInstalled = recommendation.canAutoInstall && installSucceeded;
8610
+ const scanner = recommendation?.scanner ?? "unknown";
8645
8611
  let summary;
8646
- if (scannerInstalled && autoScanResult) {
8647
- summary = `Installed ${recommendation.scanner} for ${projectType} project. Auto-scan found ${findings} finding(s).`;
8648
- } else if (scannerInstalled) {
8649
- summary = `Installed ${recommendation.scanner} for ${projectType} project. Auto-scan did not run.`;
8650
- } else if (!recommendation.canAutoInstall) {
8651
- summary = `Detected ${projectType} project. Install ${recommendation.scanner} manually: ${recommendation.installInstructions}`;
8612
+ if (success && autoScanResult) {
8613
+ summary = `Configured ${scanner} for ${projectType} project. Scan found ${findings} finding(s).`;
8614
+ } else if (success && recommendation && !recommendation.canAutoInstall) {
8615
+ summary = `Detected ${projectType} project. Install ${scanner} manually: ${recommendation.installInstructions}`;
8616
+ } else if (success) {
8617
+ summary = `Configured ${scanner} for ${projectType} project.`;
8652
8618
  } else {
8653
- summary = `Failed to install ${recommendation.scanner}. Check the error details in the steps.`;
8619
+ summary = `Failed to configure ${scanner}. Check the error details in the steps.`;
8654
8620
  }
8655
8621
  return {
8656
8622
  projectType,
@@ -8658,11 +8624,166 @@ async function bootstrapScanner(workspaceRoot, sarifStore, logger2) {
8658
8624
  existingScanners: [],
8659
8625
  steps,
8660
8626
  autoScanResult,
8661
- success: installSucceeded,
8627
+ success,
8662
8628
  summary
8663
8629
  };
8664
8630
  }
8665
8631
 
8632
+ // src/scanner/auto-scan.ts
8633
+ function ingestScannerRun(scanner, rawOutput, sarifStore) {
8634
+ let parsed;
8635
+ try {
8636
+ parsed = JSON.parse(rawOutput);
8637
+ } catch {
8638
+ parsed = rawOutput;
8639
+ }
8640
+ const adapted = adaptScannerOutput(scanner, parsed);
8641
+ const stats = sarifStore.ingestRun(adapted.document, adapted.sourceTool);
8642
+ return { accepted: stats.accepted };
8643
+ }
8644
+ async function autoScan(workspaceRoot, sarifStore, logger2) {
8645
+ const start = Date.now();
8646
+ const detected = await detectScanners(workspaceRoot);
8647
+ const available = detected.filter((d) => d.available);
8648
+ logger2.info(
8649
+ {
8650
+ detected: detected.map((d) => `${d.scanner}:${d.available}`),
8651
+ available: available.length
8652
+ },
8653
+ "auto-scan: detection complete"
8654
+ );
8655
+ const eslintConfigFiles = [
8656
+ "eslint.config.js",
8657
+ "eslint.config.mjs",
8658
+ "eslint.config.cjs",
8659
+ "eslint.config.ts",
8660
+ "eslint.config.mts",
8661
+ "eslint.config.cts",
8662
+ ".eslintrc.js",
8663
+ ".eslintrc.cjs",
8664
+ ".eslintrc.yaml",
8665
+ ".eslintrc.yml",
8666
+ ".eslintrc.json"
8667
+ ];
8668
+ const eslintDetected = available.some((d) => d.scanner === "eslint");
8669
+ const hasEslintConfig = eslintConfigFiles.some((f) => existsSync4(join9(workspaceRoot, f)));
8670
+ if (eslintDetected && !hasEslintConfig) {
8671
+ logger2.info("auto-scan: ESLint detected but no config \u2014 running bootstrap");
8672
+ try {
8673
+ const bootstrapResult = await bootstrapScanner(workspaceRoot, sarifStore, logger2);
8674
+ if (bootstrapResult.autoScanResult) {
8675
+ return bootstrapResult.autoScanResult;
8676
+ }
8677
+ } catch (err) {
8678
+ logger2.warn(
8679
+ { err: err.message },
8680
+ "auto-scan: bootstrap config creation failed"
8681
+ );
8682
+ }
8683
+ }
8684
+ if (available.length === 0) {
8685
+ logger2.info("auto-scan: no scanners found, attempting bootstrap");
8686
+ try {
8687
+ const bootstrapResult = await bootstrapScanner(workspaceRoot, sarifStore, logger2);
8688
+ if (bootstrapResult.autoScanResult) {
8689
+ return bootstrapResult.autoScanResult;
8690
+ }
8691
+ } catch (err) {
8692
+ logger2.warn(
8693
+ { err: err.message },
8694
+ "auto-scan: bootstrap failed \u2014 continuing with empty results"
8695
+ );
8696
+ }
8697
+ return {
8698
+ detected,
8699
+ results: [],
8700
+ totalFindings: 0,
8701
+ totalDurationMs: Date.now() - start
8702
+ };
8703
+ }
8704
+ const runResults = await Promise.allSettled(
8705
+ available.map((d) => runScanner(d.scanner, workspaceRoot))
8706
+ );
8707
+ const results = [];
8708
+ let totalFindings = 0;
8709
+ let persistNeeded = false;
8710
+ for (let i = 0; i < available.length; i++) {
8711
+ const detection = available[i];
8712
+ const settled = runResults[i];
8713
+ if (settled.status === "rejected") {
8714
+ const error = String(settled.reason);
8715
+ logger2.warn(
8716
+ { scanner: detection.scanner, error },
8717
+ "auto-scan: scanner execution rejected"
8718
+ );
8719
+ results.push({
8720
+ scanner: detection.scanner,
8721
+ success: false,
8722
+ findingsIngested: 0,
8723
+ durationMs: 0,
8724
+ error
8725
+ });
8726
+ continue;
8727
+ }
8728
+ const runResult = settled.value;
8729
+ if (!runResult.success) {
8730
+ logger2.warn(
8731
+ { scanner: runResult.scanner, error: runResult.error },
8732
+ "auto-scan: scanner returned failure"
8733
+ );
8734
+ results.push({
8735
+ scanner: runResult.scanner,
8736
+ success: false,
8737
+ findingsIngested: 0,
8738
+ durationMs: runResult.durationMs,
8739
+ error: runResult.error ?? "unknown error"
8740
+ });
8741
+ continue;
8742
+ }
8743
+ try {
8744
+ const { accepted } = ingestScannerRun(
8745
+ runResult.scanner,
8746
+ runResult.rawOutput,
8747
+ sarifStore
8748
+ );
8749
+ totalFindings += accepted;
8750
+ persistNeeded = true;
8751
+ logger2.info(
8752
+ { scanner: runResult.scanner, accepted, durationMs: runResult.durationMs },
8753
+ "auto-scan: scanner ingested"
8754
+ );
8755
+ results.push({
8756
+ scanner: runResult.scanner,
8757
+ success: true,
8758
+ findingsIngested: accepted,
8759
+ durationMs: runResult.durationMs
8760
+ });
8761
+ } catch (err) {
8762
+ const error = err.message;
8763
+ logger2.warn(
8764
+ { scanner: runResult.scanner, error },
8765
+ "auto-scan: adapter/ingestion failed"
8766
+ );
8767
+ results.push({
8768
+ scanner: runResult.scanner,
8769
+ success: false,
8770
+ findingsIngested: 0,
8771
+ durationMs: runResult.durationMs,
8772
+ error
8773
+ });
8774
+ }
8775
+ }
8776
+ if (persistNeeded) {
8777
+ await sarifStore.persist();
8778
+ }
8779
+ return {
8780
+ detected,
8781
+ results,
8782
+ totalFindings,
8783
+ totalDurationMs: Date.now() - start
8784
+ };
8785
+ }
8786
+
8666
8787
  // src/schemas/tool-schemas.ts
8667
8788
  var computeCrapSchema = {
8668
8789
  type: "object",