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/dist/sdk.js ADDED
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Root public SDK for `claude-crap`.
3
+ *
4
+ * This is the module you get when you do
5
+ * `import ... from "claude-crap"`. It is intentionally
6
+ * **side-effect-free**: importing this file does NOT start the MCP
7
+ * server, does NOT open the dashboard, does NOT touch the filesystem.
8
+ * Only the executable entrypoint in `dist/index.js` boots the
9
+ * server — that file is invoked by the `.mcp.json` command and the
10
+ * CLI bin, never as a library.
11
+ *
12
+ * Structure:
13
+ *
14
+ * - `./metrics` — CRAP, TDR, project score, workspace walker
15
+ * - `./sarif` — SARIF 2.1.0 builder and on-disk store
16
+ * - `./ast` — tree-sitter engine, cyclomatic complexity, language config
17
+ * - `./tools` — test-harness resolver used by `require_test_harness`
18
+ *
19
+ * Prefer deep imports
20
+ * (`import { computeCrap } from "claude-crap/metrics"`) over
21
+ * pulling everything through the root — they give TypeScript more
22
+ * precise type information and help tree-shakers drop unused modules.
23
+ *
24
+ * The symbols re-exported here are the ones most code paths need:
25
+ *
26
+ * - `computeCrap`, `computeTdr`, `computeProjectScore`
27
+ * - `renderProjectScoreMarkdown`
28
+ * - `classifyTdr`, `ratingIsWorseThan`
29
+ * - `SarifStore`, `buildSarifDocument`
30
+ * - `TreeSitterEngine`
31
+ *
32
+ * @module claude-crap
33
+ */
34
+ // --- metrics ---------------------------------------------------------------
35
+ export { computeCrap, computeTdr, classifyTdr, ratingIsWorseThan, ratingToRank, computeProjectScore, renderProjectScoreMarkdown, estimateWorkspaceLoc, } from "./metrics/index.js";
36
+ // --- sarif -----------------------------------------------------------------
37
+ export { SarifStore, buildSarifDocument } from "./sarif/index.js";
38
+ // --- ast -------------------------------------------------------------------
39
+ export { TreeSitterEngine, computeCyclomaticComplexity, detectLanguageFromPath, LANGUAGE_TABLE, } from "./ast/index.js";
40
+ // --- tools -----------------------------------------------------------------
41
+ export { findTestFile, isTestFile, candidatePaths } from "./tools/index.js";
42
+ // --- adapters --------------------------------------------------------------
43
+ export { adaptScannerOutput, adaptSemgrep, adaptEslint, adaptBandit, adaptStryker, KNOWN_SCANNERS, } from "./adapters/index.js";
44
+ //# sourceMappingURL=sdk.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sdk.js","sourceRoot":"","sources":["../src/sdk.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,8EAA8E;AAC9E,OAAO,EACL,WAAW,EACX,UAAU,EACV,WAAW,EACX,iBAAiB,EACjB,YAAY,EACZ,mBAAmB,EACnB,0BAA0B,EAC1B,oBAAoB,GACrB,MAAM,oBAAoB,CAAC;AAiB5B,8EAA8E;AAC9E,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAWlE,8EAA8E;AAC9E,OAAO,EACL,gBAAgB,EAChB,2BAA2B,EAC3B,sBAAsB,EACtB,cAAc,GACf,MAAM,gBAAgB,CAAC;AAWxB,8EAA8E;AAC9E,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAG5E,8EAA8E;AAC9E,OAAO,EACL,kBAAkB,EAClB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,YAAY,EACZ,cAAc,GACf,MAAM,qBAAqB,CAAC"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Public SDK entry point for the tool backends that sit behind
3
+ * the `claude-crap` MCP server.
4
+ *
5
+ * These are the same pure functions the MCP server calls into — the
6
+ * server layer just wraps them in JSON-RPC. Downstream consumers can
7
+ * reuse them directly from any Node.js context without running the
8
+ * MCP server.
9
+ *
10
+ * Usage:
11
+ *
12
+ * ```ts
13
+ * import {
14
+ * findTestFile,
15
+ * isTestFile,
16
+ * candidatePaths,
17
+ * } from "claude-crap/tools";
18
+ * ```
19
+ *
20
+ * @module tools
21
+ */
22
+ export { candidatePaths, findTestFile, isTestFile } from "./test-harness.js";
23
+ export type { TestFileResolution } from "./test-harness.js";
24
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAC7E,YAAY,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Public SDK entry point for the tool backends that sit behind
3
+ * the `claude-crap` MCP server.
4
+ *
5
+ * These are the same pure functions the MCP server calls into — the
6
+ * server layer just wraps them in JSON-RPC. Downstream consumers can
7
+ * reuse them directly from any Node.js context without running the
8
+ * MCP server.
9
+ *
10
+ * Usage:
11
+ *
12
+ * ```ts
13
+ * import {
14
+ * findTestFile,
15
+ * isTestFile,
16
+ * candidatePaths,
17
+ * } from "claude-crap/tools";
18
+ * ```
19
+ *
20
+ * @module tools
21
+ */
22
+ export { candidatePaths, findTestFile, isTestFile } from "./test-harness.js";
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAEH,OAAO,EAAE,cAAc,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Deterministic test-file resolver used by the `require_test_harness`
3
+ * MCP tool.
4
+ *
5
+ * Given a production source file (for example `src/foo/bar.ts`), this
6
+ * module enumerates the conventional locations where a matching test
7
+ * file would live and returns the first existing match — or `null` when
8
+ * none of the candidates exist.
9
+ *
10
+ * This is a TypeScript twin of `hooks/lib/test-harness.mjs`. The two
11
+ * are intentionally independent so neither side has to import files
12
+ * from outside its own project tree:
13
+ *
14
+ * - Hooks use the `.mjs` copy (vanilla JS, zero deps, runs everywhere).
15
+ * - The MCP server uses this typed copy so its consumers get full
16
+ * type safety and so the server stays a self-contained npm package.
17
+ *
18
+ * Both copies implement the same conventions and are validated against
19
+ * the same unit tests — see `src/tests/test-harness.test.ts`.
20
+ *
21
+ * @module tools/test-harness
22
+ */
23
+ /**
24
+ * Result of probing the filesystem for a matching test file.
25
+ */
26
+ export interface TestFileResolution {
27
+ /** Absolute path of the first matching test file, or `null` when none exists. */
28
+ readonly testFile: string | null;
29
+ /** Absolute paths of every location the resolver tried. */
30
+ readonly candidates: ReadonlyArray<string>;
31
+ /** `true` when the input path itself is a test file. */
32
+ readonly isTestFile: boolean;
33
+ }
34
+ /**
35
+ * Return `true` when the given path is already a test file. Matching is
36
+ * done against the basename (`foo.test.ts`, `test_foo.py`) and against
37
+ * common test directory names in the path (`__tests__`, `tests`, `test`).
38
+ *
39
+ * @param filePath An absolute or relative source path.
40
+ */
41
+ export declare function isTestFile(filePath: string): boolean;
42
+ /**
43
+ * Enumerate every plausible test file path for a given production source
44
+ * file. Does not touch the filesystem — the caller is expected to probe
45
+ * existence separately (see {@link findTestFile}).
46
+ *
47
+ * Supported conventions, in the order they are probed:
48
+ *
49
+ * 1. Sibling `<base>.test.<ext>` / `<base>.spec.<ext>`
50
+ * 2. Sibling `__tests__/<base>.test.<ext>`
51
+ * 3. Mirror tree under `tests/`, `test/`, or `__tests__/` at the
52
+ * workspace root (e.g. `tests/src/foo/bar.test.ts`)
53
+ * 4. **Nearest-ancestor flat test directory**: walk up from the source
54
+ * file's directory toward the workspace root, and at every ancestor
55
+ * check for `tests/<base>.test.<ext>`. Matches layouts where tests
56
+ * live in a single flat directory near the source (this project
57
+ * uses it for `src/mcp-server/src/tests/`).
58
+ * 5. Python-specific: sibling `test_<base>.py` and mirror-tree
59
+ * `tests/.../test_<base>.py`.
60
+ *
61
+ * @param workspaceRoot Absolute workspace root.
62
+ * @param filePath Absolute path to the production file.
63
+ * @returns Ordered list of absolute candidate paths.
64
+ */
65
+ export declare function candidatePaths(workspaceRoot: string, filePath: string): ReadonlyArray<string>;
66
+ /**
67
+ * Probe the filesystem and return the first candidate that exists, or
68
+ * `null` when none of them do. Returns early with `isTestFile: true`
69
+ * when the input is already a test file.
70
+ *
71
+ * @param workspaceRoot Absolute workspace root.
72
+ * @param filePath Absolute or relative path to the production file.
73
+ */
74
+ export declare function findTestFile(workspaceRoot: string, filePath: string): Promise<TestFileResolution>;
75
+ //# sourceMappingURL=test-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-harness.d.ts","sourceRoot":"","sources":["../../src/tools/test-harness.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAKH;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,iFAAiF;IACjF,QAAQ,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,2DAA2D;IAC3D,QAAQ,CAAC,UAAU,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC3C,wDAAwD;IACxD,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;CAC9B;AAKD;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAMpD;AAED;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,cAAc,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,CAAC,MAAM,CAAC,CAkD7F;AAED;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,CAAC,CAe7B"}
@@ -0,0 +1,137 @@
1
+ /**
2
+ * Deterministic test-file resolver used by the `require_test_harness`
3
+ * MCP tool.
4
+ *
5
+ * Given a production source file (for example `src/foo/bar.ts`), this
6
+ * module enumerates the conventional locations where a matching test
7
+ * file would live and returns the first existing match — or `null` when
8
+ * none of the candidates exist.
9
+ *
10
+ * This is a TypeScript twin of `hooks/lib/test-harness.mjs`. The two
11
+ * are intentionally independent so neither side has to import files
12
+ * from outside its own project tree:
13
+ *
14
+ * - Hooks use the `.mjs` copy (vanilla JS, zero deps, runs everywhere).
15
+ * - The MCP server uses this typed copy so its consumers get full
16
+ * type safety and so the server stays a self-contained npm package.
17
+ *
18
+ * Both copies implement the same conventions and are validated against
19
+ * the same unit tests — see `src/tests/test-harness.test.ts`.
20
+ *
21
+ * @module tools/test-harness
22
+ */
23
+ import { promises as fs } from "node:fs";
24
+ import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
25
+ /** Matches `.test.` and `.spec.` suffixes inside a file basename. */
26
+ const TEST_SUFFIX_PATTERN = /\.(test|spec)\./;
27
+ /**
28
+ * Return `true` when the given path is already a test file. Matching is
29
+ * done against the basename (`foo.test.ts`, `test_foo.py`) and against
30
+ * common test directory names in the path (`__tests__`, `tests`, `test`).
31
+ *
32
+ * @param filePath An absolute or relative source path.
33
+ */
34
+ export function isTestFile(filePath) {
35
+ const base = basename(filePath);
36
+ if (TEST_SUFFIX_PATTERN.test(base))
37
+ return true;
38
+ if (base.startsWith("test_") && base.endsWith(".py"))
39
+ return true;
40
+ const parts = filePath.split(sep);
41
+ return parts.includes("__tests__") || parts.includes("tests") || parts.includes("test");
42
+ }
43
+ /**
44
+ * Enumerate every plausible test file path for a given production source
45
+ * file. Does not touch the filesystem — the caller is expected to probe
46
+ * existence separately (see {@link findTestFile}).
47
+ *
48
+ * Supported conventions, in the order they are probed:
49
+ *
50
+ * 1. Sibling `<base>.test.<ext>` / `<base>.spec.<ext>`
51
+ * 2. Sibling `__tests__/<base>.test.<ext>`
52
+ * 3. Mirror tree under `tests/`, `test/`, or `__tests__/` at the
53
+ * workspace root (e.g. `tests/src/foo/bar.test.ts`)
54
+ * 4. **Nearest-ancestor flat test directory**: walk up from the source
55
+ * file's directory toward the workspace root, and at every ancestor
56
+ * check for `tests/<base>.test.<ext>`. Matches layouts where tests
57
+ * live in a single flat directory near the source (this project
58
+ * uses it for `src/mcp-server/src/tests/`).
59
+ * 5. Python-specific: sibling `test_<base>.py` and mirror-tree
60
+ * `tests/.../test_<base>.py`.
61
+ *
62
+ * @param workspaceRoot Absolute workspace root.
63
+ * @param filePath Absolute path to the production file.
64
+ * @returns Ordered list of absolute candidate paths.
65
+ */
66
+ export function candidatePaths(workspaceRoot, filePath) {
67
+ const absSource = resolve(filePath);
68
+ const ext = extname(absSource);
69
+ const base = basename(absSource, ext);
70
+ const dir = dirname(absSource);
71
+ const absWorkspace = resolve(workspaceRoot);
72
+ const relFromRoot = relative(absWorkspace, absSource);
73
+ const relDir = dirname(relFromRoot);
74
+ const candidates = new Set();
75
+ // 1. Sibling <base>.test.<ext> / <base>.spec.<ext>
76
+ candidates.add(join(dir, `${base}.test${ext}`));
77
+ candidates.add(join(dir, `${base}.spec${ext}`));
78
+ // 2. Sibling __tests__/<base>.test.<ext>
79
+ candidates.add(join(dir, "__tests__", `${base}.test${ext}`));
80
+ candidates.add(join(dir, "__tests__", `${base}.spec${ext}`));
81
+ // 3. Mirror tree under tests/, test/, or __tests__ at the workspace root.
82
+ for (const testRoot of ["tests", "test", "__tests__"]) {
83
+ candidates.add(join(absWorkspace, testRoot, relDir, `${base}.test${ext}`));
84
+ candidates.add(join(absWorkspace, testRoot, relDir, `${base}.spec${ext}`));
85
+ candidates.add(join(absWorkspace, testRoot, relDir, `${base}${ext}`));
86
+ }
87
+ // 4. Nearest-ancestor flat test directory. Walk up from `dir` to
88
+ // `absWorkspace`, and at each ancestor probe for a flat
89
+ // `tests/<base>.test.<ext>` (or `test/`, `__tests__/`) layout.
90
+ let current = dir;
91
+ while (current.length >= absWorkspace.length) {
92
+ for (const testRoot of ["tests", "test", "__tests__"]) {
93
+ candidates.add(join(current, testRoot, `${base}.test${ext}`));
94
+ candidates.add(join(current, testRoot, `${base}.spec${ext}`));
95
+ candidates.add(join(current, testRoot, `${base}${ext}`));
96
+ }
97
+ if (current === absWorkspace)
98
+ break;
99
+ const parent = dirname(current);
100
+ if (parent === current)
101
+ break; // reached filesystem root, stop
102
+ current = parent;
103
+ }
104
+ // 5. Python-specific variants.
105
+ if (ext === ".py") {
106
+ candidates.add(join(dir, `test_${base}.py`));
107
+ candidates.add(join(absWorkspace, "tests", `test_${base}.py`));
108
+ candidates.add(join(absWorkspace, "tests", relDir, `test_${base}.py`));
109
+ }
110
+ return Array.from(candidates);
111
+ }
112
+ /**
113
+ * Probe the filesystem and return the first candidate that exists, or
114
+ * `null` when none of them do. Returns early with `isTestFile: true`
115
+ * when the input is already a test file.
116
+ *
117
+ * @param workspaceRoot Absolute workspace root.
118
+ * @param filePath Absolute or relative path to the production file.
119
+ */
120
+ export async function findTestFile(workspaceRoot, filePath) {
121
+ const absolute = isAbsolute(filePath) ? filePath : resolve(workspaceRoot, filePath);
122
+ if (isTestFile(absolute)) {
123
+ return { testFile: absolute, candidates: [absolute], isTestFile: true };
124
+ }
125
+ const candidates = candidatePaths(workspaceRoot, absolute);
126
+ for (const candidate of candidates) {
127
+ try {
128
+ await fs.access(candidate);
129
+ return { testFile: candidate, candidates, isTestFile: false };
130
+ }
131
+ catch {
132
+ // Probe next candidate.
133
+ }
134
+ }
135
+ return { testFile: null, candidates, isTestFile: false };
136
+ }
137
+ //# sourceMappingURL=test-harness.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"test-harness.js","sourceRoot":"","sources":["../../src/tools/test-harness.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Workspace path containment guard.
3
+ *
4
+ * Every MCP tool that accepts a user-supplied file path routes it
5
+ * through {@link resolveWithinWorkspace} before touching the
6
+ * filesystem. The guard rejects any resolved absolute path that is
7
+ * outside the configured workspace root, so the agent cannot be
8
+ * tricked (via prompt injection through scanner output, or via its
9
+ * own confusion) into reading files that live next to the project
10
+ * but outside it.
11
+ *
12
+ * F-A01-01: the original guard in `src/index.ts` used a naive
13
+ * `candidate.startsWith(workspace)` check, which suffers from prefix
14
+ * confusion — for a workspace like `/Users/x/claude-crap`, an
15
+ * absolute input path such as `/Users/x/claude-crap-evil/secret.ts`
16
+ * would pass the check because the two share the literal prefix up
17
+ * to the final segment. This module replaces that check with a
18
+ * separator-aware comparison: the candidate is only accepted if it
19
+ * equals the workspace exactly OR begins with `workspace + sep`.
20
+ *
21
+ * This module is intentionally pure (no I/O, no global state) so it
22
+ * can be unit-tested without any fixtures.
23
+ *
24
+ * @module workspace-guard
25
+ */
26
+ /**
27
+ * Resolve a user-supplied file path against a workspace root, returning
28
+ * the absolute path only if it is contained inside the root. Throws a
29
+ * descriptive error when the resolved candidate escapes the workspace.
30
+ *
31
+ * Rules enforced (must stay in sync with
32
+ * `src/tests/workspace-guard.test.ts`):
33
+ *
34
+ * 1. Relative paths are resolved against `workspaceRoot`.
35
+ * 2. Absolute paths are accepted as-is for resolution.
36
+ * 3. The resolved candidate must equal `workspaceRoot` OR begin with
37
+ * `workspaceRoot + sep`. Sibling directories that merely share a
38
+ * prefix (e.g. `/tmp/workspace-evil` vs `/tmp/workspace`) are
39
+ * rejected.
40
+ * 4. The comparison uses the platform's native path separator, so
41
+ * the guard works on both POSIX and Windows.
42
+ *
43
+ * @param workspaceRoot Absolute or relative path to the workspace root.
44
+ * Non-absolute values are resolved against the
45
+ * current working directory, which matches the
46
+ * behavior of the previous in-lined guard.
47
+ * @param filePath User-supplied path. May be absolute or relative
48
+ * to the workspace root.
49
+ * @returns The absolute, workspace-contained path.
50
+ * @throws `Error` when the candidate escapes the workspace.
51
+ */
52
+ export declare function resolveWithinWorkspace(workspaceRoot: string, filePath: string): string;
53
+ //# sourceMappingURL=workspace-guard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-guard.d.ts","sourceRoot":"","sources":["../src/workspace-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,sBAAsB,CAAC,aAAa,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAStF"}
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Workspace path containment guard.
3
+ *
4
+ * Every MCP tool that accepts a user-supplied file path routes it
5
+ * through {@link resolveWithinWorkspace} before touching the
6
+ * filesystem. The guard rejects any resolved absolute path that is
7
+ * outside the configured workspace root, so the agent cannot be
8
+ * tricked (via prompt injection through scanner output, or via its
9
+ * own confusion) into reading files that live next to the project
10
+ * but outside it.
11
+ *
12
+ * F-A01-01: the original guard in `src/index.ts` used a naive
13
+ * `candidate.startsWith(workspace)` check, which suffers from prefix
14
+ * confusion — for a workspace like `/Users/x/claude-crap`, an
15
+ * absolute input path such as `/Users/x/claude-crap-evil/secret.ts`
16
+ * would pass the check because the two share the literal prefix up
17
+ * to the final segment. This module replaces that check with a
18
+ * separator-aware comparison: the candidate is only accepted if it
19
+ * equals the workspace exactly OR begins with `workspace + sep`.
20
+ *
21
+ * This module is intentionally pure (no I/O, no global state) so it
22
+ * can be unit-tested without any fixtures.
23
+ *
24
+ * @module workspace-guard
25
+ */
26
+ import { isAbsolute, resolve, sep } from "node:path";
27
+ /**
28
+ * Resolve a user-supplied file path against a workspace root, returning
29
+ * the absolute path only if it is contained inside the root. Throws a
30
+ * descriptive error when the resolved candidate escapes the workspace.
31
+ *
32
+ * Rules enforced (must stay in sync with
33
+ * `src/tests/workspace-guard.test.ts`):
34
+ *
35
+ * 1. Relative paths are resolved against `workspaceRoot`.
36
+ * 2. Absolute paths are accepted as-is for resolution.
37
+ * 3. The resolved candidate must equal `workspaceRoot` OR begin with
38
+ * `workspaceRoot + sep`. Sibling directories that merely share a
39
+ * prefix (e.g. `/tmp/workspace-evil` vs `/tmp/workspace`) are
40
+ * rejected.
41
+ * 4. The comparison uses the platform's native path separator, so
42
+ * the guard works on both POSIX and Windows.
43
+ *
44
+ * @param workspaceRoot Absolute or relative path to the workspace root.
45
+ * Non-absolute values are resolved against the
46
+ * current working directory, which matches the
47
+ * behavior of the previous in-lined guard.
48
+ * @param filePath User-supplied path. May be absolute or relative
49
+ * to the workspace root.
50
+ * @returns The absolute, workspace-contained path.
51
+ * @throws `Error` when the candidate escapes the workspace.
52
+ */
53
+ export function resolveWithinWorkspace(workspaceRoot, filePath) {
54
+ const workspace = resolve(workspaceRoot);
55
+ const candidate = isAbsolute(filePath) ? resolve(filePath) : resolve(workspace, filePath);
56
+ if (candidate !== workspace && !candidate.startsWith(workspace + sep)) {
57
+ throw new Error(`[claude-crap] Refusing to access '${filePath}' — path escapes the workspace root`);
58
+ }
59
+ return candidate;
60
+ }
61
+ //# sourceMappingURL=workspace-guard.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace-guard.js","sourceRoot":"","sources":["../src/workspace-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,UAAU,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAErD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,UAAU,sBAAsB,CAAC,aAAqB,EAAE,QAAgB;IAC5E,MAAM,SAAS,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACzC,MAAM,SAAS,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;IAC1F,IAAI,SAAS,KAAK,SAAS,IAAI,CAAC,SAAS,CAAC,UAAU,CAAC,SAAS,GAAG,GAAG,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,KAAK,CACb,qCAAqC,QAAQ,qCAAqC,CACnF,CAAC;IACJ,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC"}
package/package.json ADDED
@@ -0,0 +1,133 @@
1
+ {
2
+ "name": "claude-crap",
3
+ "version": "0.1.2",
4
+ "description": "Deterministic QA plugin for Claude Code — CRAP index, Technical Debt Ratio, tree-sitter AST, SARIF 2.1.0, hooks, and a local Vue dashboard.",
5
+ "keywords": [
6
+ "claude-code",
7
+ "claude-code-plugin",
8
+ "quality-assurance",
9
+ "sast",
10
+ "sarif",
11
+ "crap-index",
12
+ "technical-debt",
13
+ "quality-gate",
14
+ "shift-left",
15
+ "deterministic",
16
+ "mutation-testing",
17
+ "mcp-server",
18
+ "mcp",
19
+ "tree-sitter"
20
+ ],
21
+ "homepage": "https://github.com/ahernandez-developer/claude-crap",
22
+ "bugs": {
23
+ "url": "https://github.com/ahernandez-developer/claude-crap/issues"
24
+ },
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/ahernandez-developer/claude-crap.git"
28
+ },
29
+ "license": "MIT",
30
+ "author": "Alan Hernandez",
31
+ "type": "module",
32
+ "main": "./dist/sdk.js",
33
+ "types": "./dist/sdk.d.ts",
34
+ "bin": {
35
+ "claude-crap": "./bin/claude-crap.mjs"
36
+ },
37
+ "exports": {
38
+ ".": {
39
+ "types": "./dist/sdk.d.ts",
40
+ "import": "./dist/sdk.js"
41
+ },
42
+ "./metrics": {
43
+ "types": "./dist/metrics/index.d.ts",
44
+ "import": "./dist/metrics/index.js"
45
+ },
46
+ "./sarif": {
47
+ "types": "./dist/sarif/index.d.ts",
48
+ "import": "./dist/sarif/index.js"
49
+ },
50
+ "./ast": {
51
+ "types": "./dist/ast/index.d.ts",
52
+ "import": "./dist/ast/index.js"
53
+ },
54
+ "./tools": {
55
+ "types": "./dist/tools/index.d.ts",
56
+ "import": "./dist/tools/index.js"
57
+ },
58
+ "./adapters": {
59
+ "types": "./dist/adapters/index.d.ts",
60
+ "import": "./dist/adapters/index.js"
61
+ },
62
+ "./package.json": "./package.json"
63
+ },
64
+ "engines": {
65
+ "node": ">=20.0.0",
66
+ "bun": ">=1.0.0"
67
+ },
68
+ "files": [
69
+ "CHANGELOG.md",
70
+ "CLAUDE.md",
71
+ "LICENSE",
72
+ "README.md",
73
+ "bin",
74
+ "dist",
75
+ "plugin",
76
+ "scripts",
77
+ "src",
78
+ "tsconfig.json"
79
+ ],
80
+ "scripts": {
81
+ "build": "tsc -p tsconfig.json",
82
+ "build:fast": "node ./scripts/build-fast.mjs",
83
+ "build:watch": "tsc -p tsconfig.json --watch",
84
+ "start": "node ./plugin/bundle/mcp-server.mjs --transport stdio",
85
+ "dev": "tsx ./src/index.ts --transport stdio",
86
+ "typecheck": "tsc -p tsconfig.json --noEmit",
87
+ "test": "node ./scripts/run-tests.mjs \"./src/tests/**/*.test.ts\"",
88
+ "test:metrics": "node ./scripts/run-tests.mjs \"./src/tests/crap.test.ts\" \"./src/tests/tdr.test.ts\" \"./src/tests/score.test.ts\"",
89
+ "test:sarif": "node ./scripts/run-tests.mjs \"./src/tests/sarif-store.test.ts\" \"./src/tests/sarif-validator.test.ts\"",
90
+ "test:ast": "node ./scripts/run-tests.mjs \"./src/tests/cyclomatic.test.ts\"",
91
+ "test:harness": "node ./scripts/run-tests.mjs \"./src/tests/test-harness.test.ts\"",
92
+ "test:adapters": "node ./scripts/run-tests.mjs \"./src/tests/adapters/**/*.test.ts\"",
93
+ "test:integration": "node ./scripts/run-tests.mjs \"./src/tests/integration/**/*.test.ts\"",
94
+ "doctor": "node ./bin/claude-crap.mjs doctor",
95
+ "status": "node ./bin/claude-crap.mjs status",
96
+ "bug-report": "node ./scripts/bug-report.mjs",
97
+ "clean": "rm -rf ./dist",
98
+ "build:plugin": "node ./scripts/bundle-plugin.mjs",
99
+ "postinstall": "node ./scripts/postinstall.mjs",
100
+ "prepublishOnly": "npm run clean && npm run build && npm run build:plugin && npm test && npm audit --omit=dev --audit-level=high",
101
+ "release": "np",
102
+ "release:patch": "np patch --no-cleanup",
103
+ "release:minor": "np minor --no-cleanup",
104
+ "release:major": "np major --no-cleanup"
105
+ },
106
+ "np": {
107
+ "yarn": false,
108
+ "contents": ".",
109
+ "testScript": "test",
110
+ "2fa": false
111
+ },
112
+ "publishConfig": {
113
+ "access": "public"
114
+ },
115
+ "dependencies": {
116
+ "@fastify/static": "^8.0.3",
117
+ "@modelcontextprotocol/sdk": "^1.0.4",
118
+ "ajv": "^8.17.1",
119
+ "ajv-formats": "^3.0.1",
120
+ "fast-glob": "^3.3.2",
121
+ "fastify": "^5.2.0",
122
+ "pino": "^9.5.0",
123
+ "tree-sitter-wasms": "^0.1.12",
124
+ "web-tree-sitter": "^0.24.4"
125
+ },
126
+ "devDependencies": {
127
+ "@types/node": "^22.10.2",
128
+ "esbuild": "^0.28.0",
129
+ "np": "^11.0.2",
130
+ "tsx": "^4.19.2",
131
+ "typescript": "^5.7.2"
132
+ }
133
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "$schema": "https://code.claude.com/schemas/plugin.json",
3
+ "name": "claude-crap",
4
+ "version": "0.1.2",
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
+ "author": {
7
+ "name": "Alan Hernandez",
8
+ "url": "https://github.com/ahernandez-developer"
9
+ },
10
+ "homepage": "https://github.com/ahernandez-developer/claude-crap",
11
+ "repository": "https://github.com/ahernandez-developer/claude-crap",
12
+ "license": "MIT",
13
+ "keywords": [
14
+ "claude-code",
15
+ "claude-code-plugin",
16
+ "quality-assurance",
17
+ "sast",
18
+ "sarif",
19
+ "crap-index",
20
+ "technical-debt",
21
+ "quality-gate",
22
+ "shift-left",
23
+ "deterministic",
24
+ "mutation-testing",
25
+ "mcp-server",
26
+ "mcp",
27
+ "tree-sitter"
28
+ ]
29
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://code.claude.com/schemas/mcp.json",
3
+ "mcpServers": {
4
+ "claude-crap": {
5
+ "type": "stdio",
6
+ "command": "node",
7
+ "args": [
8
+ "${CLAUDE_PLUGIN_ROOT}/bundle/mcp-server.mjs",
9
+ "--transport",
10
+ "stdio"
11
+ ],
12
+ "env": {
13
+ "NODE_ENV": "production",
14
+ "CLAUDE_CRAP_PLUGIN_ROOT": "${CLAUDE_PLUGIN_ROOT}"
15
+ }
16
+ }
17
+ }
18
+ }