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.
Files changed (131) hide show
  1. package/README.md +177 -177
  2. package/dist/cli.js +19 -2
  3. package/package.json +42 -42
  4. package/template/.claude/CLAUDE.md +174 -174
  5. package/template/.claude/agents/01-orchestration/agent-selector.md +130 -130
  6. package/template/.claude/agents/01-orchestration/checkpoint-manager.md +142 -142
  7. package/template/.claude/agents/01-orchestration/context-manager.md +138 -138
  8. package/template/.claude/agents/01-orchestration/error-recovery.md +182 -182
  9. package/template/.claude/agents/01-orchestration/orchestrator.md +114 -114
  10. package/template/.claude/agents/01-orchestration/parallel-coordinator.md +141 -141
  11. package/template/.claude/agents/01-orchestration/task-decomposer.md +121 -121
  12. package/template/.claude/agents/01-orchestration/workflow-router.md +114 -114
  13. package/template/.claude/agents/02-typescript/bun-runtime-expert.md +197 -197
  14. package/template/.claude/agents/02-typescript/esm-resolver.md +193 -193
  15. package/template/.claude/agents/02-typescript/import-alias-enforcer.md +158 -158
  16. package/template/.claude/agents/02-typescript/ts-generics-helper.md +183 -183
  17. package/template/.claude/agents/02-typescript/ts-migration-helper.md +238 -238
  18. package/template/.claude/agents/02-typescript/ts-strict-checker.md +180 -180
  19. package/template/.claude/agents/02-typescript/ts-types-analyzer.md +199 -199
  20. package/template/.claude/agents/02-typescript/type-definition-writer.md +187 -187
  21. package/template/.claude/agents/02-typescript/zod-schema-designer.md +212 -212
  22. package/template/.claude/agents/02-typescript/zod-validator.md +158 -158
  23. package/template/.claude/agents/03-testing/playwright-assertions.md +265 -265
  24. package/template/.claude/agents/03-testing/playwright-e2e.md +247 -247
  25. package/template/.claude/agents/03-testing/playwright-fixtures.md +234 -234
  26. package/template/.claude/agents/03-testing/playwright-multi-viewport.md +256 -256
  27. package/template/.claude/agents/03-testing/playwright-page-objects.md +247 -247
  28. package/template/.claude/agents/03-testing/test-cleanup-manager.md +248 -248
  29. package/template/.claude/agents/03-testing/test-data-generator.md +254 -254
  30. package/template/.claude/agents/03-testing/tester-integration.md +278 -278
  31. package/template/.claude/agents/03-testing/tester-unit.md +207 -207
  32. package/template/.claude/agents/03-testing/vitest-config.md +287 -287
  33. package/template/.claude/agents/04-docker/container-health.md +255 -255
  34. package/template/.claude/agents/04-docker/deployment-validator.md +225 -225
  35. package/template/.claude/agents/04-docker/docker-compose-designer.md +281 -281
  36. package/template/.claude/agents/04-docker/docker-env-manager.md +235 -235
  37. package/template/.claude/agents/04-docker/docker-multi-stage.md +241 -241
  38. package/template/.claude/agents/04-docker/dockerfile-optimizer.md +208 -208
  39. package/template/.claude/agents/05-database/database-seeder.md +273 -273
  40. package/template/.claude/agents/05-database/mongodb-query-optimizer.md +230 -230
  41. package/template/.claude/agents/05-database/mongoose-aggregation.md +306 -306
  42. package/template/.claude/agents/05-database/mongoose-index-optimizer.md +182 -182
  43. package/template/.claude/agents/05-database/mongoose-schema-designer.md +267 -267
  44. package/template/.claude/agents/06-security/auth-session-validator.md +68 -68
  45. package/template/.claude/agents/06-security/input-sanitizer.md +80 -80
  46. package/template/.claude/agents/06-security/owasp-checker.md +97 -97
  47. package/template/.claude/agents/06-security/permission-auditor.md +100 -100
  48. package/template/.claude/agents/06-security/security-auditor.md +84 -84
  49. package/template/.claude/agents/06-security/sensitive-data-scanner.md +83 -83
  50. package/template/.claude/agents/07-documentation/api-documenter.md +136 -136
  51. package/template/.claude/agents/07-documentation/changelog-manager.md +105 -105
  52. package/template/.claude/agents/07-documentation/documenter.md +76 -76
  53. package/template/.claude/agents/07-documentation/domain-updater.md +81 -81
  54. package/template/.claude/agents/07-documentation/jsdoc-generator.md +114 -114
  55. package/template/.claude/agents/07-documentation/readme-generator.md +135 -135
  56. package/template/.claude/agents/08-git/branch-manager.md +58 -58
  57. package/template/.claude/agents/08-git/commit-manager.md +63 -63
  58. package/template/.claude/agents/08-git/pr-creator.md +76 -76
  59. package/template/.claude/agents/09-quality/code-reviewer.md +71 -71
  60. package/template/.claude/agents/09-quality/quality-checker.md +67 -67
  61. package/template/.claude/agents/10-research/best-practices-finder.md +89 -89
  62. package/template/.claude/agents/10-research/competitor-analyzer.md +106 -106
  63. package/template/.claude/agents/10-research/pattern-researcher.md +93 -93
  64. package/template/.claude/agents/10-research/research-cache-manager.md +76 -76
  65. package/template/.claude/agents/10-research/research-web.md +98 -98
  66. package/template/.claude/agents/10-research/tech-evaluator.md +101 -101
  67. package/template/.claude/agents/11-ui-ux/accessibility-auditor.md +136 -136
  68. package/template/.claude/agents/11-ui-ux/design-system-enforcer.md +125 -125
  69. package/template/.claude/agents/11-ui-ux/skeleton-generator.md +118 -118
  70. package/template/.claude/agents/11-ui-ux/ui-desktop.md +132 -132
  71. package/template/.claude/agents/11-ui-ux/ui-mobile.md +98 -98
  72. package/template/.claude/agents/11-ui-ux/ui-tablet.md +110 -110
  73. package/template/.claude/agents/12-performance/api-latency-analyzer.md +156 -156
  74. package/template/.claude/agents/12-performance/bundle-analyzer.md +113 -113
  75. package/template/.claude/agents/12-performance/memory-leak-detector.md +137 -137
  76. package/template/.claude/agents/12-performance/performance-profiler.md +115 -115
  77. package/template/.claude/agents/12-performance/query-optimizer.md +124 -124
  78. package/template/.claude/agents/12-performance/render-optimizer.md +154 -154
  79. package/template/.claude/agents/13-debugging/build-error-fixer.md +207 -207
  80. package/template/.claude/agents/13-debugging/debugger.md +149 -149
  81. package/template/.claude/agents/13-debugging/error-stack-analyzer.md +141 -141
  82. package/template/.claude/agents/13-debugging/network-debugger.md +208 -208
  83. package/template/.claude/agents/13-debugging/runtime-error-fixer.md +181 -181
  84. package/template/.claude/agents/13-debugging/type-error-resolver.md +185 -185
  85. package/template/.claude/agents/14-validation/final-validator.md +93 -93
  86. package/template/.claude/agents/_backup/analyzer.md +134 -134
  87. package/template/.claude/agents/_backup/code-reviewer.md +279 -279
  88. package/template/.claude/agents/_backup/commit-manager.md +219 -219
  89. package/template/.claude/agents/_backup/debugger.md +280 -280
  90. package/template/.claude/agents/_backup/documenter.md +237 -237
  91. package/template/.claude/agents/_backup/domain-updater.md +197 -197
  92. package/template/.claude/agents/_backup/final-validator.md +169 -169
  93. package/template/.claude/agents/_backup/orchestrator.md +149 -149
  94. package/template/.claude/agents/_backup/performance.md +232 -232
  95. package/template/.claude/agents/_backup/quality-checker.md +240 -240
  96. package/template/.claude/agents/_backup/research.md +315 -315
  97. package/template/.claude/agents/_backup/security-auditor.md +192 -192
  98. package/template/.claude/agents/_backup/tester.md +566 -566
  99. package/template/.claude/agents/_backup/ui-ux-reviewer.md +247 -247
  100. package/template/.claude/config/README.md +30 -30
  101. package/template/.claude/config/mcp-config.json +344 -344
  102. package/template/.claude/config/project-config.json +53 -53
  103. package/template/.claude/config/quality-gates.json +46 -46
  104. package/template/.claude/config/security-rules.json +45 -45
  105. package/template/.claude/config/testing-config.json +164 -164
  106. package/template/.claude/hooks/SETUP.md +126 -126
  107. package/template/.claude/hooks/run-hook.ts +176 -176
  108. package/template/.claude/hooks/stop-validator.ts +914 -824
  109. package/template/.claude/hooks/user-prompt-submit.ts +886 -886
  110. package/template/.claude/scripts/mcp-quick-install.ts +151 -151
  111. package/template/.claude/scripts/setup-mcps.ts +651 -651
  112. package/template/.claude/settings.json +275 -275
  113. package/template/.claude/skills/bun-runtime/SKILL.md +430 -430
  114. package/template/.claude/skills/codebase-knowledge/domains/claude-system.md +431 -431
  115. package/template/.claude/skills/codebase-knowledge/domains/mcp-integration.md +295 -295
  116. package/template/.claude/skills/debugging-patterns/SKILL.md +485 -485
  117. package/template/.claude/skills/docker-patterns/SKILL.md +555 -555
  118. package/template/.claude/skills/git-workflow/SKILL.md +454 -454
  119. package/template/.claude/skills/mongoose-patterns/SKILL.md +499 -499
  120. package/template/.claude/skills/nextjs-app-router/SKILL.md +327 -327
  121. package/template/.claude/skills/performance-patterns/SKILL.md +547 -547
  122. package/template/.claude/skills/playwright-automation/SKILL.md +438 -438
  123. package/template/.claude/skills/react-patterns/SKILL.md +389 -389
  124. package/template/.claude/skills/research-cache/SKILL.md +222 -222
  125. package/template/.claude/skills/shadcn-ui/SKILL.md +511 -511
  126. package/template/.claude/skills/tailwind-patterns/SKILL.md +465 -465
  127. package/template/.claude/skills/test-coverage/SKILL.md +467 -467
  128. package/template/.claude/skills/trpc-api/SKILL.md +434 -434
  129. package/template/.claude/skills/typescript-strict/SKILL.md +367 -367
  130. package/template/.claude/skills/zod-validation/SKILL.md +403 -403
  131. 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 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
