rebar-mcp 2.0.0

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 (248) hide show
  1. package/.claude/agents/template-writer.md +43 -0
  2. package/.claude/agents/test-runner.md +47 -0
  3. package/.claude/mcp.json +9 -0
  4. package/.claude/settings.json +29 -0
  5. package/.claude/skills/ /SKILL.md +21 -0
  6. package/.claude/skills/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/SKILL.md +21 -0
  7. package/.claude/skills/bmmibwetxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  8. package/.claude/skills/bmmibwjgvxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  9. package/.claude/skills/bmmibwsesxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  10. package/.claude/skills/bmmibwxufxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  11. package/.claude/skills/bmmibx3r9xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  12. package/.claude/skills/bmmji0lrkxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  13. package/.claude/skills/bmmjiniphxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  14. package/.claude/skills/bmmjio86zxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  15. package/.claude/skills/bmmjiolfbxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  16. package/.claude/skills/bmmjit1lvxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  17. package/.claude/skills/bmmjita1qxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/SKILL.md +21 -0
  18. package/.claude/skills/bnd-mmibweu3/SKILL.md +21 -0
  19. package/.claude/skills/bnd-mmibwjh4/SKILL.md +21 -0
  20. package/.claude/skills/bnd-mmibwsey/SKILL.md +21 -0
  21. package/.claude/skills/bnd-mmibwxup/SKILL.md +21 -0
  22. package/.claude/skills/bnd-mmibx3rg/SKILL.md +21 -0
  23. package/.claude/skills/bnd-mmji0lrp/SKILL.md +21 -0
  24. package/.claude/skills/bnd-mmjinipm/SKILL.md +21 -0
  25. package/.claude/skills/bnd-mmjio875/SKILL.md +21 -0
  26. package/.claude/skills/bnd-mmjiolfg/SKILL.md +21 -0
  27. package/.claude/skills/bnd-mmjit1m3/SKILL.md +21 -0
  28. package/.claude/skills/bnd-mmjita1x/SKILL.md +21 -0
  29. package/.claude/skills/coercion-test/SKILL.md +50 -0
  30. package/.claude/skills/large-skill/SKILL.md +21 -0
  31. package/.claude/skills/long-desc-skill/SKILL.md +21 -0
  32. package/.claude/skills/mcp-dev/SKILL.md +61 -0
  33. package/.claude/skills/nl-mmibweus/SKILL.md +25 -0
  34. package/.claude/skills/nl-mmibwjhf/SKILL.md +25 -0
  35. package/.claude/skills/nl-mmibwsf7/SKILL.md +25 -0
  36. package/.claude/skills/nl-mmibwxvq/SKILL.md +25 -0
  37. package/.claude/skills/nl-mmibx3rt/SKILL.md +25 -0
  38. package/.claude/skills/nl-mmji0lrz/SKILL.md +25 -0
  39. package/.claude/skills/nl-mmjinipx/SKILL.md +25 -0
  40. package/.claude/skills/nl-mmjio87f/SKILL.md +25 -0
  41. package/.claude/skills/nl-mmjiolfs/SKILL.md +25 -0
  42. package/.claude/skills/nl-mmjit1mc/SKILL.md +25 -0
  43. package/.claude/skills/nl-mmjita26/SKILL.md +25 -0
  44. package/.claude/skills/rapid-1/SKILL.md +21 -0
  45. package/.claude/skills/rapid-2/SKILL.md +21 -0
  46. package/.claude/skills/rapid-3/SKILL.md +21 -0
  47. package/.claude/skills/rapid-4/SKILL.md +21 -0
  48. package/.claude/skills/rapid-5/SKILL.md +21 -0
  49. package/.claude/skills/test/", /"malicious/": /"true/SKILL.md" +69 -0
  50. package/.claude/skills/test-emoji-/360/237/230/200-skill/SKILL.md +69 -0
  51. package/.claude/skills/test-skill/SKILL.md +69 -0
  52. package/.claude/skills/test; rm -rf /; skill/SKILL.md +69 -0
  53. package/.claude/skills/test<script>alert(1)</script>skill/SKILL.md +69 -0
  54. package/.claudeignore +5 -0
  55. package/.mcp.json +3 -0
  56. package/CHANGELOG.md +29 -0
  57. package/CLAUDE.md +76 -0
  58. package/LICENSE +21 -0
  59. package/README.md +149 -0
  60. package/ROADMAP.md +526 -0
  61. package/ccboot-PRD-v1.0.docx.md +732 -0
  62. package/ccboot-v1.2.0-enforcement-spec.md +1272 -0
  63. package/dist/cli.d.ts +3 -0
  64. package/dist/cli.d.ts.map +1 -0
  65. package/dist/cli.js +674 -0
  66. package/dist/cli.js.map +1 -0
  67. package/dist/constants.d.ts +25 -0
  68. package/dist/constants.d.ts.map +1 -0
  69. package/dist/constants.js +118 -0
  70. package/dist/constants.js.map +1 -0
  71. package/dist/index.d.ts +3 -0
  72. package/dist/index.d.ts.map +1 -0
  73. package/dist/index.js +47 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/schemas/common.d.ts +62 -0
  76. package/dist/schemas/common.d.ts.map +1 -0
  77. package/dist/schemas/common.js +15 -0
  78. package/dist/schemas/common.js.map +1 -0
  79. package/dist/schemas/scaffolding.d.ts +277 -0
  80. package/dist/schemas/scaffolding.d.ts.map +1 -0
  81. package/dist/schemas/scaffolding.js +133 -0
  82. package/dist/schemas/scaffolding.js.map +1 -0
  83. package/dist/services/claudemd-generator.d.ts +16 -0
  84. package/dist/services/claudemd-generator.d.ts.map +1 -0
  85. package/dist/services/claudemd-generator.js +426 -0
  86. package/dist/services/claudemd-generator.js.map +1 -0
  87. package/dist/services/codex-generator.d.ts +6 -0
  88. package/dist/services/codex-generator.d.ts.map +1 -0
  89. package/dist/services/codex-generator.js +35 -0
  90. package/dist/services/codex-generator.js.map +1 -0
  91. package/dist/services/cursor-generator.d.ts +15 -0
  92. package/dist/services/cursor-generator.d.ts.map +1 -0
  93. package/dist/services/cursor-generator.js +134 -0
  94. package/dist/services/cursor-generator.js.map +1 -0
  95. package/dist/services/file-ops.d.ts +48 -0
  96. package/dist/services/file-ops.d.ts.map +1 -0
  97. package/dist/services/file-ops.js +153 -0
  98. package/dist/services/file-ops.js.map +1 -0
  99. package/dist/services/output-formatter.d.ts +57 -0
  100. package/dist/services/output-formatter.d.ts.map +1 -0
  101. package/dist/services/output-formatter.js +88 -0
  102. package/dist/services/output-formatter.js.map +1 -0
  103. package/dist/services/platform-detect.d.ts +14 -0
  104. package/dist/services/platform-detect.d.ts.map +1 -0
  105. package/dist/services/platform-detect.js +63 -0
  106. package/dist/services/platform-detect.js.map +1 -0
  107. package/dist/services/project-analyzer.d.ts +71 -0
  108. package/dist/services/project-analyzer.d.ts.map +1 -0
  109. package/dist/services/project-analyzer.js +595 -0
  110. package/dist/services/project-analyzer.js.map +1 -0
  111. package/dist/services/rules-engine.d.ts +41 -0
  112. package/dist/services/rules-engine.d.ts.map +1 -0
  113. package/dist/services/rules-engine.js +304 -0
  114. package/dist/services/rules-engine.js.map +1 -0
  115. package/dist/services/strictness.d.ts +37 -0
  116. package/dist/services/strictness.d.ts.map +1 -0
  117. package/dist/services/strictness.js +182 -0
  118. package/dist/services/strictness.js.map +1 -0
  119. package/dist/services/template-engine.d.ts +16 -0
  120. package/dist/services/template-engine.d.ts.map +1 -0
  121. package/dist/services/template-engine.js +85 -0
  122. package/dist/services/template-engine.js.map +1 -0
  123. package/dist/services/validation.d.ts +41 -0
  124. package/dist/services/validation.d.ts.map +1 -0
  125. package/dist/services/validation.js +104 -0
  126. package/dist/services/validation.js.map +1 -0
  127. package/dist/services/windsurf-generator.d.ts +15 -0
  128. package/dist/services/windsurf-generator.d.ts.map +1 -0
  129. package/dist/services/windsurf-generator.js +127 -0
  130. package/dist/services/windsurf-generator.js.map +1 -0
  131. package/dist/tests/enforcement.test.d.ts +2 -0
  132. package/dist/tests/enforcement.test.d.ts.map +1 -0
  133. package/dist/tests/enforcement.test.js +541 -0
  134. package/dist/tests/enforcement.test.js.map +1 -0
  135. package/dist/tests/enterprise.test.d.ts +2 -0
  136. package/dist/tests/enterprise.test.d.ts.map +1 -0
  137. package/dist/tests/enterprise.test.js +353 -0
  138. package/dist/tests/enterprise.test.js.map +1 -0
  139. package/dist/tests/fuzzing.test.d.ts +2 -0
  140. package/dist/tests/fuzzing.test.d.ts.map +1 -0
  141. package/dist/tests/fuzzing.test.js +596 -0
  142. package/dist/tests/fuzzing.test.js.map +1 -0
  143. package/dist/tests/knowledge.test.d.ts +2 -0
  144. package/dist/tests/knowledge.test.d.ts.map +1 -0
  145. package/dist/tests/knowledge.test.js +292 -0
  146. package/dist/tests/knowledge.test.js.map +1 -0
  147. package/dist/tests/management.test.d.ts +2 -0
  148. package/dist/tests/management.test.d.ts.map +1 -0
  149. package/dist/tests/management.test.js +338 -0
  150. package/dist/tests/management.test.js.map +1 -0
  151. package/dist/tests/scaffolding.test.d.ts +2 -0
  152. package/dist/tests/scaffolding.test.d.ts.map +1 -0
  153. package/dist/tests/scaffolding.test.js +419 -0
  154. package/dist/tests/scaffolding.test.js.map +1 -0
  155. package/dist/tests/test-utils.d.ts +76 -0
  156. package/dist/tests/test-utils.d.ts.map +1 -0
  157. package/dist/tests/test-utils.js +171 -0
  158. package/dist/tests/test-utils.js.map +1 -0
  159. package/dist/tests/tool-harness.d.ts +18 -0
  160. package/dist/tests/tool-harness.d.ts.map +1 -0
  161. package/dist/tests/tool-harness.js +51 -0
  162. package/dist/tests/tool-harness.js.map +1 -0
  163. package/dist/tools/enterprise.d.ts +8 -0
  164. package/dist/tools/enterprise.d.ts.map +1 -0
  165. package/dist/tools/enterprise.js +571 -0
  166. package/dist/tools/enterprise.js.map +1 -0
  167. package/dist/tools/knowledge.d.ts +7 -0
  168. package/dist/tools/knowledge.d.ts.map +1 -0
  169. package/dist/tools/knowledge.js +120 -0
  170. package/dist/tools/knowledge.js.map +1 -0
  171. package/dist/tools/management.d.ts +10 -0
  172. package/dist/tools/management.d.ts.map +1 -0
  173. package/dist/tools/management.js +1541 -0
  174. package/dist/tools/management.js.map +1 -0
  175. package/dist/tools/scaffolding.d.ts +8 -0
  176. package/dist/tools/scaffolding.d.ts.map +1 -0
  177. package/dist/tools/scaffolding.js +736 -0
  178. package/dist/tools/scaffolding.js.map +1 -0
  179. package/dist/types.d.ts +54 -0
  180. package/dist/types.d.ts.map +1 -0
  181. package/dist/types.js +5 -0
  182. package/dist/types.js.map +1 -0
  183. package/landing/app/layout.tsx +30 -0
  184. package/landing/app/page.tsx +944 -0
  185. package/landing/next-env.d.ts +6 -0
  186. package/landing/next.config.js +6 -0
  187. package/landing/package-lock.json +896 -0
  188. package/landing/package.json +20 -0
  189. package/landing/tsconfig.json +40 -0
  190. package/package.json +49 -0
  191. package/rebar-v2.0.0-platform-spec.md +1567 -0
  192. package/server.json +20 -0
  193. package/src/cli.ts +735 -0
  194. package/src/constants.ts +131 -0
  195. package/src/index.ts +54 -0
  196. package/src/schemas/common.ts +22 -0
  197. package/src/schemas/scaffolding.ts +161 -0
  198. package/src/services/claudemd-generator.ts +481 -0
  199. package/src/services/codex-generator.ts +44 -0
  200. package/src/services/cursor-generator.ts +153 -0
  201. package/src/services/file-ops.ts +172 -0
  202. package/src/services/platform-detect.ts +80 -0
  203. package/src/services/project-analyzer.ts +690 -0
  204. package/src/services/rules-engine.ts +353 -0
  205. package/src/services/strictness.ts +202 -0
  206. package/src/services/template-engine.ts +119 -0
  207. package/src/services/validation.ts +138 -0
  208. package/src/services/windsurf-generator.ts +145 -0
  209. package/src/tests/enforcement.test.ts +794 -0
  210. package/src/tests/enterprise.test.ts +483 -0
  211. package/src/tests/fuzzing.test.ts +690 -0
  212. package/src/tests/knowledge.test.ts +371 -0
  213. package/src/tests/management.test.ts +451 -0
  214. package/src/tests/scaffolding.test.ts +575 -0
  215. package/src/tests/test-utils.ts +206 -0
  216. package/src/tests/tool-harness.ts +70 -0
  217. package/src/tools/enterprise.ts +666 -0
  218. package/src/tools/knowledge.ts +162 -0
  219. package/src/tools/management.ts +1706 -0
  220. package/src/tools/scaffolding.ts +909 -0
  221. package/src/types.ts +93 -0
  222. package/supabase/.temp/cli-latest +1 -0
  223. package/supabase/.temp/gotrue-version +1 -0
  224. package/supabase/.temp/pooler-url +1 -0
  225. package/supabase/.temp/postgres-version +1 -0
  226. package/supabase/.temp/project-ref +1 -0
  227. package/supabase/.temp/rest-version +1 -0
  228. package/supabase/.temp/storage-migration +1 -0
  229. package/supabase/.temp/storage-version +1 -0
  230. package/templates/agents/explore.md +41 -0
  231. package/templates/agents/plan.md +73 -0
  232. package/templates/agents/security-auditor.md +77 -0
  233. package/templates/agents/test-runner.md +60 -0
  234. package/templates/claudemd/fastapi.md +49 -0
  235. package/templates/claudemd/monorepo.md +48 -0
  236. package/templates/claudemd/nextjs.md +52 -0
  237. package/templates/claudemd/react-spa.md +50 -0
  238. package/templates/claudemd/springboot.md +50 -0
  239. package/templates/hooks/danger-blocker.json +11 -0
  240. package/templates/hooks/format-on-write.json +17 -0
  241. package/templates/hooks/lint-on-write.json +16 -0
  242. package/templates/hooks/secret-detector.json +11 -0
  243. package/templates/skills/code-review.md +68 -0
  244. package/templates/skills/documentation.md +62 -0
  245. package/templates/skills/performance-audit.md +80 -0
  246. package/templates/skills/security-scan.md +66 -0
  247. package/templates/skills/test-writer.md +56 -0
  248. package/tsconfig.json +19 -0
