@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,154 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Analyzer: Code Quality Checker
|
|
5
|
+
*
|
|
6
|
+
* This analyzer checks for common code quality issues in the diff:
|
|
7
|
+
* - Debug statements (console.log, debugger)
|
|
8
|
+
* - TODO/FIXME comments
|
|
9
|
+
* - Large file changes
|
|
10
|
+
* - Potential sensitive data (API keys, passwords)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from 'fs';
|
|
14
|
+
|
|
15
|
+
interface HookData {
|
|
16
|
+
event: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
repository: string;
|
|
19
|
+
branch: string;
|
|
20
|
+
staged_diff: string;
|
|
21
|
+
unstaged_diff: string;
|
|
22
|
+
changed_files: string[];
|
|
23
|
+
stats: {
|
|
24
|
+
staged_files: number;
|
|
25
|
+
has_staged_changes: boolean;
|
|
26
|
+
has_unstaged_changes: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface Issue {
|
|
31
|
+
severity: 'warning' | 'info';
|
|
32
|
+
message: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function analyzeDiff(diff: string): Promise<Issue[]> {
|
|
36
|
+
const issues: Issue[] = [];
|
|
37
|
+
const lines = diff.split('\n');
|
|
38
|
+
|
|
39
|
+
// Only check added lines (starting with +)
|
|
40
|
+
const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++'));
|
|
41
|
+
|
|
42
|
+
// Check for console.log statements
|
|
43
|
+
const consoleLogs = addedLines.filter(line => /console\.(log|debug|info|warn|error)/.test(line));
|
|
44
|
+
if (consoleLogs.length > 0) {
|
|
45
|
+
issues.push({
|
|
46
|
+
severity: 'warning',
|
|
47
|
+
message: `Found ${consoleLogs.length} console.log statement(s) - consider removing debug code`
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Check for debugger statements
|
|
52
|
+
const debuggers = addedLines.filter(line => /\bdebugger\b/.test(line));
|
|
53
|
+
if (debuggers.length > 0) {
|
|
54
|
+
issues.push({
|
|
55
|
+
severity: 'warning',
|
|
56
|
+
message: `Found ${debuggers.length} debugger statement(s) - remove before committing`
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Check for TODO/FIXME comments
|
|
61
|
+
const todos = addedLines.filter(line => /(TODO|FIXME|XXX|HACK)/i.test(line));
|
|
62
|
+
if (todos.length > 0) {
|
|
63
|
+
issues.push({
|
|
64
|
+
severity: 'info',
|
|
65
|
+
message: `Found ${todos.length} TODO/FIXME comment(s) - ensure they're tracked`
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Check for potential API keys or secrets
|
|
70
|
+
const potentialSecrets = addedLines.filter(line =>
|
|
71
|
+
/(api[_-]?key|secret|password|token|auth[_-]?key)[\s]*[:=][\s]*['"][^'"]{20,}['"]/.test(line.toLowerCase())
|
|
72
|
+
);
|
|
73
|
+
if (potentialSecrets.length > 0) {
|
|
74
|
+
issues.push({
|
|
75
|
+
severity: 'warning',
|
|
76
|
+
message: `ā ļø SECURITY: Found ${potentialSecrets.length} potential secret(s) - verify no credentials are committed`
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return issues;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
interface AnalyzerResult {
|
|
84
|
+
approved: boolean;
|
|
85
|
+
reason?: string;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
async function main() {
|
|
89
|
+
try {
|
|
90
|
+
// Read JSON data from stdin
|
|
91
|
+
const input = readFileSync(0, 'utf-8');
|
|
92
|
+
const data: HookData = JSON.parse(input);
|
|
93
|
+
|
|
94
|
+
console.log('š Code Quality Analysis:');
|
|
95
|
+
|
|
96
|
+
// Analyze staged changes
|
|
97
|
+
const stagedIssues = await analyzeDiff(data.staged_diff);
|
|
98
|
+
const unstagedIssues = await analyzeDiff(data.unstaged_diff);
|
|
99
|
+
|
|
100
|
+
const allIssues = [...stagedIssues, ...unstagedIssues];
|
|
101
|
+
|
|
102
|
+
if (allIssues.length === 0) {
|
|
103
|
+
console.log(' ā
No issues found');
|
|
104
|
+
|
|
105
|
+
const result: AnalyzerResult = {
|
|
106
|
+
approved: true,
|
|
107
|
+
reason: 'No code quality issues found'
|
|
108
|
+
};
|
|
109
|
+
console.log('\n' + JSON.stringify(result));
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Group by severity
|
|
114
|
+
const warnings = allIssues.filter(i => i.severity === 'warning');
|
|
115
|
+
const infos = allIssues.filter(i => i.severity === 'info');
|
|
116
|
+
|
|
117
|
+
if (warnings.length > 0) {
|
|
118
|
+
console.log('\n ā ļø Warnings:');
|
|
119
|
+
warnings.forEach(issue => console.log(` - ${issue.message}`));
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (infos.length > 0) {
|
|
123
|
+
console.log('\n ā¹ļø Info:');
|
|
124
|
+
infos.forEach(issue => console.log(` - ${issue.message}`));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check for blocking issues (potential secrets)
|
|
128
|
+
const hasSecrets = warnings.some(w => w.message.includes('SECURITY'));
|
|
129
|
+
|
|
130
|
+
let result: AnalyzerResult;
|
|
131
|
+
if (hasSecrets) {
|
|
132
|
+
// BLOCK commit if potential secrets found
|
|
133
|
+
result = {
|
|
134
|
+
approved: false,
|
|
135
|
+
reason: 'Potential secrets detected - verify no credentials are committed'
|
|
136
|
+
};
|
|
137
|
+
} else {
|
|
138
|
+
// Just warn but allow commit
|
|
139
|
+
result = {
|
|
140
|
+
approved: true,
|
|
141
|
+
reason: `Code quality warnings found (${warnings.length} warnings, ${infos.length} info)`
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log('\n' + JSON.stringify(result));
|
|
146
|
+
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.error('ā Error in code quality checker:', error);
|
|
149
|
+
console.log(JSON.stringify({ approved: true, reason: 'analyzer_error' }));
|
|
150
|
+
process.exit(1);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
main();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Code Quality Analyzer
|
|
2
|
+
|
|
3
|
+
## Configuration
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
name: code-quality-checker
|
|
7
|
+
blocking: secrets-only
|
|
8
|
+
priority: 1
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Prompt
|
|
12
|
+
|
|
13
|
+
You are a code quality analyzer reviewing git commit changes before they are committed.
|
|
14
|
+
|
|
15
|
+
Analyze the provided diff and check for:
|
|
16
|
+
|
|
17
|
+
1. **Debug Code** - console.log, debugger statements, commented-out code
|
|
18
|
+
2. **Security Issues** - Potential secrets, API keys, passwords, tokens hardcoded in code
|
|
19
|
+
3. **Code Smells** - TODO/FIXME comments, magic numbers, overly complex code
|
|
20
|
+
4. **Best Practices** - Proper error handling, appropriate variable names
|
|
21
|
+
|
|
22
|
+
## Review Criteria
|
|
23
|
+
|
|
24
|
+
**BLOCK the commit (approved: false) if:**
|
|
25
|
+
- Potential secrets/credentials detected (API keys, passwords, tokens with values)
|
|
26
|
+
- Obvious security vulnerabilities (eval, dangerouslySetInnerHTML without sanitization)
|
|
27
|
+
|
|
28
|
+
**WARN but ALLOW (approved: true) if:**
|
|
29
|
+
- console.log or debugger statements found
|
|
30
|
+
- TODO/FIXME comments present
|
|
31
|
+
- Minor code quality issues
|
|
32
|
+
|
|
33
|
+
## Response Format
|
|
34
|
+
|
|
35
|
+
Return your analysis as JSON:
|
|
36
|
+
|
|
37
|
+
```json
|
|
38
|
+
{
|
|
39
|
+
"approved": boolean,
|
|
40
|
+
"reason": "Brief explanation",
|
|
41
|
+
"details": [
|
|
42
|
+
"Specific issue 1",
|
|
43
|
+
"Specific issue 2"
|
|
44
|
+
]
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Input Data
|
|
49
|
+
|
|
50
|
+
You will receive:
|
|
51
|
+
- `staged_diff`: The git diff of staged changes
|
|
52
|
+
- `changed_files`: List of files being committed
|
|
53
|
+
- `branch`: Current git branch
|
|
54
|
+
- Full git context
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Analyzer: Commit Blocker
|
|
5
|
+
*
|
|
6
|
+
* This analyzer demonstrates how to BLOCK commits based on specific conditions.
|
|
7
|
+
* It's disabled by default (note the .disabled extension).
|
|
8
|
+
*
|
|
9
|
+
* To enable: Rename to commit-blocker-example.ts
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { readFileSync } from 'fs';
|
|
13
|
+
|
|
14
|
+
interface HookData {
|
|
15
|
+
event: string;
|
|
16
|
+
timestamp: string;
|
|
17
|
+
repository: string;
|
|
18
|
+
branch: string;
|
|
19
|
+
staged_diff: string;
|
|
20
|
+
unstaged_diff: string;
|
|
21
|
+
changed_files: string[];
|
|
22
|
+
stats: {
|
|
23
|
+
staged_files: number;
|
|
24
|
+
has_staged_changes: boolean;
|
|
25
|
+
has_unstaged_changes: boolean;
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface AnalyzerResult {
|
|
30
|
+
approved: boolean;
|
|
31
|
+
reason?: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function main() {
|
|
35
|
+
try {
|
|
36
|
+
// Read JSON data from stdin
|
|
37
|
+
const input = readFileSync(0, 'utf-8');
|
|
38
|
+
const data: HookData = JSON.parse(input);
|
|
39
|
+
|
|
40
|
+
console.log('š« Commit Blocker Example:');
|
|
41
|
+
|
|
42
|
+
// Example 1: Block commits to main branch
|
|
43
|
+
if (data.branch === 'main') {
|
|
44
|
+
const result: AnalyzerResult = {
|
|
45
|
+
approved: false,
|
|
46
|
+
reason: 'Direct commits to main branch are not allowed'
|
|
47
|
+
};
|
|
48
|
+
console.log('\n' + JSON.stringify(result));
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Example 2: Block commits with too many files
|
|
53
|
+
const MAX_FILES = 50;
|
|
54
|
+
if (data.changed_files.length > MAX_FILES) {
|
|
55
|
+
const result: AnalyzerResult = {
|
|
56
|
+
approved: false,
|
|
57
|
+
reason: `Too many files changed (${data.changed_files.length} > ${MAX_FILES}) - split into smaller commits`
|
|
58
|
+
};
|
|
59
|
+
console.log('\n' + JSON.stringify(result));
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Example 3: Block if critical files changed without tests
|
|
64
|
+
const criticalFiles = data.changed_files.filter(f =>
|
|
65
|
+
f.includes('/api/') ||
|
|
66
|
+
f.includes('/core/') ||
|
|
67
|
+
f.endsWith('Handler.ts')
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
const testFiles = data.changed_files.filter(f => f.endsWith('.test.ts'));
|
|
71
|
+
|
|
72
|
+
if (criticalFiles.length > 0 && testFiles.length === 0) {
|
|
73
|
+
const result: AnalyzerResult = {
|
|
74
|
+
approved: false,
|
|
75
|
+
reason: 'Critical files changed but no test files updated - add tests first'
|
|
76
|
+
};
|
|
77
|
+
console.log(`\n Critical files: ${criticalFiles.join(', ')}`);
|
|
78
|
+
console.log('\n' + JSON.stringify(result));
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Example 4: Block if package.json changed without package-lock.json
|
|
83
|
+
const packageJsonChanged = data.changed_files.includes('package.json');
|
|
84
|
+
const lockfileChanged = data.changed_files.includes('package-lock.json');
|
|
85
|
+
|
|
86
|
+
if (packageJsonChanged && !lockfileChanged) {
|
|
87
|
+
const result: AnalyzerResult = {
|
|
88
|
+
approved: false,
|
|
89
|
+
reason: 'package.json changed but package-lock.json not updated - run npm install'
|
|
90
|
+
};
|
|
91
|
+
console.log('\n' + JSON.stringify(result));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// All checks passed
|
|
96
|
+
console.log(' ā
All blocking checks passed');
|
|
97
|
+
const result: AnalyzerResult = {
|
|
98
|
+
approved: true,
|
|
99
|
+
reason: 'No blocking issues found'
|
|
100
|
+
};
|
|
101
|
+
console.log('\n' + JSON.stringify(result));
|
|
102
|
+
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error('ā Error in commit blocker:', error);
|
|
105
|
+
console.log(JSON.stringify({ approved: true, reason: 'analyzer_error' }));
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
main();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Commit Policy Enforcer
|
|
2
|
+
|
|
3
|
+
## Configuration
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
name: commit-policy
|
|
7
|
+
blocking: true
|
|
8
|
+
priority: 0
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Prompt
|
|
12
|
+
|
|
13
|
+
You are a commit policy enforcer ensuring repository standards are maintained.
|
|
14
|
+
|
|
15
|
+
Review the commit and enforce these policies:
|
|
16
|
+
|
|
17
|
+
1. **Branch Protection** - Check if direct commits to protected branches (main, production) are allowed
|
|
18
|
+
2. **Commit Size** - Ensure commits aren't too large (>50 files suggests should be split)
|
|
19
|
+
3. **Critical File Protection** - If critical files change (package.json, config files), ensure related files also change
|
|
20
|
+
4. **Required Files** - If package.json changes, package-lock.json should also change
|
|
21
|
+
|
|
22
|
+
## Review Criteria
|
|
23
|
+
|
|
24
|
+
**BLOCK the commit (approved: false) if:**
|
|
25
|
+
- Direct commit to 'main' or 'production' branch
|
|
26
|
+
- More than 50 files changed in a single commit
|
|
27
|
+
- package.json changed but package-lock.json didn't change
|
|
28
|
+
- Critical API files changed without corresponding test updates
|
|
29
|
+
|
|
30
|
+
**ALLOW (approved: true) otherwise**
|
|
31
|
+
|
|
32
|
+
## Response Format
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"approved": boolean,
|
|
37
|
+
"reason": "Policy check result",
|
|
38
|
+
"details": [
|
|
39
|
+
"Policy violation or approval reason"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Input Data
|
|
45
|
+
|
|
46
|
+
You will receive:
|
|
47
|
+
- `branch`: Current branch name
|
|
48
|
+
- `changed_files`: List and count of files
|
|
49
|
+
- `staged_diff`: Full diff of changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Event Model Validator
|
|
2
|
+
|
|
3
|
+
## Configuration
|
|
4
|
+
|
|
5
|
+
```yaml
|
|
6
|
+
name: event-model-validator
|
|
7
|
+
blocking: false
|
|
8
|
+
priority: 2
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Prompt
|
|
12
|
+
|
|
13
|
+
You are an event model architecture validator for event-sourced systems.
|
|
14
|
+
|
|
15
|
+
Review the git diff and validate that changes follow event modeling best practices:
|
|
16
|
+
|
|
17
|
+
1. **Event Naming** - Events should be in past tense (e.g., "UserRegistered", "OrderPlaced")
|
|
18
|
+
2. **Command Naming** - Commands should be imperative (e.g., "RegisterUser", "PlaceOrder")
|
|
19
|
+
3. **Test Coverage** - When logic files change (CommandHandler, Projection, processor), corresponding test files should also change
|
|
20
|
+
4. **Slice Structure** - Files should follow the standard slice organization pattern
|
|
21
|
+
|
|
22
|
+
## Review Criteria
|
|
23
|
+
|
|
24
|
+
**Always ALLOW (approved: true)** - This analyzer provides guidance but doesn't block commits.
|
|
25
|
+
|
|
26
|
+
Provide warnings for:
|
|
27
|
+
- Event names that don't follow past-tense convention
|
|
28
|
+
- Missing test updates when logic changes
|
|
29
|
+
- Deviations from slice structure patterns
|
|
30
|
+
|
|
31
|
+
## Response Format
|
|
32
|
+
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"approved": true,
|
|
36
|
+
"reason": "Event model validation completed",
|
|
37
|
+
"details": [
|
|
38
|
+
"ā ļø Event 'UserRegister' should be 'UserRegistered'",
|
|
39
|
+
"š” Logic changed in RegisterUser slice but no test updates"
|
|
40
|
+
]
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Input Data
|
|
45
|
+
|
|
46
|
+
You will receive:
|
|
47
|
+
- `staged_diff`: Changes being committed
|
|
48
|
+
- `changed_files`: List of modified files (check for *Command.ts, *Projection.ts, *.test.ts)
|
|
49
|
+
- Full repository context
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Analyzer: Event Model Validator
|
|
5
|
+
*
|
|
6
|
+
* This analyzer validates changes to event model slices:
|
|
7
|
+
* - Checks if event names follow past-tense convention
|
|
8
|
+
* - Validates command naming (imperative form)
|
|
9
|
+
* - Ensures test files are updated when logic changes
|
|
10
|
+
* - Verifies TypeScript files follow slice structure
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from 'fs';
|
|
14
|
+
|
|
15
|
+
interface HookData {
|
|
16
|
+
event: string;
|
|
17
|
+
timestamp: string;
|
|
18
|
+
repository: string;
|
|
19
|
+
branch: string;
|
|
20
|
+
staged_diff: string;
|
|
21
|
+
unstaged_diff: string;
|
|
22
|
+
changed_files: string[];
|
|
23
|
+
stats: {
|
|
24
|
+
staged_files: number;
|
|
25
|
+
has_staged_changes: boolean;
|
|
26
|
+
has_unstaged_changes: boolean;
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ValidationResult {
|
|
31
|
+
valid: boolean;
|
|
32
|
+
warnings: string[];
|
|
33
|
+
suggestions: string[];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function validateEventNames(diff: string): ValidationResult {
|
|
37
|
+
const warnings: string[] = [];
|
|
38
|
+
const suggestions: string[] = [];
|
|
39
|
+
|
|
40
|
+
// Look for event definitions in the diff
|
|
41
|
+
const eventPattern = /interface\s+(\w+Event)\s*{/g;
|
|
42
|
+
const lines = diff.split('\n').filter(line => line.startsWith('+'));
|
|
43
|
+
const content = lines.join('\n');
|
|
44
|
+
|
|
45
|
+
const matches = [...content.matchAll(eventPattern)];
|
|
46
|
+
|
|
47
|
+
for (const match of matches) {
|
|
48
|
+
const eventName = match[1];
|
|
49
|
+
|
|
50
|
+
// Check if event name ends with past tense indicators
|
|
51
|
+
const pastTenseEndings = ['ed', 'Created', 'Updated', 'Deleted', 'Activated', 'Deactivated', 'Registered', 'Confirmed'];
|
|
52
|
+
const hasPastTense = pastTenseEndings.some(ending => eventName.includes(ending));
|
|
53
|
+
|
|
54
|
+
if (!hasPastTense) {
|
|
55
|
+
warnings.push(`Event name "${eventName}" may not follow past-tense convention`);
|
|
56
|
+
suggestions.push(`Consider renaming to express a completed action (e.g., "${eventName}Created" or "${eventName}Updated")`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
valid: warnings.length === 0,
|
|
62
|
+
warnings,
|
|
63
|
+
suggestions
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function checkSliceStructure(changedFiles: string[]): ValidationResult {
|
|
68
|
+
const warnings: string[] = [];
|
|
69
|
+
const suggestions: string[] = [];
|
|
70
|
+
|
|
71
|
+
// Group files by slice
|
|
72
|
+
const sliceFiles = changedFiles.filter(f => f.includes('src/slices/'));
|
|
73
|
+
|
|
74
|
+
const sliceMap = new Map<string, string[]>();
|
|
75
|
+
|
|
76
|
+
for (const file of sliceFiles) {
|
|
77
|
+
const match = file.match(/src\/slices\/([^/]+)\//);
|
|
78
|
+
if (match) {
|
|
79
|
+
const sliceName = match[1];
|
|
80
|
+
if (!sliceMap.has(sliceName)) {
|
|
81
|
+
sliceMap.set(sliceName, []);
|
|
82
|
+
}
|
|
83
|
+
sliceMap.get(sliceName)!.push(file);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Check if test files are included when logic changes
|
|
88
|
+
for (const [sliceName, files] of sliceMap.entries()) {
|
|
89
|
+
const hasLogicChange = files.some(f =>
|
|
90
|
+
f.endsWith('Command.ts') ||
|
|
91
|
+
f.endsWith('CommandHandler.ts') ||
|
|
92
|
+
f.endsWith('Projection.ts') ||
|
|
93
|
+
f.endsWith('processor.ts')
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
const hasTestChange = files.some(f => f.endsWith('.test.ts'));
|
|
97
|
+
|
|
98
|
+
if (hasLogicChange && !hasTestChange) {
|
|
99
|
+
warnings.push(`Logic changed in "${sliceName}" slice but no test file updated`);
|
|
100
|
+
suggestions.push(`Consider updating ${sliceName}.test.ts to cover the changes`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
valid: warnings.length === 0,
|
|
106
|
+
warnings,
|
|
107
|
+
suggestions
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
interface AnalyzerResult {
|
|
112
|
+
approved: boolean;
|
|
113
|
+
reason?: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function main() {
|
|
117
|
+
try {
|
|
118
|
+
// Read JSON data from stdin
|
|
119
|
+
const input = readFileSync(0, 'utf-8');
|
|
120
|
+
const data: HookData = JSON.parse(input);
|
|
121
|
+
|
|
122
|
+
console.log('šļø Event Model Validation:');
|
|
123
|
+
|
|
124
|
+
// Validate event names
|
|
125
|
+
const eventValidation = validateEventNames(data.staged_diff);
|
|
126
|
+
|
|
127
|
+
// Check slice structure
|
|
128
|
+
const structureValidation = checkSliceStructure(data.changed_files);
|
|
129
|
+
|
|
130
|
+
const allWarnings = [...eventValidation.warnings, ...structureValidation.warnings];
|
|
131
|
+
const allSuggestions = [...eventValidation.suggestions, ...structureValidation.suggestions];
|
|
132
|
+
|
|
133
|
+
if (allWarnings.length === 0) {
|
|
134
|
+
console.log(' ā
Event model structure looks good');
|
|
135
|
+
|
|
136
|
+
const result: AnalyzerResult = {
|
|
137
|
+
approved: true,
|
|
138
|
+
reason: 'Event model validation passed'
|
|
139
|
+
};
|
|
140
|
+
console.log('\n' + JSON.stringify(result));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (allWarnings.length > 0) {
|
|
145
|
+
console.log('\n ā ļø Validation warnings:');
|
|
146
|
+
allWarnings.forEach(warning => console.log(` - ${warning}`));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (allSuggestions.length > 0) {
|
|
150
|
+
console.log('\n š” Suggestions:');
|
|
151
|
+
allSuggestions.forEach(suggestion => console.log(` - ${suggestion}`));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Approve but provide warnings
|
|
155
|
+
const result: AnalyzerResult = {
|
|
156
|
+
approved: true,
|
|
157
|
+
reason: `Event model validation warnings (${allWarnings.length} warnings)`
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
console.log('\n' + JSON.stringify(result));
|
|
161
|
+
|
|
162
|
+
} catch (error) {
|
|
163
|
+
console.error('ā Error in event model validator:', error);
|
|
164
|
+
console.log(JSON.stringify({ approved: true, reason: 'analyzer_error' }));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
main();
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example Analyzer: Simple Logger
|
|
5
|
+
*
|
|
6
|
+
* This analyzer receives git diff information and logs basic statistics.
|
|
7
|
+
* It demonstrates how to parse the JSON payload and extract useful information.
|
|
8
|
+
* Always approves commits.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { readFileSync } from 'fs';
|
|
12
|
+
|
|
13
|
+
interface HookData {
|
|
14
|
+
event: string;
|
|
15
|
+
timestamp: string;
|
|
16
|
+
repository: string;
|
|
17
|
+
branch: string;
|
|
18
|
+
staged_diff: string;
|
|
19
|
+
unstaged_diff: string;
|
|
20
|
+
changed_files: string[];
|
|
21
|
+
stats: {
|
|
22
|
+
staged_files: number;
|
|
23
|
+
has_staged_changes: boolean;
|
|
24
|
+
has_unstaged_changes: boolean;
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface AnalyzerResult {
|
|
29
|
+
approved: boolean;
|
|
30
|
+
reason?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function main() {
|
|
34
|
+
try {
|
|
35
|
+
// Read JSON data from stdin
|
|
36
|
+
const input = readFileSync(0, 'utf-8');
|
|
37
|
+
const data: HookData = JSON.parse(input);
|
|
38
|
+
|
|
39
|
+
console.log('š Commit Analysis:');
|
|
40
|
+
console.log(` Branch: ${data.branch}`);
|
|
41
|
+
console.log(` Files changed: ${data.changed_files.length}`);
|
|
42
|
+
console.log(` Has staged changes: ${data.stats.has_staged_changes}`);
|
|
43
|
+
console.log(` Has unstaged changes: ${data.stats.has_unstaged_changes}`);
|
|
44
|
+
|
|
45
|
+
// Log changed files
|
|
46
|
+
if (data.changed_files.length > 0) {
|
|
47
|
+
console.log('\nš Changed files:');
|
|
48
|
+
data.changed_files.forEach(file => {
|
|
49
|
+
if (file.trim()) {
|
|
50
|
+
console.log(` - ${file}`);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Always approve - this is just an informational analyzer
|
|
56
|
+
const result: AnalyzerResult = {
|
|
57
|
+
approved: true,
|
|
58
|
+
reason: 'Logger - informational only'
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
console.log('\n' + JSON.stringify(result));
|
|
62
|
+
|
|
63
|
+
} catch (error) {
|
|
64
|
+
console.error('ā Error in analyzer:', error);
|
|
65
|
+
console.log(JSON.stringify({ approved: true, reason: 'analyzer_error' }));
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
main();
|