claude-crap 0.4.3 → 0.4.4
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 +98 -0
- package/README.md +1 -1
- package/dist/config.d.ts +29 -3
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +95 -8
- package/dist/config.js.map +1 -1
- package/package.json +1 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -2
- package/plugin/bundle/mcp-server.mjs +47 -4
- 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 +113 -9
- package/src/tests/config.test.ts +111 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,104 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.4] - 2026-04-13
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **MCP server workspace root resolution (follow-up to 0.4.3)** —
|
|
13
|
+
v0.4.3 attempted to fix the workspace-cache bug by setting
|
|
14
|
+
`"CLAUDE_PROJECT_DIR": "${CLAUDE_PROJECT_DIR}"` in `plugin/.mcp.json`
|
|
15
|
+
env, on the assumption that Claude Code would expand the variable.
|
|
16
|
+
It does not — only `${CLAUDE_PLUGIN_ROOT}` is expanded inside
|
|
17
|
+
`.mcp.json`. The literal string `${CLAUDE_PROJECT_DIR}` was passed
|
|
18
|
+
through to `process.env.CLAUDE_PROJECT_DIR`, the MCP server happily
|
|
19
|
+
used it as a path, and `auto_scan` returned 0 files because the
|
|
20
|
+
"workspace" was a literal template string. This release fixes that:
|
|
21
|
+
- **Reverted** the broken `.mcp.json` env entry.
|
|
22
|
+
- **Defensive `${...}` literal detection.** Any env var whose value
|
|
23
|
+
matches `^${VAR_NAME}$` is treated as unset by `sanitizeEnvPath()`,
|
|
24
|
+
so an unexpanded template can never leak through as a "valid" path.
|
|
25
|
+
- **Parent-cwd fallback.** `loadConfig()` now delegates to
|
|
26
|
+
`discoverWorkspaceRoot()`, which walks a 4-strategy chain:
|
|
27
|
+
`CLAUDE_PROJECT_DIR` → `CLAUDE_CRAP_PLUGIN_ROOT` → parent process
|
|
28
|
+
cwd (via `lsof` on macOS, `/proc/<pid>/cwd` on Linux) →
|
|
29
|
+
`process.cwd()`. Claude Code's cwd IS the user's workspace, so the
|
|
30
|
+
parent-cwd probe is the most reliable fallback when env-var
|
|
31
|
+
inheritance is unavailable.
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **`discoverWorkspaceRoot(options)`** export from `src/config.ts` with
|
|
36
|
+
an injectable `readParentCwd` for testability.
|
|
37
|
+
- **11 new characterization tests** in `src/tests/config.test.ts`
|
|
38
|
+
covering literal `${...}` detection on every source, parent-cwd
|
|
39
|
+
fallback ordering, and the `process.cwd()` last-resort. Suite total:
|
|
40
|
+
**366 tests across 95 suites**.
|
|
41
|
+
|
|
42
|
+
## [0.4.3] - 2026-04-13
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **MCP server workspace root (incomplete fix)** — `loadConfig()` now
|
|
47
|
+
reads `CLAUDE_PROJECT_DIR` ahead of `CLAUDE_CRAP_PLUGIN_ROOT` and
|
|
48
|
+
`process.cwd()`, so SARIF reports stop landing inside the plugin
|
|
49
|
+
cache directory.
|
|
50
|
+
- **`.mcp.json` env pass-through** — added an explicit
|
|
51
|
+
`"CLAUDE_PROJECT_DIR": "${CLAUDE_PROJECT_DIR}"` entry to forward the
|
|
52
|
+
variable to the MCP server process.
|
|
53
|
+
> **Known issue (fixed in 0.4.4):** Claude Code does NOT expand
|
|
54
|
+
> `${CLAUDE_PROJECT_DIR}` inside `.mcp.json` env (only
|
|
55
|
+
> `${CLAUDE_PLUGIN_ROOT}` is expanded), so the literal template
|
|
56
|
+
> string was passed through and broke workspace resolution
|
|
57
|
+
> completely. Upgrade to 0.4.4.
|
|
58
|
+
|
|
59
|
+
## [0.4.2] - 2026-04-13
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- **Default strictness changed from `strict` to `warn`** so existing
|
|
64
|
+
projects with findings can adopt the plugin without being hard-blocked
|
|
65
|
+
on every task close. Teams that want hard enforcement set
|
|
66
|
+
`"strictness": "strict"` in `.claude-crap.json` explicitly.
|
|
67
|
+
|
|
68
|
+
### Fixed
|
|
69
|
+
|
|
70
|
+
- **Plugin infrastructure directories excluded from scans** — added
|
|
71
|
+
`plugin/`, `hooks/`, `skills/`, `.claude-plugin/`, and `.claude-sonar/`
|
|
72
|
+
to `DEFAULT_SKIP_DIRS`. These directories used to inflate workspace
|
|
73
|
+
LOC totals and produce false complexity hotspots in the dashboard.
|
|
74
|
+
The hooks-layer `LOC_WALK_SKIP_DIRS` was synced to match so the Stop
|
|
75
|
+
quality gate computes TDR over the same file set as the MCP server.
|
|
76
|
+
|
|
77
|
+
## [0.4.1] - 2026-04-13
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- **Launcher dependency validation** — the launcher now verifies that
|
|
82
|
+
every externalized package (`@modelcontextprotocol/sdk`, `fastify`,
|
|
83
|
+
`@fastify/static`, `pino`, `web-tree-sitter`, `tree-sitter-wasms`,
|
|
84
|
+
`picomatch`) actually exists inside `node_modules/` instead of just
|
|
85
|
+
checking that the directory is present. Fixes the case where a new
|
|
86
|
+
dependency was added but cached `node_modules/` is stale.
|
|
87
|
+
- **Cache sync clears stale `node_modules/`** — `build:plugin` now
|
|
88
|
+
deletes `node_modules/` in every cached plugin version after copying
|
|
89
|
+
updated files, forcing the launcher to re-install with the correct
|
|
90
|
+
dependency set on the next session boot.
|
|
91
|
+
|
|
92
|
+
### Changed
|
|
93
|
+
|
|
94
|
+
- **`handleToolCall` refactored** — the 10-case switch (cyclomatic
|
|
95
|
+
complexity 32) was extracted into 10 named handlers, leaving
|
|
96
|
+
`handleToolCall` as a thin dispatcher (CC 11). Each handler handles
|
|
97
|
+
exactly one MCP tool and is independently testable.
|
|
98
|
+
|
|
99
|
+
### Added
|
|
100
|
+
|
|
101
|
+
- **`docs/supported-languages.md`** — per-language setup, detection
|
|
102
|
+
rules, scanner behavior, monorepo discovery, file exclusions, and a
|
|
103
|
+
walkthrough for adding a new language. Linked from the README
|
|
104
|
+
Documentation section.
|
|
105
|
+
|
|
8
106
|
## [0.4.0] - 2026-04-13
|
|
9
107
|
|
|
10
108
|
### Added
|
package/README.md
CHANGED
|
@@ -240,7 +240,7 @@ added via `.claude-crap.json`:
|
|
|
240
240
|
|
|
241
241
|
```bash
|
|
242
242
|
npm install # postinstall builds dist/ automatically
|
|
243
|
-
npm test #
|
|
243
|
+
npm test # 366 tests across 95 suites
|
|
244
244
|
npm run build:fast # esbuild dev build (10-20x faster than tsc)
|
|
245
245
|
npm run doctor # full diagnostic
|
|
246
246
|
```
|
package/dist/config.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* user settings → plugin.json "options" → .mcp.json "env" → this file
|
|
10
10
|
*
|
|
11
11
|
* If any environment variable is missing or empty, a safe default is used,
|
|
12
|
-
* but the loader NEVER invents stochastic values
|
|
13
|
-
*
|
|
12
|
+
* but the loader NEVER invents stochastic values. This module is the
|
|
13
|
+
* single source of truth for runtime configuration.
|
|
14
14
|
*
|
|
15
15
|
* @module config
|
|
16
16
|
*/
|
|
@@ -28,7 +28,7 @@ export type MaintainabilityRating = "A" | "B" | "C" | "D" | "E";
|
|
|
28
28
|
* configuration at runtime — any change must go through a server restart.
|
|
29
29
|
*/
|
|
30
30
|
export interface CrapConfig {
|
|
31
|
-
/** Absolute path to the user's workspace. Resolved
|
|
31
|
+
/** Absolute path to the user's workspace. Resolved via {@link discoverWorkspaceRoot}. */
|
|
32
32
|
readonly pluginRoot: string;
|
|
33
33
|
/** Directory (relative to the workspace) where consolidated SARIF reports are written. */
|
|
34
34
|
readonly sarifOutputDir: string;
|
|
@@ -43,6 +43,32 @@ export interface CrapConfig {
|
|
|
43
43
|
/** Local TCP port the Vue.js dashboard will bind to. */
|
|
44
44
|
readonly dashboardPort: number;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Parameters accepted by {@link discoverWorkspaceRoot}. The only field is
|
|
48
|
+
* an injectable `readParentCwd` implementation so that tests can pin the
|
|
49
|
+
* fallback behavior without spawning `lsof` or reading `/proc`.
|
|
50
|
+
*/
|
|
51
|
+
export interface DiscoverWorkspaceOptions {
|
|
52
|
+
readParentCwd?: () => string | undefined;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Resolve the user's workspace directory. Strategy, in strict priority
|
|
56
|
+
* order:
|
|
57
|
+
*
|
|
58
|
+
* 1. `CLAUDE_PROJECT_DIR` (sanitized — ignored if it's `${...}`)
|
|
59
|
+
* 2. `CLAUDE_CRAP_PLUGIN_ROOT` (sanitized — legacy explicit override)
|
|
60
|
+
* 3. Parent process cwd (Claude Code's cwd = the workspace)
|
|
61
|
+
* 4. `process.cwd()` (last-resort fallback; usually wrong for
|
|
62
|
+
* MCP servers because Claude Code sets
|
|
63
|
+
* cwd to the plugin cache directory)
|
|
64
|
+
*
|
|
65
|
+
* This function NEVER returns an unexpanded `${...}` template; any source
|
|
66
|
+
* that contains one is skipped as if it were unset.
|
|
67
|
+
*
|
|
68
|
+
* @param options Injection points for tests.
|
|
69
|
+
* @returns A concrete filesystem path.
|
|
70
|
+
*/
|
|
71
|
+
export declare function discoverWorkspaceRoot(options?: DiscoverWorkspaceOptions): string;
|
|
46
72
|
/**
|
|
47
73
|
* Build the complete {@link CrapConfig} from the current process environment.
|
|
48
74
|
*
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,yFAAyF;IACzF,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,0FAA0F;IAC1F,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,gGAAgG;IAChG,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,+EAA+E;IAC/E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,6EAA6E;IAC7E,QAAQ,CAAC,YAAY,EAAE,qBAAqB,CAAC;IAC7C,0FAA0F;IAC1F,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,wDAAwD;IACxD,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAmED;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,aAAa,CAAC,EAAE,MAAM,MAAM,GAAG,SAAS,CAAC;CAC1C;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,GAAE,wBAA6B,GAAG,MAAM,CAapF;AA0CD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAUvC"}
|
package/dist/config.js
CHANGED
|
@@ -9,11 +9,103 @@
|
|
|
9
9
|
* user settings → plugin.json "options" → .mcp.json "env" → this file
|
|
10
10
|
*
|
|
11
11
|
* If any environment variable is missing or empty, a safe default is used,
|
|
12
|
-
* but the loader NEVER invents stochastic values
|
|
13
|
-
*
|
|
12
|
+
* but the loader NEVER invents stochastic values. This module is the
|
|
13
|
+
* single source of truth for runtime configuration.
|
|
14
14
|
*
|
|
15
15
|
* @module config
|
|
16
16
|
*/
|
|
17
|
+
import { execFileSync } from "node:child_process";
|
|
18
|
+
import { readlinkSync } from "node:fs";
|
|
19
|
+
/**
|
|
20
|
+
* Detects an unexpanded `.mcp.json` variable template such as
|
|
21
|
+
* `${CLAUDE_PROJECT_DIR}`. Claude Code only expands `${CLAUDE_PLUGIN_ROOT}`
|
|
22
|
+
* inside `.mcp.json`; every other `${VAR}` is passed through verbatim and
|
|
23
|
+
* must NOT be treated as a real filesystem path.
|
|
24
|
+
*/
|
|
25
|
+
function isLiteralVarTemplate(value) {
|
|
26
|
+
if (value === undefined)
|
|
27
|
+
return false;
|
|
28
|
+
return /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(value.trim());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Normalize an environment variable that is expected to contain a path.
|
|
32
|
+
* Returns `undefined` if the value is missing, empty, or an unexpanded
|
|
33
|
+
* `${...}` template. Any non-empty concrete string is returned as-is.
|
|
34
|
+
*/
|
|
35
|
+
function sanitizeEnvPath(value) {
|
|
36
|
+
if (value === undefined || value === "")
|
|
37
|
+
return undefined;
|
|
38
|
+
if (isLiteralVarTemplate(value))
|
|
39
|
+
return undefined;
|
|
40
|
+
return value;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read the current working directory of the parent process. Claude Code
|
|
44
|
+
* spawns MCP servers with its own cwd set to the user's workspace, so the
|
|
45
|
+
* parent's cwd is the most reliable fallback when `CLAUDE_PROJECT_DIR` is
|
|
46
|
+
* not inherited (e.g. because Claude Code only exports it for hooks).
|
|
47
|
+
*
|
|
48
|
+
* Returns `undefined` on any platform or failure mode the probe cannot
|
|
49
|
+
* handle — callers must be prepared for a missing result.
|
|
50
|
+
*/
|
|
51
|
+
function readParentCwdDefault() {
|
|
52
|
+
try {
|
|
53
|
+
const ppid = process.ppid;
|
|
54
|
+
if (!ppid || ppid === 0)
|
|
55
|
+
return undefined;
|
|
56
|
+
if (process.platform === "linux") {
|
|
57
|
+
// /proc/<pid>/cwd is a symlink to the process's cwd.
|
|
58
|
+
return readlinkSync(`/proc/${ppid}/cwd`);
|
|
59
|
+
}
|
|
60
|
+
if (process.platform === "darwin") {
|
|
61
|
+
// `lsof -a -p <pid> -d cwd -F n` emits a single line starting with
|
|
62
|
+
// `n<path>` for the cwd file descriptor. `-F` keeps the output
|
|
63
|
+
// machine-readable.
|
|
64
|
+
const output = execFileSync("lsof", ["-a", "-p", String(ppid), "-d", "cwd", "-F", "n"], {
|
|
65
|
+
encoding: "utf8",
|
|
66
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
67
|
+
timeout: 2000,
|
|
68
|
+
});
|
|
69
|
+
const match = output.match(/^n(.+)$/m);
|
|
70
|
+
return match?.[1];
|
|
71
|
+
}
|
|
72
|
+
// Windows and other platforms: no reliable no-dep probe.
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the user's workspace directory. Strategy, in strict priority
|
|
81
|
+
* order:
|
|
82
|
+
*
|
|
83
|
+
* 1. `CLAUDE_PROJECT_DIR` (sanitized — ignored if it's `${...}`)
|
|
84
|
+
* 2. `CLAUDE_CRAP_PLUGIN_ROOT` (sanitized — legacy explicit override)
|
|
85
|
+
* 3. Parent process cwd (Claude Code's cwd = the workspace)
|
|
86
|
+
* 4. `process.cwd()` (last-resort fallback; usually wrong for
|
|
87
|
+
* MCP servers because Claude Code sets
|
|
88
|
+
* cwd to the plugin cache directory)
|
|
89
|
+
*
|
|
90
|
+
* This function NEVER returns an unexpanded `${...}` template; any source
|
|
91
|
+
* that contains one is skipped as if it were unset.
|
|
92
|
+
*
|
|
93
|
+
* @param options Injection points for tests.
|
|
94
|
+
* @returns A concrete filesystem path.
|
|
95
|
+
*/
|
|
96
|
+
export function discoverWorkspaceRoot(options = {}) {
|
|
97
|
+
const readParentCwd = options.readParentCwd ?? readParentCwdDefault;
|
|
98
|
+
const fromProjectDir = sanitizeEnvPath(process.env.CLAUDE_PROJECT_DIR);
|
|
99
|
+
if (fromProjectDir)
|
|
100
|
+
return fromProjectDir;
|
|
101
|
+
const fromPluginRoot = sanitizeEnvPath(process.env.CLAUDE_CRAP_PLUGIN_ROOT);
|
|
102
|
+
if (fromPluginRoot)
|
|
103
|
+
return fromPluginRoot;
|
|
104
|
+
const fromParent = sanitizeEnvPath(readParentCwd());
|
|
105
|
+
if (fromParent)
|
|
106
|
+
return fromParent;
|
|
107
|
+
return process.cwd();
|
|
108
|
+
}
|
|
17
109
|
/**
|
|
18
110
|
* Parse a numeric environment variable, falling back to `fallback` when the
|
|
19
111
|
* variable is undefined or empty. Throws if the value is present but not a
|
|
@@ -66,12 +158,7 @@ function parseRating(raw, fallback) {
|
|
|
66
158
|
*/
|
|
67
159
|
export function loadConfig() {
|
|
68
160
|
return {
|
|
69
|
-
|
|
70
|
-
// process.cwd() is NOT reliable — Claude Code sets it to the plugin
|
|
71
|
-
// cache directory when starting MCP servers, not the user's project.
|
|
72
|
-
pluginRoot: process.env.CLAUDE_PROJECT_DIR
|
|
73
|
-
?? process.env.CLAUDE_CRAP_PLUGIN_ROOT
|
|
74
|
-
?? process.cwd(),
|
|
161
|
+
pluginRoot: discoverWorkspaceRoot(),
|
|
75
162
|
sarifOutputDir: process.env.CLAUDE_CRAP_SARIF_OUTPUT_DIR ?? ".claude-crap/reports",
|
|
76
163
|
crapThreshold: parseNumber("CLAUDE_CRAP_CRAP_THRESHOLD", process.env.CLAUDE_CRAP_CRAP_THRESHOLD, 30),
|
|
77
164
|
cyclomaticMax: parseNumber("CLAUDE_CRAP_CYCLOMATIC_MAX", process.env.CLAUDE_CRAP_CYCLOMATIC_MAX, 15),
|
package/dist/config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAiCvC;;;;;GAKG;AACH,SAAS,oBAAoB,CAAC,KAAyB;IACrD,IAAI,KAAK,KAAK,SAAS;QAAE,OAAO,KAAK,CAAC;IACtC,OAAO,gCAAgC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAC7D,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,KAAyB;IAChD,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,SAAS,CAAC;IAC1D,IAAI,oBAAoB,CAAC,KAAK,CAAC;QAAE,OAAO,SAAS,CAAC;IAClD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,oBAAoB;IAC3B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QAC1B,IAAI,CAAC,IAAI,IAAI,IAAI,KAAK,CAAC;YAAE,OAAO,SAAS,CAAC;QAE1C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,qDAAqD;YACrD,OAAO,YAAY,CAAC,SAAS,IAAI,MAAM,CAAC,CAAC;QAC3C,CAAC;QAED,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAClC,mEAAmE;YACnE,+DAA+D;YAC/D,oBAAoB;YACpB,MAAM,MAAM,GAAG,YAAY,CACzB,MAAM,EACN,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,GAAG,CAAC,EAClD;gBACE,QAAQ,EAAE,MAAM;gBAChB,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC;gBACnC,OAAO,EAAE,IAAI;aACd,CACF,CAAC;YACF,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;YACvC,OAAO,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;QACpB,CAAC;QAED,yDAAyD;QACzD,OAAO,SAAS,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,SAAS,CAAC;IACnB,CAAC;AACH,CAAC;AAWD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,qBAAqB,CAAC,UAAoC,EAAE;IAC1E,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,oBAAoB,CAAC;IAEpE,MAAM,cAAc,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IACvE,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAE1C,MAAM,cAAc,GAAG,eAAe,CAAC,OAAO,CAAC,GAAG,CAAC,uBAAuB,CAAC,CAAC;IAC5E,IAAI,cAAc;QAAE,OAAO,cAAc,CAAC;IAE1C,MAAM,UAAU,GAAG,eAAe,CAAC,aAAa,EAAE,CAAC,CAAC;IACpD,IAAI,UAAU;QAAE,OAAO,UAAU,CAAC;IAElC,OAAO,OAAO,CAAC,GAAG,EAAE,CAAC;AACvB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,SAAS,WAAW,CAAC,IAAY,EAAE,GAAuB,EAAE,QAAgB;IAC1E,IAAI,GAAG,KAAK,SAAS,IAAI,GAAG,KAAK,EAAE;QAAE,OAAO,QAAQ,CAAC;IACrD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CAAC,qBAAqB,IAAI,KAAK,GAAG,0BAA0B,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,GAAuB,EAAE,QAA+B;IAC3E,IAAI,CAAC,GAAG;QAAE,OAAO,QAAQ,CAAC;IAC1B,MAAM,UAAU,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,IAAI,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,iCAAiC,GAAG,gCAAgC,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,UAAmC,CAAC;AAC7C,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO;QACL,UAAU,EAAE,qBAAqB,EAAE;QACnC,cAAc,EAAE,OAAO,CAAC,GAAG,CAAC,4BAA4B,IAAI,sBAAsB;QAClF,aAAa,EAAE,WAAW,CAAC,4BAA4B,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,EAAE,CAAC;QACpG,aAAa,EAAE,WAAW,CAAC,4BAA4B,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,EAAE,CAAC;QACpG,YAAY,EAAE,WAAW,CAAC,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,GAAG,CAAC;QACtE,aAAa,EAAE,WAAW,CAAC,6BAA6B,EAAE,OAAO,CAAC,GAAG,CAAC,2BAA2B,EAAE,EAAE,CAAC;QACtG,aAAa,EAAE,WAAW,CAAC,4BAA4B,EAAE,OAAO,CAAC,GAAG,CAAC,0BAA0B,EAAE,IAAI,CAAC;KACvG,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://code.claude.com/schemas/plugin.json",
|
|
3
3
|
"name": "claude-crap",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.4",
|
|
5
5
|
"description": "Deterministic Quality Assurance plugin for Claude Code. Wraps every Write / Edit / Bash tool call with a PreToolUse gatekeeper, a PostToolUse verifier, and a Stop quality gate backed by CRAP index, Technical Debt Ratio, tree-sitter AST metrics, and SARIF 2.1.0 reports. Forbids the agent from writing functional code before a test safety net exists.",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "Alan Hernandez",
|
package/plugin/.mcp.json
CHANGED
|
@@ -7358,6 +7358,52 @@ function countLines(source) {
|
|
|
7358
7358
|
}
|
|
7359
7359
|
|
|
7360
7360
|
// src/config.ts
|
|
7361
|
+
import { execFileSync } from "node:child_process";
|
|
7362
|
+
import { readlinkSync } from "node:fs";
|
|
7363
|
+
function isLiteralVarTemplate(value) {
|
|
7364
|
+
if (value === void 0) return false;
|
|
7365
|
+
return /^\$\{[A-Za-z_][A-Za-z0-9_]*\}$/.test(value.trim());
|
|
7366
|
+
}
|
|
7367
|
+
function sanitizeEnvPath(value) {
|
|
7368
|
+
if (value === void 0 || value === "") return void 0;
|
|
7369
|
+
if (isLiteralVarTemplate(value)) return void 0;
|
|
7370
|
+
return value;
|
|
7371
|
+
}
|
|
7372
|
+
function readParentCwdDefault() {
|
|
7373
|
+
try {
|
|
7374
|
+
const ppid = process.ppid;
|
|
7375
|
+
if (!ppid || ppid === 0) return void 0;
|
|
7376
|
+
if (process.platform === "linux") {
|
|
7377
|
+
return readlinkSync(`/proc/${ppid}/cwd`);
|
|
7378
|
+
}
|
|
7379
|
+
if (process.platform === "darwin") {
|
|
7380
|
+
const output = execFileSync(
|
|
7381
|
+
"lsof",
|
|
7382
|
+
["-a", "-p", String(ppid), "-d", "cwd", "-F", "n"],
|
|
7383
|
+
{
|
|
7384
|
+
encoding: "utf8",
|
|
7385
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
7386
|
+
timeout: 2e3
|
|
7387
|
+
}
|
|
7388
|
+
);
|
|
7389
|
+
const match = output.match(/^n(.+)$/m);
|
|
7390
|
+
return match?.[1];
|
|
7391
|
+
}
|
|
7392
|
+
return void 0;
|
|
7393
|
+
} catch {
|
|
7394
|
+
return void 0;
|
|
7395
|
+
}
|
|
7396
|
+
}
|
|
7397
|
+
function discoverWorkspaceRoot(options = {}) {
|
|
7398
|
+
const readParentCwd = options.readParentCwd ?? readParentCwdDefault;
|
|
7399
|
+
const fromProjectDir = sanitizeEnvPath(process.env.CLAUDE_PROJECT_DIR);
|
|
7400
|
+
if (fromProjectDir) return fromProjectDir;
|
|
7401
|
+
const fromPluginRoot = sanitizeEnvPath(process.env.CLAUDE_CRAP_PLUGIN_ROOT);
|
|
7402
|
+
if (fromPluginRoot) return fromPluginRoot;
|
|
7403
|
+
const fromParent = sanitizeEnvPath(readParentCwd());
|
|
7404
|
+
if (fromParent) return fromParent;
|
|
7405
|
+
return process.cwd();
|
|
7406
|
+
}
|
|
7361
7407
|
function parseNumber(name, raw, fallback) {
|
|
7362
7408
|
if (raw === void 0 || raw === "") return fallback;
|
|
7363
7409
|
const value = Number(raw);
|
|
@@ -7376,10 +7422,7 @@ function parseRating(raw, fallback) {
|
|
|
7376
7422
|
}
|
|
7377
7423
|
function loadConfig() {
|
|
7378
7424
|
return {
|
|
7379
|
-
|
|
7380
|
-
// process.cwd() is NOT reliable — Claude Code sets it to the plugin
|
|
7381
|
-
// cache directory when starting MCP servers, not the user's project.
|
|
7382
|
-
pluginRoot: process.env.CLAUDE_PROJECT_DIR ?? process.env.CLAUDE_CRAP_PLUGIN_ROOT ?? process.cwd(),
|
|
7425
|
+
pluginRoot: discoverWorkspaceRoot(),
|
|
7383
7426
|
sarifOutputDir: process.env.CLAUDE_CRAP_SARIF_OUTPUT_DIR ?? ".claude-crap/reports",
|
|
7384
7427
|
crapThreshold: parseNumber("CLAUDE_CRAP_CRAP_THRESHOLD", process.env.CLAUDE_CRAP_CRAP_THRESHOLD, 30),
|
|
7385
7428
|
cyclomaticMax: parseNumber("CLAUDE_CRAP_CYCLOMATIC_MAX", process.env.CLAUDE_CRAP_CYCLOMATIC_MAX, 15),
|