package/src/cli.ts ADDED
@@ -0,0 +1,735 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Rebar CLI — Standalone command-line interface
4
+ *
5
+ * Run Rebar commands without an MCP client. Useful for:
6
+ * - CI/CD pipelines (audit, badge generation)
7
+ * - Quick project setup (init, doctor)
8
+ * - Local development (metrics, set-strictness)
9
+ */
10
+ import * as path from "node:path";
11
+ import * as fs from "node:fs/promises";
12
+ import {
13
+ resolveProjectPath,
14
+ sanitizePath,
15
+ readFileSafe,
16
+ fileExists,
17
+ listFiles,
18
+ listFilesRecursive,
19
+ } from "./services/file-ops.js";
20
+ import { validateClaudeMD, validateSkillFrontmatter, validateJSON, estimateTokens } from "./services/validation.js";
21
+ import { detectCurrentProfile, getHooksForProfile, hooksToSettingsFormat, type StrictnessProfile } from "./services/strictness.js";
22
+ import { analyzeProject } from "./services/project-analyzer.js";
23
+ import { generateClaudeMD as generateClaudeMDContent } from "./services/claudemd-generator.js";
24
+ import { resolvePlatforms } from "./services/platform-detect.js";
25
+ import { generateCursorConfig } from "./services/cursor-generator.js";
26
+ import { generateWindsurfConfig } from "./services/windsurf-generator.js";
27
+ import { generateCodexConfig } from "./services/codex-generator.js";
28
+ import { atomicWrite } from "./services/file-ops.js";
29
+ import { CONTEXT_WINDOW_TOKENS } from "./constants.js";
30
+
31
+ const VERSION = "2.0.0";
32
+
33
+ interface CLIOptions {
34
+ path: string;
35
+ strictness: StrictnessProfile;
36
+ platforms: string[];
37
+ format: "markdown" | "json";
38
+ threshold: number;
39
+ }
40
+
41
+ function parseArgs(args: string[]): { command: string; options: CLIOptions; positional: string[] } {
42
+ const options: CLIOptions = {
43
+ path: ".",
44
+ strictness: "standard",
45
+ platforms: ["all"],
46
+ format: "markdown",
47
+ threshold: 0,
48
+ };
49
+ const positional: string[] = [];
50
+ let command = "";
51
+
52
+ for (let i = 0; i < args.length; i++) {
53
+ const arg = args[i];
54
+
55
+ if (arg.startsWith("--")) {
56
+ const key = arg.slice(2);
57
+ const value = args[++i];
58
+
59
+ switch (key) {
60
+ case "path":
61
+ options.path = value;
62
+ break;
63
+ case "strictness":
64
+ if (value === "standard" || value === "strict" || value === "paranoid") {
65
+ options.strictness = value;
66
+ }
67
+ break;
68
+ case "platforms":
69
+ options.platforms = value.split(",");
70
+ break;
71
+ case "format":
72
+ if (value === "markdown" || value === "json") {
73
+ options.format = value;
74
+ }
75
+ break;
76
+ case "threshold":
77
+ options.threshold = parseInt(value, 10);
78
+ break;
79
+ }
80
+ } else if (arg.startsWith("-")) {
81
+ // Short options (ignored for now)
82
+ } else if (!command) {
83
+ command = arg;
84
+ } else {
85
+ positional.push(arg);
86
+ }
87
+ }
88
+
89
+ return { command, options, positional };
90
+ }
91
+
92
+ async function cmdInit(options: CLIOptions): Promise<number> {
93
+ const projectPath = resolveProjectPath(options.path);
94
+ console.log(`Initializing Rebar in ${projectPath}...`);
95
+ console.log(`Strictness: ${options.strictness}`);
96
+ console.log(`Platforms: ${options.platforms.join(", ")}`);
97
+ console.log();
98
+
99
+ try {
100
+ // Analyze project
101
+ const analysis = await analyzeProject(projectPath);
102
+ console.log(`Detected tech stack: ${analysis.stacks.join(", ") || "none"}`);
103
+ console.log(`Project name: ${analysis.name}`);
104
+ console.log();
105
+
106
+ // Generate CLAUDE.md
107
+ const claudeMdContent = generateClaudeMDContent(analysis);
108
+ const claudeMdPath = path.join(projectPath, "CLAUDE.md");
109
+ await atomicWrite(claudeMdPath, claudeMdContent);
110
+ console.log("✓ Created CLAUDE.md");
111
+
112
+ // Set up directories
113
+ const claudeDir = path.join(projectPath, ".claude");
114
+ await fs.mkdir(claudeDir, { recursive: true });
115
+ await fs.mkdir(path.join(claudeDir, "skills"), { recursive: true });
116
+ await fs.mkdir(path.join(claudeDir, "agents"), { recursive: true });
117
+ await fs.mkdir(path.join(claudeDir, "docs"), { recursive: true });
118
+ console.log("✓ Created .claude/ directory structure");
119
+
120
+ // Derive commands from analysis
121
+ const testCmd = Object.values(analysis.testCommands)[0] || "npm test";
122
+ const lintCmd = analysis.linter ? `npx ${analysis.linter.toLowerCase()} .` : "npm run lint";
123
+ const formatCmd = analysis.formatter ? `npx ${analysis.formatter.toLowerCase()} --write .` : "npm run format";
124
+
125
+ // Generate settings with hooks based on strictness
126
+ const hooks = getHooksForProfile(
127
+ options.strictness,
128
+ testCmd,
129
+ lintCmd,
130
+ formatCmd
131
+ );
132
+ const settingsContent = JSON.stringify(
133
+ { hooks: hooksToSettingsFormat(hooks) },
134
+ null,
135
+ 2
136
+ );
137
+ await atomicWrite(path.join(claudeDir, "settings.json"), settingsContent);
138
+ console.log(`✓ Created .claude/settings.json (${options.strictness} strictness)`);
139
+
140
+ // Generate platform-specific configs
141
+ const platforms = await resolvePlatforms(projectPath, options.platforms);
142
+ for (const platform of platforms) {
143
+ if (platform === "cursor") {
144
+ await generateCursorConfig(projectPath, analysis.name, claudeMdContent, []);
145
+ console.log("✓ Created .cursor/rules/ for Cursor");
146
+ } else if (platform === "windsurf") {
147
+ await generateWindsurfConfig(projectPath, claudeMdContent, []);
148
+ console.log("✓ Created .windsurf/rules/ for Windsurf");
149
+ } else if (platform === "codex") {
150
+ await generateCodexConfig(projectPath, analysis.name, claudeMdContent);
151
+ console.log("✓ Created AGENTS.md for Codex");
152
+ }
153
+ }
154
+
155
+ // Create .claudeignore
156
+ const claudeignorePath = path.join(projectPath, ".claudeignore");
157
+ if (!(await fileExists(claudeignorePath))) {
158
+ const claudeignoreContent = `# Rebar default .claudeignore
159
+ node_modules/
160
+ .git/
161
+ dist/
162
+ build/
163
+ coverage/
164
+ *.log
165
+ .env
166
+ .env.*
167
+ *.pem
168
+ *.key
169
+ `;
170
+ await atomicWrite(claudeignorePath, claudeignoreContent);
171
+ console.log("✓ Created .claudeignore");
172
+ }
173
+
174
+ console.log();
175
+ console.log("Rebar initialization complete!");
176
+ console.log();
177
+ console.log("Next steps:");
178
+ console.log(" rebar doctor Check setup health");
179
+ console.log(" rebar audit Run quality audit");
180
+ console.log();
181
+ return 0;
182
+ } catch (err) {
183
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
184
+ return 1;
185
+ }
186
+ }
187
+
188
+ async function cmdDoctor(options: CLIOptions): Promise<number> {
189
+ const projectPath = resolveProjectPath(options.path);
190
+
191
+ interface Check {
192
+ name: string;
193
+ status: "PASS" | "WARN" | "FAIL";
194
+ message: string;
195
+ details?: string[];
196
+ }
197
+
198
+ const checks: Check[] = [];
199
+
200
+ try {
201
+ // Check 1: CLAUDE.md
202
+ const claudemd = await readFileSafe(path.join(projectPath, "CLAUDE.md"));
203
+ if (!claudemd) {
204
+ checks.push({ name: "CLAUDE.md", status: "FAIL", message: "Not found", details: ["Run: rebar init"] });
205
+ } else {
206
+ const validation = validateClaudeMD(claudemd, "CLAUDE.md");
207
+ if (validation.errors.length > 0) {
208
+ checks.push({ name: "CLAUDE.md", status: "FAIL", message: "Invalid", details: validation.errors });
209
+ } else {
210
+ checks.push({ name: "CLAUDE.md", status: "PASS", message: "Present and valid" });
211
+ }
212
+ }
213
+
214
+ // Check 2: Settings/Hooks
215
+ const settings = await readFileSafe(path.join(projectPath, ".claude/settings.json"));
216
+ if (!settings) {
217
+ checks.push({ name: "Hooks", status: "FAIL", message: "No settings.json", details: ["Run: rebar init"] });
218
+ } else {
219
+ const parsed = validateJSON(settings, "settings.json");
220
+ if (!parsed.valid) {
221
+ checks.push({ name: "Hooks", status: "FAIL", message: "Invalid JSON", details: parsed.errors });
222
+ } else {
223
+ const s = parsed.parsed as Record<string, unknown>;
224
+ const hooks = s.hooks as Record<string, unknown[]> | undefined;
225
+ if (!hooks || Object.keys(hooks).length === 0) {
226
+ checks.push({ name: "Hooks", status: "WARN", message: "No hooks configured" });
227
+ } else {
228
+ const count = Object.values(hooks).reduce((sum, arr) => sum + arr.length, 0);
229
+ checks.push({ name: "Hooks", status: "PASS", message: `${count} hook(s) installed` });
230
+ }
231
+ }
232
+ }
233
+
234
+ // Check 3: Skills
235
+ const skillsDir = path.join(projectPath, ".claude/skills");
236
+ const skillFiles = await listFilesRecursive(skillsDir, ".md");
237
+ if (skillFiles.length === 0) {
238
+ checks.push({ name: "Skills", status: "WARN", message: "None installed (optional)" });
239
+ } else {
240
+ checks.push({ name: "Skills", status: "PASS", message: `${skillFiles.length} skill(s)` });
241
+ }
242
+
243
+ // Check 4: .claudeignore
244
+ if (await fileExists(path.join(projectPath, ".claudeignore"))) {
245
+ checks.push({ name: ".claudeignore", status: "PASS", message: "Present" });
246
+ } else {
247
+ checks.push({ name: ".claudeignore", status: "WARN", message: "Not found (recommended)" });
248
+ }
249
+
250
+ // Check 5: Strictness profile
251
+ if (settings) {
252
+ const parsed = validateJSON(settings, "settings.json");
253
+ if (parsed.valid && parsed.parsed) {
254
+ const s = parsed.parsed as Record<string, unknown>;
255
+ const hooks = s.hooks as Record<string, unknown[]> | undefined;
256
+ if (hooks) {
257
+ const profile = detectCurrentProfile(hooks);
258
+ if (profile) {
259
+ checks.push({ name: "Strictness", status: "PASS", message: `Profile: ${profile}` });
260
+ } else {
261
+ checks.push({ name: "Strictness", status: "WARN", message: "Custom/mixed config" });
262
+ }
263
+ }
264
+ }
265
+ }
266
+
267
+ // Check 6: Context budget
268
+ let totalTokens = 0;
269
+ if (claudemd) totalTokens += estimateTokens(claudemd);
270
+ for (const sf of skillFiles) {
271
+ const content = await readFileSafe(sf);
272
+ if (content) totalTokens += estimateTokens(content);
273
+ }
274
+ const budgetPercent = (totalTokens / CONTEXT_WINDOW_TOKENS) * 100;
275
+ if (budgetPercent > 10) {
276
+ checks.push({ name: "Context", status: "FAIL", message: `${budgetPercent.toFixed(1)}% (>10%)` });
277
+ } else if (budgetPercent > 5) {
278
+ checks.push({ name: "Context", status: "WARN", message: `${budgetPercent.toFixed(1)}% (target <5%)` });
279
+ } else {
280
+ checks.push({ name: "Context", status: "PASS", message: `${budgetPercent.toFixed(1)}%` });
281
+ }
282
+
283
+ // Output
284
+ const passCount = checks.filter(c => c.status === "PASS").length;
285
+ const warnCount = checks.filter(c => c.status === "WARN").length;
286
+ const failCount = checks.filter(c => c.status === "FAIL").length;
287
+
288
+ if (options.format === "json") {
289
+ console.log(JSON.stringify({
290
+ overall: failCount === 0 ? (warnCount === 0 ? "Healthy" : "Needs Attention") : "Broken",
291
+ summary: { pass: passCount, warn: warnCount, fail: failCount },
292
+ checks,
293
+ }, null, 2));
294
+ } else {
295
+ console.log("Rebar Doctor Report");
296
+ console.log("===================");
297
+ console.log();
298
+
299
+ for (const check of checks) {
300
+ const icon = check.status === "PASS" ? "✓" : check.status === "WARN" ? "⚠" : "✗";
301
+ console.log(`${icon} ${check.name}: ${check.message}`);
302
+ if (check.details) {
303
+ for (const d of check.details) {
304
+ console.log(` ${d}`);
305
+ }
306
+ }
307
+ }
308
+
309
+ console.log();
310
+ const overall = failCount === 0 ? (warnCount === 0 ? "✓ Healthy" : "⚠ Needs Attention") : "✗ Broken";
311
+ console.log(`Overall: ${overall} (${passCount} pass, ${warnCount} warn, ${failCount} fail)`);
312
+ }
313
+
314
+ return failCount > 0 ? 1 : 0;
315
+ } catch (err) {
316
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
317
+ return 1;
318
+ }
319
+ }
320
+
321
+ async function cmdAudit(options: CLIOptions): Promise<number> {
322
+ const projectPath = resolveProjectPath(options.path);
323
+
324
+ interface AuditResult {
325
+ category: string;
326
+ score: number;
327
+ maxScore: number;
328
+ issues: string[];
329
+ }
330
+
331
+ const results: AuditResult[] = [];
332
+
333
+ try {
334
+ // 1. CLAUDE.md quality (25 points)
335
+ const claudemd = await readFileSafe(path.join(projectPath, "CLAUDE.md"));
336
+ const claudemdResult: AuditResult = { category: "CLAUDE.md", score: 0, maxScore: 25, issues: [] };
337
+ if (claudemd) {
338
+ claudemdResult.score += 10; // Exists
339
+ if (validateClaudeMD(claudemd, "CLAUDE.md").valid) claudemdResult.score += 5;
340
+ if (/##\s*(Build|Test)/i.test(claudemd)) claudemdResult.score += 5;
341
+ if (/##\s*(Architecture)/i.test(claudemd)) claudemdResult.score += 5;
342
+ } else {
343
+ claudemdResult.issues.push("No CLAUDE.md found");
344
+ }
345
+ results.push(claudemdResult);
346
+
347
+ // 2. Enforcement hooks (25 points)
348
+ const settings = await readFileSafe(path.join(projectPath, ".claude/settings.json"));
349
+ const hooksResult: AuditResult = { category: "Enforcement", score: 0, maxScore: 25, issues: [] };
350
+ if (settings) {
351
+ const parsed = validateJSON(settings, "settings.json");
352
+ if (parsed.valid && parsed.parsed) {
353
+ const s = parsed.parsed as Record<string, unknown>;
354
+ const hooks = s.hooks as Record<string, unknown[]> | undefined;
355
+ if (hooks) {
356
+ hooksResult.score += 10; // Has hooks
357
+ const profile = detectCurrentProfile(hooks);
358
+ if (profile === "standard") hooksResult.score += 5;
359
+ if (profile === "strict") hooksResult.score += 10;
360
+ if (profile === "paranoid") hooksResult.score += 15;
361
+ if (hooks.PreToolCall || hooks.PostToolCall) hooksResult.score += 0; // Already counted
362
+ } else {
363
+ hooksResult.issues.push("No hooks configured");
364
+ }
365
+ } else {
366
+ hooksResult.issues.push("Invalid settings.json");
367
+ }
368
+ } else {
369
+ hooksResult.issues.push("No settings.json found");
370
+ }
371
+ results.push(hooksResult);
372
+
373
+ // 3. Skills quality (20 points)
374
+ const skillsDir = path.join(projectPath, ".claude/skills");
375
+ const skillFiles = await listFilesRecursive(skillsDir, ".md");
376
+ const skillsResult: AuditResult = { category: "Skills", score: 0, maxScore: 20, issues: [] };
377
+ if (skillFiles.length > 0) {
378
+ skillsResult.score += 10;
379
+ let validSkills = 0;
380
+ for (const sf of skillFiles) {
381
+ const content = await readFileSafe(sf);
382
+ if (content && validateSkillFrontmatter(content).valid) {
383
+ validSkills++;
384
+ }
385
+ }
386
+ if (validSkills === skillFiles.length) {
387
+ skillsResult.score += 10;
388
+ } else {
389
+ skillsResult.issues.push(`${skillFiles.length - validSkills} skill(s) have invalid frontmatter`);
390
+ }
391
+ } else {
392
+ skillsResult.score += 10; // Skills are optional, don't penalize heavily
393
+ }
394
+ results.push(skillsResult);
395
+
396
+ // 4. Context budget (15 points)
397
+ const contextResult: AuditResult = { category: "Context Budget", score: 0, maxScore: 15, issues: [] };
398
+ let totalTokens = 0;
399
+ if (claudemd) totalTokens += estimateTokens(claudemd);
400
+ for (const sf of skillFiles) {
401
+ const content = await readFileSafe(sf);
402
+ if (content) totalTokens += estimateTokens(content);
403
+ }
404
+ const budgetPercent = (totalTokens / CONTEXT_WINDOW_TOKENS) * 100;
405
+ if (budgetPercent <= 2) {
406
+ contextResult.score = 15;
407
+ } else if (budgetPercent <= 5) {
408
+ contextResult.score = 10;
409
+ } else if (budgetPercent <= 10) {
410
+ contextResult.score = 5;
411
+ contextResult.issues.push(`Config uses ${budgetPercent.toFixed(1)}% of context`);
412
+ } else {
413
+ contextResult.issues.push(`Config uses ${budgetPercent.toFixed(1)}% (too high)`);
414
+ }
415
+ results.push(contextResult);
416
+
417
+ // 5. Best practices (15 points)
418
+ const practicesResult: AuditResult = { category: "Best Practices", score: 0, maxScore: 15, issues: [] };
419
+ if (await fileExists(path.join(projectPath, ".claudeignore"))) {
420
+ practicesResult.score += 5;
421
+ } else {
422
+ practicesResult.issues.push("No .claudeignore");
423
+ }
424
+ if (await fileExists(path.join(projectPath, ".claude/agents"))) {
425
+ const agents = await listFiles(path.join(projectPath, ".claude/agents"), ".md");
426
+ if (agents.length > 0) practicesResult.score += 5;
427
+ }
428
+ if (await fileExists(path.join(projectPath, ".claude/docs"))) {
429
+ practicesResult.score += 5;
430
+ }
431
+ results.push(practicesResult);
432
+
433
+ // Calculate total
434
+ const totalScore = results.reduce((sum, r) => sum + r.score, 0);
435
+ const maxScore = results.reduce((sum, r) => sum + r.maxScore, 0);
436
+ const percentage = Math.round((totalScore / maxScore) * 100);
437
+
438
+ // Output
439
+ if (options.format === "json") {
440
+ console.log(JSON.stringify({
441
+ score: percentage,
442
+ total: totalScore,
443
+ maxScore,
444
+ categories: results,
445
+ pass: options.threshold === 0 || percentage >= options.threshold,
446
+ }, null, 2));
447
+ } else {
448
+ console.log("Rebar Quality Audit");
449
+ console.log("===================");
450
+ console.log();
451
+
452
+ for (const r of results) {
453
+ const bar = "█".repeat(Math.round(r.score / r.maxScore * 10)) +
454
+ "░".repeat(10 - Math.round(r.score / r.maxScore * 10));
455
+ console.log(`${r.category.padEnd(16)} ${bar} ${r.score}/${r.maxScore}`);
456
+ for (const issue of r.issues) {
457
+ console.log(` ⚠ ${issue}`);
458
+ }
459
+ }
460
+
461
+ console.log();
462
+ console.log(`Overall Score: ${percentage}/100`);
463
+
464
+ if (options.threshold > 0) {
465
+ if (percentage >= options.threshold) {
466
+ console.log(`✓ Passed threshold (${options.threshold})`);
467
+ } else {
468
+ console.log(`✗ Below threshold (${options.threshold})`);
469
+ }
470
+ }
471
+ }
472
+
473
+ if (options.threshold > 0 && percentage < options.threshold) {
474
+ return 1;
475
+ }
476
+ return 0;
477
+ } catch (err) {
478
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
479
+ return 1;
480
+ }
481
+ }
482
+
483
+ async function cmdSetStrictness(options: CLIOptions, level: string): Promise<number> {
484
+ const projectPath = resolveProjectPath(options.path);
485
+
486
+ if (level !== "standard" && level !== "strict" && level !== "paranoid") {
487
+ console.error(`Invalid strictness level: ${level}`);
488
+ console.error("Valid levels: standard, strict, paranoid");
489
+ return 1;
490
+ }
491
+
492
+ try {
493
+ // Detect test/lint/format commands
494
+ const analysis = await analyzeProject(projectPath);
495
+ const testCmd = Object.values(analysis.testCommands)[0] || "npm test";
496
+ const lintCmd = analysis.linter ? `npx ${analysis.linter.toLowerCase()} .` : "npm run lint";
497
+ const formatCmd = analysis.formatter ? `npx ${analysis.formatter.toLowerCase()} --write .` : "npm run format";
498
+
499
+ // Generate hooks for profile
500
+ const hooks = getHooksForProfile(level, testCmd, lintCmd, formatCmd);
501
+
502
+ // Read existing settings or create new
503
+ const settingsPath = path.join(projectPath, ".claude/settings.json");
504
+ let existingSettings: Record<string, unknown> = {};
505
+ const settingsContent = await readFileSafe(settingsPath);
506
+ if (settingsContent) {
507
+ try {
508
+ existingSettings = JSON.parse(settingsContent) as Record<string, unknown>;
509
+ } catch {
510
+ // Start fresh
511
+ }
512
+ }
513
+
514
+ // Merge hooks into settings
515
+ existingSettings.hooks = hooksToSettingsFormat(hooks);
516
+
517
+ // Ensure directory exists
518
+ await fs.mkdir(path.join(projectPath, ".claude"), { recursive: true });
519
+
520
+ // Write settings
521
+ await atomicWrite(settingsPath, JSON.stringify(existingSettings, null, 2));
522
+
523
+ console.log(`✓ Strictness set to: ${level}`);
524
+ console.log();
525
+ console.log("Installed hooks:");
526
+ for (const hook of hooks) {
527
+ // Determine if hook blocks based on command contents
528
+ const isBlocking = hook.command.includes("exit 2");
529
+ const behavior = isBlocking ? "blocks" : "notifies";
530
+ console.log(` • ${hook.event}: ${hook.description} (${behavior})`);
531
+ }
532
+
533
+ return 0;
534
+ } catch (err) {
535
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
536
+ return 1;
537
+ }
538
+ }
539
+
540
+ async function cmdMetrics(options: CLIOptions): Promise<number> {
541
+ const projectPath = resolveProjectPath(options.path);
542
+
543
+ // Metrics are stored in .rebar/metrics.json
544
+ const metricsPath = path.join(projectPath, ".rebar/metrics.json");
545
+ const metricsContent = await readFileSafe(metricsPath);
546
+
547
+ if (!metricsContent) {
548
+ console.log("No metrics history found.");
549
+ console.log("Run 'rebar audit' to generate metrics.");
550
+ return 0;
551
+ }
552
+
553
+ try {
554
+ const metrics = JSON.parse(metricsContent) as Array<{
555
+ timestamp: string;
556
+ score: number;
557
+ categories: Record<string, number>;
558
+ }>;
559
+
560
+ if (options.format === "json") {
561
+ console.log(JSON.stringify(metrics, null, 2));
562
+ } else {
563
+ console.log("Rebar Quality Metrics History");
564
+ console.log("=============================");
565
+ console.log();
566
+
567
+ for (const m of metrics.slice(-10)) { // Last 10 entries
568
+ const date = new Date(m.timestamp).toLocaleDateString();
569
+ const bar = "█".repeat(Math.round(m.score / 10)) + "░".repeat(10 - Math.round(m.score / 10));
570
+ console.log(`${date} ${bar} ${m.score}/100`);
571
+ }
572
+ }
573
+
574
+ return 0;
575
+ } catch (err) {
576
+ console.error(`Error parsing metrics: ${err instanceof Error ? err.message : String(err)}`);
577
+ return 1;
578
+ }
579
+ }
580
+
581
+ async function cmdBadge(options: CLIOptions): Promise<number> {
582
+ const projectPath = resolveProjectPath(options.path);
583
+
584
+ // Run audit to get current score
585
+ let score = 0;
586
+ try {
587
+ const claudemd = await readFileSafe(path.join(projectPath, "CLAUDE.md"));
588
+ const settings = await readFileSafe(path.join(projectPath, ".claude/settings.json"));
589
+
590
+ // Quick score calculation
591
+ if (claudemd) score += 25;
592
+ if (settings) {
593
+ const parsed = validateJSON(settings, "settings.json");
594
+ if (parsed.valid) {
595
+ const s = parsed.parsed as Record<string, unknown>;
596
+ if (s.hooks && Object.keys(s.hooks as object).length > 0) {
597
+ score += 25;
598
+ }
599
+ }
600
+ }
601
+ if (await fileExists(path.join(projectPath, ".claudeignore"))) score += 15;
602
+ if (await fileExists(path.join(projectPath, ".claude/skills"))) score += 15;
603
+ score += 20; // Base context budget score
604
+ } catch {
605
+ // Use score of 0
606
+ }
607
+
608
+ // Determine color
609
+ let color: string;
610
+ let label: string;
611
+ if (score >= 80) {
612
+ color = "#4c1"; // Green
613
+ label = "excellent";
614
+ } else if (score >= 60) {
615
+ color = "#97ca00"; // Yellow-green
616
+ label = "good";
617
+ } else if (score >= 40) {
618
+ color = "#dfb317"; // Yellow
619
+ label = "fair";
620
+ } else {
621
+ color = "#e05d44"; // Red
622
+ label = "needs work";
623
+ }
624
+
625
+ const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="120" height="20">
626
+ <linearGradient id="b" x2="0" y2="100%">
627
+ <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
628
+ <stop offset="1" stop-opacity=".1"/>
629
+ </linearGradient>
630
+ <clipPath id="a">
631
+ <rect width="120" height="20" rx="3" fill="#fff"/>
632
+ </clipPath>
633
+ <g clip-path="url(#a)">
634
+ <path fill="#555" d="M0 0h55v20H0z"/>
635
+ <path fill="${color}" d="M55 0h65v20H55z"/>
636
+ <path fill="url(#b)" d="M0 0h120v20H0z"/>
637
+ </g>
638
+ <g fill="#fff" text-anchor="middle" font-family="DejaVu Sans,Verdana,Geneva,sans-serif" font-size="11">
639
+ <text x="27.5" y="15" fill="#010101" fill-opacity=".3">rebar</text>
640
+ <text x="27.5" y="14">rebar</text>
641
+ <text x="87.5" y="15" fill="#010101" fill-opacity=".3">${score}/100</text>
642
+ <text x="87.5" y="14">${score}/100</text>
643
+ </g>
644
+ </svg>`;
645
+
646
+ if (options.format === "json") {
647
+ console.log(JSON.stringify({ score, label, color, svg }, null, 2));
648
+ } else {
649
+ // Write SVG file
650
+ const badgePath = path.join(projectPath, ".rebar/badge.svg");
651
+ await fs.mkdir(path.join(projectPath, ".rebar"), { recursive: true });
652
+ await atomicWrite(badgePath, svg);
653
+ console.log(`✓ Badge generated: ${badgePath}`);
654
+ console.log(` Score: ${score}/100 (${label})`);
655
+ console.log();
656
+ console.log("Add to README.md:");
657
+ console.log(" ![Rebar Score](.rebar/badge.svg)");
658
+ }
659
+
660
+ return 0;
661
+ }
662
+
663
+ export async function runCLI(args: string[]): Promise<number> {
664
+ const { command, options, positional } = parseArgs(args);
665
+
666
+ switch (command) {
667
+ case "init":
668
+ return cmdInit(options);
669
+
670
+ case "doctor":
671
+ return cmdDoctor(options);
672
+
673
+ case "audit":
674
+ return cmdAudit(options);
675
+
676
+ case "set-strictness":
677
+ if (positional.length === 0) {
678
+ console.error("Usage: rebar set-strictness <standard|strict|paranoid>");
679
+ return 1;
680
+ }
681
+ return cmdSetStrictness(options, positional[0]);
682
+
683
+ case "metrics":
684
+ return cmdMetrics(options);
685
+
686
+ case "badge":
687
+ return cmdBadge(options);
688
+
689
+ case "version":
690
+ case "--version":
691
+ case "-v":
692
+ console.log(`rebar-mcp v${VERSION}`);
693
+ return 0;
694
+
695
+ case "help":
696
+ case "--help":
697
+ case "-h":
698
+ case "":
699
+ console.log(`Rebar — Reinforcement for AI-generated code
700
+
701
+ Usage:
702
+ rebar init [options] Initialize Rebar enforcement in a project
703
+ rebar audit [options] Run quality audit (exit 1 if below threshold)
704
+ rebar doctor [options] Check Rebar setup health
705
+ rebar set-strictness <level> Change enforcement strictness
706
+ rebar metrics [options] View quality score history
707
+ rebar badge [options] Generate quality score badge SVG
708
+ rebar version Print version
709
+ rebar help Show this help
710
+
711
+ Options:
712
+ --path <dir> Project path (default: current directory)
713
+ --strictness <level> standard | strict | paranoid (default: standard)
714
+ --platforms <list> claude-code,cursor,windsurf,codex,all (default: all)
715
+ --format <type> markdown | json (default: markdown)
716
+ --threshold <score> Minimum quality score for audit (exits 1 if below)
717
+
718
+ Examples:
719
+ rebar init --strictness strict --platforms all
720
+ rebar audit --threshold 80 --format json
721
+ rebar doctor
722
+ npx rebar-mcp audit --threshold 70 # CI/CD usage
723
+
724
+ Strictness Levels:
725
+ standard - Notify on issues (tests, lint, secrets)
726
+ strict - Block on test/lint failures
727
+ paranoid - Block + file size limits + TODO guards`);
728
+ return 0;
729
+
730
+ default:
731
+ console.error(`Unknown command: ${command}`);
732
+ console.error("Run 'rebar help' for usage information.");
733
+ return 1;
734
+ }
735
+ }