claudify-agent 1.0.1 → 1.0.3
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/.claude/QUICK-REFERENCE.md +213 -213
- package/.claude/cost-dashboard.md +162 -162
- package/.claude/docs/COST-OPTIMIZATION.md +402 -403
- package/.claude/docs/MCP-INTEGRATION-GUIDE.md +348 -348
- package/.claude/docs/PLAN-MODE-OPTIMIZATION.md +835 -836
- package/.claude/hooks/auto-learning-review.ts +200 -195
- package/.claude/hooks/auto-lint-fix.ts +60 -104
- package/.claude/hooks/auto-security-review.ts +314 -354
- package/.claude/hooks/auto-validate.ts +249 -348
- package/.claude/hooks/lib/claude-hook.ts +168 -0
- package/.claude/hooks/plan-quality-tracker.ts +598 -719
- package/.claude/rules/anti-patterns.md +22 -22
- package/.claude/rules/context-tier-1.md +41 -41
- package/.claude/rules/context-tier-2.md +285 -285
- package/.claude/rules/general.md +25 -25
- package/.claude/rules/mcp-auto-use.md +46 -46
- package/.claude/rules/parallel-exploration-auto.md +30 -31
- package/.claude/rules/plan-mode-execution.md +289 -289
- package/.claude/rules/plan-mode-research.md +34 -34
- package/.claude/rules/plan-mode-structure.md +166 -166
- package/.claude/rules/plan-mode-validation.md +129 -130
- package/.claude/rules/security-critical.md +19 -19
- package/.claude/rules/tool-auto-selection.md +45 -45
- package/.claude/rules/tool-strategy.md +22 -22
- package/.claude/scripts/security-scan.js +1 -1
- package/.claude/security-dashboard.md +142 -142
- package/.claude/settings.json +50 -0
- package/.claude/skills/debug-optimizer/SKILL.md +420 -399
- package/.claude/skills/learning-loop/SKILL.md +313 -308
- package/.claude/skills/mcp-mastery/SKILL.md +218 -214
- package/.claude/skills/nextjs-guide/SKILL.md +687 -687
- package/.claude/skills/parallel-exploration/SKILL.md +197 -193
- package/.claude/skills/plan-mode-mastery/SKILL.md +268 -263
- package/.claude/skills/python-guide/SKILL.md +822 -822
- package/.claude/skills/react-guide/SKILL.md +762 -762
- package/.claude/skills/{security.md → security-guide/SKILL.md} +10 -0
- package/.claude/skills/solidity-guide/SKILL.md +5 -0
- package/.claude/skills/tool-selection/SKILL.md +433 -428
- package/.claude/skills/vite-guide/SKILL.md +445 -445
- package/CLAUDE.md +1 -0
- package/README.md +74 -35
- package/README.vi.md +106 -0
- package/bin/cli.js +13 -4
- package/package.json +13 -1
- package/.claude/skills/debug-optimizer.md +0 -12
- package/.claude/skills/nextjs.md +0 -13
- package/.claude/skills/react.md +0 -12
|
@@ -1,195 +1,200 @@
|
|
|
1
|
-
// .
|
|
2
|
-
//
|
|
3
|
-
|
|
4
|
-
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
5
|
-
import { join } from "path";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
console.error(`[Auto Learning Review]
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
"
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
${data.repeatedPatterns.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
`
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
1
|
+
// .claude/hooks/auto-learning-review.ts
|
|
2
|
+
// SessionStart hook — learning loop review over the last 10 tracked plans
|
|
3
|
+
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
import { readHookInput, stateDir } from "./lib/claude-hook.ts";
|
|
7
|
+
|
|
8
|
+
const input = await readHookInput();
|
|
9
|
+
|
|
10
|
+
const METRICS_FILE = join(stateDir(input), "plan-metrics.json");
|
|
11
|
+
const WEEKLY_REPORT_FILE = join(stateDir(input), "weekly-learning-report.md");
|
|
12
|
+
|
|
13
|
+
interface PlanMetrics {
|
|
14
|
+
plan_accuracy: number;
|
|
15
|
+
iteration_count: number;
|
|
16
|
+
tool_efficiency: number;
|
|
17
|
+
cost_estimate?: number;
|
|
18
|
+
anti_patterns_detected?: string[];
|
|
19
|
+
timestamp: string;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AntiPattern {
|
|
23
|
+
id: string;
|
|
24
|
+
name: string;
|
|
25
|
+
count: number;
|
|
26
|
+
lastOccurrence: string;
|
|
27
|
+
suggestion: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
console.error("[Auto Learning Review] Starting automated review...");
|
|
31
|
+
|
|
32
|
+
// Load metrics
|
|
33
|
+
if (!existsSync(METRICS_FILE)) {
|
|
34
|
+
console.error("[Auto Learning Review] No metrics file found. Run some plans first.");
|
|
35
|
+
process.exit(0);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const allMetrics: PlanMetrics[] = JSON.parse(readFileSync(METRICS_FILE, "utf-8"));
|
|
39
|
+
const recentMetrics = allMetrics.slice(-10); // Last 10 plans
|
|
40
|
+
|
|
41
|
+
if (recentMetrics.length < 3) {
|
|
42
|
+
console.error("[Auto Learning Review] Not enough data (need 3+ plans). Current:", recentMetrics.length);
|
|
43
|
+
process.exit(0);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Calculate averages
|
|
47
|
+
const avgAccuracy = recentMetrics.reduce((sum, m) => sum + m.plan_accuracy, 0) / recentMetrics.length;
|
|
48
|
+
const avgIterations = recentMetrics.reduce((sum, m) => sum + m.iteration_count, 0) / recentMetrics.length;
|
|
49
|
+
const avgEfficiency = recentMetrics.reduce((sum, m) => sum + m.tool_efficiency, 0) / recentMetrics.length;
|
|
50
|
+
const totalCost = recentMetrics.reduce((sum, m) => sum + (m.cost_estimate || 0), 0);
|
|
51
|
+
|
|
52
|
+
// Detect patterns
|
|
53
|
+
const patternCounts: Record<string, number> = {};
|
|
54
|
+
recentMetrics.forEach(m => {
|
|
55
|
+
if (m.anti_patterns_detected) {
|
|
56
|
+
m.anti_patterns_detected.forEach(pattern => {
|
|
57
|
+
patternCounts[pattern] = (patternCounts[pattern] || 0) + 1;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// Identify repeated patterns (3+ occurrences)
|
|
63
|
+
const repeatedPatterns: AntiPattern[] = Object.entries(patternCounts)
|
|
64
|
+
.filter(([, count]) => count >= 3)
|
|
65
|
+
.map(([name, count]) => ({
|
|
66
|
+
id: name.toLowerCase().replace(/\s+/g, '-'),
|
|
67
|
+
name,
|
|
68
|
+
count,
|
|
69
|
+
lastOccurrence: recentMetrics.find(m => m.anti_patterns_detected?.includes(name))?.timestamp || '',
|
|
70
|
+
suggestion: getSuggestionForPattern(name)
|
|
71
|
+
}));
|
|
72
|
+
|
|
73
|
+
// Generate report
|
|
74
|
+
const report = generateReport({
|
|
75
|
+
planCount: recentMetrics.length,
|
|
76
|
+
avgAccuracy,
|
|
77
|
+
avgIterations,
|
|
78
|
+
avgEfficiency,
|
|
79
|
+
totalCost,
|
|
80
|
+
repeatedPatterns,
|
|
81
|
+
trend: calculateTrend(recentMetrics)
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Write report
|
|
85
|
+
writeFileSync(WEEKLY_REPORT_FILE, report, 'utf-8');
|
|
86
|
+
console.error(`[Auto Learning Review] Report generated: ${WEEKLY_REPORT_FILE}`);
|
|
87
|
+
|
|
88
|
+
// Auto-update rules if patterns detected
|
|
89
|
+
if (repeatedPatterns.length > 0) {
|
|
90
|
+
console.error(`[Auto Learning Review] Detected ${repeatedPatterns.length} repeated patterns (3+ occurrences)`);
|
|
91
|
+
console.error(`[Auto Learning Review] Review ${WEEKLY_REPORT_FILE} for details`);
|
|
92
|
+
} else {
|
|
93
|
+
console.error("[Auto Learning Review] No repeated patterns detected. Great job!");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function getSuggestionForPattern(patternName: string): string {
|
|
97
|
+
const suggestions: Record<string, string> = {
|
|
98
|
+
"Over-Engineering": "Follow tool escalation protocol: Tier 1 (Read/Glob/Grep) → Tier 2 → Tier 3 → Tier 4",
|
|
99
|
+
"Planning Without Research": "Always research with Grep/Glob and Explore subagents before creating plans. Spend 2-3 min on research.",
|
|
100
|
+
"Repeated Execution Failures": "Add validation subagent before plan execution. Create checkpoints.",
|
|
101
|
+
"MCP Overuse": "Use MCP tools only when essential. Prefer built-in tools for simple tasks.",
|
|
102
|
+
"Skipping Validation": "Keep the auto-validate Stop hook enabled. Run lint/typecheck after EVERY code change.",
|
|
103
|
+
"Context Bloat": "Keep rules under 20 lines. Use references over content."
|
|
104
|
+
};
|
|
105
|
+
return suggestions[patternName] || "Review pattern in learning-loop skill documentation.";
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function calculateTrend(metrics: PlanMetrics[]): "improving" | "stable" | "declining" {
|
|
109
|
+
if (metrics.length < 5) return "stable";
|
|
110
|
+
|
|
111
|
+
const firstHalf = metrics.slice(0, Math.floor(metrics.length / 2));
|
|
112
|
+
const secondHalf = metrics.slice(Math.floor(metrics.length / 2));
|
|
113
|
+
|
|
114
|
+
const firstAvg = firstHalf.reduce((sum, m) => sum + m.plan_accuracy, 0) / firstHalf.length;
|
|
115
|
+
const secondAvg = secondHalf.reduce((sum, m) => sum + m.plan_accuracy, 0) / secondHalf.length;
|
|
116
|
+
|
|
117
|
+
if (secondAvg > firstAvg + 5) return "improving";
|
|
118
|
+
if (secondAvg < firstAvg - 5) return "declining";
|
|
119
|
+
return "stable";
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
interface ReportData {
|
|
123
|
+
planCount: number;
|
|
124
|
+
avgAccuracy: number;
|
|
125
|
+
avgIterations: number;
|
|
126
|
+
avgEfficiency: number;
|
|
127
|
+
totalCost: number;
|
|
128
|
+
repeatedPatterns: AntiPattern[];
|
|
129
|
+
trend: "improving" | "stable" | "declining";
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function generateReport(data: ReportData): string {
|
|
133
|
+
const date = new Date().toISOString().split('T')[0];
|
|
134
|
+
|
|
135
|
+
return `# Weekly Learning Report
|
|
136
|
+
|
|
137
|
+
**Generated**: ${date}
|
|
138
|
+
**Analysis Period**: Last ${data.planCount} plans
|
|
139
|
+
**Trend**: ${data.trend.toUpperCase()}
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Performance Summary
|
|
144
|
+
|
|
145
|
+
| Metric | Current | Target | Status |
|
|
146
|
+
|--------|---------|--------|--------|
|
|
147
|
+
| Avg Accuracy | ${data.avgAccuracy.toFixed(1)}% | >80% | ${data.avgAccuracy >= 80 ? '✅' : '⚠️'} |
|
|
148
|
+
| Avg Iterations | ${data.avgIterations.toFixed(1)} | <2.0 | ${data.avgIterations < 2 ? '✅' : '⚠️'} |
|
|
149
|
+
| Tool Efficiency | ${data.avgEfficiency.toFixed(1)}% | >70% | ${data.avgEfficiency >= 70 ? '✅' : '⚠️'} |
|
|
150
|
+
| Total Cost | ${data.totalCost.toLocaleString()} tokens | Optimize | ${data.totalCost < 100000 ? '✅' : '⚠️'} |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Repeated Patterns Detected (3+ Occurrences)
|
|
155
|
+
|
|
156
|
+
${data.repeatedPatterns.length > 0 ? data.repeatedPatterns.map((p: AntiPattern) => `### ${p.name}
|
|
157
|
+
|
|
158
|
+
**Occurrences**: ${p.count} times
|
|
159
|
+
**Last Seen**: ${p.lastOccurrence || 'Unknown'}
|
|
160
|
+
**Suggestion**: ${p.suggestion}
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
`).join('\n') : '✅ No repeated patterns detected! Great job maintaining best practices.'}
|
|
164
|
+
|
|
165
|
+
## Action Items
|
|
166
|
+
|
|
167
|
+
${data.repeatedPatterns.length > 0 ? `
|
|
168
|
+
### Critical (Fix This Week)
|
|
169
|
+
${data.repeatedPatterns.filter((p: AntiPattern) => p.count >= 5).map((p: AntiPattern) => `- [ ] ${p.name}: ${p.suggestion}`).join('\n') || '- [ ] None'}
|
|
170
|
+
|
|
171
|
+
### Important (Fix Next Week)
|
|
172
|
+
${data.repeatedPatterns.filter((p: AntiPattern) => p.count >= 3 && p.count < 5).map((p: AntiPattern) => `- [ ] ${p.name}: ${p.suggestion}`).join('\n') || '- [ ] None'}
|
|
173
|
+
` : `
|
|
174
|
+
### Continue Current Practices
|
|
175
|
+
- [ ] Maintain research-first approach
|
|
176
|
+
- [ ] Keep following tool escalation protocol
|
|
177
|
+
- [ ] Continue automatic validation
|
|
178
|
+
`}
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Trend Analysis
|
|
183
|
+
|
|
184
|
+
${data.trend === 'improving' ? '📈 **Performance is IMPROVING** - Keep doing what works!' :
|
|
185
|
+
data.trend === 'declining' ? '📉 **Performance is DECLINING** - Review recent changes and patterns.' :
|
|
186
|
+
'➡️ **Performance is STABLE** - Look for optimization opportunities.'}
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Next Review
|
|
191
|
+
|
|
192
|
+
**Scheduled**: In 10 plans or 7 days (whichever comes first)
|
|
193
|
+
**Focus Areas**:
|
|
194
|
+
${data.repeatedPatterns.length > 0 ? data.repeatedPatterns.slice(0, 3).map((p: AntiPattern) => `- ${p.name}`) : '- Continue monitoring current practices'}
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
*This report was automatically generated by the auto-learning-review hook*
|
|
199
|
+
`;
|
|
200
|
+
}
|
|
@@ -1,104 +1,60 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
// Check for remaining issues
|
|
63
|
-
if (lintOutput.includes("error")) {
|
|
64
|
-
hasErrors = true;
|
|
65
|
-
}
|
|
66
|
-
if (lintOutput.includes("warning")) {
|
|
67
|
-
hasWarnings = true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// Generate lint report
|
|
71
|
-
const timestamp = new Date().toISOString();
|
|
72
|
-
const report = `## ${timestamp}\n\n### ESLint Results\n\n${lintOutput}\n\n`;
|
|
73
|
-
|
|
74
|
-
if (existsSync(LINT_REPORT_PATH)) {
|
|
75
|
-
writeFileSync(LINT_REPORT_PATH, report, { flag: 'a' });
|
|
76
|
-
} else {
|
|
77
|
-
writeFileSync(LINT_REPORT_PATH, `# ESLint Report\n\n${report}`);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// Return appropriate message
|
|
81
|
-
if (hasErrors) {
|
|
82
|
-
const output: StopHookOutput = {
|
|
83
|
-
followup_message: `⚠️ ESLint found errors that couldn't be auto-fixed. Please review and fix manually.`,
|
|
84
|
-
};
|
|
85
|
-
console.log(JSON.stringify(output));
|
|
86
|
-
} else if (hasWarnings) {
|
|
87
|
-
const output: StopHookOutput = {
|
|
88
|
-
followup_message: `✅ ESLint auto-fixed issues. Some warnings remain (check ${LINT_REPORT_PATH}).`,
|
|
89
|
-
};
|
|
90
|
-
console.log(JSON.stringify(output));
|
|
91
|
-
} else {
|
|
92
|
-
const output: StopHookOutput = {
|
|
93
|
-
followup_message: `✅ ESLint passed with no issues.`,
|
|
94
|
-
};
|
|
95
|
-
console.log(JSON.stringify(output));
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
} catch (error: any) {
|
|
99
|
-
// ESLint not available or failed
|
|
100
|
-
const output: StopHookOutput = {
|
|
101
|
-
followup_message: `⚠️ ESLint check failed: ${error.message}. Continuing without linting.`,
|
|
102
|
-
};
|
|
103
|
-
console.log(JSON.stringify(output));
|
|
104
|
-
}
|
|
1
|
+
// .claude/hooks/auto-lint-fix.ts
|
|
2
|
+
// PostToolUse (Edit|Write|MultiEdit): auto-fix ESLint issues in the edited file
|
|
3
|
+
|
|
4
|
+
import { execFileSync } from "child_process";
|
|
5
|
+
import { writeFileSync, existsSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
import { readHookInput, stateDir, done, type ExecError } from "./lib/claude-hook.ts";
|
|
8
|
+
|
|
9
|
+
const LINTABLE = /\.(c|m)?(j|t)sx?$/;
|
|
10
|
+
|
|
11
|
+
const input = await readHookInput();
|
|
12
|
+
const file = input.tool_input?.file_path as string | undefined;
|
|
13
|
+
|
|
14
|
+
const projectDir = process.env.CLAUDE_PROJECT_DIR || input.cwd || process.cwd();
|
|
15
|
+
// Only use the project's own ESLint, never download one
|
|
16
|
+
const eslintBin = join(projectDir, "node_modules", ".bin", process.platform === "win32" ? "eslint.cmd" : "eslint");
|
|
17
|
+
|
|
18
|
+
if (!file || !LINTABLE.test(file) || !existsSync(file) || !existsSync(eslintBin)) done();
|
|
19
|
+
|
|
20
|
+
const LINT_REPORT_PATH = join(stateDir(input), "lint-report.md");
|
|
21
|
+
|
|
22
|
+
let lintOutput: string;
|
|
23
|
+
let hasErrors = false;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
lintOutput = execFileSync(eslintBin, ["--fix", file], {
|
|
27
|
+
cwd: projectDir,
|
|
28
|
+
encoding: "utf-8",
|
|
29
|
+
stdio: "pipe",
|
|
30
|
+
timeout: 60000,
|
|
31
|
+
});
|
|
32
|
+
} catch (err) {
|
|
33
|
+
const error = err as ExecError;
|
|
34
|
+
lintOutput = error.stdout?.toString() || error.stderr?.toString() || error.message;
|
|
35
|
+
// ESLint exits 1 on remaining errors; 2 = config error/crash → skip silently
|
|
36
|
+
if (error.status !== 1) done();
|
|
37
|
+
hasErrors = true;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!hasErrors) {
|
|
41
|
+
if (lintOutput.includes("warning")) {
|
|
42
|
+
console.error(`[Auto Lint Fix] ✓ ${file} auto-fixed, warnings remain`);
|
|
43
|
+
}
|
|
44
|
+
done();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const report = `## ${new Date().toISOString()}\n\n### ESLint Results: ${file}\n\n${lintOutput}\n\n`;
|
|
48
|
+
writeFileSync(LINT_REPORT_PATH, existsSync(LINT_REPORT_PATH) ? report : `# ESLint Report\n\n${report}`, {
|
|
49
|
+
flag: existsSync(LINT_REPORT_PATH) ? "a" : "w",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Feed remaining errors back to Claude so it fixes them right away
|
|
53
|
+
console.log(
|
|
54
|
+
JSON.stringify({
|
|
55
|
+
hookSpecificOutput: {
|
|
56
|
+
hookEventName: "PostToolUse",
|
|
57
|
+
additionalContext: `⚠️ ESLint found errors in ${file} that couldn't be auto-fixed:\n${lintOutput.slice(0, 4000)}`,
|
|
58
|
+
},
|
|
59
|
+
}),
|
|
60
|
+
);
|