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,79 @@
1
+ /**
2
+ * Minimal SARIF 2.1.0 document builder.
3
+ *
4
+ * Every report that leaves the MCP server on its way to the agent is
5
+ * normalized to SARIF 2.1.0 first. This module provides the typed
6
+ * helpers used to wrap raw findings in the canonical
7
+ * `tool → runs → results` taxonomy with exact file coordinates.
8
+ *
9
+ * Per-scanner adapters (Semgrep, ESLint, Bandit, Stryker) live under
10
+ * `src/adapters/` and call into `buildSarifDocument` through the
11
+ * `wrapResultsInSarif` helper in `src/adapters/common.ts`. The
12
+ * on-disk deduplication store lives in `./sarif-store.ts`.
13
+ *
14
+ * The SARIF 2.1.0 spec lives at:
15
+ * https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
16
+ *
17
+ * @module sarif/sarif-builder
18
+ */
19
+ /**
20
+ * Build a minimal but valid SARIF 2.1.0 document from a list of findings.
21
+ *
22
+ * The returned object conforms to the SARIF JSON schema hosted at:
23
+ * https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json
24
+ *
25
+ * Rules are deduplicated by `ruleId` and emitted in the `tool.driver.rules`
26
+ * array so that downstream consumers (Claude Code, the dashboard, or any
27
+ * third-party SARIF viewer) can render a rule index.
28
+ *
29
+ * @param tool Metadata about the producing tool.
30
+ * @param findings Findings to include in the single run.
31
+ * @returns A SARIF 2.1.0 document literal (frozen by `as const`).
32
+ */
33
+ export function buildSarifDocument(tool, findings) {
34
+ return {
35
+ $schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json",
36
+ version: "2.1.0",
37
+ runs: [
38
+ {
39
+ tool: {
40
+ driver: {
41
+ name: tool.name,
42
+ version: tool.version,
43
+ informationUri: tool.informationUri ?? "https://github.com/local/claude-crap",
44
+ // Deduplicate rules by id while preserving insertion order so
45
+ // the emitted `rules` array matches the order findings appear.
46
+ rules: Array.from(new Map(findings.map((f) => [
47
+ f.ruleId,
48
+ {
49
+ id: f.ruleId,
50
+ shortDescription: { text: f.ruleId },
51
+ defaultConfiguration: { level: f.level },
52
+ },
53
+ ])).values()),
54
+ },
55
+ },
56
+ results: findings.map((f) => ({
57
+ ruleId: f.ruleId,
58
+ level: f.level,
59
+ message: { text: f.message },
60
+ locations: [
61
+ {
62
+ physicalLocation: {
63
+ artifactLocation: { uri: f.location.uri },
64
+ region: {
65
+ startLine: f.location.startLine,
66
+ startColumn: f.location.startColumn,
67
+ ...(f.location.endLine !== undefined ? { endLine: f.location.endLine } : {}),
68
+ ...(f.location.endColumn !== undefined ? { endColumn: f.location.endColumn } : {}),
69
+ },
70
+ },
71
+ },
72
+ ],
73
+ ...(f.properties ? { properties: f.properties } : {}),
74
+ })),
75
+ },
76
+ ],
77
+ };
78
+ }
79
+ //# sourceMappingURL=sarif-builder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif-builder.js","sourceRoot":"","sources":["../../src/sarif/sarif-builder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AA0DH;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAAmB,EAAE,QAAqC;IAC3F,OAAO;QACL,OAAO,EAAE,qEAAqE;QAC9E,OAAO,EAAE,OAAO;QAChB,IAAI,EAAE;YACJ;gBACE,IAAI,EAAE;oBACJ,MAAM,EAAE;wBACN,IAAI,EAAE,IAAI,CAAC,IAAI;wBACf,OAAO,EAAE,IAAI,CAAC,OAAO;wBACrB,cAAc,EAAE,IAAI,CAAC,cAAc,IAAI,sCAAsC;wBAC7E,8DAA8D;wBAC9D,+DAA+D;wBAC/D,KAAK,EAAE,KAAK,CAAC,IAAI,CACf,IAAI,GAAG,CACL,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;4BAClB,CAAC,CAAC,MAAM;4BACR;gCACE,EAAE,EAAE,CAAC,CAAC,MAAM;gCACZ,gBAAgB,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;gCACpC,oBAAoB,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE;6BACzC;yBACF,CAAC,CACH,CAAC,MAAM,EAAE,CACX;qBACF;iBACF;gBACD,OAAO,EAAE,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;oBAC5B,MAAM,EAAE,CAAC,CAAC,MAAM;oBAChB,KAAK,EAAE,CAAC,CAAC,KAAK;oBACd,OAAO,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;oBAC5B,SAAS,EAAE;wBACT;4BACE,gBAAgB,EAAE;gCAChB,gBAAgB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC,QAAQ,CAAC,GAAG,EAAE;gCACzC,MAAM,EAAE;oCACN,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS;oCAC/B,WAAW,EAAE,CAAC,CAAC,QAAQ,CAAC,WAAW;oCACnC,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;oCAC5E,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iCACnF;6BACF;yBACF;qBACF;oBACD,GAAG,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;iBACtD,CAAC,CAAC;aACJ;SACF;KACO,CAAC;AACb,CAAC"}
@@ -0,0 +1,205 @@
1
+ /**
2
+ * On-disk SARIF 2.1.0 store with finding deduplication.
3
+ *
4
+ * The Stop quality gate and the `ingest_sarif` MCP tool both need a
5
+ * single, consolidated view of every finding produced across the current
6
+ * session. This module provides:
7
+ *
8
+ * - `loadLatest()` — read the consolidated SARIF document from disk,
9
+ * or return an empty seed when no report exists yet.
10
+ * - `ingestRun()` — merge a new SARIF run from an external scanner
11
+ * (Semgrep, ESLint, Bandit, Stryker, ...) into the
12
+ * in-memory store, deduplicating by
13
+ * `(ruleId, uri, startLine, startColumn)`.
14
+ * - `persist()` — atomically write the consolidated document back
15
+ * to disk so other processes (the dashboard) can
16
+ * read it.
17
+ *
18
+ * The store is intentionally simple: it does NOT attempt to preserve
19
+ * per-tool run separation inside the persisted file. Instead, every
20
+ * ingested run is flattened into a single `runs[0]` entry whose `tool.driver`
21
+ * is claude-crap itself, and the original scanner name is recorded on
22
+ * each finding via the `properties.sourceTool` field. This keeps the
23
+ * consolidated document easy to diff between sessions.
24
+ *
25
+ * @module sarif/sarif-store
26
+ */
27
+ import { type SarifFinding, type SarifLevel } from "./sarif-builder.js";
28
+ /**
29
+ * The shape of a persisted SARIF 2.1.0 document, narrowed to the fields
30
+ * we actually read and write. The full spec has many more optional
31
+ * fields; we ignore them on read and do not emit them on write.
32
+ */
33
+ export interface PersistedSarif {
34
+ readonly $schema?: string;
35
+ readonly version: "2.1.0";
36
+ readonly runs: ReadonlyArray<SarifRun>;
37
+ }
38
+ interface SarifRun {
39
+ readonly tool: {
40
+ readonly driver: {
41
+ readonly name: string;
42
+ readonly version: string;
43
+ readonly informationUri?: string;
44
+ readonly rules?: ReadonlyArray<unknown>;
45
+ };
46
+ };
47
+ readonly results: ReadonlyArray<SarifResult>;
48
+ }
49
+ interface SarifResult {
50
+ readonly ruleId: string;
51
+ readonly level?: SarifLevel;
52
+ readonly message: {
53
+ readonly text: string;
54
+ };
55
+ readonly locations?: ReadonlyArray<SarifResultLocation>;
56
+ readonly properties?: Record<string, unknown>;
57
+ }
58
+ interface SarifResultLocation {
59
+ readonly physicalLocation?: {
60
+ readonly artifactLocation?: {
61
+ readonly uri?: string;
62
+ };
63
+ readonly region?: {
64
+ readonly startLine?: number;
65
+ readonly startColumn?: number;
66
+ readonly endLine?: number;
67
+ readonly endColumn?: number;
68
+ };
69
+ };
70
+ }
71
+ /**
72
+ * Options accepted by the {@link SarifStore} constructor.
73
+ */
74
+ export interface SarifStoreOptions {
75
+ /** Workspace root. Used to resolve relative `outputDir`. */
76
+ readonly workspaceRoot: string;
77
+ /** Directory (absolute or workspace-relative) where reports are written. */
78
+ readonly outputDir: string;
79
+ /** Filename for the consolidated SARIF document. Defaults to `latest.sarif`. */
80
+ readonly fileName?: string;
81
+ }
82
+ /**
83
+ * A finding together with its deduplication key. Used internally and
84
+ * returned by {@link SarifStore.ingestRun} so callers can see which
85
+ * findings were accepted.
86
+ */
87
+ export interface IngestedFinding extends SarifFinding {
88
+ /** Stable deduplication key, shape: `ruleId|uri|line|col`. */
89
+ readonly dedupKey: string;
90
+ /** Name of the scanner that produced the finding (propagated from `sourceTool`). */
91
+ readonly sourceTool: string;
92
+ }
93
+ /**
94
+ * On-disk SARIF store.
95
+ */
96
+ export declare class SarifStore {
97
+ private readonly filePath;
98
+ /** In-memory index of findings keyed by their dedup string. */
99
+ private readonly findings;
100
+ /** Tool invocations we have already ingested, for telemetry. */
101
+ private toolInvocations;
102
+ constructor(options: SarifStoreOptions);
103
+ /**
104
+ * Absolute path to the consolidated SARIF file on disk.
105
+ */
106
+ get consolidatedReportPath(): string;
107
+ /**
108
+ * Load the consolidated document from disk into memory. If the file is
109
+ * missing, the store is initialized empty. Top-level parsing errors
110
+ * still throw (a file that is not valid JSON, or that declares a
111
+ * different SARIF version, is a real safety signal). However, once
112
+ * the document is parsed, malformed individual runs and results are
113
+ * tolerated: F-A08-01 showed that a single bad entry in `latest.sarif`
114
+ * could crash the MCP server on boot and persistently DoS the
115
+ * developer. Each run / result is wrapped in its own try/catch so a
116
+ * single bad entry logs to stderr and is dropped, but the rest of
117
+ * the file still loads.
118
+ *
119
+ * @throws When the file exists but is not valid SARIF 2.1.0 JSON.
120
+ */
121
+ loadLatest(): Promise<void>;
122
+ /**
123
+ * Merge a raw SARIF document (from any external scanner) into the
124
+ * store, deduplicating by `(ruleId, uri, startLine, startColumn)`. The
125
+ * last writer wins for the message and level fields — later ingestions
126
+ * can refine earlier ones.
127
+ *
128
+ * @param sarifDocument The raw SARIF document as received from the tool.
129
+ * @param sourceTool Stable identifier of the producing scanner.
130
+ * @returns Stats describing what was accepted.
131
+ */
132
+ ingestRun(sarifDocument: PersistedSarif, sourceTool: string): {
133
+ accepted: number;
134
+ duplicates: number;
135
+ total: number;
136
+ };
137
+ /**
138
+ * Snapshot all currently tracked findings as a plain array. Mostly
139
+ * useful for tests and for the dashboard API.
140
+ */
141
+ list(): ReadonlyArray<IngestedFinding>;
142
+ /**
143
+ * Atomically write the consolidated document back to disk. Uses a
144
+ * temporary file and `rename` so concurrent readers never observe
145
+ * a half-written document.
146
+ */
147
+ persist(): Promise<void>;
148
+ /**
149
+ * Build the current consolidated SARIF document from the in-memory
150
+ * findings without touching disk.
151
+ */
152
+ toSarifDocument(): {
153
+ readonly $schema: "https://schemastore.azurewebsites.net/schemas/json/sarif-2.1.0.json";
154
+ readonly version: "2.1.0";
155
+ readonly runs: readonly [{
156
+ readonly tool: {
157
+ readonly driver: {
158
+ readonly name: string;
159
+ readonly version: string;
160
+ readonly informationUri: string;
161
+ readonly rules: {
162
+ id: string;
163
+ shortDescription: {
164
+ text: string;
165
+ };
166
+ defaultConfiguration: {
167
+ level: SarifLevel;
168
+ };
169
+ }[];
170
+ };
171
+ };
172
+ readonly results: {
173
+ properties?: Record<string, unknown>;
174
+ ruleId: string;
175
+ level: SarifLevel;
176
+ message: {
177
+ text: string;
178
+ };
179
+ locations: {
180
+ physicalLocation: {
181
+ artifactLocation: {
182
+ uri: string;
183
+ };
184
+ region: {
185
+ endColumn?: number;
186
+ endLine?: number;
187
+ startLine: number;
188
+ startColumn: number;
189
+ };
190
+ };
191
+ }[];
192
+ }[];
193
+ }];
194
+ };
195
+ /**
196
+ * Number of unique findings currently tracked.
197
+ */
198
+ size(): number;
199
+ /**
200
+ * Number of times `ingestRun` has been called on this instance.
201
+ */
202
+ get invocationsCount(): number;
203
+ }
204
+ export {};
205
+ //# sourceMappingURL=sarif-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif-store.d.ts","sourceRoot":"","sources":["../../src/sarif/sarif-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,246 @@
1
+ /**
2
+ * On-disk SARIF 2.1.0 store with finding deduplication.
3
+ *
4
+ * The Stop quality gate and the `ingest_sarif` MCP tool both need a
5
+ * single, consolidated view of every finding produced across the current
6
+ * session. This module provides:
7
+ *
8
+ * - `loadLatest()` — read the consolidated SARIF document from disk,
9
+ * or return an empty seed when no report exists yet.
10
+ * - `ingestRun()` — merge a new SARIF run from an external scanner
11
+ * (Semgrep, ESLint, Bandit, Stryker, ...) into the
12
+ * in-memory store, deduplicating by
13
+ * `(ruleId, uri, startLine, startColumn)`.
14
+ * - `persist()` — atomically write the consolidated document back
15
+ * to disk so other processes (the dashboard) can
16
+ * read it.
17
+ *
18
+ * The store is intentionally simple: it does NOT attempt to preserve
19
+ * per-tool run separation inside the persisted file. Instead, every
20
+ * ingested run is flattened into a single `runs[0]` entry whose `tool.driver`
21
+ * is claude-crap itself, and the original scanner name is recorded on
22
+ * each finding via the `properties.sourceTool` field. This keeps the
23
+ * consolidated document easy to diff between sessions.
24
+ *
25
+ * @module sarif/sarif-store
26
+ */
27
+ import { promises as fs } from "node:fs";
28
+ import { dirname, isAbsolute, join, resolve } from "node:path";
29
+ import { buildSarifDocument } from "./sarif-builder.js";
30
+ /**
31
+ * On-disk SARIF store.
32
+ */
33
+ export class SarifStore {
34
+ filePath;
35
+ /** In-memory index of findings keyed by their dedup string. */
36
+ findings = new Map();
37
+ /** Tool invocations we have already ingested, for telemetry. */
38
+ toolInvocations = 0;
39
+ constructor(options) {
40
+ const dir = isAbsolute(options.outputDir)
41
+ ? options.outputDir
42
+ : resolve(options.workspaceRoot, options.outputDir);
43
+ this.filePath = join(dir, options.fileName ?? "latest.sarif");
44
+ }
45
+ /**
46
+ * Absolute path to the consolidated SARIF file on disk.
47
+ */
48
+ get consolidatedReportPath() {
49
+ return this.filePath;
50
+ }
51
+ /**
52
+ * Load the consolidated document from disk into memory. If the file is
53
+ * missing, the store is initialized empty. Top-level parsing errors
54
+ * still throw (a file that is not valid JSON, or that declares a
55
+ * different SARIF version, is a real safety signal). However, once
56
+ * the document is parsed, malformed individual runs and results are
57
+ * tolerated: F-A08-01 showed that a single bad entry in `latest.sarif`
58
+ * could crash the MCP server on boot and persistently DoS the
59
+ * developer. Each run / result is wrapped in its own try/catch so a
60
+ * single bad entry logs to stderr and is dropped, but the rest of
61
+ * the file still loads.
62
+ *
63
+ * @throws When the file exists but is not valid SARIF 2.1.0 JSON.
64
+ */
65
+ async loadLatest() {
66
+ try {
67
+ const raw = await fs.readFile(this.filePath, "utf8");
68
+ const parsed = JSON.parse(raw);
69
+ if (parsed.version !== "2.1.0") {
70
+ throw new Error(`Expected SARIF 2.1.0, got ${parsed.version}`);
71
+ }
72
+ this.findings.clear();
73
+ // Defensive against tampered / mis-generated files: `runs` must
74
+ // be an array. Anything else is dropped with a stderr warning.
75
+ if (!Array.isArray(parsed.runs)) {
76
+ process.stderr.write(`[sarif-store] ${this.filePath}: 'runs' is not an array, dropping entire document\n`);
77
+ return;
78
+ }
79
+ for (const run of parsed.runs) {
80
+ try {
81
+ if (!run || typeof run !== "object" || !Array.isArray(run.results)) {
82
+ process.stderr.write(`[sarif-store] ${this.filePath}: skipping run with non-iterable 'results'\n`);
83
+ continue;
84
+ }
85
+ for (const result of run.results) {
86
+ try {
87
+ const finding = hydrateFindingFromResult(result);
88
+ if (finding)
89
+ this.findings.set(finding.dedupKey, finding);
90
+ }
91
+ catch (entryErr) {
92
+ process.stderr.write(`[sarif-store] ${this.filePath}: dropping malformed result: ${entryErr.message}\n`);
93
+ }
94
+ }
95
+ }
96
+ catch (runErr) {
97
+ process.stderr.write(`[sarif-store] ${this.filePath}: dropping malformed run: ${runErr.message}\n`);
98
+ }
99
+ }
100
+ }
101
+ catch (err) {
102
+ const error = err;
103
+ if (error.code === "ENOENT") {
104
+ // No report on disk yet — normal for a fresh workspace.
105
+ this.findings.clear();
106
+ return;
107
+ }
108
+ throw new Error(`[sarif-store] Failed to load consolidated report at ${this.filePath}: ${error.message}`);
109
+ }
110
+ }
111
+ /**
112
+ * Merge a raw SARIF document (from any external scanner) into the
113
+ * store, deduplicating by `(ruleId, uri, startLine, startColumn)`. The
114
+ * last writer wins for the message and level fields — later ingestions
115
+ * can refine earlier ones.
116
+ *
117
+ * @param sarifDocument The raw SARIF document as received from the tool.
118
+ * @param sourceTool Stable identifier of the producing scanner.
119
+ * @returns Stats describing what was accepted.
120
+ */
121
+ ingestRun(sarifDocument, sourceTool) {
122
+ if (sarifDocument.version !== "2.1.0") {
123
+ throw new Error(`[sarif-store] ingestRun received version ${sarifDocument.version}, expected 2.1.0`);
124
+ }
125
+ this.toolInvocations += 1;
126
+ let accepted = 0;
127
+ let duplicates = 0;
128
+ let total = 0;
129
+ for (const run of sarifDocument.runs) {
130
+ for (const result of run.results) {
131
+ total += 1;
132
+ const finding = hydrateFindingFromResult(result, sourceTool);
133
+ if (!finding)
134
+ continue;
135
+ if (this.findings.has(finding.dedupKey)) {
136
+ duplicates += 1;
137
+ // Overwrite with the latest metadata so the consolidated view
138
+ // reflects the most recent scanner output for this location.
139
+ this.findings.set(finding.dedupKey, finding);
140
+ continue;
141
+ }
142
+ this.findings.set(finding.dedupKey, finding);
143
+ accepted += 1;
144
+ }
145
+ }
146
+ return { accepted, duplicates, total };
147
+ }
148
+ /**
149
+ * Snapshot all currently tracked findings as a plain array. Mostly
150
+ * useful for tests and for the dashboard API.
151
+ */
152
+ list() {
153
+ return Array.from(this.findings.values());
154
+ }
155
+ /**
156
+ * Atomically write the consolidated document back to disk. Uses a
157
+ * temporary file and `rename` so concurrent readers never observe
158
+ * a half-written document.
159
+ */
160
+ async persist() {
161
+ const doc = this.toSarifDocument();
162
+ await fs.mkdir(dirname(this.filePath), { recursive: true });
163
+ const tmp = `${this.filePath}.${process.pid}.tmp`;
164
+ await fs.writeFile(tmp, JSON.stringify(doc, null, 2), "utf8");
165
+ await fs.rename(tmp, this.filePath);
166
+ }
167
+ /**
168
+ * Build the current consolidated SARIF document from the in-memory
169
+ * findings without touching disk.
170
+ */
171
+ toSarifDocument() {
172
+ // Strip the store-only `dedupKey` and `sourceTool` fields before
173
+ // serializing, but keep `sourceTool` in the per-finding `properties`
174
+ // bag so consumers can still trace origin.
175
+ const findings = Array.from(this.findings.values()).map((f) => ({
176
+ ruleId: f.ruleId,
177
+ level: f.level,
178
+ message: f.message,
179
+ location: f.location,
180
+ properties: {
181
+ ...(f.properties ?? {}),
182
+ sourceTool: f.sourceTool,
183
+ },
184
+ }));
185
+ return buildSarifDocument({
186
+ name: "claude-crap",
187
+ version: "0.1.0",
188
+ informationUri: "https://github.com/local/claude-crap",
189
+ }, findings);
190
+ }
191
+ /**
192
+ * Number of unique findings currently tracked.
193
+ */
194
+ size() {
195
+ return this.findings.size;
196
+ }
197
+ /**
198
+ * Number of times `ingestRun` has been called on this instance.
199
+ */
200
+ get invocationsCount() {
201
+ return this.toolInvocations;
202
+ }
203
+ }
204
+ /**
205
+ * Convert a raw SARIF `result` object into an {@link IngestedFinding}.
206
+ * Returns `null` when the result is malformed (missing ruleId, message,
207
+ * or physical location), since a finding without coordinates cannot be
208
+ * deduplicated and is therefore useless.
209
+ *
210
+ * @param result Raw SARIF `result` object from the scanner's document.
211
+ * @param sourceTool Optional scanner identifier. If omitted, we read it
212
+ * from `result.properties.sourceTool` (used when
213
+ * reloading a persisted report).
214
+ * @returns The hydrated finding, or `null` when invalid.
215
+ */
216
+ function hydrateFindingFromResult(result, sourceTool) {
217
+ if (!result.ruleId || !result.message?.text)
218
+ return null;
219
+ const loc = result.locations?.[0]?.physicalLocation;
220
+ const uri = loc?.artifactLocation?.uri;
221
+ const region = loc?.region;
222
+ if (!uri || region?.startLine === undefined || region.startColumn === undefined)
223
+ return null;
224
+ const resolvedSourceTool = sourceTool ??
225
+ (typeof result.properties?.sourceTool === "string"
226
+ ? result.properties.sourceTool
227
+ : "unknown");
228
+ const level = result.level ?? "warning";
229
+ const dedupKey = `${result.ruleId}|${uri}|${region.startLine}|${region.startColumn}`;
230
+ return {
231
+ ruleId: result.ruleId,
232
+ level,
233
+ message: result.message.text,
234
+ location: {
235
+ uri,
236
+ startLine: region.startLine,
237
+ startColumn: region.startColumn,
238
+ ...(region.endLine !== undefined ? { endLine: region.endLine } : {}),
239
+ ...(region.endColumn !== undefined ? { endColumn: region.endColumn } : {}),
240
+ },
241
+ ...(result.properties ? { properties: result.properties } : {}),
242
+ dedupKey,
243
+ sourceTool: resolvedSourceTool,
244
+ };
245
+ }
246
+ //# sourceMappingURL=sarif-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif-store.js","sourceRoot":"","sources":["../../src/sarif/sarif-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * AJV-backed minimal SARIF 2.1.0 document validator.
3
+ *
4
+ * F-A05-01: the `ingest_sarif` MCP tool accepts a caller-supplied
5
+ * `sarifDocument` object and, before this module existed, only
6
+ * checked `version === "2.1.0"`. That was enough for tool-call
7
+ * dispatch but not for the payload itself — a SARIF with a missing
8
+ * `runs[]`, a `results` array of wrong-type entries, or a result
9
+ * without a `ruleId` would still be accepted by the MCP tool and
10
+ * flow through to the store, the dashboard, and any downstream
11
+ * consumer that uploads claude-crap's SARIF to GitHub code-scanning
12
+ * or an IDE viewer.
13
+ *
14
+ * This module uses the `ajv` dependency (already in package.json) to
15
+ * compile a minimal JSON Schema that covers exactly the fields
16
+ * claude-crap reads: `version`, `runs`, `runs[].tool.driver.name`,
17
+ * and the per-result shape. Everything else (tool metadata, rule
18
+ * definitions, snippets, etc.) is passthrough — we do not enforce
19
+ * the full SARIF 2.1.0 spec because claude-crap does not consume
20
+ * those fields.
21
+ *
22
+ * The compiled validator is cached so the ~5 ms AJV compile cost is
23
+ * paid once per MCP server process, not once per ingestion.
24
+ *
25
+ * @module sarif/sarif-validator
26
+ */
27
+ /**
28
+ * Returned by {@link validateSarifDocument} when the document fails
29
+ * schema validation. Includes the full AJV error array for callers
30
+ * that want to surface structured diagnostics.
31
+ */
32
+ export declare class SarifValidationError extends Error {
33
+ readonly errors: unknown;
34
+ constructor(message: string, errors: unknown);
35
+ }
36
+ /**
37
+ * Validate a SARIF 2.1.0 document against the minimal schema. Throws
38
+ * {@link SarifValidationError} when the document does not match.
39
+ *
40
+ * @param doc Document to validate. May be any value — the validator
41
+ * treats non-object inputs as a schema violation.
42
+ * @throws {@link SarifValidationError} on any validation failure.
43
+ */
44
+ export declare function validateSarifDocument(doc: unknown): void;
45
+ //# sourceMappingURL=sarif-validator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sarif-validator.d.ts","sourceRoot":"","sources":["../../src/sarif/sarif-validator.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAsEH;;;;GAIG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;IAC7C,SAAgB,MAAM,EAAE,OAAO,CAAC;gBAEpB,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO;CAK7C;AAkBD;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAUxD"}