claude-crap 0.4.2 → 0.4.3
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/README.md +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +6 -1
- package/dist/config.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +2 -1
- package/plugin/bundle/mcp-server.mjs +4 -1
- package/plugin/bundle/mcp-server.mjs.map +2 -2
- package/plugin/package-lock.json +2 -2
- package/plugin/package.json +1 -1
- package/src/config.ts +7 -2
- package/src/tests/config.test.ts +185 -0
package/plugin/package-lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-crap-plugin",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "claude-crap-plugin",
|
|
9
|
-
"version": "0.4.
|
|
9
|
+
"version": "0.4.3",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"@fastify/static": "^8.0.3",
|
|
12
12
|
"@modelcontextprotocol/sdk": "^1.0.4",
|
package/plugin/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -30,7 +30,7 @@ export type MaintainabilityRating = "A" | "B" | "C" | "D" | "E";
|
|
|
30
30
|
* configuration at runtime — any change must go through a server restart.
|
|
31
31
|
*/
|
|
32
32
|
export interface CrapConfig {
|
|
33
|
-
/** Absolute path to the
|
|
33
|
+
/** Absolute path to the user's workspace. Resolved from `CLAUDE_PROJECT_DIR` → `CLAUDE_CRAP_PLUGIN_ROOT` → `process.cwd()`. */
|
|
34
34
|
readonly pluginRoot: string;
|
|
35
35
|
/** Directory (relative to the workspace) where consolidated SARIF reports are written. */
|
|
36
36
|
readonly sarifOutputDir: string;
|
|
@@ -98,7 +98,12 @@ function parseRating(raw: string | undefined, fallback: MaintainabilityRating):
|
|
|
98
98
|
*/
|
|
99
99
|
export function loadConfig(): CrapConfig {
|
|
100
100
|
return {
|
|
101
|
-
|
|
101
|
+
// CLAUDE_PROJECT_DIR is set by Claude Code to the user's workspace.
|
|
102
|
+
// process.cwd() is NOT reliable — Claude Code sets it to the plugin
|
|
103
|
+
// cache directory when starting MCP servers, not the user's project.
|
|
104
|
+
pluginRoot: process.env.CLAUDE_PROJECT_DIR
|
|
105
|
+
?? process.env.CLAUDE_CRAP_PLUGIN_ROOT
|
|
106
|
+
?? process.cwd(),
|
|
102
107
|
sarifOutputDir: process.env.CLAUDE_CRAP_SARIF_OUTPUT_DIR ?? ".claude-crap/reports",
|
|
103
108
|
crapThreshold: parseNumber("CLAUDE_CRAP_CRAP_THRESHOLD", process.env.CLAUDE_CRAP_CRAP_THRESHOLD, 30),
|
|
104
109
|
cyclomaticMax: parseNumber("CLAUDE_CRAP_CYCLOMATIC_MAX", process.env.CLAUDE_CRAP_CYCLOMATIC_MAX, 15),
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Characterization tests for the MCP server config loader.
|
|
3
|
+
*
|
|
4
|
+
* `loadConfig()` resolves `pluginRoot` from three env vars with
|
|
5
|
+
* strict priority:
|
|
6
|
+
*
|
|
7
|
+
* 1. `CLAUDE_PROJECT_DIR` — set by Claude Code to the workspace
|
|
8
|
+
* 2. `CLAUDE_CRAP_PLUGIN_ROOT` — legacy explicit override
|
|
9
|
+
* 3. `process.cwd()` — last-resort fallback
|
|
10
|
+
*
|
|
11
|
+
* These tests pin the priority chain and the numeric/rating parsers
|
|
12
|
+
* so that regressions in workspace resolution are caught before they
|
|
13
|
+
* ship a bundle that writes SARIF reports into the plugin cache.
|
|
14
|
+
*
|
|
15
|
+
* @module tests/config.test
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { describe, it, beforeEach, afterEach } from "node:test";
|
|
19
|
+
import assert from "node:assert/strict";
|
|
20
|
+
|
|
21
|
+
import { loadConfig } from "../config.js";
|
|
22
|
+
|
|
23
|
+
/* ── env snapshot / restore ─────────────────────────────────────── */
|
|
24
|
+
|
|
25
|
+
const ENV_KEYS = [
|
|
26
|
+
"CLAUDE_PROJECT_DIR",
|
|
27
|
+
"CLAUDE_CRAP_PLUGIN_ROOT",
|
|
28
|
+
"CLAUDE_CRAP_SARIF_OUTPUT_DIR",
|
|
29
|
+
"CLAUDE_CRAP_CRAP_THRESHOLD",
|
|
30
|
+
"CLAUDE_CRAP_CYCLOMATIC_MAX",
|
|
31
|
+
"CLAUDE_CRAP_TDR_MAX_RATING",
|
|
32
|
+
"CLAUDE_CRAP_MINUTES_PER_LOC",
|
|
33
|
+
"CLAUDE_CRAP_DASHBOARD_PORT",
|
|
34
|
+
] as const;
|
|
35
|
+
|
|
36
|
+
type Snapshot = Map<string, string | undefined>;
|
|
37
|
+
|
|
38
|
+
function snapshotEnv(): Snapshot {
|
|
39
|
+
const snap = new Map<string, string | undefined>();
|
|
40
|
+
for (const key of ENV_KEYS) snap.set(key, process.env[key]);
|
|
41
|
+
return snap;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function restoreEnv(snap: Snapshot): void {
|
|
45
|
+
for (const [key, val] of snap) {
|
|
46
|
+
if (val === undefined) delete process.env[key];
|
|
47
|
+
else process.env[key] = val;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function clearConfigEnv(): void {
|
|
52
|
+
for (const key of ENV_KEYS) delete process.env[key];
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* ── tests ──────────────────────────────────────────────────────── */
|
|
56
|
+
|
|
57
|
+
describe("loadConfig — pluginRoot priority chain", () => {
|
|
58
|
+
let saved: Snapshot;
|
|
59
|
+
|
|
60
|
+
beforeEach(() => {
|
|
61
|
+
saved = snapshotEnv();
|
|
62
|
+
clearConfigEnv();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
afterEach(() => {
|
|
66
|
+
restoreEnv(saved);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("falls back to process.cwd() when no env vars are set", () => {
|
|
70
|
+
const cfg = loadConfig();
|
|
71
|
+
assert.equal(cfg.pluginRoot, process.cwd());
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("CLAUDE_CRAP_PLUGIN_ROOT wins over process.cwd()", () => {
|
|
75
|
+
process.env.CLAUDE_CRAP_PLUGIN_ROOT = "/explicit/plugin/root";
|
|
76
|
+
const cfg = loadConfig();
|
|
77
|
+
assert.equal(cfg.pluginRoot, "/explicit/plugin/root");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("CLAUDE_PROJECT_DIR wins over CLAUDE_CRAP_PLUGIN_ROOT", () => {
|
|
81
|
+
process.env.CLAUDE_PROJECT_DIR = "/workspace/project";
|
|
82
|
+
process.env.CLAUDE_CRAP_PLUGIN_ROOT = "/explicit/plugin/root";
|
|
83
|
+
const cfg = loadConfig();
|
|
84
|
+
assert.equal(cfg.pluginRoot, "/workspace/project");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("CLAUDE_PROJECT_DIR alone resolves correctly", () => {
|
|
88
|
+
process.env.CLAUDE_PROJECT_DIR = "/workspace/project";
|
|
89
|
+
const cfg = loadConfig();
|
|
90
|
+
assert.equal(cfg.pluginRoot, "/workspace/project");
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
describe("loadConfig — defaults", () => {
|
|
95
|
+
let saved: Snapshot;
|
|
96
|
+
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
saved = snapshotEnv();
|
|
99
|
+
clearConfigEnv();
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
afterEach(() => {
|
|
103
|
+
restoreEnv(saved);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("sarifOutputDir defaults to .claude-crap/reports", () => {
|
|
107
|
+
assert.equal(loadConfig().sarifOutputDir, ".claude-crap/reports");
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("crapThreshold defaults to 30", () => {
|
|
111
|
+
assert.equal(loadConfig().crapThreshold, 30);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("cyclomaticMax defaults to 15", () => {
|
|
115
|
+
assert.equal(loadConfig().cyclomaticMax, 15);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("tdrMaxRating defaults to C", () => {
|
|
119
|
+
assert.equal(loadConfig().tdrMaxRating, "C");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("minutesPerLoc defaults to 30", () => {
|
|
123
|
+
assert.equal(loadConfig().minutesPerLoc, 30);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("dashboardPort defaults to 5117", () => {
|
|
127
|
+
assert.equal(loadConfig().dashboardPort, 5117);
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe("loadConfig — numeric parsing", () => {
|
|
132
|
+
let saved: Snapshot;
|
|
133
|
+
|
|
134
|
+
beforeEach(() => {
|
|
135
|
+
saved = snapshotEnv();
|
|
136
|
+
clearConfigEnv();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
afterEach(() => {
|
|
140
|
+
restoreEnv(saved);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("reads CLAUDE_CRAP_CRAP_THRESHOLD from env", () => {
|
|
144
|
+
process.env.CLAUDE_CRAP_CRAP_THRESHOLD = "50";
|
|
145
|
+
assert.equal(loadConfig().crapThreshold, 50);
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it("throws on non-numeric CLAUDE_CRAP_CRAP_THRESHOLD", () => {
|
|
149
|
+
process.env.CLAUDE_CRAP_CRAP_THRESHOLD = "not-a-number";
|
|
150
|
+
assert.throws(() => loadConfig(), /not a finite number/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("ignores empty string and falls back to default", () => {
|
|
154
|
+
process.env.CLAUDE_CRAP_CRAP_THRESHOLD = "";
|
|
155
|
+
assert.equal(loadConfig().crapThreshold, 30);
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
describe("loadConfig — rating parsing", () => {
|
|
160
|
+
let saved: Snapshot;
|
|
161
|
+
|
|
162
|
+
beforeEach(() => {
|
|
163
|
+
saved = snapshotEnv();
|
|
164
|
+
clearConfigEnv();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
afterEach(() => {
|
|
168
|
+
restoreEnv(saved);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("accepts lowercase rating", () => {
|
|
172
|
+
process.env.CLAUDE_CRAP_TDR_MAX_RATING = "a";
|
|
173
|
+
assert.equal(loadConfig().tdrMaxRating, "A");
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it("accepts padded rating", () => {
|
|
177
|
+
process.env.CLAUDE_CRAP_TDR_MAX_RATING = " B ";
|
|
178
|
+
assert.equal(loadConfig().tdrMaxRating, "B");
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
it("throws on invalid rating letter", () => {
|
|
182
|
+
process.env.CLAUDE_CRAP_TDR_MAX_RATING = "F";
|
|
183
|
+
assert.throws(() => loadConfig(), /must be one of A, B, C, D, E/);
|
|
184
|
+
});
|
|
185
|
+
});
|