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,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRAP (Change Risk Anti-Patterns) index — deterministic computation.
|
|
3
|
+
*
|
|
4
|
+
* The CRAP index is a single number that summarizes how dangerous it is
|
|
5
|
+
* to change a given function. It combines two signals:
|
|
6
|
+
*
|
|
7
|
+
* 1. Cyclomatic complexity (`comp`) — how many independent paths the
|
|
8
|
+
* function has. Tracks how easy it is to reason about the code.
|
|
9
|
+
* 2. Test coverage percentage (`cov`) — empirical safety net provided
|
|
10
|
+
* by the automated test suite.
|
|
11
|
+
*
|
|
12
|
+
* The formula (see docs/quality-gate.md) is:
|
|
13
|
+
*
|
|
14
|
+
* CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)
|
|
15
|
+
*
|
|
16
|
+
* The cubic uncovered-weight term makes CRAP punish uncovered, branchy
|
|
17
|
+
* code extremely aggressively. A function with complexity 10 and 0%
|
|
18
|
+
* coverage scores CRAP = 10² × 1³ + 10 = 110, well above the 30 threshold.
|
|
19
|
+
*
|
|
20
|
+
* The additive `+ comp(m)` tail is intentional: it means that any function
|
|
21
|
+
* with `comp ≥ 30` can NEVER reach a passing CRAP score, even with 100%
|
|
22
|
+
* coverage (because the final term alone equals the threshold). This
|
|
23
|
+
* encodes the policy "functions above complexity 30 must be decomposed,
|
|
24
|
+
* period" — you cannot test your way out of structural complexity.
|
|
25
|
+
*
|
|
26
|
+
* @module metrics/crap
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Inputs required to compute CRAP for a single function.
|
|
30
|
+
*/
|
|
31
|
+
export interface CrapInput {
|
|
32
|
+
/** Cyclomatic complexity of the function. Must be an integer ≥ 1. */
|
|
33
|
+
readonly cyclomaticComplexity: number;
|
|
34
|
+
/** Test coverage percentage for the function. Must be in `[0, 100]`. */
|
|
35
|
+
readonly coveragePercent: number;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Result of a CRAP computation, including the inputs used so callers can
|
|
39
|
+
* echo the context back to the LLM or dump it to a SARIF result's
|
|
40
|
+
* `properties` bag without re-reading the source data.
|
|
41
|
+
*/
|
|
42
|
+
export interface CrapResult {
|
|
43
|
+
/** The CRAP score, rounded to 4 decimals for stable serialization. */
|
|
44
|
+
readonly crap: number;
|
|
45
|
+
/** Cyclomatic complexity echoed from the input. */
|
|
46
|
+
readonly cyclomaticComplexity: number;
|
|
47
|
+
/** Coverage percentage echoed from the input. */
|
|
48
|
+
readonly coveragePercent: number;
|
|
49
|
+
/** `true` when `crap > threshold` — caller should block on this. */
|
|
50
|
+
readonly exceedsThreshold: boolean;
|
|
51
|
+
/** The threshold used for the `exceedsThreshold` decision. */
|
|
52
|
+
readonly threshold: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Compute the CRAP index for a single function, against a configurable
|
|
56
|
+
* block threshold. This function is pure, deterministic, and performs no
|
|
57
|
+
* I/O — it can be called from any context (MCP tool handler, hook, unit
|
|
58
|
+
* test) without side effects.
|
|
59
|
+
*
|
|
60
|
+
* @param input Cyclomatic complexity and coverage for the function.
|
|
61
|
+
* @param threshold The CRAP score above which the caller should block.
|
|
62
|
+
* @returns A {@link CrapResult} containing the score and decision.
|
|
63
|
+
* @throws When any input is out of range or not finite.
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* // 12 branches, 60% coverage, threshold = 30
|
|
67
|
+
* computeCrap({ cyclomaticComplexity: 12, coveragePercent: 60 }, 30)
|
|
68
|
+
* // → { crap: 21.216, exceedsThreshold: false, ... }
|
|
69
|
+
*/
|
|
70
|
+
export declare function computeCrap(input: CrapInput, threshold: number): CrapResult;
|
|
71
|
+
//# sourceMappingURL=crap.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crap.d.ts","sourceRoot":"","sources":["../../src/metrics/crap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH;;GAEG;AACH,MAAM,WAAW,SAAS;IACxB,qEAAqE;IACrE,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,wEAAwE;IACxE,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,sEAAsE;IACtE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,QAAQ,CAAC,oBAAoB,EAAE,MAAM,CAAC;IACtC,iDAAiD;IACjD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,oEAAoE;IACpE,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,CA4B3E"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CRAP (Change Risk Anti-Patterns) index — deterministic computation.
|
|
3
|
+
*
|
|
4
|
+
* The CRAP index is a single number that summarizes how dangerous it is
|
|
5
|
+
* to change a given function. It combines two signals:
|
|
6
|
+
*
|
|
7
|
+
* 1. Cyclomatic complexity (`comp`) — how many independent paths the
|
|
8
|
+
* function has. Tracks how easy it is to reason about the code.
|
|
9
|
+
* 2. Test coverage percentage (`cov`) — empirical safety net provided
|
|
10
|
+
* by the automated test suite.
|
|
11
|
+
*
|
|
12
|
+
* The formula (see docs/quality-gate.md) is:
|
|
13
|
+
*
|
|
14
|
+
* CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)
|
|
15
|
+
*
|
|
16
|
+
* The cubic uncovered-weight term makes CRAP punish uncovered, branchy
|
|
17
|
+
* code extremely aggressively. A function with complexity 10 and 0%
|
|
18
|
+
* coverage scores CRAP = 10² × 1³ + 10 = 110, well above the 30 threshold.
|
|
19
|
+
*
|
|
20
|
+
* The additive `+ comp(m)` tail is intentional: it means that any function
|
|
21
|
+
* with `comp ≥ 30` can NEVER reach a passing CRAP score, even with 100%
|
|
22
|
+
* coverage (because the final term alone equals the threshold). This
|
|
23
|
+
* encodes the policy "functions above complexity 30 must be decomposed,
|
|
24
|
+
* period" — you cannot test your way out of structural complexity.
|
|
25
|
+
*
|
|
26
|
+
* @module metrics/crap
|
|
27
|
+
*/
|
|
28
|
+
/**
|
|
29
|
+
* Compute the CRAP index for a single function, against a configurable
|
|
30
|
+
* block threshold. This function is pure, deterministic, and performs no
|
|
31
|
+
* I/O — it can be called from any context (MCP tool handler, hook, unit
|
|
32
|
+
* test) without side effects.
|
|
33
|
+
*
|
|
34
|
+
* @param input Cyclomatic complexity and coverage for the function.
|
|
35
|
+
* @param threshold The CRAP score above which the caller should block.
|
|
36
|
+
* @returns A {@link CrapResult} containing the score and decision.
|
|
37
|
+
* @throws When any input is out of range or not finite.
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // 12 branches, 60% coverage, threshold = 30
|
|
41
|
+
* computeCrap({ cyclomaticComplexity: 12, coveragePercent: 60 }, 30)
|
|
42
|
+
* // → { crap: 21.216, exceedsThreshold: false, ... }
|
|
43
|
+
*/
|
|
44
|
+
export function computeCrap(input, threshold) {
|
|
45
|
+
if (!Number.isFinite(input.cyclomaticComplexity) || input.cyclomaticComplexity < 1) {
|
|
46
|
+
throw new Error(`[crap] cyclomaticComplexity must be ≥ 1, got ${input.cyclomaticComplexity}`);
|
|
47
|
+
}
|
|
48
|
+
if (!Number.isFinite(input.coveragePercent) || input.coveragePercent < 0 || input.coveragePercent > 100) {
|
|
49
|
+
throw new Error(`[crap] coveragePercent must be in [0, 100], got ${input.coveragePercent}`);
|
|
50
|
+
}
|
|
51
|
+
if (!Number.isFinite(threshold) || threshold <= 0) {
|
|
52
|
+
throw new Error(`[crap] threshold must be > 0, got ${threshold}`);
|
|
53
|
+
}
|
|
54
|
+
const comp = input.cyclomaticComplexity;
|
|
55
|
+
const uncovered = 1 - input.coveragePercent / 100;
|
|
56
|
+
const crap = comp * comp * Math.pow(uncovered, 3) + comp;
|
|
57
|
+
return {
|
|
58
|
+
// Round to 4 decimals so JSON serialization is stable across runs
|
|
59
|
+
// (important for SARIF diffing and dashboard caching).
|
|
60
|
+
crap: Number(crap.toFixed(4)),
|
|
61
|
+
cyclomaticComplexity: comp,
|
|
62
|
+
coveragePercent: input.coveragePercent,
|
|
63
|
+
exceedsThreshold: crap > threshold,
|
|
64
|
+
threshold,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=crap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"crap.js","sourceRoot":"","sources":["../../src/metrics/crap.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AA8BH;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,WAAW,CAAC,KAAgB,EAAE,SAAiB;IAC7D,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,oBAAoB,CAAC,IAAI,KAAK,CAAC,oBAAoB,GAAG,CAAC,EAAE,CAAC;QACnF,MAAM,IAAI,KAAK,CACb,gDAAgD,KAAK,CAAC,oBAAoB,EAAE,CAC7E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,KAAK,CAAC,eAAe,GAAG,CAAC,IAAI,KAAK,CAAC,eAAe,GAAG,GAAG,EAAE,CAAC;QACxG,MAAM,IAAI,KAAK,CACb,mDAAmD,KAAK,CAAC,eAAe,EAAE,CAC3E,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;QAClD,MAAM,IAAI,KAAK,CAAC,qCAAqC,SAAS,EAAE,CAAC,CAAC;IACpE,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,oBAAoB,CAAC;IACxC,MAAM,SAAS,GAAG,CAAC,GAAG,KAAK,CAAC,eAAe,GAAG,GAAG,CAAC;IAClD,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC;IAEzD,OAAO;QACL,kEAAkE;QAClE,uDAAuD;QACvD,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QAC7B,oBAAoB,EAAE,IAAI;QAC1B,eAAe,EAAE,KAAK,CAAC,eAAe;QACtC,gBAAgB,EAAE,IAAI,GAAG,SAAS;QAClC,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public SDK entry point for the deterministic metrics engines.
|
|
3
|
+
*
|
|
4
|
+
* Everything re-exported from this barrel is part of the stable public
|
|
5
|
+
* API of `claude-crap/metrics`. Downstream consumers can rely
|
|
6
|
+
* on the shapes here remaining semver-stable — breaking changes only
|
|
7
|
+
* land in major versions.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {
|
|
13
|
+
* computeCrap,
|
|
14
|
+
* computeTdr,
|
|
15
|
+
* computeProjectScore,
|
|
16
|
+
* classifyTdr,
|
|
17
|
+
* ratingIsWorseThan,
|
|
18
|
+
* } from "claude-crap/metrics";
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module metrics
|
|
22
|
+
*/
|
|
23
|
+
export { computeCrap } from "./crap.js";
|
|
24
|
+
export type { CrapInput, CrapResult } from "./crap.js";
|
|
25
|
+
export { classifyTdr, computeTdr, ratingIsWorseThan, ratingToRank, } from "./tdr.js";
|
|
26
|
+
export type { TdrInput, TdrResult } from "./tdr.js";
|
|
27
|
+
export { computeProjectScore, renderProjectScoreMarkdown, } from "./score.js";
|
|
28
|
+
export type { ComputeProjectScoreInput, DimensionScore, FindingsSummary, MaintainabilityScore, ProjectScore, ScoreLocation, SeverityRating, WorkspaceStats, } from "./score.js";
|
|
29
|
+
export { estimateWorkspaceLoc, MAX_FILES_WALKED } from "./workspace-walker.js";
|
|
30
|
+
export type { WorkspaceWalkResult } from "./workspace-walker.js";
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AACxC,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAEvD,OAAO,EACL,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,YAAY,GACb,MAAM,UAAU,CAAC;AAClB,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAEpD,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AACpB,YAAY,EACV,wBAAwB,EACxB,cAAc,EACd,eAAe,EACf,oBAAoB,EACpB,YAAY,EACZ,aAAa,EACb,cAAc,EACd,cAAc,GACf,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAC/E,YAAY,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public SDK entry point for the deterministic metrics engines.
|
|
3
|
+
*
|
|
4
|
+
* Everything re-exported from this barrel is part of the stable public
|
|
5
|
+
* API of `claude-crap/metrics`. Downstream consumers can rely
|
|
6
|
+
* on the shapes here remaining semver-stable — breaking changes only
|
|
7
|
+
* land in major versions.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import {
|
|
13
|
+
* computeCrap,
|
|
14
|
+
* computeTdr,
|
|
15
|
+
* computeProjectScore,
|
|
16
|
+
* classifyTdr,
|
|
17
|
+
* ratingIsWorseThan,
|
|
18
|
+
* } from "claude-crap/metrics";
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module metrics
|
|
22
|
+
*/
|
|
23
|
+
export { computeCrap } from "./crap.js";
|
|
24
|
+
export { classifyTdr, computeTdr, ratingIsWorseThan, ratingToRank, } from "./tdr.js";
|
|
25
|
+
export { computeProjectScore, renderProjectScoreMarkdown, } from "./score.js";
|
|
26
|
+
export { estimateWorkspaceLoc, MAX_FILES_WALKED } from "./workspace-walker.js";
|
|
27
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/metrics/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,EACL,WAAW,EACX,UAAU,EACV,iBAAiB,EACjB,YAAY,GACb,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,mBAAmB,EACnB,0BAA0B,GAC3B,MAAM,YAAY,CAAC;AAYpB,OAAO,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregate project score engine.
|
|
3
|
+
*
|
|
4
|
+
* Given a fully resolved configuration, the live SARIF store, and a
|
|
5
|
+
* workspace LOC walker, this module produces a single immutable
|
|
6
|
+
* `ProjectScore` snapshot describing the entire project's quality
|
|
7
|
+
* posture across three dimensions:
|
|
8
|
+
*
|
|
9
|
+
* - **Maintainability** — derived from the Technical Debt Ratio (TDR)
|
|
10
|
+
* using the existing `metrics/tdr.ts` engine.
|
|
11
|
+
* - **Reliability** — derived from the worst non-security finding.
|
|
12
|
+
* - **Security** — derived from the worst security finding.
|
|
13
|
+
*
|
|
14
|
+
* Each dimension produces a letter grade A..E and the `overall` field
|
|
15
|
+
* collapses them by taking the worst grade. The `passes` field tells
|
|
16
|
+
* the caller whether the overall grade is within the configured
|
|
17
|
+
* `tdrMaxRating` tolerance — handy for the Stop quality gate and the
|
|
18
|
+
* `score_project` MCP tool.
|
|
19
|
+
*
|
|
20
|
+
* Security vs reliability is determined by a heuristic on the `ruleId`:
|
|
21
|
+
* any rule whose identifier matches a security keyword (`sec`, `sql`,
|
|
22
|
+
* `xss`, `csrf`, `injection`, `crypt`, `auth`, `secret`, `password`,
|
|
23
|
+
* `cve`, `vuln`) is treated as security. Everything else is reliability.
|
|
24
|
+
* This is intentionally coarse: adapters that stamp a richer SARIF
|
|
25
|
+
* taxonomy (e.g. `properties.tags = ["security"]`) could replace this
|
|
26
|
+
* classifier with an exact match, but the regex is sufficient for the
|
|
27
|
+
* scanners this plugin ships with.
|
|
28
|
+
*
|
|
29
|
+
* The score engine is pure: it does no I/O, takes a `WorkspaceStats`
|
|
30
|
+
* value (which the caller produces from the bounded LOC walker), and
|
|
31
|
+
* returns a brand new score object on every call. Tests can construct
|
|
32
|
+
* a `SarifStore` in memory and verify the boundaries directly.
|
|
33
|
+
*
|
|
34
|
+
* @module metrics/score
|
|
35
|
+
*/
|
|
36
|
+
import type { MaintainabilityRating } from "../config.js";
|
|
37
|
+
import type { SarifStore } from "../sarif/sarif-store.js";
|
|
38
|
+
/**
|
|
39
|
+
* Letter rating shared by every dimension. The same A..E scale used by
|
|
40
|
+
* SonarQube's reliability and security ratings, where A is best and E
|
|
41
|
+
* is unmaintainable / blocker-class.
|
|
42
|
+
*/
|
|
43
|
+
export type SeverityRating = MaintainabilityRating;
|
|
44
|
+
/**
|
|
45
|
+
* Workspace size statistics produced by an external bounded walker.
|
|
46
|
+
* The score engine does not walk the disk itself — pass these in.
|
|
47
|
+
*/
|
|
48
|
+
export interface WorkspaceStats {
|
|
49
|
+
/** Total physical lines of code under the workspace root. */
|
|
50
|
+
readonly physicalLoc: number;
|
|
51
|
+
/** Number of files visited by the walker. */
|
|
52
|
+
readonly fileCount: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Per-dimension breakdown.
|
|
56
|
+
*/
|
|
57
|
+
export interface DimensionScore {
|
|
58
|
+
readonly rating: SeverityRating;
|
|
59
|
+
readonly findings: number;
|
|
60
|
+
readonly errorFindings: number;
|
|
61
|
+
readonly warningFindings: number;
|
|
62
|
+
readonly noteFindings: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Per-finding-level summary plus per-tool counts.
|
|
66
|
+
*/
|
|
67
|
+
export interface FindingsSummary {
|
|
68
|
+
readonly total: number;
|
|
69
|
+
readonly error: number;
|
|
70
|
+
readonly warning: number;
|
|
71
|
+
readonly note: number;
|
|
72
|
+
readonly byTool: Readonly<Record<string, number>>;
|
|
73
|
+
readonly byFile: Readonly<Record<string, number>>;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Maintainability dimension expressed as a TDR percentage.
|
|
77
|
+
*/
|
|
78
|
+
export interface MaintainabilityScore {
|
|
79
|
+
readonly rating: MaintainabilityRating;
|
|
80
|
+
readonly tdrPercent: number;
|
|
81
|
+
readonly remediationMinutes: number;
|
|
82
|
+
readonly developmentCostMinutes: number;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Pointer to where the consolidated report can be found.
|
|
86
|
+
*/
|
|
87
|
+
export interface ScoreLocation {
|
|
88
|
+
/** Local dashboard URL when the HTTP server is running, otherwise `null`. */
|
|
89
|
+
readonly dashboardUrl: string | null;
|
|
90
|
+
/** Absolute path to the consolidated SARIF document on disk. */
|
|
91
|
+
readonly sarifReportPath: string;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* The full project score snapshot. Returned from {@link computeProjectScore}.
|
|
95
|
+
*/
|
|
96
|
+
export interface ProjectScore {
|
|
97
|
+
readonly generatedAt: string;
|
|
98
|
+
readonly workspaceRoot: string;
|
|
99
|
+
readonly loc: {
|
|
100
|
+
readonly physical: number;
|
|
101
|
+
readonly files: number;
|
|
102
|
+
};
|
|
103
|
+
readonly findings: FindingsSummary;
|
|
104
|
+
readonly maintainability: MaintainabilityScore;
|
|
105
|
+
readonly reliability: DimensionScore;
|
|
106
|
+
readonly security: DimensionScore;
|
|
107
|
+
readonly overall: {
|
|
108
|
+
readonly rating: SeverityRating;
|
|
109
|
+
/** True when `overall.rating` is no worse than `policyRating`. */
|
|
110
|
+
readonly passes: boolean;
|
|
111
|
+
/** Echoed from the configured policy. */
|
|
112
|
+
readonly policyRating: MaintainabilityRating;
|
|
113
|
+
};
|
|
114
|
+
readonly location: ScoreLocation;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Inputs accepted by {@link computeProjectScore}.
|
|
118
|
+
*/
|
|
119
|
+
export interface ComputeProjectScoreInput {
|
|
120
|
+
readonly workspaceRoot: string;
|
|
121
|
+
readonly minutesPerLoc: number;
|
|
122
|
+
readonly tdrMaxRating: MaintainabilityRating;
|
|
123
|
+
readonly workspace: WorkspaceStats;
|
|
124
|
+
readonly sarifStore: SarifStore;
|
|
125
|
+
readonly dashboardUrl: string | null;
|
|
126
|
+
readonly sarifReportPath: string;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Compute the full project score. Pure function — no side effects.
|
|
130
|
+
*
|
|
131
|
+
* @param input Aggregated inputs.
|
|
132
|
+
* @returns A {@link ProjectScore} ready to be serialized.
|
|
133
|
+
*/
|
|
134
|
+
export declare function computeProjectScore(input: ComputeProjectScoreInput): ProjectScore;
|
|
135
|
+
/**
|
|
136
|
+
* Render a project score as a compact Markdown summary suitable for
|
|
137
|
+
* display directly in a chat session. Keep it under ~30 lines so it
|
|
138
|
+
* does not dominate the conversation context.
|
|
139
|
+
*
|
|
140
|
+
* @param score The score to render.
|
|
141
|
+
*/
|
|
142
|
+
export declare function renderProjectScoreMarkdown(score: ProjectScore): string;
|
|
143
|
+
//# sourceMappingURL=score.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.d.ts","sourceRoot":"","sources":["../../src/metrics/score.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Aggregate project score engine.
|
|
3
|
+
*
|
|
4
|
+
* Given a fully resolved configuration, the live SARIF store, and a
|
|
5
|
+
* workspace LOC walker, this module produces a single immutable
|
|
6
|
+
* `ProjectScore` snapshot describing the entire project's quality
|
|
7
|
+
* posture across three dimensions:
|
|
8
|
+
*
|
|
9
|
+
* - **Maintainability** — derived from the Technical Debt Ratio (TDR)
|
|
10
|
+
* using the existing `metrics/tdr.ts` engine.
|
|
11
|
+
* - **Reliability** — derived from the worst non-security finding.
|
|
12
|
+
* - **Security** — derived from the worst security finding.
|
|
13
|
+
*
|
|
14
|
+
* Each dimension produces a letter grade A..E and the `overall` field
|
|
15
|
+
* collapses them by taking the worst grade. The `passes` field tells
|
|
16
|
+
* the caller whether the overall grade is within the configured
|
|
17
|
+
* `tdrMaxRating` tolerance — handy for the Stop quality gate and the
|
|
18
|
+
* `score_project` MCP tool.
|
|
19
|
+
*
|
|
20
|
+
* Security vs reliability is determined by a heuristic on the `ruleId`:
|
|
21
|
+
* any rule whose identifier matches a security keyword (`sec`, `sql`,
|
|
22
|
+
* `xss`, `csrf`, `injection`, `crypt`, `auth`, `secret`, `password`,
|
|
23
|
+
* `cve`, `vuln`) is treated as security. Everything else is reliability.
|
|
24
|
+
* This is intentionally coarse: adapters that stamp a richer SARIF
|
|
25
|
+
* taxonomy (e.g. `properties.tags = ["security"]`) could replace this
|
|
26
|
+
* classifier with an exact match, but the regex is sufficient for the
|
|
27
|
+
* scanners this plugin ships with.
|
|
28
|
+
*
|
|
29
|
+
* The score engine is pure: it does no I/O, takes a `WorkspaceStats`
|
|
30
|
+
* value (which the caller produces from the bounded LOC walker), and
|
|
31
|
+
* returns a brand new score object on every call. Tests can construct
|
|
32
|
+
* a `SarifStore` in memory and verify the boundaries directly.
|
|
33
|
+
*
|
|
34
|
+
* @module metrics/score
|
|
35
|
+
*/
|
|
36
|
+
import { classifyTdr, ratingIsWorseThan } from "./tdr.js";
|
|
37
|
+
/**
|
|
38
|
+
* Pattern that classifies a rule identifier as security-relevant.
|
|
39
|
+
* Matches case-insensitively against the rule id text. Intentionally
|
|
40
|
+
* permissive — false positives in classification are recoverable, but
|
|
41
|
+
* a missed security finding being graded as reliability is not.
|
|
42
|
+
*/
|
|
43
|
+
const SECURITY_RULE_PATTERN = /(sec|sql|xss|csrf|ssrf|injection|crypt|auth|secret|password|token|cve|vuln|jwt|cors|rce|deserial|prototype-pollution)/i;
|
|
44
|
+
/**
|
|
45
|
+
* Compute the full project score. Pure function — no side effects.
|
|
46
|
+
*
|
|
47
|
+
* @param input Aggregated inputs.
|
|
48
|
+
* @returns A {@link ProjectScore} ready to be serialized.
|
|
49
|
+
*/
|
|
50
|
+
export function computeProjectScore(input) {
|
|
51
|
+
const findingsList = input.sarifStore.list();
|
|
52
|
+
// ---- Findings summary ----
|
|
53
|
+
/** @type {Record<string, number>} */
|
|
54
|
+
const byTool = {};
|
|
55
|
+
const byFile = {};
|
|
56
|
+
let errorCount = 0;
|
|
57
|
+
let warningCount = 0;
|
|
58
|
+
let noteCount = 0;
|
|
59
|
+
let remediationMinutes = 0;
|
|
60
|
+
/** Findings split by classification. */
|
|
61
|
+
const securityFindings = [];
|
|
62
|
+
const reliabilityFindings = [];
|
|
63
|
+
for (const finding of findingsList) {
|
|
64
|
+
if (finding.level === "error")
|
|
65
|
+
errorCount += 1;
|
|
66
|
+
else if (finding.level === "warning")
|
|
67
|
+
warningCount += 1;
|
|
68
|
+
else if (finding.level === "note")
|
|
69
|
+
noteCount += 1;
|
|
70
|
+
byTool[finding.sourceTool] = (byTool[finding.sourceTool] ?? 0) + 1;
|
|
71
|
+
byFile[finding.location.uri] = (byFile[finding.location.uri] ?? 0) + 1;
|
|
72
|
+
const effort = typeof finding.properties?.effortMinutes === "number"
|
|
73
|
+
? finding.properties.effortMinutes
|
|
74
|
+
: 0;
|
|
75
|
+
remediationMinutes += effort;
|
|
76
|
+
if (SECURITY_RULE_PATTERN.test(finding.ruleId)) {
|
|
77
|
+
securityFindings.push({ level: finding.level });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
reliabilityFindings.push({ level: finding.level });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
const findings = {
|
|
84
|
+
total: findingsList.length,
|
|
85
|
+
error: errorCount,
|
|
86
|
+
warning: warningCount,
|
|
87
|
+
note: noteCount,
|
|
88
|
+
byTool,
|
|
89
|
+
byFile,
|
|
90
|
+
};
|
|
91
|
+
// ---- Maintainability (TDR) ----
|
|
92
|
+
// Guard against an empty workspace; the TDR formula divides by LOC.
|
|
93
|
+
const safeLoc = Math.max(input.workspace.physicalLoc, 1);
|
|
94
|
+
const developmentCostMinutes = input.minutesPerLoc * safeLoc;
|
|
95
|
+
const tdrPercent = (remediationMinutes / developmentCostMinutes) * 100;
|
|
96
|
+
const tdrRating = classifyTdr(tdrPercent);
|
|
97
|
+
const maintainability = {
|
|
98
|
+
rating: tdrRating,
|
|
99
|
+
tdrPercent: Number(tdrPercent.toFixed(4)),
|
|
100
|
+
remediationMinutes,
|
|
101
|
+
developmentCostMinutes,
|
|
102
|
+
};
|
|
103
|
+
// ---- Reliability and security dimensions ----
|
|
104
|
+
const reliability = scoreDimension(reliabilityFindings);
|
|
105
|
+
const security = scoreDimension(securityFindings);
|
|
106
|
+
// ---- Overall = the worst of the three ----
|
|
107
|
+
const overallRating = worstOf(maintainability.rating, reliability.rating, security.rating);
|
|
108
|
+
const passes = !ratingIsWorseThan(overallRating, input.tdrMaxRating);
|
|
109
|
+
return {
|
|
110
|
+
generatedAt: new Date().toISOString(),
|
|
111
|
+
workspaceRoot: input.workspaceRoot,
|
|
112
|
+
loc: { physical: input.workspace.physicalLoc, files: input.workspace.fileCount },
|
|
113
|
+
findings,
|
|
114
|
+
maintainability,
|
|
115
|
+
reliability,
|
|
116
|
+
security,
|
|
117
|
+
overall: {
|
|
118
|
+
rating: overallRating,
|
|
119
|
+
passes,
|
|
120
|
+
policyRating: input.tdrMaxRating,
|
|
121
|
+
},
|
|
122
|
+
location: {
|
|
123
|
+
dashboardUrl: input.dashboardUrl,
|
|
124
|
+
sarifReportPath: input.sarifReportPath,
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Score a single dimension (reliability or security) from its findings.
|
|
130
|
+
*
|
|
131
|
+
* The mapping is intentionally coarse and maps directly from SARIF
|
|
132
|
+
* levels to letter ratings:
|
|
133
|
+
*
|
|
134
|
+
* - 0 findings → A
|
|
135
|
+
* - only `note` findings → B
|
|
136
|
+
* - 1+ `warning`, 0 `error` → C
|
|
137
|
+
* - 1–2 `error` → D
|
|
138
|
+
* - 3+ `error` → E
|
|
139
|
+
*
|
|
140
|
+
* Projects that stamp explicit blocker / major / minor categories on
|
|
141
|
+
* their SARIF properties can wrap this function with their own
|
|
142
|
+
* taxonomy-aware classifier.
|
|
143
|
+
*
|
|
144
|
+
* @param findings Findings classified into this dimension.
|
|
145
|
+
*/
|
|
146
|
+
function scoreDimension(findings) {
|
|
147
|
+
let errorCount = 0;
|
|
148
|
+
let warningCount = 0;
|
|
149
|
+
let noteCount = 0;
|
|
150
|
+
for (const f of findings) {
|
|
151
|
+
if (f.level === "error")
|
|
152
|
+
errorCount += 1;
|
|
153
|
+
else if (f.level === "warning")
|
|
154
|
+
warningCount += 1;
|
|
155
|
+
else if (f.level === "note")
|
|
156
|
+
noteCount += 1;
|
|
157
|
+
}
|
|
158
|
+
let rating;
|
|
159
|
+
if (errorCount >= 3)
|
|
160
|
+
rating = "E";
|
|
161
|
+
else if (errorCount >= 1)
|
|
162
|
+
rating = "D";
|
|
163
|
+
else if (warningCount >= 1)
|
|
164
|
+
rating = "C";
|
|
165
|
+
else if (noteCount >= 1)
|
|
166
|
+
rating = "B";
|
|
167
|
+
else
|
|
168
|
+
rating = "A";
|
|
169
|
+
return {
|
|
170
|
+
rating,
|
|
171
|
+
findings: findings.length,
|
|
172
|
+
errorFindings: errorCount,
|
|
173
|
+
warningFindings: warningCount,
|
|
174
|
+
noteFindings: noteCount,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Return the worst (alphabetically highest) of an arbitrary number of
|
|
179
|
+
* letter ratings. Used to collapse the three dimension ratings into the
|
|
180
|
+
* overall project rating.
|
|
181
|
+
*
|
|
182
|
+
* @param ratings Two or more letter ratings.
|
|
183
|
+
* @returns The worst rating.
|
|
184
|
+
*/
|
|
185
|
+
function worstOf(...ratings) {
|
|
186
|
+
let worst = "A";
|
|
187
|
+
for (const r of ratings) {
|
|
188
|
+
if (ratingIsWorseThan(r, worst))
|
|
189
|
+
worst = r;
|
|
190
|
+
}
|
|
191
|
+
return worst;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Render a project score as a compact Markdown summary suitable for
|
|
195
|
+
* display directly in a chat session. Keep it under ~30 lines so it
|
|
196
|
+
* does not dominate the conversation context.
|
|
197
|
+
*
|
|
198
|
+
* @param score The score to render.
|
|
199
|
+
*/
|
|
200
|
+
export function renderProjectScoreMarkdown(score) {
|
|
201
|
+
const verdict = score.overall.passes ? "✅ passes policy" : "❌ FAILS policy";
|
|
202
|
+
const dashboardLine = score.location.dashboardUrl
|
|
203
|
+
? `📊 Dashboard: ${score.location.dashboardUrl}`
|
|
204
|
+
: `📊 Dashboard: <not running — start the MCP server to enable>`;
|
|
205
|
+
return [
|
|
206
|
+
`## claude-crap :: project score`,
|
|
207
|
+
``,
|
|
208
|
+
`**Overall: ${score.overall.rating}** (${verdict}, policy ceiling = ${score.overall.policyRating})`,
|
|
209
|
+
``,
|
|
210
|
+
`| Dimension | Rating | Detail |`,
|
|
211
|
+
`| --------------- | :----: | --------------------------------------------------- |`,
|
|
212
|
+
`| Maintainability | ${score.maintainability.rating} | TDR ${score.maintainability.tdrPercent}% (${score.maintainability.remediationMinutes} min over ${score.loc.physical} LOC) |`,
|
|
213
|
+
`| Reliability | ${score.reliability.rating} | ${score.reliability.errorFindings} error · ${score.reliability.warningFindings} warning · ${score.reliability.noteFindings} note |`,
|
|
214
|
+
`| Security | ${score.security.rating} | ${score.security.errorFindings} error · ${score.security.warningFindings} warning · ${score.security.noteFindings} note |`,
|
|
215
|
+
``,
|
|
216
|
+
`Workspace: **${score.loc.physical} LOC** across **${score.loc.files} files**`,
|
|
217
|
+
`Findings: **${score.findings.total} total** (${score.findings.error} error · ${score.findings.warning} warning · ${score.findings.note} note)`,
|
|
218
|
+
`Tools: ${Object.keys(score.findings.byTool).join(", ") || "<none ingested>"}`,
|
|
219
|
+
``,
|
|
220
|
+
dashboardLine,
|
|
221
|
+
`📄 Report: ${score.location.sarifReportPath}`,
|
|
222
|
+
].join("\n");
|
|
223
|
+
}
|
|
224
|
+
//# sourceMappingURL=score.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score.js","sourceRoot":"","sources":["../../src/metrics/score.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAIH,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAiG1D;;;;;GAKG;AACH,MAAM,qBAAqB,GACzB,wHAAwH,CAAC;AAE3H;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA+B;IACjE,MAAM,YAAY,GAAG,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;IAE7C,6BAA6B;IAC7B,qCAAqC;IACrC,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,MAAM,MAAM,GAA2B,EAAE,CAAC;IAC1C,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAE3B,wCAAwC;IACxC,MAAM,gBAAgB,GAA6B,EAAE,CAAC;IACtD,MAAM,mBAAmB,GAA6B,EAAE,CAAC;IAEzD,KAAK,MAAM,OAAO,IAAI,YAAY,EAAE,CAAC;QACnC,IAAI,OAAO,CAAC,KAAK,KAAK,OAAO;YAAE,UAAU,IAAI,CAAC,CAAC;aAC1C,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS;YAAE,YAAY,IAAI,CAAC,CAAC;aACnD,IAAI,OAAO,CAAC,KAAK,KAAK,MAAM;YAAE,SAAS,IAAI,CAAC,CAAC;QAElD,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QACnE,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;QAEvE,MAAM,MAAM,GACV,OAAO,OAAO,CAAC,UAAU,EAAE,aAAa,KAAK,QAAQ;YACnD,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,aAAa;YAClC,CAAC,CAAC,CAAC,CAAC;QACR,kBAAkB,IAAI,MAAM,CAAC;QAE7B,IAAI,qBAAqB,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC/C,gBAAgB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QAClD,CAAC;aAAM,CAAC;YACN,mBAAmB,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACrD,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAoB;QAChC,KAAK,EAAE,YAAY,CAAC,MAAM;QAC1B,KAAK,EAAE,UAAU;QACjB,OAAO,EAAE,YAAY;QACrB,IAAI,EAAE,SAAS;QACf,MAAM;QACN,MAAM;KACP,CAAC;IAEF,kCAAkC;IAClC,oEAAoE;IACpE,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;IACzD,MAAM,sBAAsB,GAAG,KAAK,CAAC,aAAa,GAAG,OAAO,CAAC;IAC7D,MAAM,UAAU,GAAG,CAAC,kBAAkB,GAAG,sBAAsB,CAAC,GAAG,GAAG,CAAC;IACvE,MAAM,SAAS,GAAG,WAAW,CAAC,UAAU,CAAC,CAAC;IAE1C,MAAM,eAAe,GAAyB;QAC5C,MAAM,EAAE,SAAS;QACjB,UAAU,EAAE,MAAM,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;QACzC,kBAAkB;QAClB,sBAAsB;KACvB,CAAC;IAEF,gDAAgD;IAChD,MAAM,WAAW,GAAG,cAAc,CAAC,mBAAmB,CAAC,CAAC;IACxD,MAAM,QAAQ,GAAG,cAAc,CAAC,gBAAgB,CAAC,CAAC;IAElD,6CAA6C;IAC7C,MAAM,aAAa,GAAG,OAAO,CAAC,eAAe,CAAC,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC3F,MAAM,MAAM,GAAG,CAAC,iBAAiB,CAAC,aAAa,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAErE,OAAO;QACL,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACrC,aAAa,EAAE,KAAK,CAAC,aAAa;QAClC,GAAG,EAAE,EAAE,QAAQ,EAAE,KAAK,CAAC,SAAS,CAAC,WAAW,EAAE,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE;QAChF,QAAQ;QACR,eAAe;QACf,WAAW;QACX,QAAQ;QACR,OAAO,EAAE;YACP,MAAM,EAAE,aAAa;YACrB,MAAM;YACN,YAAY,EAAE,KAAK,CAAC,YAAY;SACjC;QACD,QAAQ,EAAE;YACR,YAAY,EAAE,KAAK,CAAC,YAAY;YAChC,eAAe,EAAE,KAAK,CAAC,eAAe;SACvC;KACF,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,SAAS,cAAc,CAAC,QAA0C;IAChE,IAAI,UAAU,GAAG,CAAC,CAAC;IACnB,IAAI,YAAY,GAAG,CAAC,CAAC;IACrB,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,KAAK,MAAM,CAAC,IAAI,QAAQ,EAAE,CAAC;QACzB,IAAI,CAAC,CAAC,KAAK,KAAK,OAAO;YAAE,UAAU,IAAI,CAAC,CAAC;aACpC,IAAI,CAAC,CAAC,KAAK,KAAK,SAAS;YAAE,YAAY,IAAI,CAAC,CAAC;aAC7C,IAAI,CAAC,CAAC,KAAK,KAAK,MAAM;YAAE,SAAS,IAAI,CAAC,CAAC;IAC9C,CAAC;IACD,IAAI,MAAsB,CAAC;IAC3B,IAAI,UAAU,IAAI,CAAC;QAAE,MAAM,GAAG,GAAG,CAAC;SAC7B,IAAI,UAAU,IAAI,CAAC;QAAE,MAAM,GAAG,GAAG,CAAC;SAClC,IAAI,YAAY,IAAI,CAAC;QAAE,MAAM,GAAG,GAAG,CAAC;SACpC,IAAI,SAAS,IAAI,CAAC;QAAE,MAAM,GAAG,GAAG,CAAC;;QACjC,MAAM,GAAG,GAAG,CAAC;IAElB,OAAO;QACL,MAAM;QACN,QAAQ,EAAE,QAAQ,CAAC,MAAM;QACzB,aAAa,EAAE,UAAU;QACzB,eAAe,EAAE,YAAY;QAC7B,YAAY,EAAE,SAAS;KACxB,CAAC;AACJ,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,OAAO,CAAC,GAAG,OAAsC;IACxD,IAAI,KAAK,GAAmB,GAAG,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;QACxB,IAAI,iBAAiB,CAAC,CAAC,EAAE,KAAK,CAAC;YAAE,KAAK,GAAG,CAAC,CAAC;IAC7C,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,0BAA0B,CAAC,KAAmB;IAC5D,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,iBAAiB,CAAC,CAAC,CAAC,gBAAgB,CAAC;IAC5E,MAAM,aAAa,GAAG,KAAK,CAAC,QAAQ,CAAC,YAAY;QAC/C,CAAC,CAAC,mBAAmB,KAAK,CAAC,QAAQ,CAAC,YAAY,EAAE;QAClD,CAAC,CAAC,gEAAgE,CAAC;IAErE,OAAO;QACL,iCAAiC;QACjC,EAAE;QACF,cAAc,KAAK,CAAC,OAAO,CAAC,MAAM,OAAO,OAAO,sBAAsB,KAAK,CAAC,OAAO,CAAC,YAAY,GAAG;QACnG,EAAE;QACF,oFAAoF;QACpF,oFAAoF;QACpF,yBAAyB,KAAK,CAAC,eAAe,CAAC,MAAM,aAAa,KAAK,CAAC,eAAe,CAAC,UAAU,MAAM,KAAK,CAAC,eAAe,CAAC,kBAAkB,aAAa,KAAK,CAAC,GAAG,CAAC,QAAQ,SAAS;QACxL,yBAAyB,KAAK,CAAC,WAAW,CAAC,MAAM,SAAS,KAAK,CAAC,WAAW,CAAC,aAAa,YAAY,KAAK,CAAC,WAAW,CAAC,eAAe,cAAc,KAAK,CAAC,WAAW,CAAC,YAAY,SAAS;QAC3L,yBAAyB,KAAK,CAAC,QAAQ,CAAC,MAAM,SAAS,KAAK,CAAC,QAAQ,CAAC,aAAa,YAAY,KAAK,CAAC,QAAQ,CAAC,eAAe,cAAc,KAAK,CAAC,QAAQ,CAAC,YAAY,SAAS;QAC/K,EAAE;QACF,gBAAgB,KAAK,CAAC,GAAG,CAAC,QAAQ,mBAAmB,KAAK,CAAC,GAAG,CAAC,KAAK,UAAU;QAC9E,gBAAgB,KAAK,CAAC,QAAQ,CAAC,KAAK,aAAa,KAAK,CAAC,QAAQ,CAAC,KAAK,YAAY,KAAK,CAAC,QAAQ,CAAC,OAAO,cAAc,KAAK,CAAC,QAAQ,CAAC,IAAI,QAAQ;QAChJ,cAAc,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,iBAAiB,EAAE;QAClF,EAAE;QACF,aAAa;QACb,mBAAmB,KAAK,CAAC,QAAQ,CAAC,eAAe,EAAE;KACpD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|