@nebulit/embuilder 0.1.39

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 (212) hide show
  1. package/README.md +254 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +138 -0
  4. package/package.json +49 -0
  5. package/templates/.claude/hooks/QUICKSTART.md +256 -0
  6. package/templates/.claude/hooks/README.md +533 -0
  7. package/templates/.claude/hooks/analyze-commit.sh +22 -0
  8. package/templates/.claude/hooks/analyze-commit.ts +518 -0
  9. package/templates/.claude/hooks/analyzers/README.md +198 -0
  10. package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
  11. package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
  12. package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
  13. package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
  14. package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
  15. package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
  16. package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
  17. package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
  18. package/templates/.claude/hooks/check-review-result.sh +47 -0
  19. package/templates/.claude/hooks/prepare-review.sh +34 -0
  20. package/templates/.claude/hooks/review-agent-prompt.md +42 -0
  21. package/templates/.claude/hooks/run-review-agent.sh +124 -0
  22. package/templates/.claude/settings.local.json +37 -0
  23. package/templates/.claude/skills/help/README.md +84 -0
  24. package/templates/.claude/skills/help/SKILL.md +393 -0
  25. package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
  26. package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
  27. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
  28. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
  29. package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
  30. package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
  31. package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
  32. package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
  33. package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
  34. package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
  35. package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
  36. package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
  37. package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
  38. package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
  39. package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
  40. package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
  41. package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
  42. package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
  43. package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
  44. package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
  45. package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
  46. package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
  47. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
  48. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
  49. package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
  50. package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
  51. package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
  52. package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
  53. package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
  54. package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
  55. package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
  56. package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
  57. package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
  58. package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
  59. package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
  60. package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
  61. package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
  62. package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
  63. package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
  64. package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
  65. package/templates/AGENTS.md +110 -0
  66. package/templates/Claude.md +58 -0
  67. package/templates/README.md +178 -0
  68. package/templates/backend/.env +9 -0
  69. package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
  70. package/templates/backend/SWAGGER.md +213 -0
  71. package/templates/backend/eslint.config.mjs +31 -0
  72. package/templates/backend/flyway.conf +17 -0
  73. package/templates/backend/package.json +44 -0
  74. package/templates/backend/prd.json.example +64 -0
  75. package/templates/backend/public/assets/images/banner.png +0 -0
  76. package/templates/backend/public/assets/logo.png +0 -0
  77. package/templates/backend/public/file.svg +4 -0
  78. package/templates/backend/public/globe.svg +12 -0
  79. package/templates/backend/public/next.svg +6 -0
  80. package/templates/backend/public/vercel.svg +3 -0
  81. package/templates/backend/public/window.svg +5 -0
  82. package/templates/backend/server.ts +129 -0
  83. package/templates/backend/setup-env.sh +50 -0
  84. package/templates/backend/src/common/assertions.ts +6 -0
  85. package/templates/backend/src/common/db.ts +1 -0
  86. package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
  87. package/templates/backend/src/common/parseEndpoint.ts +51 -0
  88. package/templates/backend/src/common/replay.ts +9 -0
  89. package/templates/backend/src/common/routes.ts +19 -0
  90. package/templates/backend/src/common/testHelpers.ts +53 -0
  91. package/templates/backend/src/core/readmodel.ts +28 -0
  92. package/templates/backend/src/core/types.ts +26 -0
  93. package/templates/backend/src/process/process.ts +53 -0
  94. package/templates/backend/src/supabase/LoginHandler.ts +36 -0
  95. package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
  96. package/templates/backend/src/supabase/README.md +171 -0
  97. package/templates/backend/src/supabase/api.ts +63 -0
  98. package/templates/backend/src/supabase/authMiddleware.ts +53 -0
  99. package/templates/backend/src/supabase/component.ts +12 -0
  100. package/templates/backend/src/supabase/requireUser.ts +72 -0
  101. package/templates/backend/src/supabase/serverProps.ts +25 -0
  102. package/templates/backend/src/supabase/staticProps.ts +10 -0
  103. package/templates/backend/src/swagger.ts +34 -0
  104. package/templates/backend/src/util/assertions.ts +6 -0
  105. package/templates/backend/supabase/config.toml +295 -0
  106. package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
  107. package/templates/backend/supabase/seed.sql +1 -0
  108. package/templates/backend/tsconfig.json +31 -0
  109. package/templates/frontend/.env.development +3 -0
  110. package/templates/frontend/AGENTS.md +7 -0
  111. package/templates/frontend/README.md +73 -0
  112. package/templates/frontend/components.json +20 -0
  113. package/templates/frontend/eslint.config.js +26 -0
  114. package/templates/frontend/index.html +18 -0
  115. package/templates/frontend/package-lock.json +8347 -0
  116. package/templates/frontend/package.json +94 -0
  117. package/templates/frontend/postcss.config.js +6 -0
  118. package/templates/frontend/public/favicon.ico +0 -0
  119. package/templates/frontend/public/logo.png +0 -0
  120. package/templates/frontend/public/placeholder.svg +1 -0
  121. package/templates/frontend/public/robots.txt +14 -0
  122. package/templates/frontend/src/App.css +42 -0
  123. package/templates/frontend/src/App.tsx +47 -0
  124. package/templates/frontend/src/components/NavLink.tsx +28 -0
  125. package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
  126. package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
  127. package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
  128. package/templates/frontend/src/components/layout/Header.tsx +45 -0
  129. package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
  130. package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
  131. package/templates/frontend/src/components/ui/accordion.tsx +52 -0
  132. package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
  133. package/templates/frontend/src/components/ui/alert.tsx +43 -0
  134. package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
  135. package/templates/frontend/src/components/ui/avatar.tsx +38 -0
  136. package/templates/frontend/src/components/ui/badge.tsx +29 -0
  137. package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
  138. package/templates/frontend/src/components/ui/button.tsx +47 -0
  139. package/templates/frontend/src/components/ui/calendar.tsx +54 -0
  140. package/templates/frontend/src/components/ui/card.tsx +43 -0
  141. package/templates/frontend/src/components/ui/carousel.tsx +224 -0
  142. package/templates/frontend/src/components/ui/chart.tsx +303 -0
  143. package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
  144. package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
  145. package/templates/frontend/src/components/ui/command.tsx +132 -0
  146. package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
  147. package/templates/frontend/src/components/ui/dialog.tsx +95 -0
  148. package/templates/frontend/src/components/ui/drawer.tsx +87 -0
  149. package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
  150. package/templates/frontend/src/components/ui/form.tsx +129 -0
  151. package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
  152. package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
  153. package/templates/frontend/src/components/ui/input.tsx +22 -0
  154. package/templates/frontend/src/components/ui/label.tsx +17 -0
  155. package/templates/frontend/src/components/ui/menubar.tsx +207 -0
  156. package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
  157. package/templates/frontend/src/components/ui/pagination.tsx +81 -0
  158. package/templates/frontend/src/components/ui/popover.tsx +29 -0
  159. package/templates/frontend/src/components/ui/progress.tsx +23 -0
  160. package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
  161. package/templates/frontend/src/components/ui/resizable.tsx +37 -0
  162. package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
  163. package/templates/frontend/src/components/ui/select.tsx +143 -0
  164. package/templates/frontend/src/components/ui/separator.tsx +20 -0
  165. package/templates/frontend/src/components/ui/sheet.tsx +107 -0
  166. package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
  167. package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
  168. package/templates/frontend/src/components/ui/slider.tsx +23 -0
  169. package/templates/frontend/src/components/ui/sonner.tsx +27 -0
  170. package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
  171. package/templates/frontend/src/components/ui/switch.tsx +27 -0
  172. package/templates/frontend/src/components/ui/table.tsx +72 -0
  173. package/templates/frontend/src/components/ui/tabs.tsx +53 -0
  174. package/templates/frontend/src/components/ui/textarea.tsx +21 -0
  175. package/templates/frontend/src/components/ui/toast.tsx +111 -0
  176. package/templates/frontend/src/components/ui/toaster.tsx +24 -0
  177. package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
  178. package/templates/frontend/src/components/ui/toggle.tsx +37 -0
  179. package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
  180. package/templates/frontend/src/components/ui/use-toast.ts +3 -0
  181. package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
  182. package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
  183. package/templates/frontend/src/hooks/api/index.ts +2 -0
  184. package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
  185. package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
  186. package/templates/frontend/src/hooks/use-toast.ts +186 -0
  187. package/templates/frontend/src/hooks/useApiContext.ts +11 -0
  188. package/templates/frontend/src/index.css +118 -0
  189. package/templates/frontend/src/integrations/supabase/client.ts +9 -0
  190. package/templates/frontend/src/lib/api-client.ts +136 -0
  191. package/templates/frontend/src/lib/api.ts +1028 -0
  192. package/templates/frontend/src/lib/utils.ts +6 -0
  193. package/templates/frontend/src/main.tsx +5 -0
  194. package/templates/frontend/src/pages/Auth.tsx +408 -0
  195. package/templates/frontend/src/pages/Dashboard.tsx +168 -0
  196. package/templates/frontend/src/pages/Menus.tsx +224 -0
  197. package/templates/frontend/src/pages/NotFound.tsx +24 -0
  198. package/templates/frontend/src/pages/Register.tsx +285 -0
  199. package/templates/frontend/src/test/example.test.ts +0 -0
  200. package/templates/frontend/src/test/setup.ts +15 -0
  201. package/templates/frontend/src/types/index.ts +8 -0
  202. package/templates/frontend/src/vite-env.d.ts +1 -0
  203. package/templates/frontend/tailwind.config.ts +101 -0
  204. package/templates/frontend/tsconfig.app.json +31 -0
  205. package/templates/frontend/tsconfig.json +16 -0
  206. package/templates/frontend/tsconfig.node.json +22 -0
  207. package/templates/frontend/vite.config.ts +21 -0
  208. package/templates/frontend/vitest.config.ts +16 -0
  209. package/templates/init.sh +1 -0
  210. package/templates/prompt.md +139 -0
  211. package/templates/ralph.sh +120 -0
  212. package/templates/server.mjs +505 -0
