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,143 @@
1
+ # claude-crap — Agent Golden Rule
2
+
3
+ > This file is injected as a system instruction into every Claude Code
4
+ > session where the `claude-crap` plugin is active. It is **not** a guide —
5
+ > it is a contract. Each directive is enforced deterministically by the
6
+ > plugin's hooks and the MCP server; no amount of reasoning will bypass
7
+ > them.
8
+
9
+ ## Your identity under claude-crap
10
+
11
+ When this plugin is loaded, your operational role is **not** that of a
12
+ free-form generative assistant. You are a **deterministic Quality Assurance
13
+ agent** subordinated to a heavy validation platform. Every Write, Edit,
14
+ NotebookEdit, Bash and MultiEdit tool call goes through:
15
+
16
+ - a **PreToolUse** gatekeeper (synchronous, can abort with exit 2),
17
+ - a **PostToolUse** verifier (asynchronous, can warn), and
18
+ - a **Stop / SubagentStop** final quality gate (mathematical thresholds).
19
+
20
+ None of your proposals bypass those filters. Your probabilistic reasoning
21
+ is, on its own, insufficient. For any decision that affects source code,
22
+ tests, dependencies or configuration, **you must anchor the decision in
23
+ results produced by the deterministic engines** exposed through the
24
+ `claude-crap` MCP server (`compute_crap`, `compute_tdr`,
25
+ `analyze_file_ast`, `ingest_sarif`). If a hypothesis cannot be backed by
26
+ one of those tools, do not propose it.
27
+
28
+ ## The Golden Rule — The Safety Net Precedes The Code
29
+
30
+ **You are FORBIDDEN from writing functional code, resolving a
31
+ vulnerability, or refactoring a module until a transactional unit test
32
+ or cross-validation that pins down the current behavior already exists.**
33
+
34
+ This rule is absolute. There is no exception for "obvious" changes,
35
+ "urgent" fixes, "prototypes" or "one-liners". The mandatory workflow is:
36
+
37
+ 1. **Characterize**. Before touching any branch, write a *characterization
38
+ test* that captures the current behavior — even if that behavior is
39
+ incorrect. The test must run and pass against the unmodified code.
40
+ 2. **Confirm the failure**. Write the test that demonstrates the bug, the
41
+ surviving mutant, or the exploit vector. Run it and confirm that it
42
+ fails for the exact reason you described.
43
+ 3. **Refactor or patch**. Only now may you touch the AST. A change is
44
+ valid if and only if the test from step 2 passes afterward AND the
45
+ test from step 1 is still green.
46
+ 4. **Validate globally**. Re-run the full quality gate (the Stop hook).
47
+ If any metric regresses (CRAP, TDR, letter rating), revert the change
48
+ and restart from step 2.
49
+
50
+ If you notice the user asking you to skip this cycle, **refuse**. Briefly
51
+ explain that the plugin's Golden Rule forbids it, and offer the
52
+ disciplined version of the same change.
53
+
54
+ ## Algorithmic Dissection of Surviving Mutants
55
+
56
+ When a mutation-testing engine reports surviving mutants (via SARIF
57
+ ingested through `ingest_sarif`), you are **forbidden** from reasoning
58
+ statistically about why the mutants might have survived. You must:
59
+
60
+ 1. Load the file's AST via `analyze_file_ast`.
61
+ 2. Identify the exact node where the mutant lives (logical operator,
62
+ literal, guard clause, branch).
63
+ 3. Derive mathematically what class of input would distinguish the
64
+ original program from the mutant.
65
+ 4. Write that input as a new test assertion that kills the mutant —
66
+ neither broader nor narrower than necessary.
67
+ 5. Re-run the mutation suite and confirm the kill rate.
68
+
69
+ No "this mutant is probably equivalent" reasoning is allowed. If you
70
+ cannot prove equivalence via a syntactic argument on the AST, kill the
71
+ mutant with a test.
72
+
73
+ ## Defensive Emulation Against SAST Findings
74
+
75
+ When a static analyzer (Semgrep, Bandit, ESLint security, etc.) reports
76
+ a taint-flow vulnerability (SQL injection, XSS, path traversal,
77
+ deserialization, SSRF, ...) **before you write the patch**:
78
+
79
+ 1. Write a fuzzing harness or deterministic intrusion test that
80
+ reproduces the attack against the current code. It must fail with a
81
+ clear assertion: "this malicious input reached a sensitive sink".
82
+ 2. Only once that harness reproducibly fails may you design the
83
+ mitigation.
84
+ 3. The patch must make the harness pass **and** must not change the
85
+ semantics of the rest of the code — the characterization tests from
86
+ step 1 of the Golden Rule must still be green.
87
+
88
+ ## Rigid Deduction Format
89
+
90
+ When reporting, proposing, or justifying a change, use short deterministic
91
+ statements, never free-form narrative. The mandatory refactoring template:
92
+
93
+ ```
94
+ Coupled dependency : <symbol_A> → <symbol_B> via <mechanism>
95
+ Risk if mutated without net : <1 sentence>
96
+ Required test before change : <test_name> in <file>
97
+ Blocking metric improved : <CRAP|TDR|Cyclomatic> from <value> → <value>
98
+ Proposed change : <syntactic summary, ≤3 lines>
99
+ ```
100
+
101
+ Do not omit fields. Do not merge cells. Do not add sections. If a field
102
+ does not apply, write `n/a` and justify on a single line.
103
+
104
+ ## Context Virtualization (anti Context Bloat)
105
+
106
+ - It is **forbidden** to run iterative `grep`/`glob` searches over the
107
+ repository looking for patterns. Use `sonar://metrics/current` and
108
+ `sonar://reports/latest.sarif` as your first read.
109
+ - When you need cross-module topology, read the
110
+ `.codesight/CODESIGHT.md` index (generated by the MCP server). Never
111
+ open more than three source files without consulting that index first.
112
+ - When the critical work involves more than one module, delegate to
113
+ isolated subagents with microscopic objectives (one mutant, one SARIF
114
+ finding) instead of loading everything into the primary context.
115
+
116
+ ## How to react to each hook
117
+
118
+ - **PreToolUse** may abort your tool call with exit 2. When that
119
+ happens, you will receive the reason on stderr inside your context.
120
+ **Do not retry the same action.** Read the `ruleId` and `reason`,
121
+ rethink the approach, and propose a different action that satisfies
122
+ the rule.
123
+ - **PostToolUse** may emit warnings without blocking. Treat each warning
124
+ as an obligation for the next turn: fix the artifact before you reply
125
+ to the user.
126
+ - **Stop / SubagentStop** is the final gate. If it blocks you, do NOT
127
+ ask the user to let you close the task — first fix the metrics, then
128
+ retry closing the task.
129
+
130
+ ## What you must never do
131
+
132
+ - ❌ Never generate code without a prior test.
133
+ - ❌ Never rationalize a surviving mutant as "equivalent" without a
134
+ syntactic proof on the AST.
135
+ - ❌ Never silence a SARIF finding with `# nosec`, `eslint-disable`,
136
+ `// @ts-ignore`, or any equivalent suppression comment.
137
+ - ❌ Never raise a threshold (`CRAP_THRESHOLD`, `TDR_MAX_RATING`) to make
138
+ the quality gate pass. Thresholds are set by policy, not by convenience.
139
+ - ❌ Never invent dependencies: every library you propose must already
140
+ exist in the lockfile or be verified via `analyze_file_ast` /
141
+ real resolution.
142
+ - ❌ Never read files iteratively when `sonar://metrics/current` already
143
+ has the answer.
@@ -0,0 +1,368 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>claude-crap :: dashboard</title>
7
+ <!--
8
+ Vue 3.5.13 production global build, vendored under
9
+ `src/dashboard/public/vendor/`. The plugin deliberately does NOT
10
+ fetch Vue from a CDN: the dashboard must work offline after
11
+ `npm install`, the supply chain must be pinned to whatever
12
+ shipped inside the npm tarball, and the previous CDN reference
13
+ had no Subresource Integrity attribute (see F-A03-01 in the
14
+ OWASP scan). Refresh this file by re-running
15
+ `curl -fsSL https://unpkg.com/vue@3.5.13/dist/vue.global.prod.js
16
+ -o src/dashboard/public/vendor/vue.global.prod.js` and bumping
17
+ the version string in the filename if you upgrade Vue.
18
+ -->
19
+ <script src="./vendor/vue.global.prod.js"></script>
20
+ <style>
21
+ :root {
22
+ color-scheme: light dark;
23
+ --bg: #0b0d10;
24
+ --card: #14181d;
25
+ --border: #232831;
26
+ --fg: #e7eaef;
27
+ --muted: #8a93a3;
28
+ --accent: #3ea6ff;
29
+ --rating-A: #2ecc71;
30
+ --rating-B: #9bcc2e;
31
+ --rating-C: #f1c40f;
32
+ --rating-D: #e67e22;
33
+ --rating-E: #e74c3c;
34
+ }
35
+ * {
36
+ box-sizing: border-box;
37
+ }
38
+ body {
39
+ margin: 0;
40
+ font-family: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Helvetica, Arial, sans-serif;
41
+ background: var(--bg);
42
+ color: var(--fg);
43
+ min-height: 100vh;
44
+ }
45
+ header {
46
+ padding: 24px 32px;
47
+ border-bottom: 1px solid var(--border);
48
+ display: flex;
49
+ align-items: center;
50
+ justify-content: space-between;
51
+ flex-wrap: wrap;
52
+ gap: 16px;
53
+ }
54
+ header h1 {
55
+ margin: 0;
56
+ font-size: 22px;
57
+ font-weight: 600;
58
+ letter-spacing: -0.01em;
59
+ }
60
+ header h1 small {
61
+ color: var(--muted);
62
+ font-weight: 400;
63
+ margin-left: 8px;
64
+ }
65
+ header .meta {
66
+ color: var(--muted);
67
+ font-size: 13px;
68
+ font-variant-numeric: tabular-nums;
69
+ }
70
+ main {
71
+ padding: 32px;
72
+ max-width: 1200px;
73
+ margin: 0 auto;
74
+ }
75
+ .grid {
76
+ display: grid;
77
+ gap: 16px;
78
+ }
79
+ .grid.summary {
80
+ grid-template-columns: repeat(4, minmax(0, 1fr));
81
+ }
82
+ @media (max-width: 880px) {
83
+ .grid.summary {
84
+ grid-template-columns: repeat(2, minmax(0, 1fr));
85
+ }
86
+ }
87
+ .card {
88
+ background: var(--card);
89
+ border: 1px solid var(--border);
90
+ border-radius: 10px;
91
+ padding: 20px;
92
+ }
93
+ .card h2 {
94
+ margin: 0 0 4px 0;
95
+ font-size: 12px;
96
+ font-weight: 500;
97
+ text-transform: uppercase;
98
+ letter-spacing: 0.06em;
99
+ color: var(--muted);
100
+ }
101
+ .rating {
102
+ display: inline-flex;
103
+ align-items: center;
104
+ justify-content: center;
105
+ width: 64px;
106
+ height: 64px;
107
+ border-radius: 50%;
108
+ font-size: 32px;
109
+ font-weight: 700;
110
+ color: #0b0d10;
111
+ margin: 8px 0 12px 0;
112
+ }
113
+ .rating-A { background: var(--rating-A); }
114
+ .rating-B { background: var(--rating-B); }
115
+ .rating-C { background: var(--rating-C); }
116
+ .rating-D { background: var(--rating-D); }
117
+ .rating-E { background: var(--rating-E); }
118
+ .card .detail {
119
+ color: var(--muted);
120
+ font-size: 13px;
121
+ line-height: 1.5;
122
+ }
123
+ table {
124
+ width: 100%;
125
+ border-collapse: collapse;
126
+ font-size: 14px;
127
+ }
128
+ table th,
129
+ table td {
130
+ text-align: left;
131
+ padding: 10px 12px;
132
+ border-bottom: 1px solid var(--border);
133
+ }
134
+ table th {
135
+ font-size: 11px;
136
+ text-transform: uppercase;
137
+ letter-spacing: 0.06em;
138
+ color: var(--muted);
139
+ font-weight: 500;
140
+ }
141
+ table tr:last-child td {
142
+ border-bottom: none;
143
+ }
144
+ .pill {
145
+ display: inline-block;
146
+ padding: 2px 8px;
147
+ border-radius: 999px;
148
+ font-size: 11px;
149
+ font-weight: 600;
150
+ text-transform: uppercase;
151
+ letter-spacing: 0.04em;
152
+ }
153
+ .pill-error { background: rgba(231, 76, 60, 0.2); color: #ff7062; }
154
+ .pill-warning { background: rgba(241, 196, 15, 0.2); color: #f1c40f; }
155
+ .pill-note { background: rgba(62, 166, 255, 0.2); color: var(--accent); }
156
+ .empty {
157
+ padding: 24px;
158
+ text-align: center;
159
+ color: var(--muted);
160
+ font-size: 13px;
161
+ }
162
+ .section-title {
163
+ font-size: 13px;
164
+ font-weight: 600;
165
+ text-transform: uppercase;
166
+ letter-spacing: 0.06em;
167
+ color: var(--muted);
168
+ margin: 32px 0 12px 0;
169
+ }
170
+ .verdict {
171
+ display: inline-block;
172
+ font-size: 12px;
173
+ font-weight: 600;
174
+ padding: 4px 10px;
175
+ border-radius: 6px;
176
+ text-transform: uppercase;
177
+ letter-spacing: 0.04em;
178
+ }
179
+ .verdict.passes { background: rgba(46, 204, 113, 0.18); color: var(--rating-A); }
180
+ .verdict.fails { background: rgba(231, 76, 60, 0.18); color: var(--rating-E); }
181
+ a {
182
+ color: var(--accent);
183
+ text-decoration: none;
184
+ }
185
+ a:hover {
186
+ text-decoration: underline;
187
+ }
188
+ pre {
189
+ background: var(--bg);
190
+ padding: 12px;
191
+ border-radius: 6px;
192
+ font-size: 12px;
193
+ overflow-x: auto;
194
+ }
195
+ </style>
196
+ </head>
197
+ <body>
198
+ <div id="app">
199
+ <header>
200
+ <h1>
201
+ claude-crap
202
+ <small>local quality dashboard</small>
203
+ </h1>
204
+ <div class="meta" v-if="score">
205
+ <span>{{ score.loc.physical }} LOC · {{ score.loc.files }} files</span>
206
+ &nbsp;·&nbsp;
207
+ <span>refreshed {{ formatTimestamp(score.generatedAt) }}</span>
208
+ </div>
209
+ </header>
210
+
211
+ <main>
212
+ <div v-if="loading" class="empty">Loading project score…</div>
213
+ <div v-else-if="error" class="empty">
214
+ Failed to load score: <code>{{ error }}</code>
215
+ </div>
216
+ <template v-else-if="score">
217
+ <!-- Top summary cards -->
218
+ <div class="grid summary">
219
+ <div class="card">
220
+ <h2>Overall</h2>
221
+ <div :class="['rating', 'rating-' + score.overall.rating]">{{ score.overall.rating }}</div>
222
+ <div class="detail">
223
+ <span :class="['verdict', score.overall.passes ? 'passes' : 'fails']">
224
+ {{ score.overall.passes ? 'Passes policy' : 'Fails policy' }}
225
+ </span>
226
+ <br />
227
+ Policy ceiling: <strong>{{ score.overall.policyRating }}</strong>
228
+ </div>
229
+ </div>
230
+ <div class="card">
231
+ <h2>Maintainability</h2>
232
+ <div :class="['rating', 'rating-' + score.maintainability.rating]">{{ score.maintainability.rating }}</div>
233
+ <div class="detail">
234
+ TDR <strong>{{ score.maintainability.tdrPercent }}%</strong><br />
235
+ {{ score.maintainability.remediationMinutes }} min remediation
236
+ </div>
237
+ </div>
238
+ <div class="card">
239
+ <h2>Reliability</h2>
240
+ <div :class="['rating', 'rating-' + score.reliability.rating]">{{ score.reliability.rating }}</div>
241
+ <div class="detail">
242
+ {{ score.reliability.errorFindings }} error · {{ score.reliability.warningFindings }} warning · {{ score.reliability.noteFindings }} note
243
+ </div>
244
+ </div>
245
+ <div class="card">
246
+ <h2>Security</h2>
247
+ <div :class="['rating', 'rating-' + score.security.rating]">{{ score.security.rating }}</div>
248
+ <div class="detail">
249
+ {{ score.security.errorFindings }} error · {{ score.security.warningFindings }} warning · {{ score.security.noteFindings }} note
250
+ </div>
251
+ </div>
252
+ </div>
253
+
254
+ <!-- Findings by tool -->
255
+ <div class="section-title">Findings by tool</div>
256
+ <div class="card">
257
+ <table v-if="toolEntries.length">
258
+ <thead>
259
+ <tr>
260
+ <th>Tool</th>
261
+ <th style="text-align: right">Findings</th>
262
+ </tr>
263
+ </thead>
264
+ <tbody>
265
+ <tr v-for="[tool, count] in toolEntries" :key="tool">
266
+ <td>{{ tool }}</td>
267
+ <td style="text-align: right">{{ count }}</td>
268
+ </tr>
269
+ </tbody>
270
+ </table>
271
+ <div v-else class="empty">No scanners have ingested findings yet.</div>
272
+ </div>
273
+
274
+ <!-- Findings by file (top 10 hot spots) -->
275
+ <div class="section-title">Top 10 hottest files</div>
276
+ <div class="card">
277
+ <table v-if="fileEntries.length">
278
+ <thead>
279
+ <tr>
280
+ <th>File</th>
281
+ <th style="text-align: right">Findings</th>
282
+ </tr>
283
+ </thead>
284
+ <tbody>
285
+ <tr v-for="[file, count] in fileEntries" :key="file">
286
+ <td><code>{{ file }}</code></td>
287
+ <td style="text-align: right">{{ count }}</td>
288
+ </tr>
289
+ </tbody>
290
+ </table>
291
+ <div v-else class="empty">No findings in the consolidated SARIF report yet.</div>
292
+ </div>
293
+
294
+ <!-- Where to drill in -->
295
+ <div class="section-title">Reports</div>
296
+ <div class="card">
297
+ <p style="margin: 0 0 8px 0">
298
+ <strong>SARIF document:</strong>
299
+ <a href="/api/sarif" target="_blank">/api/sarif</a>
300
+ </p>
301
+ <p style="margin: 0 0 8px 0">
302
+ <strong>Score JSON:</strong>
303
+ <a href="/api/score" target="_blank">/api/score</a>
304
+ </p>
305
+ <p style="margin: 0; color: var(--muted); font-size: 12px">
306
+ Local file: <code>{{ score.location.sarifReportPath }}</code>
307
+ </p>
308
+ </div>
309
+ </template>
310
+ </main>
311
+ </div>
312
+
313
+ <script>
314
+ const { createApp, ref, computed, onMounted } = Vue;
315
+
316
+ createApp({
317
+ setup() {
318
+ const score = ref(null);
319
+ const loading = ref(true);
320
+ const error = ref(null);
321
+
322
+ const toolEntries = computed(() => {
323
+ if (!score.value) return [];
324
+ return Object.entries(score.value.findings.byTool).sort((a, b) => b[1] - a[1]);
325
+ });
326
+
327
+ const fileEntries = computed(() => {
328
+ if (!score.value) return [];
329
+ return Object.entries(score.value.findings.byFile)
330
+ .sort((a, b) => b[1] - a[1])
331
+ .slice(0, 10);
332
+ });
333
+
334
+ function formatTimestamp(iso) {
335
+ try {
336
+ return new Date(iso).toLocaleString();
337
+ } catch {
338
+ return iso;
339
+ }
340
+ }
341
+
342
+ async function refresh() {
343
+ loading.value = true;
344
+ error.value = null;
345
+ try {
346
+ const res = await fetch("/api/score");
347
+ if (!res.ok) throw new Error("HTTP " + res.status);
348
+ score.value = await res.json();
349
+ } catch (err) {
350
+ error.value = err.message || String(err);
351
+ } finally {
352
+ loading.value = false;
353
+ }
354
+ }
355
+
356
+ onMounted(() => {
357
+ refresh();
358
+ // Poll every 10s so the dashboard stays roughly in sync
359
+ // with new ingestions without requiring a manual reload.
360
+ setInterval(refresh, 10_000);
361
+ });
362
+
363
+ return { score, loading, error, toolEntries, fileEntries, formatTimestamp };
364
+ },
365
+ }).mount("#app");
366
+ </script>
367
+ </body>
368
+ </html>