coderev-cli 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/src/rules.js ADDED
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Custom rule configuration for coderev.
3
+ *
4
+ * Config file (.coderevrc.json) can include a "rules" array:
5
+ *
6
+ * ```json
7
+ * {
8
+ * "rules": {
9
+ * "maxLineLength": 100,
10
+ * "predefined": ["security", "performance", "style", "typescript"],
11
+ * "custom": [
12
+ * {
13
+ * "name": "no-console-log",
14
+ * "pattern": "console\\.log\\(",
15
+ * "severity": "warning",
16
+ * "message": "Avoid console.log in production code",
17
+ * "filePattern": "src\/**\/*.js"
18
+ * }
19
+ * ]
20
+ * }
21
+ * }
22
+ * ```
23
+ */
24
+
25
+ const DEFAULT_PREDEFINED = ['security', 'performance', 'style'];
26
+
27
+ // Map file extensions to detected languages
28
+ const EXTENSION_LANG_MAP = {
29
+ '.js': 'javascript',
30
+ '.jsx': 'javascript',
31
+ '.ts': 'typescript',
32
+ '.tsx': 'typescript',
33
+ '.py': 'python',
34
+ '.rs': 'rust',
35
+ '.go': 'go',
36
+ '.java': 'java',
37
+ '.rb': 'ruby',
38
+ '.php': 'php',
39
+ '.swift': 'swift',
40
+ '.kt': 'kotlin',
41
+ '.c': 'c',
42
+ '.cpp': 'cpp',
43
+ '.h': 'c',
44
+ '.cs': 'csharp',
45
+ '.sql': 'sql',
46
+ '.yaml': 'yaml',
47
+ '.yml': 'yaml',
48
+ '.json': 'json',
49
+ '.html': 'html',
50
+ '.css': 'css',
51
+ '.scss': 'scss',
52
+ };
53
+
54
+ // Language-specific rule descriptions
55
+ const LANG_SPECIFIC_RULES = {
56
+ javascript: [
57
+ '- Check async/await usage: avoid mixing .then() and await in the same chain',
58
+ '- Check for common JS pitfalls: == vs ===, var vs let/const',
59
+ '- Verify proper error handling in async functions (try/catch or .catch())',
60
+ '- Check for memory leaks: event listeners not removed, closures holding references',
61
+ '- Validate import/export usage and circular dependencies',
62
+ ],
63
+ typescript: [
64
+ '- Enforce strict TypeScript: avoid `any`, prefer `unknown`, use proper generics',
65
+ '- Check interface vs type usage consistency',
66
+ '- Verify proper use of strictNullChecks (no implicit undefined)',
67
+ '- Check for unsafe type assertions (as) and type casts',
68
+ '- Validate generic constraints are properly defined',
69
+ ],
70
+ python: [
71
+ '- Check for PEP 8 style violations (naming, imports, whitespace)',
72
+ '- Verify proper exception handling: avoid bare except, specify exception types',
73
+ '- Check for common Python anti-patterns: mutable default args, import *',
74
+ '- Verify async/await usage in asyncio code',
75
+ '- Check for proper context manager usage (with statements)',
76
+ ],
77
+ rust: [
78
+ '- Check for unsafe code blocks and verify they are justified',
79
+ '- Verify proper error handling: prefer Result/Option over unwrap()/expect()',
80
+ '- Check lifetime annotations for correctness',
81
+ '- Verify proper use of ownership and borrowing (no unnecessary clones)',
82
+ '- Check for correct use of async/await with tokio or async-std',
83
+ ],
84
+ go: [
85
+ '- Verify proper error handling: always check returned errors',
86
+ '- Check for common Go pitfalls: shadowed variables, incorrect use of goroutines',
87
+ '- Verify proper context propagation through function calls',
88
+ '- Check for data races: proper use of mutexes or channels',
89
+ '- Validate interface compliance and naming conventions',
90
+ ],
91
+ java: [
92
+ '- Check for proper null handling: Optional vs null checks',
93
+ '- Verify proper exception handling: checked vs unchecked exceptions',
94
+ '- Check for common Java pitfalls: == vs .equals(), raw types',
95
+ '- Verify proper use of streams and lambdas',
96
+ '- Check for thread safety issues in concurrent code',
97
+ ],
98
+ sql: [
99
+ '- Check for SQL injection vulnerabilities: parameterized queries vs string concatenation',
100
+ '- Verify proper indexing: avoid full table scans in WHERE clauses',
101
+ '- Check for N+1 query problems in JOIN-heavy queries',
102
+ '- Verify proper use of transactions for multi-step operations',
103
+ '- Check for large IN-clauses that may cause performance issues',
104
+ ],
105
+ };
106
+
107
+ /**
108
+ * Get the list of active review rules for prompt generation.
109
+ * @param {object} rulesConfig - The rules section from config
110
+ * @param {string} [diff] - Optional diff content for language detection
111
+ * @returns {string[]} Array of rule description strings
112
+ */
113
+ function getRuleDescriptions(rulesConfig, diff) {
114
+ if (!rulesConfig) rulesConfig = {};
115
+ if (!rulesConfig.predefined) rulesConfig.predefined = DEFAULT_PREDEFINED;
116
+
117
+ const descriptions = [];
118
+
119
+ // Built-in toggles
120
+ if (rulesConfig.maxLineLength) {
121
+ descriptions.push(`- Maximum line length: ${rulesConfig.maxLineLength} characters`);
122
+ }
123
+
124
+ // Predefined rule sets
125
+ descriptions.push(...getPredefinedDescriptions(rulesConfig.predefined));
126
+
127
+ // Auto-detect language from diff and add language-specific rules
128
+ if (diff && rulesConfig.autoLanguage !== false) {
129
+ const langs = detectLanguages(diff);
130
+ for (const lang of langs) {
131
+ const langRules = LANG_SPECIFIC_RULES[lang];
132
+ if (langRules) {
133
+ descriptions.push(`\n# ${lang.toUpperCase()}-specific checks:`);
134
+ descriptions.push(...langRules);
135
+ }
136
+ }
137
+ }
138
+
139
+ // Custom rules
140
+ if (Array.isArray(rulesConfig.custom)) {
141
+ for (const rule of rulesConfig.custom) {
142
+ if (rule.enabled === false) continue;
143
+ const sev = rule.severity ? ` [${rule.severity}]` : '';
144
+ descriptions.push(`- ${rule.message || rule.name}${sev}`);
145
+ if (rule.filePattern) {
146
+ descriptions.push(` Applies to: ${rule.filePattern}`);
147
+ }
148
+ }
149
+ }
150
+
151
+ return descriptions;
152
+ }
153
+
154
+ function getPredefinedDescriptions(selected) {
155
+ const all = {
156
+ security: '- Check for security vulnerabilities (injection, XSS, auth issues, secrets exposure)',
157
+ performance: '- Check for performance issues (unnecessary loops, memory leaks, N+1 queries)',
158
+ style: '- Check code style and consistency (spacing, imports, unused variables)',
159
+ typescript: '- Enforce TypeScript best practices (strict types, avoid `any`, use proper generics)',
160
+ react: '- Check React best practices (hooks rules, key props, component naming)',
161
+ node: '- Check Node.js best practices (error handling, async patterns, file system safety)',
162
+ naming: '- Enforce naming conventions (camelCase, PascalCase, CONSTANT_CASE as appropriate)',
163
+ testing: '- Check test quality (assertions, edge cases, test isolation)',
164
+ };
165
+
166
+ return selected
167
+ .filter((key) => all[key])
168
+ .map((key) => all[key]);
169
+ }
170
+
171
+ /**
172
+ * Detect programming languages from a git diff.
173
+ * Scans file extensions in diff headers (+++ b/...).
174
+ * @param {string} diff - Git diff text
175
+ * @returns {string[]} Array of language identifiers
176
+ */
177
+ function detectLanguages(diff) {
178
+ if (!diff) return [];
179
+
180
+ // Extract file extensions from diff headers
181
+ const extSet = new Set();
182
+ const lines = diff.split('\n');
183
+ for (const line of lines) {
184
+ const match = line.match(/^\+\+\+ b\/(.+)/);
185
+ if (match) {
186
+ const filename = match[1];
187
+ const ext = '.' + filename.split('.').pop();
188
+ const lang = EXTENSION_LANG_MAP[ext];
189
+ if (lang) extSet.add(lang);
190
+ }
191
+ }
192
+ return Array.from(extSet);
193
+ }
194
+
195
+ module.exports = { getRuleDescriptions, DEFAULT_PREDEFINED, detectLanguages, LANG_SPECIFIC_RULES };
package/src/stats.js ADDED
@@ -0,0 +1,126 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const os = require('os');
4
+
5
+ const DB_PATH = path.join(os.homedir(), '.coderev', 'history.json');
6
+
7
+ /**
8
+ * Record a review result to history.
9
+ * @param {object} result - The review result object
10
+ * @param {string} [diffId] - Optional diff hash
11
+ */
12
+ function recordReview(result, diffId) {
13
+ try {
14
+ const db = loadDB();
15
+ db.reviews.push({
16
+ timestamp: Date.now(),
17
+ diffId: diffId || '',
18
+ score: result.score || 0,
19
+ summary: result.summary || '',
20
+ issueCount: (result.issues || []).length,
21
+ suggestionCount: (result.suggestions || []).length,
22
+ praiseCount: (result.praise || []).length,
23
+ topIssues: (result.issues || []).slice(0, 5).map(i => ({
24
+ type: i.type,
25
+ severity: i.severity,
26
+ message: i.message,
27
+ })),
28
+ });
29
+
30
+ // Keep only last 1000 reviews
31
+ if (db.reviews.length > 1000) {
32
+ db.reviews = db.reviews.slice(-1000);
33
+ }
34
+
35
+ fs.writeFileSync(DB_PATH, JSON.stringify(db, null, 2));
36
+ } catch {
37
+ // Non-fatal
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Load review statistics.
43
+ * @param {object} options
44
+ * @param {string} [options.period] - 'day' | 'week' | 'month' | 'all'
45
+ * @returns {object} Stats object
46
+ */
47
+ function getStats(options = {}) {
48
+ const db = loadDB();
49
+ const reviews = db.reviews;
50
+
51
+ if (reviews.length === 0) {
52
+ return { total: 0, averageScore: 0, issueTypes: {}, severityBreakdown: {}, trend: [] };
53
+ }
54
+
55
+ // Filter by period
56
+ const now = Date.now();
57
+ const periodMs = {
58
+ day: 24 * 60 * 60 * 1000,
59
+ week: 7 * 24 * 60 * 60 * 1000,
60
+ month: 30 * 24 * 60 * 60 * 1000,
61
+ all: Infinity,
62
+ }[options.period || 'all'] || Infinity;
63
+
64
+ const filtered = reviews.filter(r => (now - r.timestamp) <= periodMs);
65
+
66
+ if (filtered.length === 0) {
67
+ return { total: 0, averageScore: 0, issueTypes: {}, severityBreakdown: {}, trend: [] };
68
+ }
69
+
70
+ // Calculate stats
71
+ const scores = filtered.map(r => r.score);
72
+ const avgScore = scores.reduce((a, b) => a + b, 0) / scores.length;
73
+ const totalIssues = filtered.reduce((sum, r) => sum + r.issueCount, 0);
74
+
75
+ // Issue type breakdown
76
+ const issueTypes = {};
77
+ const severityBreakdown = {};
78
+ for (const r of filtered) {
79
+ for (const issue of r.topIssues || []) {
80
+ issueTypes[issue.type] = (issueTypes[issue.type] || 0) + 1;
81
+ severityBreakdown[issue.severity] = (severityBreakdown[issue.severity] || 0) + 1;
82
+ }
83
+ }
84
+
85
+ // Trend: last 10 reviews in order
86
+ const trend = filtered.slice(-20).map(r => ({
87
+ date: new Date(r.timestamp).toISOString().slice(0, 10),
88
+ score: r.score,
89
+ issues: r.issueCount,
90
+ }));
91
+
92
+ return {
93
+ total: filtered.length,
94
+ totalAllTime: reviews.length,
95
+ averageScore: Math.round(avgScore * 10) / 10,
96
+ highestScore: Math.max(...scores),
97
+ lowestScore: Math.min(...scores),
98
+ totalIssues,
99
+ issueTypes,
100
+ severityBreakdown,
101
+ trend,
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Clear review history.
107
+ */
108
+ function clearHistory() {
109
+ try {
110
+ fs.writeFileSync(DB_PATH, JSON.stringify({ reviews: [] }, null, 2));
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ }
116
+
117
+ function loadDB() {
118
+ try {
119
+ if (fs.existsSync(DB_PATH)) {
120
+ return JSON.parse(fs.readFileSync(DB_PATH, 'utf-8'));
121
+ }
122
+ } catch {}
123
+ return { reviews: [] };
124
+ }
125
+
126
+ module.exports = { recordReview, getStats, clearHistory };