start-vibing 2.0.11 → 2.0.13
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 +177 -177
- package/dist/cli.js +19 -2
- package/package.json +42 -42
- package/template/.claude/CLAUDE.md +174 -174
- package/template/.claude/agents/01-orchestration/agent-selector.md +130 -130
- package/template/.claude/agents/01-orchestration/checkpoint-manager.md +142 -142
- package/template/.claude/agents/01-orchestration/context-manager.md +138 -138
- package/template/.claude/agents/01-orchestration/error-recovery.md +182 -182
- package/template/.claude/agents/01-orchestration/orchestrator.md +114 -114
- package/template/.claude/agents/01-orchestration/parallel-coordinator.md +141 -141
- package/template/.claude/agents/01-orchestration/task-decomposer.md +121 -121
- package/template/.claude/agents/01-orchestration/workflow-router.md +114 -114
- package/template/.claude/agents/02-typescript/bun-runtime-expert.md +197 -197
- package/template/.claude/agents/02-typescript/esm-resolver.md +193 -193
- package/template/.claude/agents/02-typescript/import-alias-enforcer.md +158 -158
- package/template/.claude/agents/02-typescript/ts-generics-helper.md +183 -183
- package/template/.claude/agents/02-typescript/ts-migration-helper.md +238 -238
- package/template/.claude/agents/02-typescript/ts-strict-checker.md +180 -180
- package/template/.claude/agents/02-typescript/ts-types-analyzer.md +199 -199
- package/template/.claude/agents/02-typescript/type-definition-writer.md +187 -187
- package/template/.claude/agents/02-typescript/zod-schema-designer.md +212 -212
- package/template/.claude/agents/02-typescript/zod-validator.md +158 -158
- package/template/.claude/agents/03-testing/playwright-assertions.md +265 -265
- package/template/.claude/agents/03-testing/playwright-e2e.md +247 -247
- package/template/.claude/agents/03-testing/playwright-fixtures.md +234 -234
- package/template/.claude/agents/03-testing/playwright-multi-viewport.md +256 -256
- package/template/.claude/agents/03-testing/playwright-page-objects.md +247 -247
- package/template/.claude/agents/03-testing/test-cleanup-manager.md +248 -248
- package/template/.claude/agents/03-testing/test-data-generator.md +254 -254
- package/template/.claude/agents/03-testing/tester-integration.md +278 -278
- package/template/.claude/agents/03-testing/tester-unit.md +207 -207
- package/template/.claude/agents/03-testing/vitest-config.md +287 -287
- package/template/.claude/agents/04-docker/container-health.md +255 -255
- package/template/.claude/agents/04-docker/deployment-validator.md +225 -225
- package/template/.claude/agents/04-docker/docker-compose-designer.md +281 -281
- package/template/.claude/agents/04-docker/docker-env-manager.md +235 -235
- package/template/.claude/agents/04-docker/docker-multi-stage.md +241 -241
- package/template/.claude/agents/04-docker/dockerfile-optimizer.md +208 -208
- package/template/.claude/agents/05-database/database-seeder.md +273 -273
- package/template/.claude/agents/05-database/mongodb-query-optimizer.md +230 -230
- package/template/.claude/agents/05-database/mongoose-aggregation.md +306 -306
- package/template/.claude/agents/05-database/mongoose-index-optimizer.md +182 -182
- package/template/.claude/agents/05-database/mongoose-schema-designer.md +267 -267
- package/template/.claude/agents/06-security/auth-session-validator.md +68 -68
- package/template/.claude/agents/06-security/input-sanitizer.md +80 -80
- package/template/.claude/agents/06-security/owasp-checker.md +97 -97
- package/template/.claude/agents/06-security/permission-auditor.md +100 -100
- package/template/.claude/agents/06-security/security-auditor.md +84 -84
- package/template/.claude/agents/06-security/sensitive-data-scanner.md +83 -83
- package/template/.claude/agents/07-documentation/api-documenter.md +136 -136
- package/template/.claude/agents/07-documentation/changelog-manager.md +105 -105
- package/template/.claude/agents/07-documentation/documenter.md +76 -76
- package/template/.claude/agents/07-documentation/domain-updater.md +81 -81
- package/template/.claude/agents/07-documentation/jsdoc-generator.md +114 -114
- package/template/.claude/agents/07-documentation/readme-generator.md +135 -135
- package/template/.claude/agents/08-git/branch-manager.md +58 -58
- package/template/.claude/agents/08-git/commit-manager.md +63 -63
- package/template/.claude/agents/08-git/pr-creator.md +76 -76
- package/template/.claude/agents/09-quality/code-reviewer.md +71 -71
- package/template/.claude/agents/09-quality/quality-checker.md +67 -67
- package/template/.claude/agents/10-research/best-practices-finder.md +89 -89
- package/template/.claude/agents/10-research/competitor-analyzer.md +106 -106
- package/template/.claude/agents/10-research/pattern-researcher.md +93 -93
- package/template/.claude/agents/10-research/research-cache-manager.md +76 -76
- package/template/.claude/agents/10-research/research-web.md +98 -98
- package/template/.claude/agents/10-research/tech-evaluator.md +101 -101
- package/template/.claude/agents/11-ui-ux/accessibility-auditor.md +136 -136
- package/template/.claude/agents/11-ui-ux/design-system-enforcer.md +125 -125
- package/template/.claude/agents/11-ui-ux/skeleton-generator.md +118 -118
- package/template/.claude/agents/11-ui-ux/ui-desktop.md +132 -132
- package/template/.claude/agents/11-ui-ux/ui-mobile.md +98 -98
- package/template/.claude/agents/11-ui-ux/ui-tablet.md +110 -110
- package/template/.claude/agents/12-performance/api-latency-analyzer.md +156 -156
- package/template/.claude/agents/12-performance/bundle-analyzer.md +113 -113
- package/template/.claude/agents/12-performance/memory-leak-detector.md +137 -137
- package/template/.claude/agents/12-performance/performance-profiler.md +115 -115
- package/template/.claude/agents/12-performance/query-optimizer.md +124 -124
- package/template/.claude/agents/12-performance/render-optimizer.md +154 -154
- package/template/.claude/agents/13-debugging/build-error-fixer.md +207 -207
- package/template/.claude/agents/13-debugging/debugger.md +149 -149
- package/template/.claude/agents/13-debugging/error-stack-analyzer.md +141 -141
- package/template/.claude/agents/13-debugging/network-debugger.md +208 -208
- package/template/.claude/agents/13-debugging/runtime-error-fixer.md +181 -181
- package/template/.claude/agents/13-debugging/type-error-resolver.md +185 -185
- package/template/.claude/agents/14-validation/final-validator.md +93 -93
- package/template/.claude/agents/_backup/analyzer.md +134 -134
- package/template/.claude/agents/_backup/code-reviewer.md +279 -279
- package/template/.claude/agents/_backup/commit-manager.md +219 -219
- package/template/.claude/agents/_backup/debugger.md +280 -280
- package/template/.claude/agents/_backup/documenter.md +237 -237
- package/template/.claude/agents/_backup/domain-updater.md +197 -197
- package/template/.claude/agents/_backup/final-validator.md +169 -169
- package/template/.claude/agents/_backup/orchestrator.md +149 -149
- package/template/.claude/agents/_backup/performance.md +232 -232
- package/template/.claude/agents/_backup/quality-checker.md +240 -240
- package/template/.claude/agents/_backup/research.md +315 -315
- package/template/.claude/agents/_backup/security-auditor.md +192 -192
- package/template/.claude/agents/_backup/tester.md +566 -566
- package/template/.claude/agents/_backup/ui-ux-reviewer.md +247 -247
- package/template/.claude/config/README.md +30 -30
- package/template/.claude/config/mcp-config.json +344 -344
- package/template/.claude/config/project-config.json +53 -53
- package/template/.claude/config/quality-gates.json +46 -46
- package/template/.claude/config/security-rules.json +45 -45
- package/template/.claude/config/testing-config.json +164 -164
- package/template/.claude/hooks/SETUP.md +126 -126
- package/template/.claude/hooks/run-hook.ts +176 -176
- package/template/.claude/hooks/stop-validator.ts +914 -824
- package/template/.claude/hooks/user-prompt-submit.ts +886 -886
- package/template/.claude/scripts/mcp-quick-install.ts +151 -151
- package/template/.claude/scripts/setup-mcps.ts +651 -651
- package/template/.claude/settings.json +275 -275
- package/template/.claude/skills/bun-runtime/SKILL.md +430 -430
- package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +431 -431
- package/template/.claude/skills/codebase-knowledge/domains/mcp-integration.md +295 -295
- package/template/.claude/skills/debugging-patterns/SKILL.md +485 -485
- package/template/.claude/skills/docker-patterns/SKILL.md +555 -555
- package/template/.claude/skills/git-workflow/SKILL.md +454 -454
- package/template/.claude/skills/mongoose-patterns/SKILL.md +499 -499
- package/template/.claude/skills/nextjs-app-router/SKILL.md +327 -327
- package/template/.claude/skills/performance-patterns/SKILL.md +547 -547
- package/template/.claude/skills/playwright-automation/SKILL.md +438 -438
- package/template/.claude/skills/react-patterns/SKILL.md +389 -389
- package/template/.claude/skills/research-cache/SKILL.md +222 -222
- package/template/.claude/skills/shadcn-ui/SKILL.md +511 -511
- package/template/.claude/skills/tailwind-patterns/SKILL.md +465 -465
- package/template/.claude/skills/test-coverage/SKILL.md +467 -467
- package/template/.claude/skills/trpc-api/SKILL.md +434 -434
- package/template/.claude/skills/typescript-strict/SKILL.md +367 -367
- package/template/.claude/skills/zod-validation/SKILL.md +403 -403
- package/template/CLAUDE.md +117 -117
|
@@ -1,824 +1,914 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* Stop Validator Hook - Complete Validation System
|
|
4
|
-
*
|
|
5
|
-
* THIS HOOK BLOCKS TASK COMPLETION IF ANY OF THESE CONDITIONS FAIL:
|
|
6
|
-
*
|
|
7
|
-
* 1. BRANCH CHECK: Must be on 'main' branch (work must be merged)
|
|
8
|
-
* 2. GIT TREE CHECK: Working tree must be clean (no uncommitted changes)
|
|
9
|
-
* 3. CLAUDE.MD CHECK: Must be updated with session changes
|
|
10
|
-
* 4. CLAUDE.MD STRUCTURE: Must have required sections
|
|
11
|
-
* 5. CLAUDE.MD SIZE: Must not exceed 40,000 characters
|
|
12
|
-
* 6. DOCUMENTATION CHECK: All source files must be documented
|
|
13
|
-
*
|
|
14
|
-
* ERROR MESSAGES ARE DESCRIPTIVE: They guide the agent on exactly what to do.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { execSync } from 'child_process';
|
|
18
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
19
|
-
import { join, basename, extname } from 'path';
|
|
20
|
-
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// CONFIGURATION
|
|
23
|
-
// ============================================================================
|
|
24
|
-
|
|
25
|
-
const PROJECT_DIR = process.env['CLAUDE_PROJECT_DIR'] || process.cwd();
|
|
26
|
-
const CLAUDE_MD_PATH = join(PROJECT_DIR, 'CLAUDE.md');
|
|
27
|
-
const MAX_CHARACTERS = 40000;
|
|
28
|
-
|
|
29
|
-
const IGNORE_DIRS = new Set([
|
|
30
|
-
'.next',
|
|
31
|
-
'node_modules',
|
|
32
|
-
'dist',
|
|
33
|
-
'build',
|
|
34
|
-
'coverage',
|
|
35
|
-
'.git',
|
|
36
|
-
'__pycache__',
|
|
37
|
-
'.turbo',
|
|
38
|
-
'.cache',
|
|
39
|
-
'.husky',
|
|
40
|
-
'packages',
|
|
41
|
-
]);
|
|
42
|
-
|
|
43
|
-
const IGNORE_PATTERNS = [
|
|
44
|
-
'.lock',
|
|
45
|
-
'.log',
|
|
46
|
-
'.map',
|
|
47
|
-
'.min.js',
|
|
48
|
-
'.min.css',
|
|
49
|
-
'package-lock.json',
|
|
50
|
-
'bun.lockb',
|
|
51
|
-
'.DS_Store',
|
|
52
|
-
'Thumbs.db',
|
|
53
|
-
];
|
|
54
|
-
|
|
55
|
-
const DOC_EXTENSIONS = new Set(['.md', '.mdx', '.txt', '.rst']);
|
|
56
|
-
|
|
57
|
-
const SOURCE_EXTENSIONS = new Set([
|
|
58
|
-
'.ts',
|
|
59
|
-
'.tsx',
|
|
60
|
-
'.js',
|
|
61
|
-
'.jsx',
|
|
62
|
-
'.py',
|
|
63
|
-
'.go',
|
|
64
|
-
'.rs',
|
|
65
|
-
'.java',
|
|
66
|
-
'.kt',
|
|
67
|
-
'.swift',
|
|
68
|
-
'.vue',
|
|
69
|
-
'.svelte',
|
|
70
|
-
]);
|
|
71
|
-
|
|
72
|
-
// Required sections in CLAUDE.md
|
|
73
|
-
const REQUIRED_SECTIONS = [
|
|
74
|
-
{ pattern: /^# .+/m, name: 'Project Title (H1)' },
|
|
75
|
-
{ pattern: /^## Last Change/m, name: 'Last Change' },
|
|
76
|
-
{ pattern: /^## 30 Seconds Overview/m, name: '30 Seconds Overview' },
|
|
77
|
-
{ pattern: /^## Stack/m, name: 'Stack' },
|
|
78
|
-
{ pattern: /^## Architecture/m, name: 'Architecture' },
|
|
79
|
-
];
|
|
80
|
-
|
|
81
|
-
// ============================================================================
|
|
82
|
-
// HELPER FUNCTIONS
|
|
83
|
-
// ============================================================================
|
|
84
|
-
|
|
85
|
-
function shouldIgnoreFile(filePath: string): boolean {
|
|
86
|
-
const parts = filePath.split(/[/\\]/);
|
|
87
|
-
for (const part of parts) {
|
|
88
|
-
if (IGNORE_DIRS.has(part)) return true;
|
|
89
|
-
}
|
|
90
|
-
for (const pattern of IGNORE_PATTERNS) {
|
|
91
|
-
if (filePath.includes(pattern)) return true;
|
|
92
|
-
}
|
|
93
|
-
return false;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function isSourceFile(filePath: string): boolean {
|
|
97
|
-
const ext = extname(filePath);
|
|
98
|
-
return SOURCE_EXTENSIONS.has(ext) && !shouldIgnoreFile(filePath);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
function getCurrentBranch(): string {
|
|
102
|
-
try {
|
|
103
|
-
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
104
|
-
cwd: PROJECT_DIR,
|
|
105
|
-
encoding: 'utf8',
|
|
106
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
-
}).trim();
|
|
108
|
-
} catch {
|
|
109
|
-
return 'unknown';
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function getModifiedFiles(): string[] {
|
|
114
|
-
try {
|
|
115
|
-
const staged = execSync('git diff --name-only --cached', {
|
|
116
|
-
cwd: PROJECT_DIR,
|
|
117
|
-
encoding: 'utf8',
|
|
118
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
119
|
-
})
|
|
120
|
-
.trim()
|
|
121
|
-
.split('\n')
|
|
122
|
-
.filter(Boolean);
|
|
123
|
-
|
|
124
|
-
const unstaged = execSync('git diff --name-only', {
|
|
125
|
-
cwd: PROJECT_DIR,
|
|
126
|
-
encoding: 'utf8',
|
|
127
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
|
-
})
|
|
129
|
-
.trim()
|
|
130
|
-
.split('\n')
|
|
131
|
-
.filter(Boolean);
|
|
132
|
-
|
|
133
|
-
const untracked = execSync('git ls-files --others --exclude-standard', {
|
|
134
|
-
cwd: PROJECT_DIR,
|
|
135
|
-
encoding: 'utf8',
|
|
136
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
137
|
-
})
|
|
138
|
-
.trim()
|
|
139
|
-
.split('\n')
|
|
140
|
-
.filter(Boolean);
|
|
141
|
-
|
|
142
|
-
return [...new Set([...staged, ...unstaged, ...untracked])].filter(Boolean);
|
|
143
|
-
} catch {
|
|
144
|
-
return [];
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function* walkDir(dir: string): Generator<string> {
|
|
149
|
-
if (!existsSync(dir)) return;
|
|
150
|
-
const entries = readdirSync(dir);
|
|
151
|
-
for (const entry of entries) {
|
|
152
|
-
const fullPath = join(dir, entry);
|
|
153
|
-
try {
|
|
154
|
-
const stat = statSync(fullPath);
|
|
155
|
-
if (stat.isDirectory()) {
|
|
156
|
-
yield* walkDir(fullPath);
|
|
157
|
-
} else if (stat.isFile()) {
|
|
158
|
-
yield fullPath;
|
|
159
|
-
}
|
|
160
|
-
} catch {
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function searchInDocs(filePath: string): boolean {
|
|
167
|
-
const fileName = basename(filePath);
|
|
168
|
-
const fileStem = basename(filePath, extname(filePath));
|
|
169
|
-
|
|
170
|
-
const docDirs = [
|
|
171
|
-
join(PROJECT_DIR, 'docs'),
|
|
172
|
-
join(PROJECT_DIR, '.claude', 'skills', 'codebase-knowledge', 'domains'),
|
|
173
|
-
];
|
|
174
|
-
|
|
175
|
-
for (const docDir of docDirs) {
|
|
176
|
-
if (!existsSync(docDir)) continue;
|
|
177
|
-
for (const docFile of walkDir(docDir)) {
|
|
178
|
-
const ext = extname(docFile);
|
|
179
|
-
if (!DOC_EXTENSIONS.has(ext)) continue;
|
|
180
|
-
try {
|
|
181
|
-
const content = readFileSync(docFile, 'utf8');
|
|
182
|
-
if (
|
|
183
|
-
content.includes(fileName) ||
|
|
184
|
-
content.includes(fileStem) ||
|
|
185
|
-
content.includes(filePath)
|
|
186
|
-
) {
|
|
187
|
-
return true;
|
|
188
|
-
}
|
|
189
|
-
} catch {
|
|
190
|
-
continue;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
return false;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// ============================================================================
|
|
198
|
-
// VALIDATION FUNCTIONS
|
|
199
|
-
// ============================================================================
|
|
200
|
-
|
|
201
|
-
interface ValidationError {
|
|
202
|
-
type: string;
|
|
203
|
-
message: string;
|
|
204
|
-
action: string;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
function validateBranch(currentBranch: string, modifiedFiles: string[]): ValidationError | null {
|
|
208
|
-
const isMainBranch = currentBranch === 'main' || currentBranch === 'master';
|
|
209
|
-
|
|
210
|
-
// Case 1: On feature branch with changes - need to complete workflow
|
|
211
|
-
if (!isMainBranch && modifiedFiles.length > 0) {
|
|
212
|
-
return {
|
|
213
|
-
type: 'FEATURE_BRANCH_NOT_MERGED',
|
|
214
|
-
message: `Currently on branch '${currentBranch}' with uncommitted changes. The task cannot complete until work is merged to main.`,
|
|
215
|
-
action: `
|
|
216
|
-
================================================================================
|
|
217
|
-
COMPLETE THE GIT WORKFLOW BEFORE TASK COMPLETION
|
|
218
|
-
================================================================================
|
|
219
|
-
|
|
220
|
-
You are on branch '${currentBranch}' with ${modifiedFiles.length} modified file(s).
|
|
221
|
-
|
|
222
|
-
REQUIRED STEPS (execute in order):
|
|
223
|
-
|
|
224
|
-
1. STAGE all changes:
|
|
225
|
-
git add -A
|
|
226
|
-
|
|
227
|
-
2. COMMIT with conventional message:
|
|
228
|
-
git commit -m "feat: description of changes"
|
|
229
|
-
|
|
230
|
-
3. SWITCH to main:
|
|
231
|
-
git checkout main
|
|
232
|
-
|
|
233
|
-
4. MERGE branch:
|
|
234
|
-
git merge ${currentBranch}
|
|
235
|
-
|
|
236
|
-
5. SYNC with remote (if exists):
|
|
237
|
-
git pull origin main --rebase || true
|
|
238
|
-
git push origin main || true
|
|
239
|
-
|
|
240
|
-
6. DELETE branch (cleanup):
|
|
241
|
-
git branch -d ${currentBranch}
|
|
242
|
-
|
|
243
|
-
THEN the task can complete. The stop hook will verify main branch + clean tree.
|
|
244
|
-
================================================================================`,
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Case 2: On feature branch with clean tree - just need to merge
|
|
249
|
-
if (!isMainBranch && modifiedFiles.length === 0) {
|
|
250
|
-
return {
|
|
251
|
-
type: 'NOT_ON_MAIN_BRANCH',
|
|
252
|
-
message: `Currently on branch '${currentBranch}'. Task completion requires being on 'main'.`,
|
|
253
|
-
action: `
|
|
254
|
-
================================================================================
|
|
255
|
-
MERGE TO MAIN BRANCH
|
|
256
|
-
================================================================================
|
|
257
|
-
|
|
258
|
-
The working tree is clean but you're on branch '${currentBranch}'.
|
|
259
|
-
|
|
260
|
-
REQUIRED STEPS:
|
|
261
|
-
|
|
262
|
-
1. SWITCH to main:
|
|
263
|
-
git checkout main
|
|
264
|
-
|
|
265
|
-
2. MERGE branch:
|
|
266
|
-
git merge ${currentBranch}
|
|
267
|
-
|
|
268
|
-
3. SYNC with remote (if exists):
|
|
269
|
-
git pull origin main --rebase || true
|
|
270
|
-
git push origin main || true
|
|
271
|
-
|
|
272
|
-
4. DELETE branch (cleanup):
|
|
273
|
-
git branch -d ${currentBranch}
|
|
274
|
-
|
|
275
|
-
IMPORTANT: Task completion is BLOCKED until you are on 'main' with a clean tree.
|
|
276
|
-
================================================================================`,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
// Case 3: On main with changes - FORBIDDEN
|
|
281
|
-
if (isMainBranch && modifiedFiles.length > 0) {
|
|
282
|
-
const fileList = modifiedFiles
|
|
283
|
-
.slice(0, 10)
|
|
284
|
-
.map((f) => ` - ${f}`)
|
|
285
|
-
.join('\n');
|
|
286
|
-
return {
|
|
287
|
-
type: 'DIRECT_MAIN_COMMIT_FORBIDDEN',
|
|
288
|
-
message: `CRITICAL: Attempting to work directly on '${currentBranch}' branch with changes!`,
|
|
289
|
-
action: `
|
|
290
|
-
================================================================================
|
|
291
|
-
FORBIDDEN: DIRECT COMMITS TO MAIN
|
|
292
|
-
================================================================================
|
|
293
|
-
|
|
294
|
-
You have ${modifiedFiles.length} modified file(s) on main branch:
|
|
295
|
-
${fileList}${modifiedFiles.length > 10 ? '\n ... and more' : ''}
|
|
296
|
-
|
|
297
|
-
ALL work MUST be done on feature branches. This is MANDATORY.
|
|
298
|
-
|
|
299
|
-
REQUIRED STEPS:
|
|
300
|
-
|
|
301
|
-
1. CREATE a feature branch:
|
|
302
|
-
git checkout -b feature/your-feature-name
|
|
303
|
-
(or fix/, refactor/, chore/, test/ as appropriate)
|
|
304
|
-
|
|
305
|
-
2. CONTINUE your work on the new branch
|
|
306
|
-
|
|
307
|
-
3. When done, merge back to main
|
|
308
|
-
|
|
309
|
-
NEVER commit directly to main. The stop hook will BLOCK this.
|
|
310
|
-
================================================================================`,
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return null; // All good - on main with clean tree
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
function validateGitTree(modifiedFiles: string[]): ValidationError | null {
|
|
318
|
-
if (modifiedFiles.length === 0) return null;
|
|
319
|
-
|
|
320
|
-
const fileList = modifiedFiles
|
|
321
|
-
.slice(0, 15)
|
|
322
|
-
.map((f) => ` - ${f}`)
|
|
323
|
-
.join('\n');
|
|
324
|
-
|
|
325
|
-
return {
|
|
326
|
-
type: 'GIT_TREE_NOT_CLEAN',
|
|
327
|
-
message: `Git working tree is not clean. Found ${modifiedFiles.length} modified/untracked file(s).`,
|
|
328
|
-
action: `
|
|
329
|
-
================================================================================
|
|
330
|
-
GIT TREE MUST BE CLEAN FOR TASK COMPLETION
|
|
331
|
-
================================================================================
|
|
332
|
-
|
|
333
|
-
Modified files:
|
|
334
|
-
${fileList}${modifiedFiles.length > 15 ? '\n ... and more' : ''}
|
|
335
|
-
|
|
336
|
-
The task cannot complete with uncommitted work.
|
|
337
|
-
|
|
338
|
-
OPTIONS:
|
|
339
|
-
|
|
340
|
-
1. COMMIT the changes (recommended):
|
|
341
|
-
git add -A
|
|
342
|
-
git commit -m "type: description"
|
|
343
|
-
git push
|
|
344
|
-
|
|
345
|
-
2. STASH for later:
|
|
346
|
-
git stash push -m "WIP: description"
|
|
347
|
-
|
|
348
|
-
3. DISCARD changes (use with caution):
|
|
349
|
-
git checkout -- .
|
|
350
|
-
git clean -fd
|
|
351
|
-
|
|
352
|
-
After cleaning the tree, the stop hook will pass.
|
|
353
|
-
================================================================================`,
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
function validateClaudeMdExists(): ValidationError | null {
|
|
358
|
-
if (!existsSync(CLAUDE_MD_PATH)) {
|
|
359
|
-
return {
|
|
360
|
-
type: 'CLAUDE_MD_MISSING',
|
|
361
|
-
message: 'CLAUDE.md file not found at project root.',
|
|
362
|
-
action: `
|
|
363
|
-
================================================================================
|
|
364
|
-
CLAUDE.MD IS REQUIRED
|
|
365
|
-
================================================================================
|
|
366
|
-
|
|
367
|
-
The project MUST have a CLAUDE.md file at the root with these sections:
|
|
368
|
-
|
|
369
|
-
# Project Name
|
|
370
|
-
|
|
371
|
-
## Last Change
|
|
372
|
-
**Branch:** branch-name
|
|
373
|
-
**Date:** YYYY-MM-DD
|
|
374
|
-
**Summary:** What was done in this session
|
|
375
|
-
|
|
376
|
-
## 30 Seconds Overview
|
|
377
|
-
Quick description of what this project does.
|
|
378
|
-
|
|
379
|
-
## Stack
|
|
380
|
-
| Component | Technology |
|
|
381
|
-
|-----------|------------|
|
|
382
|
-
| Runtime | Bun |
|
|
383
|
-
| Language | TypeScript |
|
|
384
|
-
| Database | MongoDB |
|
|
385
|
-
|
|
386
|
-
## Architecture
|
|
387
|
-
Project structure and key directories.
|
|
388
|
-
|
|
389
|
-
CREATE this file before the task can complete.
|
|
390
|
-
================================================================================`,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
return null;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
function validateClaudeMdSize(): ValidationError | null {
|
|
397
|
-
if (!existsSync(CLAUDE_MD_PATH)) return null;
|
|
398
|
-
|
|
399
|
-
const content = readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
400
|
-
if (content.length <= MAX_CHARACTERS) return null;
|
|
401
|
-
|
|
402
|
-
const excess = content.length - MAX_CHARACTERS;
|
|
403
|
-
|
|
404
|
-
return {
|
|
405
|
-
type: 'CLAUDE_MD_SIZE_EXCEEDED',
|
|
406
|
-
message: `CLAUDE.md exceeds 40,000 character limit by ${excess} characters (current: ${content.length}).`,
|
|
407
|
-
action: `
|
|
408
|
-
================================================================================
|
|
409
|
-
CLAUDE.MD MUST BE COMPACTED (MAX 40,000 CHARACTERS)
|
|
410
|
-
================================================================================
|
|
411
|
-
|
|
412
|
-
Current size: ${content.length} characters
|
|
413
|
-
Maximum allowed: ${MAX_CHARACTERS} characters
|
|
414
|
-
Excess: ${excess} characters
|
|
415
|
-
|
|
416
|
-
COMPACTION RULES (what to keep vs remove):
|
|
417
|
-
|
|
418
|
-
KEEP (critical):
|
|
419
|
-
- # Project Title
|
|
420
|
-
- ## Last Change (only the MOST RECENT)
|
|
421
|
-
- ## 30 Seconds Overview
|
|
422
|
-
- ## Stack
|
|
423
|
-
- ## Architecture
|
|
424
|
-
- ## Critical Rules
|
|
425
|
-
- ## FORBIDDEN Actions
|
|
426
|
-
- ## Quality Gates
|
|
427
|
-
|
|
428
|
-
REMOVE/CONDENSE:
|
|
429
|
-
- Verbose explanations (use bullet points)
|
|
430
|
-
- Duplicate information
|
|
431
|
-
- Old/outdated sections
|
|
432
|
-
- Long code examples (keep minimal)
|
|
433
|
-
- Multiple "Last Change" entries (keep only latest)
|
|
434
|
-
|
|
435
|
-
After editing, verify: wc -m CLAUDE.md
|
|
436
|
-
================================================================================`,
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
function
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
.
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
console.log(JSON.stringify(result));
|
|
796
|
-
process.exit(0);
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
//
|
|
800
|
-
const
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop Validator Hook - Complete Validation System
|
|
4
|
+
*
|
|
5
|
+
* THIS HOOK BLOCKS TASK COMPLETION IF ANY OF THESE CONDITIONS FAIL:
|
|
6
|
+
*
|
|
7
|
+
* 1. BRANCH CHECK: Must be on 'main' branch (work must be merged)
|
|
8
|
+
* 2. GIT TREE CHECK: Working tree must be clean (no uncommitted changes)
|
|
9
|
+
* 3. CLAUDE.MD CHECK: Must be updated with session changes
|
|
10
|
+
* 4. CLAUDE.MD STRUCTURE: Must have required sections
|
|
11
|
+
* 5. CLAUDE.MD SIZE: Must not exceed 40,000 characters
|
|
12
|
+
* 6. DOCUMENTATION CHECK: All source files must be documented
|
|
13
|
+
*
|
|
14
|
+
* ERROR MESSAGES ARE DESCRIPTIVE: They guide the agent on exactly what to do.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { execSync } from 'child_process';
|
|
18
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'fs';
|
|
19
|
+
import { join, basename, extname } from 'path';
|
|
20
|
+
|
|
21
|
+
// ============================================================================
|
|
22
|
+
// CONFIGURATION
|
|
23
|
+
// ============================================================================
|
|
24
|
+
|
|
25
|
+
const PROJECT_DIR = process.env['CLAUDE_PROJECT_DIR'] || process.cwd();
|
|
26
|
+
const CLAUDE_MD_PATH = join(PROJECT_DIR, 'CLAUDE.md');
|
|
27
|
+
const MAX_CHARACTERS = 40000;
|
|
28
|
+
|
|
29
|
+
const IGNORE_DIRS = new Set([
|
|
30
|
+
'.next',
|
|
31
|
+
'node_modules',
|
|
32
|
+
'dist',
|
|
33
|
+
'build',
|
|
34
|
+
'coverage',
|
|
35
|
+
'.git',
|
|
36
|
+
'__pycache__',
|
|
37
|
+
'.turbo',
|
|
38
|
+
'.cache',
|
|
39
|
+
'.husky',
|
|
40
|
+
'packages',
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
const IGNORE_PATTERNS = [
|
|
44
|
+
'.lock',
|
|
45
|
+
'.log',
|
|
46
|
+
'.map',
|
|
47
|
+
'.min.js',
|
|
48
|
+
'.min.css',
|
|
49
|
+
'package-lock.json',
|
|
50
|
+
'bun.lockb',
|
|
51
|
+
'.DS_Store',
|
|
52
|
+
'Thumbs.db',
|
|
53
|
+
];
|
|
54
|
+
|
|
55
|
+
const DOC_EXTENSIONS = new Set(['.md', '.mdx', '.txt', '.rst']);
|
|
56
|
+
|
|
57
|
+
const SOURCE_EXTENSIONS = new Set([
|
|
58
|
+
'.ts',
|
|
59
|
+
'.tsx',
|
|
60
|
+
'.js',
|
|
61
|
+
'.jsx',
|
|
62
|
+
'.py',
|
|
63
|
+
'.go',
|
|
64
|
+
'.rs',
|
|
65
|
+
'.java',
|
|
66
|
+
'.kt',
|
|
67
|
+
'.swift',
|
|
68
|
+
'.vue',
|
|
69
|
+
'.svelte',
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
// Required sections in CLAUDE.md
|
|
73
|
+
const REQUIRED_SECTIONS = [
|
|
74
|
+
{ pattern: /^# .+/m, name: 'Project Title (H1)' },
|
|
75
|
+
{ pattern: /^## Last Change/m, name: 'Last Change' },
|
|
76
|
+
{ pattern: /^## 30 Seconds Overview/m, name: '30 Seconds Overview' },
|
|
77
|
+
{ pattern: /^## Stack/m, name: 'Stack' },
|
|
78
|
+
{ pattern: /^## Architecture/m, name: 'Architecture' },
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
// ============================================================================
|
|
82
|
+
// HELPER FUNCTIONS
|
|
83
|
+
// ============================================================================
|
|
84
|
+
|
|
85
|
+
function shouldIgnoreFile(filePath: string): boolean {
|
|
86
|
+
const parts = filePath.split(/[/\\]/);
|
|
87
|
+
for (const part of parts) {
|
|
88
|
+
if (IGNORE_DIRS.has(part)) return true;
|
|
89
|
+
}
|
|
90
|
+
for (const pattern of IGNORE_PATTERNS) {
|
|
91
|
+
if (filePath.includes(pattern)) return true;
|
|
92
|
+
}
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isSourceFile(filePath: string): boolean {
|
|
97
|
+
const ext = extname(filePath);
|
|
98
|
+
return SOURCE_EXTENSIONS.has(ext) && !shouldIgnoreFile(filePath);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getCurrentBranch(): string {
|
|
102
|
+
try {
|
|
103
|
+
return execSync('git rev-parse --abbrev-ref HEAD', {
|
|
104
|
+
cwd: PROJECT_DIR,
|
|
105
|
+
encoding: 'utf8',
|
|
106
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
107
|
+
}).trim();
|
|
108
|
+
} catch {
|
|
109
|
+
return 'unknown';
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function getModifiedFiles(): string[] {
|
|
114
|
+
try {
|
|
115
|
+
const staged = execSync('git diff --name-only --cached', {
|
|
116
|
+
cwd: PROJECT_DIR,
|
|
117
|
+
encoding: 'utf8',
|
|
118
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
119
|
+
})
|
|
120
|
+
.trim()
|
|
121
|
+
.split('\n')
|
|
122
|
+
.filter(Boolean);
|
|
123
|
+
|
|
124
|
+
const unstaged = execSync('git diff --name-only', {
|
|
125
|
+
cwd: PROJECT_DIR,
|
|
126
|
+
encoding: 'utf8',
|
|
127
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
128
|
+
})
|
|
129
|
+
.trim()
|
|
130
|
+
.split('\n')
|
|
131
|
+
.filter(Boolean);
|
|
132
|
+
|
|
133
|
+
const untracked = execSync('git ls-files --others --exclude-standard', {
|
|
134
|
+
cwd: PROJECT_DIR,
|
|
135
|
+
encoding: 'utf8',
|
|
136
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
137
|
+
})
|
|
138
|
+
.trim()
|
|
139
|
+
.split('\n')
|
|
140
|
+
.filter(Boolean);
|
|
141
|
+
|
|
142
|
+
return [...new Set([...staged, ...unstaged, ...untracked])].filter(Boolean);
|
|
143
|
+
} catch {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function* walkDir(dir: string): Generator<string> {
|
|
149
|
+
if (!existsSync(dir)) return;
|
|
150
|
+
const entries = readdirSync(dir);
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const fullPath = join(dir, entry);
|
|
153
|
+
try {
|
|
154
|
+
const stat = statSync(fullPath);
|
|
155
|
+
if (stat.isDirectory()) {
|
|
156
|
+
yield* walkDir(fullPath);
|
|
157
|
+
} else if (stat.isFile()) {
|
|
158
|
+
yield fullPath;
|
|
159
|
+
}
|
|
160
|
+
} catch {
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function searchInDocs(filePath: string): boolean {
|
|
167
|
+
const fileName = basename(filePath);
|
|
168
|
+
const fileStem = basename(filePath, extname(filePath));
|
|
169
|
+
|
|
170
|
+
const docDirs = [
|
|
171
|
+
join(PROJECT_DIR, 'docs'),
|
|
172
|
+
join(PROJECT_DIR, '.claude', 'skills', 'codebase-knowledge', 'domains'),
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
for (const docDir of docDirs) {
|
|
176
|
+
if (!existsSync(docDir)) continue;
|
|
177
|
+
for (const docFile of walkDir(docDir)) {
|
|
178
|
+
const ext = extname(docFile);
|
|
179
|
+
if (!DOC_EXTENSIONS.has(ext)) continue;
|
|
180
|
+
try {
|
|
181
|
+
const content = readFileSync(docFile, 'utf8');
|
|
182
|
+
if (
|
|
183
|
+
content.includes(fileName) ||
|
|
184
|
+
content.includes(fileStem) ||
|
|
185
|
+
content.includes(filePath)
|
|
186
|
+
) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
} catch {
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// ============================================================================
|
|
198
|
+
// VALIDATION FUNCTIONS
|
|
199
|
+
// ============================================================================
|
|
200
|
+
|
|
201
|
+
interface ValidationError {
|
|
202
|
+
type: string;
|
|
203
|
+
message: string;
|
|
204
|
+
action: string;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function validateBranch(currentBranch: string, modifiedFiles: string[]): ValidationError | null {
|
|
208
|
+
const isMainBranch = currentBranch === 'main' || currentBranch === 'master';
|
|
209
|
+
|
|
210
|
+
// Case 1: On feature branch with changes - need to complete workflow
|
|
211
|
+
if (!isMainBranch && modifiedFiles.length > 0) {
|
|
212
|
+
return {
|
|
213
|
+
type: 'FEATURE_BRANCH_NOT_MERGED',
|
|
214
|
+
message: `Currently on branch '${currentBranch}' with uncommitted changes. The task cannot complete until work is merged to main.`,
|
|
215
|
+
action: `
|
|
216
|
+
================================================================================
|
|
217
|
+
COMPLETE THE GIT WORKFLOW BEFORE TASK COMPLETION
|
|
218
|
+
================================================================================
|
|
219
|
+
|
|
220
|
+
You are on branch '${currentBranch}' with ${modifiedFiles.length} modified file(s).
|
|
221
|
+
|
|
222
|
+
REQUIRED STEPS (execute in order):
|
|
223
|
+
|
|
224
|
+
1. STAGE all changes:
|
|
225
|
+
git add -A
|
|
226
|
+
|
|
227
|
+
2. COMMIT with conventional message:
|
|
228
|
+
git commit -m "feat: description of changes"
|
|
229
|
+
|
|
230
|
+
3. SWITCH to main:
|
|
231
|
+
git checkout main
|
|
232
|
+
|
|
233
|
+
4. MERGE branch:
|
|
234
|
+
git merge ${currentBranch}
|
|
235
|
+
|
|
236
|
+
5. SYNC with remote (if exists):
|
|
237
|
+
git pull origin main --rebase || true
|
|
238
|
+
git push origin main || true
|
|
239
|
+
|
|
240
|
+
6. DELETE branch (cleanup):
|
|
241
|
+
git branch -d ${currentBranch}
|
|
242
|
+
|
|
243
|
+
THEN the task can complete. The stop hook will verify main branch + clean tree.
|
|
244
|
+
================================================================================`,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Case 2: On feature branch with clean tree - just need to merge
|
|
249
|
+
if (!isMainBranch && modifiedFiles.length === 0) {
|
|
250
|
+
return {
|
|
251
|
+
type: 'NOT_ON_MAIN_BRANCH',
|
|
252
|
+
message: `Currently on branch '${currentBranch}'. Task completion requires being on 'main'.`,
|
|
253
|
+
action: `
|
|
254
|
+
================================================================================
|
|
255
|
+
MERGE TO MAIN BRANCH
|
|
256
|
+
================================================================================
|
|
257
|
+
|
|
258
|
+
The working tree is clean but you're on branch '${currentBranch}'.
|
|
259
|
+
|
|
260
|
+
REQUIRED STEPS:
|
|
261
|
+
|
|
262
|
+
1. SWITCH to main:
|
|
263
|
+
git checkout main
|
|
264
|
+
|
|
265
|
+
2. MERGE branch:
|
|
266
|
+
git merge ${currentBranch}
|
|
267
|
+
|
|
268
|
+
3. SYNC with remote (if exists):
|
|
269
|
+
git pull origin main --rebase || true
|
|
270
|
+
git push origin main || true
|
|
271
|
+
|
|
272
|
+
4. DELETE branch (cleanup):
|
|
273
|
+
git branch -d ${currentBranch}
|
|
274
|
+
|
|
275
|
+
IMPORTANT: Task completion is BLOCKED until you are on 'main' with a clean tree.
|
|
276
|
+
================================================================================`,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// Case 3: On main with changes - FORBIDDEN
|
|
281
|
+
if (isMainBranch && modifiedFiles.length > 0) {
|
|
282
|
+
const fileList = modifiedFiles
|
|
283
|
+
.slice(0, 10)
|
|
284
|
+
.map((f) => ` - ${f}`)
|
|
285
|
+
.join('\n');
|
|
286
|
+
return {
|
|
287
|
+
type: 'DIRECT_MAIN_COMMIT_FORBIDDEN',
|
|
288
|
+
message: `CRITICAL: Attempting to work directly on '${currentBranch}' branch with changes!`,
|
|
289
|
+
action: `
|
|
290
|
+
================================================================================
|
|
291
|
+
FORBIDDEN: DIRECT COMMITS TO MAIN
|
|
292
|
+
================================================================================
|
|
293
|
+
|
|
294
|
+
You have ${modifiedFiles.length} modified file(s) on main branch:
|
|
295
|
+
${fileList}${modifiedFiles.length > 10 ? '\n ... and more' : ''}
|
|
296
|
+
|
|
297
|
+
ALL work MUST be done on feature branches. This is MANDATORY.
|
|
298
|
+
|
|
299
|
+
REQUIRED STEPS:
|
|
300
|
+
|
|
301
|
+
1. CREATE a feature branch:
|
|
302
|
+
git checkout -b feature/your-feature-name
|
|
303
|
+
(or fix/, refactor/, chore/, test/ as appropriate)
|
|
304
|
+
|
|
305
|
+
2. CONTINUE your work on the new branch
|
|
306
|
+
|
|
307
|
+
3. When done, merge back to main
|
|
308
|
+
|
|
309
|
+
NEVER commit directly to main. The stop hook will BLOCK this.
|
|
310
|
+
================================================================================`,
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return null; // All good - on main with clean tree
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
function validateGitTree(modifiedFiles: string[]): ValidationError | null {
|
|
318
|
+
if (modifiedFiles.length === 0) return null;
|
|
319
|
+
|
|
320
|
+
const fileList = modifiedFiles
|
|
321
|
+
.slice(0, 15)
|
|
322
|
+
.map((f) => ` - ${f}`)
|
|
323
|
+
.join('\n');
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
type: 'GIT_TREE_NOT_CLEAN',
|
|
327
|
+
message: `Git working tree is not clean. Found ${modifiedFiles.length} modified/untracked file(s).`,
|
|
328
|
+
action: `
|
|
329
|
+
================================================================================
|
|
330
|
+
GIT TREE MUST BE CLEAN FOR TASK COMPLETION
|
|
331
|
+
================================================================================
|
|
332
|
+
|
|
333
|
+
Modified files:
|
|
334
|
+
${fileList}${modifiedFiles.length > 15 ? '\n ... and more' : ''}
|
|
335
|
+
|
|
336
|
+
The task cannot complete with uncommitted work.
|
|
337
|
+
|
|
338
|
+
OPTIONS:
|
|
339
|
+
|
|
340
|
+
1. COMMIT the changes (recommended):
|
|
341
|
+
git add -A
|
|
342
|
+
git commit -m "type: description"
|
|
343
|
+
git push
|
|
344
|
+
|
|
345
|
+
2. STASH for later:
|
|
346
|
+
git stash push -m "WIP: description"
|
|
347
|
+
|
|
348
|
+
3. DISCARD changes (use with caution):
|
|
349
|
+
git checkout -- .
|
|
350
|
+
git clean -fd
|
|
351
|
+
|
|
352
|
+
After cleaning the tree, the stop hook will pass.
|
|
353
|
+
================================================================================`,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function validateClaudeMdExists(): ValidationError | null {
|
|
358
|
+
if (!existsSync(CLAUDE_MD_PATH)) {
|
|
359
|
+
return {
|
|
360
|
+
type: 'CLAUDE_MD_MISSING',
|
|
361
|
+
message: 'CLAUDE.md file not found at project root.',
|
|
362
|
+
action: `
|
|
363
|
+
================================================================================
|
|
364
|
+
CLAUDE.MD IS REQUIRED
|
|
365
|
+
================================================================================
|
|
366
|
+
|
|
367
|
+
The project MUST have a CLAUDE.md file at the root with these sections:
|
|
368
|
+
|
|
369
|
+
# Project Name
|
|
370
|
+
|
|
371
|
+
## Last Change
|
|
372
|
+
**Branch:** branch-name
|
|
373
|
+
**Date:** YYYY-MM-DD
|
|
374
|
+
**Summary:** What was done in this session
|
|
375
|
+
|
|
376
|
+
## 30 Seconds Overview
|
|
377
|
+
Quick description of what this project does.
|
|
378
|
+
|
|
379
|
+
## Stack
|
|
380
|
+
| Component | Technology |
|
|
381
|
+
|-----------|------------|
|
|
382
|
+
| Runtime | Bun |
|
|
383
|
+
| Language | TypeScript |
|
|
384
|
+
| Database | MongoDB |
|
|
385
|
+
|
|
386
|
+
## Architecture
|
|
387
|
+
Project structure and key directories.
|
|
388
|
+
|
|
389
|
+
CREATE this file before the task can complete.
|
|
390
|
+
================================================================================`,
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
return null;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function validateClaudeMdSize(): ValidationError | null {
|
|
397
|
+
if (!existsSync(CLAUDE_MD_PATH)) return null;
|
|
398
|
+
|
|
399
|
+
const content = readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
400
|
+
if (content.length <= MAX_CHARACTERS) return null;
|
|
401
|
+
|
|
402
|
+
const excess = content.length - MAX_CHARACTERS;
|
|
403
|
+
|
|
404
|
+
return {
|
|
405
|
+
type: 'CLAUDE_MD_SIZE_EXCEEDED',
|
|
406
|
+
message: `CLAUDE.md exceeds 40,000 character limit by ${excess} characters (current: ${content.length}).`,
|
|
407
|
+
action: `
|
|
408
|
+
================================================================================
|
|
409
|
+
CLAUDE.MD MUST BE COMPACTED (MAX 40,000 CHARACTERS)
|
|
410
|
+
================================================================================
|
|
411
|
+
|
|
412
|
+
Current size: ${content.length} characters
|
|
413
|
+
Maximum allowed: ${MAX_CHARACTERS} characters
|
|
414
|
+
Excess: ${excess} characters
|
|
415
|
+
|
|
416
|
+
COMPACTION RULES (what to keep vs remove):
|
|
417
|
+
|
|
418
|
+
KEEP (critical):
|
|
419
|
+
- # Project Title
|
|
420
|
+
- ## Last Change (only the MOST RECENT)
|
|
421
|
+
- ## 30 Seconds Overview
|
|
422
|
+
- ## Stack
|
|
423
|
+
- ## Architecture
|
|
424
|
+
- ## Critical Rules
|
|
425
|
+
- ## FORBIDDEN Actions
|
|
426
|
+
- ## Quality Gates
|
|
427
|
+
|
|
428
|
+
REMOVE/CONDENSE:
|
|
429
|
+
- Verbose explanations (use bullet points)
|
|
430
|
+
- Duplicate information
|
|
431
|
+
- Old/outdated sections
|
|
432
|
+
- Long code examples (keep minimal)
|
|
433
|
+
- Multiple "Last Change" entries (keep only latest)
|
|
434
|
+
|
|
435
|
+
After editing, verify: wc -m CLAUDE.md
|
|
436
|
+
================================================================================`,
|
|
437
|
+
};
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function validateClaudeMdTemplateMerge(): ValidationError | null {
|
|
441
|
+
const templatePath = join(PROJECT_DIR, '.claude', 'CLAUDE.template.md');
|
|
442
|
+
|
|
443
|
+
// Only check if template exists (created by start-vibing when user has existing CLAUDE.md)
|
|
444
|
+
if (!existsSync(templatePath)) return null;
|
|
445
|
+
if (!existsSync(CLAUDE_MD_PATH)) return null;
|
|
446
|
+
|
|
447
|
+
const userContent = readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
448
|
+
const templateContent = readFileSync(templatePath, 'utf8');
|
|
449
|
+
|
|
450
|
+
// Check if user's CLAUDE.md is missing required sections
|
|
451
|
+
const missingSections: string[] = [];
|
|
452
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
453
|
+
if (!section.pattern.test(userContent)) {
|
|
454
|
+
missingSections.push(section.name);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// If user has all required sections, they're probably fine - skip merge suggestion
|
|
459
|
+
if (missingSections.length === 0) {
|
|
460
|
+
return null; // User's CLAUDE.md is well-structured, no merge needed
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
return {
|
|
464
|
+
type: 'CLAUDE_MD_TEMPLATE_MERGE_NEEDED',
|
|
465
|
+
message: `Your CLAUDE.md is missing ${missingSections.length} required section(s). A template with recommended structure is available.`,
|
|
466
|
+
action: `
|
|
467
|
+
================================================================================
|
|
468
|
+
SMART MERGE: COMBINE YOUR CLAUDE.MD WITH NEW TEMPLATE
|
|
469
|
+
================================================================================
|
|
470
|
+
|
|
471
|
+
Your CLAUDE.md is missing these sections:
|
|
472
|
+
${missingSections.map((s) => ` - ${s}`).join('\n')}
|
|
473
|
+
|
|
474
|
+
A template file exists at: .claude/CLAUDE.template.md
|
|
475
|
+
|
|
476
|
+
REQUIRED ACTION:
|
|
477
|
+
Perform a smart merge that:
|
|
478
|
+
1. KEEPS all your existing project-specific content
|
|
479
|
+
2. ADDS the missing required sections from the template
|
|
480
|
+
3. PRESERVES your custom rules and configurations
|
|
481
|
+
|
|
482
|
+
MERGE STEPS:
|
|
483
|
+
1. Read your current CLAUDE.md content
|
|
484
|
+
2. Read .claude/CLAUDE.template.md for structure reference
|
|
485
|
+
3. Create a merged version that:
|
|
486
|
+
- Keeps your project title, overview, and custom rules
|
|
487
|
+
- Adds missing sections (${missingSections.join(', ')})
|
|
488
|
+
- Follows the template structure
|
|
489
|
+
4. Write the merged content to CLAUDE.md
|
|
490
|
+
5. Delete .claude/CLAUDE.template.md after successful merge
|
|
491
|
+
|
|
492
|
+
IMPORTANT: Do NOT just overwrite - intelligently MERGE the content!
|
|
493
|
+
After merging, delete the template file to mark merge as complete.
|
|
494
|
+
================================================================================`,
|
|
495
|
+
};
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function validateClaudeMdStructure(): ValidationError | null {
|
|
499
|
+
if (!existsSync(CLAUDE_MD_PATH)) return null;
|
|
500
|
+
|
|
501
|
+
const content = readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
502
|
+
const missingSections: string[] = [];
|
|
503
|
+
|
|
504
|
+
for (const section of REQUIRED_SECTIONS) {
|
|
505
|
+
if (!section.pattern.test(content)) {
|
|
506
|
+
missingSections.push(section.name);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (missingSections.length === 0) return null;
|
|
511
|
+
|
|
512
|
+
return {
|
|
513
|
+
type: 'CLAUDE_MD_MISSING_SECTIONS',
|
|
514
|
+
message: `CLAUDE.md is missing required sections: ${missingSections.join(', ')}`,
|
|
515
|
+
action: `
|
|
516
|
+
================================================================================
|
|
517
|
+
CLAUDE.MD MISSING REQUIRED SECTIONS
|
|
518
|
+
================================================================================
|
|
519
|
+
|
|
520
|
+
Missing sections:
|
|
521
|
+
${missingSections.map((s) => ` - ${s}`).join('\n')}
|
|
522
|
+
|
|
523
|
+
REQUIRED STRUCTURE:
|
|
524
|
+
|
|
525
|
+
# Project Name <- H1 title
|
|
526
|
+
|
|
527
|
+
## Last Change <- ONLY the most recent change
|
|
528
|
+
**Branch:** feature/xxx
|
|
529
|
+
**Date:** YYYY-MM-DD
|
|
530
|
+
**Summary:** What was done
|
|
531
|
+
|
|
532
|
+
## 30 Seconds Overview <- Quick project description
|
|
533
|
+
|
|
534
|
+
## Stack <- Technology table
|
|
535
|
+
|
|
536
|
+
## Architecture <- Project structure
|
|
537
|
+
|
|
538
|
+
ADD the missing sections before the task can complete.
|
|
539
|
+
================================================================================`,
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function validateClaudeMdLastChange(): ValidationError | null {
|
|
544
|
+
if (!existsSync(CLAUDE_MD_PATH)) return null;
|
|
545
|
+
|
|
546
|
+
const content = readFileSync(CLAUDE_MD_PATH, 'utf8');
|
|
547
|
+
const lastChangeMatch = content.match(/## Last Change\n([\s\S]*?)(?=\n## |$)/);
|
|
548
|
+
|
|
549
|
+
if (!lastChangeMatch) return null; // Covered by structure check
|
|
550
|
+
|
|
551
|
+
const lastChangeContent = lastChangeMatch[1].trim();
|
|
552
|
+
|
|
553
|
+
// Check for meaningful content
|
|
554
|
+
if (lastChangeContent.length < 50) {
|
|
555
|
+
return {
|
|
556
|
+
type: 'CLAUDE_MD_LAST_CHANGE_EMPTY',
|
|
557
|
+
message: 'The "Last Change" section exists but lacks sufficient content.',
|
|
558
|
+
action: `
|
|
559
|
+
================================================================================
|
|
560
|
+
UPDATE "LAST CHANGE" SECTION
|
|
561
|
+
================================================================================
|
|
562
|
+
|
|
563
|
+
The "## Last Change" section must contain:
|
|
564
|
+
|
|
565
|
+
**Branch:** the-branch-name-used
|
|
566
|
+
**Date:** ${new Date().toISOString().split('T')[0]}
|
|
567
|
+
**Summary:** 1-2 sentences describing what was changed/implemented
|
|
568
|
+
|
|
569
|
+
Example:
|
|
570
|
+
## Last Change
|
|
571
|
+
|
|
572
|
+
**Branch:** feature/add-auth
|
|
573
|
+
**Date:** 2025-01-05
|
|
574
|
+
**Summary:** Implemented JWT authentication with refresh tokens and session management.
|
|
575
|
+
|
|
576
|
+
IMPORTANT: This section should ONLY contain the LAST change, not a history.
|
|
577
|
+
================================================================================`,
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Check for multiple Last Change sections (stacking is forbidden)
|
|
582
|
+
const multipleChanges = content.match(/## Last Change/g);
|
|
583
|
+
if (multipleChanges && multipleChanges.length > 1) {
|
|
584
|
+
return {
|
|
585
|
+
type: 'CLAUDE_MD_STACKED_CHANGES',
|
|
586
|
+
message: `Found ${multipleChanges.length} "## Last Change" sections. Only ONE is allowed.`,
|
|
587
|
+
action: `
|
|
588
|
+
================================================================================
|
|
589
|
+
REMOVE STACKED CHANGES - KEEP ONLY THE LATEST
|
|
590
|
+
================================================================================
|
|
591
|
+
|
|
592
|
+
Rule: CLAUDE.md should only have ONE "## Last Change" section.
|
|
593
|
+
Previous changes belong in git history, not in the documentation.
|
|
594
|
+
|
|
595
|
+
Found ${multipleChanges.length} instances of "## Last Change".
|
|
596
|
+
|
|
597
|
+
ACTION: Remove all but the most recent "## Last Change" section.
|
|
598
|
+
|
|
599
|
+
This keeps the file focused and within the 40k character limit.
|
|
600
|
+
================================================================================`,
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return null;
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function validateClaudeMdUpdated(modifiedFiles: string[]): ValidationError | null {
|
|
608
|
+
// Files that don't require CLAUDE.md update (auto-generated, locks, etc)
|
|
609
|
+
const EXEMPT_PATTERNS = [
|
|
610
|
+
'bun.lockb',
|
|
611
|
+
'package-lock.json',
|
|
612
|
+
'yarn.lock',
|
|
613
|
+
'pnpm-lock.yaml',
|
|
614
|
+
'.DS_Store',
|
|
615
|
+
'Thumbs.db',
|
|
616
|
+
/^packages\/start-vibing\/dist\//,
|
|
617
|
+
/^packages\/start-vibing\/template\//,
|
|
618
|
+
];
|
|
619
|
+
|
|
620
|
+
// Filter out exempt files
|
|
621
|
+
const significantFiles = modifiedFiles.filter((f) => {
|
|
622
|
+
// Always exempt CLAUDE.md itself
|
|
623
|
+
if (f === 'CLAUDE.md' || f.endsWith('/CLAUDE.md') || f.endsWith('\\CLAUDE.md')) {
|
|
624
|
+
return false;
|
|
625
|
+
}
|
|
626
|
+
// Check exempt patterns
|
|
627
|
+
for (const pattern of EXEMPT_PATTERNS) {
|
|
628
|
+
if (typeof pattern === 'string') {
|
|
629
|
+
if (f === pattern || f.includes(pattern)) return false;
|
|
630
|
+
} else if (pattern.test(f)) {
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
return true;
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
// If no significant files modified, no need to check
|
|
638
|
+
if (significantFiles.length === 0) return null;
|
|
639
|
+
|
|
640
|
+
// Check if CLAUDE.md is in the modified files
|
|
641
|
+
const claudeMdModified = modifiedFiles.some(
|
|
642
|
+
(f) => f === 'CLAUDE.md' || f.endsWith('/CLAUDE.md') || f.endsWith('\\CLAUDE.md')
|
|
643
|
+
);
|
|
644
|
+
|
|
645
|
+
if (claudeMdModified) return null;
|
|
646
|
+
|
|
647
|
+
return {
|
|
648
|
+
type: 'CLAUDE_MD_NOT_UPDATED',
|
|
649
|
+
message: `${significantFiles.length} file(s) were modified but CLAUDE.md was not updated.`,
|
|
650
|
+
action: `
|
|
651
|
+
================================================================================
|
|
652
|
+
UPDATE CLAUDE.MD WITH SESSION CHANGES (MANDATORY)
|
|
653
|
+
================================================================================
|
|
654
|
+
|
|
655
|
+
You modified files but did not update CLAUDE.md.
|
|
656
|
+
|
|
657
|
+
Modified files:
|
|
658
|
+
${significantFiles
|
|
659
|
+
.slice(0, 10)
|
|
660
|
+
.map((f) => ` - ${f}`)
|
|
661
|
+
.join('\n')}${significantFiles.length > 10 ? '\n ... and more' : ''}
|
|
662
|
+
|
|
663
|
+
REQUIRED UPDATES TO CLAUDE.MD:
|
|
664
|
+
|
|
665
|
+
1. Update "## Last Change" section:
|
|
666
|
+
**Branch:** current-branch-name
|
|
667
|
+
**Date:** ${new Date().toISOString().split('T')[0]}
|
|
668
|
+
**Summary:** What you implemented/fixed
|
|
669
|
+
|
|
670
|
+
2. If architecture changed:
|
|
671
|
+
Update "## Architecture" section
|
|
672
|
+
|
|
673
|
+
3. If new patterns/rules were established:
|
|
674
|
+
Add to appropriate section
|
|
675
|
+
|
|
676
|
+
4. If user mentioned preferences or corrections:
|
|
677
|
+
Add as rules in relevant section
|
|
678
|
+
|
|
679
|
+
CONTEXT SYNTHESIS:
|
|
680
|
+
Think about what the user asked and what you learned.
|
|
681
|
+
Capture important decisions and patterns for the next session.
|
|
682
|
+
|
|
683
|
+
The stop hook will BLOCK until CLAUDE.md is updated.
|
|
684
|
+
================================================================================`,
|
|
685
|
+
};
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function validateDocumentation(sourceFiles: string[]): ValidationError | null {
|
|
689
|
+
if (sourceFiles.length === 0) return null;
|
|
690
|
+
|
|
691
|
+
const undocumented: string[] = [];
|
|
692
|
+
for (const filePath of sourceFiles) {
|
|
693
|
+
if (!searchInDocs(filePath)) {
|
|
694
|
+
undocumented.push(filePath);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (undocumented.length === 0) return null;
|
|
699
|
+
|
|
700
|
+
const fileList = undocumented
|
|
701
|
+
.slice(0, 15)
|
|
702
|
+
.map((f) => ` - ${f}`)
|
|
703
|
+
.join('\n');
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
type: 'SOURCE_FILES_NOT_DOCUMENTED',
|
|
707
|
+
message: `${undocumented.length} source file(s) are not documented.`,
|
|
708
|
+
action: `
|
|
709
|
+
================================================================================
|
|
710
|
+
DOCUMENT ALL MODIFIED SOURCE FILES (MANDATORY)
|
|
711
|
+
================================================================================
|
|
712
|
+
|
|
713
|
+
Undocumented files:
|
|
714
|
+
${fileList}${undocumented.length > 15 ? '\n ... and more' : ''}
|
|
715
|
+
|
|
716
|
+
REQUIRED ACTION:
|
|
717
|
+
|
|
718
|
+
Run the documenter agent to update documentation:
|
|
719
|
+
|
|
720
|
+
Task(subagent_type="documenter", prompt="Update documentation for all modified files")
|
|
721
|
+
|
|
722
|
+
The documenter will:
|
|
723
|
+
1. Detect changed files via git diff
|
|
724
|
+
2. Update domain files in .claude/skills/codebase-knowledge/domains/
|
|
725
|
+
3. Update docs/ as needed
|
|
726
|
+
4. Ensure all modified files are mentioned in documentation
|
|
727
|
+
|
|
728
|
+
A file is considered documented if its name appears in:
|
|
729
|
+
- docs/ folder
|
|
730
|
+
- .claude/skills/codebase-knowledge/domains/ folder
|
|
731
|
+
|
|
732
|
+
The stop hook will BLOCK until all source files are documented.
|
|
733
|
+
================================================================================`,
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
// ============================================================================
|
|
738
|
+
// MAIN HOOK LOGIC
|
|
739
|
+
// ============================================================================
|
|
740
|
+
|
|
741
|
+
interface HookInput {
|
|
742
|
+
stop_hook_active?: boolean;
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
interface HookResult {
|
|
746
|
+
decision: 'approve' | 'block';
|
|
747
|
+
reason: string;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
async function readStdinWithTimeout(timeoutMs: number): Promise<string> {
|
|
751
|
+
return new Promise((resolve) => {
|
|
752
|
+
const timeout = setTimeout(() => {
|
|
753
|
+
process.stdin.destroy();
|
|
754
|
+
resolve('{}');
|
|
755
|
+
}, timeoutMs);
|
|
756
|
+
|
|
757
|
+
let data = '';
|
|
758
|
+
process.stdin.setEncoding('utf8');
|
|
759
|
+
process.stdin.on('data', (chunk: string) => {
|
|
760
|
+
data += chunk;
|
|
761
|
+
});
|
|
762
|
+
process.stdin.on('end', () => {
|
|
763
|
+
clearTimeout(timeout);
|
|
764
|
+
resolve(data || '{}');
|
|
765
|
+
});
|
|
766
|
+
process.stdin.on('error', () => {
|
|
767
|
+
clearTimeout(timeout);
|
|
768
|
+
resolve('{}');
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
if (process.stdin.readableEnded) {
|
|
772
|
+
clearTimeout(timeout);
|
|
773
|
+
resolve('{}');
|
|
774
|
+
}
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
async function main(): Promise<void> {
|
|
779
|
+
let hookInput: HookInput = {};
|
|
780
|
+
try {
|
|
781
|
+
const stdin = await readStdinWithTimeout(1000);
|
|
782
|
+
if (stdin && stdin.trim()) {
|
|
783
|
+
hookInput = JSON.parse(stdin);
|
|
784
|
+
}
|
|
785
|
+
} catch {
|
|
786
|
+
hookInput = {};
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
// Prevent infinite loops
|
|
790
|
+
if (hookInput.stop_hook_active) {
|
|
791
|
+
const result: HookResult = {
|
|
792
|
+
decision: 'approve',
|
|
793
|
+
reason: 'Stop hook cycle detected, allowing exit',
|
|
794
|
+
};
|
|
795
|
+
console.log(JSON.stringify(result));
|
|
796
|
+
process.exit(0);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
// Gather state
|
|
800
|
+
const currentBranch = getCurrentBranch();
|
|
801
|
+
const modifiedFiles = getModifiedFiles();
|
|
802
|
+
const sourceFiles = modifiedFiles.filter(isSourceFile);
|
|
803
|
+
const isMainBranch = currentBranch === 'main' || currentBranch === 'master';
|
|
804
|
+
const isCleanTree = modifiedFiles.length === 0;
|
|
805
|
+
|
|
806
|
+
// Run all validations
|
|
807
|
+
const errors: ValidationError[] = [];
|
|
808
|
+
|
|
809
|
+
// Validation order matters - most critical first
|
|
810
|
+
const branchError = validateBranch(currentBranch, modifiedFiles);
|
|
811
|
+
if (branchError) errors.push(branchError);
|
|
812
|
+
|
|
813
|
+
// Only check these if we're close to completion (on main or clean tree)
|
|
814
|
+
if (isMainBranch || isCleanTree) {
|
|
815
|
+
const treeError = validateGitTree(modifiedFiles);
|
|
816
|
+
if (treeError) errors.push(treeError);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
const claudeMdExistsError = validateClaudeMdExists();
|
|
820
|
+
if (claudeMdExistsError) errors.push(claudeMdExistsError);
|
|
821
|
+
|
|
822
|
+
if (!claudeMdExistsError) {
|
|
823
|
+
// Check if there's a template pending merge (from start-vibing install)
|
|
824
|
+
const templateMergeError = validateClaudeMdTemplateMerge();
|
|
825
|
+
if (templateMergeError) errors.push(templateMergeError);
|
|
826
|
+
|
|
827
|
+
const sizeError = validateClaudeMdSize();
|
|
828
|
+
if (sizeError) errors.push(sizeError);
|
|
829
|
+
|
|
830
|
+
const structureError = validateClaudeMdStructure();
|
|
831
|
+
if (structureError) errors.push(structureError);
|
|
832
|
+
|
|
833
|
+
const lastChangeError = validateClaudeMdLastChange();
|
|
834
|
+
if (lastChangeError) errors.push(lastChangeError);
|
|
835
|
+
|
|
836
|
+
const updatedError = validateClaudeMdUpdated(modifiedFiles);
|
|
837
|
+
if (updatedError) errors.push(updatedError);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
const docError = validateDocumentation(sourceFiles);
|
|
841
|
+
if (docError) errors.push(docError);
|
|
842
|
+
|
|
843
|
+
// ============================================================================
|
|
844
|
+
// OUTPUT RESULTS
|
|
845
|
+
// ============================================================================
|
|
846
|
+
|
|
847
|
+
if (errors.length > 0) {
|
|
848
|
+
let output = `
|
|
849
|
+
################################################################################
|
|
850
|
+
# STOP VALIDATOR - TASK COMPLETION BLOCKED #
|
|
851
|
+
################################################################################
|
|
852
|
+
|
|
853
|
+
${errors.length} validation(s) failed. You MUST fix these before the task can complete.
|
|
854
|
+
|
|
855
|
+
`;
|
|
856
|
+
|
|
857
|
+
for (let i = 0; i < errors.length; i++) {
|
|
858
|
+
const err = errors[i];
|
|
859
|
+
output += `
|
|
860
|
+
--------------------------------------------------------------------------------
|
|
861
|
+
ERROR ${i + 1}/${errors.length}: ${err.type}
|
|
862
|
+
--------------------------------------------------------------------------------
|
|
863
|
+
|
|
864
|
+
${err.message}
|
|
865
|
+
|
|
866
|
+
${err.action}
|
|
867
|
+
`;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
output += `
|
|
871
|
+
################################################################################
|
|
872
|
+
# FIX ALL ERRORS ABOVE BEFORE TASK CAN COMPLETE #
|
|
873
|
+
################################################################################
|
|
874
|
+
|
|
875
|
+
SYNTHESIS REMINDER:
|
|
876
|
+
Before completing, ask yourself:
|
|
877
|
+
- Did the user mention any preferences I should remember?
|
|
878
|
+
- Did I learn any patterns that should be documented?
|
|
879
|
+
- Were there any corrections I should add as rules?
|
|
880
|
+
|
|
881
|
+
Update CLAUDE.md with any learnings from this session.
|
|
882
|
+
`;
|
|
883
|
+
|
|
884
|
+
const result: HookResult = { decision: 'block', reason: output.trim() };
|
|
885
|
+
console.log(JSON.stringify(result));
|
|
886
|
+
process.exit(0);
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
// All validations passed
|
|
890
|
+
const successOutput = `
|
|
891
|
+
################################################################################
|
|
892
|
+
# STOP VALIDATOR - ALL CHECKS PASSED #
|
|
893
|
+
################################################################################
|
|
894
|
+
|
|
895
|
+
Branch: ${currentBranch}
|
|
896
|
+
Tree: ${isCleanTree ? 'Clean' : `${modifiedFiles.length} modified files`}
|
|
897
|
+
CLAUDE.md: Valid
|
|
898
|
+
|
|
899
|
+
All validations passed. Task may complete.
|
|
900
|
+
################################################################################
|
|
901
|
+
`;
|
|
902
|
+
|
|
903
|
+
const result: HookResult = { decision: 'approve', reason: successOutput.trim() };
|
|
904
|
+
console.log(JSON.stringify(result));
|
|
905
|
+
process.exit(0);
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
main().catch((err) => {
|
|
909
|
+
console.error('Hook error:', err);
|
|
910
|
+
// On error, allow to continue to not block user
|
|
911
|
+
const result: HookResult = { decision: 'approve', reason: 'Hook error, allowing by default' };
|
|
912
|
+
console.log(JSON.stringify(result));
|
|
913
|
+
process.exit(0);
|
|
914
|
+
});
|