@@ -0,0 +1,518 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Git Commit Analyzer - Main Entry Point
5
+ *
6
+ * This script:
7
+ * 1. Collects git diff information
8
+ * 2. Loads all analyzer markdown files
9
+ * 3. Sends everything to a review agent
10
+ * 4. The agent goes through each analyzer and returns approved/rejected
11
+ */
12
+
13
+ import { execSync } from 'child_process';
14
+ import { readdirSync, readFileSync } from 'fs';
15
+ import { join } from 'path';
16
+
17
+ interface SliceInfo {
18
+ id: string;
19
+ slice: string;
20
+ index: number;
21
+ context: string;
22
+ folder: string;
23
+ status: string;
24
+ }
25
+
26
+ interface HookData {
27
+ event: string;
28
+ timestamp: string;
29
+ repository: string;
30
+ branch: string;
31
+ staged_diff: string;
32
+ unstaged_diff: string;
33
+ changed_files: string[];
34
+ current_slice?: SliceInfo;
35
+ stats: {
36
+ staged_files: number;
37
+ has_staged_changes: boolean;
38
+ has_unstaged_changes: boolean;
39
+ };
40
+ }
41
+
42
+ interface AnalyzerDefinition {
43
+ name: string;
44
+ filepath: string;
45
+ content: string;
46
+ priority: number;
47
+ blocking: boolean | string;
48
+ }
49
+
50
+ interface ReviewResult {
51
+ approved: boolean;
52
+ reason: string;
53
+ analyzer_results: Array<{
54
+ analyzer: string;
55
+ approved: boolean;
56
+ details: string[];
57
+ }>;
58
+ }
59
+
60
+ function getCurrentSlice(): SliceInfo | undefined {
61
+ try {
62
+ const slicesIndexPath = join('.slices', 'index.json');
63
+ const indexContent = readFileSync(slicesIndexPath, 'utf-8');
64
+ const slices: SliceInfo[] = JSON.parse(indexContent);
65
+
66
+ // Find the slice with status "in_progress" or "In Progress" (case insensitive)
67
+ const currentSlice = slices.find(s =>
68
+ s.status.toLowerCase() === 'in_progress' ||
69
+ s.status.toLowerCase() === 'in progress'
70
+ );
71
+
72
+ return currentSlice;
73
+ } catch (error) {
74
+ // No .slices/index.json or parsing error - that's OK
75
+ return undefined;
76
+ }
77
+ }
78
+
79
+ function getGitInfo(): HookData {
80
+ let stagedDiff = '';
81
+ let unstagedDiff = '';
82
+ let changedFiles: string[] = [];
83
+ let repository = 'unknown';
84
+ let branch = 'unknown';
85
+
86
+ try {
87
+ stagedDiff = execSync('git diff --cached', { encoding: 'utf-8' });
88
+ } catch {
89
+ stagedDiff = '';
90
+ }
91
+
92
+ try {
93
+ unstagedDiff = execSync('git diff', { encoding: 'utf-8' });
94
+ } catch {
95
+ unstagedDiff = '';
96
+ }
97
+
98
+ try {
99
+ const filesOutput = execSync('git diff --name-only --cached', { encoding: 'utf-8' });
100
+ changedFiles = filesOutput.split('\n').filter(f => f.trim());
101
+ } catch {
102
+ changedFiles = [];
103
+ }
104
+
105
+ try {
106
+ repository = execSync('git rev-parse --show-toplevel', { encoding: 'utf-8' }).trim();
107
+ } catch {
108
+ repository = 'unknown';
109
+ }
110
+
111
+ try {
112
+ branch = execSync('git branch --show-current', { encoding: 'utf-8' }).trim();
113
+ } catch {
114
+ branch = 'unknown';
115
+ }
116
+
117
+ // Get current slice information
118
+ const currentSlice = getCurrentSlice();
119
+
120
+ return {
121
+ event: 'pre-commit',
122
+ timestamp: new Date().toISOString(),
123
+ repository,
124
+ branch,
125
+ staged_diff: stagedDiff,
126
+ unstaged_diff: unstagedDiff,
127
+ changed_files: changedFiles,
128
+ current_slice: currentSlice,
129
+ stats: {
130
+ staged_files: changedFiles.length,
131
+ has_staged_changes: stagedDiff.length > 0,
132
+ has_unstaged_changes: unstagedDiff.length > 0
133
+ }
134
+ };
135
+ }
136
+
137
+ function loadAnalyzers(analyzersDir: string): AnalyzerDefinition[] {
138
+ const analyzers: AnalyzerDefinition[] = [];
139
+
140
+ try {
141
+ const files = readdirSync(analyzersDir)
142
+ .filter(f => f.endsWith('.md'))
143
+ .filter(f => !f.includes('.disabled'));
144
+
145
+ for (const file of files) {
146
+ const filepath = join(analyzersDir, file);
147
+ const content = readFileSync(filepath, 'utf-8');
148
+
149
+ // Extract configuration from YAML frontmatter
150
+ const configMatch = content.match(/```yaml\n([\s\S]*?)\n```/);
151
+ let priority = 999;
152
+ let blocking: boolean | string = false;
153
+ let name = file.replace('.md', '');
154
+
155
+ if (configMatch) {
156
+ const yamlContent = configMatch[1];
157
+ const priorityMatch = yamlContent.match(/priority:\s*(\d+)/);
158
+ const blockingMatch = yamlContent.match(/blocking:\s*(\S+)/);
159
+ const nameMatch = yamlContent.match(/name:\s*(.+)/);
160
+
161
+ if (priorityMatch) priority = parseInt(priorityMatch[1]);
162
+ if (blockingMatch) {
163
+ const blockingValue = blockingMatch[1].trim();
164
+ blocking = blockingValue === 'true' ? true :
165
+ blockingValue === 'false' ? false :
166
+ blockingValue;
167
+ }
168
+ if (nameMatch) name = nameMatch[1].trim();
169
+ }
170
+
171
+ analyzers.push({
172
+ name,
173
+ filepath,
174
+ content,
175
+ priority,
176
+ blocking: blocking
177
+ });
178
+ }
179
+
180
+ // Sort by priority (lower number = higher priority)
181
+ analyzers.sort((a, b) => a.priority - b.priority);
182
+
183
+ } catch (error: any) {
184
+ console.error(`Error loading analyzers: ${error.message}`);
185
+ }
186
+
187
+ return analyzers;
188
+ }
189
+
190
+ function buildReviewPrompt(hookData: HookData, analyzers: AnalyzerDefinition[]): string {
191
+ const sliceContext = hookData.current_slice ? `
192
+ ## Current Slice (In Progress)
193
+
194
+ **Slice:** ${hookData.current_slice.slice}
195
+ **Context:** ${hookData.current_slice.context}
196
+ **Folder:** ${hookData.current_slice.folder}
197
+ **Status:** ${hookData.current_slice.status}
198
+
199
+ This commit is part of implementing the "${hookData.current_slice.slice}" slice in the "${hookData.current_slice.context}" context.
200
+ Changes should be related to the \`${hookData.current_slice.folder}\` folder.
201
+
202
+ ---
203
+ ` : '';
204
+
205
+ return `You are a code review agent analyzing a git commit before it's committed.
206
+
207
+ # Commit Information
208
+
209
+ **Branch:** ${hookData.branch}
210
+ **Files Changed:** ${hookData.changed_files.length}
211
+ **Timestamp:** ${hookData.timestamp}
212
+
213
+ ${sliceContext}
214
+ ## Changed Files
215
+ ${hookData.changed_files.map(f => `- ${f}`).join('\n')}
216
+
217
+ ## Staged Diff
218
+ \`\`\`diff
219
+ ${hookData.staged_diff || '(no staged changes)'}
220
+ \`\`\`
221
+
222
+ ---
223
+
224
+ # Your Task
225
+
226
+ Go through each analyzer below **in order** and evaluate the commit against its criteria.
227
+ Each analyzer has specific blocking/warning rules.
228
+
229
+ ${analyzers.map((analyzer, idx) => `
230
+ ## Analyzer ${idx + 1}: ${analyzer.name}
231
+
232
+ ${analyzer.content}
233
+
234
+ ---
235
+ `).join('\n')}
236
+
237
+ # Final Decision
238
+
239
+ After reviewing all analyzers, return your decision as JSON:
240
+
241
+ \`\`\`json
242
+ {
243
+ "approved": boolean,
244
+ "reason": "Summary of the decision",
245
+ "analyzer_results": [
246
+ {
247
+ "analyzer": "analyzer-name",
248
+ "approved": boolean,
249
+ "details": ["finding 1", "finding 2"]
250
+ }
251
+ ]
252
+ }
253
+ \`\`\`
254
+
255
+ **IMPORTANT:**
256
+ - If ANY analyzer with \`blocking: true\` rejects, set \`approved: false\`
257
+ - If analyzer has \`blocking: false\`, it can only warn (still \`approved: true\`)
258
+ - Work through analyzers in the order given (by priority)
259
+ - Be thorough but concise in your analysis
260
+ `;
261
+ }
262
+
263
+ async function runReview(prompt: string): Promise<ReviewResult> {
264
+ console.log('🤖 Launching dedicated review agent...');
265
+ console.log('');
266
+
267
+ try {
268
+ const { writeFileSync, mkdtempSync, rmSync, existsSync } = await import('fs');
269
+ const { tmpdir } = await import('os');
270
+ const { join } = await import('path');
271
+
272
+ // Create temp directory for review files
273
+ const tempDir = mkdtempSync(join(tmpdir(), 'commit-review-'));
274
+ const promptFile = join(tempDir, 'review-prompt.md');
275
+ const resultFile = join(tempDir, 'review-result.json');
276
+
277
+ // Write the comprehensive review prompt
278
+ writeFileSync(promptFile, prompt);
279
+
280
+ // Create the command that will be executed by the review agent
281
+ const reviewCommand = `
282
+ cat "${promptFile}"
283
+ echo ""
284
+ echo "---"
285
+ echo ""
286
+ echo "Please analyze this commit and write your JSON response to: ${resultFile}"
287
+ echo ""
288
+ echo "The JSON must have this exact structure:"
289
+ echo '{"approved": boolean, "reason": "string", "analyzer_results": [{"analyzer": "name", "approved": boolean, "details": []}]}'
290
+ `.trim();
291
+
292
+ console.log(' Review prompt saved to:', promptFile);
293
+ console.log(' Result will be written to:', resultFile);
294
+ console.log('');
295
+
296
+ // Run the review agent script
297
+ const reviewAgentScript = join('.claude', 'hooks', 'run-review-agent.sh');
298
+ execSync(`"${reviewAgentScript}" "${promptFile}" "${resultFile}"`, {
299
+ encoding: 'utf-8',
300
+ stdio: 'inherit',
301
+ shell: '/bin/bash'
302
+ });
303
+
304
+ console.log('');
305
+ console.log('⏳ Waiting for review agent to complete...');
306
+ console.log(' (Agent should write result to:', resultFile, ')');
307
+ console.log('');
308
+
309
+ // Wait for result file (with timeout)
310
+ const maxWaitTime = 120000; // 2 minutes
311
+ const startTime = Date.now();
312
+ const pollInterval = 1000; // 1 second
313
+
314
+ while (!existsSync(resultFile)) {
315
+ if (Date.now() - startTime > maxWaitTime) {
316
+ throw new Error('Timeout waiting for review agent to write result');
317
+ }
318
+ // Wait a bit before checking again
319
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
320
+ }
321
+
322
+ // Read the result
323
+ const resultContent = readFileSync(resultFile, 'utf-8');
324
+ let reviewResult: ReviewResult;
325
+
326
+ try {
327
+ // Try to parse as JSON
328
+ reviewResult = JSON.parse(resultContent);
329
+ } catch (error) {
330
+ // Try to extract JSON from the content
331
+ const jsonMatch = resultContent.match(/\{[\s\S]*"approved"[\s\S]*\}/);
332
+ if (jsonMatch) {
333
+ reviewResult = JSON.parse(jsonMatch[0]);
334
+ } else {
335
+ throw new Error('Could not parse review result as JSON');
336
+ }
337
+ }
338
+
339
+ // Cleanup
340
+ rmSync(tempDir, { recursive: true, force: true });
341
+
342
+ return reviewResult;
343
+
344
+ } catch (error: any) {
345
+ console.error(`⚠️ Review agent error: ${error.message}`);
346
+ console.log(' Allowing commit (fail-safe mode)\n');
347
+
348
+ // Fail-open: approve on error
349
+ return {
350
+ approved: true,
351
+ reason: 'Review agent failed - allowing commit in fail-safe mode',
352
+ analyzer_results: []
353
+ };
354
+ }
355
+ }
356
+
357
+ async function recordReviewFailure(reviewResult: ReviewResult, hookData: HookData): Promise<void> {
358
+ try {
359
+ const { appendFileSync, existsSync } = await import('fs');
360
+ const { join } = await import('path');
361
+
362
+ const progressFile = join(process.cwd(), 'progress.txt');
363
+
364
+ // Create progress.txt if it doesn't exist
365
+ if (!existsSync(progressFile)) {
366
+ const { writeFileSync } = await import('fs');
367
+ writeFileSync(progressFile, '# Event Model Development Progress Log\n');
368
+ writeFileSync(progressFile, `Started: ${new Date().toISOString()}\n`, { flag: 'a' });
369
+ writeFileSync(progressFile, '---\n\n', { flag: 'a' });
370
+ }
371
+
372
+ // Build detailed failure report
373
+ const failureReport = `
374
+ ## ❌ COMMIT REVIEW FAILED - ${new Date().toISOString()}
375
+
376
+ **Branch:** ${hookData.branch}
377
+ **Files Changed:** ${hookData.changed_files.length}
378
+ ${hookData.current_slice ? `**Current Slice:** ${hookData.current_slice.slice} (${hookData.current_slice.folder})` : ''}
379
+
380
+ **Reason:** ${reviewResult.reason}
381
+
382
+ ### Detailed Analyzer Results:
383
+
384
+ ${reviewResult.analyzer_results.map(result => `
385
+ #### ${result.approved ? '✅' : '❌'} ${result.analyzer}
386
+ ${result.details.map(d => `- ${d}`).join('\n')}
387
+ `).join('\n')}
388
+
389
+ ### Changed Files:
390
+ ${hookData.changed_files.map(f => `- ${f}`).join('\n')}
391
+
392
+ ### Action Taken:
393
+ - All changes discarded (git reset --hard)
394
+ - Development loop stopped
395
+ - Please review the failures above before restarting
396
+
397
+ ---
398
+
399
+ `;
400
+
401
+ appendFileSync(progressFile, failureReport);
402
+ console.log(` ✓ Failure recorded to progress.txt`);
403
+
404
+ } catch (error: any) {
405
+ console.error(` ⚠️ Could not record to progress.txt: ${error.message}`);
406
+ }
407
+ }
408
+
409
+ async function discardChanges(): Promise<void> {
410
+ try {
411
+ console.log(' 🗑️ Discarding all changes...');
412
+
413
+ // Reset staged changes
414
+ execSync('git reset HEAD .', { encoding: 'utf-8' });
415
+
416
+ // Discard working directory changes
417
+ execSync('git checkout -- .', { encoding: 'utf-8' });
418
+
419
+ // Clean untracked files
420
+ execSync('git clean -fd', { encoding: 'utf-8' });
421
+
422
+ console.log(' ✓ All changes discarded');
423
+
424
+ } catch (error: any) {
425
+ console.error(` ⚠️ Error discarding changes: ${error.message}`);
426
+ console.error(' Please manually run: git reset --hard HEAD');
427
+ }
428
+ }
429
+
430
+ async function main() {
431
+ try {
432
+ console.log('🔍 Analyzing commit with AI-powered reviewers...\n');
433
+
434
+ // Get git information
435
+ const hookData = getGitInfo();
436
+
437
+ if (hookData.changed_files.length === 0) {
438
+ console.log('ℹ️ No files to commit');
439
+ process.exit(0);
440
+ }
441
+
442
+ // Display current slice if available
443
+ if (hookData.current_slice) {
444
+ console.log('📦 Current Slice:');
445
+ console.log(` ${hookData.current_slice.slice}`);
446
+ console.log(` Context: ${hookData.current_slice.context}`);
447
+ console.log(` Folder: ${hookData.current_slice.folder}\n`);
448
+ }
449
+
450
+ // Load all analyzer definitions
451
+ const analyzersDir = join('.claude', 'hooks', 'analyzers');
452
+ const analyzers = loadAnalyzers(analyzersDir);
453
+
454
+ if (analyzers.length === 0) {
455
+ console.log(`ℹ️ No analyzer definitions found in ${analyzersDir}`);
456
+ console.log(' Create .md files with analyzer prompts');
457
+ process.exit(0);
458
+ }
459
+
460
+ console.log(`📋 Loaded ${analyzers.length} analyzers:`);
461
+ analyzers.forEach(a => {
462
+ const blockIcon = a.blocking === true ? '🔒' :
463
+ a.blocking === false ? '💡' : `⚠️ `;
464
+ console.log(` ${blockIcon} ${a.name} (priority: ${a.priority})`);
465
+ });
466
+ console.log('');
467
+
468
+ // Build the comprehensive review prompt
469
+ const reviewPrompt = buildReviewPrompt(hookData, analyzers);
470
+
471
+ // TODO: Send to AI review agent (Claude API)
472
+ const reviewResult = await runReview(reviewPrompt);
473
+
474
+ // Display results
475
+ console.log('📊 Review Results:\n');
476
+
477
+ for (const result of reviewResult.analyzer_results) {
478
+ const icon = result.approved ? '✅' : '❌';
479
+ console.log(`${icon} ${result.analyzer}`);
480
+
481
+ if (result.details.length > 0) {
482
+ result.details.forEach(detail => {
483
+ console.log(` ${detail}`);
484
+ });
485
+ }
486
+ console.log('');
487
+ }
488
+
489
+ // Final decision
490
+ if (reviewResult.approved) {
491
+ console.log('✅ Commit APPROVED');
492
+ console.log(` ${reviewResult.reason}\n`);
493
+ process.exit(0);
494
+ } else {
495
+ console.log('❌ Commit REJECTED');
496
+ console.log(` ${reviewResult.reason}\n`);
497
+ console.log('💡 Recording failure and resetting workspace...\n');
498
+
499
+ // Record detailed failure to progress.txt
500
+ await recordReviewFailure(reviewResult, hookData);
501
+
502
+ // Discard all changes (git reset)
503
+ await discardChanges();
504
+
505
+ // Exit with error - Ralph will detect this and continue to next iteration
506
+ console.log('🔄 Ralph will continue to next iteration\n');
507
+ process.exit(1);
508
+ }
509
+
510
+ } catch (error: any) {
511
+ console.error(`❌ Error: ${error.message}`);
512
+ // Fail-open: allow commit on error
513
+ console.log('⚠️ Allowing commit (fail-safe mode)\n');
514
+ process.exit(0);
515
+ }
516
+ }
517
+
518
+ main();
@@ -0,0 +1,198 @@
1
+ # Git Commit Analyzers
2
+
3
+ This directory contains analyzer definitions as markdown files. Each analyzer defines review criteria that an AI agent uses to evaluate git commits before they're committed.
4
+
5
+ ## How It Works
6
+
7
+ 1. **You commit code** - Claude runs `git commit`
8
+ 2. **Hook triggers** - `.claude/hooks/analyze-commit.sh` is called
9
+ 3. **Analyzers load** - All `.md` files in this directory are loaded
10
+ 4. **AI reviews** - A single AI review agent receives:
11
+ - The git diff
12
+ - All analyzer prompts (in priority order)
13
+ 5. **Decision made** - Agent goes through each analyzer and returns approved/rejected
14
+ 6. **Commit proceeds or blocks** - Based on the AI's decision
15
+
16
+ ## Analyzer Format
17
+
18
+ Each analyzer is a markdown file with this structure:
19
+
20
+ ```markdown
21
+ # Analyzer Name
22
+
23
+ ## Configuration
24
+
25
+ \`\`\`yaml
26
+ name: analyzer-name
27
+ blocking: true|false|secrets-only
28
+ priority: 0
29
+ \`\`\`
30
+
31
+ ## Prompt
32
+
33
+ Instructions for the AI review agent...
34
+
35
+ ## Review Criteria
36
+
37
+ **BLOCK (approved: false) if:**
38
+ - Critical condition 1
39
+ - Critical condition 2
40
+
41
+ **WARN but ALLOW (approved: true) if:**
42
+ - Warning condition 1
43
+ - Warning condition 2
44
+
45
+ ## Response Format
46
+
47
+ Expected JSON response format...
48
+ ```
49
+
50
+ ### Configuration Options
51
+
52
+ - **name**: Identifier for the analyzer
53
+ - **blocking**:
54
+ - `true` - Can block commits
55
+ - `false` - Warning only (never blocks)
56
+ - Custom string (e.g., `secrets-only`) - Context-specific blocking
57
+ - **priority**: Lower number = runs first (0 is highest priority)
58
+
59
+ ## Built-in Analyzers
60
+
61
+ ### 1. commit-policy.md
62
+ **Priority:** 0 (runs first)
63
+ **Blocking:** true
64
+
65
+ Enforces repository policies:
66
+ - No direct commits to main/production
67
+ - Commit size limits
68
+ - Related file changes (package.json + lockfile)
69
+
70
+ ### 2. code-quality.md
71
+ **Priority:** 1
72
+ **Blocking:** secrets-only
73
+
74
+ Checks for:
75
+ - Debug code (console.log, debugger)
76
+ - Security issues (secrets, API keys)
77
+ - Code smells (TODOs, magic numbers)
78
+
79
+ Blocks only on security issues.
80
+
81
+ ### 3. event-model-validator.md
82
+ **Priority:** 2
83
+ **Blocking:** false
84
+
85
+ Validates event modeling patterns:
86
+ - Event naming (past-tense)
87
+ - Command naming (imperative)
88
+ - Test coverage
89
+ - Slice structure
90
+
91
+ Warning only, never blocks.
92
+
93
+ ## Creating Custom Analyzers
94
+
95
+ 1. Create a new `.md` file in this directory
96
+ 2. Follow the format above
97
+ 3. Define your review criteria clearly
98
+ 4. Specify blocking behavior
99
+
100
+ ### Example: Database Migration Checker
101
+
102
+ ```markdown
103
+ # Database Migration Checker
104
+
105
+ ## Configuration
106
+
107
+ \`\`\`yaml
108
+ name: migration-checker
109
+ blocking: true
110
+ priority: 1
111
+ \`\`\`
112
+
113
+ ## Prompt
114
+
115
+ You are reviewing database-related changes.
116
+
117
+ Check if:
118
+ 1. Migration files are properly named with timestamps
119
+ 2. Migrations have both up and down methods
120
+ 3. Breaking schema changes are documented
121
+
122
+ ## Review Criteria
123
+
124
+ **BLOCK if:**
125
+ - Migration file without down method
126
+ - Breaking change without migration guide
127
+
128
+ **ALLOW if:**
129
+ - All migrations are complete
130
+ - Changes are backward compatible
131
+ ```
132
+
133
+ ## Disabling Analyzers
134
+
135
+ Rename the file to include `.disabled`:
136
+
137
+ ```bash
138
+ mv code-quality.md code-quality.md.disabled
139
+ ```
140
+
141
+ ## Testing Analyzers
142
+
143
+ The review prompt is built by combining:
144
+ 1. Commit info (branch, files, diff)
145
+ 2. All analyzer prompts (in priority order)
146
+ 3. Instructions to return JSON
147
+
148
+ The AI agent processes all analyzers and returns a single decision.
149
+
150
+ ## Response Format
151
+
152
+ The AI agent returns:
153
+
154
+ ```json
155
+ {
156
+ "approved": boolean,
157
+ "reason": "Overall summary",
158
+ "analyzer_results": [
159
+ {
160
+ "analyzer": "commit-policy",
161
+ "approved": true,
162
+ "details": ["All policies met"]
163
+ },
164
+ {
165
+ "analyzer": "code-quality",
166
+ "approved": false,
167
+ "details": ["Potential API key found in config.ts"]
168
+ }
169
+ ]
170
+ }
171
+ ```
172
+
173
+ If any `blocking: true` analyzer returns `approved: false`, the commit is blocked.
174
+
175
+ ## Best Practices
176
+
177
+ 1. **Keep prompts focused** - One concern per analyzer
178
+ 2. **Use clear criteria** - Explicitly state block vs warn conditions
179
+ 3. **Order by priority** - Critical checks first (lower priority number)
180
+ 4. **Be specific** - Give the AI clear examples of what to look for
181
+ 5. **Test thoroughly** - Make test commits to verify analyzer behavior
182
+
183
+ ## Benefits of Markdown Analyzers
184
+
185
+ - **Easy to read and edit** - No code knowledge required
186
+ - **Declarative** - Describe what to check, not how
187
+ - **Centralized AI review** - One agent processes all checks
188
+ - **Flexible** - Add new analyzers without coding
189
+ - **Context-aware** - AI understands nuance better than regex
190
+
191
+ ## Limitations
192
+
193
+ - Requires AI API access (Claude, GPT, etc.)
194
+ - Review speed depends on AI response time
195
+ - Cost per commit (API calls)
196
+ - Needs network connectivity
197
+
198
+ For simple pattern matching (e.g., checking for specific file extensions), traditional git hooks may be faster and cheaper. Use markdown analyzers for sophisticated code review that requires understanding context.