- // If no source files modified, no need to check
551
- const sourceFiles = modifiedFiles.filter(isSourceFile);
552
- if (sourceFiles.length === 0) return null;
553
-
554
- // Check if CLAUDE.md is in the modified files
555
- const claudeMdModified = modifiedFiles.some(
556
- (f) => f === 'CLAUDE.md' || f.endsWith('/CLAUDE.md') || f.endsWith('\\CLAUDE.md')
557
- );
558
-
559
- if (claudeMdModified) return null;
560
-
561
- return {
562
- type: 'CLAUDE_MD_NOT_UPDATED',
563
- message: `${sourceFiles.length} source file(s) were modified but CLAUDE.md was not updated.`,
564
- action: `
565
- ================================================================================
566
- UPDATE CLAUDE.MD WITH SESSION CHANGES (MANDATORY)
567
- ================================================================================
568
-
569
- You modified source files but did not update CLAUDE.md.
570
-
571
- Modified source files:
572
- ${sourceFiles
573
- .slice(0, 10)
574
- .map((f) => ` - ${f}`)
575
- .join('\n')}${sourceFiles.length > 10 ? '\n ... and more' : ''}
576
-
577
- REQUIRED UPDATES TO CLAUDE.MD:
578
-
579
- 1. Update "## Last Change" section:
580
- **Branch:** current-branch-name
581
- **Date:** ${new Date().toISOString().split('T')[0]}
582
- **Summary:** What you implemented/fixed
583
-
584
- 2. If architecture changed:
585
- Update "## Architecture" section
586
-
587
- 3. If new patterns/rules were established:
588
- Add to appropriate section
589
-
590
- 4. If user mentioned preferences or corrections:
591
- Add as rules in relevant section
592
-
593
- CONTEXT SYNTHESIS:
594
- Think about what the user asked and what you learned.
595
- Capture important decisions and patterns for the next session.
596
-
597
- The stop hook will BLOCK until CLAUDE.md is updated.
598
- ================================================================================`,
599
- };
600
- }
601
-
602
- function validateDocumentation(sourceFiles: string[]): ValidationError | null {
603
- if (sourceFiles.length === 0) return null;
604
-
605
- const undocumented: string[] = [];
606
- for (const filePath of sourceFiles) {
607
- if (!searchInDocs(filePath)) {
608
- undocumented.push(filePath);
609
- }
610
- }
611
-
612
- if (undocumented.length === 0) return null;
613
-
614
- const fileList = undocumented
615
- .slice(0, 15)
616
- .map((f) => ` - ${f}`)
617
- .join('\n');
618
-
619
- return {
620
- type: 'SOURCE_FILES_NOT_DOCUMENTED',
621
- message: `${undocumented.length} source file(s) are not documented.`,
622
- action: `
623
- ================================================================================
624
- DOCUMENT ALL MODIFIED SOURCE FILES (MANDATORY)
625
- ================================================================================
626
-
627
- Undocumented files:
628
- ${fileList}${undocumented.length > 15 ? '\n ... and more' : ''}
629
-
630
- REQUIRED ACTION:
631
-
632
- Run the documenter agent to update documentation:
633
-
634
- Task(subagent_type="documenter", prompt="Update documentation for all modified files")
635
-
636
- The documenter will:
637
- 1. Detect changed files via git diff
638
- 2. Update domain files in .claude/skills/codebase-knowledge/domains/
639
- 3. Update docs/ as needed
640
- 4. Ensure all modified files are mentioned in documentation
641
-
642
- A file is considered documented if its name appears in:
643
- - docs/ folder
644
- - .claude/skills/codebase-knowledge/domains/ folder
645
-
646
- The stop hook will BLOCK until all source files are documented.
647
- ================================================================================`,
648
- };
649
- }
650
-
651
- // ============================================================================
652
- // MAIN HOOK LOGIC
653
- // ============================================================================
654
-
655
- interface HookInput {
656
- stop_hook_active?: boolean;
657
- }
658
-
659
- interface HookResult {
660
- decision: 'approve' | 'block';
661
- reason: string;
662
- }
663
-
664
- async function readStdinWithTimeout(timeoutMs: number): Promise<string> {
665
- return new Promise((resolve) => {
666
- const timeout = setTimeout(() => {
667
- process.stdin.destroy();
668
- resolve('{}');
669
- }, timeoutMs);
670
-
671
- let data = '';
672
- process.stdin.setEncoding('utf8');
673
- process.stdin.on('data', (chunk: string) => {
674
- data += chunk;
675
- });
676
- process.stdin.on('end', () => {
677
- clearTimeout(timeout);
678
- resolve(data || '{}');
679
- });
680
- process.stdin.on('error', () => {
681
- clearTimeout(timeout);
682
- resolve('{}');
683
- });
684
-
685
- if (process.stdin.readableEnded) {
686
- clearTimeout(timeout);
687
- resolve('{}');
688
- }
689
- });
690
- }
691
-
692
- async function main(): Promise<void> {
693
- let hookInput: HookInput = {};
694
- try {
695
- const stdin = await readStdinWithTimeout(1000);
696
- if (stdin && stdin.trim()) {
697
- hookInput = JSON.parse(stdin);
698
- }
699
- } catch {
700
- hookInput = {};
701
- }
702
-
703
- // Prevent infinite loops
704
- if (hookInput.stop_hook_active) {
705
- const result: HookResult = {
706
- decision: 'approve',
707
- reason: 'Stop hook cycle detected, allowing exit',
708
- };
709
- console.log(JSON.stringify(result));
710
- process.exit(0);
711
- }
712
-
713
- // Gather state
714
- const currentBranch = getCurrentBranch();
715
- const modifiedFiles = getModifiedFiles();
716
- const sourceFiles = modifiedFiles.filter(isSourceFile);
717
- const isMainBranch = currentBranch === 'main' || currentBranch === 'master';
718
- const isCleanTree = modifiedFiles.length === 0;
719
-
720
- // Run all validations
721
- const errors: ValidationError[] = [];
722
-
723
- // Validation order matters - most critical first
724
- const branchError = validateBranch(currentBranch, modifiedFiles);
725
- if (branchError) errors.push(branchError);
726
-
727
- // Only check these if we're close to completion (on main or clean tree)
728
- if (isMainBranch || isCleanTree) {
729
- const treeError = validateGitTree(modifiedFiles);
730
- if (treeError) errors.push(treeError);
731
- }
732
-
733
- const claudeMdExistsError = validateClaudeMdExists();
734
- if (claudeMdExistsError) errors.push(claudeMdExistsError);
735
-
736
- if (!claudeMdExistsError) {
737
- const sizeError = validateClaudeMdSize();
738
- if (sizeError) errors.push(sizeError);
739
-
740
- const structureError = validateClaudeMdStructure();
741
- if (structureError) errors.push(structureError);
742
-
743
- const lastChangeError = validateClaudeMdLastChange();
744
- if (lastChangeError) errors.push(lastChangeError);
745
-
746
- const updatedError = validateClaudeMdUpdated(modifiedFiles);
747
- if (updatedError) errors.push(updatedError);
748
- }
749
-
750
- const docError = validateDocumentation(sourceFiles);
751
- if (docError) errors.push(docError);
752
-
753
- // ============================================================================
754
- // OUTPUT RESULTS
755
- // ============================================================================
756
-
757
- if (errors.length > 0) {
758
- let output = `
759
- ################################################################################
760
- # STOP VALIDATOR - TASK COMPLETION BLOCKED #
761
- ################################################################################
762
-
763
- ${errors.length} validation(s) failed. You MUST fix these before the task can complete.
764
-
765
- `;
766
-
767
- for (let i = 0; i < errors.length; i++) {
768
- const err = errors[i];
769
- output += `
770
- --------------------------------------------------------------------------------
771
- ERROR ${i + 1}/${errors.length}: ${err.type}
772
- --------------------------------------------------------------------------------
773
-
774
- ${err.message}
775
-
776
- ${err.action}
777
- `;
778
- }
779
-
780
- output += `
781
- ################################################################################
782
- # FIX ALL ERRORS ABOVE BEFORE TASK CAN COMPLETE #
783
- ################################################################################
784
-
785
- SYNTHESIS REMINDER:
786
- Before completing, ask yourself:
787
- - Did the user mention any preferences I should remember?
788
- - Did I learn any patterns that should be documented?
789
- - Were there any corrections I should add as rules?
790
-
791
- Update CLAUDE.md with any learnings from this session.
792
- `;
793
-
794
- const result: HookResult = { decision: 'block', reason: output.trim() };
795
- console.log(JSON.stringify(result));
796
- process.exit(0);
797
- }
798
-
799
- // All validations passed
800
- const successOutput = `
801
- ################################################################################
802
- # STOP VALIDATOR - ALL CHECKS PASSED #
803
- ################################################################################
804
-
805
- Branch: ${currentBranch}
806
- Tree: ${isCleanTree ? 'Clean' : `${modifiedFiles.length} modified files`}
807
- CLAUDE.md: Valid
808
-
809
- All validations passed. Task may complete.
810
- ################################################################################
811
- `;
812
-
813
- const result: HookResult = { decision: 'approve', reason: successOutput.trim() };
814
- console.log(JSON.stringify(result));
815
- process.exit(0);
816
- }
817
-
818
- main().catch((err) => {
819
- console.error('Hook error:', err);
820
- // On error, allow to continue to not block user
821
- const result: HookResult = { decision: 'approve', reason: 'Hook error, allowing by default' };
822
- console.log(JSON.stringify(result));
823
- process.exit(0);
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
+ });