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.
Files changed (103) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +576 -0
  3. package/dist/api/index.d.ts +8 -0
  4. package/dist/api/index.js +421 -0
  5. package/dist/app.d.ts +2 -0
  6. package/dist/app.js +1406 -0
  7. package/dist/components/AgentProgress.d.ts +33 -0
  8. package/dist/components/AgentProgress.js +97 -0
  9. package/dist/components/Export.d.ts +8 -0
  10. package/dist/components/Export.js +27 -0
  11. package/dist/components/Help.d.ts +2 -0
  12. package/dist/components/Help.js +3 -0
  13. package/dist/components/Input.d.ts +9 -0
  14. package/dist/components/Input.js +89 -0
  15. package/dist/components/Loading.d.ts +9 -0
  16. package/dist/components/Loading.js +31 -0
  17. package/dist/components/Login.d.ts +7 -0
  18. package/dist/components/Login.js +77 -0
  19. package/dist/components/Logo.d.ts +8 -0
  20. package/dist/components/Logo.js +89 -0
  21. package/dist/components/LogoutPicker.d.ts +8 -0
  22. package/dist/components/LogoutPicker.js +61 -0
  23. package/dist/components/Message.d.ts +10 -0
  24. package/dist/components/Message.js +234 -0
  25. package/dist/components/MessageList.d.ts +10 -0
  26. package/dist/components/MessageList.js +8 -0
  27. package/dist/components/ProjectPermission.d.ts +7 -0
  28. package/dist/components/ProjectPermission.js +52 -0
  29. package/dist/components/Search.d.ts +10 -0
  30. package/dist/components/Search.js +30 -0
  31. package/dist/components/SessionPicker.d.ts +9 -0
  32. package/dist/components/SessionPicker.js +88 -0
  33. package/dist/components/Sessions.d.ts +12 -0
  34. package/dist/components/Sessions.js +102 -0
  35. package/dist/components/Settings.d.ts +7 -0
  36. package/dist/components/Settings.js +162 -0
  37. package/dist/components/Status.d.ts +2 -0
  38. package/dist/components/Status.js +12 -0
  39. package/dist/config/config.test.d.ts +1 -0
  40. package/dist/config/config.test.js +157 -0
  41. package/dist/config/index.d.ts +121 -0
  42. package/dist/config/index.js +555 -0
  43. package/dist/config/providers.d.ts +43 -0
  44. package/dist/config/providers.js +82 -0
  45. package/dist/config/providers.test.d.ts +1 -0
  46. package/dist/config/providers.test.js +132 -0
  47. package/dist/index.d.ts +2 -0
  48. package/dist/index.js +38 -0
  49. package/dist/utils/agent.d.ts +37 -0
  50. package/dist/utils/agent.js +627 -0
  51. package/dist/utils/codeReview.d.ts +36 -0
  52. package/dist/utils/codeReview.js +390 -0
  53. package/dist/utils/context.d.ts +49 -0
  54. package/dist/utils/context.js +216 -0
  55. package/dist/utils/diffPreview.d.ts +57 -0
  56. package/dist/utils/diffPreview.js +335 -0
  57. package/dist/utils/export.d.ts +19 -0
  58. package/dist/utils/export.js +94 -0
  59. package/dist/utils/git.d.ts +85 -0
  60. package/dist/utils/git.js +399 -0
  61. package/dist/utils/git.test.d.ts +1 -0
  62. package/dist/utils/git.test.js +193 -0
  63. package/dist/utils/history.d.ts +93 -0
  64. package/dist/utils/history.js +348 -0
  65. package/dist/utils/interactive.d.ts +34 -0
  66. package/dist/utils/interactive.js +206 -0
  67. package/dist/utils/keychain.d.ts +17 -0
  68. package/dist/utils/keychain.js +160 -0
  69. package/dist/utils/learning.d.ts +89 -0
  70. package/dist/utils/learning.js +330 -0
  71. package/dist/utils/logger.d.ts +33 -0
  72. package/dist/utils/logger.js +130 -0
  73. package/dist/utils/project.d.ts +86 -0
  74. package/dist/utils/project.js +415 -0
  75. package/dist/utils/project.test.d.ts +1 -0
  76. package/dist/utils/project.test.js +212 -0
  77. package/dist/utils/ratelimit.d.ts +26 -0
  78. package/dist/utils/ratelimit.js +132 -0
  79. package/dist/utils/ratelimit.test.d.ts +1 -0
  80. package/dist/utils/ratelimit.test.js +131 -0
  81. package/dist/utils/retry.d.ts +28 -0
  82. package/dist/utils/retry.js +109 -0
  83. package/dist/utils/retry.test.d.ts +1 -0
  84. package/dist/utils/retry.test.js +163 -0
  85. package/dist/utils/search.d.ts +11 -0
  86. package/dist/utils/search.js +29 -0
  87. package/dist/utils/shell.d.ts +45 -0
  88. package/dist/utils/shell.js +242 -0
  89. package/dist/utils/skills.d.ts +144 -0
  90. package/dist/utils/skills.js +1137 -0
  91. package/dist/utils/smartContext.d.ts +29 -0
  92. package/dist/utils/smartContext.js +441 -0
  93. package/dist/utils/tools.d.ts +224 -0
  94. package/dist/utils/tools.js +731 -0
  95. package/dist/utils/update.d.ts +22 -0
  96. package/dist/utils/update.js +128 -0
  97. package/dist/utils/validation.d.ts +28 -0
  98. package/dist/utils/validation.js +141 -0
  99. package/dist/utils/validation.test.d.ts +1 -0
  100. package/dist/utils/validation.test.js +164 -0
  101. package/dist/utils/verify.d.ts +78 -0
  102. package/dist/utils/verify.js +464 -0
  103. 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
+ }