@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.
- package/README.md +254 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +138 -0
- package/package.json +49 -0
- package/templates/.claude/hooks/QUICKSTART.md +256 -0
- package/templates/.claude/hooks/README.md +533 -0
- package/templates/.claude/hooks/analyze-commit.sh +22 -0
- package/templates/.claude/hooks/analyze-commit.ts +518 -0
- package/templates/.claude/hooks/analyzers/README.md +198 -0
- package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
- package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
- package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
- package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
- package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
- package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
- package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
- package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
- package/templates/.claude/hooks/check-review-result.sh +47 -0
- package/templates/.claude/hooks/prepare-review.sh +34 -0
- package/templates/.claude/hooks/review-agent-prompt.md +42 -0
- package/templates/.claude/hooks/run-review-agent.sh +124 -0
- package/templates/.claude/settings.local.json +37 -0
- package/templates/.claude/skills/help/README.md +84 -0
- package/templates/.claude/skills/help/SKILL.md +393 -0
- package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
- package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
- package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
- package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
- package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
- package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
- package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
- package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
- package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
- package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
- package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
- package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
- package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
- package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
- package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
- package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
- package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
- package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
- package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
- package/templates/AGENTS.md +110 -0
- package/templates/Claude.md +58 -0
- package/templates/README.md +178 -0
- package/templates/backend/.env +9 -0
- package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
- package/templates/backend/SWAGGER.md +213 -0
- package/templates/backend/eslint.config.mjs +31 -0
- package/templates/backend/flyway.conf +17 -0
- package/templates/backend/package.json +44 -0
- package/templates/backend/prd.json.example +64 -0
- package/templates/backend/public/assets/images/banner.png +0 -0
- package/templates/backend/public/assets/logo.png +0 -0
- package/templates/backend/public/file.svg +4 -0
- package/templates/backend/public/globe.svg +12 -0
- package/templates/backend/public/next.svg +6 -0
- package/templates/backend/public/vercel.svg +3 -0
- package/templates/backend/public/window.svg +5 -0
- package/templates/backend/server.ts +129 -0
- package/templates/backend/setup-env.sh +50 -0
- package/templates/backend/src/common/assertions.ts +6 -0
- package/templates/backend/src/common/db.ts +1 -0
- package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
- package/templates/backend/src/common/parseEndpoint.ts +51 -0
- package/templates/backend/src/common/replay.ts +9 -0
- package/templates/backend/src/common/routes.ts +19 -0
- package/templates/backend/src/common/testHelpers.ts +53 -0
- package/templates/backend/src/core/readmodel.ts +28 -0
- package/templates/backend/src/core/types.ts +26 -0
- package/templates/backend/src/process/process.ts +53 -0
- package/templates/backend/src/supabase/LoginHandler.ts +36 -0
- package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
- package/templates/backend/src/supabase/README.md +171 -0
- package/templates/backend/src/supabase/api.ts +63 -0
- package/templates/backend/src/supabase/authMiddleware.ts +53 -0
- package/templates/backend/src/supabase/component.ts +12 -0
- package/templates/backend/src/supabase/requireUser.ts +72 -0
- package/templates/backend/src/supabase/serverProps.ts +25 -0
- package/templates/backend/src/supabase/staticProps.ts +10 -0
- package/templates/backend/src/swagger.ts +34 -0
- package/templates/backend/src/util/assertions.ts +6 -0
- package/templates/backend/supabase/config.toml +295 -0
- package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
- package/templates/backend/supabase/seed.sql +1 -0
- package/templates/backend/tsconfig.json +31 -0
- package/templates/frontend/.env.development +3 -0
- package/templates/frontend/AGENTS.md +7 -0
- package/templates/frontend/README.md +73 -0
- package/templates/frontend/components.json +20 -0
- package/templates/frontend/eslint.config.js +26 -0
- package/templates/frontend/index.html +18 -0
- package/templates/frontend/package-lock.json +8347 -0
- package/templates/frontend/package.json +94 -0
- package/templates/frontend/postcss.config.js +6 -0
- package/templates/frontend/public/favicon.ico +0 -0
- package/templates/frontend/public/logo.png +0 -0
- package/templates/frontend/public/placeholder.svg +1 -0
- package/templates/frontend/public/robots.txt +14 -0
- package/templates/frontend/src/App.css +42 -0
- package/templates/frontend/src/App.tsx +47 -0
- package/templates/frontend/src/components/NavLink.tsx +28 -0
- package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
- package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
- package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
- package/templates/frontend/src/components/layout/Header.tsx +45 -0
- package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
- package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
- package/templates/frontend/src/components/ui/accordion.tsx +52 -0
- package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
- package/templates/frontend/src/components/ui/alert.tsx +43 -0
- package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
- package/templates/frontend/src/components/ui/avatar.tsx +38 -0
- package/templates/frontend/src/components/ui/badge.tsx +29 -0
- package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
- package/templates/frontend/src/components/ui/button.tsx +47 -0
- package/templates/frontend/src/components/ui/calendar.tsx +54 -0
- package/templates/frontend/src/components/ui/card.tsx +43 -0
- package/templates/frontend/src/components/ui/carousel.tsx +224 -0
- package/templates/frontend/src/components/ui/chart.tsx +303 -0
- package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
- package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
- package/templates/frontend/src/components/ui/command.tsx +132 -0
- package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
- package/templates/frontend/src/components/ui/dialog.tsx +95 -0
- package/templates/frontend/src/components/ui/drawer.tsx +87 -0
- package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
- package/templates/frontend/src/components/ui/form.tsx +129 -0
- package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
- package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
- package/templates/frontend/src/components/ui/input.tsx +22 -0
- package/templates/frontend/src/components/ui/label.tsx +17 -0
- package/templates/frontend/src/components/ui/menubar.tsx +207 -0
- package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
- package/templates/frontend/src/components/ui/pagination.tsx +81 -0
- package/templates/frontend/src/components/ui/popover.tsx +29 -0
- package/templates/frontend/src/components/ui/progress.tsx +23 -0
- package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
- package/templates/frontend/src/components/ui/resizable.tsx +37 -0
- package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
- package/templates/frontend/src/components/ui/select.tsx +143 -0
- package/templates/frontend/src/components/ui/separator.tsx +20 -0
- package/templates/frontend/src/components/ui/sheet.tsx +107 -0
- package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
- package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
- package/templates/frontend/src/components/ui/slider.tsx +23 -0
- package/templates/frontend/src/components/ui/sonner.tsx +27 -0
- package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
- package/templates/frontend/src/components/ui/switch.tsx +27 -0
- package/templates/frontend/src/components/ui/table.tsx +72 -0
- package/templates/frontend/src/components/ui/tabs.tsx +53 -0
- package/templates/frontend/src/components/ui/textarea.tsx +21 -0
- package/templates/frontend/src/components/ui/toast.tsx +111 -0
- package/templates/frontend/src/components/ui/toaster.tsx +24 -0
- package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
- package/templates/frontend/src/components/ui/toggle.tsx +37 -0
- package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
- package/templates/frontend/src/components/ui/use-toast.ts +3 -0
- package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
- package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
- package/templates/frontend/src/hooks/api/index.ts +2 -0
- package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
- package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
- package/templates/frontend/src/hooks/use-toast.ts +186 -0
- package/templates/frontend/src/hooks/useApiContext.ts +11 -0
- package/templates/frontend/src/index.css +118 -0
- package/templates/frontend/src/integrations/supabase/client.ts +9 -0
- package/templates/frontend/src/lib/api-client.ts +136 -0
- package/templates/frontend/src/lib/api.ts +1028 -0
- package/templates/frontend/src/lib/utils.ts +6 -0
- package/templates/frontend/src/main.tsx +5 -0
- package/templates/frontend/src/pages/Auth.tsx +408 -0
- package/templates/frontend/src/pages/Dashboard.tsx +168 -0
- package/templates/frontend/src/pages/Menus.tsx +224 -0
- package/templates/frontend/src/pages/NotFound.tsx +24 -0
- package/templates/frontend/src/pages/Register.tsx +285 -0
- package/templates/frontend/src/test/example.test.ts +0 -0
- package/templates/frontend/src/test/setup.ts +15 -0
- package/templates/frontend/src/types/index.ts +8 -0
- package/templates/frontend/src/vite-env.d.ts +1 -0
- package/templates/frontend/tailwind.config.ts +101 -0
- package/templates/frontend/tsconfig.app.json +31 -0
- package/templates/frontend/tsconfig.json +16 -0
- package/templates/frontend/tsconfig.node.json +22 -0
- package/templates/frontend/vite.config.ts +21 -0
- package/templates/frontend/vitest.config.ts +16 -0
- package/templates/init.sh +1 -0
- package/templates/prompt.md +139 -0
- package/templates/ralph.sh +120 -0
- 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.
|