claudify-agent 0.0.0-stage → 1.0.1
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,195 @@
|
|
|
1
|
+
// .cursor/hooks/auto-learning-review.ts
|
|
2
|
+
// Automated weekly learning loop review - runs every 10 plans or weekly
|
|
3
|
+
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
const METRICS_FILE = join(process.cwd(), ".cursor/plan-metrics.json");
|
|
8
|
+
const WEEKLY_REPORT_FILE = join(process.cwd(), ".cursor/weekly-learning-report.md");
|
|
9
|
+
const ANTI_PATTERNS_FILE = join(process.cwd(), ".cursor/rules/anti-patterns.md");
|
|
10
|
+
|
|
11
|
+
interface PlanMetrics {
|
|
12
|
+
plan_accuracy: number;
|
|
13
|
+
iteration_count: number;
|
|
14
|
+
tool_efficiency: number;
|
|
15
|
+
cost_estimate?: number;
|
|
16
|
+
anti_patterns_detected?: string[];
|
|
17
|
+
timestamp: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface AntiPattern {
|
|
21
|
+
id: string;
|
|
22
|
+
name: string;
|
|
23
|
+
count: number;
|
|
24
|
+
lastOccurrence: string;
|
|
25
|
+
suggestion: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Read input from stdin (hook trigger)
|
|
29
|
+
const stdin = await new Promise<string>((resolve) => {
|
|
30
|
+
let data = "";
|
|
31
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
32
|
+
process.stdin.on("end", () => resolve(data));
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
console.error("[Auto Learning Review] Starting automated review...");
|
|
36
|
+
|
|
37
|
+
// Load metrics
|
|
38
|
+
if (!existsSync(METRICS_FILE)) {
|
|
39
|
+
console.error("[Auto Learning Review] No metrics file found. Run some plans first.");
|
|
40
|
+
process.exit(0);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const allMetrics: PlanMetrics[] = JSON.parse(readFileSync(METRICS_FILE, "utf-8"));
|
|
44
|
+
const recentMetrics = allMetrics.slice(-10); // Last 10 plans
|
|
45
|
+
|
|
46
|
+
if (recentMetrics.length < 3) {
|
|
47
|
+
console.error("[Auto Learning Review] Not enough data (need 3+ plans). Current:", recentMetrics.length);
|
|
48
|
+
process.exit(0);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Calculate averages
|
|
52
|
+
const avgAccuracy = recentMetrics.reduce((sum, m) => sum + m.plan_accuracy, 0) / recentMetrics.length;
|
|
53
|
+
const avgIterations = recentMetrics.reduce((sum, m) => sum + m.iteration_count, 0) / recentMetrics.length;
|
|
54
|
+
const avgEfficiency = recentMetrics.reduce((sum, m) => sum + m.tool_efficiency, 0) / recentMetrics.length;
|
|
55
|
+
const totalCost = recentMetrics.reduce((sum, m) => sum + (m.cost_estimate || 0), 0);
|
|
56
|
+
|
|
57
|
+
// Detect patterns
|
|
58
|
+
const patternCounts: Record<string, number> = {};
|
|
59
|
+
recentMetrics.forEach(m => {
|
|
60
|
+
if (m.anti_patterns_detected) {
|
|
61
|
+
m.anti_patterns_detected.forEach(pattern => {
|
|
62
|
+
patternCounts[pattern] = (patternCounts[pattern] || 0) + 1;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Identify repeated patterns (3+ occurrences)
|
|
68
|
+
const repeatedPatterns: AntiPattern[] = Object.entries(patternCounts)
|
|
69
|
+
.filter(([_, count]) => count >= 3)
|
|
70
|
+
.map(([name, count]) => ({
|
|
71
|
+
id: name.toLowerCase().replace(/\s+/g, '-'),
|
|
72
|
+
name,
|
|
73
|
+
count,
|
|
74
|
+
lastOccurrence: recentMetrics.find(m => m.anti_patterns_detected?.includes(name))?.timestamp || '',
|
|
75
|
+
suggestion: getSuggestionForPattern(name)
|
|
76
|
+
}));
|
|
77
|
+
|
|
78
|
+
// Generate report
|
|
79
|
+
const report = generateReport({
|
|
80
|
+
planCount: recentMetrics.length,
|
|
81
|
+
avgAccuracy,
|
|
82
|
+
avgIterations,
|
|
83
|
+
avgEfficiency,
|
|
84
|
+
totalCost,
|
|
85
|
+
repeatedPatterns,
|
|
86
|
+
trend: calculateTrend(recentMetrics)
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Write report
|
|
90
|
+
writeFileSync(WEEKLY_REPORT_FILE, report, 'utf-8');
|
|
91
|
+
console.error(`[Auto Learning Review] Report generated: ${WEEKLY_REPORT_FILE}`);
|
|
92
|
+
|
|
93
|
+
// Auto-update rules if patterns detected
|
|
94
|
+
if (repeatedPatterns.length > 0) {
|
|
95
|
+
console.error(`[Auto Learning Review] Detected ${repeatedPatterns.length} repeated patterns (3+ occurrences)`);
|
|
96
|
+
console.error("[Auto Learning Review] Review .cursor/weekly-learning-report.md for details");
|
|
97
|
+
} else {
|
|
98
|
+
console.error("[Auto Learning Review] No repeated patterns detected. Great job!");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getSuggestionForPattern(patternName: string): string {
|
|
102
|
+
const suggestions: Record<string, string> = {
|
|
103
|
+
"Over-Engineering": "Follow tool escalation protocol: Tier 1 (Read/Glob/Grep) → Tier 2 → Tier 3 → Tier 4",
|
|
104
|
+
"Planning Without Research": "Always run SemanticSearch before creating plans. Spend 2-3 min on research.",
|
|
105
|
+
"Repeated Execution Failures": "Add validation subagent before plan execution. Create checkpoints.",
|
|
106
|
+
"MCP Overuse": "Use MCP tools only when essential. Prefer built-in tools for simple tasks.",
|
|
107
|
+
"Skipping Validation": "Enable auto-validate hook. Run ReadLints after EVERY code change.",
|
|
108
|
+
"Context Bloat": "Keep rules under 20 lines. Use references over content."
|
|
109
|
+
};
|
|
110
|
+
return suggestions[patternName] || "Review pattern in learning-loop skill documentation.";
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function calculateTrend(metrics: PlanMetrics[]): "improving" | "stable" | "declining" {
|
|
114
|
+
if (metrics.length < 5) return "stable";
|
|
115
|
+
|
|
116
|
+
const firstHalf = metrics.slice(0, Math.floor(metrics.length / 2));
|
|
117
|
+
const secondHalf = metrics.slice(Math.floor(metrics.length / 2));
|
|
118
|
+
|
|
119
|
+
const firstAvg = firstHalf.reduce((sum, m) => sum + m.plan_accuracy, 0) / firstHalf.length;
|
|
120
|
+
const secondAvg = secondHalf.reduce((sum, m) => sum + m.plan_accuracy, 0) / secondHalf.length;
|
|
121
|
+
|
|
122
|
+
if (secondAvg > firstAvg + 5) return "improving";
|
|
123
|
+
if (secondAvg < firstAvg - 5) return "declining";
|
|
124
|
+
return "stable";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function generateReport(data: any): string {
|
|
128
|
+
const date = new Date().toISOString().split('T')[0];
|
|
129
|
+
|
|
130
|
+
return `# Weekly Learning Report
|
|
131
|
+
|
|
132
|
+
**Generated**: ${date}
|
|
133
|
+
**Analysis Period**: Last ${data.planCount} plans
|
|
134
|
+
**Trend**: ${data.trend.toUpperCase()}
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Performance Summary
|
|
139
|
+
|
|
140
|
+
| Metric | Current | Target | Status |
|
|
141
|
+
|--------|---------|--------|--------|
|
|
142
|
+
| Avg Accuracy | ${data.avgAccuracy.toFixed(1)}% | >80% | ${data.avgAccuracy >= 80 ? '✅' : '⚠️'} |
|
|
143
|
+
| Avg Iterations | ${data.avgIterations.toFixed(1)} | <2.0 | ${data.avgIterations < 2 ? '✅' : '⚠️'} |
|
|
144
|
+
| Tool Efficiency | ${data.avgEfficiency.toFixed(1)}% | >70% | ${data.avgEfficiency >= 70 ? '✅' : '⚠️'} |
|
|
145
|
+
| Total Cost | ${data.totalCost.toLocaleString()} tokens | Optimize | ${data.totalCost < 100000 ? '✅' : '⚠️'} |
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Repeated Patterns Detected (3+ Occurrences)
|
|
150
|
+
|
|
151
|
+
${data.repeatedPatterns.length > 0 ? data.repeatedPatterns.map((p: AntiPattern) => `### ${p.name}
|
|
152
|
+
|
|
153
|
+
**Occurrences**: ${p.count} times
|
|
154
|
+
**Last Seen**: ${p.lastOccurrence || 'Unknown'}
|
|
155
|
+
**Suggestion**: ${p.suggestion}
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
`).join('\n') : '✅ No repeated patterns detected! Great job maintaining best practices.'}
|
|
159
|
+
|
|
160
|
+
## Action Items
|
|
161
|
+
|
|
162
|
+
${data.repeatedPatterns.length > 0 ? `
|
|
163
|
+
### Critical (Fix This Week)
|
|
164
|
+
${data.repeatedPatterns.filter((p: AntiPattern) => p.count >= 5).map((p: AntiPattern) => `- [ ] ${p.name}: ${p.suggestion}`).join('\n') || '- [ ] None'}
|
|
165
|
+
|
|
166
|
+
### Important (Fix Next Week)
|
|
167
|
+
${data.repeatedPatterns.filter((p: AntiPattern) => p.count >= 3 && p.count < 5).map((p: AntiPattern) => `- [ ] ${p.name}: ${p.suggestion}`).join('\n') || '- [ ] None'}
|
|
168
|
+
` : `
|
|
169
|
+
### Continue Current Practices
|
|
170
|
+
- [ ] Maintain research-first approach
|
|
171
|
+
- [ ] Keep following tool escalation protocol
|
|
172
|
+
- [ ] Continue automatic validation
|
|
173
|
+
`}
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Trend Analysis
|
|
178
|
+
|
|
179
|
+
${data.trend === 'improving' ? '📈 **Performance is IMPROVING** - Keep doing what works!' :
|
|
180
|
+
data.trend === 'declining' ? '📉 **Performance is DECLINING** - Review recent changes and patterns.' :
|
|
181
|
+
'➡️ **Performance is STABLE** - Look for optimization opportunities.'}
|
|
182
|
+
|
|
183
|
+
---
|
|
184
|
+
|
|
185
|
+
## Next Review
|
|
186
|
+
|
|
187
|
+
**Scheduled**: In 10 plans or 7 days (whichever comes first)
|
|
188
|
+
**Focus Areas**:
|
|
189
|
+
${data.repeatedPatterns.length > 0 ? data.repeatedPatterns.slice(0, 3).map((p: AntiPattern) => `- ${p.name}`) : '- Continue monitoring current practices'}
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
*This report was automatically generated by the auto-learning-review hook*
|
|
194
|
+
`;
|
|
195
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
|
|
5
|
+
interface StopHookInput {
|
|
6
|
+
conversation_id: string;
|
|
7
|
+
status: "completed" | "aborted" | "error";
|
|
8
|
+
loop_count: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface StopHookOutput {
|
|
12
|
+
followup_message?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const LINT_REPORT_PATH = join(process.cwd(), ".cursor/lint-report.md");
|
|
16
|
+
|
|
17
|
+
// Read input from stdin
|
|
18
|
+
const stdin = await new Promise<string>((resolve) => {
|
|
19
|
+
let data = "";
|
|
20
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
21
|
+
process.stdin.on("end", () => resolve(data));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const input: StopHookInput = JSON.parse(stdin);
|
|
25
|
+
|
|
26
|
+
// Only lint on completion
|
|
27
|
+
if (input.status !== "completed") {
|
|
28
|
+
console.log(JSON.stringify({}));
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Check if any code changes were made (simplified check)
|
|
33
|
+
// In real implementation, would track file changes in conversation
|
|
34
|
+
const codeChanged = true; // Assume code changed for now
|
|
35
|
+
|
|
36
|
+
if (!codeChanged) {
|
|
37
|
+
console.log(JSON.stringify({}));
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Run ESLint on changed files
|
|
42
|
+
try {
|
|
43
|
+
// Try to run ESLint with auto-fix
|
|
44
|
+
console.log("Running ESLint with auto-fix...");
|
|
45
|
+
|
|
46
|
+
let lintOutput = "";
|
|
47
|
+
let hasErrors = false;
|
|
48
|
+
let hasWarnings = false;
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
// First try auto-fix
|
|
52
|
+
lintOutput = execSync("npm run lint -- --fix", {
|
|
53
|
+
encoding: "utf-8",
|
|
54
|
+
stdio: "pipe",
|
|
55
|
+
timeout: 60000 // 60s timeout
|
|
56
|
+
}).toString();
|
|
57
|
+
} catch (error: any) {
|
|
58
|
+
lintOutput = error.stdout?.toString() || error.stderr?.toString() || error.message;
|
|
59
|
+
hasErrors = true;
|
|
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
|
+
}
|
|
@@ -0,0 +1,354 @@
|
|
|
1
|
+
// .cursor/hooks/auto-security-review.ts
|
|
2
|
+
// Automatic security vulnerability scanning for AI-generated code
|
|
3
|
+
|
|
4
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
5
|
+
import { join } from "path";
|
|
6
|
+
|
|
7
|
+
export {};
|
|
8
|
+
|
|
9
|
+
interface SecurityHookInput {
|
|
10
|
+
conversation_id: string;
|
|
11
|
+
status: "completed" | "aborted" | "error";
|
|
12
|
+
loop_count: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface SecurityFinding {
|
|
16
|
+
severity: "critical" | "high" | "medium" | "low";
|
|
17
|
+
type: string;
|
|
18
|
+
description: string;
|
|
19
|
+
location?: string;
|
|
20
|
+
suggestion: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface SecurityConfig {
|
|
24
|
+
enableSecurityReview?: boolean;
|
|
25
|
+
severityThreshold?: "critical" | "high" | "medium" | "low";
|
|
26
|
+
scanPatterns?: {
|
|
27
|
+
hardcodedCredentials?: boolean;
|
|
28
|
+
sqlInjection?: boolean;
|
|
29
|
+
xss?: boolean;
|
|
30
|
+
insecureCrypto?: boolean;
|
|
31
|
+
pathTraversal?: boolean;
|
|
32
|
+
commandInjection?: boolean;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Security pattern definitions (OWASP Top 10 inspired)
|
|
37
|
+
const SECURITY_PATTERNS = {
|
|
38
|
+
hardcodedCredentials: [
|
|
39
|
+
{
|
|
40
|
+
pattern: /(password|passwd|pwd)\s*[=:]\s*["'][^"']+["']/gi,
|
|
41
|
+
type: "Hardcoded Password",
|
|
42
|
+
severity: "critical" as const,
|
|
43
|
+
suggestion: "Use environment variables or secure secret management (e.g., AWS Secrets Manager, Azure Key Vault)"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
pattern: /(api[_-]?key|apikey)\s*[=:]\s*["'][^"']+["']/gi,
|
|
47
|
+
type: "Hardcoded API Key",
|
|
48
|
+
severity: "critical" as const,
|
|
49
|
+
suggestion: "Store API keys in environment variables or secure vault"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
pattern: /(secret|token|auth)\s*[=:]\s*["'][^"']+["']/gi,
|
|
53
|
+
type: "Hardcoded Secret/Token",
|
|
54
|
+
severity: "critical" as const,
|
|
55
|
+
suggestion: "Use environment variables for secrets"
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /AWS[_A-Z]*KEY|AWS[_A-Z]*SECRET/gi,
|
|
59
|
+
type: "AWS Credentials",
|
|
60
|
+
severity: "critical" as const,
|
|
61
|
+
suggestion: "Use IAM roles or AWS Secrets Manager"
|
|
62
|
+
}
|
|
63
|
+
],
|
|
64
|
+
sqlInjection: [
|
|
65
|
+
{
|
|
66
|
+
pattern: /(execute|query|raw)\s*\(\s*`[^`]*\$\{[^}]+\}/gi,
|
|
67
|
+
type: "Potential SQL Injection",
|
|
68
|
+
severity: "critical" as const,
|
|
69
|
+
suggestion: "Use parameterized queries or ORM methods instead of string interpolation"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
pattern: /execute\s*\(\s*["'][^"']*["']\s*\+/gi,
|
|
73
|
+
type: "SQL String Concatenation",
|
|
74
|
+
severity: "critical" as const,
|
|
75
|
+
suggestion: "Use parameterized queries"
|
|
76
|
+
}
|
|
77
|
+
],
|
|
78
|
+
xss: [
|
|
79
|
+
{
|
|
80
|
+
pattern: /dangerouslySetInnerHTML\s*=/gi,
|
|
81
|
+
type: "Dangerous HTML Rendering",
|
|
82
|
+
severity: "high" as const,
|
|
83
|
+
suggestion: "Avoid dangerouslySetInnerHTML or sanitize input with DOMPurify"
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
pattern: /innerHTML\s*=/gi,
|
|
87
|
+
type: "Inner HTML Assignment",
|
|
88
|
+
severity: "high" as const,
|
|
89
|
+
suggestion: "Use textContent or sanitize HTML before assignment"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
pattern: /eval\s*\(/gi,
|
|
93
|
+
type: "Eval Usage",
|
|
94
|
+
severity: "high" as const,
|
|
95
|
+
suggestion: "Avoid eval() - use safer alternatives"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
insecureCrypto: [
|
|
99
|
+
{
|
|
100
|
+
pattern: /crypto\s*\.\s*createHash\s*\(\s*["']md5["']/gi,
|
|
101
|
+
type: "Weak Hash Algorithm (MD5)",
|
|
102
|
+
severity: "high" as const,
|
|
103
|
+
suggestion: "Use SHA-256 or stronger (crypto.createHash('sha256'))"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
pattern: /crypto\s*\.\s*createHash\s*\(\s*["']sha1["']/gi,
|
|
107
|
+
type: "Weak Hash Algorithm (SHA1)",
|
|
108
|
+
severity: "medium" as const,
|
|
109
|
+
suggestion: "Use SHA-256 or stronger for security-critical applications"
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
pattern: /Math\.random\s*\(/gi,
|
|
113
|
+
type: "Insecure Random Number Generator",
|
|
114
|
+
severity: "medium" as const,
|
|
115
|
+
suggestion: "Use crypto.randomBytes() for security-sensitive randomness"
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
pathTraversal: [
|
|
119
|
+
{
|
|
120
|
+
pattern: /fs\s*\.\s*(readFile|writeFile|appendFile)\s*\([^)]*\+/gi,
|
|
121
|
+
type: "Potential Path Traversal",
|
|
122
|
+
severity: "high" as const,
|
|
123
|
+
suggestion: "Validate and sanitize file paths, use path.resolve() and path.normalize()"
|
|
124
|
+
},
|
|
125
|
+
{
|
|
126
|
+
pattern: /require\s*\(\s*[^)]*\+/gi,
|
|
127
|
+
type: "Dynamic Require with User Input",
|
|
128
|
+
severity: "high" as const,
|
|
129
|
+
suggestion: "Avoid dynamic require with user-controlled paths"
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
commandInjection: [
|
|
133
|
+
{
|
|
134
|
+
pattern: /exec\s*\(\s*`[^`]*\$\{[^}]+\}/gi,
|
|
135
|
+
type: "Command Injection via Template Literal",
|
|
136
|
+
severity: "critical" as const,
|
|
137
|
+
suggestion: "Avoid shell commands with user input, use execFile with arguments array"
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
pattern: /spawn\s*\(\s*[^)]*\+/gi,
|
|
141
|
+
type: "Command Injection via String Concatenation",
|
|
142
|
+
severity: "critical" as const,
|
|
143
|
+
suggestion: "Use spawn with arguments array instead of string concatenation"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
pattern: /child_process\s*\.\s*exec\s*\(/gi,
|
|
147
|
+
type: "Unsafe exec Usage",
|
|
148
|
+
severity: "medium" as const,
|
|
149
|
+
suggestion: "Prefer execFile or spawn with argument separation"
|
|
150
|
+
}
|
|
151
|
+
]
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
// Read input from stdin
|
|
155
|
+
const stdin = await new Promise<string>((resolve) => {
|
|
156
|
+
let data = "";
|
|
157
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
158
|
+
process.stdin.on("end", () => resolve(data));
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
const input: SecurityHookInput = JSON.parse(stdin);
|
|
162
|
+
|
|
163
|
+
// Load configuration
|
|
164
|
+
const configPath = join(process.cwd(), '.cursor/hooks.config.json');
|
|
165
|
+
const config: SecurityConfig = existsSync(configPath)
|
|
166
|
+
? JSON.parse(readFileSync(configPath, 'utf-8'))
|
|
167
|
+
: {};
|
|
168
|
+
|
|
169
|
+
const securityConfig = {
|
|
170
|
+
enableSecurityReview: (config as any).validation?.enableSecurityReview !== false,
|
|
171
|
+
severityThreshold: (config as any).validation?.securitySeverityThreshold || "high",
|
|
172
|
+
scanPatterns: (config as any).validation?.securityScanPatterns || {
|
|
173
|
+
hardcodedCredentials: true,
|
|
174
|
+
sqlInjection: true,
|
|
175
|
+
xss: true,
|
|
176
|
+
insecureCrypto: true,
|
|
177
|
+
pathTraversal: true,
|
|
178
|
+
commandInjection: true
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
// Only run on completion
|
|
183
|
+
if (input.status !== "completed") {
|
|
184
|
+
console.log(JSON.stringify({}));
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (!securityConfig.enableSecurityReview) {
|
|
189
|
+
console.error("[Security Review] ⚠ Security review disabled in config");
|
|
190
|
+
console.log(JSON.stringify({}));
|
|
191
|
+
process.exit(0);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
console.error("[Security Review] Running automatic security scan...");
|
|
195
|
+
|
|
196
|
+
const findings: SecurityFinding[] = [];
|
|
197
|
+
|
|
198
|
+
// Try to scan changed files
|
|
199
|
+
const conversationFile = join(process.cwd(), `.cursor/conversations/${input.conversation_id}.json`);
|
|
200
|
+
let conversation;
|
|
201
|
+
try {
|
|
202
|
+
if (existsSync(conversationFile)) {
|
|
203
|
+
conversation = JSON.parse(readFileSync(conversationFile, 'utf-8'));
|
|
204
|
+
}
|
|
205
|
+
} catch {
|
|
206
|
+
console.error("[Security Review] Could not read conversation file, proceeding with scan");
|
|
207
|
+
conversation = null;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Extract code from conversation messages
|
|
211
|
+
function extractCodeFromConversation(conv: any): string[] {
|
|
212
|
+
const codeBlocks: string[] = [];
|
|
213
|
+
|
|
214
|
+
if (!conv?.messages) return codeBlocks;
|
|
215
|
+
|
|
216
|
+
for (const message of conv.messages) {
|
|
217
|
+
if (message.content) {
|
|
218
|
+
// Extract code blocks
|
|
219
|
+
const codeBlockRegex = /```(?:typescript|javascript|tsx|ts|js)?\n([\s\S]*?)```/g;
|
|
220
|
+
let match;
|
|
221
|
+
while ((match = codeBlockRegex.exec(message.content)) !== null) {
|
|
222
|
+
codeBlocks.push(match[1]);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return codeBlocks;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Scan code for security issues
|
|
231
|
+
function scanCodeForSecurity(code: string, patternCategory: string): SecurityFinding[] {
|
|
232
|
+
const categoryFindings: SecurityFinding[] = [];
|
|
233
|
+
const patterns = SECURITY_PATTERNS[patternCategory as keyof typeof SECURITY_PATTERNS];
|
|
234
|
+
|
|
235
|
+
if (!patterns || !securityConfig.scanPatterns[patternCategory as keyof typeof securityConfig.scanPatterns]) {
|
|
236
|
+
return categoryFindings;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const { pattern, type, severity, suggestion } of patterns) {
|
|
240
|
+
const matches = code.match(pattern);
|
|
241
|
+
if (matches) {
|
|
242
|
+
categoryFindings.push({
|
|
243
|
+
severity,
|
|
244
|
+
type,
|
|
245
|
+
description: `Found ${matches.length} occurrence(s) of potentially insecure pattern`,
|
|
246
|
+
suggestion
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return categoryFindings;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Scan all pattern categories
|
|
255
|
+
const codeToScan = conversation ? extractCodeFromConversation(conversation) : [];
|
|
256
|
+
|
|
257
|
+
// If no code extracted, skip scan
|
|
258
|
+
if (codeToScan.length === 0) {
|
|
259
|
+
console.error("[Security Review] ⚠ No code blocks found to scan");
|
|
260
|
+
console.log(JSON.stringify({}));
|
|
261
|
+
process.exit(0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const combinedCode = codeToScan.join('\n');
|
|
265
|
+
|
|
266
|
+
// Run security scans
|
|
267
|
+
const patternCategories = Object.keys(SECURITY_PATTERNS);
|
|
268
|
+
for (const category of patternCategories) {
|
|
269
|
+
const categoryFindings = scanCodeForSecurity(combinedCode, category);
|
|
270
|
+
findings.push(...categoryFindings);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Filter by severity threshold
|
|
274
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
275
|
+
const thresholdLevel = severityOrder[securityConfig.severityThreshold];
|
|
276
|
+
|
|
277
|
+
const filteredFindings = findings.filter(
|
|
278
|
+
finding => severityOrder[finding.severity] <= thresholdLevel
|
|
279
|
+
);
|
|
280
|
+
|
|
281
|
+
// Generate security report
|
|
282
|
+
const criticalCount = filteredFindings.filter(f => f.severity === "critical").length;
|
|
283
|
+
const highCount = filteredFindings.filter(f => f.severity === "high").length;
|
|
284
|
+
const mediumCount = filteredFindings.filter(f => f.severity === "medium").length;
|
|
285
|
+
const lowCount = filteredFindings.filter(f => f.severity === "low").length;
|
|
286
|
+
|
|
287
|
+
const reportPath = join(process.cwd(), '.cursor/security-report.md');
|
|
288
|
+
|
|
289
|
+
const securityReport = `## Security Scan Report
|
|
290
|
+
|
|
291
|
+
Generated: ${new Date().toISOString()}
|
|
292
|
+
Conversation: ${input.conversation_id}
|
|
293
|
+
Severity Threshold: ${securityConfig.severityThreshold}
|
|
294
|
+
|
|
295
|
+
## Summary
|
|
296
|
+
|
|
297
|
+
- 🔴 Critical: ${criticalCount}
|
|
298
|
+
- 🟠 High: ${highCount}
|
|
299
|
+
- 🟡 Medium: ${mediumCount}
|
|
300
|
+
- 🟢 Low: ${lowCount}
|
|
301
|
+
- **Total Findings**: ${filteredFindings.length}
|
|
302
|
+
|
|
303
|
+
## Findings
|
|
304
|
+
|
|
305
|
+
${filteredFindings.length === 0 ? '✅ No security issues found!' : filteredFindings.map((finding, i) => `
|
|
306
|
+
### ${i + 1}. ${finding.type}
|
|
307
|
+
|
|
308
|
+
**Severity**: ${finding.severity.toUpperCase()}
|
|
309
|
+
|
|
310
|
+
**Description**: ${finding.description}
|
|
311
|
+
|
|
312
|
+
**Recommendation**: ${finding.suggestion}
|
|
313
|
+
|
|
314
|
+
---
|
|
315
|
+
`).join('\n')}
|
|
316
|
+
|
|
317
|
+
## Security Checklist (OWASP Top 10)
|
|
318
|
+
|
|
319
|
+
- [ ] All user input validated and sanitized
|
|
320
|
+
- [ ] SQL injection prevented (parameterized queries)
|
|
321
|
+
- [ ] XSS prevented (escaping/sanitization)
|
|
322
|
+
- [ ] Authentication/authorization verified
|
|
323
|
+
- [ ] No hardcoded credentials or secrets
|
|
324
|
+
- [ ] Error handling doesn't leak sensitive info
|
|
325
|
+
- [ ] Rate limiting on auth endpoints
|
|
326
|
+
- [ ] Secure random number generation used
|
|
327
|
+
- [ ] Strong cryptographic algorithms used
|
|
328
|
+
- [ ] File paths validated and normalized
|
|
329
|
+
|
|
330
|
+
## Next Steps
|
|
331
|
+
|
|
332
|
+
${criticalCount > 0 ? '🚨 CRITICAL issues found! Address immediately before deployment.' :
|
|
333
|
+
highCount > 0 ? '⚠️ HIGH severity issues found. Review and fix before production.' :
|
|
334
|
+
mediumCount > 0 ? '📋 Medium severity issues found. Schedule for next sprint.' :
|
|
335
|
+
lowCount > 0 ? '💡 Low severity issues found. Consider fixing for best practices.' :
|
|
336
|
+
'✅ No security issues detected at current threshold.'}
|
|
337
|
+
`;
|
|
338
|
+
|
|
339
|
+
writeFileSync(reportPath, securityReport);
|
|
340
|
+
console.error(`[Security Review] Report saved to ${reportPath}`);
|
|
341
|
+
|
|
342
|
+
// Provide feedback to user
|
|
343
|
+
if (criticalCount > 0 || highCount > 0) {
|
|
344
|
+
const output = {
|
|
345
|
+
followup_message: `🚨 Security scan found ${criticalCount} critical and ${highCount} high severity issues. See \`.cursor/security-report.md\` for details.`,
|
|
346
|
+
};
|
|
347
|
+
console.log(JSON.stringify(output));
|
|
348
|
+
} else if (filteredFindings.length > 0) {
|
|
349
|
+
console.error(`[Security Review] ⚠️ Found ${filteredFindings.length} security finding(s). Report saved.`);
|
|
350
|
+
console.log(JSON.stringify({}));
|
|
351
|
+
} else {
|
|
352
|
+
console.error("[Security Review] ✓ No security issues found");
|
|
353
|
+
console.log(JSON.stringify({}));
|
|
354
|
+
}
|