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/README.md
ADDED
|
@@ -0,0 +1,550 @@
|
|
|
1
|
+
# claude-crap
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/claude-crap)
|
|
4
|
+
[](https://github.com/ahernandez-developer/claude-crap/actions/workflows/ci.yml)
|
|
5
|
+
[](https://nodejs.org/)
|
|
6
|
+
[](https://bun.sh/)
|
|
7
|
+
[](./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
|
+
});
|