@nclamvn/vibecode-cli 2.0.0 → 2.2.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/.vibecode/learning/fixes.json +1 -0
- package/.vibecode/learning/preferences.json +1 -0
- package/README.md +310 -49
- package/SESSION_NOTES.md +154 -0
- package/bin/vibecode.js +235 -2
- package/package.json +5 -2
- package/src/agent/decomposition.js +476 -0
- package/src/agent/index.js +391 -0
- package/src/agent/memory.js +542 -0
- package/src/agent/orchestrator.js +917 -0
- package/src/agent/self-healing.js +516 -0
- package/src/commands/agent.js +349 -0
- package/src/commands/ask.js +230 -0
- package/src/commands/assist.js +413 -0
- package/src/commands/build.js +345 -4
- package/src/commands/debug.js +565 -0
- package/src/commands/docs.js +167 -0
- package/src/commands/git.js +1024 -0
- package/src/commands/go.js +635 -0
- package/src/commands/learn.js +294 -0
- package/src/commands/migrate.js +341 -0
- package/src/commands/plan.js +8 -2
- package/src/commands/refactor.js +205 -0
- package/src/commands/review.js +126 -1
- package/src/commands/security.js +229 -0
- package/src/commands/shell.js +486 -0
- package/src/commands/templates.js +397 -0
- package/src/commands/test.js +194 -0
- package/src/commands/undo.js +281 -0
- package/src/commands/watch.js +556 -0
- package/src/commands/wizard.js +322 -0
- package/src/config/constants.js +5 -1
- package/src/config/templates.js +146 -15
- package/src/core/backup.js +325 -0
- package/src/core/error-analyzer.js +237 -0
- package/src/core/fix-generator.js +195 -0
- package/src/core/iteration.js +226 -0
- package/src/core/learning.js +295 -0
- package/src/core/session.js +18 -2
- package/src/core/test-runner.js +281 -0
- package/src/debug/analyzer.js +329 -0
- package/src/debug/evidence.js +228 -0
- package/src/debug/fixer.js +348 -0
- package/src/debug/image-analyzer.js +304 -0
- package/src/debug/index.js +378 -0
- package/src/debug/verifier.js +346 -0
- package/src/index.js +102 -0
- package/src/providers/claude-code.js +12 -7
- package/src/templates/index.js +724 -0
- package/src/ui/__tests__/error-translator.test.js +390 -0
- package/src/ui/dashboard.js +364 -0
- package/src/ui/error-translator.js +775 -0
- package/src/utils/image.js +222 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Agent Command
|
|
3
|
+
// Autonomous multi-module builder
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import fs from 'fs-extra';
|
|
9
|
+
|
|
10
|
+
import { createAgent, loadProgress } from '../agent/index.js';
|
|
11
|
+
import { printError, printSuccess } from '../ui/output.js';
|
|
12
|
+
import { isClaudeCodeAvailable } from '../providers/index.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Agent command entry point
|
|
16
|
+
* vibecode agent "description" [options]
|
|
17
|
+
*/
|
|
18
|
+
export async function agentCommand(description, options = {}) {
|
|
19
|
+
// Handle description as array (from commander variadic)
|
|
20
|
+
const desc = Array.isArray(description) ? description.join(' ') : description;
|
|
21
|
+
|
|
22
|
+
// Handle sub-commands that don't need Claude Code check first
|
|
23
|
+
if (options.status) {
|
|
24
|
+
return showAgentStatus(options);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Check for Claude Code
|
|
28
|
+
const claudeAvailable = await isClaudeCodeAvailable();
|
|
29
|
+
if (!claudeAvailable) {
|
|
30
|
+
printError('Claude Code CLI not found.');
|
|
31
|
+
console.log(chalk.gray('Install with: npm install -g @anthropic-ai/claude-code'));
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Handle resume
|
|
36
|
+
if (options.resume) {
|
|
37
|
+
return resumeCommand(options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Validate description for non-resume commands
|
|
41
|
+
if (!desc || desc.trim().length < 5) {
|
|
42
|
+
// Check if there's a session to resume
|
|
43
|
+
const progress = await loadProgress(process.cwd());
|
|
44
|
+
if (progress) {
|
|
45
|
+
console.log(chalk.cyan('\n📦 Found existing session:'));
|
|
46
|
+
console.log(chalk.white(` Project: ${progress.projectName}`));
|
|
47
|
+
console.log(chalk.white(` Progress: ${progress.completedModules?.length || progress.currentModule}/${progress.totalModules} modules\n`));
|
|
48
|
+
console.log(chalk.gray(' Resume: vibecode agent --resume'));
|
|
49
|
+
console.log(chalk.gray(' Status: vibecode agent --status\n'));
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
printError('Description too short. Please provide more details.');
|
|
54
|
+
console.log(chalk.gray('Example: vibecode agent "SaaS dashboard with auth, billing, and analytics"'));
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Handle sub-commands
|
|
59
|
+
if (options.analyze) {
|
|
60
|
+
return analyzeCommand(desc, options);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (options.report) {
|
|
64
|
+
return reportCommand(options);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (options.clear) {
|
|
68
|
+
return clearCommand(options);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Main build flow
|
|
72
|
+
return buildCommand(desc, options);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Main agent build command
|
|
77
|
+
*/
|
|
78
|
+
async function buildCommand(description, options) {
|
|
79
|
+
// Determine project path
|
|
80
|
+
let projectPath = process.cwd();
|
|
81
|
+
|
|
82
|
+
// If --new flag, create new directory
|
|
83
|
+
if (options.new) {
|
|
84
|
+
const projectName = generateProjectName(description);
|
|
85
|
+
projectPath = path.join(process.cwd(), projectName);
|
|
86
|
+
|
|
87
|
+
if (await fs.pathExists(projectPath)) {
|
|
88
|
+
printError(`Directory already exists: ${projectName}`);
|
|
89
|
+
console.log(chalk.gray('Choose a different name or delete the existing directory.'));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
await fs.ensureDir(projectPath);
|
|
94
|
+
await fs.ensureDir(path.join(projectPath, '.vibecode'));
|
|
95
|
+
process.chdir(projectPath);
|
|
96
|
+
|
|
97
|
+
console.log(chalk.gray(`Created project: ${projectName}`));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Create and initialize agent
|
|
101
|
+
const agent = createAgent({
|
|
102
|
+
projectPath,
|
|
103
|
+
verbose: options.verbose || false
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
await agent.initialize();
|
|
108
|
+
|
|
109
|
+
// Build with options
|
|
110
|
+
const buildOptions = {
|
|
111
|
+
maxModuleRetries: options.maxRetries || 3,
|
|
112
|
+
testAfterEachModule: !options.skipTests,
|
|
113
|
+
continueOnFailure: options.continue || false
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const result = await agent.build(description, buildOptions);
|
|
117
|
+
|
|
118
|
+
// Exit with appropriate code
|
|
119
|
+
if (!result.success) {
|
|
120
|
+
process.exit(1);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
} catch (error) {
|
|
124
|
+
printError(`Agent failed: ${error.message}`);
|
|
125
|
+
console.log(chalk.gray('Check .vibecode/agent/orchestrator.log for details.'));
|
|
126
|
+
process.exit(1);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Analyze project without building
|
|
132
|
+
*/
|
|
133
|
+
async function analyzeCommand(description, options) {
|
|
134
|
+
const agent = createAgent({
|
|
135
|
+
projectPath: process.cwd(),
|
|
136
|
+
verbose: options.verbose || false
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
await agent.initialize();
|
|
141
|
+
const analysis = await agent.analyze(description);
|
|
142
|
+
|
|
143
|
+
// Output JSON if requested
|
|
144
|
+
if (options.json) {
|
|
145
|
+
console.log(JSON.stringify(analysis, null, 2));
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return analysis;
|
|
149
|
+
|
|
150
|
+
} catch (error) {
|
|
151
|
+
printError(`Analysis failed: ${error.message}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Show agent status with resume info
|
|
158
|
+
*/
|
|
159
|
+
async function showAgentStatus(options) {
|
|
160
|
+
const cwd = process.cwd();
|
|
161
|
+
const progress = await loadProgress(cwd);
|
|
162
|
+
|
|
163
|
+
if (!progress) {
|
|
164
|
+
console.log(chalk.yellow('\n📭 No active agent session.\n'));
|
|
165
|
+
console.log(chalk.gray(' Start new: vibecode agent "description" --new\n'));
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
console.log();
|
|
170
|
+
console.log(chalk.cyan('╭' + '─'.repeat(68) + '╮'));
|
|
171
|
+
console.log(chalk.cyan('│') + chalk.bold.white(' 🤖 AGENT STATUS') + ' '.repeat(50) + chalk.cyan('│'));
|
|
172
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
173
|
+
|
|
174
|
+
const projectLine = ` Project: ${progress.projectName}`;
|
|
175
|
+
console.log(chalk.cyan('│') + chalk.white(projectLine) + ' '.repeat(Math.max(0, 66 - projectLine.length)) + chalk.cyan('│'));
|
|
176
|
+
|
|
177
|
+
const completed = progress.completedModules?.length || progress.currentModule || 0;
|
|
178
|
+
const progressLine = ` Progress: ${completed}/${progress.totalModules} modules`;
|
|
179
|
+
console.log(chalk.cyan('│') + chalk.white(progressLine) + ' '.repeat(Math.max(0, 66 - progressLine.length)) + chalk.cyan('│'));
|
|
180
|
+
|
|
181
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
182
|
+
|
|
183
|
+
// Show modules with status
|
|
184
|
+
for (const mod of progress.modules || []) {
|
|
185
|
+
let icon, color;
|
|
186
|
+
switch (mod.status) {
|
|
187
|
+
case 'done':
|
|
188
|
+
icon = chalk.green('✓');
|
|
189
|
+
color = chalk.green;
|
|
190
|
+
break;
|
|
191
|
+
case 'building':
|
|
192
|
+
icon = chalk.yellow('◐');
|
|
193
|
+
color = chalk.yellow;
|
|
194
|
+
break;
|
|
195
|
+
case 'failed':
|
|
196
|
+
icon = chalk.red('✗');
|
|
197
|
+
color = chalk.red;
|
|
198
|
+
break;
|
|
199
|
+
default:
|
|
200
|
+
icon = chalk.gray('○');
|
|
201
|
+
color = chalk.gray;
|
|
202
|
+
}
|
|
203
|
+
const modLine = ` ${icon} ${color(mod.name)}`;
|
|
204
|
+
// Approximate length without ANSI codes
|
|
205
|
+
const approxLen = 5 + mod.name.length;
|
|
206
|
+
console.log(chalk.cyan('│') + modLine + ' '.repeat(Math.max(0, 66 - approxLen)) + chalk.cyan('│'));
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (progress.error) {
|
|
210
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
211
|
+
const errorLine = ` Error: ${progress.error.substring(0, 55)}`;
|
|
212
|
+
console.log(chalk.cyan('│') + chalk.red(errorLine) + ' '.repeat(Math.max(0, 66 - errorLine.length)) + chalk.cyan('│'));
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
216
|
+
|
|
217
|
+
const timeLine = ` Last updated: ${new Date(progress.lastUpdated).toLocaleString()}`;
|
|
218
|
+
console.log(chalk.cyan('│') + chalk.gray(timeLine) + ' '.repeat(Math.max(0, 66 - timeLine.length)) + chalk.cyan('│'));
|
|
219
|
+
|
|
220
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
221
|
+
console.log(chalk.cyan('│') + chalk.yellow(' 💡 Resume: vibecode agent --resume') + ' '.repeat(29) + chalk.cyan('│'));
|
|
222
|
+
console.log(chalk.cyan('│') + ' '.repeat(68) + chalk.cyan('│'));
|
|
223
|
+
console.log(chalk.cyan('╰' + '─'.repeat(68) + '╯'));
|
|
224
|
+
console.log();
|
|
225
|
+
|
|
226
|
+
// Output JSON if requested
|
|
227
|
+
if (options.json) {
|
|
228
|
+
console.log(JSON.stringify(progress, null, 2));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Resume agent from last stopped module
|
|
234
|
+
*/
|
|
235
|
+
async function resumeCommand(options) {
|
|
236
|
+
const cwd = process.cwd();
|
|
237
|
+
|
|
238
|
+
// Check for progress
|
|
239
|
+
const progress = await loadProgress(cwd);
|
|
240
|
+
if (!progress) {
|
|
241
|
+
console.log(chalk.yellow('\n📭 No session to resume.\n'));
|
|
242
|
+
console.log(chalk.gray(' Start new: vibecode agent "description" --new\n'));
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Create agent and resume
|
|
247
|
+
const agent = createAgent({
|
|
248
|
+
projectPath: cwd,
|
|
249
|
+
verbose: options.verbose || false
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
try {
|
|
253
|
+
// Determine from module
|
|
254
|
+
const fromModule = options.from ? parseInt(options.from) - 1 : undefined;
|
|
255
|
+
|
|
256
|
+
const resumeOptions = {
|
|
257
|
+
maxModuleRetries: options.maxRetries || 3,
|
|
258
|
+
testAfterEachModule: !options.skipTests,
|
|
259
|
+
continueOnFailure: options.continue || false,
|
|
260
|
+
fromModule
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
const result = await agent.resume(resumeOptions);
|
|
264
|
+
|
|
265
|
+
// Exit with appropriate code
|
|
266
|
+
if (result && !result.success) {
|
|
267
|
+
process.exit(1);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
} catch (error) {
|
|
271
|
+
printError(`Resume failed: ${error.message}`);
|
|
272
|
+
console.log(chalk.gray('Check .vibecode/agent/orchestrator.log for details.'));
|
|
273
|
+
process.exit(1);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Legacy status command (for compatibility)
|
|
279
|
+
*/
|
|
280
|
+
async function statusCommand(options) {
|
|
281
|
+
return showAgentStatus(options);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Export memory report
|
|
286
|
+
*/
|
|
287
|
+
async function reportCommand(options) {
|
|
288
|
+
const agent = createAgent({
|
|
289
|
+
projectPath: process.cwd()
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
try {
|
|
293
|
+
await agent.initialize();
|
|
294
|
+
const report = await agent.exportReport();
|
|
295
|
+
|
|
296
|
+
if (options.stdout) {
|
|
297
|
+
console.log(report);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
} catch (error) {
|
|
301
|
+
printError(`Report failed: ${error.message}`);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Clear agent memory
|
|
308
|
+
*/
|
|
309
|
+
async function clearCommand(options) {
|
|
310
|
+
if (!options.force) {
|
|
311
|
+
console.log(chalk.yellow('This will clear all agent memory.'));
|
|
312
|
+
console.log(chalk.gray('Use --force to confirm.'));
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const agent = createAgent({
|
|
317
|
+
projectPath: process.cwd()
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
try {
|
|
321
|
+
await agent.initialize();
|
|
322
|
+
await agent.clearMemory();
|
|
323
|
+
printSuccess('Agent memory cleared');
|
|
324
|
+
|
|
325
|
+
} catch (error) {
|
|
326
|
+
printError(`Clear failed: ${error.message}`);
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Generate project name from description
|
|
333
|
+
*/
|
|
334
|
+
function generateProjectName(description) {
|
|
335
|
+
const stopWords = ['a', 'an', 'the', 'for', 'and', 'or', 'but', 'in', 'on', 'at', 'to', 'with', 'my', 'our'];
|
|
336
|
+
|
|
337
|
+
const words = description
|
|
338
|
+
.toLowerCase()
|
|
339
|
+
.replace(/[^a-z0-9\s]/g, '')
|
|
340
|
+
.split(/\s+/)
|
|
341
|
+
.filter(w => w.length > 2 && !stopWords.includes(w))
|
|
342
|
+
.slice(0, 3);
|
|
343
|
+
|
|
344
|
+
if (words.length === 0) {
|
|
345
|
+
return `vibecode-agent-${Date.now().toString(36)}`;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return words.join('-') + '-agent';
|
|
349
|
+
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Ask Command
|
|
3
|
+
// Phase K6: Codebase Q&A
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { spawn } from 'child_process';
|
|
7
|
+
import fs from 'fs/promises';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import chalk from 'chalk';
|
|
10
|
+
import readline from 'readline';
|
|
11
|
+
|
|
12
|
+
export async function askCommand(question, options = {}) {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
|
|
15
|
+
// If no question, enter interactive mode
|
|
16
|
+
if (!question || question.length === 0) {
|
|
17
|
+
return interactiveAsk(cwd);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const questionText = Array.isArray(question) ? question.join(' ') : question;
|
|
21
|
+
return answerQuestion(cwd, questionText);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function interactiveAsk(cwd) {
|
|
25
|
+
console.log(chalk.cyan(`
|
|
26
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
27
|
+
│ 💬 CODEBASE Q&A │
|
|
28
|
+
│ │
|
|
29
|
+
│ Ask anything about your codebase. │
|
|
30
|
+
│ Type 'exit' to quit. │
|
|
31
|
+
│ │
|
|
32
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
33
|
+
`));
|
|
34
|
+
|
|
35
|
+
const rl = readline.createInterface({
|
|
36
|
+
input: process.stdin,
|
|
37
|
+
output: process.stdout,
|
|
38
|
+
prompt: chalk.green('ask> ')
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
rl.prompt();
|
|
42
|
+
|
|
43
|
+
rl.on('line', async (line) => {
|
|
44
|
+
const input = line.trim();
|
|
45
|
+
|
|
46
|
+
if (!input) {
|
|
47
|
+
rl.prompt();
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (input === 'exit' || input === 'quit' || input === 'q') {
|
|
52
|
+
console.log(chalk.cyan('\n👋 Goodbye!\n'));
|
|
53
|
+
rl.close();
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
await answerQuestion(cwd, input);
|
|
58
|
+
console.log('');
|
|
59
|
+
rl.prompt();
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
rl.on('close', () => process.exit(0));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function answerQuestion(cwd, question) {
|
|
66
|
+
console.log(chalk.gray('\n Analyzing codebase...\n'));
|
|
67
|
+
|
|
68
|
+
// Build context
|
|
69
|
+
const projectInfo = await getProjectContext(cwd);
|
|
70
|
+
|
|
71
|
+
const prompt = `
|
|
72
|
+
# Codebase Question
|
|
73
|
+
|
|
74
|
+
## Project: ${path.basename(cwd)}
|
|
75
|
+
## Type: ${projectInfo.type}
|
|
76
|
+
|
|
77
|
+
## Project Structure:
|
|
78
|
+
${projectInfo.structure}
|
|
79
|
+
|
|
80
|
+
## Key Files:
|
|
81
|
+
${projectInfo.keyFiles.join('\n')}
|
|
82
|
+
|
|
83
|
+
## Question:
|
|
84
|
+
${question}
|
|
85
|
+
|
|
86
|
+
## Instructions:
|
|
87
|
+
1. Analyze the codebase to answer the question
|
|
88
|
+
2. Reference specific files and line numbers when applicable
|
|
89
|
+
3. Provide code examples if helpful
|
|
90
|
+
4. Be concise but thorough
|
|
91
|
+
5. If you need to look at specific files, do so
|
|
92
|
+
|
|
93
|
+
Answer the question now.
|
|
94
|
+
`;
|
|
95
|
+
|
|
96
|
+
await runClaudeCode(prompt, cwd);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async function getProjectContext(cwd) {
|
|
100
|
+
const context = {
|
|
101
|
+
type: 'unknown',
|
|
102
|
+
structure: '',
|
|
103
|
+
keyFiles: []
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Detect project type
|
|
107
|
+
try {
|
|
108
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
109
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
110
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
111
|
+
|
|
112
|
+
if (deps.next) context.type = 'Next.js';
|
|
113
|
+
else if (deps.nuxt) context.type = 'Nuxt';
|
|
114
|
+
else if (deps['@angular/core']) context.type = 'Angular';
|
|
115
|
+
else if (deps.react) context.type = 'React';
|
|
116
|
+
else if (deps.vue) context.type = 'Vue';
|
|
117
|
+
else if (deps.svelte) context.type = 'Svelte';
|
|
118
|
+
else if (deps.express) context.type = 'Express';
|
|
119
|
+
else if (deps.fastify) context.type = 'Fastify';
|
|
120
|
+
else if (deps.koa) context.type = 'Koa';
|
|
121
|
+
else if (deps.nestjs || deps['@nestjs/core']) context.type = 'NestJS';
|
|
122
|
+
else context.type = 'Node.js';
|
|
123
|
+
} catch {
|
|
124
|
+
// Check for other project types
|
|
125
|
+
try {
|
|
126
|
+
await fs.access(path.join(cwd, 'Cargo.toml'));
|
|
127
|
+
context.type = 'Rust';
|
|
128
|
+
} catch {}
|
|
129
|
+
try {
|
|
130
|
+
await fs.access(path.join(cwd, 'go.mod'));
|
|
131
|
+
context.type = 'Go';
|
|
132
|
+
} catch {}
|
|
133
|
+
try {
|
|
134
|
+
await fs.access(path.join(cwd, 'requirements.txt'));
|
|
135
|
+
context.type = 'Python';
|
|
136
|
+
} catch {}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Get directory structure
|
|
140
|
+
context.structure = await getDirectoryTree(cwd, 3);
|
|
141
|
+
|
|
142
|
+
// Get key files
|
|
143
|
+
context.keyFiles = await findKeyFiles(cwd);
|
|
144
|
+
|
|
145
|
+
return context;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function getDirectoryTree(dir, depth, prefix = '') {
|
|
149
|
+
if (depth === 0) return '';
|
|
150
|
+
|
|
151
|
+
let result = '';
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
155
|
+
const filtered = entries.filter(e =>
|
|
156
|
+
!e.name.startsWith('.') &&
|
|
157
|
+
e.name !== 'node_modules' &&
|
|
158
|
+
e.name !== 'dist' &&
|
|
159
|
+
e.name !== 'build' &&
|
|
160
|
+
e.name !== '.next' &&
|
|
161
|
+
e.name !== 'coverage' &&
|
|
162
|
+
e.name !== '__pycache__'
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
for (const entry of filtered.slice(0, 15)) {
|
|
166
|
+
const icon = entry.isDirectory() ? '📁' : '📄';
|
|
167
|
+
result += `${prefix}${icon} ${entry.name}\n`;
|
|
168
|
+
|
|
169
|
+
if (entry.isDirectory() && depth > 1) {
|
|
170
|
+
result += await getDirectoryTree(
|
|
171
|
+
path.join(dir, entry.name),
|
|
172
|
+
depth - 1,
|
|
173
|
+
prefix + ' '
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (filtered.length > 15) {
|
|
179
|
+
result += `${prefix}... and ${filtered.length - 15} more\n`;
|
|
180
|
+
}
|
|
181
|
+
} catch {}
|
|
182
|
+
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function findKeyFiles(cwd) {
|
|
187
|
+
const keyFiles = [];
|
|
188
|
+
const importantFiles = [
|
|
189
|
+
'package.json',
|
|
190
|
+
'tsconfig.json',
|
|
191
|
+
'README.md',
|
|
192
|
+
'CLAUDE.md',
|
|
193
|
+
'.env.example',
|
|
194
|
+
'src/index.ts',
|
|
195
|
+
'src/index.js',
|
|
196
|
+
'src/main.ts',
|
|
197
|
+
'src/main.js',
|
|
198
|
+
'src/app.ts',
|
|
199
|
+
'src/app.js',
|
|
200
|
+
'app/page.tsx',
|
|
201
|
+
'app/layout.tsx',
|
|
202
|
+
'pages/index.tsx',
|
|
203
|
+
'pages/_app.tsx',
|
|
204
|
+
'prisma/schema.prisma',
|
|
205
|
+
'drizzle.config.ts',
|
|
206
|
+
'Dockerfile',
|
|
207
|
+
'docker-compose.yml'
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
for (const file of importantFiles) {
|
|
211
|
+
try {
|
|
212
|
+
await fs.access(path.join(cwd, file));
|
|
213
|
+
keyFiles.push(file);
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
return keyFiles;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
async function runClaudeCode(prompt, cwd) {
|
|
221
|
+
return new Promise((resolve) => {
|
|
222
|
+
const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
|
|
223
|
+
cwd,
|
|
224
|
+
stdio: 'inherit'
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
child.on('close', resolve);
|
|
228
|
+
child.on('error', () => resolve());
|
|
229
|
+
});
|
|
230
|
+
}
|