codeep 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/LICENSE +201 -0
- package/README.md +576 -0
- package/dist/api/index.d.ts +8 -0
- package/dist/api/index.js +421 -0
- package/dist/app.d.ts +2 -0
- package/dist/app.js +1406 -0
- package/dist/components/AgentProgress.d.ts +33 -0
- package/dist/components/AgentProgress.js +97 -0
- package/dist/components/Export.d.ts +8 -0
- package/dist/components/Export.js +27 -0
- package/dist/components/Help.d.ts +2 -0
- package/dist/components/Help.js +3 -0
- package/dist/components/Input.d.ts +9 -0
- package/dist/components/Input.js +89 -0
- package/dist/components/Loading.d.ts +9 -0
- package/dist/components/Loading.js +31 -0
- package/dist/components/Login.d.ts +7 -0
- package/dist/components/Login.js +77 -0
- package/dist/components/Logo.d.ts +8 -0
- package/dist/components/Logo.js +89 -0
- package/dist/components/LogoutPicker.d.ts +8 -0
- package/dist/components/LogoutPicker.js +61 -0
- package/dist/components/Message.d.ts +10 -0
- package/dist/components/Message.js +234 -0
- package/dist/components/MessageList.d.ts +10 -0
- package/dist/components/MessageList.js +8 -0
- package/dist/components/ProjectPermission.d.ts +7 -0
- package/dist/components/ProjectPermission.js +52 -0
- package/dist/components/Search.d.ts +10 -0
- package/dist/components/Search.js +30 -0
- package/dist/components/SessionPicker.d.ts +9 -0
- package/dist/components/SessionPicker.js +88 -0
- package/dist/components/Sessions.d.ts +12 -0
- package/dist/components/Sessions.js +102 -0
- package/dist/components/Settings.d.ts +7 -0
- package/dist/components/Settings.js +162 -0
- package/dist/components/Status.d.ts +2 -0
- package/dist/components/Status.js +12 -0
- package/dist/config/config.test.d.ts +1 -0
- package/dist/config/config.test.js +157 -0
- package/dist/config/index.d.ts +121 -0
- package/dist/config/index.js +555 -0
- package/dist/config/providers.d.ts +43 -0
- package/dist/config/providers.js +82 -0
- package/dist/config/providers.test.d.ts +1 -0
- package/dist/config/providers.test.js +132 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +38 -0
- package/dist/utils/agent.d.ts +37 -0
- package/dist/utils/agent.js +627 -0
- package/dist/utils/codeReview.d.ts +36 -0
- package/dist/utils/codeReview.js +390 -0
- package/dist/utils/context.d.ts +49 -0
- package/dist/utils/context.js +216 -0
- package/dist/utils/diffPreview.d.ts +57 -0
- package/dist/utils/diffPreview.js +335 -0
- package/dist/utils/export.d.ts +19 -0
- package/dist/utils/export.js +94 -0
- package/dist/utils/git.d.ts +85 -0
- package/dist/utils/git.js +399 -0
- package/dist/utils/git.test.d.ts +1 -0
- package/dist/utils/git.test.js +193 -0
- package/dist/utils/history.d.ts +93 -0
- package/dist/utils/history.js +348 -0
- package/dist/utils/interactive.d.ts +34 -0
- package/dist/utils/interactive.js +206 -0
- package/dist/utils/keychain.d.ts +17 -0
- package/dist/utils/keychain.js +160 -0
- package/dist/utils/learning.d.ts +89 -0
- package/dist/utils/learning.js +330 -0
- package/dist/utils/logger.d.ts +33 -0
- package/dist/utils/logger.js +130 -0
- package/dist/utils/project.d.ts +86 -0
- package/dist/utils/project.js +415 -0
- package/dist/utils/project.test.d.ts +1 -0
- package/dist/utils/project.test.js +212 -0
- package/dist/utils/ratelimit.d.ts +26 -0
- package/dist/utils/ratelimit.js +132 -0
- package/dist/utils/ratelimit.test.d.ts +1 -0
- package/dist/utils/ratelimit.test.js +131 -0
- package/dist/utils/retry.d.ts +28 -0
- package/dist/utils/retry.js +109 -0
- package/dist/utils/retry.test.d.ts +1 -0
- package/dist/utils/retry.test.js +163 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +29 -0
- package/dist/utils/shell.d.ts +45 -0
- package/dist/utils/shell.js +242 -0
- package/dist/utils/skills.d.ts +144 -0
- package/dist/utils/skills.js +1137 -0
- package/dist/utils/smartContext.d.ts +29 -0
- package/dist/utils/smartContext.js +441 -0
- package/dist/utils/tools.d.ts +224 -0
- package/dist/utils/tools.js +731 -0
- package/dist/utils/update.d.ts +22 -0
- package/dist/utils/update.js +128 -0
- package/dist/utils/validation.d.ts +28 -0
- package/dist/utils/validation.js +141 -0
- package/dist/utils/validation.test.d.ts +1 -0
- package/dist/utils/validation.test.js +164 -0
- package/dist/utils/verify.d.ts +78 -0
- package/dist/utils/verify.js +464 -0
- package/package.json +68 -0
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code Review Mode - AI-powered code review
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, readdirSync } from 'fs';
|
|
5
|
+
import { join, extname, relative } from 'path';
|
|
6
|
+
import { getChangedFiles } from './git';
|
|
7
|
+
// Common code patterns that indicate issues
|
|
8
|
+
const CODE_PATTERNS = [
|
|
9
|
+
// Security issues
|
|
10
|
+
{
|
|
11
|
+
pattern: /eval\s*\(/g,
|
|
12
|
+
category: 'security',
|
|
13
|
+
severity: 'error',
|
|
14
|
+
message: 'Use of eval() is dangerous and can lead to code injection',
|
|
15
|
+
suggestion: 'Use JSON.parse() or a safer alternative',
|
|
16
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
pattern: /innerHTML\s*=/g,
|
|
20
|
+
category: 'security',
|
|
21
|
+
severity: 'warning',
|
|
22
|
+
message: 'innerHTML can lead to XSS vulnerabilities',
|
|
23
|
+
suggestion: 'Use textContent or sanitize input before using innerHTML',
|
|
24
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
pattern: /dangerouslySetInnerHTML/g,
|
|
28
|
+
category: 'security',
|
|
29
|
+
severity: 'warning',
|
|
30
|
+
message: 'dangerouslySetInnerHTML can lead to XSS if not properly sanitized',
|
|
31
|
+
suggestion: 'Ensure content is sanitized using DOMPurify or similar',
|
|
32
|
+
extensions: ['.jsx', '.tsx'],
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
pattern: /password\s*=\s*['"][^'"]+['"]/gi,
|
|
36
|
+
category: 'security',
|
|
37
|
+
severity: 'error',
|
|
38
|
+
message: 'Hardcoded password detected',
|
|
39
|
+
suggestion: 'Use environment variables for sensitive data',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /api[_-]?key\s*=\s*['"][^'"]+['"]/gi,
|
|
43
|
+
category: 'security',
|
|
44
|
+
severity: 'error',
|
|
45
|
+
message: 'Hardcoded API key detected',
|
|
46
|
+
suggestion: 'Use environment variables for API keys',
|
|
47
|
+
},
|
|
48
|
+
// Performance issues
|
|
49
|
+
{
|
|
50
|
+
pattern: /\.forEach\s*\([^)]*\)\s*{\s*await/g,
|
|
51
|
+
category: 'performance',
|
|
52
|
+
severity: 'warning',
|
|
53
|
+
message: 'Sequential async operations in forEach are inefficient',
|
|
54
|
+
suggestion: 'Use Promise.all() with map() for parallel execution',
|
|
55
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
pattern: /for\s*\([^)]+\)\s*{\s*await/g,
|
|
59
|
+
category: 'performance',
|
|
60
|
+
severity: 'info',
|
|
61
|
+
message: 'Consider if sequential await in loop is necessary',
|
|
62
|
+
suggestion: 'Use Promise.all() if operations can run in parallel',
|
|
63
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
pattern: /SELECT\s+\*/gi,
|
|
67
|
+
category: 'performance',
|
|
68
|
+
severity: 'warning',
|
|
69
|
+
message: 'SELECT * can be inefficient, select only needed columns',
|
|
70
|
+
suggestion: 'Specify required columns explicitly',
|
|
71
|
+
},
|
|
72
|
+
// Bug-prone patterns
|
|
73
|
+
{
|
|
74
|
+
pattern: /==\s*null|null\s*==/g,
|
|
75
|
+
category: 'bug',
|
|
76
|
+
severity: 'info',
|
|
77
|
+
message: 'Using == for null check also matches undefined',
|
|
78
|
+
suggestion: 'Use === null or == null intentionally',
|
|
79
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
pattern: /catch\s*\(\s*\w*\s*\)\s*{\s*}/g,
|
|
83
|
+
category: 'bug',
|
|
84
|
+
severity: 'warning',
|
|
85
|
+
message: 'Empty catch block swallows errors silently',
|
|
86
|
+
suggestion: 'Log the error or handle it appropriately',
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
pattern: /console\.(log|debug|info|warn|error)\s*\(/g,
|
|
90
|
+
category: 'maintainability',
|
|
91
|
+
severity: 'info',
|
|
92
|
+
message: 'Console statement found - remove before production',
|
|
93
|
+
suggestion: 'Use a proper logging library',
|
|
94
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
95
|
+
},
|
|
96
|
+
{
|
|
97
|
+
pattern: /TODO|FIXME|HACK|XXX/g,
|
|
98
|
+
category: 'maintainability',
|
|
99
|
+
severity: 'info',
|
|
100
|
+
message: 'TODO/FIXME comment found',
|
|
101
|
+
suggestion: 'Address the TODO or create a ticket',
|
|
102
|
+
},
|
|
103
|
+
// Type safety
|
|
104
|
+
{
|
|
105
|
+
pattern: /:\s*any\b/g,
|
|
106
|
+
category: 'types',
|
|
107
|
+
severity: 'warning',
|
|
108
|
+
message: 'Using "any" type bypasses type checking',
|
|
109
|
+
suggestion: 'Use a more specific type or unknown',
|
|
110
|
+
extensions: ['.ts', '.tsx'],
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
pattern: /@ts-ignore/g,
|
|
114
|
+
category: 'types',
|
|
115
|
+
severity: 'warning',
|
|
116
|
+
message: '@ts-ignore suppresses TypeScript errors',
|
|
117
|
+
suggestion: 'Fix the underlying type issue instead',
|
|
118
|
+
extensions: ['.ts', '.tsx'],
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
pattern: /as\s+any\b/g,
|
|
122
|
+
category: 'types',
|
|
123
|
+
severity: 'warning',
|
|
124
|
+
message: 'Type assertion to "any" bypasses type safety',
|
|
125
|
+
suggestion: 'Use proper type assertion or fix the types',
|
|
126
|
+
extensions: ['.ts', '.tsx'],
|
|
127
|
+
},
|
|
128
|
+
// Best practices
|
|
129
|
+
{
|
|
130
|
+
pattern: /var\s+\w+/g,
|
|
131
|
+
category: 'best-practice',
|
|
132
|
+
severity: 'info',
|
|
133
|
+
message: 'Using var instead of let/const',
|
|
134
|
+
suggestion: 'Use const for constants, let for variables',
|
|
135
|
+
extensions: ['.js', '.jsx'],
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
pattern: /function\s*\(/g,
|
|
139
|
+
category: 'style',
|
|
140
|
+
severity: 'info',
|
|
141
|
+
message: 'Anonymous function expression',
|
|
142
|
+
suggestion: 'Consider using arrow functions or named functions',
|
|
143
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
144
|
+
},
|
|
145
|
+
// Documentation
|
|
146
|
+
{
|
|
147
|
+
pattern: /export\s+(default\s+)?(?:function|class|const)\s+\w+/g,
|
|
148
|
+
category: 'documentation',
|
|
149
|
+
severity: 'suggestion',
|
|
150
|
+
message: 'Exported function/class without JSDoc',
|
|
151
|
+
suggestion: 'Add JSDoc documentation for public APIs',
|
|
152
|
+
extensions: ['.js', '.ts', '.jsx', '.tsx'],
|
|
153
|
+
},
|
|
154
|
+
];
|
|
155
|
+
/**
|
|
156
|
+
* Analyze a single file for issues
|
|
157
|
+
*/
|
|
158
|
+
function analyzeFile(filePath, content, projectRoot) {
|
|
159
|
+
const issues = [];
|
|
160
|
+
const ext = extname(filePath);
|
|
161
|
+
const relativePath = relative(projectRoot, filePath);
|
|
162
|
+
const lines = content.split('\n');
|
|
163
|
+
for (const pattern of CODE_PATTERNS) {
|
|
164
|
+
// Skip if pattern doesn't apply to this file type
|
|
165
|
+
if (pattern.extensions && !pattern.extensions.includes(ext)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
// Find all matches
|
|
169
|
+
let match;
|
|
170
|
+
const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags);
|
|
171
|
+
while ((match = regex.exec(content)) !== null) {
|
|
172
|
+
// Find line number
|
|
173
|
+
const beforeMatch = content.slice(0, match.index);
|
|
174
|
+
const lineNumber = beforeMatch.split('\n').length;
|
|
175
|
+
issues.push({
|
|
176
|
+
file: relativePath,
|
|
177
|
+
line: lineNumber,
|
|
178
|
+
severity: pattern.severity,
|
|
179
|
+
category: pattern.category,
|
|
180
|
+
message: pattern.message,
|
|
181
|
+
suggestion: pattern.suggestion,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// Check for long files
|
|
186
|
+
if (lines.length > 500) {
|
|
187
|
+
issues.push({
|
|
188
|
+
file: relativePath,
|
|
189
|
+
severity: 'info',
|
|
190
|
+
category: 'maintainability',
|
|
191
|
+
message: `File has ${lines.length} lines - consider splitting into smaller modules`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
// Check for long functions (basic heuristic)
|
|
195
|
+
let braceDepth = 0;
|
|
196
|
+
let functionStart = -1;
|
|
197
|
+
for (let i = 0; i < lines.length; i++) {
|
|
198
|
+
const line = lines[i];
|
|
199
|
+
if (/function\s+\w+|=>\s*{|\)\s*{/.test(line)) {
|
|
200
|
+
if (braceDepth === 0) {
|
|
201
|
+
functionStart = i;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
braceDepth += (line.match(/{/g) || []).length;
|
|
205
|
+
braceDepth -= (line.match(/}/g) || []).length;
|
|
206
|
+
if (braceDepth === 0 && functionStart !== -1) {
|
|
207
|
+
const functionLength = i - functionStart;
|
|
208
|
+
if (functionLength > 50) {
|
|
209
|
+
issues.push({
|
|
210
|
+
file: relativePath,
|
|
211
|
+
line: functionStart + 1,
|
|
212
|
+
severity: 'info',
|
|
213
|
+
category: 'maintainability',
|
|
214
|
+
message: `Function is ${functionLength} lines long - consider breaking it down`,
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
functionStart = -1;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return issues;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Get files to review
|
|
224
|
+
*/
|
|
225
|
+
function getFilesToReview(projectRoot, specificFiles) {
|
|
226
|
+
if (specificFiles && specificFiles.length > 0) {
|
|
227
|
+
return specificFiles
|
|
228
|
+
.map(f => join(projectRoot, f))
|
|
229
|
+
.filter(f => existsSync(f));
|
|
230
|
+
}
|
|
231
|
+
// Get changed files from git
|
|
232
|
+
const changedFiles = getChangedFiles(projectRoot);
|
|
233
|
+
if (changedFiles.length > 0) {
|
|
234
|
+
return changedFiles.map(f => join(projectRoot, f));
|
|
235
|
+
}
|
|
236
|
+
// Otherwise, review src directory
|
|
237
|
+
const srcDir = join(projectRoot, 'src');
|
|
238
|
+
if (existsSync(srcDir)) {
|
|
239
|
+
return getAllSourceFiles(srcDir);
|
|
240
|
+
}
|
|
241
|
+
return getAllSourceFiles(projectRoot);
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Get all source files in directory
|
|
245
|
+
*/
|
|
246
|
+
function getAllSourceFiles(dir, maxFiles = 50) {
|
|
247
|
+
const files = [];
|
|
248
|
+
const skipDirs = new Set(['node_modules', '.git', 'dist', 'build', 'vendor', '__pycache__']);
|
|
249
|
+
function walk(currentDir) {
|
|
250
|
+
if (files.length >= maxFiles)
|
|
251
|
+
return;
|
|
252
|
+
try {
|
|
253
|
+
const entries = readdirSync(currentDir, { withFileTypes: true });
|
|
254
|
+
for (const entry of entries) {
|
|
255
|
+
if (files.length >= maxFiles)
|
|
256
|
+
return;
|
|
257
|
+
const fullPath = join(currentDir, entry.name);
|
|
258
|
+
if (entry.isDirectory()) {
|
|
259
|
+
if (!skipDirs.has(entry.name) && !entry.name.startsWith('.')) {
|
|
260
|
+
walk(fullPath);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
else if (entry.isFile()) {
|
|
264
|
+
const ext = extname(entry.name);
|
|
265
|
+
if (['.ts', '.tsx', '.js', '.jsx', '.py', '.php', '.go', '.rs'].includes(ext)) {
|
|
266
|
+
files.push(fullPath);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
catch { }
|
|
272
|
+
}
|
|
273
|
+
walk(dir);
|
|
274
|
+
return files;
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Perform code review
|
|
278
|
+
*/
|
|
279
|
+
export function performCodeReview(projectContext, specificFiles) {
|
|
280
|
+
const projectRoot = projectContext.root || process.cwd();
|
|
281
|
+
const filesToReview = getFilesToReview(projectRoot, specificFiles);
|
|
282
|
+
const allIssues = [];
|
|
283
|
+
for (const filePath of filesToReview) {
|
|
284
|
+
try {
|
|
285
|
+
const content = readFileSync(filePath, 'utf-8');
|
|
286
|
+
const issues = analyzeFile(filePath, content, projectRoot);
|
|
287
|
+
allIssues.push(...issues);
|
|
288
|
+
}
|
|
289
|
+
catch { }
|
|
290
|
+
}
|
|
291
|
+
// Calculate summary
|
|
292
|
+
const summary = {
|
|
293
|
+
totalIssues: allIssues.length,
|
|
294
|
+
byCategory: {},
|
|
295
|
+
bySeverity: { error: 0, warning: 0, info: 0, suggestion: 0 },
|
|
296
|
+
};
|
|
297
|
+
for (const issue of allIssues) {
|
|
298
|
+
summary.byCategory[issue.category] = (summary.byCategory[issue.category] || 0) + 1;
|
|
299
|
+
summary.bySeverity[issue.severity]++;
|
|
300
|
+
}
|
|
301
|
+
// Calculate score (100 - deductions)
|
|
302
|
+
let score = 100;
|
|
303
|
+
score -= summary.bySeverity.error * 10;
|
|
304
|
+
score -= summary.bySeverity.warning * 3;
|
|
305
|
+
score -= summary.bySeverity.info * 1;
|
|
306
|
+
score = Math.max(0, Math.min(100, score));
|
|
307
|
+
return {
|
|
308
|
+
files: filesToReview.map(f => relative(projectRoot, f)),
|
|
309
|
+
issues: allIssues,
|
|
310
|
+
summary,
|
|
311
|
+
score,
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Format review result for display
|
|
316
|
+
*/
|
|
317
|
+
export function formatReviewResult(result) {
|
|
318
|
+
const lines = [];
|
|
319
|
+
// Header
|
|
320
|
+
lines.push('# Code Review Report');
|
|
321
|
+
lines.push('');
|
|
322
|
+
// Score
|
|
323
|
+
const scoreEmoji = result.score >= 80 ? '✅' : result.score >= 60 ? '⚠️' : '❌';
|
|
324
|
+
lines.push(`## Score: ${result.score}/100 ${scoreEmoji}`);
|
|
325
|
+
lines.push('');
|
|
326
|
+
// Summary
|
|
327
|
+
lines.push('## Summary');
|
|
328
|
+
lines.push(`- Files reviewed: ${result.files.length}`);
|
|
329
|
+
lines.push(`- Total issues: ${result.summary.totalIssues}`);
|
|
330
|
+
lines.push(` - Errors: ${result.summary.bySeverity.error}`);
|
|
331
|
+
lines.push(` - Warnings: ${result.summary.bySeverity.warning}`);
|
|
332
|
+
lines.push(` - Info: ${result.summary.bySeverity.info}`);
|
|
333
|
+
lines.push('');
|
|
334
|
+
// Issues by category
|
|
335
|
+
if (result.summary.totalIssues > 0) {
|
|
336
|
+
lines.push('## Issues by Category');
|
|
337
|
+
for (const [category, count] of Object.entries(result.summary.byCategory)) {
|
|
338
|
+
if (count > 0) {
|
|
339
|
+
lines.push(`- ${category}: ${count}`);
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
lines.push('');
|
|
343
|
+
// Detailed issues
|
|
344
|
+
lines.push('## Detailed Issues');
|
|
345
|
+
lines.push('');
|
|
346
|
+
// Group by file
|
|
347
|
+
const byFile = new Map();
|
|
348
|
+
for (const issue of result.issues) {
|
|
349
|
+
const existing = byFile.get(issue.file) || [];
|
|
350
|
+
existing.push(issue);
|
|
351
|
+
byFile.set(issue.file, existing);
|
|
352
|
+
}
|
|
353
|
+
for (const [file, issues] of byFile) {
|
|
354
|
+
lines.push(`### ${file}`);
|
|
355
|
+
for (const issue of issues.slice(0, 10)) { // Limit issues per file
|
|
356
|
+
const loc = issue.line ? `:${issue.line}` : '';
|
|
357
|
+
const icon = issue.severity === 'error' ? '❌' : issue.severity === 'warning' ? '⚠️' : 'ℹ️';
|
|
358
|
+
lines.push(`${icon} **${issue.category}**${loc}: ${issue.message}`);
|
|
359
|
+
if (issue.suggestion) {
|
|
360
|
+
lines.push(` → ${issue.suggestion}`);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
if (issues.length > 10) {
|
|
364
|
+
lines.push(` ... and ${issues.length - 10} more issues`);
|
|
365
|
+
}
|
|
366
|
+
lines.push('');
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
lines.push('## No issues found! 🎉');
|
|
371
|
+
}
|
|
372
|
+
return lines.join('\n');
|
|
373
|
+
}
|
|
374
|
+
/**
|
|
375
|
+
* Get review prompt for AI-enhanced review
|
|
376
|
+
*/
|
|
377
|
+
export function getReviewSystemPrompt(result) {
|
|
378
|
+
return `You are a code reviewer. Analyze the following code review results and provide additional insights.
|
|
379
|
+
|
|
380
|
+
## Automated Review Results
|
|
381
|
+
${formatReviewResult(result)}
|
|
382
|
+
|
|
383
|
+
## Your Task
|
|
384
|
+
1. Identify any additional issues the automated review might have missed
|
|
385
|
+
2. Prioritize the most critical issues to fix first
|
|
386
|
+
3. Provide specific, actionable recommendations
|
|
387
|
+
4. Highlight any positive aspects of the code
|
|
388
|
+
|
|
389
|
+
Be concise and practical. Focus on issues that matter most for code quality and maintainability.`;
|
|
390
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context persistence - save and load conversation context between sessions
|
|
3
|
+
*/
|
|
4
|
+
import { Message } from '../config/index';
|
|
5
|
+
export interface ConversationContext {
|
|
6
|
+
id: string;
|
|
7
|
+
projectPath: string;
|
|
8
|
+
projectName: string;
|
|
9
|
+
createdAt: number;
|
|
10
|
+
updatedAt: number;
|
|
11
|
+
messages: Message[];
|
|
12
|
+
summary?: string;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Save conversation context for a project
|
|
16
|
+
*/
|
|
17
|
+
export declare function saveContext(projectPath: string, messages: Message[], summary?: string): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Load conversation context for a project
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadContext(projectPath: string): ConversationContext | null;
|
|
22
|
+
/**
|
|
23
|
+
* Clear context for a project
|
|
24
|
+
*/
|
|
25
|
+
export declare function clearContext(projectPath: string): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Get all saved contexts
|
|
28
|
+
*/
|
|
29
|
+
export declare function getAllContexts(): ConversationContext[];
|
|
30
|
+
/**
|
|
31
|
+
* Summarize messages for context persistence
|
|
32
|
+
* Keeps recent messages and summarizes older ones
|
|
33
|
+
*/
|
|
34
|
+
export declare function summarizeContext(messages: Message[], maxMessages?: number): {
|
|
35
|
+
messages: Message[];
|
|
36
|
+
summary?: string;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Merge loaded context with current conversation
|
|
40
|
+
*/
|
|
41
|
+
export declare function mergeContext(loaded: ConversationContext | null, currentMessages: Message[]): Message[];
|
|
42
|
+
/**
|
|
43
|
+
* Format context info for display
|
|
44
|
+
*/
|
|
45
|
+
export declare function formatContextInfo(context: ConversationContext): string;
|
|
46
|
+
/**
|
|
47
|
+
* Clear all contexts
|
|
48
|
+
*/
|
|
49
|
+
export declare function clearAllContexts(): number;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Context persistence - save and load conversation context between sessions
|
|
3
|
+
*/
|
|
4
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
5
|
+
import { join, basename } from 'path';
|
|
6
|
+
import { homedir } from 'os';
|
|
7
|
+
import { logger } from './logger';
|
|
8
|
+
// Context storage directory
|
|
9
|
+
const CONTEXT_DIR = join(homedir(), '.codeep', 'contexts');
|
|
10
|
+
/**
|
|
11
|
+
* Ensure context directory exists
|
|
12
|
+
*/
|
|
13
|
+
function ensureContextDir() {
|
|
14
|
+
if (!existsSync(CONTEXT_DIR)) {
|
|
15
|
+
mkdirSync(CONTEXT_DIR, { recursive: true });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Generate context ID from project path
|
|
20
|
+
*/
|
|
21
|
+
function generateContextId(projectPath) {
|
|
22
|
+
// Create a stable ID from project path
|
|
23
|
+
return Buffer.from(projectPath).toString('base64url').substring(0, 32);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get context file path for a project
|
|
27
|
+
*/
|
|
28
|
+
function getContextPath(projectPath) {
|
|
29
|
+
ensureContextDir();
|
|
30
|
+
const id = generateContextId(projectPath);
|
|
31
|
+
return join(CONTEXT_DIR, `${id}.json`);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Save conversation context for a project
|
|
35
|
+
*/
|
|
36
|
+
export function saveContext(projectPath, messages, summary) {
|
|
37
|
+
try {
|
|
38
|
+
const contextPath = getContextPath(projectPath);
|
|
39
|
+
const existing = loadContext(projectPath);
|
|
40
|
+
const context = {
|
|
41
|
+
id: generateContextId(projectPath),
|
|
42
|
+
projectPath,
|
|
43
|
+
projectName: basename(projectPath),
|
|
44
|
+
createdAt: existing?.createdAt || Date.now(),
|
|
45
|
+
updatedAt: Date.now(),
|
|
46
|
+
messages,
|
|
47
|
+
summary,
|
|
48
|
+
};
|
|
49
|
+
writeFileSync(contextPath, JSON.stringify(context, null, 2));
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
logger.error('Failed to save context', error);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Load conversation context for a project
|
|
59
|
+
*/
|
|
60
|
+
export function loadContext(projectPath) {
|
|
61
|
+
try {
|
|
62
|
+
const contextPath = getContextPath(projectPath);
|
|
63
|
+
if (!existsSync(contextPath)) {
|
|
64
|
+
return null;
|
|
65
|
+
}
|
|
66
|
+
const content = readFileSync(contextPath, 'utf-8');
|
|
67
|
+
return JSON.parse(content);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
logger.error('Failed to load context', error);
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Clear context for a project
|
|
76
|
+
*/
|
|
77
|
+
export function clearContext(projectPath) {
|
|
78
|
+
try {
|
|
79
|
+
const contextPath = getContextPath(projectPath);
|
|
80
|
+
if (existsSync(contextPath)) {
|
|
81
|
+
unlinkSync(contextPath);
|
|
82
|
+
}
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
logger.error('Failed to clear context', error);
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get all saved contexts
|
|
92
|
+
*/
|
|
93
|
+
export function getAllContexts() {
|
|
94
|
+
ensureContextDir();
|
|
95
|
+
try {
|
|
96
|
+
const files = readdirSync(CONTEXT_DIR)
|
|
97
|
+
.filter(f => f.endsWith('.json'));
|
|
98
|
+
const contexts = [];
|
|
99
|
+
for (const file of files) {
|
|
100
|
+
try {
|
|
101
|
+
const content = readFileSync(join(CONTEXT_DIR, file), 'utf-8');
|
|
102
|
+
const context = JSON.parse(content);
|
|
103
|
+
contexts.push(context);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Skip invalid files
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// Sort by most recent
|
|
110
|
+
return contexts.sort((a, b) => b.updatedAt - a.updatedAt);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Summarize messages for context persistence
|
|
118
|
+
* Keeps recent messages and summarizes older ones
|
|
119
|
+
*/
|
|
120
|
+
export function summarizeContext(messages, maxMessages = 20) {
|
|
121
|
+
if (messages.length <= maxMessages) {
|
|
122
|
+
return { messages };
|
|
123
|
+
}
|
|
124
|
+
// Keep recent messages
|
|
125
|
+
const recentMessages = messages.slice(-maxMessages);
|
|
126
|
+
const oldMessages = messages.slice(0, -maxMessages);
|
|
127
|
+
// Create summary of old messages
|
|
128
|
+
const summary = createSummary(oldMessages);
|
|
129
|
+
return {
|
|
130
|
+
messages: recentMessages,
|
|
131
|
+
summary,
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Create a text summary of messages
|
|
136
|
+
*/
|
|
137
|
+
function createSummary(messages) {
|
|
138
|
+
const lines = ['Previous conversation summary:'];
|
|
139
|
+
for (const msg of messages) {
|
|
140
|
+
if (msg.role === 'user') {
|
|
141
|
+
// Extract key actions/questions
|
|
142
|
+
const content = msg.content.slice(0, 100);
|
|
143
|
+
lines.push(`- User: ${content}${msg.content.length > 100 ? '...' : ''}`);
|
|
144
|
+
}
|
|
145
|
+
else if (msg.role === 'assistant') {
|
|
146
|
+
// Check for tool calls or key actions
|
|
147
|
+
if (msg.content.includes('created') || msg.content.includes('modified')) {
|
|
148
|
+
lines.push(`- Assistant: Made file changes`);
|
|
149
|
+
}
|
|
150
|
+
else if (msg.content.includes('executed')) {
|
|
151
|
+
lines.push(`- Assistant: Executed commands`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return lines.join('\n');
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Merge loaded context with current conversation
|
|
159
|
+
*/
|
|
160
|
+
export function mergeContext(loaded, currentMessages) {
|
|
161
|
+
if (!loaded) {
|
|
162
|
+
return currentMessages;
|
|
163
|
+
}
|
|
164
|
+
// If there's a summary, add it as a system message
|
|
165
|
+
const messages = [];
|
|
166
|
+
if (loaded.summary) {
|
|
167
|
+
messages.push({
|
|
168
|
+
role: 'user',
|
|
169
|
+
content: `[Context from previous session]\n${loaded.summary}`,
|
|
170
|
+
});
|
|
171
|
+
messages.push({
|
|
172
|
+
role: 'assistant',
|
|
173
|
+
content: 'I understand the previous context. How can I help you continue?',
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
// Add loaded messages
|
|
177
|
+
messages.push(...loaded.messages);
|
|
178
|
+
// Add current messages
|
|
179
|
+
messages.push(...currentMessages);
|
|
180
|
+
return messages;
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Format context info for display
|
|
184
|
+
*/
|
|
185
|
+
export function formatContextInfo(context) {
|
|
186
|
+
const date = new Date(context.updatedAt).toLocaleString();
|
|
187
|
+
const messageCount = context.messages.length;
|
|
188
|
+
return `Project: ${context.projectName}
|
|
189
|
+
Path: ${context.projectPath}
|
|
190
|
+
Last updated: ${date}
|
|
191
|
+
Messages: ${messageCount}${context.summary ? ' (+ summary)' : ''}`;
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Clear all contexts
|
|
195
|
+
*/
|
|
196
|
+
export function clearAllContexts() {
|
|
197
|
+
ensureContextDir();
|
|
198
|
+
let cleared = 0;
|
|
199
|
+
try {
|
|
200
|
+
const files = readdirSync(CONTEXT_DIR)
|
|
201
|
+
.filter(f => f.endsWith('.json'));
|
|
202
|
+
for (const file of files) {
|
|
203
|
+
try {
|
|
204
|
+
unlinkSync(join(CONTEXT_DIR, file));
|
|
205
|
+
cleared++;
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
// Skip errors
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Ignore errors
|
|
214
|
+
}
|
|
215
|
+
return cleared;
|
|
216
|
+
}
|