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,205 @@
1
+ /**
2
+ * Local Vue.js dashboard for claude-crap — Fastify HTTP server.
3
+ *
4
+ * The dashboard runs in the same Node.js process as the MCP server,
5
+ * but on a separate TCP port (default 5117). It exposes:
6
+ *
7
+ * GET / → static index.html (Vue 3 SPA from CDN)
8
+ * GET /api/score → live ProjectScore JSON from the score engine
9
+ * GET /api/sarif → consolidated SARIF 2.1.0 document
10
+ * GET /api/health → simple {status:"ok"} liveness probe
11
+ *
12
+ * The server binds to `127.0.0.1` only — never to `0.0.0.0` — so the
13
+ * dashboard cannot be reached from outside the developer's machine.
14
+ *
15
+ * If the configured port is already in use (or the bind otherwise
16
+ * fails), `startDashboard()` rejects gracefully and the caller falls
17
+ * back to "no dashboard". The MCP server will keep running.
18
+ *
19
+ * IMPORTANT: this module never writes to stdout. The MCP stdio
20
+ * transport reserves stdout for JSON-RPC framing, so all logs and
21
+ * errors here go through the same pino-on-stderr instance the rest of
22
+ * the server uses.
23
+ *
24
+ * @module dashboard/server
25
+ */
26
+
27
+ import { promises as fs } from "node:fs";
28
+ import { dirname, resolve } from "node:path";
29
+ import { fileURLToPath } from "node:url";
30
+
31
+ import Fastify, { type FastifyInstance } from "fastify";
32
+ import fastifyStatic from "@fastify/static";
33
+ import type { Logger } from "pino";
34
+
35
+ import type { CrapConfig } from "../config.js";
36
+ import {
37
+ computeProjectScore,
38
+ type ProjectScore,
39
+ type WorkspaceStats,
40
+ } from "../metrics/score.js";
41
+ import type { SarifStore } from "../sarif/sarif-store.js";
42
+
43
+ /**
44
+ * Callback used by the dashboard to refresh workspace LOC stats on
45
+ * every score request. The MCP server provides this so the dashboard
46
+ * does not have to know how to walk the disk itself.
47
+ */
48
+ export type WorkspaceStatsProvider = () => Promise<WorkspaceStats>;
49
+
50
+ /**
51
+ * Inputs accepted by {@link startDashboard}.
52
+ */
53
+ export interface StartDashboardOptions {
54
+ /** Fully resolved server configuration. */
55
+ readonly config: CrapConfig;
56
+ /** Live SARIF store the dashboard reads findings from. */
57
+ readonly sarifStore: SarifStore;
58
+ /** Function that returns up-to-date LOC + file count for the workspace. */
59
+ readonly workspaceStatsProvider: WorkspaceStatsProvider;
60
+ /** Pino logger from the MCP server (writes to stderr). */
61
+ readonly logger: Logger;
62
+ }
63
+
64
+ /**
65
+ * Handle returned by {@link startDashboard}. Use `url` to build the
66
+ * link the user clicks; call `close()` during shutdown.
67
+ */
68
+ export interface DashboardHandle {
69
+ readonly url: string;
70
+ close(): Promise<void>;
71
+ }
72
+
73
+ /**
74
+ * Boot the Fastify dashboard server. Resolves with a {@link DashboardHandle}
75
+ * once the server is listening, or rejects when the bind fails (caller
76
+ * should treat that as a non-fatal degradation).
77
+ *
78
+ * @param options Configuration, store, and provider callback.
79
+ */
80
+ export async function startDashboard(options: StartDashboardOptions): Promise<DashboardHandle> {
81
+ const { config, sarifStore, workspaceStatsProvider, logger } = options;
82
+
83
+ // Resolve the public/ directory. After `npm run build` the compiled
84
+ // server lives in `dist/dashboard/server.js`, but we keep the static
85
+ // SPA assets in `src/dashboard/public/` so we don't need a postbuild
86
+ // copy step. We probe both candidate locations in priority order.
87
+ const publicRoot = await resolvePublicRoot(logger);
88
+
89
+ const fastify: FastifyInstance = Fastify({
90
+ logger: false, // we route everything through pino-on-stderr ourselves
91
+ disableRequestLogging: true,
92
+ });
93
+
94
+ await fastify.register(fastifyStatic, {
95
+ root: publicRoot,
96
+ prefix: "/",
97
+ decorateReply: false,
98
+ });
99
+
100
+ // ------------------------------------------------------------------
101
+ // /api/health — liveness probe
102
+ // ------------------------------------------------------------------
103
+ fastify.get("/api/health", async () => ({ status: "ok", server: "claude-crap", version: "0.1.0" }));
104
+
105
+ // ------------------------------------------------------------------
106
+ // /api/score — live project score
107
+ // ------------------------------------------------------------------
108
+ fastify.get("/api/score", async () => {
109
+ const stats = await workspaceStatsProvider();
110
+ const score = await buildScore(config, sarifStore, stats, urlOf(fastify, config));
111
+ return score;
112
+ });
113
+
114
+ // ------------------------------------------------------------------
115
+ // /api/sarif — consolidated SARIF 2.1.0 document
116
+ // ------------------------------------------------------------------
117
+ fastify.get("/api/sarif", async () => sarifStore.toSarifDocument());
118
+
119
+ // ------------------------------------------------------------------
120
+ // / — static SPA fallback (Fastify-static handles index.html)
121
+ // ------------------------------------------------------------------
122
+
123
+ await fastify.listen({ port: config.dashboardPort, host: "127.0.0.1" });
124
+ const url = `http://127.0.0.1:${config.dashboardPort}`;
125
+ logger.info({ url, publicRoot }, "claude-crap dashboard listening");
126
+
127
+ return {
128
+ url,
129
+ async close() {
130
+ await fastify.close();
131
+ },
132
+ };
133
+ }
134
+
135
+ /**
136
+ * Probe the candidate public/ directories in priority order and return
137
+ * the first one that contains an `index.html`. Throws when none of the
138
+ * candidates exist — that points at a packaging mistake the developer
139
+ * should fix immediately.
140
+ *
141
+ * @param logger Pino instance for diagnostics.
142
+ */
143
+ async function resolvePublicRoot(logger: Logger): Promise<string> {
144
+ const here = dirname(fileURLToPath(import.meta.url));
145
+ const candidates = [
146
+ // 0. Bundled layout: plugin/bundle/mcp-server.mjs → ./dashboard/public
147
+ resolve(here, "dashboard", "public"),
148
+ // 1. Compiled layout: dist/dashboard/server.js → ./public next to it
149
+ // (only present if a build step copies the assets — not used
150
+ // today, but accepted so a future copy step does not break us).
151
+ resolve(here, "public"),
152
+ // 2. Source-relative layout: dist/dashboard/server.js → ../../src/dashboard/public
153
+ // This is the default — no copy step required because we resolve
154
+ // upward from `dist/` into `src/` at runtime.
155
+ resolve(here, "..", "..", "src", "dashboard", "public"),
156
+ ];
157
+ for (const candidate of candidates) {
158
+ try {
159
+ await fs.access(resolve(candidate, "index.html"));
160
+ return candidate;
161
+ } catch {
162
+ // probe next
163
+ }
164
+ }
165
+ logger.error({ candidates }, "dashboard public/ directory not found");
166
+ throw new Error(
167
+ `[claude-crap] dashboard: index.html not found in any of ${candidates.join(", ")}`,
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Resolve the canonical dashboard URL using the live Fastify address
173
+ * info. Falls back to the configured port when the address info is
174
+ * not yet available (e.g. on the very first request during startup).
175
+ */
176
+ function urlOf(fastify: FastifyInstance, config: CrapConfig): string {
177
+ const addresses = fastify.addresses?.() ?? [];
178
+ const first = addresses[0];
179
+ if (first) {
180
+ const host = first.address === "::" || first.address === "0.0.0.0" ? "127.0.0.1" : first.address;
181
+ return `http://${host}:${first.port}`;
182
+ }
183
+ return `http://127.0.0.1:${config.dashboardPort}`;
184
+ }
185
+
186
+ /**
187
+ * Wrap {@link computeProjectScore} so the dashboard endpoint can call
188
+ * it with the live store and provide consistent location metadata.
189
+ */
190
+ async function buildScore(
191
+ config: CrapConfig,
192
+ sarifStore: SarifStore,
193
+ workspace: WorkspaceStats,
194
+ dashboardUrl: string | null,
195
+ ): Promise<ProjectScore> {
196
+ return computeProjectScore({
197
+ workspaceRoot: config.pluginRoot,
198
+ minutesPerLoc: config.minutesPerLoc,
199
+ tdrMaxRating: config.tdrMaxRating,
200
+ workspace,
201
+ sarifStore,
202
+ dashboardUrl,
203
+ sarifReportPath: sarifStore.consolidatedReportPath,
204
+ });
205
+ }