claude-crap 0.1.2

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.
Files changed (202) hide show
  1. package/CHANGELOG.md +308 -0
  2. package/LICENSE +21 -0
  3. package/README.md +550 -0
  4. package/bin/claude-crap.mjs +141 -0
  5. package/dist/adapters/bandit.d.ts +48 -0
  6. package/dist/adapters/bandit.d.ts.map +1 -0
  7. package/dist/adapters/bandit.js +145 -0
  8. package/dist/adapters/bandit.js.map +1 -0
  9. package/dist/adapters/common.d.ts +73 -0
  10. package/dist/adapters/common.d.ts.map +1 -0
  11. package/dist/adapters/common.js +78 -0
  12. package/dist/adapters/common.js.map +1 -0
  13. package/dist/adapters/eslint.d.ts +52 -0
  14. package/dist/adapters/eslint.d.ts.map +1 -0
  15. package/dist/adapters/eslint.js +142 -0
  16. package/dist/adapters/eslint.js.map +1 -0
  17. package/dist/adapters/index.d.ts +47 -0
  18. package/dist/adapters/index.d.ts.map +1 -0
  19. package/dist/adapters/index.js +64 -0
  20. package/dist/adapters/index.js.map +1 -0
  21. package/dist/adapters/semgrep.d.ts +30 -0
  22. package/dist/adapters/semgrep.d.ts.map +1 -0
  23. package/dist/adapters/semgrep.js +130 -0
  24. package/dist/adapters/semgrep.js.map +1 -0
  25. package/dist/adapters/stryker.d.ts +55 -0
  26. package/dist/adapters/stryker.d.ts.map +1 -0
  27. package/dist/adapters/stryker.js +165 -0
  28. package/dist/adapters/stryker.js.map +1 -0
  29. package/dist/ast/cyclomatic.d.ts +48 -0
  30. package/dist/ast/cyclomatic.d.ts.map +1 -0
  31. package/dist/ast/cyclomatic.js +106 -0
  32. package/dist/ast/cyclomatic.js.map +1 -0
  33. package/dist/ast/index.d.ts +26 -0
  34. package/dist/ast/index.d.ts.map +1 -0
  35. package/dist/ast/index.js +23 -0
  36. package/dist/ast/index.js.map +1 -0
  37. package/dist/ast/language-config.d.ts +70 -0
  38. package/dist/ast/language-config.d.ts.map +1 -0
  39. package/dist/ast/language-config.js +192 -0
  40. package/dist/ast/language-config.js.map +1 -0
  41. package/dist/ast/tree-sitter-engine.d.ts +133 -0
  42. package/dist/ast/tree-sitter-engine.d.ts.map +1 -0
  43. package/dist/ast/tree-sitter-engine.js +270 -0
  44. package/dist/ast/tree-sitter-engine.js.map +1 -0
  45. package/dist/config.d.ts +57 -0
  46. package/dist/config.d.ts.map +1 -0
  47. package/dist/config.js +78 -0
  48. package/dist/config.js.map +1 -0
  49. package/dist/crap-config.d.ts +97 -0
  50. package/dist/crap-config.d.ts.map +1 -0
  51. package/dist/crap-config.js +144 -0
  52. package/dist/crap-config.js.map +1 -0
  53. package/dist/dashboard/server.d.ts +65 -0
  54. package/dist/dashboard/server.d.ts.map +1 -0
  55. package/dist/dashboard/server.js +147 -0
  56. package/dist/dashboard/server.js.map +1 -0
  57. package/dist/index.d.ts +32 -0
  58. package/dist/index.d.ts.map +1 -0
  59. package/dist/index.js +574 -0
  60. package/dist/index.js.map +1 -0
  61. package/dist/metrics/crap.d.ts +71 -0
  62. package/dist/metrics/crap.d.ts.map +1 -0
  63. package/dist/metrics/crap.js +67 -0
  64. package/dist/metrics/crap.js.map +1 -0
  65. package/dist/metrics/index.d.ts +31 -0
  66. package/dist/metrics/index.d.ts.map +1 -0
  67. package/dist/metrics/index.js +27 -0
  68. package/dist/metrics/index.js.map +1 -0
  69. package/dist/metrics/score.d.ts +143 -0
  70. package/dist/metrics/score.d.ts.map +1 -0
  71. package/dist/metrics/score.js +224 -0
  72. package/dist/metrics/score.js.map +1 -0
  73. package/dist/metrics/tdr.d.ts +106 -0
  74. package/dist/metrics/tdr.d.ts.map +1 -0
  75. package/dist/metrics/tdr.js +117 -0
  76. package/dist/metrics/tdr.js.map +1 -0
  77. package/dist/metrics/workspace-walker.d.ts +43 -0
  78. package/dist/metrics/workspace-walker.d.ts.map +1 -0
  79. package/dist/metrics/workspace-walker.js +137 -0
  80. package/dist/metrics/workspace-walker.js.map +1 -0
  81. package/dist/sarif/index.d.ts +21 -0
  82. package/dist/sarif/index.d.ts.map +1 -0
  83. package/dist/sarif/index.js +19 -0
  84. package/dist/sarif/index.js.map +1 -0
  85. package/dist/sarif/sarif-builder.d.ts +128 -0
  86. package/dist/sarif/sarif-builder.d.ts.map +1 -0
  87. package/dist/sarif/sarif-builder.js +79 -0
  88. package/dist/sarif/sarif-builder.js.map +1 -0
  89. package/dist/sarif/sarif-store.d.ts +205 -0
  90. package/dist/sarif/sarif-store.d.ts.map +1 -0
  91. package/dist/sarif/sarif-store.js +246 -0
  92. package/dist/sarif/sarif-store.js.map +1 -0
  93. package/dist/sarif/sarif-validator.d.ts +45 -0
  94. package/dist/sarif/sarif-validator.d.ts.map +1 -0
  95. package/dist/sarif/sarif-validator.js +138 -0
  96. package/dist/sarif/sarif-validator.js.map +1 -0
  97. package/dist/schemas/tool-schemas.d.ts +216 -0
  98. package/dist/schemas/tool-schemas.d.ts.map +1 -0
  99. package/dist/schemas/tool-schemas.js +208 -0
  100. package/dist/schemas/tool-schemas.js.map +1 -0
  101. package/dist/sdk.d.ts +45 -0
  102. package/dist/sdk.d.ts.map +1 -0
  103. package/dist/sdk.js +44 -0
  104. package/dist/sdk.js.map +1 -0
  105. package/dist/tools/index.d.ts +24 -0
  106. package/dist/tools/index.d.ts.map +1 -0
  107. package/dist/tools/index.js +23 -0
  108. package/dist/tools/index.js.map +1 -0
  109. package/dist/tools/test-harness.d.ts +75 -0
  110. package/dist/tools/test-harness.d.ts.map +1 -0
  111. package/dist/tools/test-harness.js +137 -0
  112. package/dist/tools/test-harness.js.map +1 -0
  113. package/dist/workspace-guard.d.ts +53 -0
  114. package/dist/workspace-guard.d.ts.map +1 -0
  115. package/dist/workspace-guard.js +61 -0
  116. package/dist/workspace-guard.js.map +1 -0
  117. package/package.json +133 -0
  118. package/plugin/.claude-plugin/plugin.json +29 -0
  119. package/plugin/.mcp.json +18 -0
  120. package/plugin/CLAUDE.md +143 -0
  121. package/plugin/bundle/dashboard/public/index.html +368 -0
  122. package/plugin/bundle/dashboard/public/vendor/vue.global.prod.js +9 -0
  123. package/plugin/bundle/mcp-server.mjs +8718 -0
  124. package/plugin/bundle/mcp-server.mjs.map +7 -0
  125. package/plugin/bundle/tdr-engine.mjs +50 -0
  126. package/plugin/bundle/tdr-engine.mjs.map +7 -0
  127. package/plugin/hooks/hooks.json +62 -0
  128. package/plugin/hooks/lib/crap-config.mjs +152 -0
  129. package/plugin/hooks/lib/gatekeeper-rules.mjs +257 -0
  130. package/plugin/hooks/lib/hook-io.mjs +151 -0
  131. package/plugin/hooks/lib/quality-gate.mjs +329 -0
  132. package/plugin/hooks/lib/test-harness.mjs +152 -0
  133. package/plugin/hooks/post-tool-use.mjs +245 -0
  134. package/plugin/hooks/pre-tool-use.mjs +290 -0
  135. package/plugin/hooks/session-start.mjs +109 -0
  136. package/plugin/hooks/stop-quality-gate.mjs +226 -0
  137. package/plugin/package.json +18 -0
  138. package/plugin/skills/adopt/SKILL.md +74 -0
  139. package/plugin/skills/analyze/SKILL.md +77 -0
  140. package/plugin/skills/check-test/SKILL.md +50 -0
  141. package/plugin/skills/score/SKILL.md +31 -0
  142. package/scripts/bug-report.mjs +328 -0
  143. package/scripts/build-fast.mjs +130 -0
  144. package/scripts/bundle-plugin.mjs +74 -0
  145. package/scripts/doctor.mjs +320 -0
  146. package/scripts/install.mjs +192 -0
  147. package/scripts/lib/cli-ui.mjs +122 -0
  148. package/scripts/postinstall.mjs +127 -0
  149. package/scripts/run-tests.mjs +95 -0
  150. package/scripts/status.mjs +110 -0
  151. package/scripts/uninstall.mjs +72 -0
  152. package/src/adapters/bandit.ts +191 -0
  153. package/src/adapters/common.ts +133 -0
  154. package/src/adapters/eslint.ts +187 -0
  155. package/src/adapters/index.ts +78 -0
  156. package/src/adapters/semgrep.ts +150 -0
  157. package/src/adapters/stryker.ts +218 -0
  158. package/src/ast/cyclomatic.ts +131 -0
  159. package/src/ast/index.ts +33 -0
  160. package/src/ast/language-config.ts +231 -0
  161. package/src/ast/tree-sitter-engine.ts +385 -0
  162. package/src/config.ts +109 -0
  163. package/src/crap-config.ts +196 -0
  164. package/src/dashboard/public/index.html +368 -0
  165. package/src/dashboard/public/vendor/vue.global.prod.js +9 -0
  166. package/src/dashboard/server.ts +205 -0
  167. package/src/index.ts +696 -0
  168. package/src/metrics/crap.ts +101 -0
  169. package/src/metrics/index.ts +51 -0
  170. package/src/metrics/score.ts +329 -0
  171. package/src/metrics/tdr.ts +155 -0
  172. package/src/metrics/workspace-walker.ts +146 -0
  173. package/src/sarif/index.ts +31 -0
  174. package/src/sarif/sarif-builder.ts +139 -0
  175. package/src/sarif/sarif-store.ts +347 -0
  176. package/src/sarif/sarif-validator.ts +145 -0
  177. package/src/schemas/tool-schemas.ts +225 -0
  178. package/src/sdk.ts +110 -0
  179. package/src/tests/adapters/bandit.test.ts +111 -0
  180. package/src/tests/adapters/dispatch.test.ts +100 -0
  181. package/src/tests/adapters/eslint.test.ts +138 -0
  182. package/src/tests/adapters/semgrep.test.ts +125 -0
  183. package/src/tests/adapters/stryker.test.ts +103 -0
  184. package/src/tests/crap-config.test.ts +228 -0
  185. package/src/tests/crap.test.ts +59 -0
  186. package/src/tests/cyclomatic.test.ts +87 -0
  187. package/src/tests/dashboard-http.test.ts +108 -0
  188. package/src/tests/dashboard-integrity.test.ts +128 -0
  189. package/src/tests/integration/mcp-server.integration.test.ts +352 -0
  190. package/src/tests/pre-tool-use-hook.test.ts +178 -0
  191. package/src/tests/sarif-store.test.ts +241 -0
  192. package/src/tests/sarif-validator.test.ts +164 -0
  193. package/src/tests/score.test.ts +260 -0
  194. package/src/tests/skills-frontmatter.test.ts +172 -0
  195. package/src/tests/stop-quality-gate-strictness.test.ts +243 -0
  196. package/src/tests/tdr.test.ts +86 -0
  197. package/src/tests/test-harness.test.ts +153 -0
  198. package/src/tests/workspace-guard.test.ts +111 -0
  199. package/src/tools/index.ts +24 -0
  200. package/src/tools/test-harness.ts +158 -0
  201. package/src/workspace-guard.ts +64 -0
  202. package/tsconfig.json +27 -0
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Public SDK entry point for the per-scanner SARIF adapters.
3
+ *
4
+ * Adapters convert a scanner's native output (SARIF, JSON, or some
5
+ * other structured format) into a normalized `PersistedSarif`
6
+ * document that the `SarifStore` can ingest directly. Every adapter
7
+ * enriches its findings with a stable `effortMinutes` value on the
8
+ * `properties` bag so the Stop quality gate and the project score
9
+ * engine can compute a Technical Debt Ratio.
10
+ *
11
+ * Usage:
12
+ *
13
+ * ```ts
14
+ * import {
15
+ * adaptScannerOutput,
16
+ * adaptSemgrep,
17
+ * adaptEslint,
18
+ * adaptBandit,
19
+ * adaptStryker,
20
+ * } from "claude-crap/adapters";
21
+ *
22
+ * const result = adaptScannerOutput("eslint", rawJsonFromEslint);
23
+ * sarifStore.ingestRun(result.document, result.sourceTool);
24
+ * ```
25
+ *
26
+ * @module adapters
27
+ */
28
+ export { adaptSemgrep } from "./semgrep.js";
29
+ export { adaptEslint } from "./eslint.js";
30
+ export { adaptBandit } from "./bandit.js";
31
+ export { adaptStryker } from "./stryker.js";
32
+ export { DEFAULT_EFFORT_BY_SEVERITY, KNOWN_SCANNERS, estimateEffortMinutes, wrapResultsInSarif, } from "./common.js";
33
+ import { adaptSemgrep } from "./semgrep.js";
34
+ import { adaptEslint } from "./eslint.js";
35
+ import { adaptBandit } from "./bandit.js";
36
+ import { adaptStryker } from "./stryker.js";
37
+ /**
38
+ * Route a raw scanner output to the correct adapter based on its
39
+ * name. Preferred entry point for the `ingest_scanner_output` MCP
40
+ * tool — the dispatch is a single switch so the compiler can verify
41
+ * every case with `never` exhaustiveness.
42
+ *
43
+ * @param scanner One of the known scanner identifiers.
44
+ * @param rawOutput The scanner's native output (string or parsed).
45
+ * @returns A normalized `AdapterResult`.
46
+ * @throws When `scanner` is unknown or the raw output is malformed.
47
+ */
48
+ export function adaptScannerOutput(scanner, rawOutput) {
49
+ switch (scanner) {
50
+ case "semgrep":
51
+ return adaptSemgrep(rawOutput);
52
+ case "eslint":
53
+ return adaptEslint(rawOutput);
54
+ case "bandit":
55
+ return adaptBandit(rawOutput);
56
+ case "stryker":
57
+ return adaptStryker(rawOutput);
58
+ default: {
59
+ const exhaustive = scanner;
60
+ throw new Error(`[adapters] Unknown scanner: ${String(exhaustive)}`);
61
+ }
62
+ }
63
+ }
64
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/adapters/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAE5C,OAAO,EACL,0BAA0B,EAC1B,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAG5C;;;;;;;;;;GAUG;AACH,MAAM,UAAU,kBAAkB,CAChC,OAAqB,EACrB,SAAkB;IAElB,QAAQ,OAAO,EAAE,CAAC;QAChB,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,QAAQ;YACX,OAAO,WAAW,CAAC,SAAS,CAAC,CAAC;QAChC,KAAK,SAAS;YACZ,OAAO,YAAY,CAAC,SAAS,CAAC,CAAC;QACjC,OAAO,CAAC,CAAC,CAAC;YACR,MAAM,UAAU,GAAU,OAAO,CAAC;YAClC,MAAM,IAAI,KAAK,CAAC,+BAA+B,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC;QACvE,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Semgrep adapter.
3
+ *
4
+ * Semgrep already emits SARIF 2.1.0 natively when invoked with
5
+ * `--sarif`, so this adapter's job is not translation but
6
+ * **enrichment**: we walk every `result` entry and stamp a
7
+ * `properties.effortMinutes` value so the Stop quality gate can
8
+ * compute a Technical Debt Ratio, plus we normalize the
9
+ * `properties.sourceTool` field so downstream consumers always know
10
+ * the finding came from Semgrep.
11
+ *
12
+ * If the caller passes a string, we parse it as JSON. If they pass an
13
+ * object that already matches the SARIF 2.1.0 envelope, we use it
14
+ * directly. Anything else throws a descriptive error that the MCP
15
+ * tool handler surfaces back to the LLM.
16
+ *
17
+ * @module adapters/semgrep
18
+ */
19
+ import { type AdapterResult } from "./common.js";
20
+ /**
21
+ * Accept a Semgrep SARIF document (as a string or object) and return
22
+ * an enriched `PersistedSarif` with effort estimates and a normalized
23
+ * `sourceTool` field.
24
+ *
25
+ * @param input Raw SARIF document from Semgrep (`JSON.stringify`ed or parsed).
26
+ * @returns The enriched document plus per-run stats.
27
+ * @throws When the input is not a SARIF 2.1.0 document.
28
+ */
29
+ export declare function adaptSemgrep(input: unknown): AdapterResult;
30
+ //# sourceMappingURL=semgrep.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semgrep.d.ts","sourceRoot":"","sources":["../../src/adapters/semgrep.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAEL,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AAiBrB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CA8D1D"}
@@ -0,0 +1,130 @@
1
+ /**
2
+ * Semgrep adapter.
3
+ *
4
+ * Semgrep already emits SARIF 2.1.0 natively when invoked with
5
+ * `--sarif`, so this adapter's job is not translation but
6
+ * **enrichment**: we walk every `result` entry and stamp a
7
+ * `properties.effortMinutes` value so the Stop quality gate can
8
+ * compute a Technical Debt Ratio, plus we normalize the
9
+ * `properties.sourceTool` field so downstream consumers always know
10
+ * the finding came from Semgrep.
11
+ *
12
+ * If the caller passes a string, we parse it as JSON. If they pass an
13
+ * object that already matches the SARIF 2.1.0 envelope, we use it
14
+ * directly. Anything else throws a descriptive error that the MCP
15
+ * tool handler surfaces back to the LLM.
16
+ *
17
+ * @module adapters/semgrep
18
+ */
19
+ import { estimateEffortMinutes, } from "./common.js";
20
+ const SEMGREP = "semgrep";
21
+ /**
22
+ * Rule-id effort overrides. Semgrep emits lots of stylistic rules
23
+ * that take less than a minute to fix and a handful of deep security
24
+ * rules that deserve more budget than the default warning tier. The
25
+ * list below is intentionally short — teams should extend it.
26
+ */
27
+ const SEMGREP_EFFORT_OVERRIDES = new Map([
28
+ [/security\./i, 90],
29
+ [/sqli|xss|ssrf|rce|deserial|crypto/i, 120],
30
+ [/style\./i, 5],
31
+ [/formatting\./i, 3],
32
+ ]);
33
+ /**
34
+ * Accept a Semgrep SARIF document (as a string or object) and return
35
+ * an enriched `PersistedSarif` with effort estimates and a normalized
36
+ * `sourceTool` field.
37
+ *
38
+ * @param input Raw SARIF document from Semgrep (`JSON.stringify`ed or parsed).
39
+ * @returns The enriched document plus per-run stats.
40
+ * @throws When the input is not a SARIF 2.1.0 document.
41
+ */
42
+ export function adaptSemgrep(input) {
43
+ const doc = coerceToSarif(input);
44
+ let findingCount = 0;
45
+ let totalEffortMinutes = 0;
46
+ // We deep-clone the document so callers don't observe a mutation on
47
+ // the value they passed us. JSON round-trip is cheap here; a
48
+ // typical Semgrep SARIF report is well under 1 MB.
49
+ //
50
+ // We operate on the cloned value through a loose `Record`-based view
51
+ // to keep the adapter agnostic to the full SARIF schema — the
52
+ // canonical types live in `sarif-store.ts` and we only care about a
53
+ // handful of fields here. The final return casts through `unknown`
54
+ // because the JSON round-trip is shape-preserving by construction.
55
+ const cloned = JSON.parse(JSON.stringify(doc));
56
+ const runs = Array.isArray(cloned.runs) ? cloned.runs : [];
57
+ for (const run of runs) {
58
+ const rawResults = run["results"];
59
+ const results = Array.isArray(rawResults) ? rawResults : [];
60
+ for (const result of results) {
61
+ findingCount += 1;
62
+ const ruleId = typeof result["ruleId"] === "string" ? result["ruleId"] : "";
63
+ const level = result["level"];
64
+ const override = matchOverride(ruleId);
65
+ const effort = estimateEffortMinutes(level, override);
66
+ totalEffortMinutes += effort;
67
+ const existingProps = result["properties"] && typeof result["properties"] === "object"
68
+ ? result["properties"]
69
+ : {};
70
+ result["properties"] = {
71
+ ...existingProps,
72
+ sourceTool: SEMGREP,
73
+ effortMinutes: effort,
74
+ };
75
+ }
76
+ // Overwrite tool.driver.name so store-level filters always match
77
+ // `"semgrep"` regardless of the label Semgrep reported. Preserve
78
+ // the existing version and rules[] array when present.
79
+ const existingTool = run["tool"] ?? {};
80
+ const existingDriver = existingTool["driver"] ?? {};
81
+ const driverOut = {
82
+ name: SEMGREP,
83
+ version: typeof existingDriver["version"] === "string" ? existingDriver["version"] : "unknown",
84
+ };
85
+ if (Array.isArray(existingDriver["rules"])) {
86
+ driverOut["rules"] = existingDriver["rules"];
87
+ }
88
+ run["tool"] = { driver: driverOut };
89
+ }
90
+ return {
91
+ document: cloned,
92
+ sourceTool: SEMGREP,
93
+ findingCount,
94
+ totalEffortMinutes,
95
+ };
96
+ }
97
+ /**
98
+ * Accept either a pre-parsed SARIF object or a JSON string and return
99
+ * a strongly-typed `PersistedSarif`. Throws on malformed input.
100
+ *
101
+ * @param input Raw caller-provided value.
102
+ */
103
+ function coerceToSarif(input) {
104
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
105
+ if (!parsed || typeof parsed !== "object") {
106
+ throw new Error(`[adapter:semgrep] input is not a SARIF object`);
107
+ }
108
+ const doc = parsed;
109
+ if (doc.version !== "2.1.0") {
110
+ throw new Error(`[adapter:semgrep] expected SARIF version 2.1.0, got ${String(doc.version)}`);
111
+ }
112
+ if (!Array.isArray(doc.runs)) {
113
+ throw new Error(`[adapter:semgrep] document is missing a runs[] array`);
114
+ }
115
+ return parsed;
116
+ }
117
+ /**
118
+ * Return the effort override (in minutes) matching the first pattern
119
+ * that matches the given rule id, or `undefined` when none match.
120
+ *
121
+ * @param ruleId Semgrep rule identifier.
122
+ */
123
+ function matchOverride(ruleId) {
124
+ for (const [pattern, minutes] of SEMGREP_EFFORT_OVERRIDES) {
125
+ if (pattern.test(ruleId))
126
+ return minutes;
127
+ }
128
+ return undefined;
129
+ }
130
+ //# sourceMappingURL=semgrep.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semgrep.js","sourceRoot":"","sources":["../../src/adapters/semgrep.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * Stryker (JavaScript mutation testing) adapter.
3
+ *
4
+ * Stryker emits a JSON report under `reports/mutation/mutation.json`.
5
+ * The report shape is documented at
6
+ * https://stryker-mutator.io/docs/mutation-testing-elements/mutant-result-schema/ :
7
+ *
8
+ * {
9
+ * "schemaVersion": "1.0",
10
+ * "thresholds": { ... },
11
+ * "files": {
12
+ * "src/foo.ts": {
13
+ * "language": "typescript",
14
+ * "source": "...",
15
+ * "mutants": [
16
+ * {
17
+ * "id": "1",
18
+ * "mutatorName": "ConditionalExpression",
19
+ * "replacement": "false",
20
+ * "location": {
21
+ * "start": { "line": 10, "column": 5 },
22
+ * "end": { "line": 10, "column": 15 }
23
+ * },
24
+ * "status": "Survived", // Killed | Survived | Timeout | NoCoverage | RuntimeError | CompileError | Ignored
25
+ * "statusReason": "..."
26
+ * }
27
+ * ]
28
+ * }
29
+ * }
30
+ * }
31
+ *
32
+ * This adapter treats every **surviving mutant** as a SARIF
33
+ * `error`-level finding — surviving mutants are exactly the ones the
34
+ * Golden Rule forbids, because they prove the test suite does not
35
+ * pin the code's behavior tightly enough to notice a change.
36
+ *
37
+ * Mutants with status `NoCoverage` become `warning`-level findings
38
+ * (not blocking the Stop gate by themselves, but still ingested so
39
+ * the dashboard can surface uncovered lines). All other statuses are
40
+ * ignored — they do not represent defects.
41
+ *
42
+ * @module adapters/stryker
43
+ */
44
+ import { type AdapterResult } from "./common.js";
45
+ /**
46
+ * Accept a Stryker JSON mutation report and return a normalized
47
+ * `PersistedSarif` document with one finding per surviving mutant
48
+ * and per uncovered mutant.
49
+ *
50
+ * @param input Raw Stryker report (string or parsed object).
51
+ * @returns Adapter result.
52
+ * @throws When the input is not a Stryker mutation report.
53
+ */
54
+ export declare function adaptStryker(input: unknown): AdapterResult;
55
+ //# sourceMappingURL=stryker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stryker.d.ts","sourceRoot":"","sources":["../../src/adapters/stryker.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0CG;AAGH,OAAO,EAGL,KAAK,aAAa,EAEnB,MAAM,aAAa,CAAC;AA0BrB;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,aAAa,CA8D1D"}
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Stryker (JavaScript mutation testing) adapter.
3
+ *
4
+ * Stryker emits a JSON report under `reports/mutation/mutation.json`.
5
+ * The report shape is documented at
6
+ * https://stryker-mutator.io/docs/mutation-testing-elements/mutant-result-schema/ :
7
+ *
8
+ * {
9
+ * "schemaVersion": "1.0",
10
+ * "thresholds": { ... },
11
+ * "files": {
12
+ * "src/foo.ts": {
13
+ * "language": "typescript",
14
+ * "source": "...",
15
+ * "mutants": [
16
+ * {
17
+ * "id": "1",
18
+ * "mutatorName": "ConditionalExpression",
19
+ * "replacement": "false",
20
+ * "location": {
21
+ * "start": { "line": 10, "column": 5 },
22
+ * "end": { "line": 10, "column": 15 }
23
+ * },
24
+ * "status": "Survived", // Killed | Survived | Timeout | NoCoverage | RuntimeError | CompileError | Ignored
25
+ * "statusReason": "..."
26
+ * }
27
+ * ]
28
+ * }
29
+ * }
30
+ * }
31
+ *
32
+ * This adapter treats every **surviving mutant** as a SARIF
33
+ * `error`-level finding — surviving mutants are exactly the ones the
34
+ * Golden Rule forbids, because they prove the test suite does not
35
+ * pin the code's behavior tightly enough to notice a change.
36
+ *
37
+ * Mutants with status `NoCoverage` become `warning`-level findings
38
+ * (not blocking the Stop gate by themselves, but still ingested so
39
+ * the dashboard can surface uncovered lines). All other statuses are
40
+ * ignored — they do not represent defects.
41
+ *
42
+ * @module adapters/stryker
43
+ */
44
+ import { estimateEffortMinutes, wrapResultsInSarif, } from "./common.js";
45
+ const STRYKER = "stryker";
46
+ /**
47
+ * Accept a Stryker JSON mutation report and return a normalized
48
+ * `PersistedSarif` document with one finding per surviving mutant
49
+ * and per uncovered mutant.
50
+ *
51
+ * @param input Raw Stryker report (string or parsed object).
52
+ * @returns Adapter result.
53
+ * @throws When the input is not a Stryker mutation report.
54
+ */
55
+ export function adaptStryker(input) {
56
+ const parsed = typeof input === "string" ? JSON.parse(input) : input;
57
+ if (!parsed || typeof parsed !== "object") {
58
+ throw new Error(`[adapter:stryker] expected a JSON object`);
59
+ }
60
+ const report = parsed;
61
+ if (!report.files || typeof report.files !== "object") {
62
+ throw new Error(`[adapter:stryker] report is missing a files{} map`);
63
+ }
64
+ const results = [];
65
+ let totalEffortMinutes = 0;
66
+ for (const [filename, fileReport] of Object.entries(report.files)) {
67
+ const mutants = Array.isArray(fileReport?.mutants) ? fileReport.mutants : [];
68
+ for (const mutant of mutants) {
69
+ const level = classifyMutant(mutant.status);
70
+ if (level === null)
71
+ continue; // Killed / Ignored / CompileError — not a defect
72
+ const startLine = mutant.location?.start?.line ?? 1;
73
+ const startColumn = mutant.location?.start?.column ?? 1;
74
+ const endLine = mutant.location?.end?.line;
75
+ const endColumn = mutant.location?.end?.column;
76
+ // Surviving mutants cost more to fix than the default error
77
+ // budget because the agent has to first write a killing test,
78
+ // THEN possibly rewrite the code.
79
+ const effortOverride = level === "error" ? 45 : 15;
80
+ const effort = estimateEffortMinutes(level, effortOverride);
81
+ totalEffortMinutes += effort;
82
+ const mutator = mutant.mutatorName ?? "Unknown";
83
+ const ruleId = `stryker.${mutator}`;
84
+ const statusText = mutant.status ?? "Unknown";
85
+ const message = `${statusText}: ${mutator} mutant on '${mutant.replacement ?? "?"}'` +
86
+ (mutant.statusReason ? ` — ${mutant.statusReason}` : "");
87
+ results.push(buildSarifResult({
88
+ ruleId,
89
+ level,
90
+ message,
91
+ uri: filename,
92
+ startLine,
93
+ startColumn,
94
+ endLine: typeof endLine === "number" ? endLine : undefined,
95
+ endColumn: typeof endColumn === "number" ? endColumn : undefined,
96
+ effortMinutes: effort,
97
+ mutantId: mutant.id,
98
+ mutator,
99
+ mutantStatus: statusText,
100
+ }));
101
+ }
102
+ }
103
+ return {
104
+ document: wrapResultsInSarif(STRYKER, String(report.schemaVersion ?? "unknown"), results),
105
+ sourceTool: STRYKER,
106
+ findingCount: results.length,
107
+ totalEffortMinutes,
108
+ };
109
+ }
110
+ /**
111
+ * Classify a Stryker mutant status into a SARIF level, or `null` when
112
+ * the status represents a mutant that was correctly handled by the
113
+ * test suite and should not produce a finding.
114
+ */
115
+ function classifyMutant(status) {
116
+ switch ((status ?? "").toLowerCase()) {
117
+ case "survived":
118
+ return "error";
119
+ case "nocoverage":
120
+ return "warning";
121
+ case "timeout":
122
+ // Timeout mutants are suspicious but not proof of defect —
123
+ // they deserve a note so the dashboard highlights them.
124
+ return "note";
125
+ case "killed":
126
+ case "ignored":
127
+ case "compileerror":
128
+ case "runtimeerror":
129
+ default:
130
+ return null;
131
+ }
132
+ }
133
+ /**
134
+ * Build the SARIF `result` object for a single Stryker mutant. We
135
+ * propagate the mutant id, mutator name, and raw status into
136
+ * `properties` so the dashboard can display them as tags.
137
+ */
138
+ function buildSarifResult(opts) {
139
+ return {
140
+ ruleId: opts.ruleId,
141
+ level: opts.level,
142
+ message: { text: opts.message },
143
+ locations: [
144
+ {
145
+ physicalLocation: {
146
+ artifactLocation: { uri: opts.uri },
147
+ region: {
148
+ startLine: opts.startLine,
149
+ startColumn: opts.startColumn,
150
+ ...(opts.endLine !== undefined ? { endLine: opts.endLine } : {}),
151
+ ...(opts.endColumn !== undefined ? { endColumn: opts.endColumn } : {}),
152
+ },
153
+ },
154
+ },
155
+ ],
156
+ properties: {
157
+ sourceTool: STRYKER,
158
+ effortMinutes: opts.effortMinutes,
159
+ ...(opts.mutantId ? { mutantId: opts.mutantId } : {}),
160
+ ...(opts.mutator ? { mutator: opts.mutator } : {}),
161
+ ...(opts.mutantStatus ? { mutantStatus: opts.mutantStatus } : {}),
162
+ },
163
+ };
164
+ }
165
+ //# sourceMappingURL=stryker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stryker.js","sourceRoot":"","sources":["../../src/adapters/stryker.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Deterministic cyclomatic complexity walker.
3
+ *
4
+ * Given a tree-sitter subtree rooted at a function node, this module walks
5
+ * the tree and returns the McCabe cyclomatic complexity number. The
6
+ * algorithm is the standard "1 + (branching nodes + short-circuit operators)"
7
+ * formulation:
8
+ *
9
+ * CC = 1
10
+ * + count of branching-statement nodes (if, while, for, case, catch, ...)
11
+ * + count of short-circuit operators (&&, ||, ??, `and`, `or`)
12
+ * + count of ternary expressions (counted as branching nodes)
13
+ *
14
+ * The baseline of 1 represents the straight-line path through the function.
15
+ * Every additional branching point multiplies the set of reachable paths.
16
+ *
17
+ * The walker is deliberately language-agnostic: it consults the
18
+ * {@link LanguageConfig} passed in to decide which node types to count.
19
+ * Nested functions inside the subtree are **skipped** — each function's
20
+ * complexity is reported independently by {@link TreeSitterEngine}.
21
+ *
22
+ * @module ast/cyclomatic
23
+ */
24
+ import type { LanguageConfig } from "./language-config.js";
25
+ /**
26
+ * Minimal structural contract of a tree-sitter node. We intentionally
27
+ * avoid importing `web-tree-sitter` types here so this module stays
28
+ * unit-testable with a hand-rolled mock tree.
29
+ */
30
+ export interface AstNode {
31
+ /** Node type name from the grammar (e.g. `"if_statement"`). */
32
+ readonly type: string;
33
+ /** Raw source text for operator detection. May be large — do not log. */
34
+ readonly text: string;
35
+ /** Zero-based child count. */
36
+ readonly childCount: number;
37
+ /** Retrieve a child by index. Returns `null` if out of range. */
38
+ child(index: number): AstNode | null;
39
+ }
40
+ /**
41
+ * Compute the cyclomatic complexity of a function subtree.
42
+ *
43
+ * @param root Node rooted at the function (method, arrow, lambda, ...).
44
+ * @param languageConfig Language classification tables for node types.
45
+ * @returns The McCabe cyclomatic complexity (always ≥ 1).
46
+ */
47
+ export declare function computeCyclomaticComplexity(root: AstNode, languageConfig: LanguageConfig): number;
48
+ //# sourceMappingURL=cyclomatic.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cyclomatic.d.ts","sourceRoot":"","sources":["../../src/ast/cyclomatic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAE3D;;;;GAIG;AACH,MAAM,WAAW,OAAO;IACtB,+DAA+D;IAC/D,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,yEAAyE;IACzE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,8BAA8B;IAC9B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,iEAAiE;IACjE,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC;CACtC;AAED;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,IAAI,EAAE,OAAO,EAAE,cAAc,EAAE,cAAc,GAAG,MAAM,CAiBjG"}
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Deterministic cyclomatic complexity walker.
3
+ *
4
+ * Given a tree-sitter subtree rooted at a function node, this module walks
5
+ * the tree and returns the McCabe cyclomatic complexity number. The
6
+ * algorithm is the standard "1 + (branching nodes + short-circuit operators)"
7
+ * formulation:
8
+ *
9
+ * CC = 1
10
+ * + count of branching-statement nodes (if, while, for, case, catch, ...)
11
+ * + count of short-circuit operators (&&, ||, ??, `and`, `or`)
12
+ * + count of ternary expressions (counted as branching nodes)
13
+ *
14
+ * The baseline of 1 represents the straight-line path through the function.
15
+ * Every additional branching point multiplies the set of reachable paths.
16
+ *
17
+ * The walker is deliberately language-agnostic: it consults the
18
+ * {@link LanguageConfig} passed in to decide which node types to count.
19
+ * Nested functions inside the subtree are **skipped** — each function's
20
+ * complexity is reported independently by {@link TreeSitterEngine}.
21
+ *
22
+ * @module ast/cyclomatic
23
+ */
24
+ /**
25
+ * Compute the cyclomatic complexity of a function subtree.
26
+ *
27
+ * @param root Node rooted at the function (method, arrow, lambda, ...).
28
+ * @param languageConfig Language classification tables for node types.
29
+ * @returns The McCabe cyclomatic complexity (always ≥ 1).
30
+ */
31
+ export function computeCyclomaticComplexity(root, languageConfig) {
32
+ let complexity = 1;
33
+ walk(root, languageConfig, true, (node) => {
34
+ if (languageConfig.branchingNodeTypes.has(node.type)) {
35
+ complexity += 1;
36
+ return;
37
+ }
38
+ // Boolean / short-circuit operators are usually represented as
39
+ // "binary_expression" nodes with an operator token child. To avoid
40
+ // coupling to a specific grammar's node shape, we inspect the raw
41
+ // text of the node's direct operator child.
42
+ if (isBooleanExpression(node, languageConfig)) {
43
+ complexity += 1;
44
+ return;
45
+ }
46
+ });
47
+ return complexity;
48
+ }
49
+ /**
50
+ * Depth-first walk that skips any nested function subtree so that its
51
+ * complexity is not attributed to the enclosing function.
52
+ *
53
+ * @param node Current node being visited.
54
+ * @param languageConfig Language tables used to detect nested functions.
55
+ * @param isRoot `true` for the starting node (we do not skip it).
56
+ * @param visit Callback invoked for every non-function descendant.
57
+ */
58
+ function walk(node, languageConfig, isRoot, visit) {
59
+ if (!isRoot && languageConfig.functionNodeTypes.has(node.type)) {
60
+ // Nested function — stop the walk here. Its complexity is reported
61
+ // separately when the engine iterates the top-level function list.
62
+ return;
63
+ }
64
+ visit(node);
65
+ for (let i = 0; i < node.childCount; i++) {
66
+ const child = node.child(i);
67
+ if (child)
68
+ walk(child, languageConfig, false, visit);
69
+ }
70
+ }
71
+ /**
72
+ * Return `true` when `node` is a boolean / short-circuit expression that
73
+ * should add one to the cyclomatic complexity. We inspect the node's
74
+ * immediate children for an operator token whose text matches one of the
75
+ * language's short-circuit operators.
76
+ *
77
+ * This is a heuristic — grammars differ in how they represent operators
78
+ * — but it is stable enough for the five supported languages because:
79
+ *
80
+ * - JavaScript / TypeScript / Java: binary expression with `"&&"` or `"||"` token child.
81
+ * - Python: `boolean_operator` node with `"and"` / `"or"` token child.
82
+ * - C#: binary expression with `"&&"`, `"||"`, or `"??"` token child.
83
+ *
84
+ * @param node Candidate node.
85
+ * @param languageConfig Tables with the language's boolean operator set.
86
+ * @returns `true` when the node is a counted boolean expression.
87
+ */
88
+ function isBooleanExpression(node, languageConfig) {
89
+ // Common type names across supported grammars. We check the type first
90
+ // to avoid scanning text for every node in the tree.
91
+ if (node.type !== "binary_expression" &&
92
+ node.type !== "boolean_operator" &&
93
+ node.type !== "logical_expression") {
94
+ return false;
95
+ }
96
+ for (let i = 0; i < node.childCount; i++) {
97
+ const child = node.child(i);
98
+ if (!child)
99
+ continue;
100
+ if (languageConfig.booleanOperators.includes(child.text)) {
101
+ return true;
102
+ }
103
+ }
104
+ return false;
105
+ }
106
+ //# sourceMappingURL=cyclomatic.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cyclomatic.js","sourceRoot":"","sources":["../../src/ast/cyclomatic.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAoBH;;;;;;GAMG;AACH,MAAM,UAAU,2BAA2B,CAAC,IAAa,EAAE,cAA8B;IACvF,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,CAAC,IAAI,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE;QACxC,IAAI,cAAc,CAAC,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;YACrD,UAAU,IAAI,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;QACD,+DAA+D;QAC/D,mEAAmE;QACnE,kEAAkE;QAClE,4CAA4C;QAC5C,IAAI,mBAAmB,CAAC,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC;YAC9C,UAAU,IAAI,CAAC,CAAC;YAChB,OAAO;QACT,CAAC;IACH,CAAC,CAAC,CAAC;IACH,OAAO,UAAU,CAAC;AACpB,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,IAAI,CACX,IAAa,EACb,cAA8B,EAC9B,MAAe,EACf,KAA2B;IAE3B,IAAI,CAAC,MAAM,IAAI,cAAc,CAAC,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;QAC/D,mEAAmE;QACnE,mEAAmE;QACnE,OAAO;IACT,CAAC;IACD,KAAK,CAAC,IAAI,CAAC,CAAC;IACZ,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,KAAK;YAAE,IAAI,CAAC,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;IACvD,CAAC;AACH,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,mBAAmB,CAAC,IAAa,EAAE,cAA8B;IACxE,uEAAuE;IACvE,qDAAqD;IACrD,IACE,IAAI,CAAC,IAAI,KAAK,mBAAmB;QACjC,IAAI,CAAC,IAAI,KAAK,kBAAkB;QAChC,IAAI,CAAC,IAAI,KAAK,oBAAoB,EAClC,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IACD,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,UAAU,EAAE,CAAC,EAAE,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;QAC5B,IAAI,CAAC,KAAK;YAAE,SAAS;QACrB,IAAI,cAAc,CAAC,gBAAgB,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACzD,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Public SDK entry point for the tree-sitter based AST engine and the
3
+ * cyclomatic complexity walker.
4
+ *
5
+ * Usage:
6
+ *
7
+ * ```ts
8
+ * import {
9
+ * TreeSitterEngine,
10
+ * computeCyclomaticComplexity,
11
+ * detectLanguageFromPath,
12
+ * type FileMetrics,
13
+ * type FunctionMetrics,
14
+ * type SupportedLanguage,
15
+ * } from "claude-crap/ast";
16
+ * ```
17
+ *
18
+ * @module ast
19
+ */
20
+ export { TreeSitterEngine } from "./tree-sitter-engine.js";
21
+ export type { AnalyzeFileRequest, FileMetrics, FunctionMetrics, TreeSitterEngineOptions, } from "./tree-sitter-engine.js";
22
+ export { computeCyclomaticComplexity } from "./cyclomatic.js";
23
+ export type { AstNode } from "./cyclomatic.js";
24
+ export { LANGUAGE_TABLE, detectLanguageFromPath } from "./language-config.js";
25
+ export type { LanguageConfig, SupportedLanguage } from "./language-config.js";
26
+ //# sourceMappingURL=index.d.ts.map