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,130 @@
1
+ import { existsSync, mkdirSync, appendFileSync } from 'fs';
2
+ import { join } from 'path';
3
+ import { homedir } from 'os';
4
+ const GLOBAL_LOG_DIR = join(homedir(), '.codeep', 'logs');
5
+ // Ensure global log directory exists
6
+ if (!existsSync(GLOBAL_LOG_DIR)) {
7
+ mkdirSync(GLOBAL_LOG_DIR, { recursive: true });
8
+ }
9
+ // Current project path for local logging
10
+ let currentProjectPath = null;
11
+ /**
12
+ * Set current project path for local logging
13
+ */
14
+ export function setLogProjectPath(projectPath) {
15
+ currentProjectPath = projectPath;
16
+ }
17
+ /**
18
+ * Get local log directory for project
19
+ */
20
+ function getLocalLogDir(projectPath) {
21
+ const logDir = join(projectPath, '.codeep', 'logs');
22
+ if (!existsSync(logDir)) {
23
+ mkdirSync(logDir, { recursive: true });
24
+ }
25
+ return logDir;
26
+ }
27
+ /**
28
+ * Check if path is a project directory
29
+ */
30
+ function isProjectDirectory(path) {
31
+ return existsSync(join(path, 'package.json'));
32
+ }
33
+ /**
34
+ * Get log file paths for today (global and optionally local)
35
+ */
36
+ function getLogFilePaths() {
37
+ const date = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
38
+ const filename = `codeep-${date}.log`;
39
+ const paths = {
40
+ global: join(GLOBAL_LOG_DIR, filename),
41
+ };
42
+ // Add local path if in a project
43
+ if (currentProjectPath && isProjectDirectory(currentProjectPath)) {
44
+ const localLogDir = getLocalLogDir(currentProjectPath);
45
+ paths.local = join(localLogDir, filename);
46
+ }
47
+ return paths;
48
+ }
49
+ /**
50
+ * Format log entry as string
51
+ */
52
+ function formatLogEntry(entry) {
53
+ const dataStr = entry.data ? ` ${JSON.stringify(entry.data)}` : '';
54
+ return `[${entry.timestamp}] [${entry.level.toUpperCase()}] ${entry.message}${dataStr}\n`;
55
+ }
56
+ /**
57
+ * Write log entry to file(s)
58
+ */
59
+ function writeLog(level, message, data, localOnly = false) {
60
+ try {
61
+ const entry = {
62
+ timestamp: new Date().toISOString(),
63
+ level,
64
+ message,
65
+ data,
66
+ };
67
+ const logLine = formatLogEntry(entry);
68
+ const paths = getLogFilePaths();
69
+ // Always write to global log unless localOnly
70
+ if (!localOnly) {
71
+ appendFileSync(paths.global, logLine, 'utf-8');
72
+ }
73
+ // Write to local log if available
74
+ if (paths.local) {
75
+ appendFileSync(paths.local, logLine, 'utf-8');
76
+ }
77
+ }
78
+ catch {
79
+ // Silent fail - don't crash app if logging fails
80
+ // Cannot use logger here as it would cause infinite recursion
81
+ }
82
+ }
83
+ /**
84
+ * Logger API
85
+ */
86
+ export const logger = {
87
+ info: (message, data) => writeLog('info', message, data),
88
+ warn: (message, data) => writeLog('warn', message, data),
89
+ error: (message, data) => writeLog('error', message, data),
90
+ debug: (message, data) => writeLog('debug', message, data),
91
+ };
92
+ /**
93
+ * Log API request (both global and local)
94
+ */
95
+ export function logApiRequest(provider, model, messageCount) {
96
+ writeLog('info', 'API Request', { provider, model, messageCount });
97
+ }
98
+ /**
99
+ * Log API response (both global and local)
100
+ */
101
+ export function logApiResponse(provider, success, responseLength, error) {
102
+ if (success) {
103
+ writeLog('info', 'API Response', { provider, success, responseLength });
104
+ }
105
+ else {
106
+ writeLog('error', 'API Error', { provider, error });
107
+ }
108
+ }
109
+ /**
110
+ * Log session operation (local only - project-specific)
111
+ */
112
+ export function logSession(operation, sessionName, success) {
113
+ writeLog('info', `Session ${operation}`, { sessionName, success }, true);
114
+ }
115
+ /**
116
+ * Log application startup
117
+ */
118
+ export function logStartup(version) {
119
+ logger.info('Application started', { version });
120
+ }
121
+ /**
122
+ * Log application error
123
+ */
124
+ export function logAppError(error, context) {
125
+ logger.error('Application error', {
126
+ context,
127
+ message: error.message,
128
+ stack: error.stack,
129
+ });
130
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Project scanning and file detection utilities
3
+ */
4
+ export interface ProjectFile {
5
+ path: string;
6
+ relativePath: string;
7
+ name: string;
8
+ extension: string;
9
+ size: number;
10
+ isDirectory: boolean;
11
+ }
12
+ export interface ProjectContext {
13
+ root: string;
14
+ name: string;
15
+ type: string;
16
+ structure: string;
17
+ keyFiles: string[];
18
+ fileCount: number;
19
+ summary: string;
20
+ hasWriteAccess?: boolean;
21
+ }
22
+ export interface DetectedFile {
23
+ path: string;
24
+ content: string;
25
+ truncated: boolean;
26
+ }
27
+ /**
28
+ * Check if current directory is a project (has package.json or other markers)
29
+ */
30
+ export declare function isProjectDirectory(dir?: string): boolean;
31
+ /**
32
+ * Get project type based on config files
33
+ */
34
+ export declare function getProjectType(dir?: string): string;
35
+ /**
36
+ * Scan directory recursively up to specified depth
37
+ */
38
+ export declare function scanDirectory(dir: string, maxDepth?: number, currentDepth?: number, rootDir?: string): ProjectFile[];
39
+ /**
40
+ * Generate directory tree structure string
41
+ */
42
+ export declare function generateTreeStructure(files: ProjectFile[], maxLines?: number): string;
43
+ /**
44
+ * Read a project file with size limit
45
+ */
46
+ export declare function readProjectFile(filePath: string, maxSize?: number): DetectedFile | null;
47
+ /**
48
+ * Write content to a project file
49
+ */
50
+ export declare function deleteProjectFile(filePath: string): {
51
+ success: boolean;
52
+ error?: string;
53
+ };
54
+ export declare function writeProjectFile(filePath: string, content: string): {
55
+ success: boolean;
56
+ error?: string;
57
+ };
58
+ /**
59
+ * Parse file changes from AI response
60
+ * Supports multiple formats:
61
+ * 1. ```filepath:path/to/file.ts\ncode\n```
62
+ * 2. Box format with :filename on second line
63
+ * 3. Delete format: ```delete:path/to/file.ts```
64
+ */
65
+ export declare function parseFileChanges(response: string): Array<{
66
+ path: string;
67
+ content: string;
68
+ action?: 'create' | 'edit' | 'delete';
69
+ }>;
70
+ /**
71
+ * Detect file paths mentioned in text
72
+ */
73
+ export declare function detectFilePaths(text: string, projectRoot?: string): string[];
74
+ /**
75
+ * Get full project context for AI
76
+ */
77
+ export declare function getProjectContext(dir?: string): ProjectContext | null;
78
+ /**
79
+ * Get project summary for display
80
+ */
81
+ export declare function getProjectSummary(dir?: string): {
82
+ name: string;
83
+ type: string;
84
+ fileCount: number;
85
+ hasReadme: boolean;
86
+ } | null;
@@ -0,0 +1,415 @@
1
+ /**
2
+ * Project scanning and file detection utilities
3
+ */
4
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
5
+ import { join, basename, extname, relative, resolve, dirname } from 'path';
6
+ // Directories to ignore when scanning
7
+ const IGNORE_DIRS = [
8
+ 'node_modules',
9
+ '.git',
10
+ 'dist',
11
+ 'build',
12
+ '.next',
13
+ 'coverage',
14
+ '.cache',
15
+ '.vscode',
16
+ '.idea',
17
+ '__pycache__',
18
+ 'venv',
19
+ '.env',
20
+ ];
21
+ // File extensions to include in scanning
22
+ const CODE_EXTENSIONS = [
23
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
24
+ '.py', '.rb', '.go', '.rs', '.java', '.kt',
25
+ '.c', '.cpp', '.h', '.hpp', '.cs',
26
+ '.php', '.swift', '.vue', '.svelte',
27
+ '.css', '.scss', '.less', '.sass',
28
+ '.html', '.htm', '.xml', '.yaml', '.yml',
29
+ '.json', '.md', '.txt', '.sh', '.bash',
30
+ ];
31
+ // Key config files to always include
32
+ const KEY_FILES = [
33
+ 'package.json',
34
+ 'tsconfig.json',
35
+ 'README.md',
36
+ 'readme.md',
37
+ '.env.example',
38
+ 'Cargo.toml',
39
+ 'go.mod',
40
+ 'requirements.txt',
41
+ 'Gemfile',
42
+ 'pom.xml',
43
+ 'build.gradle',
44
+ 'Makefile',
45
+ 'docker-compose.yml',
46
+ 'Dockerfile',
47
+ ];
48
+ /**
49
+ * Check if current directory is a project (has package.json or other markers)
50
+ */
51
+ export function isProjectDirectory(dir = process.cwd()) {
52
+ const markers = ['package.json', 'Cargo.toml', 'go.mod', 'requirements.txt', 'pom.xml', '.git'];
53
+ return markers.some(marker => existsSync(join(dir, marker)));
54
+ }
55
+ /**
56
+ * Get project type based on config files
57
+ */
58
+ export function getProjectType(dir = process.cwd()) {
59
+ if (existsSync(join(dir, 'package.json'))) {
60
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
61
+ if (pkg.dependencies?.typescript || pkg.devDependencies?.typescript || existsSync(join(dir, 'tsconfig.json'))) {
62
+ return 'TypeScript/Node.js';
63
+ }
64
+ return 'JavaScript/Node.js';
65
+ }
66
+ if (existsSync(join(dir, 'Cargo.toml')))
67
+ return 'Rust';
68
+ if (existsSync(join(dir, 'go.mod')))
69
+ return 'Go';
70
+ if (existsSync(join(dir, 'requirements.txt')) || existsSync(join(dir, 'setup.py')))
71
+ return 'Python';
72
+ if (existsSync(join(dir, 'Gemfile')))
73
+ return 'Ruby';
74
+ if (existsSync(join(dir, 'pom.xml')) || existsSync(join(dir, 'build.gradle')))
75
+ return 'Java';
76
+ return 'Unknown';
77
+ }
78
+ /**
79
+ * Scan directory recursively up to specified depth
80
+ */
81
+ export function scanDirectory(dir, maxDepth = 3, currentDepth = 0, rootDir) {
82
+ const root = rootDir || dir;
83
+ const files = [];
84
+ if (currentDepth >= maxDepth)
85
+ return files;
86
+ try {
87
+ const entries = readdirSync(dir);
88
+ for (const entry of entries) {
89
+ const fullPath = join(dir, entry);
90
+ const relativePath = relative(root, fullPath);
91
+ // Skip ignored directories
92
+ if (IGNORE_DIRS.includes(entry))
93
+ continue;
94
+ try {
95
+ const stat = statSync(fullPath);
96
+ const isDirectory = stat.isDirectory();
97
+ const extension = extname(entry).toLowerCase();
98
+ // Add directories
99
+ if (isDirectory) {
100
+ files.push({
101
+ path: fullPath,
102
+ relativePath,
103
+ name: entry,
104
+ extension: '',
105
+ size: 0,
106
+ isDirectory: true,
107
+ });
108
+ // Recurse into subdirectories
109
+ files.push(...scanDirectory(fullPath, maxDepth, currentDepth + 1, root));
110
+ }
111
+ // Add relevant files
112
+ else if (CODE_EXTENSIONS.includes(extension) || KEY_FILES.includes(entry)) {
113
+ files.push({
114
+ path: fullPath,
115
+ relativePath,
116
+ name: entry,
117
+ extension,
118
+ size: stat.size,
119
+ isDirectory: false,
120
+ });
121
+ }
122
+ }
123
+ catch {
124
+ // Skip files we can't access
125
+ }
126
+ }
127
+ }
128
+ catch {
129
+ // Skip directories we can't read
130
+ }
131
+ return files;
132
+ }
133
+ /**
134
+ * Generate directory tree structure string
135
+ */
136
+ export function generateTreeStructure(files, maxLines = 30) {
137
+ const dirs = new Set();
138
+ const filesByDir = {};
139
+ for (const file of files) {
140
+ const parts = file.relativePath.split('/');
141
+ if (file.isDirectory) {
142
+ dirs.add(file.relativePath);
143
+ }
144
+ else {
145
+ const dir = parts.slice(0, -1).join('/') || '.';
146
+ if (!filesByDir[dir])
147
+ filesByDir[dir] = [];
148
+ filesByDir[dir].push(parts[parts.length - 1]);
149
+ }
150
+ }
151
+ const lines = [];
152
+ const sortedDirs = ['', ...Array.from(dirs).sort()];
153
+ for (const dir of sortedDirs) {
154
+ if (lines.length >= maxLines) {
155
+ lines.push('... (truncated)');
156
+ break;
157
+ }
158
+ const displayDir = dir || '.';
159
+ const indent = dir ? ' '.repeat(dir.split('/').length) : '';
160
+ if (dir) {
161
+ lines.push(`${indent}${basename(dir)}/`);
162
+ }
163
+ const dirFiles = filesByDir[dir] || filesByDir[displayDir] || [];
164
+ for (const file of dirFiles.slice(0, 10)) {
165
+ if (lines.length >= maxLines)
166
+ break;
167
+ lines.push(`${indent} ${file}`);
168
+ }
169
+ if (dirFiles.length > 10) {
170
+ lines.push(`${indent} ... (+${dirFiles.length - 10} more)`);
171
+ }
172
+ }
173
+ return lines.join('\n');
174
+ }
175
+ /**
176
+ * Read a project file with size limit
177
+ */
178
+ export function readProjectFile(filePath, maxSize = 50000) {
179
+ try {
180
+ const absolutePath = resolve(filePath);
181
+ if (!existsSync(absolutePath))
182
+ return null;
183
+ const stat = statSync(absolutePath);
184
+ if (stat.isDirectory())
185
+ return null;
186
+ if (stat.size > maxSize * 2)
187
+ return null; // Skip very large files
188
+ let content = readFileSync(absolutePath, 'utf-8');
189
+ let truncated = false;
190
+ if (content.length > maxSize) {
191
+ content = content.slice(0, maxSize) + '\n\n... (file truncated)';
192
+ truncated = true;
193
+ }
194
+ return {
195
+ path: absolutePath,
196
+ content,
197
+ truncated,
198
+ };
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ }
204
+ /**
205
+ * Write content to a project file
206
+ */
207
+ export function deleteProjectFile(filePath) {
208
+ try {
209
+ const absolutePath = resolve(filePath);
210
+ // Check if file exists
211
+ if (!existsSync(absolutePath)) {
212
+ return { success: false, error: 'File does not exist' };
213
+ }
214
+ // Delete file
215
+ unlinkSync(absolutePath);
216
+ return { success: true };
217
+ }
218
+ catch (error) {
219
+ const err = error;
220
+ return { success: false, error: err.message };
221
+ }
222
+ }
223
+ export function writeProjectFile(filePath, content) {
224
+ try {
225
+ const absolutePath = resolve(filePath);
226
+ // Ensure directory exists
227
+ const dir = dirname(absolutePath);
228
+ if (!existsSync(dir)) {
229
+ mkdirSync(dir, { recursive: true });
230
+ }
231
+ // Write file
232
+ writeFileSync(absolutePath, content, 'utf-8');
233
+ return { success: true };
234
+ }
235
+ catch (error) {
236
+ const err = error;
237
+ return {
238
+ success: false,
239
+ error: err.message || 'Failed to write file',
240
+ };
241
+ }
242
+ }
243
+ /**
244
+ * Parse file changes from AI response
245
+ * Supports multiple formats:
246
+ * 1. ```filepath:path/to/file.ts\ncode\n```
247
+ * 2. Box format with :filename on second line
248
+ * 3. Delete format: ```delete:path/to/file.ts```
249
+ */
250
+ export function parseFileChanges(response) {
251
+ const changes = [];
252
+ // Format 1: Match code blocks with filepath: prefix
253
+ const regex1 = /```filepath:([^\n]+)\n([\s\S]*?)```/g;
254
+ let match;
255
+ while ((match = regex1.exec(response)) !== null) {
256
+ const path = match[1].trim();
257
+ const content = match[2];
258
+ const action = existsSync(path) ? 'edit' : 'create';
259
+ changes.push({ path, content, action });
260
+ }
261
+ // Format 3: Match delete format
262
+ const deleteRegex = /```delete:([^\n]+)```/g;
263
+ while ((match = deleteRegex.exec(response)) !== null) {
264
+ const path = match[1].trim();
265
+ changes.push({ path, content: '', action: 'delete' });
266
+ }
267
+ // Format 2: Match box format - simpler line-by-line approach
268
+ const lines = response.split('\n');
269
+ let inBox = false;
270
+ let currentPath = '';
271
+ let currentContent = [];
272
+ for (let i = 0; i < lines.length; i++) {
273
+ const line = lines[i];
274
+ // Start of box
275
+ if (line.includes('╭') && line.includes('─')) {
276
+ inBox = true;
277
+ currentContent = [];
278
+ continue;
279
+ }
280
+ // End of box
281
+ if (line.includes('╰') && line.includes('─')) {
282
+ if (inBox && currentPath && currentContent.length > 0) {
283
+ const action = existsSync(currentPath) ? 'edit' : 'create';
284
+ changes.push({
285
+ path: currentPath,
286
+ content: currentContent.join('\n').trim(),
287
+ action
288
+ });
289
+ }
290
+ inBox = false;
291
+ currentPath = '';
292
+ currentContent = [];
293
+ continue;
294
+ }
295
+ // Inside box
296
+ if (inBox) {
297
+ // Line with :filename
298
+ if (line.includes('│') && line.includes(':') && !currentPath) {
299
+ const match = line.match(/:\s*([^\s│]+)/);
300
+ if (match) {
301
+ currentPath = match[1].trim();
302
+ }
303
+ }
304
+ // Content lines (skip header lines with "filepath")
305
+ else if (line.includes('│') && !line.includes('filepath') && !line.includes('[0]')) {
306
+ const content = line.replace(/^[│\s]+/, '').replace(/[│\s]+$/, '');
307
+ if (content && currentPath) {
308
+ currentContent.push(content);
309
+ }
310
+ }
311
+ }
312
+ }
313
+ return changes;
314
+ }
315
+ /**
316
+ * Detect file paths mentioned in text
317
+ */
318
+ export function detectFilePaths(text, projectRoot = process.cwd()) {
319
+ const detectedPaths = [];
320
+ // Patterns to match file paths
321
+ const patterns = [
322
+ // Explicit paths: ./src/app.tsx, ../utils/helper.ts
323
+ /(?:^|\s)(\.{1,2}\/[\w\-./]+\.\w+)/g,
324
+ // Relative paths without ./: src/app.tsx, components/Button.tsx
325
+ /(?:^|\s)((?:src|lib|app|components|pages|utils|hooks|services|api|config|test|tests|spec)\/[\w\-./]+\.\w+)/gi,
326
+ // Single files in current dir or common names: package.json, tsconfig.json
327
+ /(?:^|\s)((?:package|tsconfig|webpack|babel|jest|vite|rollup|eslint|prettier)\.(?:json|config\.\w+|js|ts|cjs|mjs))/gi,
328
+ // README, Dockerfile, Makefile
329
+ /(?:^|\s)((?:README|Dockerfile|Makefile|Cargo\.toml|go\.mod|requirements\.txt)(?:\.\w+)?)/gi,
330
+ ];
331
+ for (const pattern of patterns) {
332
+ let match;
333
+ while ((match = pattern.exec(text)) !== null) {
334
+ const potentialPath = match[1].trim();
335
+ const absolutePath = resolve(projectRoot, potentialPath);
336
+ // Check if file exists
337
+ if (existsSync(absolutePath)) {
338
+ const stat = statSync(absolutePath);
339
+ if (!stat.isDirectory() && !detectedPaths.includes(potentialPath)) {
340
+ detectedPaths.push(potentialPath);
341
+ }
342
+ }
343
+ }
344
+ }
345
+ return detectedPaths;
346
+ }
347
+ /**
348
+ * Get full project context for AI
349
+ */
350
+ export function getProjectContext(dir = process.cwd()) {
351
+ if (!isProjectDirectory(dir))
352
+ return null;
353
+ try {
354
+ const files = scanDirectory(dir, 3);
355
+ const projectType = getProjectType(dir);
356
+ const structure = generateTreeStructure(files, 25);
357
+ // Find key files that exist
358
+ const existingKeyFiles = KEY_FILES
359
+ .filter(f => existsSync(join(dir, f)))
360
+ .slice(0, 5);
361
+ // Get project name
362
+ let projectName = basename(dir);
363
+ if (existsSync(join(dir, 'package.json'))) {
364
+ try {
365
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
366
+ projectName = pkg.name || projectName;
367
+ }
368
+ catch { }
369
+ }
370
+ const codeFiles = files.filter(f => !f.isDirectory);
371
+ const fileCount = codeFiles.length;
372
+ // Generate summary
373
+ const summary = `${projectName} is a ${projectType} project with ${fileCount} code files.`;
374
+ return {
375
+ root: dir,
376
+ name: projectName,
377
+ type: projectType,
378
+ structure,
379
+ keyFiles: existingKeyFiles,
380
+ fileCount,
381
+ summary,
382
+ };
383
+ }
384
+ catch {
385
+ return null;
386
+ }
387
+ }
388
+ /**
389
+ * Get project summary for display
390
+ */
391
+ export function getProjectSummary(dir = process.cwd()) {
392
+ if (!isProjectDirectory(dir))
393
+ return null;
394
+ try {
395
+ const files = scanDirectory(dir, 2);
396
+ const codeFiles = files.filter(f => !f.isDirectory);
397
+ let name = basename(dir);
398
+ if (existsSync(join(dir, 'package.json'))) {
399
+ try {
400
+ const pkg = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf-8'));
401
+ name = pkg.name || name;
402
+ }
403
+ catch { }
404
+ }
405
+ return {
406
+ name,
407
+ type: getProjectType(dir),
408
+ fileCount: codeFiles.length,
409
+ hasReadme: existsSync(join(dir, 'README.md')) || existsSync(join(dir, 'readme.md')),
410
+ };
411
+ }
412
+ catch {
413
+ return null;
414
+ }
415
+ }
@@ -0,0 +1 @@
1
+ export {};