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,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 {};
|