@nclamvn/vibecode-cli 1.7.0 → 1.8.1
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/SESSION_NOTES.md +119 -0
- package/bin/vibecode.js +53 -0
- package/package.json +1 -1
- package/src/commands/ask.js +230 -0
- package/src/commands/docs.js +167 -0
- package/src/commands/git.js +103 -2
- package/src/commands/migrate.js +341 -0
- package/src/commands/refactor.js +205 -0
- package/src/commands/review.js +126 -1
- package/src/commands/security.js +229 -0
- package/src/commands/test.js +194 -0
- package/src/config/constants.js +5 -1
- package/src/index.js +8 -0
- package/docs-site/README.md +0 -41
- package/docs-site/blog/2019-05-28-first-blog-post.md +0 -12
- package/docs-site/blog/2019-05-29-long-blog-post.md +0 -44
- package/docs-site/blog/2021-08-01-mdx-blog-post.mdx +0 -24
- package/docs-site/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs-site/blog/2021-08-26-welcome/index.md +0 -29
- package/docs-site/blog/authors.yml +0 -25
- package/docs-site/blog/tags.yml +0 -19
- package/docs-site/docs/commands/agent.md +0 -162
- package/docs-site/docs/commands/assist.md +0 -71
- package/docs-site/docs/commands/build.md +0 -53
- package/docs-site/docs/commands/config.md +0 -30
- package/docs-site/docs/commands/debug.md +0 -173
- package/docs-site/docs/commands/doctor.md +0 -34
- package/docs-site/docs/commands/go.md +0 -128
- package/docs-site/docs/commands/index.md +0 -79
- package/docs-site/docs/commands/init.md +0 -42
- package/docs-site/docs/commands/learn.md +0 -82
- package/docs-site/docs/commands/lock.md +0 -33
- package/docs-site/docs/commands/plan.md +0 -29
- package/docs-site/docs/commands/review.md +0 -31
- package/docs-site/docs/commands/snapshot.md +0 -34
- package/docs-site/docs/commands/start.md +0 -32
- package/docs-site/docs/commands/status.md +0 -37
- package/docs-site/docs/commands/undo.md +0 -83
- package/docs-site/docs/configuration.md +0 -72
- package/docs-site/docs/faq.md +0 -83
- package/docs-site/docs/getting-started.md +0 -119
- package/docs-site/docs/guides/agent-mode.md +0 -94
- package/docs-site/docs/guides/debug-mode.md +0 -83
- package/docs-site/docs/guides/magic-mode.md +0 -107
- package/docs-site/docs/installation.md +0 -98
- package/docs-site/docs/intro.md +0 -67
- package/docs-site/docusaurus.config.ts +0 -141
- package/docs-site/package-lock.json +0 -18039
- package/docs-site/package.json +0 -48
- package/docs-site/sidebars.ts +0 -70
- package/docs-site/src/components/HomepageFeatures/index.tsx +0 -72
- package/docs-site/src/components/HomepageFeatures/styles.module.css +0 -16
- package/docs-site/src/css/custom.css +0 -30
- package/docs-site/src/pages/index.module.css +0 -23
- package/docs-site/src/pages/index.tsx +0 -44
- package/docs-site/src/pages/markdown-page.md +0 -7
- package/docs-site/src/theme/Footer/index.tsx +0 -127
- package/docs-site/src/theme/Footer/styles.module.css +0 -285
- package/docs-site/static/.nojekyll +0 -0
- package/docs-site/static/img/docusaurus-social-card.jpg +0 -0
- package/docs-site/static/img/docusaurus.png +0 -0
- package/docs-site/static/img/favicon.ico +0 -0
- package/docs-site/static/img/logo.svg +0 -1
- package/docs-site/static/img/undraw_docusaurus_mountain.svg +0 -171
- package/docs-site/static/img/undraw_docusaurus_react.svg +0 -170
- package/docs-site/static/img/undraw_docusaurus_tree.svg +0 -40
- package/docs-site/tsconfig.json +0 -8
package/src/commands/review.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
2
|
// VIBECODE CLI - Review Command
|
|
3
|
+
// Phase K1: AI Code Review
|
|
3
4
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
4
5
|
|
|
5
6
|
import chalk from 'chalk';
|
|
6
7
|
import ora from 'ora';
|
|
7
8
|
import path from 'path';
|
|
8
|
-
import
|
|
9
|
+
import fs from 'fs/promises';
|
|
10
|
+
import { exec, spawn } from 'child_process';
|
|
9
11
|
import { promisify } from 'util';
|
|
10
12
|
import inquirer from 'inquirer';
|
|
11
13
|
import { workspaceExists, getProjectName } from '../core/workspace.js';
|
|
@@ -26,6 +28,10 @@ import { printBox, printError, printSuccess, printWarning, printNextStep } from
|
|
|
26
28
|
const execAsync = promisify(exec);
|
|
27
29
|
|
|
28
30
|
export async function reviewCommand(options = {}) {
|
|
31
|
+
// K1: AI-powered code review
|
|
32
|
+
if (options.ai) {
|
|
33
|
+
return aiCodeReview(options);
|
|
34
|
+
}
|
|
29
35
|
const spinner = ora('Starting review...').start();
|
|
30
36
|
|
|
31
37
|
try {
|
|
@@ -288,3 +294,122 @@ async function checkGitStatus() {
|
|
|
288
294
|
return { clean: true, message: 'Git not available' };
|
|
289
295
|
}
|
|
290
296
|
}
|
|
297
|
+
|
|
298
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
299
|
+
// K1: AI CODE REVIEW
|
|
300
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
301
|
+
|
|
302
|
+
async function aiCodeReview(options) {
|
|
303
|
+
const cwd = process.cwd();
|
|
304
|
+
|
|
305
|
+
console.log(chalk.cyan(`
|
|
306
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
307
|
+
│ 🔍 AI CODE REVIEW │
|
|
308
|
+
│ │
|
|
309
|
+
│ Analyzing codebase for: │
|
|
310
|
+
│ • Code quality & best practices │
|
|
311
|
+
│ • Potential bugs & edge cases │
|
|
312
|
+
│ • Performance concerns │
|
|
313
|
+
│ • Security vulnerabilities │
|
|
314
|
+
│ • Maintainability issues │
|
|
315
|
+
│ │
|
|
316
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
317
|
+
`));
|
|
318
|
+
|
|
319
|
+
// Gather codebase context
|
|
320
|
+
const files = await gatherSourceFiles(cwd, options.path);
|
|
321
|
+
|
|
322
|
+
console.log(chalk.gray(` Found ${files.length} files to review\n`));
|
|
323
|
+
|
|
324
|
+
const prompt = `
|
|
325
|
+
# AI Code Review Request
|
|
326
|
+
|
|
327
|
+
## Project: ${path.basename(cwd)}
|
|
328
|
+
|
|
329
|
+
## Files to Review:
|
|
330
|
+
${files.map(f => `- ${f}`).join('\n')}
|
|
331
|
+
|
|
332
|
+
## Review Criteria:
|
|
333
|
+
1. **Code Quality**: Clean code, readability, naming conventions
|
|
334
|
+
2. **Best Practices**: Design patterns, DRY, SOLID principles
|
|
335
|
+
3. **Potential Bugs**: Edge cases, null checks, error handling
|
|
336
|
+
4. **Performance**: Inefficient loops, memory leaks, N+1 queries
|
|
337
|
+
5. **Security**: Input validation, XSS, SQL injection, auth issues
|
|
338
|
+
6. **Maintainability**: Modularity, testability, documentation
|
|
339
|
+
|
|
340
|
+
## Output Format:
|
|
341
|
+
For each issue found, provide:
|
|
342
|
+
- **Severity**: Critical / High / Medium / Low
|
|
343
|
+
- **Category**: Bug / Performance / Security / Quality / Maintainability
|
|
344
|
+
- **File**: path/to/file.ts
|
|
345
|
+
- **Line**: (if applicable)
|
|
346
|
+
- **Issue**: Description
|
|
347
|
+
- **Suggestion**: How to fix
|
|
348
|
+
|
|
349
|
+
## Summary:
|
|
350
|
+
End with overall score (A-F) and top 3 priorities.
|
|
351
|
+
|
|
352
|
+
Review the codebase now.
|
|
353
|
+
`;
|
|
354
|
+
|
|
355
|
+
// Write prompt
|
|
356
|
+
const promptFile = path.join(cwd, '.vibecode', 'review-prompt.md');
|
|
357
|
+
await fs.mkdir(path.dirname(promptFile), { recursive: true });
|
|
358
|
+
await fs.writeFile(promptFile, prompt);
|
|
359
|
+
|
|
360
|
+
// Run Claude Code
|
|
361
|
+
console.log(chalk.gray(' Analyzing with Claude Code...\n'));
|
|
362
|
+
|
|
363
|
+
await runClaudeCode(prompt, cwd);
|
|
364
|
+
|
|
365
|
+
// Save report
|
|
366
|
+
const reportDir = path.join(cwd, '.vibecode', 'reports');
|
|
367
|
+
await fs.mkdir(reportDir, { recursive: true });
|
|
368
|
+
const reportFile = path.join(reportDir, `review-${Date.now()}.md`);
|
|
369
|
+
|
|
370
|
+
console.log(chalk.green(`\n✅ Review complete!`));
|
|
371
|
+
console.log(chalk.gray(` Report saved to: .vibecode/reports/\n`));
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
async function gatherSourceFiles(cwd, targetPath) {
|
|
375
|
+
const files = [];
|
|
376
|
+
const extensions = ['.js', '.ts', '.tsx', '.jsx', '.py', '.go', '.rs', '.vue', '.svelte'];
|
|
377
|
+
const ignoreDirs = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.vibecode', '__pycache__'];
|
|
378
|
+
|
|
379
|
+
const scanDir = targetPath ? path.join(cwd, targetPath) : cwd;
|
|
380
|
+
|
|
381
|
+
async function scan(dir) {
|
|
382
|
+
try {
|
|
383
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
384
|
+
|
|
385
|
+
for (const entry of entries) {
|
|
386
|
+
if (entry.name.startsWith('.')) continue;
|
|
387
|
+
if (ignoreDirs.includes(entry.name)) continue;
|
|
388
|
+
|
|
389
|
+
const fullPath = path.join(dir, entry.name);
|
|
390
|
+
const relativePath = path.relative(cwd, fullPath);
|
|
391
|
+
|
|
392
|
+
if (entry.isDirectory()) {
|
|
393
|
+
await scan(fullPath);
|
|
394
|
+
} else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
395
|
+
files.push(relativePath);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch {}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
await scan(scanDir);
|
|
402
|
+
return files.slice(0, 50); // Limit files
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
async function runClaudeCode(prompt, cwd) {
|
|
406
|
+
return new Promise((resolve) => {
|
|
407
|
+
const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
|
|
408
|
+
cwd,
|
|
409
|
+
stdio: 'inherit'
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
child.on('close', resolve);
|
|
413
|
+
child.on('error', () => resolve());
|
|
414
|
+
});
|
|
415
|
+
}
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Security Command
|
|
3
|
+
// Phase K5: AI Security Audit
|
|
4
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
|
+
|
|
6
|
+
import { spawn, exec } from 'child_process';
|
|
7
|
+
import { promisify } from 'util';
|
|
8
|
+
import fs from 'fs/promises';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import chalk from 'chalk';
|
|
11
|
+
|
|
12
|
+
const execAsync = promisify(exec);
|
|
13
|
+
|
|
14
|
+
export async function securityCommand(options = {}) {
|
|
15
|
+
const cwd = process.cwd();
|
|
16
|
+
|
|
17
|
+
console.log(chalk.cyan(`
|
|
18
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
19
|
+
│ 🔒 SECURITY AUDIT │
|
|
20
|
+
│ │
|
|
21
|
+
│ Scanning for vulnerabilities... │
|
|
22
|
+
│ │
|
|
23
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
24
|
+
`));
|
|
25
|
+
|
|
26
|
+
const results = {
|
|
27
|
+
npmAudit: null,
|
|
28
|
+
secretsScan: [],
|
|
29
|
+
timestamp: new Date().toISOString()
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// 1. Run npm audit
|
|
33
|
+
console.log(chalk.gray('\n [1/3] Running npm audit...\n'));
|
|
34
|
+
try {
|
|
35
|
+
const { stdout } = await execAsync('npm audit --json', { cwd, timeout: 60000 });
|
|
36
|
+
results.npmAudit = JSON.parse(stdout);
|
|
37
|
+
console.log(chalk.green(' ✓ npm audit complete'));
|
|
38
|
+
} catch (error) {
|
|
39
|
+
try {
|
|
40
|
+
if (error.stdout) {
|
|
41
|
+
results.npmAudit = JSON.parse(error.stdout);
|
|
42
|
+
const vulns = results.npmAudit?.metadata?.vulnerabilities;
|
|
43
|
+
if (vulns) {
|
|
44
|
+
const total = vulns.critical + vulns.high + vulns.moderate + vulns.low;
|
|
45
|
+
if (total > 0) {
|
|
46
|
+
console.log(chalk.yellow(` ⚠ Found ${total} vulnerabilities`));
|
|
47
|
+
} else {
|
|
48
|
+
console.log(chalk.green(' ✓ No vulnerabilities found'));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
} catch {
|
|
53
|
+
results.npmAudit = { error: 'npm audit failed or not applicable' };
|
|
54
|
+
console.log(chalk.gray(' - npm audit not applicable'));
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 2. Scan for secrets
|
|
59
|
+
console.log(chalk.gray('\n [2/3] Scanning for exposed secrets...\n'));
|
|
60
|
+
results.secretsScan = await scanForSecrets(cwd);
|
|
61
|
+
if (results.secretsScan.length > 0) {
|
|
62
|
+
console.log(chalk.red(` ⚠ Found ${results.secretsScan.length} potential secrets!`));
|
|
63
|
+
for (const secret of results.secretsScan.slice(0, 5)) {
|
|
64
|
+
console.log(chalk.gray(` - ${secret.file}: ${secret.type}`));
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
console.log(chalk.green(' ✓ No exposed secrets detected'));
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// 3. AI security analysis
|
|
71
|
+
console.log(chalk.gray('\n [3/3] AI security analysis...\n'));
|
|
72
|
+
|
|
73
|
+
const vulnSummary = results.npmAudit?.metadata?.vulnerabilities;
|
|
74
|
+
const npmSummary = vulnSummary
|
|
75
|
+
? `Critical: ${vulnSummary.critical}, High: ${vulnSummary.high}, Moderate: ${vulnSummary.moderate}, Low: ${vulnSummary.low}`
|
|
76
|
+
: 'npm audit not available';
|
|
77
|
+
|
|
78
|
+
const prompt = `
|
|
79
|
+
# Security Audit Request
|
|
80
|
+
|
|
81
|
+
## Project: ${path.basename(cwd)}
|
|
82
|
+
|
|
83
|
+
## NPM Audit Results:
|
|
84
|
+
${npmSummary}
|
|
85
|
+
|
|
86
|
+
## Potential Secrets Found:
|
|
87
|
+
${results.secretsScan.map(s => `- ${s.file}: ${s.type}`).join('\n') || 'None detected'}
|
|
88
|
+
|
|
89
|
+
## Security Analysis Required:
|
|
90
|
+
Analyze the codebase for security vulnerabilities:
|
|
91
|
+
|
|
92
|
+
1. **Authentication Issues**
|
|
93
|
+
- Weak password handling
|
|
94
|
+
- Missing auth checks on protected routes
|
|
95
|
+
- Insecure session management
|
|
96
|
+
- JWT vulnerabilities
|
|
97
|
+
|
|
98
|
+
2. **Input Validation**
|
|
99
|
+
- SQL injection risks
|
|
100
|
+
- XSS vulnerabilities
|
|
101
|
+
- Command injection
|
|
102
|
+
- Path traversal
|
|
103
|
+
- ReDoS (regex denial of service)
|
|
104
|
+
|
|
105
|
+
3. **Data Exposure**
|
|
106
|
+
- Sensitive data in logs
|
|
107
|
+
- Exposed API keys in code
|
|
108
|
+
- Insecure data storage
|
|
109
|
+
- PII handling issues
|
|
110
|
+
|
|
111
|
+
4. **Configuration Issues**
|
|
112
|
+
- CORS misconfiguration
|
|
113
|
+
- Missing security headers
|
|
114
|
+
- Debug mode in production
|
|
115
|
+
- Insecure defaults
|
|
116
|
+
|
|
117
|
+
5. **Dependencies**
|
|
118
|
+
- Known vulnerable packages
|
|
119
|
+
- Outdated dependencies
|
|
120
|
+
- Unnecessary dependencies
|
|
121
|
+
|
|
122
|
+
## Output Format:
|
|
123
|
+
For each vulnerability found:
|
|
124
|
+
- **Severity**: Critical / High / Medium / Low
|
|
125
|
+
- **Category**: Auth / Injection / Exposure / Config / Dependency
|
|
126
|
+
- **Location**: File and line number
|
|
127
|
+
- **Description**: What's the issue
|
|
128
|
+
- **Remediation**: How to fix it
|
|
129
|
+
|
|
130
|
+
End with a security score (A-F) and priority fixes.
|
|
131
|
+
`;
|
|
132
|
+
|
|
133
|
+
const promptFile = path.join(cwd, '.vibecode', 'security-prompt.md');
|
|
134
|
+
await fs.mkdir(path.dirname(promptFile), { recursive: true });
|
|
135
|
+
await fs.writeFile(promptFile, prompt);
|
|
136
|
+
|
|
137
|
+
await runClaudeCode(prompt, cwd);
|
|
138
|
+
|
|
139
|
+
// Save report
|
|
140
|
+
const reportPath = path.join(cwd, '.vibecode', 'reports', `security-${Date.now()}.json`);
|
|
141
|
+
await fs.mkdir(path.dirname(reportPath), { recursive: true });
|
|
142
|
+
await fs.writeFile(reportPath, JSON.stringify(results, null, 2));
|
|
143
|
+
|
|
144
|
+
console.log(chalk.green('\n✅ Security audit complete!'));
|
|
145
|
+
console.log(chalk.gray(` Report saved to: .vibecode/reports/\n`));
|
|
146
|
+
|
|
147
|
+
// Auto-fix option
|
|
148
|
+
if (options.fix) {
|
|
149
|
+
console.log(chalk.yellow('\n Attempting auto-fix...\n'));
|
|
150
|
+
try {
|
|
151
|
+
await execAsync('npm audit fix', { cwd });
|
|
152
|
+
console.log(chalk.green(' ✓ npm audit fix completed\n'));
|
|
153
|
+
} catch {
|
|
154
|
+
console.log(chalk.gray(' - npm audit fix not applicable\n'));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
async function scanForSecrets(cwd) {
|
|
160
|
+
const secrets = [];
|
|
161
|
+
const patterns = [
|
|
162
|
+
{ regex: /(['"])?(api[_-]?key|apikey)(['"])?\s*[:=]\s*(['"])[a-zA-Z0-9]{20,}(['"])/gi, type: 'API Key' },
|
|
163
|
+
{ regex: /(['"])?(secret|password|passwd|pwd)(['"])?\s*[:=]\s*(['"])[^'"]{8,}(['"])/gi, type: 'Password/Secret' },
|
|
164
|
+
{ regex: /(['"])?(aws[_-]?access[_-]?key[_-]?id)(['"])?\s*[:=]\s*(['"])[A-Z0-9]{20}(['"])/gi, type: 'AWS Access Key' },
|
|
165
|
+
{ regex: /(['"])?(aws[_-]?secret)(['"])?\s*[:=]\s*(['"])[a-zA-Z0-9/+=]{40}(['"])/gi, type: 'AWS Secret Key' },
|
|
166
|
+
{ regex: /(['"])?(private[_-]?key)(['"])?\s*[:=]\s*(['"])-----BEGIN/gi, type: 'Private Key' },
|
|
167
|
+
{ regex: /(['"])?(auth[_-]?token|bearer)(['"])?\s*[:=]\s*(['"])[a-zA-Z0-9._-]{20,}(['"])/gi, type: 'Auth Token' },
|
|
168
|
+
{ regex: /(['"])?(github[_-]?token)(['"])?\s*[:=]\s*(['"])gh[ps]_[a-zA-Z0-9]{36}(['"])/gi, type: 'GitHub Token' },
|
|
169
|
+
{ regex: /(['"])?(stripe[_-]?key)(['"])?\s*[:=]\s*(['"])sk_[a-zA-Z0-9]{24,}(['"])/gi, type: 'Stripe Key' }
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
const files = await getAllSourceFiles(cwd);
|
|
173
|
+
|
|
174
|
+
for (const file of files) {
|
|
175
|
+
try {
|
|
176
|
+
const content = await fs.readFile(path.join(cwd, file), 'utf-8');
|
|
177
|
+
|
|
178
|
+
for (const { regex, type } of patterns) {
|
|
179
|
+
regex.lastIndex = 0; // Reset regex state
|
|
180
|
+
if (regex.test(content)) {
|
|
181
|
+
secrets.push({ file, type });
|
|
182
|
+
break; // One type per file is enough
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
} catch {}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return secrets;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function getAllSourceFiles(cwd) {
|
|
192
|
+
const files = [];
|
|
193
|
+
const extensions = ['.js', '.ts', '.jsx', '.tsx', '.json', '.env', '.yaml', '.yml', '.py', '.go'];
|
|
194
|
+
const ignoreDirs = ['node_modules', '.git', 'dist', 'build', '.next', 'coverage'];
|
|
195
|
+
|
|
196
|
+
async function scan(dir) {
|
|
197
|
+
try {
|
|
198
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
199
|
+
|
|
200
|
+
for (const entry of entries) {
|
|
201
|
+
if (entry.name.startsWith('.') && entry.name !== '.env') continue;
|
|
202
|
+
if (ignoreDirs.includes(entry.name)) continue;
|
|
203
|
+
|
|
204
|
+
const fullPath = path.join(dir, entry.name);
|
|
205
|
+
|
|
206
|
+
if (entry.isDirectory()) {
|
|
207
|
+
await scan(fullPath);
|
|
208
|
+
} else if (extensions.some(ext => entry.name.endsWith(ext))) {
|
|
209
|
+
files.push(path.relative(cwd, fullPath));
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
} catch {}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
await scan(cwd);
|
|
216
|
+
return files.slice(0, 100);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
async function runClaudeCode(prompt, cwd) {
|
|
220
|
+
return new Promise((resolve) => {
|
|
221
|
+
const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
|
|
222
|
+
cwd,
|
|
223
|
+
stdio: 'inherit'
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
child.on('close', resolve);
|
|
227
|
+
child.on('error', () => resolve());
|
|
228
|
+
});
|
|
229
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════════════════════════════════════════
|
|
2
|
+
// VIBECODE CLI - Test Command
|
|
3
|
+
// Phase K2: AI Test Generation
|
|
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 inquirer from 'inquirer';
|
|
11
|
+
|
|
12
|
+
export async function testCommand(targetPath, options = {}) {
|
|
13
|
+
const cwd = process.cwd();
|
|
14
|
+
|
|
15
|
+
// Generate tests
|
|
16
|
+
if (options.generate) {
|
|
17
|
+
return generateTests(cwd, targetPath, options);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Run tests (pass through to npm test)
|
|
21
|
+
if (options.run) {
|
|
22
|
+
const { exec } = await import('child_process');
|
|
23
|
+
const child = exec('npm test', { cwd });
|
|
24
|
+
child.stdout?.pipe(process.stdout);
|
|
25
|
+
child.stderr?.pipe(process.stderr);
|
|
26
|
+
return new Promise(resolve => child.on('close', resolve));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Coverage
|
|
30
|
+
if (options.coverage) {
|
|
31
|
+
const { exec } = await import('child_process');
|
|
32
|
+
const child = exec('npm run test:coverage || npm test -- --coverage', { cwd });
|
|
33
|
+
child.stdout?.pipe(process.stdout);
|
|
34
|
+
child.stderr?.pipe(process.stderr);
|
|
35
|
+
return new Promise(resolve => child.on('close', resolve));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Default: show menu
|
|
39
|
+
const { action } = await inquirer.prompt([{
|
|
40
|
+
type: 'list',
|
|
41
|
+
name: 'action',
|
|
42
|
+
message: 'Test options:',
|
|
43
|
+
choices: [
|
|
44
|
+
{ name: '🧪 Run tests (npm test)', value: 'run' },
|
|
45
|
+
{ name: '✨ Generate tests for file/folder', value: 'generate' },
|
|
46
|
+
{ name: '📊 Show coverage', value: 'coverage' },
|
|
47
|
+
{ name: '👋 Exit', value: 'exit' }
|
|
48
|
+
]
|
|
49
|
+
}]);
|
|
50
|
+
|
|
51
|
+
if (action === 'run') {
|
|
52
|
+
return testCommand(null, { run: true });
|
|
53
|
+
}
|
|
54
|
+
if (action === 'generate') {
|
|
55
|
+
const { target } = await inquirer.prompt([{
|
|
56
|
+
type: 'input',
|
|
57
|
+
name: 'target',
|
|
58
|
+
message: 'Path to generate tests for:',
|
|
59
|
+
default: 'src/'
|
|
60
|
+
}]);
|
|
61
|
+
return generateTests(cwd, target, options);
|
|
62
|
+
}
|
|
63
|
+
if (action === 'coverage') {
|
|
64
|
+
return testCommand(null, { coverage: true });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function generateTests(cwd, targetPath, options) {
|
|
69
|
+
console.log(chalk.cyan(`
|
|
70
|
+
╭────────────────────────────────────────────────────────────────────╮
|
|
71
|
+
│ 🧪 TEST GENERATION │
|
|
72
|
+
│ │
|
|
73
|
+
│ Target: ${(targetPath || 'src/').padEnd(52)}│
|
|
74
|
+
│ │
|
|
75
|
+
╰────────────────────────────────────────────────────────────────────╯
|
|
76
|
+
`));
|
|
77
|
+
|
|
78
|
+
// Detect test framework
|
|
79
|
+
const framework = await detectTestFramework(cwd);
|
|
80
|
+
console.log(chalk.gray(` Test framework: ${framework}\n`));
|
|
81
|
+
|
|
82
|
+
// Get files to generate tests for
|
|
83
|
+
const files = await getFilesToTest(cwd, targetPath || 'src/');
|
|
84
|
+
|
|
85
|
+
if (files.length === 0) {
|
|
86
|
+
console.log(chalk.yellow(' No files found to generate tests for.\n'));
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
console.log(chalk.gray(` Found ${files.length} files\n`));
|
|
91
|
+
|
|
92
|
+
const prompt = `
|
|
93
|
+
# Test Generation Request
|
|
94
|
+
|
|
95
|
+
## Project: ${path.basename(cwd)}
|
|
96
|
+
## Test Framework: ${framework}
|
|
97
|
+
|
|
98
|
+
## Files to Generate Tests For:
|
|
99
|
+
${files.map(f => `- ${f}`).join('\n')}
|
|
100
|
+
|
|
101
|
+
## Instructions:
|
|
102
|
+
1. Read each source file
|
|
103
|
+
2. Generate comprehensive tests including:
|
|
104
|
+
- Unit tests for each function/method
|
|
105
|
+
- Edge cases (null, undefined, empty, boundary values)
|
|
106
|
+
- Error handling tests
|
|
107
|
+
- Mock external dependencies
|
|
108
|
+
- Integration tests where appropriate
|
|
109
|
+
|
|
110
|
+
3. Use ${framework} syntax and conventions
|
|
111
|
+
4. Follow AAA pattern (Arrange, Act, Assert)
|
|
112
|
+
5. Add descriptive test names
|
|
113
|
+
6. Include setup/teardown if needed
|
|
114
|
+
|
|
115
|
+
## Output:
|
|
116
|
+
Create test files in __tests__/ or *.test.ts/js format.
|
|
117
|
+
For each source file, create corresponding test file.
|
|
118
|
+
|
|
119
|
+
Generate tests now.
|
|
120
|
+
`;
|
|
121
|
+
|
|
122
|
+
const promptFile = path.join(cwd, '.vibecode', 'test-gen-prompt.md');
|
|
123
|
+
await fs.mkdir(path.dirname(promptFile), { recursive: true });
|
|
124
|
+
await fs.writeFile(promptFile, prompt);
|
|
125
|
+
|
|
126
|
+
console.log(chalk.gray(' Generating tests with Claude Code...\n'));
|
|
127
|
+
|
|
128
|
+
await runClaudeCode(prompt, cwd);
|
|
129
|
+
|
|
130
|
+
console.log(chalk.green('\n✅ Tests generated!'));
|
|
131
|
+
console.log(chalk.gray(' Run: npm test\n'));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function detectTestFramework(cwd) {
|
|
135
|
+
try {
|
|
136
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
137
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
138
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
139
|
+
|
|
140
|
+
if (deps.vitest) return 'vitest';
|
|
141
|
+
if (deps.jest) return 'jest';
|
|
142
|
+
if (deps.mocha) return 'mocha';
|
|
143
|
+
if (deps['@testing-library/react']) return 'jest + @testing-library/react';
|
|
144
|
+
if (deps.ava) return 'ava';
|
|
145
|
+
if (deps.tap) return 'tap';
|
|
146
|
+
if (deps.uvu) return 'uvu';
|
|
147
|
+
|
|
148
|
+
return 'jest'; // default
|
|
149
|
+
} catch {
|
|
150
|
+
return 'jest';
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async function getFilesToTest(cwd, targetPath) {
|
|
155
|
+
const files = [];
|
|
156
|
+
const fullPath = path.join(cwd, targetPath);
|
|
157
|
+
|
|
158
|
+
async function scan(dir) {
|
|
159
|
+
try {
|
|
160
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
161
|
+
|
|
162
|
+
for (const entry of entries) {
|
|
163
|
+
if (entry.name.startsWith('.')) continue;
|
|
164
|
+
if (entry.name.includes('.test.') || entry.name.includes('.spec.')) continue;
|
|
165
|
+
if (entry.name === '__tests__') continue;
|
|
166
|
+
if (entry.name === 'node_modules') continue;
|
|
167
|
+
if (entry.name === 'dist' || entry.name === 'build') continue;
|
|
168
|
+
|
|
169
|
+
const entryPath = path.join(dir, entry.name);
|
|
170
|
+
|
|
171
|
+
if (entry.isDirectory()) {
|
|
172
|
+
await scan(entryPath);
|
|
173
|
+
} else if (/\.(js|ts|jsx|tsx)$/.test(entry.name)) {
|
|
174
|
+
files.push(path.relative(cwd, entryPath));
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
} catch {}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
await scan(fullPath);
|
|
181
|
+
return files.slice(0, 20); // Limit
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function runClaudeCode(prompt, cwd) {
|
|
185
|
+
return new Promise((resolve) => {
|
|
186
|
+
const child = spawn('claude', ['-p', prompt, '--dangerously-skip-permissions'], {
|
|
187
|
+
cwd,
|
|
188
|
+
stdio: 'inherit'
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
child.on('close', resolve);
|
|
192
|
+
child.on('error', () => resolve());
|
|
193
|
+
});
|
|
194
|
+
}
|
package/src/config/constants.js
CHANGED
|
@@ -3,7 +3,11 @@
|
|
|
3
3
|
// Spec Hash: 0fe43335f5a325e3279a079ce616c052
|
|
4
4
|
// ═══════════════════════════════════════════════════════════════════════════════
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
import { createRequire } from 'module';
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
const pkg = require('../../package.json');
|
|
9
|
+
|
|
10
|
+
export const VERSION = pkg.version;
|
|
7
11
|
export const SPEC_HASH = '0fe43335f5a325e3279a079ce616c052';
|
|
8
12
|
|
|
9
13
|
// ─────────────────────────────────────────────────────────────────────────────
|
package/src/index.js
CHANGED
|
@@ -50,6 +50,14 @@ export { watchCommand } from './commands/watch.js';
|
|
|
50
50
|
// Phase I3 Commands - Shell Mode
|
|
51
51
|
export { shellCommand } from './commands/shell.js';
|
|
52
52
|
|
|
53
|
+
// Phase K Commands - Maximize Claude Code
|
|
54
|
+
export { testCommand } from './commands/test.js';
|
|
55
|
+
export { docsCommand } from './commands/docs.js';
|
|
56
|
+
export { refactorCommand } from './commands/refactor.js';
|
|
57
|
+
export { securityCommand } from './commands/security.js';
|
|
58
|
+
export { askCommand } from './commands/ask.js';
|
|
59
|
+
export { migrateCommand } from './commands/migrate.js';
|
|
60
|
+
|
|
53
61
|
// UI exports (Phase H2: Dashboard)
|
|
54
62
|
export {
|
|
55
63
|
ProgressDashboard,
|
package/docs-site/README.md
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
# Website
|
|
2
|
-
|
|
3
|
-
This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.
|
|
4
|
-
|
|
5
|
-
## Installation
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
yarn
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
## Local Development
|
|
12
|
-
|
|
13
|
-
```bash
|
|
14
|
-
yarn start
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.
|
|
18
|
-
|
|
19
|
-
## Build
|
|
20
|
-
|
|
21
|
-
```bash
|
|
22
|
-
yarn build
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
This command generates static content into the `build` directory and can be served using any static contents hosting service.
|
|
26
|
-
|
|
27
|
-
## Deployment
|
|
28
|
-
|
|
29
|
-
Using SSH:
|
|
30
|
-
|
|
31
|
-
```bash
|
|
32
|
-
USE_SSH=true yarn deploy
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Not using SSH:
|
|
36
|
-
|
|
37
|
-
```bash
|
|
38
|
-
GIT_USER=<Your GitHub username> yarn deploy
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
slug: first-blog-post
|
|
3
|
-
title: First Blog Post
|
|
4
|
-
authors: [slorber, yangshun]
|
|
5
|
-
tags: [hola, docusaurus]
|
|
6
|
-
---
|
|
7
|
-
|
|
8
|
-
Lorem ipsum dolor sit amet...
|
|
9
|
-
|
|
10
|
-
<!-- truncate -->
|
|
11
|
-
|
|
12
|
-
...consectetur adipiscing elit. Pellentesque elementum dignissim ultricies. Fusce rhoncus ipsum tempor eros aliquam consequat. Lorem ipsum dolor sit amet
|