claudify-agent 0.0.0-stage → 1.0.0
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 -0
- package/.claude/cost-dashboard.md +162 -0
- package/.claude/docs/COST-OPTIMIZATION.md +403 -0
- package/.claude/docs/MCP-INTEGRATION-GUIDE.md +348 -0
- package/.claude/docs/PLAN-MODE-OPTIMIZATION.md +836 -0
- package/.claude/hooks/auto-learning-review.ts +195 -0
- package/.claude/hooks/auto-lint-fix.ts +104 -0
- package/.claude/hooks/auto-security-review.ts +354 -0
- package/.claude/hooks/auto-validate.ts +348 -0
- package/.claude/hooks/plan-quality-tracker.ts +719 -0
- package/.claude/rules/anti-patterns.md +22 -0
- package/.claude/rules/context-tier-1.md +41 -0
- package/.claude/rules/context-tier-2.md +285 -0
- package/.claude/rules/general.md +25 -0
- package/.claude/rules/mcp-auto-use.md +46 -0
- package/.claude/rules/parallel-exploration-auto.md +31 -0
- package/.claude/rules/plan-mode-execution.md +289 -0
- package/.claude/rules/plan-mode-research.md +34 -0
- package/.claude/rules/plan-mode-structure.md +166 -0
- package/.claude/rules/plan-mode-validation.md +130 -0
- package/.claude/rules/security-critical.md +19 -0
- package/.claude/rules/tool-auto-selection.md +45 -0
- package/.claude/rules/tool-strategy.md +22 -0
- package/.claude/scripts/auto-validate.js +69 -0
- package/.claude/scripts/security-scan.js +45 -0
- package/.claude/security-dashboard.md +142 -0
- package/.claude/skills/backend-concurrency/SKILL.md +203 -0
- package/.claude/skills/debug-optimizer/SKILL.md +399 -0
- package/.claude/skills/debug-optimizer.md +12 -0
- package/.claude/skills/golang-guide/SKILL.md +177 -0
- package/.claude/skills/learning-loop/SKILL.md +308 -0
- package/.claude/skills/mcp-mastery/SKILL.md +214 -0
- package/.claude/skills/nestjs-guide/SKILL.md +197 -0
- package/.claude/skills/nextjs-guide/SKILL.md +687 -0
- package/.claude/skills/nextjs.md +13 -0
- package/.claude/skills/parallel-exploration/SKILL.md +193 -0
- package/.claude/skills/plan-mode-mastery/SKILL.md +263 -0
- package/.claude/skills/python-guide/SKILL.md +822 -0
- package/.claude/skills/react-guide/SKILL.md +762 -0
- package/.claude/skills/react.md +12 -0
- package/.claude/skills/security.md +18 -0
- package/.claude/skills/solidity-guide/SKILL.md +32 -0
- package/.claude/skills/tool-selection/SKILL.md +428 -0
- package/.claude/skills/vite-guide/SKILL.md +445 -0
- package/CLAUDE.md +49 -0
- package/README.md +65 -2
- package/bin/cli.js +50 -0
- package/package.json +44 -4
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// .cursor/hooks/auto-validate.ts
|
|
2
|
+
// Unified validation hook: lint + typecheck + tests + MCP validation
|
|
3
|
+
|
|
4
|
+
import { execSync } from "child_process";
|
|
5
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
6
|
+
import { join } from "path";
|
|
7
|
+
|
|
8
|
+
export {};
|
|
9
|
+
|
|
10
|
+
interface StopHookInput {
|
|
11
|
+
conversation_id: string;
|
|
12
|
+
status: "completed" | "aborted" | "error";
|
|
13
|
+
loop_count: number;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface StopHookOutput {
|
|
17
|
+
followup_message?: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface ValidationResult {
|
|
21
|
+
name: string;
|
|
22
|
+
passed: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
skipped?: boolean;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface MCPToolResult {
|
|
28
|
+
tool: string;
|
|
29
|
+
success: boolean;
|
|
30
|
+
error?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
interface ValidationConfig {
|
|
34
|
+
enableLint?: boolean;
|
|
35
|
+
enableTypecheck?: boolean;
|
|
36
|
+
enableTests?: boolean;
|
|
37
|
+
enableMCPValidation?: boolean;
|
|
38
|
+
testCommand?: string;
|
|
39
|
+
lintCommand?: string;
|
|
40
|
+
typecheckCommand?: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
interface HooksConfig {
|
|
44
|
+
validation?: ValidationConfig;
|
|
45
|
+
reporting?: {
|
|
46
|
+
generateReports?: boolean;
|
|
47
|
+
reportDirectory?: string;
|
|
48
|
+
appendReports?: boolean;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Read input from stdin
|
|
53
|
+
const stdin = await new Promise<string>((resolve) => {
|
|
54
|
+
let data = "";
|
|
55
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
56
|
+
process.stdin.on("end", () => resolve(data));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const input: StopHookInput = JSON.parse(stdin);
|
|
60
|
+
|
|
61
|
+
// Load optional configuration
|
|
62
|
+
const configPath = join(process.cwd(), '.cursor/hooks.config.json');
|
|
63
|
+
const config: HooksConfig = existsSync(configPath)
|
|
64
|
+
? JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
65
|
+
: {};
|
|
66
|
+
|
|
67
|
+
const validationConfig = {
|
|
68
|
+
enableLint: config.validation?.enableLint !== false,
|
|
69
|
+
enableTypecheck: config.validation?.enableTypecheck !== false,
|
|
70
|
+
enableTests: config.validation?.enableTests !== false,
|
|
71
|
+
enableMCPValidation: config.validation?.enableMCPValidation !== false,
|
|
72
|
+
lintCommand: config.validation?.lintCommand,
|
|
73
|
+
typecheckCommand: config.validation?.typecheckCommand,
|
|
74
|
+
testCommand: config.validation?.testCommand
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
const reportingConfig = {
|
|
78
|
+
generateReports: config.reporting?.generateReports !== false,
|
|
79
|
+
reportDirectory: config.reporting?.reportDirectory || '.cursor',
|
|
80
|
+
appendReports: config.reporting?.appendReports !== false
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
// Only validate on completion
|
|
84
|
+
if (input.status !== "completed") {
|
|
85
|
+
console.log(JSON.stringify({}));
|
|
86
|
+
process.exit(0);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Helper functions
|
|
90
|
+
function hasScript(scriptName: string): boolean {
|
|
91
|
+
try {
|
|
92
|
+
const pkgPath = join(process.cwd(), 'package.json');
|
|
93
|
+
if (!existsSync(pkgPath)) return false;
|
|
94
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
95
|
+
return !!pkg.scripts?.[scriptName];
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function detectCodeChanges(conversation: any): boolean {
|
|
102
|
+
if (!conversation?.messages) return false;
|
|
103
|
+
|
|
104
|
+
return conversation.messages.some((m: any) =>
|
|
105
|
+
m.content?.includes('StrReplace') ||
|
|
106
|
+
m.content?.includes('Write') ||
|
|
107
|
+
m.content?.includes('EditNotebook') ||
|
|
108
|
+
m.tool_calls?.some((t: any) =>
|
|
109
|
+
['StrReplace', 'Write', 'EditNotebook'].includes(t.function?.name)
|
|
110
|
+
)
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function validateMCPTools(conversation: any): MCPToolResult[] {
|
|
115
|
+
const results: MCPToolResult[] = [];
|
|
116
|
+
|
|
117
|
+
if (!conversation?.messages) return results;
|
|
118
|
+
|
|
119
|
+
const mcpToolPatterns = [
|
|
120
|
+
'browser_', 'github_', 'eslint', 'context7',
|
|
121
|
+
'chrome', 'playwright', 'selenium', 'dart', 'stitch', 'shadcn'
|
|
122
|
+
];
|
|
123
|
+
|
|
124
|
+
for (const message of conversation.messages) {
|
|
125
|
+
if (message.tool_calls) {
|
|
126
|
+
for (const toolCall of message.tool_calls) {
|
|
127
|
+
const toolName = toolCall.function?.name;
|
|
128
|
+
if (!toolName) continue;
|
|
129
|
+
|
|
130
|
+
const isMCPTool = mcpToolPatterns.some(pattern =>
|
|
131
|
+
toolName.startsWith(pattern) || toolName.includes(pattern)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (isMCPTool) {
|
|
135
|
+
// Track MCP tool usage (simplified - assumes success)
|
|
136
|
+
// In production, would check actual tool execution results
|
|
137
|
+
results.push({
|
|
138
|
+
tool: toolName,
|
|
139
|
+
success: true
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return results;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function detectTestFiles(conversation: any): boolean {
|
|
150
|
+
if (!conversation?.messages) return false;
|
|
151
|
+
|
|
152
|
+
return conversation.messages.some((m: any) =>
|
|
153
|
+
m.content?.includes('.test.ts') ||
|
|
154
|
+
m.content?.includes('.test.tsx') ||
|
|
155
|
+
m.content?.includes('.spec.ts') ||
|
|
156
|
+
m.content?.includes('.spec.tsx') ||
|
|
157
|
+
m.content?.includes('__tests__') ||
|
|
158
|
+
m.content?.includes('.test.js') ||
|
|
159
|
+
m.content?.includes('.spec.js')
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Try to detect code changes from conversation file
|
|
164
|
+
const conversationFile = join(process.cwd(), `.cursor/conversations/${input.conversation_id}.json`);
|
|
165
|
+
let conversation;
|
|
166
|
+
try {
|
|
167
|
+
if (existsSync(conversationFile)) {
|
|
168
|
+
conversation = JSON.parse(readFileSync(conversationFile, 'utf-8'));
|
|
169
|
+
}
|
|
170
|
+
} catch {
|
|
171
|
+
console.error("[Auto-Validate] Could not read conversation file, proceeding with validation");
|
|
172
|
+
conversation = null;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Smart change detection
|
|
176
|
+
const hasCodeChanges = conversation ? detectCodeChanges(conversation) : true;
|
|
177
|
+
|
|
178
|
+
if (!hasCodeChanges) {
|
|
179
|
+
console.error("[Auto-Validate] No code changes detected, skipping validation");
|
|
180
|
+
console.log(JSON.stringify({}));
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
console.error("[Auto-Validate] Running automatic validation...");
|
|
185
|
+
|
|
186
|
+
// Automatic validation sequence
|
|
187
|
+
const validations: ValidationResult[] = [];
|
|
188
|
+
|
|
189
|
+
// 1. Run ESLint (with graceful degradation)
|
|
190
|
+
if (validationConfig.enableLint) {
|
|
191
|
+
const lintCommand = validationConfig.lintCommand || (hasScript('lint') ? 'npm run lint' : null);
|
|
192
|
+
|
|
193
|
+
if (lintCommand) {
|
|
194
|
+
try {
|
|
195
|
+
execSync(lintCommand, { stdio: 'pipe', encoding: 'utf-8', timeout: 60000 });
|
|
196
|
+
validations.push({ name: 'ESLint', passed: true });
|
|
197
|
+
console.error("[Auto-Validate] ✓ ESLint passed");
|
|
198
|
+
} catch (error: any) {
|
|
199
|
+
validations.push({
|
|
200
|
+
name: 'ESLint',
|
|
201
|
+
passed: false,
|
|
202
|
+
error: error.stdout?.toString() || error.stderr?.toString() || error.message
|
|
203
|
+
});
|
|
204
|
+
console.error("[Auto-Validate] ✗ ESLint failed");
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
validations.push({ name: 'ESLint', passed: true, skipped: true });
|
|
208
|
+
console.error("[Auto-Validate] ⚠ ESLint skipped (no lint script found)");
|
|
209
|
+
}
|
|
210
|
+
} else {
|
|
211
|
+
validations.push({ name: 'ESLint', passed: true, skipped: true });
|
|
212
|
+
console.error("[Auto-Validate] ⚠ ESLint disabled in config");
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// 2. Run typecheck (with graceful degradation)
|
|
216
|
+
if (validationConfig.enableTypecheck) {
|
|
217
|
+
const typecheckCommand = validationConfig.typecheckCommand || (hasScript('typecheck') ? 'npm run typecheck' : null);
|
|
218
|
+
|
|
219
|
+
if (typecheckCommand) {
|
|
220
|
+
try {
|
|
221
|
+
execSync(typecheckCommand, { stdio: 'pipe', encoding: 'utf-8', timeout: 60000 });
|
|
222
|
+
validations.push({ name: 'Typecheck', passed: true });
|
|
223
|
+
console.error("[Auto-Validate] ✓ Typecheck passed");
|
|
224
|
+
} catch (error: any) {
|
|
225
|
+
validations.push({
|
|
226
|
+
name: 'Typecheck',
|
|
227
|
+
passed: false,
|
|
228
|
+
error: error.stdout?.toString() || error.stderr?.toString() || error.message
|
|
229
|
+
});
|
|
230
|
+
console.error("[Auto-Validate] ✗ Typecheck failed");
|
|
231
|
+
}
|
|
232
|
+
} else {
|
|
233
|
+
validations.push({ name: 'Typecheck', passed: true, skipped: true });
|
|
234
|
+
console.error("[Auto-Validate] ⚠ Typecheck skipped (no typecheck script found)");
|
|
235
|
+
}
|
|
236
|
+
} else {
|
|
237
|
+
validations.push({ name: 'Typecheck', passed: true, skipped: true });
|
|
238
|
+
console.error("[Auto-Validate] ⚠ Typecheck disabled in config");
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// 3. Run tests (if test files detected or configured)
|
|
242
|
+
const hasTestFiles = conversation ? detectTestFiles(conversation) : false;
|
|
243
|
+
const shouldRunTests = validationConfig.enableTests && (hasTestFiles || hasScript('test'));
|
|
244
|
+
|
|
245
|
+
if (shouldRunTests) {
|
|
246
|
+
const testCommand = validationConfig.testCommand || 'npm run test';
|
|
247
|
+
|
|
248
|
+
if (hasScript('test') || validationConfig.testCommand) {
|
|
249
|
+
try {
|
|
250
|
+
execSync(testCommand, {
|
|
251
|
+
stdio: 'pipe',
|
|
252
|
+
encoding: 'utf-8',
|
|
253
|
+
timeout: 120000 // 120s timeout for test suites
|
|
254
|
+
});
|
|
255
|
+
validations.push({ name: 'Tests', passed: true });
|
|
256
|
+
console.error("[Auto-Validate] ✓ Tests passed");
|
|
257
|
+
} catch (error: any) {
|
|
258
|
+
validations.push({
|
|
259
|
+
name: 'Tests',
|
|
260
|
+
passed: false,
|
|
261
|
+
error: error.stdout?.toString() || error.stderr?.toString() || error.message
|
|
262
|
+
});
|
|
263
|
+
console.error("[Auto-Validate] ✗ Tests failed");
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
validations.push({ name: 'Tests', passed: true, skipped: true });
|
|
267
|
+
console.error("[Auto-Validate] ⚠ Tests skipped (no test script found)");
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
if (!validationConfig.enableTests) {
|
|
271
|
+
validations.push({ name: 'Tests', passed: true, skipped: true });
|
|
272
|
+
console.error("[Auto-Validate] ⚠ Tests disabled in config");
|
|
273
|
+
} else {
|
|
274
|
+
validations.push({ name: 'Tests', passed: true, skipped: true });
|
|
275
|
+
console.error("[Auto-Validate] ⚠ Tests skipped (no test files detected)");
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// 4. Validate MCP tools (if MCP tools were used)
|
|
280
|
+
if (validationConfig.enableMCPValidation && conversation) {
|
|
281
|
+
const mcpResults = validateMCPTools(conversation);
|
|
282
|
+
|
|
283
|
+
if (mcpResults.length > 0) {
|
|
284
|
+
const failedMCPTools = mcpResults.filter(r => !r.success);
|
|
285
|
+
|
|
286
|
+
if (failedMCPTools.length > 0) {
|
|
287
|
+
validations.push({
|
|
288
|
+
name: 'MCP Tools',
|
|
289
|
+
passed: false,
|
|
290
|
+
error: `Failed tools: ${failedMCPTools.map(t => t.tool).join(', ')}`
|
|
291
|
+
});
|
|
292
|
+
console.error(`[Auto-Validate] ✗ ${failedMCPTools.length} MCP tool(s) failed validation`);
|
|
293
|
+
} else {
|
|
294
|
+
validations.push({ name: 'MCP Tools', passed: true });
|
|
295
|
+
console.error(`[Auto-Validate] ✓ ${mcpResults.length} MCP tool(s) validated successfully`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
} else if (!validationConfig.enableMCPValidation) {
|
|
299
|
+
console.error("[Auto-Validate] ⚠ MCP validation disabled in config");
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Generate validation report (if enabled)
|
|
303
|
+
const failedValidations = validations.filter(v => !v.passed && !v.skipped);
|
|
304
|
+
const passedCount = validations.filter(v => v.passed && !v.skipped).length;
|
|
305
|
+
const skippedCount = validations.filter(v => v.skipped).length;
|
|
306
|
+
|
|
307
|
+
const reportPath = join(process.cwd(), reportingConfig.reportDirectory, 'auto-validation-report.md');
|
|
308
|
+
|
|
309
|
+
if (reportingConfig.generateReports) {
|
|
310
|
+
const validationReport = `## Auto-Validation Report
|
|
311
|
+
|
|
312
|
+
Generated: ${new Date().toISOString()}
|
|
313
|
+
Conversation: ${input.conversation_id}
|
|
314
|
+
|
|
315
|
+
## Results
|
|
316
|
+
|
|
317
|
+
${validations.map(v => {
|
|
318
|
+
if (v.skipped) {
|
|
319
|
+
return `⚠️ ${v.name} (skipped)`;
|
|
320
|
+
}
|
|
321
|
+
return `${v.passed ? '✅' : '❌'} ${v.name}${v.error ? `:\n\`\`\`${v.error}\`\`\`` : ''}`;
|
|
322
|
+
}).join('\n\n')}
|
|
323
|
+
|
|
324
|
+
## Summary
|
|
325
|
+
|
|
326
|
+
Total: ${validations.length} checks
|
|
327
|
+
Passed: ${passedCount}
|
|
328
|
+
Failed: ${failedValidations.length}
|
|
329
|
+
Skipped: ${skippedCount}
|
|
330
|
+
${failedValidations.length === 0 ? '\n✓ All validations passed!' : '\n⚠️ Some validations failed. Please fix the issues above.'}
|
|
331
|
+
`;
|
|
332
|
+
|
|
333
|
+
writeFileSync(reportPath, validationReport);
|
|
334
|
+
console.error(`[Auto-Validate] Report saved to ${reportPath}`);
|
|
335
|
+
} else {
|
|
336
|
+
console.error("[Auto-Validate] ⚠ Report generation disabled in config");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Provide feedback
|
|
340
|
+
if (failedValidations.length > 0) {
|
|
341
|
+
const output: StopHookOutput = {
|
|
342
|
+
followup_message: `⚠️ Validation failed: ${failedValidations.map(v => v.name).join(', ')}. See ${reportPath} for details.`,
|
|
343
|
+
};
|
|
344
|
+
console.log(JSON.stringify(output));
|
|
345
|
+
} else {
|
|
346
|
+
console.error("[Auto-Validate] ✓ All validations passed");
|
|
347
|
+
console.log(JSON.stringify({}));
|
|
348
|
+
}
|