start-vibing 2.0.10 → 2.0.12

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