@vortex-ai/cli 0.1.45 → 0.1.55
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/.turbo/turbo-build.log +5 -5
- package/README.md +7 -2
- package/dist/index.js +14193 -1484
- package/dist/index.mjs +14189 -1480
- package/package.json +1 -1
- package/src/commands/cache.ts +2 -2
- package/src/commands/config.ts +4 -2
- package/src/commands/init.ts +33 -18
- package/src/commands/review.ts +36 -51
- package/src/commands/search.ts +19 -7
- package/src/commands/solve-issue.ts +2 -1
- package/src/commands/solve.ts +152 -49
- package/src/index.ts +27 -51
- package/.vortex-bm25.json +0 -1
- package/.vortex.db +0 -0
- package/src/commands/analyze.ts +0 -74
- package/src/commands/fix-nitbits.ts +0 -63
- package/src/commands/suggest.ts +0 -54
package/package.json
CHANGED
package/src/commands/cache.ts
CHANGED
|
@@ -12,9 +12,9 @@ cacheCommand
|
|
|
12
12
|
const stats = await LLMCacheManager.getStats();
|
|
13
13
|
console.log("\nLLM Cache Statistics\n");
|
|
14
14
|
console.log(`Entries: ${stats.entries.toLocaleString()}`);
|
|
15
|
-
console.log(`
|
|
15
|
+
console.log(`Saved API Calls: ${stats.hits.toLocaleString()}`);
|
|
16
16
|
const storageMB = (stats.storage / 1024 / 1024).toFixed(2);
|
|
17
|
-
console.log(`Storage: ${storageMB} MB\n`);
|
|
17
|
+
console.log(`Storage Used: ${storageMB} MB\n`);
|
|
18
18
|
} catch (err) {
|
|
19
19
|
console.error("Failed to retrieve cache stats:", err);
|
|
20
20
|
}
|
package/src/commands/config.ts
CHANGED
|
@@ -28,9 +28,11 @@ export async function configSet(provider: string, value: string) {
|
|
|
28
28
|
envKey = "GEMINI_API_KEY";
|
|
29
29
|
} else if (providerUpper === "GROQ") {
|
|
30
30
|
envKey = "GROQ_API_KEY";
|
|
31
|
+
} else if (providerUpper === "OPENROUTER") {
|
|
32
|
+
envKey = "OPENROUTER_API_KEY";
|
|
31
33
|
} else {
|
|
32
|
-
console.log(chalk.red(`Invalid option: ${chalk.bold(provider)}. Supported options are 'gemini' and '
|
|
33
|
-
console.log(chalk.gray(`Usage: vortex config set
|
|
34
|
+
console.log(chalk.red(`Invalid option: ${chalk.bold(provider)}. Supported options are 'gemini', 'groq', and 'openrouter'.`));
|
|
35
|
+
console.log(chalk.gray(`Usage: vortex config set openrouter <your-key>`));
|
|
34
36
|
process.exit(1);
|
|
35
37
|
}
|
|
36
38
|
|
package/src/commands/init.ts
CHANGED
|
@@ -1,35 +1,50 @@
|
|
|
1
1
|
import { Indexer } from "@vortex/engine";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
import * as path from "path";
|
|
2
4
|
|
|
3
5
|
export async function initCommand(options: any) {
|
|
4
6
|
const { default: ora } = await import("ora");
|
|
5
7
|
const { default: chalk } = await import("chalk");
|
|
6
8
|
|
|
7
|
-
console.log(chalk.
|
|
8
|
-
|
|
9
|
-
if (options.reindex) {
|
|
10
|
-
console.log(chalk.yellow(" Reindex mode: Rebuilding embeddings and BM25 index"));
|
|
11
|
-
console.log(chalk.yellow(" Historical review memory will be preserved\n"));
|
|
12
|
-
}
|
|
9
|
+
console.log(`\n ${chalk.cyan('◆')} Vortex\n`);
|
|
13
10
|
|
|
11
|
+
const startTime = Date.now();
|
|
14
12
|
const spinner = ora("Scanning repository and building indices...").start();
|
|
15
13
|
|
|
16
14
|
const indexer = new Indexer();
|
|
17
15
|
try {
|
|
18
16
|
const stats = await indexer.indexRepository(process.cwd());
|
|
19
17
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
18
|
+
const durationStr = ((Date.now() - startTime) / 1000).toFixed(1) + "s";
|
|
19
|
+
spinner.succeed(chalk.green(`${stats.filesProcessed} files indexed · ${durationStr}\n`));
|
|
20
|
+
|
|
21
|
+
// Automatically update .gitignore
|
|
22
|
+
const gitignorePath = path.join(process.cwd(), ".gitignore");
|
|
23
|
+
if (fs.existsSync(gitignorePath)) {
|
|
24
|
+
let content = fs.readFileSync(gitignorePath, "utf8");
|
|
25
|
+
const entriesToAdd = [
|
|
26
|
+
".vortex.db",
|
|
27
|
+
".vortex-bm25.json",
|
|
28
|
+
".vortex_backup/"
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
let changed = false;
|
|
32
|
+
for (const entry of entriesToAdd) {
|
|
33
|
+
if (!content.includes(entry)) {
|
|
34
|
+
if (!content.endsWith("\n") && content.length > 0) {
|
|
35
|
+
content += "\n";
|
|
36
|
+
}
|
|
37
|
+
content += entry + "\n";
|
|
38
|
+
changed = true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (changed) {
|
|
43
|
+
fs.writeFileSync(gitignorePath, content, "utf8");
|
|
44
|
+
console.log(chalk.gray(` Updated .gitignore`));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
28
47
|
|
|
29
|
-
console.log(chalk.green(" Ready for:"));
|
|
30
|
-
console.log(chalk.gray(" • vortex search -q \"your query\" (hybrid search)"));
|
|
31
|
-
console.log(chalk.gray(" • vortex review --pr <number> (multi-agent review)"));
|
|
32
|
-
console.log(chalk.gray(" • vortex issue --id <number> (issue analysis)\n"));
|
|
33
48
|
} catch (err) {
|
|
34
49
|
spinner.fail(chalk.red("Failed to initialize"));
|
|
35
50
|
console.error(err);
|
package/src/commands/review.ts
CHANGED
|
@@ -19,11 +19,8 @@ export async function reviewCommand(options: any) {
|
|
|
19
19
|
renderer: new TerminalRenderer() as any,
|
|
20
20
|
});
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
} else {
|
|
25
|
-
console.log(chalk.blue.bold(`\nMulti-Agent Review for Local Changes\n`));
|
|
26
|
-
}
|
|
22
|
+
const reviewTitle = options.pr ? `PR #${options.pr}` : 'local';
|
|
23
|
+
console.log(`\n ${chalk.cyan('◆')} Vortex · review · ${reviewTitle}\n`);
|
|
27
24
|
|
|
28
25
|
if (!process.env.GITHUB_TOKEN) {
|
|
29
26
|
console.log(
|
|
@@ -68,28 +65,32 @@ export async function reviewCommand(options: any) {
|
|
|
68
65
|
}
|
|
69
66
|
}
|
|
70
67
|
|
|
68
|
+
const diffLines = diff.split('\n').filter(l => l.startsWith('+++') || l.startsWith('---')).length / 2 || 1;
|
|
69
|
+
spinner.succeed(`Fetched diff · ~${Math.floor(diffLines)} files changed`);
|
|
71
70
|
|
|
72
|
-
spinner.text = "Extracting architectural queries from diff...";
|
|
73
71
|
const agent = new IntelligenceAgent();
|
|
74
72
|
const indexer = new Indexer();
|
|
75
73
|
|
|
74
|
+
spinner.start("Scanning repository and building indices for fresh context...");
|
|
75
|
+
await indexer.indexRepository(process.cwd());
|
|
76
|
+
|
|
77
|
+
spinner.start("Extracting architectural queries from diff...");
|
|
78
|
+
|
|
76
79
|
const queries = await agent.extractSearchQueriesFromDiff(diff);
|
|
77
80
|
|
|
78
81
|
const allChunks: any[] = [];
|
|
79
82
|
if (queries.length > 0) {
|
|
80
|
-
spinner.text = `Hybrid searching for
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
spinner.text = `Hybrid searching for context (parallel)...`;
|
|
84
|
+
const allResults = await Promise.all(
|
|
85
|
+
queries.map(query => indexer.hybridSearch(query, 3))
|
|
86
|
+
);
|
|
87
|
+
allChunks.push(...allResults.flat());
|
|
85
88
|
}
|
|
86
89
|
|
|
87
|
-
|
|
88
90
|
const uniqueChunks = Array.from(
|
|
89
91
|
new Map(allChunks.map((c) => [c.id, c])).values()
|
|
90
92
|
);
|
|
91
93
|
|
|
92
|
-
|
|
93
94
|
spinner.text = "Checking memory for relevant past reviews...";
|
|
94
95
|
const memoryService = new MemoryService();
|
|
95
96
|
const memories = await memoryService.recallRelevantMemories(
|
|
@@ -97,19 +98,17 @@ export async function reviewCommand(options: any) {
|
|
|
97
98
|
3
|
|
98
99
|
);
|
|
99
100
|
|
|
100
|
-
|
|
101
|
-
spinner.text = `Running multi-agent review (Security + Architecture + Synthesis)...`;
|
|
101
|
+
spinner.text = `Running multi-agent review (Security, Architecture, Synthesis)...`;
|
|
102
102
|
const review = await agent.generateMultiAgentReview(
|
|
103
103
|
diff,
|
|
104
104
|
uniqueChunks,
|
|
105
105
|
memories
|
|
106
106
|
);
|
|
107
107
|
|
|
108
|
-
spinner.
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
);
|
|
108
|
+
spinner.stop();
|
|
109
|
+
console.log(` ${chalk.green('✔')} Security Agent ${review.agentOutputs.security.findings.length} findings`);
|
|
110
|
+
console.log(` ${chalk.green('✔')} Architecture Agent ${review.agentOutputs.architecture.findings.length} suggestions`);
|
|
111
|
+
console.log(` ${chalk.green('✔')} Synthesizer Report ready\n`);
|
|
113
112
|
|
|
114
113
|
|
|
115
114
|
const parsedReview = await marked.parse(review.markdownReport);
|
|
@@ -150,37 +149,23 @@ export async function reviewCommand(options: any) {
|
|
|
150
149
|
);
|
|
151
150
|
|
|
152
151
|
|
|
153
|
-
const { security, architecture } = review.agentOutputs;
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
);
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
console.log(
|
|
171
|
-
chalk.dim(
|
|
172
|
-
`\n Architecture: ${architecture.consistencyScore} (${architecture.findings.length} findings)`
|
|
173
|
-
)
|
|
174
|
-
);
|
|
175
|
-
architecture.findings.forEach((f: any, i: number) => {
|
|
176
|
-
const severityColor =
|
|
177
|
-
f.severity === "breaking"
|
|
178
|
-
? chalk.red
|
|
179
|
-
: f.severity === "major"
|
|
180
|
-
? chalk.yellow
|
|
181
|
-
: chalk.gray;
|
|
182
|
-
console.log(severityColor(` [${f.severity.toUpperCase()}] ${f.title}`));
|
|
183
|
-
});
|
|
152
|
+
const { security, architecture, synthesis } = review.agentOutputs;
|
|
153
|
+
|
|
154
|
+
const secCount = security.findings.length;
|
|
155
|
+
const archCount = architecture.findings.length;
|
|
156
|
+
|
|
157
|
+
// Draw the summary bar chart
|
|
158
|
+
console.log(` ────────────────────────────────────────`);
|
|
159
|
+
|
|
160
|
+
const secBar = secCount > 0 ? chalk.red('█'.repeat(Math.min(secCount, 10))) : chalk.green('░');
|
|
161
|
+
console.log(` SECURITY ${secBar.padEnd(20)} ${secCount} issues`);
|
|
162
|
+
|
|
163
|
+
const archBar = archCount > 0 ? chalk.yellow('█'.repeat(Math.min(archCount, 10))) : chalk.green('░');
|
|
164
|
+
console.log(` ARCH ${archBar.padEnd(20)} ${archCount} suggestions`);
|
|
165
|
+
|
|
166
|
+
const logicBar = synthesis.verdict === 'SAFE_TO_MERGE' ? chalk.green('░') : chalk.red('█');
|
|
167
|
+
console.log(` LOGIC ${logicBar.padEnd(20)} ${synthesis.verdict.toLowerCase().replace(/_/g, ' ')}`);
|
|
168
|
+
console.log(` ────────────────────────────────────────\n`);
|
|
184
169
|
|
|
185
170
|
if (uniqueChunks.length > 0) {
|
|
186
171
|
console.log(chalk.cyan.dim("\n Cross-Referenced Architecture:"));
|
package/src/commands/search.ts
CHANGED
|
@@ -15,21 +15,33 @@ export async function searchCommand(options: any) {
|
|
|
15
15
|
const indexer = new Indexer();
|
|
16
16
|
|
|
17
17
|
try {
|
|
18
|
-
spinner.text = "Running hybrid search (vector + BM25 + cross-encoder)...";
|
|
19
|
-
const results = await indexer.hybridSearch(options.query, parseInt(options.limit, 10));
|
|
18
|
+
spinner.text = options.expandQuery ? "Expanding query and running parallel hybrid search..." : "Running hybrid search (vector + BM25 + cross-encoder)...";
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
let queriesToSearch = [options.query];
|
|
21
|
+
const agent = new IntelligenceAgent();
|
|
22
|
+
|
|
23
|
+
if (options.expandQuery) {
|
|
24
|
+
queriesToSearch = await agent.expandQuery(options.query);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const allResults = await Promise.all(
|
|
28
|
+
queriesToSearch.map(q => indexer.hybridSearch(q, parseInt(options.limit, 10)))
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
const flatResults = allResults.flat();
|
|
32
|
+
const uniqueResults = Array.from(new Map(flatResults.map((c) => [c.id, c])).values()).sort((a, b) => b.score - a.score).slice(0, parseInt(options.limit, 10));
|
|
33
|
+
|
|
34
|
+
if (uniqueResults.length === 0) {
|
|
22
35
|
spinner.fail("No relevant code found.");
|
|
23
36
|
return;
|
|
24
37
|
}
|
|
25
38
|
|
|
26
|
-
spinner.text = `Found ${
|
|
39
|
+
spinner.text = `Found ${uniqueResults.length} relevant code chunks. Analyzing with AI engine...`;
|
|
27
40
|
|
|
28
41
|
const memoryService = new MemoryService();
|
|
29
42
|
const memories = await memoryService.recallRelevantMemories(options.query, 3);
|
|
30
43
|
|
|
31
|
-
const
|
|
32
|
-
const answer = await agent.answerQueryWithContext(options.query, results);
|
|
44
|
+
const answer = await agent.answerQueryWithContext(options.query, uniqueResults);
|
|
33
45
|
|
|
34
46
|
spinner.succeed("Analysis complete!\n");
|
|
35
47
|
|
|
@@ -47,7 +59,7 @@ export async function searchCommand(options: any) {
|
|
|
47
59
|
console.log(formatted);
|
|
48
60
|
|
|
49
61
|
console.log(chalk.cyan.dim(" Reference Material (Hybrid Retrieval)"));
|
|
50
|
-
|
|
62
|
+
uniqueResults.forEach((res: any, i: number) => {
|
|
51
63
|
const sources = res.sources ? res.sources.join("+") : "vector";
|
|
52
64
|
const scoreStr = res.score ? (res.score * 100).toFixed(1) + '%' : 'N/A';
|
|
53
65
|
console.log(chalk.gray(` │ [${i + 1}] ${res.file.replace(process.cwd(), '')} ➔ ${res.symbolPath || '(anonymous)'} (${scoreStr}) [${sources}]`));
|
|
@@ -59,7 +59,8 @@ Please fix this issue in the codebase.`;
|
|
|
59
59
|
await solveCommand(prompt, {
|
|
60
60
|
autoApprove: options.autoApprove,
|
|
61
61
|
maxSteps: options.maxSteps,
|
|
62
|
-
contextChunks: contextChunks
|
|
62
|
+
contextChunks: contextChunks,
|
|
63
|
+
verify: options.verify
|
|
63
64
|
});
|
|
64
65
|
|
|
65
66
|
} catch (err) {
|
package/src/commands/solve.ts
CHANGED
|
@@ -8,17 +8,21 @@ import {
|
|
|
8
8
|
AutonomousAgent,
|
|
9
9
|
FileReadTool,
|
|
10
10
|
FileWriteTool,
|
|
11
|
+
FileEditTool,
|
|
11
12
|
ShellExecuteTool,
|
|
12
13
|
GrepTool,
|
|
13
14
|
RagSearchTool,
|
|
15
|
+
WebSearchTool,
|
|
14
16
|
ApprovalCallback,
|
|
15
|
-
AgentContextChunk
|
|
17
|
+
AgentContextChunk,
|
|
18
|
+
IntelligenceAgent,
|
|
19
|
+
Indexer
|
|
16
20
|
} from "@vortex/engine";
|
|
17
21
|
import { initDatabase } from "@vortex/db";
|
|
18
22
|
import { getGitRoot, isGitRepo } from "@vortex/git";
|
|
19
23
|
import { VectorStore, LocalEmbedder, BM25Index, HybridRetriever } from "@vortex/retrieval";
|
|
20
24
|
|
|
21
|
-
export async function solveCommand(prompt: string, options: { autoApprove?: boolean; maxSteps?: number; contextChunks?: AgentContextChunk[]; newProject?: string } = {}) {
|
|
25
|
+
export async function solveCommand(prompt: string, options: { autoApprove?: boolean; maxSteps?: number; contextChunks?: AgentContextChunk[]; newProject?: string; verify?: string | boolean } = {}) {
|
|
22
26
|
let cwd = process.cwd();
|
|
23
27
|
|
|
24
28
|
if (options.newProject) {
|
|
@@ -29,7 +33,7 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
29
33
|
}
|
|
30
34
|
cwd = projectPath;
|
|
31
35
|
process.chdir(cwd);
|
|
32
|
-
|
|
36
|
+
|
|
33
37
|
if (!fs.existsSync(path.join(cwd, ".git"))) {
|
|
34
38
|
try {
|
|
35
39
|
execSync("git init", { cwd, stdio: "ignore" });
|
|
@@ -46,12 +50,11 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
46
50
|
rootPath = getGitRoot(cwd);
|
|
47
51
|
}
|
|
48
52
|
|
|
49
|
-
console.log(`\
|
|
50
|
-
console.log(`Task: "${prompt}"\n`);
|
|
53
|
+
console.log(`\n ${chalk.cyan('◆')} Vortex · solve\n`);
|
|
51
54
|
|
|
52
55
|
await initDatabase();
|
|
53
56
|
|
|
54
|
-
const spinner = ora("
|
|
57
|
+
const spinner = ora("Reading codebase...").start();
|
|
55
58
|
|
|
56
59
|
try {
|
|
57
60
|
const vectorStore = new VectorStore();
|
|
@@ -59,19 +62,92 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
59
62
|
const embedder = new LocalEmbedder();
|
|
60
63
|
const hybridRetriever = new HybridRetriever(vectorStore, bm25Index, embedder);
|
|
61
64
|
const agent = new AutonomousAgent();
|
|
62
|
-
|
|
65
|
+
|
|
63
66
|
if (options.maxSteps !== undefined) {
|
|
64
67
|
(agent as any).maxToolIterations = options.maxSteps;
|
|
65
68
|
}
|
|
66
69
|
|
|
67
|
-
|
|
70
|
+
if (!options.contextChunks || options.contextChunks.length === 0) {
|
|
71
|
+
try {
|
|
72
|
+
const indexer = new Indexer();
|
|
73
|
+
const relevantContext = await indexer.hybridSearch(prompt, 15);
|
|
74
|
+
options.contextChunks = relevantContext.map((c: any) => ({
|
|
75
|
+
file: c.file,
|
|
76
|
+
symbolPath: c.symbolPath || "anonymous",
|
|
77
|
+
content: c.content,
|
|
78
|
+
kind: c.kind || "unknown",
|
|
79
|
+
}));
|
|
80
|
+
} catch (e) {
|
|
81
|
+
console.warn(chalk.yellow("⚠️ Could not search vector store. Did you run 'vortex init'?"));
|
|
82
|
+
options.contextChunks = [];
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
spinner.text = "Planning approach...";
|
|
87
|
+
const intelligenceAgent = new IntelligenceAgent();
|
|
88
|
+
let executionPlanStr = await intelligenceAgent.generateExecutionPlan(prompt, options.contextChunks || []);
|
|
89
|
+
|
|
90
|
+
let planSummary = "task";
|
|
91
|
+
let stepCount = 0;
|
|
92
|
+
let executionPlan = "";
|
|
93
|
+
let filesToRead: string[] = [];
|
|
94
|
+
try {
|
|
95
|
+
const parsedPlan = JSON.parse(executionPlanStr);
|
|
96
|
+
planSummary = parsedPlan.summary || planSummary;
|
|
97
|
+
stepCount = parsedPlan.steps ? parsedPlan.steps.length : 0;
|
|
98
|
+
executionPlan = parsedPlan.steps ? parsedPlan.steps.map((s: any, i: number) => {
|
|
99
|
+
if (typeof s === 'string') return `${i + 1}. ${s}`;
|
|
100
|
+
return `${i + 1}. [${(s.action || 'MODIFY').toUpperCase()}] ${s.file || 'General'}\n ${s.description}`;
|
|
101
|
+
}).join("\n\n") : executionPlanStr;
|
|
102
|
+
filesToRead = parsedPlan.filesToRead || [];
|
|
103
|
+
} catch {
|
|
104
|
+
executionPlan = executionPlanStr;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (filesToRead.length > 0) {
|
|
108
|
+
for (const file of filesToRead) {
|
|
109
|
+
try {
|
|
110
|
+
const fullPath = path.resolve(rootPath, file);
|
|
111
|
+
if (fs.existsSync(fullPath)) {
|
|
112
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
113
|
+
options.contextChunks!.push({ file, symbolPath: "file", content, kind: "file" });
|
|
114
|
+
}
|
|
115
|
+
} catch { }
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
spinner.stopAndPersist({ symbol: chalk.green('✔'), text: `Plan ready · ${planSummary} (${stepCount} steps)\n` });
|
|
120
|
+
|
|
121
|
+
console.log(`\n${chalk.cyan.dim("─── Execution Plan ───")}`);
|
|
122
|
+
console.log(chalk.gray(executionPlan));
|
|
123
|
+
console.log(`${chalk.cyan.dim("──────────────────────")}\n`);
|
|
124
|
+
|
|
125
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
126
|
+
const answer = await rl.question(` Proceed? (Y/n/edit) › `);
|
|
127
|
+
|
|
128
|
+
if (answer.toLowerCase() === 'n') {
|
|
129
|
+
console.log(chalk.red('\n ✖ Task aborted by user.\n'));
|
|
130
|
+
rl.close();
|
|
131
|
+
process.exit(0);
|
|
132
|
+
} else if (answer.toLowerCase() === 'edit') {
|
|
133
|
+
const edits = await rl.question(` Enter instructions › `);
|
|
134
|
+
if (edits.trim()) {
|
|
135
|
+
executionPlan += `\n\n### User Modifications\n${edits.trim()}`;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
rl.close();
|
|
139
|
+
|
|
140
|
+
const enrichedPrompt = `Original Task:\n${prompt}\n\nExecution Plan generated by Architect:\n${executionPlan}`;
|
|
141
|
+
|
|
142
|
+
console.log(`\n ${chalk.cyan('◆')} Vortex · solving\n`);
|
|
143
|
+
spinner.start("Initializing agent...");
|
|
68
144
|
|
|
69
145
|
const approvalCallback: ApprovalCallback = async (action: string, description: string): Promise<boolean> => {
|
|
70
146
|
if (options.autoApprove) return true;
|
|
71
|
-
|
|
147
|
+
|
|
72
148
|
const currentText = spinner.text;
|
|
73
149
|
spinner.stop();
|
|
74
|
-
|
|
150
|
+
|
|
75
151
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
76
152
|
let question = "";
|
|
77
153
|
if (action === "shell_execute") {
|
|
@@ -81,10 +157,10 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
81
157
|
} else {
|
|
82
158
|
question = `\n${chalk.yellow('⚠️ Agent wants to perform action:')} ${action} on ${description}\nAllow? (y/N) `;
|
|
83
159
|
}
|
|
84
|
-
|
|
160
|
+
|
|
85
161
|
const answer = await rl.question(question);
|
|
86
162
|
rl.close();
|
|
87
|
-
|
|
163
|
+
|
|
88
164
|
spinner.start(currentText);
|
|
89
165
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
90
166
|
};
|
|
@@ -93,26 +169,38 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
93
169
|
agent.registerTools([
|
|
94
170
|
new FileReadTool(rootPath),
|
|
95
171
|
new FileWriteTool(rootPath, vectorStore, embedder, approvalCallback),
|
|
172
|
+
new FileEditTool(rootPath, vectorStore, embedder, approvalCallback),
|
|
96
173
|
new ShellExecuteTool(rootPath, approvalCallback),
|
|
97
174
|
new GrepTool(rootPath),
|
|
98
175
|
new RagSearchTool(hybridRetriever),
|
|
176
|
+
new WebSearchTool(),
|
|
99
177
|
]);
|
|
100
178
|
|
|
179
|
+
const initialState = {
|
|
180
|
+
evidence: { filesRead: filesToRead, symbolsObserved: [], dependenciesObserved: [], externalSchemasFound: {}, confidence: 'LOW' as any },
|
|
181
|
+
plan: { steps: [], currentStepIndex: 0, completedSteps: [] },
|
|
182
|
+
execution: { filesModified: [], commandsRun: [], lastError: null, consecutiveFailures: 0 },
|
|
183
|
+
verification: { contractItems: [], passed: [], failed: [] },
|
|
184
|
+
verdict: 'IN_PROGRESS' as any
|
|
185
|
+
};
|
|
186
|
+
|
|
101
187
|
const result = await agent.run(
|
|
102
188
|
{
|
|
103
|
-
diff:
|
|
189
|
+
diff: enrichedPrompt,
|
|
104
190
|
contextChunks: options.contextChunks || [],
|
|
105
191
|
},
|
|
106
192
|
{
|
|
193
|
+
initialState,
|
|
194
|
+
verifyCommand: options.verify,
|
|
107
195
|
onToolCall: (toolName, args) => {
|
|
108
196
|
if (toolName === "write_file") {
|
|
109
|
-
spinner.text = `Writing
|
|
197
|
+
spinner.text = `Writing ${args.path}...`;
|
|
198
|
+
} else if (toolName === "read_file") {
|
|
199
|
+
spinner.text = `Inspecting ${args.path}...`;
|
|
110
200
|
} else if (toolName === "shell_execute") {
|
|
111
|
-
spinner.text = `
|
|
112
|
-
} else if (toolName === "rag_search") {
|
|
113
|
-
spinner.text = `Searching project memory for context...`;
|
|
201
|
+
spinner.text = `Running verification...`;
|
|
114
202
|
} else {
|
|
115
|
-
spinner.text = `
|
|
203
|
+
spinner.text = `Using ${toolName}...`;
|
|
116
204
|
}
|
|
117
205
|
},
|
|
118
206
|
onToolResult: (toolName, toolResult) => {
|
|
@@ -121,15 +209,15 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
121
209
|
try {
|
|
122
210
|
const match = toolResult.match(/Wrote to (.*) successfully/);
|
|
123
211
|
if (match && match[1]) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
212
|
+
const filePath = match[1];
|
|
213
|
+
const diffOut = execSync(`git diff --numstat ${filePath}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
214
|
+
const diffParts = diffOut.split(/\s+/);
|
|
215
|
+
const additions = diffParts[0] || '0';
|
|
216
|
+
const deletions = diffParts[1] || '0';
|
|
217
|
+
console.log(` ${chalk.cyan('●')} Writing ${filePath}`);
|
|
218
|
+
console.log(` ${chalk.green(`+ ${additions} lines`)} · ${chalk.red(`- ${deletions} lines`)}\n`);
|
|
130
219
|
}
|
|
131
220
|
} catch (e) {
|
|
132
|
-
|
|
133
221
|
}
|
|
134
222
|
spinner.start("Thinking...");
|
|
135
223
|
}
|
|
@@ -137,38 +225,53 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
137
225
|
}
|
|
138
226
|
);
|
|
139
227
|
|
|
140
|
-
|
|
228
|
+
let iterationCount = 1;
|
|
229
|
+
if (result.summary) {
|
|
230
|
+
const iterMatch = result.summary.match(/Iter (\d+)/);
|
|
231
|
+
if (iterMatch) iterationCount = parseInt(iterMatch[1], 10);
|
|
232
|
+
}
|
|
141
233
|
|
|
234
|
+
let filesChangedCount = 0;
|
|
142
235
|
try {
|
|
143
|
-
const statOut = execSync(`git diff --
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
236
|
+
const statOut = execSync(`git diff --shortstat`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
237
|
+
const filesMatch = statOut.match(/(\d+) file/);
|
|
238
|
+
if (filesMatch) {
|
|
239
|
+
filesChangedCount = parseInt(filesMatch[1], 10);
|
|
147
240
|
}
|
|
148
241
|
} catch (e) {}
|
|
149
242
|
|
|
150
|
-
|
|
151
|
-
|
|
243
|
+
spinner.stop();
|
|
244
|
+
const isIncomplete = result.verdict === 'INCOMPLETE' || result.state?.verdict === 'INCOMPLETE';
|
|
245
|
+
const isComplete = !isIncomplete;
|
|
152
246
|
|
|
247
|
+
if (isIncomplete) {
|
|
248
|
+
console.log(`\n ${chalk.red('✖')} Task incomplete · stopped or failed`);
|
|
249
|
+
} else {
|
|
250
|
+
console.log(`\n ────────────────────────────────────────`);
|
|
251
|
+
console.log(` ${chalk.green('✔')} Task complete · ${filesChangedCount} files modified · ${iterationCount} iterations`);
|
|
252
|
+
console.log(` ────────────────────────────────────────\n`);
|
|
253
|
+
}
|
|
153
254
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
255
|
+
if (isComplete) {
|
|
256
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
257
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
258
|
+
try {
|
|
259
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
260
|
+
if (pkg.scripts && pkg.scripts.build) {
|
|
261
|
+
console.log(`\n${chalk.cyan('=== Verification ===')}`);
|
|
262
|
+
const buildSpinner = ora("Running build command...").start();
|
|
263
|
+
try {
|
|
264
|
+
const env = { ...process.env };
|
|
265
|
+
delete env.NODE_ENV;
|
|
266
|
+
execSync("npm run build", { stdio: "pipe", cwd, env });
|
|
267
|
+
buildSpinner.succeed("Build completed successfully.");
|
|
268
|
+
} catch (e: any) {
|
|
269
|
+
buildSpinner.fail("Build failed.");
|
|
270
|
+
console.error(chalk.red(e.stdout ? e.stdout.toString() : e.message));
|
|
271
|
+
}
|
|
169
272
|
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
273
|
+
} catch (e) { }
|
|
274
|
+
}
|
|
172
275
|
}
|
|
173
276
|
} catch (err: any) {
|
|
174
277
|
spinner.fail("Agent encountered an error");
|