claude-crap 0.1.2 → 0.3.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 +68 -0
- package/README.md +44 -23
- package/dist/index.js +142 -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/bootstrap.d.ts +89 -0
- package/dist/scanner/bootstrap.d.ts.map +1 -0
- package/dist/scanner/bootstrap.js +278 -0
- package/dist/scanner/bootstrap.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 +23 -0
- package/dist/scanner/index.d.ts.map +1 -0
- package/dist/scanner/index.js +23 -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 +23 -0
- package/dist/schemas/tool-schemas.d.ts.map +1 -1
- package/dist/schemas/tool-schemas.js +23 -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 +732 -0
- package/plugin/bundle/mcp-server.mjs.map +4 -4
- package/plugin/package.json +1 -1
- package/src/index.ts +176 -0
- package/src/scanner/auto-scan.ts +212 -0
- package/src/scanner/bootstrap.ts +383 -0
- package/src/scanner/detector.ts +224 -0
- package/src/scanner/index.ts +30 -0
- package/src/scanner/runner.ts +212 -0
- package/src/schemas/tool-schemas.ts +27 -0
- package/src/tests/auto-scan.test.ts +137 -0
- package/src/tests/integration/mcp-server.integration.test.ts +3 -1
- package/src/tests/scanner-bootstrap.test.ts +186 -0
- package/src/tests/scanner-detector.test.ts +181 -0
- package/src/tests/scanner-runner.test.ts +63 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the scanner auto-detector.
|
|
3
|
+
*
|
|
4
|
+
* These tests probe the detection logic for each scanner type:
|
|
5
|
+
* config file detection, package.json dependency detection, and
|
|
6
|
+
* the fallback to binary availability.
|
|
7
|
+
*
|
|
8
|
+
* @module tests/scanner-detector.test
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
import { mkdtempSync, writeFileSync, mkdirSync, rmSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
import { tmpdir } from "node:os";
|
|
16
|
+
|
|
17
|
+
import { detectScanners, SCANNER_SIGNALS } from "../scanner/detector.js";
|
|
18
|
+
|
|
19
|
+
function makeTmpDir(): string {
|
|
20
|
+
return mkdtempSync(join(tmpdir(), "crap-detect-"));
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("detectScanners", () => {
|
|
24
|
+
it("detects eslint when eslint.config.mjs exists", async () => {
|
|
25
|
+
const dir = makeTmpDir();
|
|
26
|
+
try {
|
|
27
|
+
writeFileSync(join(dir, "eslint.config.mjs"), "export default [];");
|
|
28
|
+
const results = await detectScanners(dir);
|
|
29
|
+
const eslint = results.find((r) => r.scanner === "eslint");
|
|
30
|
+
assert.ok(eslint);
|
|
31
|
+
assert.equal(eslint.available, true);
|
|
32
|
+
assert.ok(eslint.reason.includes("eslint.config.mjs"));
|
|
33
|
+
assert.ok(eslint.configPath);
|
|
34
|
+
} finally {
|
|
35
|
+
rmSync(dir, { recursive: true, force: true });
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("detects eslint from .eslintrc.json", async () => {
|
|
40
|
+
const dir = makeTmpDir();
|
|
41
|
+
try {
|
|
42
|
+
writeFileSync(join(dir, ".eslintrc.json"), "{}");
|
|
43
|
+
const results = await detectScanners(dir);
|
|
44
|
+
const eslint = results.find((r) => r.scanner === "eslint");
|
|
45
|
+
assert.ok(eslint);
|
|
46
|
+
assert.equal(eslint.available, true);
|
|
47
|
+
assert.ok(eslint.reason.includes(".eslintrc.json"));
|
|
48
|
+
} finally {
|
|
49
|
+
rmSync(dir, { recursive: true, force: true });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("detects semgrep when .semgrep.yml exists", async () => {
|
|
54
|
+
const dir = makeTmpDir();
|
|
55
|
+
try {
|
|
56
|
+
writeFileSync(join(dir, ".semgrep.yml"), "rules: []");
|
|
57
|
+
const results = await detectScanners(dir);
|
|
58
|
+
const semgrep = results.find((r) => r.scanner === "semgrep");
|
|
59
|
+
assert.ok(semgrep);
|
|
60
|
+
assert.equal(semgrep.available, true);
|
|
61
|
+
assert.ok(semgrep.reason.includes(".semgrep.yml"));
|
|
62
|
+
} finally {
|
|
63
|
+
rmSync(dir, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
it("detects bandit when .bandit config exists", async () => {
|
|
68
|
+
const dir = makeTmpDir();
|
|
69
|
+
try {
|
|
70
|
+
writeFileSync(join(dir, ".bandit"), "");
|
|
71
|
+
const results = await detectScanners(dir);
|
|
72
|
+
const bandit = results.find((r) => r.scanner === "bandit");
|
|
73
|
+
assert.ok(bandit);
|
|
74
|
+
assert.equal(bandit.available, true);
|
|
75
|
+
} finally {
|
|
76
|
+
rmSync(dir, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("detects stryker when stryker.conf.js exists", async () => {
|
|
81
|
+
const dir = makeTmpDir();
|
|
82
|
+
try {
|
|
83
|
+
writeFileSync(join(dir, "stryker.conf.js"), "module.exports = {};");
|
|
84
|
+
const results = await detectScanners(dir);
|
|
85
|
+
const stryker = results.find((r) => r.scanner === "stryker");
|
|
86
|
+
assert.ok(stryker);
|
|
87
|
+
assert.equal(stryker.available, true);
|
|
88
|
+
} finally {
|
|
89
|
+
rmSync(dir, { recursive: true, force: true });
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("detects eslint from package.json devDependencies", async () => {
|
|
94
|
+
const dir = makeTmpDir();
|
|
95
|
+
try {
|
|
96
|
+
writeFileSync(
|
|
97
|
+
join(dir, "package.json"),
|
|
98
|
+
JSON.stringify({ devDependencies: { eslint: "^9.0.0" } }),
|
|
99
|
+
);
|
|
100
|
+
const results = await detectScanners(dir);
|
|
101
|
+
const eslint = results.find((r) => r.scanner === "eslint");
|
|
102
|
+
assert.ok(eslint);
|
|
103
|
+
assert.equal(eslint.available, true);
|
|
104
|
+
assert.ok(eslint.reason.includes("package.json"));
|
|
105
|
+
} finally {
|
|
106
|
+
rmSync(dir, { recursive: true, force: true });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("detects stryker from package.json @stryker-mutator/core", async () => {
|
|
111
|
+
const dir = makeTmpDir();
|
|
112
|
+
try {
|
|
113
|
+
writeFileSync(
|
|
114
|
+
join(dir, "package.json"),
|
|
115
|
+
JSON.stringify({
|
|
116
|
+
devDependencies: { "@stryker-mutator/core": "^7.0.0" },
|
|
117
|
+
}),
|
|
118
|
+
);
|
|
119
|
+
const results = await detectScanners(dir);
|
|
120
|
+
const stryker = results.find((r) => r.scanner === "stryker");
|
|
121
|
+
assert.ok(stryker);
|
|
122
|
+
assert.equal(stryker.available, true);
|
|
123
|
+
assert.ok(stryker.reason.includes("package.json"));
|
|
124
|
+
} finally {
|
|
125
|
+
rmSync(dir, { recursive: true, force: true });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("returns available:false for all scanners in an empty directory", async () => {
|
|
130
|
+
const dir = makeTmpDir();
|
|
131
|
+
try {
|
|
132
|
+
const results = await detectScanners(dir);
|
|
133
|
+
// Config and package.json probes will all fail.
|
|
134
|
+
// Binary probe results depend on the host — don't assert on those,
|
|
135
|
+
// but do assert the structure is correct.
|
|
136
|
+
assert.equal(results.length, 4);
|
|
137
|
+
for (const r of results) {
|
|
138
|
+
assert.ok(["eslint", "semgrep", "bandit", "stryker"].includes(r.scanner));
|
|
139
|
+
assert.equal(typeof r.available, "boolean");
|
|
140
|
+
assert.equal(typeof r.reason, "string");
|
|
141
|
+
}
|
|
142
|
+
} finally {
|
|
143
|
+
rmSync(dir, { recursive: true, force: true });
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it("handles malformed package.json gracefully", async () => {
|
|
148
|
+
const dir = makeTmpDir();
|
|
149
|
+
try {
|
|
150
|
+
writeFileSync(join(dir, "package.json"), "not json at all");
|
|
151
|
+
// Should not throw — just skip the package.json probe
|
|
152
|
+
const results = await detectScanners(dir);
|
|
153
|
+
assert.equal(results.length, 4);
|
|
154
|
+
} finally {
|
|
155
|
+
rmSync(dir, { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("short-circuits on config file — does not need binary", async () => {
|
|
160
|
+
const dir = makeTmpDir();
|
|
161
|
+
try {
|
|
162
|
+
writeFileSync(join(dir, "eslint.config.mjs"), "export default [];");
|
|
163
|
+
const results = await detectScanners(dir);
|
|
164
|
+
const eslint = results.find((r) => r.scanner === "eslint");
|
|
165
|
+
assert.ok(eslint);
|
|
166
|
+
assert.equal(eslint.available, true);
|
|
167
|
+
// Reason mentions config file, not binary
|
|
168
|
+
assert.ok(eslint.reason.includes("config file"));
|
|
169
|
+
assert.ok(!eslint.reason.includes("binary"));
|
|
170
|
+
} finally {
|
|
171
|
+
rmSync(dir, { recursive: true, force: true });
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it("SCANNER_SIGNALS covers all four scanners", () => {
|
|
176
|
+
assert.deepEqual(
|
|
177
|
+
Object.keys(SCANNER_SIGNALS).sort(),
|
|
178
|
+
["bandit", "eslint", "semgrep", "stryker"],
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for the scanner runner.
|
|
3
|
+
*
|
|
4
|
+
* These tests verify the command definitions and error handling of the
|
|
5
|
+
* runner module. Actual scanner execution is not tested here — that
|
|
6
|
+
* requires the scanner binaries to be installed.
|
|
7
|
+
*
|
|
8
|
+
* @module tests/scanner-runner.test
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { describe, it } from "node:test";
|
|
12
|
+
import assert from "node:assert/strict";
|
|
13
|
+
|
|
14
|
+
import { getScannerCommand, type ScannerCommand } from "../scanner/runner.js";
|
|
15
|
+
|
|
16
|
+
describe("getScannerCommand", () => {
|
|
17
|
+
it("returns correct eslint command", () => {
|
|
18
|
+
const cmd = getScannerCommand("eslint", "/tmp/project");
|
|
19
|
+
assert.equal(cmd.command, "npx");
|
|
20
|
+
assert.deepEqual(cmd.args, ["eslint", "-f", "json", "."]);
|
|
21
|
+
assert.equal(cmd.nonZeroIsNormal, true);
|
|
22
|
+
assert.equal(cmd.outputFile, undefined);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("returns correct semgrep command", () => {
|
|
26
|
+
const cmd = getScannerCommand("semgrep", "/tmp/project");
|
|
27
|
+
assert.equal(cmd.command, "semgrep");
|
|
28
|
+
assert.deepEqual(cmd.args, ["--sarif", "--quiet", "."]);
|
|
29
|
+
assert.equal(cmd.nonZeroIsNormal, false);
|
|
30
|
+
assert.equal(cmd.outputFile, undefined);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("returns correct bandit command", () => {
|
|
34
|
+
const cmd = getScannerCommand("bandit", "/tmp/project");
|
|
35
|
+
assert.equal(cmd.command, "bandit");
|
|
36
|
+
assert.deepEqual(cmd.args, ["-f", "json", "-r", ".", "-q"]);
|
|
37
|
+
assert.equal(cmd.nonZeroIsNormal, true);
|
|
38
|
+
assert.equal(cmd.outputFile, undefined);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("returns correct stryker command with output file", () => {
|
|
42
|
+
const cmd = getScannerCommand("stryker", "/tmp/project");
|
|
43
|
+
assert.equal(cmd.command, "npx");
|
|
44
|
+
assert.deepEqual(cmd.args, ["stryker", "run"]);
|
|
45
|
+
assert.equal(cmd.nonZeroIsNormal, false);
|
|
46
|
+
assert.ok(cmd.outputFile);
|
|
47
|
+
assert.ok(cmd.outputFile.includes("mutation.json"));
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("all scanners have reasonable timeouts", () => {
|
|
51
|
+
for (const scanner of ["eslint", "semgrep", "bandit", "stryker"] as const) {
|
|
52
|
+
const cmd = getScannerCommand(scanner, "/tmp");
|
|
53
|
+
assert.ok(cmd.timeoutMs >= 60_000, `${scanner} timeout should be >= 60s`);
|
|
54
|
+
assert.ok(cmd.timeoutMs <= 300_000, `${scanner} timeout should be <= 300s`);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("stryker has a longer timeout than other scanners", () => {
|
|
59
|
+
const stryker = getScannerCommand("stryker", "/tmp");
|
|
60
|
+
const eslint = getScannerCommand("eslint", "/tmp");
|
|
61
|
+
assert.ok(stryker.timeoutMs > eslint.timeoutMs);
|
|
62
|
+
});
|
|
63
|
+
});
|