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.
- package/CHANGELOG.md +308 -0
- package/LICENSE +21 -0
- package/README.md +550 -0
- package/bin/claude-crap.mjs +141 -0
- package/dist/adapters/bandit.d.ts +48 -0
- package/dist/adapters/bandit.d.ts.map +1 -0
- package/dist/adapters/bandit.js +145 -0
- package/dist/adapters/bandit.js.map +1 -0
- package/dist/adapters/common.d.ts +73 -0
- package/dist/adapters/common.d.ts.map +1 -0
- package/dist/adapters/common.js +78 -0
- package/dist/adapters/common.js.map +1 -0
- package/dist/adapters/eslint.d.ts +52 -0
- package/dist/adapters/eslint.d.ts.map +1 -0
- package/dist/adapters/eslint.js +142 -0
- package/dist/adapters/eslint.js.map +1 -0
- package/dist/adapters/index.d.ts +47 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +64 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/semgrep.d.ts +30 -0
- package/dist/adapters/semgrep.d.ts.map +1 -0
- package/dist/adapters/semgrep.js +130 -0
- package/dist/adapters/semgrep.js.map +1 -0
- package/dist/adapters/stryker.d.ts +55 -0
- package/dist/adapters/stryker.d.ts.map +1 -0
- package/dist/adapters/stryker.js +165 -0
- package/dist/adapters/stryker.js.map +1 -0
- package/dist/ast/cyclomatic.d.ts +48 -0
- package/dist/ast/cyclomatic.d.ts.map +1 -0
- package/dist/ast/cyclomatic.js +106 -0
- package/dist/ast/cyclomatic.js.map +1 -0
- package/dist/ast/index.d.ts +26 -0
- package/dist/ast/index.d.ts.map +1 -0
- package/dist/ast/index.js +23 -0
- package/dist/ast/index.js.map +1 -0
- package/dist/ast/language-config.d.ts +70 -0
- package/dist/ast/language-config.d.ts.map +1 -0
- package/dist/ast/language-config.js +192 -0
- package/dist/ast/language-config.js.map +1 -0
- package/dist/ast/tree-sitter-engine.d.ts +133 -0
- package/dist/ast/tree-sitter-engine.d.ts.map +1 -0
- package/dist/ast/tree-sitter-engine.js +270 -0
- package/dist/ast/tree-sitter-engine.js.map +1 -0
- package/dist/config.d.ts +57 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +78 -0
- package/dist/config.js.map +1 -0
- package/dist/crap-config.d.ts +97 -0
- package/dist/crap-config.d.ts.map +1 -0
- package/dist/crap-config.js +144 -0
- package/dist/crap-config.js.map +1 -0
- package/dist/dashboard/server.d.ts +65 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +147 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +574 -0
- package/dist/index.js.map +1 -0
- package/dist/metrics/crap.d.ts +71 -0
- package/dist/metrics/crap.d.ts.map +1 -0
- package/dist/metrics/crap.js +67 -0
- package/dist/metrics/crap.js.map +1 -0
- package/dist/metrics/index.d.ts +31 -0
- package/dist/metrics/index.d.ts.map +1 -0
- package/dist/metrics/index.js +27 -0
- package/dist/metrics/index.js.map +1 -0
- package/dist/metrics/score.d.ts +143 -0
- package/dist/metrics/score.d.ts.map +1 -0
- package/dist/metrics/score.js +224 -0
- package/dist/metrics/score.js.map +1 -0
- package/dist/metrics/tdr.d.ts +106 -0
- package/dist/metrics/tdr.d.ts.map +1 -0
- package/dist/metrics/tdr.js +117 -0
- package/dist/metrics/tdr.js.map +1 -0
- package/dist/metrics/workspace-walker.d.ts +43 -0
- package/dist/metrics/workspace-walker.d.ts.map +1 -0
- package/dist/metrics/workspace-walker.js +137 -0
- package/dist/metrics/workspace-walker.js.map +1 -0
- package/dist/sarif/index.d.ts +21 -0
- package/dist/sarif/index.d.ts.map +1 -0
- package/dist/sarif/index.js +19 -0
- package/dist/sarif/index.js.map +1 -0
- package/dist/sarif/sarif-builder.d.ts +128 -0
- package/dist/sarif/sarif-builder.d.ts.map +1 -0
- package/dist/sarif/sarif-builder.js +79 -0
- package/dist/sarif/sarif-builder.js.map +1 -0
- package/dist/sarif/sarif-store.d.ts +205 -0
- package/dist/sarif/sarif-store.d.ts.map +1 -0
- package/dist/sarif/sarif-store.js +246 -0
- package/dist/sarif/sarif-store.js.map +1 -0
- package/dist/sarif/sarif-validator.d.ts +45 -0
- package/dist/sarif/sarif-validator.d.ts.map +1 -0
- package/dist/sarif/sarif-validator.js +138 -0
- package/dist/sarif/sarif-validator.js.map +1 -0
- package/dist/schemas/tool-schemas.d.ts +216 -0
- package/dist/schemas/tool-schemas.d.ts.map +1 -0
- package/dist/schemas/tool-schemas.js +208 -0
- package/dist/schemas/tool-schemas.js.map +1 -0
- package/dist/sdk.d.ts +45 -0
- package/dist/sdk.d.ts.map +1 -0
- package/dist/sdk.js +44 -0
- package/dist/sdk.js.map +1 -0
- package/dist/tools/index.d.ts +24 -0
- package/dist/tools/index.d.ts.map +1 -0
- package/dist/tools/index.js +23 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/test-harness.d.ts +75 -0
- package/dist/tools/test-harness.d.ts.map +1 -0
- package/dist/tools/test-harness.js +137 -0
- package/dist/tools/test-harness.js.map +1 -0
- package/dist/workspace-guard.d.ts +53 -0
- package/dist/workspace-guard.d.ts.map +1 -0
- package/dist/workspace-guard.js +61 -0
- package/dist/workspace-guard.js.map +1 -0
- package/package.json +133 -0
- package/plugin/.claude-plugin/plugin.json +29 -0
- package/plugin/.mcp.json +18 -0
- package/plugin/CLAUDE.md +143 -0
- package/plugin/bundle/dashboard/public/index.html +368 -0
- package/plugin/bundle/dashboard/public/vendor/vue.global.prod.js +9 -0
- package/plugin/bundle/mcp-server.mjs +8718 -0
- package/plugin/bundle/mcp-server.mjs.map +7 -0
- package/plugin/bundle/tdr-engine.mjs +50 -0
- package/plugin/bundle/tdr-engine.mjs.map +7 -0
- package/plugin/hooks/hooks.json +62 -0
- package/plugin/hooks/lib/crap-config.mjs +152 -0
- package/plugin/hooks/lib/gatekeeper-rules.mjs +257 -0
- package/plugin/hooks/lib/hook-io.mjs +151 -0
- package/plugin/hooks/lib/quality-gate.mjs +329 -0
- package/plugin/hooks/lib/test-harness.mjs +152 -0
- package/plugin/hooks/post-tool-use.mjs +245 -0
- package/plugin/hooks/pre-tool-use.mjs +290 -0
- package/plugin/hooks/session-start.mjs +109 -0
- package/plugin/hooks/stop-quality-gate.mjs +226 -0
- package/plugin/package.json +18 -0
- package/plugin/skills/adopt/SKILL.md +74 -0
- package/plugin/skills/analyze/SKILL.md +77 -0
- package/plugin/skills/check-test/SKILL.md +50 -0
- package/plugin/skills/score/SKILL.md +31 -0
- package/scripts/bug-report.mjs +328 -0
- package/scripts/build-fast.mjs +130 -0
- package/scripts/bundle-plugin.mjs +74 -0
- package/scripts/doctor.mjs +320 -0
- package/scripts/install.mjs +192 -0
- package/scripts/lib/cli-ui.mjs +122 -0
- package/scripts/postinstall.mjs +127 -0
- package/scripts/run-tests.mjs +95 -0
- package/scripts/status.mjs +110 -0
- package/scripts/uninstall.mjs +72 -0
- package/src/adapters/bandit.ts +191 -0
- package/src/adapters/common.ts +133 -0
- package/src/adapters/eslint.ts +187 -0
- package/src/adapters/index.ts +78 -0
- package/src/adapters/semgrep.ts +150 -0
- package/src/adapters/stryker.ts +218 -0
- package/src/ast/cyclomatic.ts +131 -0
- package/src/ast/index.ts +33 -0
- package/src/ast/language-config.ts +231 -0
- package/src/ast/tree-sitter-engine.ts +385 -0
- package/src/config.ts +109 -0
- package/src/crap-config.ts +196 -0
- package/src/dashboard/public/index.html +368 -0
- package/src/dashboard/public/vendor/vue.global.prod.js +9 -0
- package/src/dashboard/server.ts +205 -0
- package/src/index.ts +696 -0
- package/src/metrics/crap.ts +101 -0
- package/src/metrics/index.ts +51 -0
- package/src/metrics/score.ts +329 -0
- package/src/metrics/tdr.ts +155 -0
- package/src/metrics/workspace-walker.ts +146 -0
- package/src/sarif/index.ts +31 -0
- package/src/sarif/sarif-builder.ts +139 -0
- package/src/sarif/sarif-store.ts +347 -0
- package/src/sarif/sarif-validator.ts +145 -0
- package/src/schemas/tool-schemas.ts +225 -0
- package/src/sdk.ts +110 -0
- package/src/tests/adapters/bandit.test.ts +111 -0
- package/src/tests/adapters/dispatch.test.ts +100 -0
- package/src/tests/adapters/eslint.test.ts +138 -0
- package/src/tests/adapters/semgrep.test.ts +125 -0
- package/src/tests/adapters/stryker.test.ts +103 -0
- package/src/tests/crap-config.test.ts +228 -0
- package/src/tests/crap.test.ts +59 -0
- package/src/tests/cyclomatic.test.ts +87 -0
- package/src/tests/dashboard-http.test.ts +108 -0
- package/src/tests/dashboard-integrity.test.ts +128 -0
- package/src/tests/integration/mcp-server.integration.test.ts +352 -0
- package/src/tests/pre-tool-use-hook.test.ts +178 -0
- package/src/tests/sarif-store.test.ts +241 -0
- package/src/tests/sarif-validator.test.ts +164 -0
- package/src/tests/score.test.ts +260 -0
- package/src/tests/skills-frontmatter.test.ts +172 -0
- package/src/tests/stop-quality-gate-strictness.test.ts +243 -0
- package/src/tests/tdr.test.ts +86 -0
- package/src/tests/test-harness.test.ts +153 -0
- package/src/tests/workspace-guard.test.ts +111 -0
- package/src/tools/index.ts +24 -0
- package/src/tools/test-harness.ts +158 -0
- package/src/workspace-guard.ts +64 -0
- 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"}
|