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
@@ -0,0 +1,270 @@
1
+ /**
2
+ * Tree-sitter based AST analysis engine.
3
+ *
4
+ * This module wraps `web-tree-sitter` (the WASM build of tree-sitter) to
5
+ * parse source files and extract deterministic per-function metrics. The
6
+ * WASM variant is used instead of the native bindings so that `npm install`
7
+ * never has to invoke a C compiler — matching the plugin's "zero install
8
+ * friction" promise.
9
+ *
10
+ * The engine is lazy:
11
+ *
12
+ * - `web-tree-sitter` is initialized only on first use.
13
+ * - Grammar WASM files are loaded on demand and cached per language.
14
+ *
15
+ * This keeps MCP server startup fast (crucial because Claude Code will
16
+ * spin the server up and tear it down across sessions).
17
+ *
18
+ * Usage:
19
+ *
20
+ * ```ts
21
+ * const engine = new TreeSitterEngine();
22
+ * const result = await engine.analyzeFile({
23
+ * filePath: "src/foo.ts",
24
+ * language: "typescript",
25
+ * });
26
+ * console.log(result.functions);
27
+ * ```
28
+ *
29
+ * @module ast/tree-sitter-engine
30
+ */
31
+ import { promises as fs } from "node:fs";
32
+ import { createRequire } from "node:module";
33
+ import { dirname, join, resolve } from "node:path";
34
+ import { fileURLToPath } from "node:url";
35
+ import { computeCyclomaticComplexity } from "./cyclomatic.js";
36
+ import { LANGUAGE_TABLE } from "./language-config.js";
37
+ /**
38
+ * High-level AST engine. Instances are meant to be long-lived — create
39
+ * one at server startup and reuse it for every analysis request.
40
+ */
41
+ export class TreeSitterEngine {
42
+ parserCtor = null;
43
+ loadedLanguages = new Map();
44
+ grammarsDir;
45
+ runtimeDir;
46
+ loadGrammar;
47
+ initPromise = null;
48
+ constructor(options = {}) {
49
+ this.grammarsDir = options.grammarsDir ?? resolveDefaultGrammarsDir();
50
+ this.runtimeDir = options.runtimeDir ?? resolveDefaultRuntimeDir();
51
+ this.loadGrammar = options.loadGrammar ?? ((path) => fs.readFile(path));
52
+ }
53
+ /**
54
+ * Analyze a source file and return per-function and file-level metrics.
55
+ *
56
+ * @param req The analysis request.
57
+ * @returns A {@link FileMetrics} snapshot ready to be serialized.
58
+ * @throws When the file cannot be read or the grammar cannot be loaded.
59
+ */
60
+ async analyzeFile(req) {
61
+ const languageConfig = LANGUAGE_TABLE[req.language];
62
+ if (!languageConfig) {
63
+ throw new Error(`[tree-sitter-engine] Unsupported language: ${req.language}`);
64
+ }
65
+ const source = await fs.readFile(req.filePath, "utf8");
66
+ const parser = await this.ensureParserFor(languageConfig);
67
+ const tree = parser.parse(source);
68
+ const functions = collectFunctionMetrics(tree.rootNode, languageConfig);
69
+ const { physicalLoc, logicalLoc } = countLines(source);
70
+ return {
71
+ filePath: req.filePath,
72
+ language: languageConfig.id,
73
+ physicalLoc,
74
+ logicalLoc,
75
+ functions,
76
+ };
77
+ }
78
+ /**
79
+ * Ensure a parser with the requested language grammar bound is ready.
80
+ * Both the Parser class and the grammar are initialized lazily and
81
+ * cached on first use.
82
+ *
83
+ * @param config Language configuration for the requested grammar.
84
+ * @returns A fresh parser instance configured for the language.
85
+ */
86
+ async ensureParserFor(config) {
87
+ if (!this.parserCtor) {
88
+ if (!this.initPromise) {
89
+ this.initPromise = this.initParserModule();
90
+ }
91
+ await this.initPromise;
92
+ }
93
+ const Parser = this.parserCtor;
94
+ if (!Parser) {
95
+ throw new Error("[tree-sitter-engine] Parser class failed to initialize");
96
+ }
97
+ let language = this.loadedLanguages.get(config.id);
98
+ if (!language) {
99
+ const wasmPath = join(this.grammarsDir, config.wasmName);
100
+ const bytes = await this.loadGrammar(wasmPath);
101
+ language = await Parser.Language.load(bytes);
102
+ this.loadedLanguages.set(config.id, language);
103
+ }
104
+ const parser = new Parser();
105
+ parser.setLanguage(language);
106
+ return parser;
107
+ }
108
+ /**
109
+ * Import and initialize `web-tree-sitter`. Isolated in its own method
110
+ * so the dynamic import runs exactly once per engine instance.
111
+ *
112
+ * `web-tree-sitter` uses `export = Parser` so under ESM interop the
113
+ * Parser class arrives on the `default` property of the imported
114
+ * namespace. `Parser.init()` is a STATIC method on the class, not a
115
+ * top-level module function.
116
+ */
117
+ async initParserModule() {
118
+ const imported = (await import("web-tree-sitter"));
119
+ const Parser = imported.default;
120
+ if (!Parser || typeof Parser.init !== "function") {
121
+ throw new Error("[tree-sitter-engine] web-tree-sitter did not expose the expected Parser class");
122
+ }
123
+ // Emscripten calls `locateFile` to resolve the runtime WASM during
124
+ // `Parser.init()`. The runtime file (`tree-sitter.wasm`) lives inside
125
+ // the `web-tree-sitter` package itself, NOT alongside the grammars,
126
+ // so we route requests for that exact filename to `runtimeDir`.
127
+ // Anything else falls back to `grammarsDir` for the per-language
128
+ // grammar files loaded later by `Parser.Language.load()`.
129
+ await Parser.init({
130
+ locateFile: (name) => name === "tree-sitter.wasm"
131
+ ? join(this.runtimeDir, name)
132
+ : join(this.grammarsDir, name),
133
+ });
134
+ this.parserCtor = Parser;
135
+ }
136
+ }
137
+ /**
138
+ * Resolve the default grammar directory to `tree-sitter-wasms/out` inside
139
+ * `node_modules`. Uses `createRequire` so the lookup works regardless of
140
+ * whether the caller is running from source (`tsx`) or from the built
141
+ * `dist/` directory.
142
+ */
143
+ function resolveDefaultGrammarsDir() {
144
+ try {
145
+ const requireFromHere = createRequire(import.meta.url);
146
+ // `tree-sitter-wasms` exposes its grammar files under `out/`.
147
+ const pkgJsonPath = requireFromHere.resolve("tree-sitter-wasms/package.json");
148
+ return join(dirname(pkgJsonPath), "out");
149
+ }
150
+ catch {
151
+ // Fall back to a sibling `grammars/` directory if the npm package
152
+ // is not installed — useful for repo-local grammars.
153
+ const here = dirname(fileURLToPath(import.meta.url));
154
+ return resolve(here, "..", "..", "grammars");
155
+ }
156
+ }
157
+ /**
158
+ * Resolve the default runtime directory to the `web-tree-sitter` package
159
+ * root inside `node_modules`. The runtime WASM (`tree-sitter.wasm`) ships
160
+ * with the `web-tree-sitter` package itself rather than with the grammar
161
+ * package, so we have to look it up separately from the grammars.
162
+ */
163
+ function resolveDefaultRuntimeDir() {
164
+ try {
165
+ const requireFromHere = createRequire(import.meta.url);
166
+ const pkgJsonPath = requireFromHere.resolve("web-tree-sitter/package.json");
167
+ return dirname(pkgJsonPath);
168
+ }
169
+ catch {
170
+ // Fall back to the grammars directory — better than nothing if
171
+ // someone is running with a custom layout.
172
+ return resolveDefaultGrammarsDir();
173
+ }
174
+ }
175
+ /**
176
+ * Walk the top-level AST and collect metrics for every function node,
177
+ * including nested functions. The caller gets a flat, line-sorted list.
178
+ *
179
+ * @param root AST root node returned by tree-sitter.
180
+ * @param languageConfig Language tables to classify nodes.
181
+ * @returns Flat list of function metrics sorted by start line.
182
+ */
183
+ function collectFunctionMetrics(root, languageConfig) {
184
+ const out = [];
185
+ function visit(node) {
186
+ if (languageConfig.functionNodeTypes.has(node.type)) {
187
+ out.push(buildFunctionMetrics(node, languageConfig));
188
+ // Intentionally continue the walk so nested functions are also
189
+ // reported. Each nested function's complexity is computed against
190
+ // its own subtree; cyclomatic.ts skips nested functions during
191
+ // the walk so the parent's score is not inflated.
192
+ }
193
+ for (let i = 0; i < node.childCount; i++) {
194
+ const child = node.child(i);
195
+ if (child)
196
+ visit(child);
197
+ }
198
+ }
199
+ visit(root);
200
+ out.sort((a, b) => a.startLine - b.startLine);
201
+ return out;
202
+ }
203
+ /**
204
+ * Build a {@link FunctionMetrics} record for a single function node.
205
+ */
206
+ function buildFunctionMetrics(node, languageConfig) {
207
+ const name = extractFunctionName(node, languageConfig);
208
+ const position = extractPosition(node);
209
+ const complexity = computeCyclomaticComplexity(node, languageConfig);
210
+ return {
211
+ name,
212
+ startLine: position.startLine,
213
+ endLine: position.endLine,
214
+ cyclomaticComplexity: complexity,
215
+ lineCount: position.endLine - position.startLine + 1,
216
+ };
217
+ }
218
+ /**
219
+ * Pull the function name out of a function node. Tree-sitter exposes a
220
+ * `childForFieldName` accessor on its real nodes; we feature-detect it
221
+ * because our minimal {@link AstNode} contract does not require it.
222
+ */
223
+ function extractFunctionName(node, languageConfig) {
224
+ const anyNode = node;
225
+ if (typeof anyNode.childForFieldName === "function") {
226
+ for (const field of languageConfig.nameFieldCandidates) {
227
+ const nameNode = anyNode.childForFieldName(field);
228
+ if (nameNode && nameNode.text)
229
+ return nameNode.text;
230
+ }
231
+ }
232
+ return "<anonymous>";
233
+ }
234
+ /**
235
+ * Extract 1-based start/end line numbers for a node. Tree-sitter nodes
236
+ * expose `startPosition` and `endPosition` with zero-based rows.
237
+ */
238
+ function extractPosition(node) {
239
+ const anyNode = node;
240
+ const startRow = anyNode.startPosition?.row ?? 0;
241
+ const endRow = anyNode.endPosition?.row ?? startRow;
242
+ return { startLine: startRow + 1, endLine: endRow + 1 };
243
+ }
244
+ /**
245
+ * Count physical and logical lines of code in a raw source string.
246
+ *
247
+ * - **Physical LOC**: number of newline-separated lines, matching how
248
+ * most IDEs report file length.
249
+ * - **Logical LOC**: number of lines that contain at least one
250
+ * non-whitespace character.
251
+ *
252
+ * Comment detection is intentionally NOT done here; comment stripping
253
+ * requires language-aware parsing and the caller can derive a comment
254
+ * ratio from the AST node list if needed.
255
+ *
256
+ * @param source Raw source text.
257
+ * @returns An object with `physicalLoc` and `logicalLoc`.
258
+ */
259
+ function countLines(source) {
260
+ if (source.length === 0)
261
+ return { physicalLoc: 0, logicalLoc: 0 };
262
+ const lines = source.split(/\r?\n/);
263
+ let logical = 0;
264
+ for (const line of lines) {
265
+ if (line.trim().length > 0)
266
+ logical += 1;
267
+ }
268
+ return { physicalLoc: lines.length, logicalLoc: logical };
269
+ }
270
+ //# sourceMappingURL=tree-sitter-engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-sitter-engine.js","sourceRoot":"","sources":["../../src/ast/tree-sitter-engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Deterministic configuration loader for the claude-crap MCP server.
3
+ *
4
+ * Every tunable knob is read from environment variables that are injected
5
+ * by `.mcp.json` at server startup. Those variables are themselves derived
6
+ * from the `CLAUDE_PLUGIN_OPTION_*` values defined in the plugin manifest,
7
+ * which means the configuration chain is:
8
+ *
9
+ * user settings → plugin.json "options" → .mcp.json "env" → this file
10
+ *
11
+ * If any environment variable is missing or empty, a safe default is used,
12
+ * but the loader NEVER invents stochastic values and NEVER performs I/O.
13
+ * This module is the single source of truth for runtime configuration.
14
+ *
15
+ * @module config
16
+ */
17
+ /**
18
+ * Maintainability rating letter grades used throughout claude-crap.
19
+ *
20
+ * The ordering is strict: A is best, E is worst. Callers that need to
21
+ * compare two ratings should use {@link ratingToRank} from `metrics/tdr.ts`
22
+ * rather than comparing the letters directly.
23
+ */
24
+ export type MaintainabilityRating = "A" | "B" | "C" | "D" | "E";
25
+ /**
26
+ * Fully resolved configuration object consumed by every subsystem of the
27
+ * MCP server. Fields are `readonly` so that downstream code cannot mutate
28
+ * configuration at runtime — any change must go through a server restart.
29
+ */
30
+ export interface CrapConfig {
31
+ /** Absolute path to the plugin root on disk. Defaults to `process.cwd()`. */
32
+ readonly pluginRoot: string;
33
+ /** Directory (relative to the workspace) where consolidated SARIF reports are written. */
34
+ readonly sarifOutputDir: string;
35
+ /** Hard block threshold for the CRAP index. Functions above this fail the Stop quality gate. */
36
+ readonly crapThreshold: number;
37
+ /** Maximum cyclomatic complexity allowed per function before warnings fire. */
38
+ readonly cyclomaticMax: number;
39
+ /** Highest (worst) maintainability rating the project is allowed to hold. */
40
+ readonly tdrMaxRating: MaintainabilityRating;
41
+ /** Assumed development cost per line of code, in minutes. Used as the TDR denominator. */
42
+ readonly minutesPerLoc: number;
43
+ /** Local TCP port the Vue.js dashboard will bind to. */
44
+ readonly dashboardPort: number;
45
+ }
46
+ /**
47
+ * Build the complete {@link CrapConfig} from the current process environment.
48
+ *
49
+ * This should be called exactly once at server startup. Subsequent callers
50
+ * that need configuration should accept a `CrapConfig` parameter instead
51
+ * of re-reading from `process.env`, so that tests can inject custom values.
52
+ *
53
+ * @returns A fully validated, immutable configuration object.
54
+ * @throws When any environment variable is present but malformed.
55
+ */
56
+ export declare function loadConfig(): CrapConfig;
57
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH;;;;;;GAMG;AACH,MAAM,MAAM,qBAAqB,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,6EAA6E;IAC7E,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;AA0CD;;;;;;;;;GASG;AACH,wBAAgB,UAAU,IAAI,UAAU,CAUvC"}
package/dist/config.js ADDED
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Deterministic configuration loader for the claude-crap MCP server.
3
+ *
4
+ * Every tunable knob is read from environment variables that are injected
5
+ * by `.mcp.json` at server startup. Those variables are themselves derived
6
+ * from the `CLAUDE_PLUGIN_OPTION_*` values defined in the plugin manifest,
7
+ * which means the configuration chain is:
8
+ *
9
+ * user settings → plugin.json "options" → .mcp.json "env" → this file
10
+ *
11
+ * If any environment variable is missing or empty, a safe default is used,
12
+ * but the loader NEVER invents stochastic values and NEVER performs I/O.
13
+ * This module is the single source of truth for runtime configuration.
14
+ *
15
+ * @module config
16
+ */
17
+ /**
18
+ * Parse a numeric environment variable, falling back to `fallback` when the
19
+ * variable is undefined or empty. Throws if the value is present but not a
20
+ * finite number — we prefer a loud startup failure over silently using a
21
+ * wrong threshold.
22
+ *
23
+ * @param name Environment variable name, used only for the error message.
24
+ * @param raw Raw value read from `process.env`.
25
+ * @param fallback Default value used when `raw` is undefined/empty.
26
+ * @returns The parsed number.
27
+ * @throws When `raw` is present but not a finite number.
28
+ */
29
+ function parseNumber(name, raw, fallback) {
30
+ if (raw === undefined || raw === "")
31
+ return fallback;
32
+ const value = Number(raw);
33
+ if (!Number.isFinite(value)) {
34
+ throw new Error(`[claude-crap] Env ${name}="${raw}" is not a finite number`);
35
+ }
36
+ return value;
37
+ }
38
+ /**
39
+ * Parse a maintainability rating from an environment variable. Accepts any
40
+ * casing (`a`, `A`, ` a ` all become `"A"`). Throws on invalid letters so
41
+ * the server refuses to start rather than running with an unknown policy.
42
+ *
43
+ * @param raw Raw value read from `process.env`.
44
+ * @param fallback Default rating used when `raw` is undefined.
45
+ * @returns A validated {@link MaintainabilityRating}.
46
+ * @throws When `raw` is a non-empty string that is not A..E.
47
+ */
48
+ function parseRating(raw, fallback) {
49
+ if (!raw)
50
+ return fallback;
51
+ const normalized = raw.trim().toUpperCase();
52
+ if (!["A", "B", "C", "D", "E"].includes(normalized)) {
53
+ throw new Error(`[claude-crap] TDR_MAX_RATING="${raw}" must be one of A, B, C, D, E`);
54
+ }
55
+ return normalized;
56
+ }
57
+ /**
58
+ * Build the complete {@link CrapConfig} from the current process environment.
59
+ *
60
+ * This should be called exactly once at server startup. Subsequent callers
61
+ * that need configuration should accept a `CrapConfig` parameter instead
62
+ * of re-reading from `process.env`, so that tests can inject custom values.
63
+ *
64
+ * @returns A fully validated, immutable configuration object.
65
+ * @throws When any environment variable is present but malformed.
66
+ */
67
+ export function loadConfig() {
68
+ return {
69
+ pluginRoot: process.env.CLAUDE_CRAP_PLUGIN_ROOT ?? process.cwd(),
70
+ sarifOutputDir: process.env.CLAUDE_CRAP_SARIF_OUTPUT_DIR ?? ".claude-crap/reports",
71
+ crapThreshold: parseNumber("CLAUDE_CRAP_CRAP_THRESHOLD", process.env.CLAUDE_CRAP_CRAP_THRESHOLD, 30),
72
+ cyclomaticMax: parseNumber("CLAUDE_CRAP_CYCLOMATIC_MAX", process.env.CLAUDE_CRAP_CYCLOMATIC_MAX, 15),
73
+ tdrMaxRating: parseRating(process.env.CLAUDE_CRAP_TDR_MAX_RATING, "C"),
74
+ minutesPerLoc: parseNumber("CLAUDE_CRAP_MINUTES_PER_LOC", process.env.CLAUDE_CRAP_MINUTES_PER_LOC, 30),
75
+ dashboardPort: parseNumber("CLAUDE_CRAP_DASHBOARD_PORT", process.env.CLAUDE_CRAP_DASHBOARD_PORT, 5117),
76
+ };
77
+ }
78
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAiCH;;;;;;;;;;;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,OAAO,CAAC,GAAG,CAAC,uBAAuB,IAAI,OAAO,CAAC,GAAG,EAAE;QAChE,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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Workspace-level sonar configuration loader.
3
+ *
4
+ * Every subsystem that can be made stricter or looser (the Stop
5
+ * quality gate, the `score_project` tool's `isError` flag) consults
6
+ * this loader to decide how hard to push back when a policy fails.
7
+ * Teams adopt claude-crap in stages:
8
+ *
9
+ * - `strict` (default) — the Stop hook exits 2 on any policy
10
+ * failure and the `score_project` tool returns `isError: true`.
11
+ * Matches the current, hard-coded behavior.
12
+ * - `warn` — the Stop hook exits 0 but writes the
13
+ * full verdict to stdout so the agent still sees every failing
14
+ * rule in its hook transcript. `score_project.isError` stays
15
+ * false even on a failing project.
16
+ * - `advisory` — the Stop hook exits 0 and writes a
17
+ * single-line summary. Minimal pressure on the agent.
18
+ *
19
+ * The loader resolves the `strictness` value in strict priority
20
+ * order so a team's committed default can be overridden per-session
21
+ * without editing the file:
22
+ *
23
+ * 1. `CLAUDE_CRAP_STRICTNESS` environment variable
24
+ * 2. `.claude-crap.json` at the workspace root
25
+ * 3. Hardcoded default `"strict"` (zero behavior change for
26
+ * installs that never create the file)
27
+ *
28
+ * The loader is intentionally tiny — it does a single synchronous
29
+ * file read, one optional env probe, and validates the string
30
+ * against the enum. A hook script can call it from inside its
31
+ * 15-second budget without breaking a sweat.
32
+ *
33
+ * @module crap-config
34
+ */
35
+ /**
36
+ * Exhaustive list of valid strictness values. Keep this in sync with
37
+ * the `Strictness` type below — the tuple is `as const` so TypeScript
38
+ * derives the union from the same source of truth.
39
+ */
40
+ export declare const STRICTNESS_VALUES: readonly ["strict", "warn", "advisory"];
41
+ /**
42
+ * Union of valid strictness values. Used by every consumer of
43
+ * {@link CrapConfig} to branch on the mode without dealing with
44
+ * arbitrary strings.
45
+ */
46
+ export type Strictness = (typeof STRICTNESS_VALUES)[number];
47
+ /**
48
+ * Hardcoded default used when neither the environment variable nor
49
+ * `.claude-crap.json` provides a value. Chosen as `"strict"` so the
50
+ * plugin's hard-failing Stop gate stays the default experience.
51
+ */
52
+ export declare const DEFAULT_STRICTNESS: Strictness;
53
+ /**
54
+ * Thrown by {@link loadCrapConfig} when the configuration is
55
+ * rejected. Callers in the hook layer fall back to the default on a
56
+ * throw so a busted config never deadlocks the user, while callers
57
+ * in the MCP server surface the error verbatim.
58
+ */
59
+ export declare class CrapConfigError extends Error {
60
+ constructor(message: string);
61
+ }
62
+ /**
63
+ * Structure of the resolved sonar configuration returned by
64
+ * {@link loadCrapConfig}. The shape is deliberately minimal for
65
+ * v0.1.0; future releases may add threshold overrides under the
66
+ * same `.claude-crap.json` file.
67
+ */
68
+ export interface CrapConfig {
69
+ /** Final strictness, after env override, file, and default fallback. */
70
+ readonly strictness: Strictness;
71
+ /** Where the strictness value actually came from. Useful for diagnostics. */
72
+ readonly strictnessSource: "env" | "file" | "default";
73
+ }
74
+ /**
75
+ * Options accepted by {@link loadCrapConfig}. The only required
76
+ * field is the workspace root the loader should search for
77
+ * `.claude-crap.json`.
78
+ */
79
+ export interface LoadCrapConfigOptions {
80
+ /**
81
+ * Absolute path to the workspace root. The loader reads
82
+ * `.claude-crap.json` from this directory only — it does not
83
+ * walk parent directories.
84
+ */
85
+ readonly workspaceRoot: string;
86
+ }
87
+ /**
88
+ * Resolve the effective sonar configuration for a given workspace
89
+ * root. Pure function except for the one synchronous file read on
90
+ * `<workspaceRoot>/.claude-crap.json` and the two env lookups.
91
+ *
92
+ * @param options Search options. Only `workspaceRoot` is required.
93
+ * @returns The resolved {@link CrapConfig}.
94
+ * @throws {@link CrapConfigError} on any invalid input.
95
+ */
96
+ export declare function loadCrapConfig(options: LoadCrapConfigOptions): CrapConfig;
97
+ //# sourceMappingURL=crap-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crap-config.d.ts","sourceRoot":"","sources":["../src/crap-config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAKH;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,yCAA0C,CAAC;AAEzE;;;;GAIG;AACH,MAAM,MAAM,UAAU,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAE5D;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,UAAqB,CAAC;AAEvD;;;;;GAKG;AACH,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,KAAK,GAAG,MAAM,GAAG,SAAS,CAAC;CACvD;AAED;;;;GAIG;AACH,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAChC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,UAAU,CAiBzE"}
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Workspace-level sonar configuration loader.
3
+ *
4
+ * Every subsystem that can be made stricter or looser (the Stop
5
+ * quality gate, the `score_project` tool's `isError` flag) consults
6
+ * this loader to decide how hard to push back when a policy fails.
7
+ * Teams adopt claude-crap in stages:
8
+ *
9
+ * - `strict` (default) — the Stop hook exits 2 on any policy
10
+ * failure and the `score_project` tool returns `isError: true`.
11
+ * Matches the current, hard-coded behavior.
12
+ * - `warn` — the Stop hook exits 0 but writes the
13
+ * full verdict to stdout so the agent still sees every failing
14
+ * rule in its hook transcript. `score_project.isError` stays
15
+ * false even on a failing project.
16
+ * - `advisory` — the Stop hook exits 0 and writes a
17
+ * single-line summary. Minimal pressure on the agent.
18
+ *
19
+ * The loader resolves the `strictness` value in strict priority
20
+ * order so a team's committed default can be overridden per-session
21
+ * without editing the file:
22
+ *
23
+ * 1. `CLAUDE_CRAP_STRICTNESS` environment variable
24
+ * 2. `.claude-crap.json` at the workspace root
25
+ * 3. Hardcoded default `"strict"` (zero behavior change for
26
+ * installs that never create the file)
27
+ *
28
+ * The loader is intentionally tiny — it does a single synchronous
29
+ * file read, one optional env probe, and validates the string
30
+ * against the enum. A hook script can call it from inside its
31
+ * 15-second budget without breaking a sweat.
32
+ *
33
+ * @module crap-config
34
+ */
35
+ import { readFileSync } from "node:fs";
36
+ import { join } from "node:path";
37
+ /**
38
+ * Exhaustive list of valid strictness values. Keep this in sync with
39
+ * the `Strictness` type below — the tuple is `as const` so TypeScript
40
+ * derives the union from the same source of truth.
41
+ */
42
+ export const STRICTNESS_VALUES = ["strict", "warn", "advisory"];
43
+ /**
44
+ * Hardcoded default used when neither the environment variable nor
45
+ * `.claude-crap.json` provides a value. Chosen as `"strict"` so the
46
+ * plugin's hard-failing Stop gate stays the default experience.
47
+ */
48
+ export const DEFAULT_STRICTNESS = "strict";
49
+ /**
50
+ * Thrown by {@link loadCrapConfig} when the configuration is
51
+ * rejected. Callers in the hook layer fall back to the default on a
52
+ * throw so a busted config never deadlocks the user, while callers
53
+ * in the MCP server surface the error verbatim.
54
+ */
55
+ export class CrapConfigError extends Error {
56
+ constructor(message) {
57
+ super(message);
58
+ this.name = "CrapConfigError";
59
+ }
60
+ }
61
+ /**
62
+ * Resolve the effective sonar configuration for a given workspace
63
+ * root. Pure function except for the one synchronous file read on
64
+ * `<workspaceRoot>/.claude-crap.json` and the two env lookups.
65
+ *
66
+ * @param options Search options. Only `workspaceRoot` is required.
67
+ * @returns The resolved {@link CrapConfig}.
68
+ * @throws {@link CrapConfigError} on any invalid input.
69
+ */
70
+ export function loadCrapConfig(options) {
71
+ const envRaw = process.env["CLAUDE_CRAP_STRICTNESS"];
72
+ if (typeof envRaw === "string" && envRaw.trim() !== "") {
73
+ const normalized = envRaw.trim().toLowerCase();
74
+ if (!isStrictness(normalized)) {
75
+ throw new CrapConfigError(`[crap-config] CLAUDE_CRAP_STRICTNESS="${envRaw}" is not a valid strictness. ` +
76
+ `Expected one of: ${STRICTNESS_VALUES.join(", ")}.`);
77
+ }
78
+ return { strictness: normalized, strictnessSource: "env" };
79
+ }
80
+ const fromFile = readFromFile(options.workspaceRoot);
81
+ if (fromFile)
82
+ return { strictness: fromFile, strictnessSource: "file" };
83
+ return { strictness: DEFAULT_STRICTNESS, strictnessSource: "default" };
84
+ }
85
+ /**
86
+ * Attempt to read and validate `.claude-crap.json` at the
87
+ * workspace root. Returns `null` when the file is missing (which
88
+ * is the common case for fresh installs). Throws
89
+ * {@link CrapConfigError} on any other failure mode — a malformed
90
+ * JSON file, a non-object root, a missing or wrong-type
91
+ * `strictness` field, or an unknown enum value — so the caller
92
+ * cannot accidentally drop into the default on a typo.
93
+ *
94
+ * @param workspaceRoot Absolute workspace root.
95
+ * @returns The validated strictness, or `null` when no
96
+ * file is present.
97
+ */
98
+ function readFromFile(workspaceRoot) {
99
+ const filePath = join(workspaceRoot, ".claude-crap.json");
100
+ let raw;
101
+ try {
102
+ raw = readFileSync(filePath, "utf8");
103
+ }
104
+ catch (err) {
105
+ const error = err;
106
+ if (error.code === "ENOENT")
107
+ return null;
108
+ throw new CrapConfigError(`[crap-config] Failed to read ${filePath}: ${error.message}`);
109
+ }
110
+ let parsed;
111
+ try {
112
+ parsed = JSON.parse(raw);
113
+ }
114
+ catch (err) {
115
+ throw new CrapConfigError(`[crap-config] ${filePath} is not valid JSON: ${err.message}`);
116
+ }
117
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
118
+ throw new CrapConfigError(`[crap-config] ${filePath} must be a JSON object at the top level`);
119
+ }
120
+ const doc = parsed;
121
+ if (!("strictness" in doc))
122
+ return null;
123
+ const value = doc["strictness"];
124
+ if (typeof value !== "string") {
125
+ throw new CrapConfigError(`[crap-config] ${filePath}: 'strictness' must be a string, got ${typeof value}`);
126
+ }
127
+ const normalized = value.trim().toLowerCase();
128
+ if (!isStrictness(normalized)) {
129
+ throw new CrapConfigError(`[crap-config] ${filePath}: 'strictness' is "${value}"; ` +
130
+ `expected one of ${STRICTNESS_VALUES.join(", ")}.`);
131
+ }
132
+ return normalized;
133
+ }
134
+ /**
135
+ * Runtime type guard for the {@link Strictness} union. Lets callers
136
+ * narrow an arbitrary string to the union without casting.
137
+ *
138
+ * @param value Arbitrary string.
139
+ * @returns `true` when `value` is a recognized strictness.
140
+ */
141
+ function isStrictness(value) {
142
+ return STRICTNESS_VALUES.includes(value);
143
+ }
144
+ //# sourceMappingURL=crap-config.js.map