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
package/README.md ADDED
@@ -0,0 +1,550 @@
1
+ # claude-crap
2
+
3
+ [![npm version](https://img.shields.io/npm/v/claude-crap.svg)](https://www.npmjs.com/package/claude-crap)
4
+ [![CI](https://github.com/ahernandez-developer/claude-crap/actions/workflows/ci.yml/badge.svg)](https://github.com/ahernandez-developer/claude-crap/actions/workflows/ci.yml)
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org/)
6
+ [![Bun](https://img.shields.io/badge/bun-%3E%3D1-black.svg)](https://bun.sh/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE)
8
+
9
+ > **Deterministic Quality Assurance plugin for Claude Code.**
10
+ > Forces the agent through mathematical hooks, CRAP / TDR thresholds,
11
+ > and SARIF 2.1.0 reports before a single line of code is allowed to
12
+ > ship.
13
+
14
+ `claude-crap` turns Claude Code into a disciplined QA engineer. It
15
+ sits underneath your existing Claude Code session as a plugin and
16
+ wraps every `Write`, `Edit`, and `Bash` call with deterministic
17
+ validation: a synchronous **PreToolUse gatekeeper**, a retrospective
18
+ **PostToolUse verifier**, and a final **Stop quality gate** that
19
+ refuses to close a task until the project's maintainability,
20
+ reliability, and security ratings are inside the configured policy.
21
+
22
+ The agent's probabilistic reasoning is never trusted by itself. Every
23
+ decision that touches source code, tests, or configuration must be
24
+ backed by a result from one of the deterministic engines exposed by
25
+ the plugin's MCP server — `compute_crap`, `compute_tdr`,
26
+ `analyze_file_ast`, `ingest_sarif`, `ingest_scanner_output`,
27
+ `require_test_harness`, and `score_project`.
28
+
29
+ This is the **Fat Platform / Thin Agent** thesis: the LLM is an
30
+ efficient worker, but the rails are mathematical, unforgiving, and
31
+ outside the model's reach.
32
+
33
+ > **CRAP** stands for **Change Risk Anti-Patterns** — a mildly offensive
34
+ > acronym to protect you from deeply offensive code. The metric was
35
+ > originally developed by Alberto Savoia and Bob Evans at Google in 2007.
36
+ > Read the original post:
37
+ > [This Code is CRAP](https://testing.googleblog.com/2011/02/this-code-is-crap.html).
38
+
39
+ [Quick Start](#quick-start) • [Configuration](#configuration) • [Documentation](#documentation) • [How It Works](#how-it-works) • [MCP Tools](#mcp-tools) • [System Requirements](#system-requirements) • [Development](#development) • [Bug Reports](#bug-reports) • [Contributing](#contributing)
40
+
41
+ ---
42
+
43
+ ## Quick Start
44
+
45
+ `claude-crap` ships as a single npm package. One command prepares
46
+ the workspace and prints the Claude Code slash command you need to
47
+ run next:
48
+
49
+ ```bash
50
+ npx claude-crap install
51
+ ```
52
+
53
+ `npx` downloads the package, the `postinstall` step compiles `dist/`
54
+ from source, and then `claude-crap install` creates
55
+ `.claude-crap/reports/` in the current project, marks every hook
56
+ script executable, and prints the exact Claude Code command to
57
+ register the plugin:
58
+
59
+ ```
60
+ ✓ claude-crap is ready to register with Claude Code.
61
+
62
+ Plugin root: /.../claude-crap
63
+
64
+ Next steps — pick ONE of the following:
65
+
66
+ 1. Native Claude Code install from this directory:
67
+ /plugin install /.../claude-crap
68
+
69
+ 2. Marketplace install (Claude Code pulls the published npm tarball):
70
+ /plugin marketplace add https://github.com/ahernandez-developer/claude-crap
71
+ /plugin install claude-crap@herz
72
+ ```
73
+
74
+ Once Claude Code reports the plugin as active, open any new session
75
+ in your workspace. The **SessionStart** hook will print a one-line
76
+ briefing showing the plugin version, the active thresholds, and the
77
+ local dashboard URL. From that point on the PreToolUse gatekeeper
78
+ runs on every tool call and the Stop quality gate runs on every
79
+ task close — no further setup required.
80
+
81
+ > **Two install channels are live:**
82
+ >
83
+ > - **npm** — `npx claude-crap install` (direct, works anywhere `npx` does)
84
+ > - **Claude Code marketplace** — `/plugin marketplace add https://github.com/ahernandez-developer/claude-crap` followed by `/plugin install claude-crap@herz`. Claude Code resolves the marketplace entry's `source` to `claude-crap@0.1.0` on the npm registry, so both routes unpack the **same tarball** and get the same SHA.
85
+
86
+ ---
87
+
88
+ ## Configuration
89
+
90
+ > **Default: `strict`.** You don't need to create a config file or
91
+ > set any environment variables. A fresh install hard-fails the Stop
92
+ > quality gate on any policy violation — same behavior the plugin
93
+ > has always had. The rest of this section only matters if you want
94
+ > to loosen that enforcement while adopting the plugin gradually.
95
+
96
+ The `strictness` value controls how the **Stop quality gate** and
97
+ the **`score_project` MCP tool** react when a policy fails. The
98
+ PreToolUse security gatekeeper (blocked paths, destructive Bash,
99
+ hardcoded secrets) is **always** strict regardless of this setting —
100
+ security is not a quality gradient.
101
+
102
+ | Mode | Stop hook exit | Verdict sink | Agent experience |
103
+ | :----------- | :------------: | :----------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------- |
104
+ | `strict` | `2` | **stderr** | The full `BLOCKED` box is injected into the agent's context. The task cannot close until the rules are satisfied. **Default — nothing to configure.** |
105
+ | `warn` | `0` | **stdout** | The full `WARNING` box lands in the hook transcript so the agent still sees every failing rule, but the task is allowed to close. Agent can remediate voluntarily. |
106
+ | `advisory` | `0` | **stdout** | A single-line `ADVISORY` note is emitted. Minimal pressure on the agent — the task closes with a soft nudge only. |
107
+
108
+ ### How to override the default
109
+
110
+ Teams adopting the plugin on an existing codebase can dial the
111
+ default back with a single file at the workspace root:
112
+
113
+ ```jsonc
114
+ // .claude-crap.json — commit this to git for team-wide policy
115
+ {
116
+ "$schema": "https://raw.githubusercontent.com/ahernandez-developer/claude-crap/main/schemas/crap-config.json",
117
+ "strictness": "warn" // "strict" | "warn" | "advisory"
118
+ }
119
+ ```
120
+
121
+ Or override for a single session from the shell:
122
+
123
+ ```bash
124
+ CLAUDE_CRAP_STRICTNESS=advisory claude
125
+ ```
126
+
127
+ **Precedence** (most specific wins):
128
+
129
+ 1. `CLAUDE_CRAP_STRICTNESS` environment variable — session-level
130
+ override. Useful for a one-off lenient run without editing the
131
+ committed policy.
132
+ 2. `.claude-crap.json` at the workspace root — team-committed
133
+ default. Everyone who clones the repo gets the same policy.
134
+ 3. Hardcoded default `"strict"` — applies when neither source is
135
+ present. **You don't need to create either the file or the env
136
+ var to get strict mode.**
137
+
138
+ ### How to adopt gradually
139
+
140
+ Start in `advisory` so the agent simply annotates its sessions with
141
+ a quality reading. Once the team is comfortable, bump to `warn` so
142
+ the full verdict lands in the hook transcript and the agent sees
143
+ every failing rule. When the project is clean enough to ship under
144
+ policy, delete the file (or switch it to `strict`) and let CI catch
145
+ any regression.
146
+
147
+ The `.claude-crap.json` file is a plain JSON document designed to
148
+ be committed alongside the code. It is intentionally **not** matched
149
+ by the `.claude-crap/` gitignore rule (which only covers the
150
+ runtime state directory), so `git add .claude-crap.json` just works.
151
+
152
+ ### Compliance with Claude Code's plugin recommendations
153
+
154
+ The [Claude Code plugins reference](https://code.claude.com/docs/en/plugins-reference#user-configuration)
155
+ documents exactly one canonical pattern for collecting plugin user
156
+ configuration:
157
+
158
+ > The `userConfig` field declares values that Claude Code prompts
159
+ > the user for when the plugin is enabled. Use this instead of
160
+ > requiring users to hand-edit `settings.json`.
161
+
162
+ **`claude-crap` deliberately deviates from that pattern** and reads
163
+ `.claude-crap.json` from the workspace root instead. We chose this
164
+ knowingly, not by accident. The trade-off:
165
+
166
+ - The canonical `userConfig` pattern prompts every user at
167
+ `/plugin install` time, stores the answer in Claude Code's own
168
+ `.claude/settings.json` under `pluginConfigs[claude-crap].options`,
169
+ and exposes it as `${user_config.KEY}` or `CLAUDE_PLUGIN_OPTION_KEY`.
170
+ It is the right channel for per-user secrets like API tokens.
171
+ - For an **enum policy with a sensible default** (`strict`), an
172
+ install-time prompt is friction with no upside: 99% of users will
173
+ just accept the default, and the 1% who want to tune it are
174
+ better served by committing a JSON file to git alongside the rest
175
+ of their project's quality config (`.eslintrc.json`,
176
+ `.prettierrc.json`, `biome.json`, `tsconfig.json`, etc.).
177
+ - The workspace file also lets us ship a proper JSON schema under
178
+ [`schemas/crap-config.json`](./schemas/crap-config.json) for
179
+ IDE autocompletion and CI validation — `userConfig` has no
180
+ equivalent surface.
181
+
182
+ So the honest answer to "are we in compliance with the Claude Code
183
+ recommendations?" is: **we comply with every other part of the
184
+ plugin spec** (manifest schema, hook events, MCP server location,
185
+ substitution tokens, directory layout) and **we deviate from one**:
186
+ user configuration, where we read a workspace file instead of
187
+ declaring a `userConfig` prompt. The deviation is documented here
188
+ and in `CHANGELOG.md`.
189
+
190
+ ---
191
+
192
+ ## Documentation
193
+
194
+ Deep reference lives under [`docs/`](./docs/README.md). Everything
195
+ below is indexed from the [docs README](./docs/README.md) so you can
196
+ navigate there if you prefer browsing by chapter.
197
+
198
+ ### Getting Started
199
+
200
+ - [Architecture Overview](./docs/architecture-overview.md) — the Fat
201
+ Platform / Thin Agent thesis, boot sequence, data flow, and the
202
+ design decisions behind stdio-only transport and loopback-only
203
+ dashboard.
204
+ - [Quick install walk-through](./docs/README.md) — the step-by-step
205
+ version of the [Quick Start](#quick-start) above, including first
206
+ run expectations and the `claude-crap doctor` diagnostic.
207
+
208
+ ### Architecture & Concepts
209
+
210
+ - [Quality gate and math](./docs/quality-gate.md) — CRAP formula,
211
+ TDR formula, letter ratings, Stop hook policies.
212
+ - [Project score](./docs/scoring.md) — how Maintainability /
213
+ Reliability / Security / Overall A..E grades are aggregated and
214
+ rendered to chat.
215
+ - [Hooks reference](./docs/hooks.md) — every Claude Code lifecycle
216
+ hook, contract, rule catalog, and extension points.
217
+
218
+ ### Reference
219
+
220
+ - [MCP tools reference](./docs/mcp-tools.md) — schemas, inputs,
221
+ outputs, and error semantics for every MCP tool and resource.
222
+ - [Scanner adapters](./docs/scanner-adapters.md) — Semgrep, ESLint,
223
+ Bandit, Stryker — mapping rules, effort tables, how to add a new
224
+ adapter.
225
+ - [SDK reference](./docs/sdk.md) — every symbol exported from
226
+ `claude-crap`, `claude-crap/metrics`,
227
+ `claude-crap/sarif`, `claude-crap/ast`,
228
+ `claude-crap/tools`, `claude-crap/adapters`.
229
+
230
+ ### Contributing & Releases
231
+
232
+ - [Contributing guide](./docs/contributing.md) — dev loop, test
233
+ layout, coding conventions, release process.
234
+ - [Changelog](./CHANGELOG.md) — Keep-a-Changelog-formatted release
235
+ history, with the security subsection documenting every OWASP
236
+ Top 10:2025 finding that shipped fixed in `v0.1.0`.
237
+ - [Agent contract (CLAUDE.md)](./plugin/CLAUDE.md) — the Golden Rule that is
238
+ auto-injected into every Claude Code session where the plugin is
239
+ active.
240
+
241
+ ---
242
+
243
+ ## How It Works
244
+
245
+ **Core components:**
246
+
247
+ 1. **PreToolUse gatekeeper** (`plugin/hooks/pre-tool-use.mjs`). A
248
+ synchronous, zero-I/O speed bump that inspects the proposed
249
+ `tool_input` before the tool runs. Sensitive paths, destructive
250
+ Bash, hardcoded secrets, and path-traversal attempts trigger
251
+ `exit 2`, which Claude Code converts into an injection into the
252
+ agent's context — the model then rethinks the approach with the
253
+ exact corrective message in hand. For the high-risk tool
254
+ allowlist (`Write`, `Edit`, `MultiEdit`, `NotebookEdit`, `Bash`),
255
+ any failure to evaluate the rules fails **closed**, so the gate
256
+ cannot be bypassed by crashing a rule.
257
+
258
+ 2. **PostToolUse verifier** (`plugin/hooks/post-tool-use.mjs`). Runs
259
+ immediately after a file-mutating tool call and scans the
260
+ just-written artifact for a missing test harness, inline
261
+ suppression markers (`eslint-disable`, `@ts-ignore`, `# nosec`,
262
+ `# type: ignore`), and fresh TODO / FIXME / HACK markers.
263
+ Warnings are emitted on stderr — non-blocking, but the Stop gate
264
+ will enforce the strict verdict later.
265
+
266
+ 3. **Stop / SubagentStop quality gate**
267
+ (`plugin/hooks/stop-quality-gate.mjs`). When the agent declares a task
268
+ done, this hook reads the consolidated SARIF report, computes
269
+ CRAP / TDR / reliability / security ratings against the entire
270
+ workspace, and refuses to let the task close if any metric is
271
+ outside policy. The corrective message lists every failing rule
272
+ so the agent can remediate on the next turn.
273
+
274
+ 4. **Deterministic MCP server** (`src/index.ts`). A Node.js
275
+ stdio-transport MCP server that exposes the math engines
276
+ (CRAP, TDR, tree-sitter AST, SARIF store with deduplication) as
277
+ first-class tools. Everything is a pure function or a small
278
+ class; no engine performs I/O outside the SARIF store's on-disk
279
+ persistence.
280
+
281
+ 5. **SARIF 2.1.0 store** (`src/sarif/sarif-store.ts`). On-disk
282
+ consolidated report with finding deduplication by
283
+ `(ruleId, uri, startLine, startColumn)`. Loading tolerates
284
+ malformed entries (per-run and per-result try/catch) so a
285
+ tampered `latest.sarif` cannot DoS the MCP server boot. Every
286
+ incoming document is validated against a minimal AJV 2.1.0
287
+ schema before it is persisted.
288
+
289
+ 6. **Per-scanner adapters** (`src/adapters/`). Semgrep (SARIF
290
+ passthrough with enrichment), ESLint (native JSON), Bandit
291
+ (native JSON), Stryker (JSON mutation report). Every adapter
292
+ stamps `properties.effortMinutes` on each finding so the Stop
293
+ gate and the project score engine can compute a uniform
294
+ Technical Debt Ratio across scanner families.
295
+
296
+ 7. **Local dashboard** (`src/dashboard/server.ts`). A Fastify HTTP
297
+ server that binds to `127.0.0.1` only — never `0.0.0.0` — and
298
+ serves a Vue 3 SPA from `src/dashboard/public/`. The Vue runtime
299
+ is vendored under `src/dashboard/public/vendor/` so the
300
+ dashboard works offline after install and is not exposed to
301
+ CDN-compromise or first-boot-MITM attacks.
302
+
303
+ All findings are normalized to **SARIF 2.1.0** before the agent ever
304
+ sees them. One vocabulary, exact file / line / column coordinates,
305
+ and no walls of grep output polluting the context window.
306
+
307
+ See [Architecture Overview](./docs/architecture-overview.md) for the
308
+ boot sequence, the full data-flow diagram, and the design decisions
309
+ behind each component.
310
+
311
+ ---
312
+
313
+ ## MCP Tools
314
+
315
+ The MCP server exposes seven deterministic tools and two resources.
316
+ Every tool has a strict JSON Schema (Draft-07) with
317
+ `additionalProperties: false`, `enum`, `pattern`, and numeric bounds,
318
+ so any drift from the contract produces a deterministic error the
319
+ agent can consume and correct.
320
+
321
+ **The three-layer workflow.** For any non-trivial quality task the
322
+ agent follows the same three-step pattern, mirroring how the
323
+ platform is designed to be used:
324
+
325
+ 1. **Analyze** — `analyze_file_ast` for per-function metrics, or
326
+ `compute_crap` / `compute_tdr` when coverage and LOC are already
327
+ known.
328
+ 2. **Ingest** — `ingest_sarif` or `ingest_scanner_output` to fold
329
+ external scanner output (Semgrep, ESLint, Bandit, Stryker) into
330
+ the consolidated SARIF store with deduplication.
331
+ 3. **Score** — `score_project` to aggregate everything into a
332
+ single Markdown + JSON verdict with A..E grades per dimension.
333
+
334
+ **Available MCP tools:**
335
+
336
+ | Tool | Purpose | Key inputs |
337
+ | ----------------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- |
338
+ | `compute_crap` | CRAP index for a single function plus a block verdict against the configured threshold. | `cyclomaticComplexity`, `coveragePercent`, `functionName`, `filePath` |
339
+ | `compute_tdr` | Technical Debt Ratio and A..E maintainability rating for a project / module / file scope. | `remediationMinutes`, `totalLinesOfCode`, `scope` |
340
+ | `analyze_file_ast` | Tree-sitter AST metrics: physical and logical LOC plus per-function cyclomatic complexity. Supports TypeScript, JavaScript, Python, Java, C#. | `filePath`, `language` |
341
+ | `ingest_sarif` | Merge a raw SARIF 2.1.0 document into the store with deduplication. Validated against a minimal AJV schema before persistence. | `sarifDocument`, `sourceTool` |
342
+ | `ingest_scanner_output` | Route a scanner's native output through the matching adapter, enrich each finding with an `effortMinutes` estimate, and persist as SARIF. | `scanner` (`semgrep` / `eslint` / `bandit` / `stryker`), `rawOutput` |
343
+ | `require_test_harness` | Check whether a production source file has an accompanying test file in any of the supported conventions. | `filePath` |
344
+ | `score_project` | Aggregate the entire workspace into Maintainability / Reliability / Security / Overall A..E grades with Markdown and JSON output. | `format` (`markdown` / `json` / `both`) |
345
+
346
+ **Available MCP resources:**
347
+
348
+ | URI | MIME | Description |
349
+ | ------------------------------ | ------------------------ | --------------------------------------------------------------------- |
350
+ | `sonar://metrics/current` | `application/json` | Live CRAP / TDR / rating snapshot derived from the in-memory store. |
351
+ | `sonar://reports/latest.sarif` | `application/sarif+json` | Last consolidated SARIF document produced by the Stop quality gate. |
352
+
353
+ **Example usage.** From an agent session, the tool call a typical
354
+ pre-publication audit lands on:
355
+
356
+ ```ts
357
+ // Fold a Semgrep SARIF report into the store.
358
+ await tools.ingest_scanner_output({
359
+ scanner: "semgrep",
360
+ rawOutput: readFileSync("./semgrep.sarif", "utf8"),
361
+ });
362
+
363
+ // Ask for the final verdict as Markdown + JSON.
364
+ const score = await tools.score_project({ format: "both" });
365
+ // score.isError === true ⇒ the agent must remediate before closing
366
+ ```
367
+
368
+ And from a consumer that wants to embed the engines directly without
369
+ running the MCP server:
370
+
371
+ ```ts
372
+ import {
373
+ computeCrap,
374
+ computeTdr,
375
+ computeProjectScore,
376
+ SarifStore,
377
+ TreeSitterEngine,
378
+ } from "claude-crap";
379
+ ```
380
+
381
+ Full details — including every schema, every error shape, and the
382
+ per-scanner effort tables — live in
383
+ [docs/mcp-tools.md](./docs/mcp-tools.md) and
384
+ [docs/scanner-adapters.md](./docs/scanner-adapters.md).
385
+
386
+ ---
387
+
388
+ ## System Requirements
389
+
390
+ - **Node.js ≥ 20.0.0** — the only runtime requirement. No .NET, no
391
+ JDK, no Python toolchain.
392
+ - **Bun ≥ 1.0** is also supported as an alternative runtime:
393
+ `bun run build`, `bun test`, and `bun ./dist/index.js` all work
394
+ out of the box. CI runs against both Node and Bun.
395
+ - **Claude Code** with local plugin support. The plugin registers
396
+ itself via the native `/plugin install` slash command — no manual
397
+ `settings.json` surgery required.
398
+ - **A POSIX-compatible shell** for the hook scripts (Bash, Zsh, or
399
+ any POSIX `/bin/sh`). On Windows, WSL or Git Bash works.
400
+ - **Zero native dependencies.** The MCP server ships as pure
401
+ Node.js with WASM-backed tree-sitter, so `npm install` never
402
+ invokes a C compiler or a linker.
403
+
404
+ ### Windows Setup Notes
405
+
406
+ On native Windows (no WSL), the hook scripts rely on a POSIX shell.
407
+ If you hit `'./plugin/hooks/pre-tool-use.mjs' is not recognized as an
408
+ internal or external command`, install [Git for Windows](https://gitforwindows.org/)
409
+ and make sure its `usr/bin` directory is on your `PATH` so `bash`
410
+ and `env` are available to Node's `child_process.spawn`. Using WSL
411
+ 2 sidesteps the issue entirely and is the recommended path for
412
+ Windows developers who run Claude Code locally.
413
+
414
+ ---
415
+
416
+ ## Development
417
+
418
+ All commands run from the repo root — there is no nested package.
419
+
420
+ ```bash
421
+ # Type-check only
422
+ npm run typecheck
423
+
424
+ # Canonical build with full type-checking + declaration files (the
425
+ # build CI and `np release` both call).
426
+ npm run build
427
+
428
+ # Fast dev build via esbuild — 10-20x faster than tsc, but no type
429
+ # check and no .d.ts files. Pair with `npm run typecheck` in watch
430
+ # mode for the fastest feedback loop.
431
+ npm run build:fast
432
+
433
+ # Watch mode for hot rebuilds during source edits
434
+ npm run build:watch
435
+
436
+ # Run in dev mode (tsx, no build step)
437
+ npm run dev
438
+
439
+ # Full native test suite — 155 tests across 27 suites
440
+ npm test
441
+
442
+ # Narrow the feedback loop to one domain while iterating
443
+ npm run test:metrics # CRAP, TDR, score
444
+ npm run test:sarif # SARIF store + dedup + validator
445
+ npm run test:ast # Cyclomatic walker
446
+ npm run test:harness # Test-file resolver
447
+ npm run test:adapters # Semgrep, ESLint, Bandit, Stryker
448
+ npm run test:integration # End-to-end MCP stdio round trips
449
+
450
+ # Clean build artifacts
451
+ npm run clean
452
+ ```
453
+
454
+ CLI shortcuts are exposed as npm scripts too:
455
+
456
+ ```bash
457
+ npm run doctor # node ./bin/claude-crap.mjs doctor
458
+ npm run status # node ./bin/claude-crap.mjs status
459
+ npm run bug-report # writes claude-crap-bug-report-<ts>.md to the cwd
460
+ ```
461
+
462
+ ### Releases
463
+
464
+ Publishing goes through [`np`](https://github.com/sindresorhus/np)
465
+ so every release runs `clean + build + test + audit` before tagging
466
+ and pushing to npm:
467
+
468
+ ```bash
469
+ npm run release # interactive — prompts for patch/minor/major
470
+ npm run release:patch # non-interactive patch bump
471
+ npm run release:minor # non-interactive minor bump
472
+ npm run release:major # non-interactive major bump
473
+ ```
474
+
475
+ `prepublishOnly` runs `npm run clean && npm run build && npm test &&
476
+ npm audit --omit=dev --audit-level=high` automatically. A broken
477
+ test OR a new high-severity advisory in a runtime dependency blocks
478
+ `np` before any tag lands.
479
+
480
+ ### Running the MCP server standalone
481
+
482
+ For debugging, the MCP server can be run outside Claude Code:
483
+
484
+ ```bash
485
+ node ./dist/index.js --transport stdio
486
+ ```
487
+
488
+ Paste an MCP `initialize` request on stdin to exercise the JSON-RPC
489
+ framing. The dashboard auto-boots at `http://127.0.0.1:5117` when
490
+ the server starts — change `CLAUDE_PLUGIN_OPTION_DASHBOARD_PORT` to
491
+ move it to a different port.
492
+
493
+ ---
494
+
495
+ ## Bug Reports
496
+
497
+ The `claude-crap` CLI ships a `bug-report` subcommand that collects
498
+ every piece of information a maintainer typically asks for when
499
+ triaging an issue and writes it to a single Markdown bundle:
500
+
501
+ ```bash
502
+ npx claude-crap bug-report
503
+ # writes ./claude-crap-bug-report-<timestamp>.md
504
+ ```
505
+
506
+ The bundle includes the plugin version, Node / npm / platform
507
+ versions, plugin file presence, the build state of `dist/`, the
508
+ resolved `CLAUDE_PLUGIN_OPTION_*` environment variables (with every
509
+ secret-looking variable automatically redacted by name), the
510
+ `claude-crap doctor` output, and a summary of the consolidated
511
+ SARIF report if one exists.
512
+
513
+ Pass `--stdout` to print the bundle instead of writing a file, or
514
+ `-o <path>` to choose the filename. Review the output for anything
515
+ sensitive that slipped past the redactor, then open a new issue at
516
+ [github.com/ahernandez-developer/claude-crap/issues](https://github.com/ahernandez-developer/claude-crap/issues)
517
+ and paste the bundle as the issue body.
518
+
519
+ ---
520
+
521
+ ## Contributing
522
+
523
+ 1. **Fork** [ahernandez-developer/claude-crap](https://github.com/ahernandez-developer/claude-crap)
524
+ and create a feature branch off `main`.
525
+ 2. **Write the test first.** The CLAUDE.md Golden Rule forbids
526
+ writing functional code before a test safety net exists, and the
527
+ PreToolUse hook will block you if you try. Add a characterization
528
+ test that pins the current behavior, then the attack test that
529
+ demonstrates the bug, then the patch.
530
+ 3. **Run `npm test`.** The full suite must stay at 177 / 177 green.
531
+ If you add new tests, update the count in the
532
+ [Development](#development) section and in `CHANGELOG.md`.
533
+ 4. **Update the `CHANGELOG.md`** with an entry describing your
534
+ change. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
535
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
536
+ 5. **Open a pull request** describing the change in the rigid
537
+ deduction format from `CLAUDE.md` (`Coupled dependency / Risk /
538
+ Required test / Blocking metric / Proposed change`). CI will run
539
+ typecheck, the full test suite, and `npm audit` on every push.
540
+
541
+ Full dev loop, test layout, coding conventions, and the release
542
+ process live in [docs/contributing.md](./docs/contributing.md).
543
+
544
+ ---
545
+
546
+ ## License
547
+
548
+ MIT. See [LICENSE](./LICENSE) for the full text.
549
+
550
+ Copyright (c) 2026 Alan Hernandez.
@@ -0,0 +1,141 @@
1
+ #!/usr/bin/env node
2
+ // @ts-check
3
+ /**
4
+ * `claude-crap` CLI dispatcher.
5
+ *
6
+ * Installed as a `bin` entry in `package.json`, so both
7
+ * `npx claude-crap <cmd>` and a globally linked install
8
+ * resolve to this file. The binary itself is named `claude-crap`
9
+ * (independent of the scoped npm package name), so after a global
10
+ * install users can just type `claude-crap <cmd>`. The CLI is
11
+ * deliberately tiny — each subcommand lives in its own module under
12
+ * `scripts/` so they can be tested in isolation and so new
13
+ * subcommands can be added without touching this dispatcher.
14
+ *
15
+ * Supported subcommands:
16
+ *
17
+ * install Prepare the workspace and print the Claude Code
18
+ * `/plugin install` command the user needs to run.
19
+ * uninstall Remove the plugin's scratch directory and print the
20
+ * matching Claude Code uninstall command.
21
+ * doctor Run a full diagnostic — Node version, dist/ freshness,
22
+ * hook executability, tree-sitter grammars, dashboard
23
+ * port availability, CLAUDE.md presence, etc.
24
+ * status Show version, resolved paths, and current registration
25
+ * state (does Claude Code know about this plugin?).
26
+ * version Print the plugin version and exit.
27
+ * help Print usage information and exit.
28
+ *
29
+ * Every subcommand exits with `0` on success and non-zero on failure
30
+ * so the CLI plays well with shell pipelines.
31
+ *
32
+ * @module bin/claude-crap
33
+ */
34
+
35
+ import { fileURLToPath } from "node:url";
36
+ import { dirname, resolve } from "node:path";
37
+
38
+ const HERE = dirname(fileURLToPath(import.meta.url));
39
+ const PLUGIN_ROOT = resolve(HERE, "..");
40
+
41
+ const USAGE = `
42
+ claude-crap — deterministic QA plugin for Claude Code
43
+
44
+ Usage:
45
+ claude-crap <command>
46
+
47
+ Commands:
48
+ install Prepare the workspace and print the Claude Code install command.
49
+ uninstall Remove the plugin's scratch directory and print the uninstall command.
50
+ doctor Diagnose the install (Node version, dist/, hooks, grammars, ports).
51
+ status Show version, paths, and registration state.
52
+ bug-report Write a diagnostic bundle for triage (auto-redacts secrets).
53
+ version Print the plugin version and exit.
54
+ help Show this message.
55
+
56
+ Examples:
57
+ npx claude-crap install
58
+ npx claude-crap doctor
59
+ npx claude-crap status
60
+ npx claude-crap bug-report --stdout
61
+ `.trim();
62
+
63
+ /**
64
+ * Dynamically import a subcommand module. Keeping imports lazy means
65
+ * `claude-crap version` or `claude-crap help` never pays the cost of
66
+ * loading the filesystem walkers, port probes, or the MCP types.
67
+ *
68
+ * @param {string} name File name under ./scripts/ without the extension.
69
+ * @returns {Promise<{default: (ctx: {pluginRoot: string, argv: string[]}) => Promise<number>}>}
70
+ */
71
+ function loadCommand(name) {
72
+ return import(resolve(PLUGIN_ROOT, "scripts", `${name}.mjs`));
73
+ }
74
+
75
+ /**
76
+ * Read the plugin version from `package.json` synchronously. Used by
77
+ * the `version` and `status` subcommands.
78
+ *
79
+ * @returns {Promise<string>} The version string.
80
+ */
81
+ async function readVersion() {
82
+ const pkgUrl = resolve(PLUGIN_ROOT, "package.json");
83
+ const { readFile } = await import("node:fs/promises");
84
+ const raw = await readFile(pkgUrl, "utf8");
85
+ const pkg = /** @type {{version?: string}} */ (JSON.parse(raw));
86
+ return pkg.version ?? "0.0.0";
87
+ }
88
+
89
+ async function main() {
90
+ const argv = process.argv.slice(2);
91
+ const command = argv[0] ?? "help";
92
+ const rest = argv.slice(1);
93
+
94
+ switch (command) {
95
+ case "install": {
96
+ const mod = await loadCommand("install");
97
+ return mod.default({ pluginRoot: PLUGIN_ROOT, argv: rest });
98
+ }
99
+ case "uninstall": {
100
+ const mod = await loadCommand("uninstall");
101
+ return mod.default({ pluginRoot: PLUGIN_ROOT, argv: rest });
102
+ }
103
+ case "doctor": {
104
+ const mod = await loadCommand("doctor");
105
+ return mod.default({ pluginRoot: PLUGIN_ROOT, argv: rest });
106
+ }
107
+ case "status": {
108
+ const mod = await loadCommand("status");
109
+ return mod.default({ pluginRoot: PLUGIN_ROOT, argv: rest });
110
+ }
111
+ case "bug-report":
112
+ case "report": {
113
+ const mod = await loadCommand("bug-report");
114
+ return mod.default({ pluginRoot: PLUGIN_ROOT, argv: rest });
115
+ }
116
+ case "version":
117
+ case "--version":
118
+ case "-v": {
119
+ const version = await readVersion();
120
+ process.stdout.write(`claude-crap ${version}\n`);
121
+ return 0;
122
+ }
123
+ case "help":
124
+ case "--help":
125
+ case "-h":
126
+ case undefined:
127
+ process.stdout.write(USAGE + "\n");
128
+ return 0;
129
+ default:
130
+ process.stderr.write(`claude-crap: unknown command '${command}'\n\n`);
131
+ process.stderr.write(USAGE + "\n");
132
+ return 1;
133
+ }
134
+ }
135
+
136
+ main()
137
+ .then((code) => process.exit(code ?? 0))
138
+ .catch((err) => {
139
+ process.stderr.write(`claude-crap: fatal error: ${err?.message ?? err}\n`);
140
+ process.exit(1);
141
+ });