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
package/plugin/CLAUDE.md
ADDED
|
@@ -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
|
+
·
|
|
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>
|