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,575 @@
1
+ /**
2
+ * Integration tests for 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 { describe, it, before, after } from "node:test";
7
+ import * as assert from "node:assert";
8
+ import {
9
+ createTestDir,
10
+ createMockProject,
11
+ readTestFile,
12
+ testFileExists,
13
+ readTestJson,
14
+ assertToolSuccess,
15
+ assertToolError,
16
+ getToolResultText,
17
+ parseToolResultJson,
18
+ } from "./test-utils.js";
19
+ import { initializeTools, invokeTool } from "./tool-harness.js";
20
+
21
+ // Initialize tools before all tests
22
+ before(() => {
23
+ initializeTools();
24
+ });
25
+
26
+ describe("rebar_init_project", () => {
27
+ it("should initialize a new project with all required files", async () => {
28
+ const { path: testDir, cleanup } = await createTestDir();
29
+
30
+ try {
31
+ await createMockProject(testDir);
32
+
33
+ const result = await invokeTool("rebar_init_project", {
34
+ project_path: testDir,
35
+ tech_stack: ["express"],
36
+ team_size: 5,
37
+ });
38
+
39
+ assertToolSuccess(result);
40
+
41
+ // Check CLAUDE.md was created
42
+ assert.ok(
43
+ await testFileExists(testDir, "CLAUDE.md"),
44
+ "CLAUDE.md should be created"
45
+ );
46
+
47
+ // Check .claudeignore was created
48
+ assert.ok(
49
+ await testFileExists(testDir, ".claudeignore"),
50
+ ".claudeignore should be created"
51
+ );
52
+
53
+ // Check .claude/settings.json was created
54
+ assert.ok(
55
+ await testFileExists(testDir, ".claude/settings.json"),
56
+ ".claude/settings.json should be created"
57
+ );
58
+
59
+ // Check .mcp.json was created
60
+ assert.ok(
61
+ await testFileExists(testDir, ".mcp.json"),
62
+ ".mcp.json should be created"
63
+ );
64
+
65
+ // Verify settings.json structure
66
+ const settings = await readTestJson<Record<string, unknown>>(
67
+ testDir,
68
+ ".claude/settings.json"
69
+ );
70
+ assert.ok(settings, "settings.json should be valid JSON");
71
+ assert.ok(settings.permissions, "settings should have permissions");
72
+ } finally {
73
+ await cleanup();
74
+ }
75
+ });
76
+
77
+ it("should return JSON output when output_format is json", async () => {
78
+ const { path: testDir, cleanup } = await createTestDir();
79
+
80
+ try {
81
+ await createMockProject(testDir);
82
+
83
+ const result = await invokeTool("rebar_init_project", {
84
+ project_path: testDir,
85
+ tech_stack: ["express"],
86
+ team_size: 5,
87
+ output_format: "json",
88
+ });
89
+
90
+ assertToolSuccess(result);
91
+
92
+ const parsed = parseToolResultJson<Record<string, unknown>>(result);
93
+ assert.strictEqual(parsed.success, true);
94
+ assert.strictEqual(parsed.operation, "init_project");
95
+ assert.ok(parsed.data, "JSON should have data field");
96
+ } finally {
97
+ await cleanup();
98
+ }
99
+ });
100
+
101
+ it("should support dry_run mode", async () => {
102
+ const { path: testDir, cleanup } = await createTestDir();
103
+
104
+ try {
105
+ await createMockProject(testDir);
106
+
107
+ const result = await invokeTool("rebar_init_project", {
108
+ project_path: testDir,
109
+ tech_stack: ["express"],
110
+ team_size: 5,
111
+ dry_run: true,
112
+ });
113
+
114
+ assertToolSuccess(result);
115
+
116
+ const text = getToolResultText(result);
117
+ assert.ok(text.includes("DRY RUN"), "Should indicate dry run");
118
+
119
+ // CLAUDE.md should NOT be created in dry-run mode
120
+ assert.ok(
121
+ !(await testFileExists(testDir, "CLAUDE.md")),
122
+ "CLAUDE.md should NOT be created in dry-run mode"
123
+ );
124
+ } finally {
125
+ await cleanup();
126
+ }
127
+ });
128
+ });
129
+
130
+ describe("rebar_generate_claudemd", () => {
131
+ it("should generate CLAUDE.md from project analysis", async () => {
132
+ const { path: testDir, cleanup } = await createTestDir();
133
+
134
+ try {
135
+ await createMockProject(testDir);
136
+
137
+ const result = await invokeTool("rebar_generate_claudemd", {
138
+ project_path: testDir,
139
+ });
140
+
141
+ assertToolSuccess(result);
142
+
143
+ const claudemd = await readTestFile(testDir, "CLAUDE.md");
144
+ assert.ok(claudemd, "CLAUDE.md should be created");
145
+ assert.ok(claudemd.includes("#"), "Should have markdown headings");
146
+ } finally {
147
+ await cleanup();
148
+ }
149
+ });
150
+
151
+ it("should detect tech stack from package.json", async () => {
152
+ const { path: testDir, cleanup } = await createTestDir();
153
+
154
+ try {
155
+ await createMockProject(testDir, {
156
+ packageJson: {
157
+ name: "nextjs-app",
158
+ dependencies: {
159
+ next: "^14.0.0",
160
+ react: "^18.0.0",
161
+ },
162
+ },
163
+ });
164
+
165
+ const result = await invokeTool("rebar_generate_claudemd", {
166
+ project_path: testDir,
167
+ output_format: "json",
168
+ });
169
+
170
+ assertToolSuccess(result);
171
+
172
+ const parsed = parseToolResultJson<Record<string, unknown>>(result);
173
+ const data = parsed.data as Record<string, unknown>;
174
+ const analysis = data.analysis as Record<string, unknown>;
175
+ assert.ok(
176
+ (analysis.stacks as string[]).includes("nextjs") ||
177
+ (analysis.stacks as string[]).includes("react"),
178
+ "Should detect Next.js or React"
179
+ );
180
+ } finally {
181
+ await cleanup();
182
+ }
183
+ });
184
+
185
+ it("should support dry_run mode", async () => {
186
+ const { path: testDir, cleanup } = await createTestDir();
187
+
188
+ try {
189
+ await createMockProject(testDir);
190
+
191
+ const result = await invokeTool("rebar_generate_claudemd", {
192
+ project_path: testDir,
193
+ dry_run: true,
194
+ output_format: "json",
195
+ });
196
+
197
+ assertToolSuccess(result);
198
+
199
+ const parsed = parseToolResultJson<Record<string, unknown>>(result);
200
+ assert.strictEqual(parsed.dry_run, true);
201
+
202
+ // File should not exist
203
+ assert.ok(
204
+ !(await testFileExists(testDir, "CLAUDE.md")),
205
+ "CLAUDE.md should NOT be created in dry-run"
206
+ );
207
+ } finally {
208
+ await cleanup();
209
+ }
210
+ });
211
+ });
212
+
213
+ describe("rebar_create_skill", () => {
214
+ it("should create a new skill directory with SKILL.md", async () => {
215
+ const { path: testDir, cleanup } = await createTestDir();
216
+
217
+ try {
218
+ // Change to test directory for process.cwd() usage
219
+ const originalCwd = process.cwd();
220
+ process.chdir(testDir);
221
+
222
+ try {
223
+ await createMockProject(testDir);
224
+
225
+ const result = await invokeTool("rebar_create_skill", {
226
+ name: "test-skill",
227
+ description: "A test skill for unit testing",
228
+ });
229
+
230
+ assertToolSuccess(result);
231
+
232
+ assert.ok(
233
+ await testFileExists(testDir, ".claude/skills/test-skill/SKILL.md"),
234
+ "SKILL.md should be created"
235
+ );
236
+
237
+ const skillContent = await readTestFile(
238
+ testDir,
239
+ ".claude/skills/test-skill/SKILL.md"
240
+ );
241
+ assert.ok(skillContent, "Skill file should have content");
242
+ assert.ok(skillContent.includes("name: test-skill"), "Should have name in frontmatter");
243
+ assert.ok(
244
+ skillContent.includes("A test skill for unit testing"),
245
+ "Should have description"
246
+ );
247
+ } finally {
248
+ process.chdir(originalCwd);
249
+ }
250
+ } finally {
251
+ await cleanup();
252
+ }
253
+ });
254
+
255
+ it("should fail if skill already exists", async () => {
256
+ const { path: testDir, cleanup } = await createTestDir();
257
+
258
+ try {
259
+ const originalCwd = process.cwd();
260
+ process.chdir(testDir);
261
+
262
+ try {
263
+ await createMockProject(testDir);
264
+
265
+ // Create first skill
266
+ await invokeTool("rebar_create_skill", {
267
+ name: "existing-skill",
268
+ description: "First skill",
269
+ });
270
+
271
+ // Try to create again
272
+ const result = await invokeTool("rebar_create_skill", {
273
+ name: "existing-skill",
274
+ description: "Duplicate skill",
275
+ });
276
+
277
+ assertToolError(result);
278
+ const text = getToolResultText(result);
279
+ assert.ok(text.includes("already exists"), "Should mention already exists");
280
+ } finally {
281
+ process.chdir(originalCwd);
282
+ }
283
+ } finally {
284
+ await cleanup();
285
+ }
286
+ });
287
+
288
+ it("should support dry_run mode", async () => {
289
+ const { path: testDir, cleanup } = await createTestDir();
290
+
291
+ try {
292
+ const originalCwd = process.cwd();
293
+ process.chdir(testDir);
294
+
295
+ try {
296
+ await createMockProject(testDir);
297
+
298
+ const result = await invokeTool("rebar_create_skill", {
299
+ name: "dry-run-skill",
300
+ description: "Should not be created",
301
+ dry_run: true,
302
+ });
303
+
304
+ assertToolSuccess(result);
305
+
306
+ assert.ok(
307
+ !(await testFileExists(testDir, ".claude/skills/dry-run-skill/SKILL.md")),
308
+ "Skill should NOT be created in dry-run"
309
+ );
310
+ } finally {
311
+ process.chdir(originalCwd);
312
+ }
313
+ } finally {
314
+ await cleanup();
315
+ }
316
+ });
317
+ });
318
+
319
+ describe("rebar_create_agent", () => {
320
+ it("should create a new agent file", async () => {
321
+ const { path: testDir, cleanup } = await createTestDir();
322
+
323
+ try {
324
+ const originalCwd = process.cwd();
325
+ process.chdir(testDir);
326
+
327
+ try {
328
+ await createMockProject(testDir);
329
+
330
+ const result = await invokeTool("rebar_create_agent", {
331
+ name: "test-agent",
332
+ description: "A test agent for unit testing",
333
+ role: "explore",
334
+ });
335
+
336
+ assertToolSuccess(result);
337
+
338
+ assert.ok(
339
+ await testFileExists(testDir, ".claude/agents/test-agent.md"),
340
+ "Agent file should be created"
341
+ );
342
+
343
+ const agentContent = await readTestFile(
344
+ testDir,
345
+ ".claude/agents/test-agent.md"
346
+ );
347
+ assert.ok(agentContent, "Agent file should have content");
348
+ assert.ok(agentContent.includes("test-agent"), "Should include agent name");
349
+ } finally {
350
+ process.chdir(originalCwd);
351
+ }
352
+ } finally {
353
+ await cleanup();
354
+ }
355
+ });
356
+
357
+ it("should fail if agent already exists", async () => {
358
+ const { path: testDir, cleanup } = await createTestDir();
359
+
360
+ try {
361
+ const originalCwd = process.cwd();
362
+ process.chdir(testDir);
363
+
364
+ try {
365
+ await createMockProject(testDir);
366
+
367
+ // Create first agent
368
+ await invokeTool("rebar_create_agent", {
369
+ name: "existing-agent",
370
+ description: "First agent",
371
+ role: "explore",
372
+ });
373
+
374
+ // Try to create again
375
+ const result = await invokeTool("rebar_create_agent", {
376
+ name: "existing-agent",
377
+ description: "Duplicate agent",
378
+ role: "plan",
379
+ });
380
+
381
+ assertToolError(result);
382
+ } finally {
383
+ process.chdir(originalCwd);
384
+ }
385
+ } finally {
386
+ await cleanup();
387
+ }
388
+ });
389
+ });
390
+
391
+ describe("rebar_create_hook", () => {
392
+ it("should add a hook to settings.json", async () => {
393
+ const { path: testDir, cleanup } = await createTestDir();
394
+
395
+ try {
396
+ const originalCwd = process.cwd();
397
+ process.chdir(testDir);
398
+
399
+ try {
400
+ await createMockProject(testDir);
401
+
402
+ const result = await invokeTool("rebar_create_hook", {
403
+ event: "PreToolCall",
404
+ command: "echo test",
405
+ description: "Test hook",
406
+ exit_behavior: "notify",
407
+ });
408
+
409
+ assertToolSuccess(result);
410
+
411
+ const settings = await readTestJson<Record<string, unknown>>(
412
+ testDir,
413
+ ".claude/settings.json"
414
+ );
415
+ assert.ok(settings, "settings.json should exist");
416
+ assert.ok(settings.hooks, "Should have hooks");
417
+
418
+ const hooks = settings.hooks as Record<string, unknown[]>;
419
+ assert.ok(hooks.PreToolCall, "Should have PreToolCall hooks");
420
+ assert.ok(
421
+ hooks.PreToolCall.some(
422
+ (h: unknown) => (h as Record<string, unknown>).command === "echo test"
423
+ ),
424
+ "Should contain the new hook"
425
+ );
426
+ } finally {
427
+ process.chdir(originalCwd);
428
+ }
429
+ } finally {
430
+ await cleanup();
431
+ }
432
+ });
433
+
434
+ it("should update existing hook with same command and matcher", async () => {
435
+ const { path: testDir, cleanup } = await createTestDir();
436
+
437
+ try {
438
+ const originalCwd = process.cwd();
439
+ process.chdir(testDir);
440
+
441
+ try {
442
+ await createMockProject(testDir);
443
+
444
+ // Create first hook
445
+ await invokeTool("rebar_create_hook", {
446
+ event: "PreToolCall",
447
+ command: "echo test",
448
+ matcher: "Bash",
449
+ description: "First description",
450
+ exit_behavior: "notify",
451
+ });
452
+
453
+ // Update with same command and matcher but different description and exit_behavior
454
+ const result = await invokeTool("rebar_create_hook", {
455
+ event: "PreToolCall",
456
+ command: "echo test",
457
+ matcher: "Bash",
458
+ description: "Updated description",
459
+ exit_behavior: "block",
460
+ });
461
+
462
+ assertToolSuccess(result);
463
+
464
+ const text = getToolResultText(result);
465
+ assert.ok(text.includes("updated"), "Should indicate update");
466
+
467
+ const settings = await readTestJson<Record<string, unknown>>(
468
+ testDir,
469
+ ".claude/settings.json"
470
+ );
471
+ const hooks = settings?.hooks as Record<string, unknown[]>;
472
+ const preToolCallHooks = hooks?.PreToolCall ?? [];
473
+
474
+ // Should only have one hook with that command
475
+ const matchingHooks = preToolCallHooks.filter(
476
+ (h: unknown) =>
477
+ (h as Record<string, unknown>).command === "echo test"
478
+ );
479
+ assert.strictEqual(matchingHooks.length, 1, "Should have exactly one hook");
480
+ assert.strictEqual(
481
+ (matchingHooks[0] as Record<string, unknown>).description,
482
+ "Updated description",
483
+ "Hook description should be updated"
484
+ );
485
+ assert.strictEqual(
486
+ (matchingHooks[0] as Record<string, unknown>).exit_behavior,
487
+ "block",
488
+ "Hook exit_behavior should be updated"
489
+ );
490
+ } finally {
491
+ process.chdir(originalCwd);
492
+ }
493
+ } finally {
494
+ await cleanup();
495
+ }
496
+ });
497
+ });
498
+
499
+ describe("rebar_create_command", () => {
500
+ it("should create a command skill", async () => {
501
+ const { path: testDir, cleanup } = await createTestDir();
502
+
503
+ try {
504
+ const originalCwd = process.cwd();
505
+ process.chdir(testDir);
506
+
507
+ try {
508
+ await createMockProject(testDir);
509
+
510
+ const result = await invokeTool("rebar_create_command", {
511
+ name: "test-cmd",
512
+ description: "A test command",
513
+ prompt_body: "Do something useful",
514
+ });
515
+
516
+ assertToolSuccess(result);
517
+
518
+ assert.ok(
519
+ await testFileExists(testDir, ".claude/skills/test-cmd/SKILL.md"),
520
+ "Command skill should be created"
521
+ );
522
+
523
+ const skillContent = await readTestFile(
524
+ testDir,
525
+ ".claude/skills/test-cmd/SKILL.md"
526
+ );
527
+ assert.ok(skillContent, "Skill file should have content");
528
+ assert.ok(skillContent.includes("invocation: user"), "Should be user-invoked");
529
+ assert.ok(skillContent.includes("Do something useful"), "Should include prompt");
530
+ } finally {
531
+ process.chdir(originalCwd);
532
+ }
533
+ } finally {
534
+ await cleanup();
535
+ }
536
+ });
537
+
538
+ it("should include arguments in command", async () => {
539
+ const { path: testDir, cleanup } = await createTestDir();
540
+
541
+ try {
542
+ const originalCwd = process.cwd();
543
+ process.chdir(testDir);
544
+
545
+ try {
546
+ await createMockProject(testDir);
547
+
548
+ const result = await invokeTool("rebar_create_command", {
549
+ name: "cmd-with-args",
550
+ description: "Command with arguments",
551
+ prompt_body: "Process the input",
552
+ arguments: [
553
+ { name: "target", description: "Target to process", required: true },
554
+ { name: "mode", description: "Processing mode", required: false },
555
+ ],
556
+ });
557
+
558
+ assertToolSuccess(result);
559
+
560
+ const skillContent = await readTestFile(
561
+ testDir,
562
+ ".claude/skills/cmd-with-args/SKILL.md"
563
+ );
564
+ assert.ok(skillContent, "Skill file should have content");
565
+ assert.ok(skillContent.includes("**target**"), "Should include target arg");
566
+ assert.ok(skillContent.includes("(required)"), "Should mark required");
567
+ assert.ok(skillContent.includes("(optional)"), "Should mark optional");
568
+ } finally {
569
+ process.chdir(originalCwd);
570
+ }
571
+ } finally {
572
+ await cleanup();
573
+ }
574
+ });
575
+ });