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
@@ -0,0 +1,909 @@
1
+ /**
2
+ * Tier 1: Core Scaffolding Tools
3
+ * rebar_init_project, rebar_generate_claudemd, rebar_create_skill,
4
+ * rebar_create_agent, rebar_create_hook, rebar_create_command
5
+ */
6
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
7
+ import * as path from "node:path";
8
+ import {
9
+ InitProjectInputSchema,
10
+ GenerateClaudeMDInputSchema,
11
+ CreateSkillInputSchema,
12
+ CreateAgentInputSchema,
13
+ CreateHookInputSchema,
14
+ CreateCommandInputSchema,
15
+ SetStrictnessInputSchema,
16
+ } from "../schemas/scaffolding.js";
17
+ import {
18
+ getHooksForProfile,
19
+ hooksToSettingsFormat,
20
+ detectCurrentProfile,
21
+ type StrictnessProfile,
22
+ } from "../services/strictness.js";
23
+ import {
24
+ resolveProjectPath,
25
+ sanitizePath,
26
+ atomicWrite,
27
+ ensureDir,
28
+ fileExists,
29
+ readFileSafe,
30
+ safeWriteFile,
31
+ } from "../services/file-ops.js";
32
+ import { analyzeProject, summarizeAnalysis } from "../services/project-analyzer.js";
33
+ import { generateClaudeMD } from "../services/claudemd-generator.js";
34
+ import { resolvePlatforms, type AIPlatform } from "../services/platform-detect.js";
35
+ import { generateCursorConfig, getDefaultCursorSkills } from "../services/cursor-generator.js";
36
+ import { generateWindsurfConfig, getDefaultWindsurfRules } from "../services/windsurf-generator.js";
37
+ import { generateCodexConfig } from "../services/codex-generator.js";
38
+ import type { GeneratedFile, ToolResult } from "../types.js";
39
+
40
+ /**
41
+ * Loads a template from the templates directory.
42
+ */
43
+ async function loadTemplate(category: string, name: string): Promise<string> {
44
+ const locations = [
45
+ path.join(import.meta.dirname, "../../templates", category, `${name}.md`),
46
+ path.join(process.cwd(), "templates", category, `${name}.md`),
47
+ ];
48
+ for (const loc of locations) {
49
+ const content = await readFileSafe(loc);
50
+ if (content !== null) return content;
51
+ }
52
+ throw new Error(`Template not found: ${category}/${name}.md`);
53
+ }
54
+
55
+ function makeResult(text: string, isError = false): ToolResult {
56
+ return {
57
+ content: [{ type: "text", text }],
58
+ ...(isError ? { isError: true } : {}),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Generates smart permissions based on what the project actually uses.
64
+ */
65
+ function generatePermissions(analysis: ReturnType<typeof Object>): { allow: string[]; deny: string[] } {
66
+ const allow: string[] = [];
67
+ const deny: string[] = [
68
+ "Bash(rm -rf *)",
69
+ "Read(./.env)",
70
+ "Read(./.env.*)",
71
+ ];
72
+
73
+ // Always allow git
74
+ allow.push("Bash(git status)", "Bash(git diff *)", "Bash(git log *)", "Bash(git add *)", "Bash(git commit *)");
75
+
76
+ const a = analysis as Awaited<ReturnType<typeof analyzeProject>>;
77
+
78
+ // Build commands from actual package.json
79
+ for (const key of Object.keys(a.buildCommands)) {
80
+ const pm = a.packageManager === "yarn" ? "yarn" : a.packageManager === "pnpm" ? "pnpm" : a.packageManager === "bun" ? "bun run" : "npm run";
81
+ allow.push(`Bash(${pm} ${key})`);
82
+ }
83
+ for (const key of Object.keys(a.testCommands)) {
84
+ const pm = a.packageManager === "yarn" ? "yarn" : a.packageManager === "pnpm" ? "pnpm" : a.packageManager === "bun" ? "bun run" : "npm run";
85
+ allow.push(`Bash(${pm} ${key} *)`);
86
+ }
87
+
88
+ // Language-specific
89
+ if (a.usesTypeScript) {
90
+ allow.push("Bash(npx tsc --noEmit)");
91
+ }
92
+ if (a.language === "python") {
93
+ allow.push("Bash(pytest *)", "Bash(ruff check *)", "Bash(mypy *)");
94
+ }
95
+ if (a.language === "go") {
96
+ allow.push("Bash(go build *)", "Bash(go test *)", "Bash(golangci-lint run *)");
97
+ }
98
+ if (a.language === "rust") {
99
+ allow.push("Bash(cargo build *)", "Bash(cargo test *)", "Bash(cargo clippy *)");
100
+ }
101
+ if (a.language === "java") {
102
+ allow.push("Bash(./mvnw *)", "Bash(./gradlew *)");
103
+ }
104
+
105
+ // ORM-specific
106
+ if (a.database === "Prisma") {
107
+ allow.push("Bash(npx prisma generate)", "Bash(npx prisma db push)");
108
+ }
109
+
110
+ return { allow, deny };
111
+ }
112
+
113
+ export function registerScaffoldingTools(server: McpServer): void {
114
+ // ─── rebar_init_project ──────────────────────────────────────────
115
+ server.registerTool(
116
+ "rebar_init_project",
117
+ {
118
+ title: "Initialize Project",
119
+ description:
120
+ "[Rebar] Full project bootstrap with enforcement hooks, opinionated templates, and quality guardrails. " +
121
+ "Reads package.json, Cargo.toml, go.mod, pom.xml, and other project manifests to generate perfectly tailored CLAUDE.md, " +
122
+ ".claude/ directory, settings.json with smart permissions, agents, .mcp.json, and .claudeignore.\n\n" +
123
+ "The generated CLAUDE.md includes real build commands, actual dependencies, " +
124
+ "framework-specific architecture rules, and detected patterns — not generic templates.\n\n" +
125
+ "Examples:\n" +
126
+ " rebar_init_project({ project_path: '.', tech_stack: ['nextjs'], team_size: 5 })\n" +
127
+ " rebar_init_project({ project_path: './api', tech_stack: ['fastapi'], team_size: 12, compliance: ['hipaa'] })\n" +
128
+ " rebar_init_project({ project_path: '.', tech_stack: ['springboot'], team_size: 50, compliance: ['sox', 'soc2'] })\n\n" +
129
+ "Returns: List of all generated files with descriptions.\n" +
130
+ "Error handling: Returns actionable suggestions if project_path doesn't exist or isn't writable.",
131
+ inputSchema: InitProjectInputSchema,
132
+ annotations: {
133
+ readOnlyHint: false,
134
+ destructiveHint: false,
135
+ idempotentHint: false,
136
+ openWorldHint: false,
137
+ },
138
+ },
139
+ async (args) => {
140
+ const { project_path, tech_stack, team_size, compliance, strictness, platforms } = args;
141
+ const absPath = resolveProjectPath(project_path);
142
+ const generatedFiles: GeneratedFile[] = [];
143
+ const effectiveStrictness = (strictness || "standard") as StrictnessProfile;
144
+
145
+ // Resolve which platforms to generate for
146
+ const platformsArray = (platforms || ["all"]) as string[];
147
+ const targetPlatforms = await resolvePlatforms(absPath, platformsArray);
148
+
149
+ try {
150
+ // 1. Deep project analysis — this is what makes the output smart
151
+ const analysis = await analyzeProject(absPath);
152
+
153
+ // Merge user-specified tech stack with auto-detected
154
+ const userStacks = tech_stack as string[];
155
+ for (const stack of userStacks) {
156
+ if (!analysis.stacks.includes(stack as typeof analysis.stacks[number])) {
157
+ analysis.stacks.push(stack as typeof analysis.stacks[number]);
158
+ }
159
+ }
160
+
161
+ // 2. Create directory structure
162
+ const dirs = [
163
+ ".claude/agents",
164
+ ".claude/skills",
165
+ ".claude/docs",
166
+ ".claude/scripts",
167
+ ];
168
+ for (const dir of dirs) {
169
+ await ensureDir(sanitizePath(absPath, dir));
170
+ }
171
+
172
+ // 3. Generate CLAUDE.md from real analysis
173
+ const claudemd = generateClaudeMD(analysis, compliance);
174
+ const claudemdPath = sanitizePath(absPath, "CLAUDE.md");
175
+ await safeWriteFile(claudemdPath, claudemd, false);
176
+ generatedFiles.push({
177
+ path: "CLAUDE.md",
178
+ content: claudemd,
179
+ description: `Project CLAUDE.md (${claudemd.split("\n").length} lines, tailored to ${analysis.stacks.join("+")})`
180
+ });
181
+
182
+ // 4. Generate .claudeignore based on actual project
183
+ const ignoreEntries = ["node_modules/", "dist/", ".git/", "coverage/"];
184
+ if (analysis.language === "python") ignoreEntries.push("__pycache__/", ".venv/", "*.pyc");
185
+ if (analysis.language === "rust") ignoreEntries.push("target/");
186
+ if (analysis.language === "go") ignoreEntries.push("vendor/");
187
+ if (analysis.language === "java") ignoreEntries.push("target/", "build/", ".gradle/");
188
+ if (analysis.hasDocker) ignoreEntries.push(".docker/");
189
+ const claudeignore = ignoreEntries.join("\n") + "\n";
190
+ const ignorePath = sanitizePath(absPath, ".claudeignore");
191
+ await safeWriteFile(ignorePath, claudeignore, false);
192
+ generatedFiles.push({ path: ".claudeignore", content: claudeignore, description: `Ignore file (${ignoreEntries.length} patterns)` });
193
+
194
+ // 5. Generate smart settings.json with real permissions and enforcement hooks
195
+ const permissions = generatePermissions(analysis);
196
+ const settings: Record<string, unknown> = { permissions };
197
+
198
+ // Determine project commands for hook generation
199
+ const testCommand = analysis.testCommands?.test || analysis.testCommands?.["test:unit"] || "npm test";
200
+ const lintCommand = analysis.linter
201
+ ? analysis.linter === "ESLint" ? "npx eslint" : "npx biome lint"
202
+ : "npx eslint";
203
+ const formatCommand = analysis.formatter
204
+ ? analysis.formatter === "Prettier" ? "npx prettier --write"
205
+ : analysis.formatter === "Biome" ? "npx @biomejs/biome format --write"
206
+ : analysis.formatter === "Ruff" ? "ruff format"
207
+ : analysis.formatter === "Black" ? "black"
208
+ : "npx prettier --write"
209
+ : "npx prettier --write";
210
+
211
+ // Generate hooks based on strictness profile
212
+ const profileHooks = getHooksForProfile(
213
+ effectiveStrictness,
214
+ testCommand,
215
+ lintCommand,
216
+ formatCommand
217
+ );
218
+ const hooksConfig = hooksToSettingsFormat(profileHooks);
219
+ settings.hooks = hooksConfig;
220
+
221
+ const hookCount = profileHooks.length;
222
+ const settingsContent = JSON.stringify(settings, null, 2);
223
+ const settingsPath = sanitizePath(absPath, ".claude/settings.json");
224
+ await safeWriteFile(settingsPath, settingsContent, false);
225
+ generatedFiles.push({
226
+ path: ".claude/settings.json",
227
+ content: settingsContent,
228
+ description: `${hookCount} enforcement hooks (strictness: ${effectiveStrictness})`,
229
+ });
230
+
231
+ // 6. Generate agents based on project type
232
+ const agentTemplates = ["explore", "plan", "test-runner"];
233
+ for (const agent of agentTemplates) {
234
+ try {
235
+ const agentContent = await loadTemplate("agents", agent);
236
+ const agentPath = sanitizePath(absPath, `.claude/agents/${agent}.md`);
237
+ await safeWriteFile(agentPath, agentContent, false);
238
+ generatedFiles.push({ path: `.claude/agents/${agent}.md`, content: agentContent, description: `${agent} subagent` });
239
+ } catch {
240
+ // Template not found, skip
241
+ }
242
+ }
243
+
244
+ // 7. Generate .mcp.json stub
245
+ const mcpConfig = { mcpServers: {} };
246
+ const mcpPath = sanitizePath(absPath, ".mcp.json");
247
+ await safeWriteFile(mcpPath, JSON.stringify(mcpConfig, null, 2), false);
248
+ generatedFiles.push({ path: ".mcp.json", content: JSON.stringify(mcpConfig, null, 2), description: "MCP server configuration" });
249
+
250
+ // 8. Multi-platform configuration generation
251
+ const projectName = path.basename(absPath);
252
+ const platformResults: Record<string, GeneratedFile[]> = {};
253
+
254
+ // Cursor support
255
+ if (targetPlatforms.includes("cursor")) {
256
+ const cursorFiles = await generateCursorConfig(
257
+ absPath,
258
+ projectName,
259
+ claudemd,
260
+ getDefaultCursorSkills()
261
+ );
262
+ platformResults.cursor = cursorFiles;
263
+ generatedFiles.push(...cursorFiles);
264
+ }
265
+
266
+ // Windsurf support
267
+ if (targetPlatforms.includes("windsurf")) {
268
+ const windsurfFiles = await generateWindsurfConfig(
269
+ absPath,
270
+ claudemd,
271
+ getDefaultWindsurfRules()
272
+ );
273
+ platformResults.windsurf = windsurfFiles;
274
+ generatedFiles.push(...windsurfFiles);
275
+ }
276
+
277
+ // Codex CLI support
278
+ if (targetPlatforms.includes("codex")) {
279
+ const codexFiles = await generateCodexConfig(absPath, projectName, claudemd);
280
+ platformResults.codex = codexFiles;
281
+ generatedFiles.push(...codexFiles);
282
+ }
283
+
284
+ // Build the analysis summary
285
+ const analysisSummary = summarizeAnalysis(analysis);
286
+
287
+ const filesSummary = generatedFiles
288
+ .map((f) => ` ${f.path} — ${f.description}`)
289
+ .join("\n");
290
+
291
+ // Build platform summary
292
+ const platformSummary = targetPlatforms.map((p) => {
293
+ const files = p === "claude-code"
294
+ ? generatedFiles.filter((f) => f.path.startsWith("CLAUDE.md") || f.path.startsWith(".claude"))
295
+ : platformResults[p] || [];
296
+ return `• ${p}: ${files.length} files`;
297
+ }).join("\n");
298
+
299
+ return makeResult(
300
+ `## Rebar Project Initialized\n\n` +
301
+ `**Project:** ${absPath}\n` +
302
+ `**Platforms:** ${targetPlatforms.join(", ")}\n` +
303
+ `**Strictness:** ${effectiveStrictness}\n\n` +
304
+ `### Analysis\n${analysisSummary}\n\n` +
305
+ `### Platforms Configured\n${platformSummary}\n\n` +
306
+ `### Enforcement\n` +
307
+ `• ${hookCount} hooks installed (strictness: ${effectiveStrictness})\n` +
308
+ (effectiveStrictness === "standard" ? "• Test/lint failures notify but don't block\n" : "") +
309
+ (effectiveStrictness === "strict" ? "• Test/lint failures BLOCK until fixed\n" : "") +
310
+ (effectiveStrictness === "paranoid" ? "• All quality checks BLOCK, plus file size limits and type safety\n" : "") +
311
+ `\n### Generated Files\n${filesSummary}\n\n` +
312
+ `### Next Steps\n` +
313
+ `1. Review CLAUDE.md — it's ready to use but you know your project best\n` +
314
+ `2. rebar_create_skill to add domain-specific skills\n` +
315
+ (effectiveStrictness === "standard" ? `3. rebar_set_strictness to increase enforcement to 'strict' or 'paranoid'\n` : "") +
316
+ (compliance && compliance.length > 0
317
+ ? `4. rebar_apply_compliance to generate ${compliance.join(", ").toUpperCase()} artifacts\n`
318
+ : "") +
319
+ (team_size > 10
320
+ ? `5. Consider rebar_create_ci_workflow for automated PR reviews (team of ${team_size})\n`
321
+ : "")
322
+ );
323
+ } catch (err: unknown) {
324
+ const message = err instanceof Error ? err.message : String(err);
325
+ return makeResult(
326
+ `Failed to initialize project: ${message}\n\n` +
327
+ `Suggestions:\n` +
328
+ `• Ensure ${absPath} exists and is writable\n` +
329
+ `• Check that the directory contains a project manifest (package.json, Cargo.toml, etc.)\n` +
330
+ `• If it's a new project, create the directory first and add a package.json`,
331
+ true
332
+ );
333
+ }
334
+ }
335
+ );
336
+
337
+ // ─── rebar_generate_claudemd ─────────────────────────────────────
338
+ server.registerTool(
339
+ "rebar_generate_claudemd",
340
+ {
341
+ title: "Generate CLAUDE.md",
342
+ description:
343
+ "[Rebar] Creates a deeply customized CLAUDE.md by analyzing your actual project. Reads package.json, " +
344
+ "detects frameworks, finds real build/test/lint commands, identifies ORMs, CSS frameworks, " +
345
+ "state management, auth libraries, and generates architecture rules specific to your stack.\n\n" +
346
+ "The output reflects what your project actually IS — not a generic template.\n\n" +
347
+ "Examples:\n" +
348
+ " rebar_generate_claudemd({ project_path: '.', scope: 'root' })\n" +
349
+ " rebar_generate_claudemd({ project_path: './apps/web', scope: 'subdir', merge_existing: true })\n" +
350
+ " rebar_generate_claudemd({ project_path: '.', tech_stack: ['nextjs', 'express'] })\n\n" +
351
+ "Returns: Path to generated file, detected stack summary, line count.\n" +
352
+ "Error: Returns if no tech stack detected (suggests providing tech_stack parameter).",
353
+ inputSchema: GenerateClaudeMDInputSchema,
354
+ annotations: {
355
+ readOnlyHint: false,
356
+ destructiveHint: false,
357
+ idempotentHint: true,
358
+ openWorldHint: false,
359
+ },
360
+ },
361
+ async (args) => {
362
+ const { project_path, scope, tech_stack, merge_existing } = args;
363
+ const absPath = resolveProjectPath(project_path);
364
+
365
+ try {
366
+ const analysis = await analyzeProject(absPath);
367
+
368
+ // Override with user-provided stacks
369
+ if (tech_stack) {
370
+ for (const stack of tech_stack) {
371
+ if (!analysis.stacks.includes(stack as typeof analysis.stacks[number])) {
372
+ analysis.stacks.push(stack as typeof analysis.stacks[number]);
373
+ }
374
+ }
375
+ }
376
+
377
+ if (analysis.stacks.length === 0 && analysis.language === "unknown") {
378
+ return makeResult(
379
+ "Could not detect your project's tech stack.\n\n" +
380
+ "This usually means:\n" +
381
+ "• No package.json, Cargo.toml, go.mod, or pom.xml was found\n" +
382
+ "• The project directory might be empty or misconfigured\n\n" +
383
+ "Fix: Provide the tech_stack parameter explicitly:\n" +
384
+ ' rebar_generate_claudemd({ project_path: ".", tech_stack: ["nextjs"] })\n\n' +
385
+ "Supported stacks: nextjs, react, vue, angular, svelte, express, fastapi, django, flask, springboot, go, rust, dotnet",
386
+ true
387
+ );
388
+ }
389
+
390
+ const content = generateClaudeMD(analysis);
391
+ const claudemdPath = scope === "subdir"
392
+ ? sanitizePath(absPath, "CLAUDE.md")
393
+ : sanitizePath(absPath, "CLAUDE.md");
394
+
395
+ const result = await safeWriteFile(claudemdPath, content, merge_existing);
396
+ const lineCount = content.split("\n").length;
397
+ const analysisSummary = summarizeAnalysis(analysis);
398
+
399
+ return makeResult(
400
+ `${result.message}\n\n` +
401
+ `── What was detected ──\n${analysisSummary}\n\n` +
402
+ `── Generated ──\n` +
403
+ `Lines: ${lineCount}\n` +
404
+ `Sections: Tech Stack, Architecture Rules, Build & Test, ` +
405
+ `${analysis.sourceDirs.length > 0 ? "File Structure, " : ""}` +
406
+ `Code Conventions` +
407
+ `${analysis.database ? ", Database" : ""}` +
408
+ `${analysis.envVarNames.length > 0 ? ", Environment Variables" : ""}\n\n` +
409
+ (result.merged ? "Note: Merged with existing CLAUDE.md (previous content was replaced).\n" : "") +
410
+ `Review and customize — this is your project's AI instruction manual.`
411
+ );
412
+ } catch (err: unknown) {
413
+ const message = err instanceof Error ? err.message : String(err);
414
+ return makeResult(`Failed to generate CLAUDE.md: ${message}`, true);
415
+ }
416
+ }
417
+ );
418
+
419
+ // ─── rebar_create_skill ──────────────────────────────────────────
420
+ server.registerTool(
421
+ "rebar_create_skill",
422
+ {
423
+ title: "Create Skill",
424
+ description:
425
+ "[Rebar] Scaffolds a production-ready skill in .claude/skills/<name>/ with SKILL.md containing " +
426
+ "valid YAML frontmatter. Skills are reusable AI capabilities — code review, test generation, " +
427
+ "documentation, security scanning, etc.\n\n" +
428
+ "The YAML frontmatter controls behavior:\n" +
429
+ "• invocation: 'user' (slash command), 'auto' (triggered by context), 'both'\n" +
430
+ "• context: 'inline' (runs in main session), 'fork' (runs in subagent)\n" +
431
+ "• allowed_tools: restricts which tools the skill can use\n\n" +
432
+ "Examples:\n" +
433
+ " rebar_create_skill({ name: 'code-review', description: 'Reviews code changes for quality and security', invocation: 'auto', context: 'fork', allowed_tools: ['Read', 'Grep', 'Glob'] })\n" +
434
+ " rebar_create_skill({ name: 'db-migrate', description: 'Generates and applies database migrations', invocation: 'user', context: 'inline', allowed_tools: ['Read', 'Write', 'Bash'] })\n\n" +
435
+ "Returns: Skill location, frontmatter summary, next steps.\n" +
436
+ "Error: If skill name already exists (suggests different name).",
437
+ inputSchema: CreateSkillInputSchema,
438
+ annotations: {
439
+ readOnlyHint: false,
440
+ destructiveHint: false,
441
+ idempotentHint: false,
442
+ openWorldHint: false,
443
+ },
444
+ },
445
+ async (args) => {
446
+ const { name, description, invocation, context, agent_type, allowed_tools } = args;
447
+ const skillDir = path.join(process.cwd(), ".claude", "skills", name);
448
+
449
+ try {
450
+ if (await fileExists(skillDir)) {
451
+ return makeResult(
452
+ `Skill "${name}" already exists at ${skillDir}\n\n` +
453
+ `Options:\n` +
454
+ `• Use a different name\n` +
455
+ `• Remove the existing skill directory and try again\n` +
456
+ `• Edit the existing SKILL.md directly`,
457
+ true
458
+ );
459
+ }
460
+
461
+ await ensureDir(skillDir);
462
+
463
+ // Try to load a matching template for common skill types
464
+ let templateBody = "";
465
+ const templateNames = ["code-review", "test-writer", "documentation", "security-scan", "performance-audit"];
466
+ for (const tpl of templateNames) {
467
+ if (name.includes(tpl.split("-")[0])) {
468
+ try {
469
+ const fullTemplate = await loadTemplate("skills", tpl);
470
+ // Extract just the body (after frontmatter)
471
+ const bodyMatch = fullTemplate.match(/^---[\s\S]*?---\s*([\s\S]*)$/);
472
+ if (bodyMatch) templateBody = bodyMatch[1].trim();
473
+ break;
474
+ } catch {
475
+ // No template found, that's fine
476
+ }
477
+ }
478
+ }
479
+
480
+ // Build YAML frontmatter
481
+ const frontmatterLines = [
482
+ "---",
483
+ `name: ${name}`,
484
+ `description: ${description}`,
485
+ `invocation: ${invocation}`,
486
+ `context: ${context}`,
487
+ ];
488
+ if (agent_type) {
489
+ frontmatterLines.push(`agent_type: ${agent_type}`);
490
+ }
491
+ if (allowed_tools && allowed_tools.length > 0) {
492
+ frontmatterLines.push("allowed_tools:");
493
+ for (const tool of allowed_tools) {
494
+ frontmatterLines.push(` - ${tool}`);
495
+ }
496
+ }
497
+ frontmatterLines.push("---");
498
+
499
+ const body = templateBody || (
500
+ `# ${name}\n\n` +
501
+ `${description}\n\n` +
502
+ "## Instructions\n\n" +
503
+ "<!-- Replace this with your skill instructions.\n" +
504
+ "Good skill instructions:\n" +
505
+ "1. Define the agent's role clearly\n" +
506
+ "2. Specify the exact output format\n" +
507
+ "3. List what to check/do step by step\n" +
508
+ "4. Include examples of good output\n" +
509
+ "-->\n"
510
+ );
511
+
512
+ const skillContent = frontmatterLines.join("\n") + "\n\n" + body + "\n";
513
+
514
+ const skillPath = path.join(skillDir, "SKILL.md");
515
+ await atomicWrite(skillPath, skillContent);
516
+
517
+ const invocationHint = invocation === "user"
518
+ ? `Invoke with: /${name}`
519
+ : invocation === "auto"
520
+ ? "Will trigger automatically based on context"
521
+ : `Invoke with: /${name} (also triggers automatically)`;
522
+
523
+ return makeResult(
524
+ `Skill created: ${name}\n\n` +
525
+ `Location: ${skillDir}/SKILL.md\n` +
526
+ `${invocationHint}\n` +
527
+ `Context: ${context === "fork" ? "Runs in isolated subagent" : "Runs in main session"}\n` +
528
+ (allowed_tools && allowed_tools.length > 0 ? `Tools: ${allowed_tools.join(", ")}\n` : "") +
529
+ (templateBody ? "\nPre-filled with a matching template — customize as needed.\n" : "\nEdit the SKILL.md to add your instructions.\n")
530
+ );
531
+ } catch (err: unknown) {
532
+ const message = err instanceof Error ? err.message : String(err);
533
+ return makeResult(`Failed to create skill: ${message}`, true);
534
+ }
535
+ }
536
+ );
537
+
538
+ // ─── rebar_create_agent ──────────────────────────────────────────
539
+ server.registerTool(
540
+ "rebar_create_agent",
541
+ {
542
+ title: "Create Agent",
543
+ description:
544
+ "[Rebar] Scaffolds a subagent definition in .claude/agents/. Subagents are specialized AI workers " +
545
+ "with restricted tool access and focused system prompts.\n\n" +
546
+ "Built-in roles with smart defaults:\n" +
547
+ "• explore: Read-only codebase investigation (Read, Grep, Glob)\n" +
548
+ "• plan: Architecture planning without code changes (Read, Grep, Glob)\n" +
549
+ "• general: Full-capability agent\n" +
550
+ "• custom: Blank template for custom behavior\n\n" +
551
+ "Examples:\n" +
552
+ " rebar_create_agent({ name: 'security-auditor', description: 'Scans for OWASP vulnerabilities', role: 'explore' })\n" +
553
+ " rebar_create_agent({ name: 'db-migrator', description: 'Plans and executes database migrations', role: 'general', allowed_tools: ['Read', 'Write', 'Bash'] })\n\n" +
554
+ "Returns: Agent file path, role, next steps.\n" +
555
+ "Error: If agent name already exists.",
556
+ inputSchema: CreateAgentInputSchema,
557
+ annotations: {
558
+ readOnlyHint: false,
559
+ destructiveHint: false,
560
+ idempotentHint: false,
561
+ openWorldHint: false,
562
+ },
563
+ },
564
+ async (args) => {
565
+ const { name, description, role, allowed_tools, model_hint } = args;
566
+ const agentDir = path.join(process.cwd(), ".claude", "agents");
567
+ const agentPath = path.join(agentDir, `${name}.md`);
568
+
569
+ try {
570
+ if (await fileExists(agentPath)) {
571
+ return makeResult(
572
+ `Agent "${name}" already exists at ${agentPath}\n\n` +
573
+ `Options:\n` +
574
+ `• Use a different name\n` +
575
+ `• Remove the existing agent file and try again\n` +
576
+ `• Edit the existing file directly`,
577
+ true
578
+ );
579
+ }
580
+
581
+ await ensureDir(agentDir);
582
+
583
+ // Try to load a role-based template
584
+ let content: string;
585
+ try {
586
+ content = await loadTemplate("agents", role === "custom" ? "explore" : role);
587
+ content = `# ${name}\n\n${description}\n\n` + content.split("\n").slice(3).join("\n");
588
+ } catch {
589
+ const toolsList = allowed_tools && allowed_tools.length > 0
590
+ ? allowed_tools.map((t) => `- ${t}`).join("\n")
591
+ : "- Read\n- Grep\n- Glob";
592
+
593
+ content =
594
+ `# ${name}\n\n` +
595
+ `${description}\n\n` +
596
+ `## Role\n${getRoleDescription(role)}\n\n` +
597
+ `## Allowed Tools\n${toolsList}\n` +
598
+ (model_hint ? `\n## Model Hint\nPreferred model: ${model_hint}\n` : "") +
599
+ "\n## Guidelines\n" +
600
+ "- Be thorough and systematic\n" +
601
+ "- Report findings with file paths and line numbers\n" +
602
+ "- Provide actionable recommendations\n";
603
+ }
604
+
605
+ await atomicWrite(agentPath, content);
606
+
607
+ return makeResult(
608
+ `Agent created: ${name}\n\n` +
609
+ `Location: ${agentPath}\n` +
610
+ `Role: ${role}\n` +
611
+ `Description: ${description}\n` +
612
+ (model_hint ? `Model hint: ${model_hint}\n` : "") +
613
+ `\nThis agent will be available as a subagent type in Claude Code.\n` +
614
+ `Edit ${agentPath} to customize its behavior.`
615
+ );
616
+ } catch (err: unknown) {
617
+ const message = err instanceof Error ? err.message : String(err);
618
+ return makeResult(`Failed to create agent: ${message}`, true);
619
+ }
620
+ }
621
+ );
622
+
623
+ // ─── rebar_create_hook ───────────────────────────────────────────
624
+ server.registerTool(
625
+ "rebar_create_hook",
626
+ {
627
+ title: "Create Hook",
628
+ description:
629
+ "[Rebar] Adds a lifecycle hook to .claude/settings.json. Hooks run shell commands in response to " +
630
+ "Claude Code events — format code after writes, lint changes, block dangerous commands, etc.\n\n" +
631
+ "Supported events:\n" +
632
+ "• PreToolCall: Before a tool executes (use to block or validate)\n" +
633
+ "• PostToolCall: After a tool executes (use to format, lint, test)\n" +
634
+ "• Notification: On status notifications\n" +
635
+ "• Stop: When Claude stops generating\n" +
636
+ "• SubagentStop: When a subagent stops\n" +
637
+ "• PreCompact: Before context compaction\n" +
638
+ "• PostCompact: After context compaction\n" +
639
+ "• SessionStart: When a new session begins\n\n" +
640
+ "Environment variables available: $TOOL_INPUT_COMMAND, $TOOL_INPUT_FILE_PATH, etc.\n\n" +
641
+ "Examples:\n" +
642
+ ' rebar_create_hook({ event: "PostToolCall", matcher: "Write|Edit", command: \'npx prettier --write "$TOOL_INPUT_FILE_PATH"\', exit_behavior: "notify", description: "Format on write" })\n' +
643
+ ' rebar_create_hook({ event: "PreToolCall", matcher: "Bash", command: \'echo "$TOOL_INPUT_COMMAND" | grep -qE "rm -rf|DROP TABLE" && exit 1\', exit_behavior: "block", description: "Block dangerous commands" })\n\n' +
644
+ "Returns: Hook details, updated settings path.\n" +
645
+ "Idempotent: Re-running with same command+matcher updates the existing hook.",
646
+ inputSchema: CreateHookInputSchema,
647
+ annotations: {
648
+ readOnlyHint: false,
649
+ destructiveHint: false,
650
+ idempotentHint: true,
651
+ openWorldHint: false,
652
+ },
653
+ },
654
+ async (args) => {
655
+ const { event, matcher, command, exit_behavior, description } = args;
656
+ const settingsPath = path.join(process.cwd(), ".claude", "settings.json");
657
+
658
+ try {
659
+ let settings: Record<string, unknown> = {};
660
+ const existing = await readFileSafe(settingsPath);
661
+ if (existing) {
662
+ settings = JSON.parse(existing) as Record<string, unknown>;
663
+ }
664
+
665
+ if (!settings.hooks) settings.hooks = {};
666
+ const hooks = settings.hooks as Record<string, unknown[]>;
667
+ if (!hooks[event]) hooks[event] = [];
668
+
669
+ const hookEntry: Record<string, unknown> = { description, command, exit_behavior };
670
+ if (matcher) hookEntry.matcher = matcher;
671
+
672
+ const eventHooks = hooks[event] as Record<string, unknown>[];
673
+ const existingIdx = eventHooks.findIndex(
674
+ (h) => h.command === command && h.matcher === matcher
675
+ );
676
+
677
+ const isUpdate = existingIdx >= 0;
678
+ if (isUpdate) {
679
+ eventHooks[existingIdx] = hookEntry;
680
+ } else {
681
+ eventHooks.push(hookEntry);
682
+ }
683
+
684
+ await ensureDir(path.dirname(settingsPath));
685
+ await atomicWrite(settingsPath, JSON.stringify(settings, null, 2));
686
+
687
+ return makeResult(
688
+ `Hook ${isUpdate ? "updated" : "added"}: ${description}\n\n` +
689
+ `Event: ${event}\n` +
690
+ (matcher ? `Matcher: ${matcher}\n` : "All tool calls\n") +
691
+ `Command: ${command}\n` +
692
+ `On failure: ${exit_behavior === "block" ? "BLOCKS the action" : "Shows notification"}\n\n` +
693
+ `Updated: ${settingsPath}`
694
+ );
695
+ } catch (err: unknown) {
696
+ const message = err instanceof Error ? err.message : String(err);
697
+ return makeResult(`Failed to create hook: ${message}`, true);
698
+ }
699
+ }
700
+ );
701
+
702
+ // ─── rebar_create_command ────────────────────────────────────────
703
+ server.registerTool(
704
+ "rebar_create_command",
705
+ {
706
+ title: "Create Command",
707
+ description:
708
+ "[Rebar] Creates a slash command (e.g., /review, /deploy, /docs) as a skill with user invocation. " +
709
+ "Slash commands are the primary way teams standardize Claude Code workflows.\n\n" +
710
+ "When a user types /command-name, Claude receives the prompt_body as instructions.\n\n" +
711
+ "Examples:\n" +
712
+ ' rebar_create_command({ name: "review", description: "Review current changes", prompt_body: "Review the current git diff. Check for security issues, performance problems, and code quality. Output findings as Critical/Warning/Suggestion." })\n' +
713
+ ' rebar_create_command({ name: "deploy-check", description: "Pre-deploy checklist", prompt_body: "Run through the deployment checklist: 1. All tests pass 2. No TODO/FIXME in changed files 3. No console.log statements 4. Database migrations are reversible", arguments: [{ name: "env", description: "Target environment", required: true }] })\n\n' +
714
+ "Returns: Slash command usage, file location.\n" +
715
+ "Error: If command name already exists.",
716
+ inputSchema: CreateCommandInputSchema,
717
+ annotations: {
718
+ readOnlyHint: false,
719
+ destructiveHint: false,
720
+ idempotentHint: false,
721
+ openWorldHint: false,
722
+ },
723
+ },
724
+ async (args) => {
725
+ const { name, description, prompt_body, arguments: cmdArgs } = args;
726
+ const skillDir = path.join(process.cwd(), ".claude", "skills", name);
727
+
728
+ try {
729
+ if (await fileExists(skillDir)) {
730
+ return makeResult(
731
+ `Command "/${name}" already exists at ${skillDir}\n\n` +
732
+ `Options:\n` +
733
+ `• Use a different name\n` +
734
+ `• Remove the existing skill directory and try again\n` +
735
+ `• Edit the existing SKILL.md directly`,
736
+ true
737
+ );
738
+ }
739
+
740
+ await ensureDir(skillDir);
741
+
742
+ const frontmatterLines = [
743
+ "---",
744
+ `name: ${name}`,
745
+ `description: ${description}`,
746
+ "invocation: user",
747
+ "context: inline",
748
+ "---",
749
+ ];
750
+
751
+ let argsSection = "";
752
+ if (cmdArgs && cmdArgs.length > 0) {
753
+ argsSection =
754
+ "\n## Arguments\n\n" +
755
+ cmdArgs
756
+ .map((a) => `- **${a.name}**${a.required ? " (required)" : " (optional)"}: ${a.description}`)
757
+ .join("\n") +
758
+ "\n";
759
+ }
760
+
761
+ const content =
762
+ frontmatterLines.join("\n") +
763
+ "\n\n" +
764
+ `# /${name}\n\n` +
765
+ `${description}\n` +
766
+ argsSection +
767
+ "\n## Prompt\n\n" +
768
+ prompt_body +
769
+ "\n";
770
+
771
+ const skillPath = path.join(skillDir, "SKILL.md");
772
+ await atomicWrite(skillPath, content);
773
+
774
+ return makeResult(
775
+ `Command created: /${name}\n\n` +
776
+ `Location: ${skillPath}\n` +
777
+ `Description: ${description}\n` +
778
+ (cmdArgs && cmdArgs.length > 0
779
+ ? `Arguments: ${cmdArgs.map((a) => `${a.name}${a.required ? "" : "?"}`).join(", ")}\n`
780
+ : "") +
781
+ `\nUsage: Type /${name} in Claude Code to invoke this command.`
782
+ );
783
+ } catch (err: unknown) {
784
+ const message = err instanceof Error ? err.message : String(err);
785
+ return makeResult(`Failed to create command: ${message}`, true);
786
+ }
787
+ }
788
+ );
789
+
790
+ // ─── rebar_set_strictness ───────────────────────────────────────────
791
+ server.registerTool(
792
+ "rebar_set_strictness",
793
+ {
794
+ title: "Set Rebar Strictness Profile",
795
+ description:
796
+ "[Rebar] Changes the enforcement strictness level for the current project. Rewrites the hooks " +
797
+ "in .claude/settings.json to match the selected profile.\n\n" +
798
+ "Profiles:\n" +
799
+ "• standard: Hooks notify on issues but don't block (except dangerous commands and secrets)\n" +
800
+ "• strict: Hooks block on test/lint failures\n" +
801
+ "• paranoid: All strict hooks + file size limits, any-type blocking, commit completeness\n\n" +
802
+ "Examples:\n" +
803
+ " rebar_set_strictness({ project_path: '.', strictness: 'strict' })\n" +
804
+ " rebar_set_strictness({ project_path: '.', strictness: 'paranoid' })\n\n" +
805
+ "Idempotent: Safe to run multiple times.",
806
+ inputSchema: SetStrictnessInputSchema,
807
+ annotations: {
808
+ readOnlyHint: false,
809
+ destructiveHint: false,
810
+ idempotentHint: true,
811
+ openWorldHint: false,
812
+ },
813
+ },
814
+ async (args) => {
815
+ const { project_path, strictness, output_format } = args;
816
+ const absPath = resolveProjectPath(project_path);
817
+ const settingsPath = sanitizePath(absPath, ".claude/settings.json");
818
+
819
+ try {
820
+ // Read existing settings
821
+ const existingContent = await readFileSafe(settingsPath);
822
+ let settings: Record<string, unknown> = {};
823
+ if (existingContent) {
824
+ settings = JSON.parse(existingContent) as Record<string, unknown>;
825
+ }
826
+
827
+ // Detect current profile
828
+ const currentHooks = (settings.hooks || {}) as Record<string, unknown[]>;
829
+ const currentProfile = detectCurrentProfile(currentHooks);
830
+
831
+ // Detect project commands (try to read from package.json or use defaults)
832
+ let testCommand = "npm test";
833
+ let lintCommand = "npx eslint";
834
+ let formatCommand = "npx prettier --write";
835
+
836
+ const pkgPath = sanitizePath(absPath, "package.json");
837
+ const pkgContent = await readFileSafe(pkgPath);
838
+ if (pkgContent) {
839
+ const pkg = JSON.parse(pkgContent) as Record<string, unknown>;
840
+ const scripts = (pkg.scripts || {}) as Record<string, string>;
841
+ if (scripts.test) testCommand = `npm run test`;
842
+ if (scripts.lint) lintCommand = `npm run lint --`;
843
+ if (scripts.format) formatCommand = `npm run format --`;
844
+ }
845
+
846
+ // Generate new hooks for the target profile
847
+ const newHooks = getHooksForProfile(
848
+ strictness as StrictnessProfile,
849
+ testCommand,
850
+ lintCommand,
851
+ formatCommand
852
+ );
853
+ const hooksConfig = hooksToSettingsFormat(newHooks);
854
+
855
+ // Update settings with new hooks
856
+ settings.hooks = hooksConfig;
857
+
858
+ // Write back atomically
859
+ await ensureDir(path.dirname(settingsPath));
860
+ await atomicWrite(settingsPath, JSON.stringify(settings, null, 2));
861
+
862
+ const hookCount = newHooks.length;
863
+ const profileDescriptions: Record<string, string> = {
864
+ standard: "Tests/lint notify but don't block. Dangerous commands and secrets always blocked.",
865
+ strict: "Tests and lint failures BLOCK. All standard protections apply.",
866
+ paranoid: "Maximum enforcement: tests, lint, file size (400 lines), any types, and TODOs all block.",
867
+ };
868
+
869
+ if (output_format === "json") {
870
+ return makeResult(JSON.stringify({
871
+ success: true,
872
+ previous_profile: currentProfile,
873
+ new_profile: strictness,
874
+ hooks_count: hookCount,
875
+ settings_path: settingsPath,
876
+ }, null, 2));
877
+ }
878
+
879
+ return makeResult(
880
+ `Strictness updated: ${currentProfile || "unknown"} → ${strictness}\n\n` +
881
+ `${profileDescriptions[strictness]}\n\n` +
882
+ `Hooks installed: ${hookCount}\n` +
883
+ `Settings updated: ${settingsPath}\n\n` +
884
+ (strictness === "paranoid"
885
+ ? "⚠️ Paranoid mode is strict. Files over 400 lines, 'any' types, and TODO comments will block commits.\n"
886
+ : "")
887
+ );
888
+ } catch (err: unknown) {
889
+ const message = err instanceof Error ? err.message : String(err);
890
+ return makeResult(`Failed to set strictness: ${message}`, true);
891
+ }
892
+ }
893
+ );
894
+ }
895
+
896
+ function getRoleDescription(role: string): string {
897
+ switch (role) {
898
+ case "explore":
899
+ return "You are an exploration agent. Search and read code without making any changes. Report findings with exact file paths and line numbers.";
900
+ case "plan":
901
+ return "You are a software architect. Analyze requirements, examine existing code, and create detailed implementation plans. Never write or modify code directly.";
902
+ case "general":
903
+ return "You are a general-purpose coding agent. Handle tasks as directed, from code changes to testing to documentation.";
904
+ case "custom":
905
+ return "You are a specialized agent. Follow the custom instructions provided below.";
906
+ default:
907
+ return "You are an agent. Follow the instructions provided.";
908
+ }
909
+ }