start-vibing 2.0.44 → 2.0.46
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -13,6 +13,7 @@ The stop hook validates `/CLAUDE.md` before allowing task completion:
|
|
|
13
13
|
| Character limit | Max 40,000 chars |
|
|
14
14
|
| Required sections | Last Change, 30s Overview, Stack, Architecture |
|
|
15
15
|
| Last Change | Must be updated with session info (branch, date, summary) |
|
|
16
|
+
| Content depth | Relevant rule/flow sections must also be updated |
|
|
16
17
|
| No stacking | Only ONE Last Change section (latest only) |
|
|
17
18
|
| Branch | Must be on main (PR merged) |
|
|
18
19
|
| Git tree | Must be clean (no uncommitted changes) |
|
|
@@ -21,6 +22,27 @@ The stop hook validates `/CLAUDE.md` before allowing task completion:
|
|
|
21
22
|
|
|
22
23
|
**If not on main:** Complete PR workflow (commit, push, PR, merge, checkout main).
|
|
23
24
|
|
|
25
|
+
### Content Depth Rule (CLAUDE_MD_SHALLOW_UPDATE)
|
|
26
|
+
|
|
27
|
+
> **"Last Change" = WHAT was done. Other sections = HOW things work NOW. Both MUST be current.**
|
|
28
|
+
|
|
29
|
+
The stop-validator categorizes changed files and maps them to CLAUDE.md sections:
|
|
30
|
+
|
|
31
|
+
| Changed Files | Must Also Update |
|
|
32
|
+
|---------------|-----------------|
|
|
33
|
+
| API/CRUD routes | Critical Rules, HTTP Requests |
|
|
34
|
+
| UI components | UI Architecture, Component Organization |
|
|
35
|
+
| Pages/layouts | Architecture, Next.js App Router Patterns |
|
|
36
|
+
| Styling/aesthetics | UI Architecture, Design System |
|
|
37
|
+
| Auth/middleware | Critical Rules, Workflow |
|
|
38
|
+
| Database/models | Architecture, Critical Rules |
|
|
39
|
+
| Config files | Stack, Configuration |
|
|
40
|
+
| Hooks/scripts | Workflow, Stop Hook Validations |
|
|
41
|
+
|
|
42
|
+
If ONLY "Last Change" was modified but **significant** categorized source files changed, `CLAUDE_MD_SHALLOW_UPDATE` blocks completion.
|
|
43
|
+
|
|
44
|
+
**Exempt (minor changes):** < 3 categorized files AND < 30 lines changed AND no new files = Last Change only is OK.
|
|
45
|
+
|
|
24
46
|
---
|
|
25
47
|
|
|
26
48
|
## System Architecture
|
|
@@ -213,45 +235,139 @@ All implementations MUST:
|
|
|
213
235
|
|
|
214
236
|
---
|
|
215
237
|
|
|
216
|
-
##
|
|
238
|
+
## Claude 4.6 Best Practices
|
|
239
|
+
|
|
240
|
+
> **Source:** [Anthropic Claude 4 Best Practices](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/claude-4-best-practices)
|
|
241
|
+
|
|
242
|
+
### Model Selection
|
|
243
|
+
|
|
244
|
+
| Model | API ID | Cost (In/Out) | Best For |
|
|
245
|
+
|-------|--------|---------------|----------|
|
|
246
|
+
| **Opus 4.6** | `claude-opus-4-6` | $5/$25 MTok | Orchestrators, long-horizon tasks, 128K output |
|
|
247
|
+
| **Sonnet 4.6** | `claude-sonnet-4-6` | $3/$15 MTok | Daily coding, teammates (5x cheaper) |
|
|
248
|
+
| **Haiku 4.5** | `claude-haiku-4-5-20251001` | $1/$5 MTok | Classification, simple tasks |
|
|
217
249
|
|
|
218
250
|
### Effort Levels
|
|
219
251
|
|
|
220
|
-
| Level | Use
|
|
221
|
-
|
|
222
|
-
| `max` |
|
|
223
|
-
| `high` |
|
|
224
|
-
| `medium` |
|
|
225
|
-
| `low` | Subagents, high-volume
|
|
252
|
+
| Level | Behavior | Use Case |
|
|
253
|
+
|-------|----------|----------|
|
|
254
|
+
| `max` | Always thinks, no limits | Complex architecture (**Opus ONLY**) |
|
|
255
|
+
| `high` | Deep thinking (DEFAULT) | Agentic tasks, complex coding |
|
|
256
|
+
| `medium` | Moderate, may skip simple | Most workloads |
|
|
257
|
+
| `low` | Minimal thinking | Subagents, teammates, high-volume |
|
|
226
258
|
|
|
227
|
-
|
|
259
|
+
**CLI:** `/model` then arrow keys to adjust effort.
|
|
228
260
|
|
|
229
|
-
|
|
230
|
-
- **USE**: "Use this tool when it would enhance understanding"
|
|
231
|
-
- Keep prompts minimal - Opus 4.6 overthinks with verbose instructions
|
|
232
|
-
- Explicit instructions work better than implied expectations
|
|
261
|
+
### Adaptive Thinking (Replaces budget_tokens)
|
|
233
262
|
|
|
234
|
-
|
|
263
|
+
```typescript
|
|
264
|
+
// CORRECT for 4.6 models
|
|
265
|
+
thinking: { type: "adaptive" },
|
|
266
|
+
output_config: { effort: "medium" }
|
|
235
267
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
268
|
+
// DEPRECATED (will be removed)
|
|
269
|
+
thinking: { type: "enabled", budget_tokens: 10000 }
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
Adaptive thinking enables **interleaved thinking** — Claude reasons BETWEEN tool calls.
|
|
273
|
+
|
|
274
|
+
### Prompting Rules (CRITICAL)
|
|
275
|
+
|
|
276
|
+
**AVOID (causes overtriggering):**
|
|
277
|
+
- "CRITICAL: You MUST use..."
|
|
278
|
+
- "be thorough", "think carefully", "do not be lazy"
|
|
279
|
+
- "use the think tool to plan your approach"
|
|
280
|
+
|
|
281
|
+
**USE INSTEAD:**
|
|
282
|
+
- "Use this tool when..." (softer language)
|
|
283
|
+
- Remove anti-laziness prompts entirely
|
|
284
|
+
- Lower effort level instead of adding constraints
|
|
241
285
|
|
|
242
|
-
|
|
286
|
+
**Why:** Opus 4.6 does MORE upfront exploration than older models. Anti-laziness prompts cause runaway thinking.
|
|
243
287
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
288
|
+
### Tool Use Patterns
|
|
289
|
+
|
|
290
|
+
```text
|
|
291
|
+
# Claude will only SUGGEST:
|
|
292
|
+
"Can you suggest some changes?"
|
|
293
|
+
|
|
294
|
+
# Claude will IMPLEMENT:
|
|
295
|
+
"Change this function to improve performance."
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
For proactive action by default, add to system prompt:
|
|
299
|
+
```text
|
|
300
|
+
By default, implement changes rather than only suggesting them.
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### Subagent Control
|
|
304
|
+
|
|
305
|
+
Opus 4.6 has a **strong predilection for subagents** — may spawn them when direct grep/read is faster.
|
|
306
|
+
|
|
307
|
+
**Use subagents when:** Parallel tasks, isolated context, independent workstreams
|
|
308
|
+
**Work directly when:** Sequential operations, single-file edits, shared state needed
|
|
309
|
+
|
|
310
|
+
```text
|
|
311
|
+
# Add if seeing excessive subagent use:
|
|
312
|
+
For simple tasks, sequential operations, or single-file edits, work directly.
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
### Avoid Over-Engineering
|
|
316
|
+
|
|
317
|
+
Opus 4.6 tends to create extra files and unnecessary abstractions.
|
|
318
|
+
|
|
319
|
+
```text
|
|
320
|
+
# Add to prompt:
|
|
321
|
+
Avoid over-engineering. Only make changes directly requested.
|
|
322
|
+
Don't add features, docstrings, or error handling beyond what's asked.
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### Parallel Tool Calls
|
|
326
|
+
|
|
327
|
+
```text
|
|
328
|
+
# Add for maximum efficiency:
|
|
329
|
+
If calling multiple tools with no dependencies, make all calls in parallel.
|
|
330
|
+
```
|
|
248
331
|
|
|
249
332
|
### Cost Control
|
|
250
333
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
334
|
+
| Issue | Solution |
|
|
335
|
+
|-------|----------|
|
|
336
|
+
| Runaway thinking | Lower effort (not prompt constraints) |
|
|
337
|
+
| `stop_reason: "max_tokens"` | Increase `max_tokens` to 64K+ |
|
|
338
|
+
| High costs | Use `low` effort for subagents |
|
|
339
|
+
| Billing confusion | Billed for FULL thinking, not visible summary |
|
|
340
|
+
|
|
341
|
+
### Common Pitfalls
|
|
342
|
+
|
|
343
|
+
| Pitfall | Fix |
|
|
344
|
+
|---------|-----|
|
|
345
|
+
| `max` effort on Sonnet/Haiku | Error — Opus only |
|
|
346
|
+
| Prefilled responses | Deprecated in 4.6 |
|
|
347
|
+
| Multiple agents editing same file | Isolate files per agent |
|
|
348
|
+
| Aggressive prompts from older models | Remove "MUST", "CRITICAL" |
|
|
349
|
+
|
|
350
|
+
### API Examples
|
|
351
|
+
|
|
352
|
+
```typescript
|
|
353
|
+
// Standard agentic call
|
|
354
|
+
await client.messages.create({
|
|
355
|
+
model: "claude-opus-4-6",
|
|
356
|
+
max_tokens: 64000,
|
|
357
|
+
thinking: { type: "adaptive" },
|
|
358
|
+
output_config: { effort: "high" },
|
|
359
|
+
messages: [...]
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// Cost-optimized subagent
|
|
363
|
+
await client.messages.create({
|
|
364
|
+
model: "claude-sonnet-4-6",
|
|
365
|
+
max_tokens: 16000,
|
|
366
|
+
thinking: { type: "adaptive" },
|
|
367
|
+
output_config: { effort: "low" },
|
|
368
|
+
messages: [...]
|
|
369
|
+
});
|
|
370
|
+
```
|
|
255
371
|
|
|
256
372
|
---
|
|
257
373
|
|
|
@@ -54,6 +54,93 @@ const IGNORE_PATTERNS = [
|
|
|
54
54
|
|
|
55
55
|
const DOC_EXTENSIONS = new Set(['.md', '.mdx', '.txt', '.rst']);
|
|
56
56
|
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// FILE CHANGE CATEGORIES → CLAUDE.MD SECTIONS MAPPING
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
interface ChangeCategory {
|
|
62
|
+
name: string;
|
|
63
|
+
claudeMdSections: string[];
|
|
64
|
+
filePatterns: RegExp[];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const CHANGE_CATEGORIES: ChangeCategory[] = [
|
|
68
|
+
{
|
|
69
|
+
name: 'API/CRUD',
|
|
70
|
+
claudeMdSections: ['Critical Rules', 'HTTP Requests'],
|
|
71
|
+
filePatterns: [/\/api\//, /\/routers\//, /\/server\//, /\.route\./, /\.controller\./],
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'UI Components',
|
|
75
|
+
claudeMdSections: ['UI Architecture', 'Component Organization', 'Design System'],
|
|
76
|
+
filePatterns: [/\/components\//, /\/ui\//],
|
|
77
|
+
},
|
|
78
|
+
{
|
|
79
|
+
name: 'Pages/Layouts',
|
|
80
|
+
claudeMdSections: ['Next.js App Router Patterns', 'Architecture'],
|
|
81
|
+
filePatterns: [/\/app\/.*page\.tsx/, /\/app\/.*layout\.tsx/, /\/app\/.*loading\.tsx/],
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
name: 'Styling/Aesthetics',
|
|
85
|
+
claudeMdSections: ['UI Architecture', 'Design System', 'Design Trends'],
|
|
86
|
+
filePatterns: [/\.css$/, /globals\.css/, /theme/, /tailwind\.config/],
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: 'Auth/Middleware',
|
|
90
|
+
claudeMdSections: ['Critical Rules', 'Workflow'],
|
|
91
|
+
filePatterns: [/middleware/, /auth/, /session/],
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
name: 'Database/Models',
|
|
95
|
+
claudeMdSections: ['Architecture', 'Critical Rules'],
|
|
96
|
+
filePatterns: [/\/models\//, /\.model\./, /\/schema\//, /\.schema\./],
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
name: 'Configuration',
|
|
100
|
+
claudeMdSections: ['Stack', 'Configuration'],
|
|
101
|
+
filePatterns: [/\.config\./, /next\.config/, /tsconfig/],
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'Testing',
|
|
105
|
+
claudeMdSections: ['Quality Gates'],
|
|
106
|
+
filePatterns: [/\.test\./, /\.spec\./, /playwright/, /vitest/],
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'Workflow/Hooks',
|
|
110
|
+
claudeMdSections: ['Workflow', 'Stop Hook Validations'],
|
|
111
|
+
filePatterns: [/\.claude\/hooks\//, /\.claude\/scripts\//, /\.husky\//],
|
|
112
|
+
},
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
function categorizeChangedFiles(
|
|
116
|
+
files: string[]
|
|
117
|
+
): Array<{ category: string; sections: string[]; files: string[] }> {
|
|
118
|
+
const result = new Map<string, { sections: string[]; files: string[] }>();
|
|
119
|
+
|
|
120
|
+
for (const file of files) {
|
|
121
|
+
const normalizedFile = file.replace(/\\/g, '/');
|
|
122
|
+
for (const cat of CHANGE_CATEGORIES) {
|
|
123
|
+
for (const pattern of cat.filePatterns) {
|
|
124
|
+
if (pattern.test(normalizedFile)) {
|
|
125
|
+
if (!result.has(cat.name)) {
|
|
126
|
+
result.set(cat.name, { sections: [...cat.claudeMdSections], files: [] });
|
|
127
|
+
}
|
|
128
|
+
const entry = result.get(cat.name)!;
|
|
129
|
+
if (!entry.files.includes(file)) {
|
|
130
|
+
entry.files.push(file);
|
|
131
|
+
}
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return Array.from(result.entries()).map(([category, data]) => ({
|
|
139
|
+
category,
|
|
140
|
+
...data,
|
|
141
|
+
}));
|
|
142
|
+
}
|
|
143
|
+
|
|
57
144
|
const SOURCE_EXTENSIONS = new Set([
|
|
58
145
|
'.ts',
|
|
59
146
|
'.tsx',
|
|
@@ -603,7 +690,8 @@ IMPORTANT: This section should ONLY contain the LAST change, not a history.
|
|
|
603
690
|
}
|
|
604
691
|
|
|
605
692
|
// Check for multiple Last Change sections (stacking is forbidden)
|
|
606
|
-
|
|
693
|
+
// Use negative lookbehind to avoid matching ### Last Change (H3 subheadings)
|
|
694
|
+
const multipleChanges = content.match(/(?<!#)## Last Change/g);
|
|
607
695
|
if (multipleChanges && multipleChanges.length > 1) {
|
|
608
696
|
return {
|
|
609
697
|
type: 'CLAUDE_MD_STACKED_CHANGES',
|
|
@@ -628,8 +716,10 @@ This keeps the file focused and within the 40k character limit.
|
|
|
628
716
|
return null;
|
|
629
717
|
}
|
|
630
718
|
|
|
631
|
-
|
|
632
|
-
|
|
719
|
+
/**
|
|
720
|
+
* Helper: Get files exempt from CLAUDE.md update requirement
|
|
721
|
+
*/
|
|
722
|
+
function getSignificantFiles(modifiedFiles: string[]): string[] {
|
|
633
723
|
const EXEMPT_PATTERNS = [
|
|
634
724
|
'bun.lockb',
|
|
635
725
|
'package-lock.json',
|
|
@@ -641,13 +731,10 @@ function validateClaudeMdUpdated(modifiedFiles: string[]): ValidationError | nul
|
|
|
641
731
|
/^packages\/start-vibing\/template\//,
|
|
642
732
|
];
|
|
643
733
|
|
|
644
|
-
|
|
645
|
-
const significantFiles = modifiedFiles.filter((f) => {
|
|
646
|
-
// Always exempt CLAUDE.md itself
|
|
734
|
+
return modifiedFiles.filter((f) => {
|
|
647
735
|
if (f === 'CLAUDE.md' || f.endsWith('/CLAUDE.md') || f.endsWith('\\CLAUDE.md')) {
|
|
648
736
|
return false;
|
|
649
737
|
}
|
|
650
|
-
// Check exempt patterns
|
|
651
738
|
for (const pattern of EXEMPT_PATTERNS) {
|
|
652
739
|
if (typeof pattern === 'string') {
|
|
653
740
|
if (f === pattern || f.includes(pattern)) return false;
|
|
@@ -657,17 +744,39 @@ function validateClaudeMdUpdated(modifiedFiles: string[]): ValidationError | nul
|
|
|
657
744
|
}
|
|
658
745
|
return true;
|
|
659
746
|
});
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
/**
|
|
750
|
+
* Helper: Build categorized section guidance for error messages
|
|
751
|
+
*/
|
|
752
|
+
function buildSectionGuidance(significantFiles: string[]): string {
|
|
753
|
+
const categories = categorizeChangedFiles(significantFiles);
|
|
754
|
+
if (categories.length === 0) return '';
|
|
755
|
+
|
|
756
|
+
const lines = categories.map((c) => {
|
|
757
|
+
const uniqueSections = [...new Set(c.sections)];
|
|
758
|
+
return ` - ${c.category} (${c.files.length} file${c.files.length > 1 ? 's' : ''}) → Update sections: ${uniqueSections.join(', ')}`;
|
|
759
|
+
});
|
|
760
|
+
|
|
761
|
+
return `
|
|
762
|
+
DETECTED CHANGE CATEGORIES (update these CLAUDE.md sections):
|
|
763
|
+
|
|
764
|
+
${lines.join('\n')}
|
|
765
|
+
`;
|
|
766
|
+
}
|
|
660
767
|
|
|
661
|
-
|
|
768
|
+
function validateClaudeMdUpdated(modifiedFiles: string[]): ValidationError | null {
|
|
769
|
+
const significantFiles = getSignificantFiles(modifiedFiles);
|
|
662
770
|
if (significantFiles.length === 0) return null;
|
|
663
771
|
|
|
664
|
-
// Check if CLAUDE.md is in the modified files
|
|
665
772
|
const claudeMdModified = modifiedFiles.some(
|
|
666
773
|
(f) => f === 'CLAUDE.md' || f.endsWith('/CLAUDE.md') || f.endsWith('\\CLAUDE.md')
|
|
667
774
|
);
|
|
668
775
|
|
|
669
776
|
if (claudeMdModified) return null;
|
|
670
777
|
|
|
778
|
+
const sectionGuidance = buildSectionGuidance(significantFiles);
|
|
779
|
+
|
|
671
780
|
return {
|
|
672
781
|
type: 'CLAUDE_MD_NOT_UPDATED',
|
|
673
782
|
message: `${significantFiles.length} file(s) were modified but CLAUDE.md was not updated.`,
|
|
@@ -683,7 +792,7 @@ ${significantFiles
|
|
|
683
792
|
.slice(0, 10)
|
|
684
793
|
.map((f) => ` - ${f}`)
|
|
685
794
|
.join('\n')}${significantFiles.length > 10 ? '\n ... and more' : ''}
|
|
686
|
-
|
|
795
|
+
${sectionGuidance}
|
|
687
796
|
REQUIRED UPDATES TO CLAUDE.MD:
|
|
688
797
|
|
|
689
798
|
1. Update "## Last Change" section:
|
|
@@ -691,24 +800,315 @@ REQUIRED UPDATES TO CLAUDE.MD:
|
|
|
691
800
|
**Date:** ${new Date().toISOString().split('T')[0]}
|
|
692
801
|
**Summary:** What you implemented/fixed
|
|
693
802
|
|
|
694
|
-
2.
|
|
695
|
-
Update "
|
|
803
|
+
2. UPDATE RELEVANT RULE/FLOW SECTIONS (NOT just Last Change!):
|
|
804
|
+
- Changed API/CRUD logic? → Update "Critical Rules" or "HTTP Requests"
|
|
805
|
+
- Changed UI components? → Update "UI Architecture" or "Component Organization"
|
|
806
|
+
- Changed page structure? → Update "Architecture" or "Next.js App Router Patterns"
|
|
807
|
+
- Changed aesthetic rules? → Update "Design System" or "UI Architecture"
|
|
808
|
+
- Changed auth/middleware? → Update "Critical Rules" or "Workflow"
|
|
809
|
+
- Changed config? → Update "Stack" or "Configuration"
|
|
810
|
+
- Changed testing? → Update "Quality Gates"
|
|
811
|
+
|
|
812
|
+
3. CONTEXT SYNTHESIS:
|
|
813
|
+
Think about what the user asked and what you learned.
|
|
814
|
+
If a rule changed, document the NEW rule in the relevant section.
|
|
815
|
+
If a flow changed, document the NEW flow.
|
|
816
|
+
CLAUDE.md is the single source of truth for ALL project rules.
|
|
817
|
+
|
|
818
|
+
The stop hook will BLOCK until CLAUDE.md is updated with ALL relevant sections.
|
|
819
|
+
================================================================================`,
|
|
820
|
+
};
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// ============================================================================
|
|
824
|
+
// CHANGE RELEVANCE ASSESSMENT
|
|
825
|
+
// ============================================================================
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* Minimum thresholds for changes to be considered "relevant enough" to require
|
|
829
|
+
* updating CLAUDE.md sections beyond "Last Change".
|
|
830
|
+
*
|
|
831
|
+
* Minor changes (small fixes, typos, internal refactors) that don't affect
|
|
832
|
+
* project rules, flows, or patterns should NOT require deep section updates.
|
|
833
|
+
*/
|
|
834
|
+
const RELEVANCE_THRESHOLDS = {
|
|
835
|
+
/** Minimum total lines added+removed across categorized files to require section updates */
|
|
836
|
+
minLinesChanged: 30,
|
|
837
|
+
/** Minimum number of categorized files changed to require section updates */
|
|
838
|
+
minFilesChanged: 3,
|
|
839
|
+
/** If ANY new files were created in categorized areas, always require section updates */
|
|
840
|
+
newFilesAlwaysRelevant: true,
|
|
841
|
+
};
|
|
842
|
+
|
|
843
|
+
/**
|
|
844
|
+
* Assess whether the changes in categorized files are significant enough
|
|
845
|
+
* to require updating CLAUDE.md sections beyond "Last Change".
|
|
846
|
+
*
|
|
847
|
+
* Returns true if changes are relevant (should require deep updates).
|
|
848
|
+
* Returns false if changes are minor (Last Change only is acceptable).
|
|
849
|
+
*/
|
|
850
|
+
function areChangesRelevantForClaudeMd(
|
|
851
|
+
categories: Array<{ category: string; sections: string[]; files: string[] }>
|
|
852
|
+
): boolean {
|
|
853
|
+
const totalCategorizedFiles = categories.reduce((sum, c) => sum + c.files.length, 0);
|
|
696
854
|
|
|
697
|
-
|
|
698
|
-
|
|
855
|
+
// Check for new files (new files = new patterns/features = always relevant)
|
|
856
|
+
if (RELEVANCE_THRESHOLDS.newFilesAlwaysRelevant) {
|
|
857
|
+
try {
|
|
858
|
+
const untrackedRaw = execSync('git ls-files --others --exclude-standard', {
|
|
859
|
+
cwd: PROJECT_DIR,
|
|
860
|
+
encoding: 'utf8',
|
|
861
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
862
|
+
}).trim();
|
|
863
|
+
const newInLastCommitRaw = execSync('git diff --name-only --diff-filter=A HEAD~1 HEAD 2>/dev/null || echo ""', {
|
|
864
|
+
cwd: PROJECT_DIR,
|
|
865
|
+
encoding: 'utf8',
|
|
866
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
867
|
+
}).trim();
|
|
868
|
+
|
|
869
|
+
const newFiles = [...untrackedRaw.split('\n'), ...newInLastCommitRaw.split('\n')].filter(Boolean);
|
|
870
|
+
const allCategorizedFiles = categories.flatMap((c) => c.files);
|
|
871
|
+
const newCategorizedFiles = newFiles.filter((f) =>
|
|
872
|
+
allCategorizedFiles.some((cf) => f.includes(cf) || cf.includes(f))
|
|
873
|
+
);
|
|
874
|
+
|
|
875
|
+
if (newCategorizedFiles.length > 0) {
|
|
876
|
+
return true; // New files in categorized areas = always relevant
|
|
877
|
+
}
|
|
878
|
+
} catch {
|
|
879
|
+
// Can't determine, continue with other checks
|
|
880
|
+
}
|
|
881
|
+
}
|
|
699
882
|
|
|
700
|
-
|
|
701
|
-
|
|
883
|
+
// Check minimum files threshold
|
|
884
|
+
if (totalCategorizedFiles < RELEVANCE_THRESHOLDS.minFilesChanged) {
|
|
885
|
+
// Few files changed - check if the diff is substantial
|
|
886
|
+
try {
|
|
887
|
+
const allCategorizedFiles = categories.flatMap((c) => c.files);
|
|
888
|
+
let totalLinesChanged = 0;
|
|
889
|
+
|
|
890
|
+
// Try to get numstat from various diff sources
|
|
891
|
+
const numstatCommands = [
|
|
892
|
+
'git diff --numstat',
|
|
893
|
+
'git diff --cached --numstat',
|
|
894
|
+
'git diff --numstat HEAD~1 HEAD',
|
|
895
|
+
];
|
|
896
|
+
|
|
897
|
+
for (const cmd of numstatCommands) {
|
|
898
|
+
try {
|
|
899
|
+
const numstat = execSync(cmd, {
|
|
900
|
+
cwd: PROJECT_DIR,
|
|
901
|
+
encoding: 'utf8',
|
|
902
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
903
|
+
}).trim();
|
|
904
|
+
|
|
905
|
+
if (!numstat) continue;
|
|
906
|
+
|
|
907
|
+
for (const line of numstat.split('\n')) {
|
|
908
|
+
const parts = line.split('\t');
|
|
909
|
+
if (parts.length < 3) continue;
|
|
910
|
+
const added = parseInt(parts[0] || '0', 10) || 0;
|
|
911
|
+
const removed = parseInt(parts[1] || '0', 10) || 0;
|
|
912
|
+
const file = parts[2] || '';
|
|
913
|
+
|
|
914
|
+
// Only count lines in categorized files
|
|
915
|
+
if (allCategorizedFiles.some((cf) => file.includes(cf) || cf.includes(file))) {
|
|
916
|
+
totalLinesChanged += added + removed;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
if (totalLinesChanged > 0) break;
|
|
921
|
+
} catch {
|
|
922
|
+
continue;
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
if (totalLinesChanged < RELEVANCE_THRESHOLDS.minLinesChanged) {
|
|
927
|
+
return false; // Small diff + few files = minor change, Last Change is enough
|
|
928
|
+
}
|
|
929
|
+
} catch {
|
|
930
|
+
// Can't determine line count, fall through to relevant
|
|
931
|
+
}
|
|
932
|
+
}
|
|
702
933
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
Capture important decisions and patterns for the next session.
|
|
934
|
+
return true; // Meets thresholds = relevant, require section updates
|
|
935
|
+
}
|
|
706
936
|
|
|
707
|
-
|
|
937
|
+
/**
|
|
938
|
+
* Content depth validation: Checks if CLAUDE.md was updated but only the
|
|
939
|
+
* "Last Change" section was modified (shallow update).
|
|
940
|
+
*
|
|
941
|
+
* ALLOWS shallow updates when changes are minor:
|
|
942
|
+
* - Few files changed (< 3 categorized files)
|
|
943
|
+
* - Small diff (< 30 lines total in categorized files)
|
|
944
|
+
* - No new files created in categorized areas
|
|
945
|
+
*
|
|
946
|
+
* BLOCKS shallow updates when changes are significant:
|
|
947
|
+
* - Many categorized files changed
|
|
948
|
+
* - Large diffs that likely introduce new rules/flows
|
|
949
|
+
* - New files created (new patterns/features)
|
|
950
|
+
*/
|
|
951
|
+
function validateClaudeMdContentDepth(modifiedFiles: string[]): ValidationError | null {
|
|
952
|
+
const significantFiles = getSignificantFiles(modifiedFiles);
|
|
953
|
+
if (significantFiles.length === 0) return null;
|
|
954
|
+
|
|
955
|
+
// Only run if CLAUDE.md WAS modified (otherwise validateClaudeMdUpdated handles it)
|
|
956
|
+
const claudeMdModified = modifiedFiles.some(
|
|
957
|
+
(f) => f === 'CLAUDE.md' || f.endsWith('/CLAUDE.md') || f.endsWith('\\CLAUDE.md')
|
|
958
|
+
);
|
|
959
|
+
if (!claudeMdModified) return null;
|
|
960
|
+
|
|
961
|
+
// Categorize the source changes
|
|
962
|
+
const categories = categorizeChangedFiles(significantFiles);
|
|
963
|
+
if (categories.length === 0) return null; // No categorizable changes, Last Change is enough
|
|
964
|
+
|
|
965
|
+
// RELEVANCE CHECK: Skip if changes are minor/not relevant enough
|
|
966
|
+
if (!areChangesRelevantForClaudeMd(categories)) {
|
|
967
|
+
return null; // Minor changes - Last Change only is acceptable
|
|
968
|
+
}
|
|
969
|
+
|
|
970
|
+
// Try to detect if only "Last Change" section was modified in CLAUDE.md
|
|
971
|
+
// Check git diff of CLAUDE.md (staged, unstaged, or last commit)
|
|
972
|
+
let claudeMdDiff = '';
|
|
973
|
+
const diffCommands = [
|
|
974
|
+
'git diff -- CLAUDE.md',
|
|
975
|
+
'git diff --cached -- CLAUDE.md',
|
|
976
|
+
'git diff HEAD~1 HEAD -- CLAUDE.md',
|
|
977
|
+
];
|
|
978
|
+
|
|
979
|
+
for (const cmd of diffCommands) {
|
|
980
|
+
try {
|
|
981
|
+
const result = execSync(cmd, {
|
|
982
|
+
cwd: PROJECT_DIR,
|
|
983
|
+
encoding: 'utf8',
|
|
984
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
985
|
+
}).trim();
|
|
986
|
+
if (result) {
|
|
987
|
+
claudeMdDiff = result;
|
|
988
|
+
break;
|
|
989
|
+
}
|
|
990
|
+
} catch {
|
|
991
|
+
continue;
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
|
|
995
|
+
if (!claudeMdDiff) return null; // Can't determine diff, skip this check
|
|
996
|
+
|
|
997
|
+
// Parse the diff to detect which sections were modified
|
|
998
|
+
const modifiedSections = parseClaudeMdDiffSections(claudeMdDiff);
|
|
999
|
+
|
|
1000
|
+
// Check if ONLY "Last Change" was modified
|
|
1001
|
+
const onlyLastChange =
|
|
1002
|
+
modifiedSections.size > 0 &&
|
|
1003
|
+
modifiedSections.size === 1 &&
|
|
1004
|
+
modifiedSections.has('Last Change');
|
|
1005
|
+
|
|
1006
|
+
if (!onlyLastChange) return null; // Other sections were also modified, good
|
|
1007
|
+
|
|
1008
|
+
// Build specific guidance for what sections need updating
|
|
1009
|
+
const totalFiles = categories.reduce((sum, c) => sum + c.files.length, 0);
|
|
1010
|
+
const sectionGuidance = categories
|
|
1011
|
+
.map((c) => {
|
|
1012
|
+
const uniqueSections = [...new Set(c.sections)];
|
|
1013
|
+
const fileExamples = c.files.slice(0, 3).join(', ');
|
|
1014
|
+
return ` - ${c.category}: ${fileExamples}\n → Must review/update: ${uniqueSections.join(', ')}`;
|
|
1015
|
+
})
|
|
1016
|
+
.join('\n');
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
type: 'CLAUDE_MD_SHALLOW_UPDATE',
|
|
1020
|
+
message: `CLAUDE.md was updated but ONLY "Last Change" was modified. ${categories.length} category(ies) with ${totalFiles} file(s) require updating additional rule/flow sections.`,
|
|
1021
|
+
action: `
|
|
1022
|
+
================================================================================
|
|
1023
|
+
CLAUDE.MD SHALLOW UPDATE DETECTED - MUST UPDATE RELEVANT SECTIONS
|
|
1024
|
+
================================================================================
|
|
1025
|
+
|
|
1026
|
+
You updated CLAUDE.md but ONLY modified the "Last Change" section.
|
|
1027
|
+
The changes are significant enough to require updating rule/flow sections.
|
|
1028
|
+
|
|
1029
|
+
(Minor changes with < ${RELEVANCE_THRESHOLDS.minFilesChanged} files and < ${RELEVANCE_THRESHOLDS.minLinesChanged} lines are exempt from this check.)
|
|
1030
|
+
|
|
1031
|
+
CHANGES DETECTED THAT REQUIRE SECTION UPDATES:
|
|
1032
|
+
|
|
1033
|
+
${sectionGuidance}
|
|
1034
|
+
|
|
1035
|
+
--------------------------------------------------------------------------------
|
|
1036
|
+
WHAT TO DO
|
|
1037
|
+
--------------------------------------------------------------------------------
|
|
1038
|
+
|
|
1039
|
+
For EACH category above, review the corresponding CLAUDE.md section and:
|
|
1040
|
+
|
|
1041
|
+
1. If a RULE changed (e.g., CRUD validation, auth flow):
|
|
1042
|
+
→ UPDATE the rule in its section (Critical Rules, Workflow, etc.)
|
|
1043
|
+
|
|
1044
|
+
2. If an AESTHETIC/UI pattern changed (e.g., new component style):
|
|
1045
|
+
→ UPDATE UI Architecture, Design System, or Component Organization
|
|
1046
|
+
|
|
1047
|
+
3. If a FLOW changed (e.g., new middleware, data fetching pattern):
|
|
1048
|
+
→ UPDATE Workflow, Architecture, or Next.js App Router Patterns
|
|
1049
|
+
|
|
1050
|
+
4. If NOTHING changed in rules/flows (just implementation details):
|
|
1051
|
+
→ This should not happen for significant changes. Review again.
|
|
1052
|
+
|
|
1053
|
+
RULE: "Last Change" documents WHAT was done.
|
|
1054
|
+
Other sections document HOW things work NOW.
|
|
1055
|
+
Both must be current for significant changes.
|
|
1056
|
+
|
|
1057
|
+
The stop hook will BLOCK until relevant sections are also updated.
|
|
708
1058
|
================================================================================`,
|
|
709
1059
|
};
|
|
710
1060
|
}
|
|
711
1061
|
|
|
1062
|
+
/**
|
|
1063
|
+
* Parse a git diff of CLAUDE.md to determine which ## sections were modified.
|
|
1064
|
+
* Returns a Set of section names that had actual content changes.
|
|
1065
|
+
*/
|
|
1066
|
+
function parseClaudeMdDiffSections(diff: string): Set<string> {
|
|
1067
|
+
const modifiedSections = new Set<string>();
|
|
1068
|
+
const lines = diff.split('\n');
|
|
1069
|
+
let currentSection = '';
|
|
1070
|
+
|
|
1071
|
+
for (const line of lines) {
|
|
1072
|
+
// Skip diff metadata
|
|
1073
|
+
if (
|
|
1074
|
+
line.startsWith('diff ') ||
|
|
1075
|
+
line.startsWith('index ') ||
|
|
1076
|
+
line.startsWith('--- ') ||
|
|
1077
|
+
line.startsWith('+++ ')
|
|
1078
|
+
) {
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
// Parse @@ hunk headers - they sometimes contain section context
|
|
1083
|
+
const hunkMatch = line.match(/^@@ .+ @@\s*(## .+)?/);
|
|
1084
|
+
if (hunkMatch && hunkMatch[1]) {
|
|
1085
|
+
currentSection = hunkMatch[1].replace('## ', '').trim();
|
|
1086
|
+
continue;
|
|
1087
|
+
}
|
|
1088
|
+
if (line.startsWith('@@')) continue;
|
|
1089
|
+
|
|
1090
|
+
// Detect section headers in changed or context lines
|
|
1091
|
+
const sectionMatch = line.match(/^[+ -]?## (.+)/);
|
|
1092
|
+
if (sectionMatch) {
|
|
1093
|
+
currentSection = sectionMatch[1].trim();
|
|
1094
|
+
// If the section header itself is a change, count it
|
|
1095
|
+
if (line.startsWith('+') || line.startsWith('-')) {
|
|
1096
|
+
modifiedSections.add(currentSection);
|
|
1097
|
+
}
|
|
1098
|
+
continue;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// Track actual content changes (+ or - lines, not context)
|
|
1102
|
+
if (line.startsWith('+') || line.startsWith('-')) {
|
|
1103
|
+
if (currentSection) {
|
|
1104
|
+
modifiedSections.add(currentSection);
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
|
|
1109
|
+
return modifiedSections;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
712
1112
|
function validateDocumentation(sourceFiles: string[]): ValidationError | null {
|
|
713
1113
|
if (sourceFiles.length === 0) return null;
|
|
714
1114
|
|
|
@@ -996,7 +1396,13 @@ Validate with: wc -m CLAUDE.md (must be < 40000)`,
|
|
|
996
1396
|
},
|
|
997
1397
|
CLAUDE_MD_NOT_UPDATED: {
|
|
998
1398
|
agent: 'documenter',
|
|
999
|
-
prompt:
|
|
1399
|
+
prompt:
|
|
1400
|
+
'Update CLAUDE.md: Last Change section AND all relevant rule/flow sections based on what files were changed (API rules, UI rules, workflow, architecture, etc.)',
|
|
1401
|
+
},
|
|
1402
|
+
CLAUDE_MD_SHALLOW_UPDATE: {
|
|
1403
|
+
agent: 'documenter',
|
|
1404
|
+
prompt:
|
|
1405
|
+
'CLAUDE.md was updated but only Last Change was modified. Review changed source files, categorize them, and update the corresponding CLAUDE.md sections (Critical Rules, UI Architecture, Workflow, etc.) to reflect current rules and flows.',
|
|
1000
1406
|
},
|
|
1001
1407
|
SOURCE_FILES_NOT_DOCUMENTED: {
|
|
1002
1408
|
agent: 'documenter',
|
|
@@ -1087,11 +1493,15 @@ function collectAllErrors(
|
|
|
1087
1493
|
const updatedError = validateClaudeMdUpdated(modifiedFiles);
|
|
1088
1494
|
if (updatedError) errors.push(updatedError);
|
|
1089
1495
|
|
|
1090
|
-
// 9.
|
|
1496
|
+
// 9. CLAUDE.md must have relevant sections updated (not just Last Change)
|
|
1497
|
+
const contentDepthError = validateClaudeMdContentDepth(modifiedFiles);
|
|
1498
|
+
if (contentDepthError) errors.push(contentDepthError);
|
|
1499
|
+
|
|
1500
|
+
// 11. Source files must be documented
|
|
1091
1501
|
const docError = validateDocumentation(sourceFiles);
|
|
1092
1502
|
if (docError) errors.push(docError);
|
|
1093
1503
|
|
|
1094
|
-
//
|
|
1504
|
+
// 12. Domain documentation must be complete
|
|
1095
1505
|
const domainDocError = validateDomainDocumentation(modifiedFiles);
|
|
1096
1506
|
if (domainDocError) errors.push(domainDocError);
|
|
1097
1507
|
|
|
@@ -75,16 +75,21 @@ async function main(): Promise<void> {
|
|
|
75
75
|
|
|
76
76
|
5. COMMIT using conventional commits via commit-manager agent.
|
|
77
77
|
|
|
78
|
-
6. UPDATE CLAUDE.md BEFORE finishing (MANDATORY):
|
|
78
|
+
6. UPDATE CLAUDE.md BEFORE finishing (MANDATORY - NOT JUST "Last Change"!):
|
|
79
79
|
a. "## Last Change" section (date: ${today}, branch, summary). Keep only latest.
|
|
80
|
-
b.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
80
|
+
b. THEN update ALL sections affected by your changes:
|
|
81
|
+
- Changed API/CRUD rules? → Update "Critical Rules" or "HTTP Requests"
|
|
82
|
+
- Changed UI components/aesthetics? → Update "UI Architecture", "Design System", "Component Organization"
|
|
83
|
+
- Changed pages/layouts? → Update "Architecture" or "Next.js App Router Patterns"
|
|
84
|
+
- Changed auth/middleware? → Update "Critical Rules" or "Workflow"
|
|
85
|
+
- Changed config/stack? → Update "Stack" or "Configuration"
|
|
86
|
+
- Changed testing patterns? → Update "Quality Gates"
|
|
87
|
+
- Changed workflow/hooks? → Update "Workflow" or "Stop Hook Validations"
|
|
88
|
+
- New gotchas discovered? → Add to "FORBIDDEN" or "NRY"
|
|
89
|
+
|
|
90
|
+
RULE: "Last Change" = WHAT was done. Other sections = HOW things work NOW.
|
|
91
|
+
If you ONLY update Last Change, the stop-validator will BLOCK with CLAUDE_MD_SHALLOW_UPDATE.
|
|
92
|
+
CLAUDE.md is the SINGLE SOURCE OF TRUTH for ALL project rules and flows.
|
|
88
93
|
|
|
89
94
|
7. RUN stop-validator before finishing: npx tsx .claude/hooks/stop-validator.ts`;
|
|
90
95
|
|