@vortex-ai/cli 0.1.45 → 0.1.50
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 +7 -7
- package/README.md +7 -2
- package/dist/index.js +13640 -1141
- package/dist/index.mjs +13635 -1136
- package/package.json +1 -1
- package/src/commands/config.ts +4 -2
- package/src/commands/init.ts +33 -18
- package/src/commands/review.ts +32 -47
- package/src/commands/search.ts +1 -1
- package/src/commands/solve.ts +147 -48
- package/src/index.ts +24 -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/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
|
|
83
|
+
spinner.text = `Hybrid searching for context...`;
|
|
81
84
|
for (const query of queries) {
|
|
82
85
|
const results = await indexer.hybridSearch(query, 3);
|
|
83
86
|
allChunks.push(...results);
|
|
84
87
|
}
|
|
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
|
@@ -23,7 +23,7 @@ export async function searchCommand(options: any) {
|
|
|
23
23
|
return;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
spinner.text = `Found ${results.length} relevant code chunks. Analyzing with
|
|
26
|
+
spinner.text = `Found ${results.length} relevant code chunks. Analyzing with AI engine...`;
|
|
27
27
|
|
|
28
28
|
const memoryService = new MemoryService();
|
|
29
29
|
const memories = await memoryService.recallRelevantMemories(options.query, 3);
|
package/src/commands/solve.ts
CHANGED
|
@@ -8,11 +8,15 @@ 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";
|
|
@@ -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,89 @@ 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, 5);
|
|
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: string, i: number) => `${i + 1}. ${s}`).join("\n") : executionPlanStr;
|
|
99
|
+
filesToRead = parsedPlan.filesToRead || [];
|
|
100
|
+
} catch {
|
|
101
|
+
executionPlan = executionPlanStr;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (filesToRead.length > 0) {
|
|
105
|
+
for (const file of filesToRead) {
|
|
106
|
+
try {
|
|
107
|
+
const fullPath = path.resolve(rootPath, file);
|
|
108
|
+
if (fs.existsSync(fullPath)) {
|
|
109
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
110
|
+
options.contextChunks!.push({ file, symbolPath: "file", content, kind: "file" });
|
|
111
|
+
}
|
|
112
|
+
} catch { }
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
spinner.stopAndPersist({ symbol: chalk.green('✔'), text: `Plan ready · ${planSummary} (${stepCount} steps)\n` });
|
|
117
|
+
|
|
118
|
+
console.log(`\n${chalk.cyan.dim("─── Execution Plan ───")}`);
|
|
119
|
+
console.log(chalk.gray(executionPlan));
|
|
120
|
+
console.log(`${chalk.cyan.dim("──────────────────────")}\n`);
|
|
121
|
+
|
|
122
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
123
|
+
const answer = await rl.question(` Proceed? (Y/n/edit) › `);
|
|
124
|
+
|
|
125
|
+
if (answer.toLowerCase() === 'n') {
|
|
126
|
+
console.log(chalk.red('\n ✖ Task aborted by user.\n'));
|
|
127
|
+
rl.close();
|
|
128
|
+
process.exit(0);
|
|
129
|
+
} else if (answer.toLowerCase() === 'edit') {
|
|
130
|
+
const edits = await rl.question(` Enter instructions › `);
|
|
131
|
+
if (edits.trim()) {
|
|
132
|
+
executionPlan += `\n\n### User Modifications\n${edits.trim()}`;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
rl.close();
|
|
136
|
+
|
|
137
|
+
const enrichedPrompt = `Original Task:\n${prompt}\n\nExecution Plan generated by Architect:\n${executionPlan}`;
|
|
138
|
+
|
|
139
|
+
console.log(`\n ${chalk.cyan('◆')} Vortex · solving\n`);
|
|
140
|
+
spinner.start("Initializing agent...");
|
|
68
141
|
|
|
69
142
|
const approvalCallback: ApprovalCallback = async (action: string, description: string): Promise<boolean> => {
|
|
70
143
|
if (options.autoApprove) return true;
|
|
71
|
-
|
|
144
|
+
|
|
72
145
|
const currentText = spinner.text;
|
|
73
146
|
spinner.stop();
|
|
74
|
-
|
|
147
|
+
|
|
75
148
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
76
149
|
let question = "";
|
|
77
150
|
if (action === "shell_execute") {
|
|
@@ -81,10 +154,10 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
81
154
|
} else {
|
|
82
155
|
question = `\n${chalk.yellow('⚠️ Agent wants to perform action:')} ${action} on ${description}\nAllow? (y/N) `;
|
|
83
156
|
}
|
|
84
|
-
|
|
157
|
+
|
|
85
158
|
const answer = await rl.question(question);
|
|
86
159
|
rl.close();
|
|
87
|
-
|
|
160
|
+
|
|
88
161
|
spinner.start(currentText);
|
|
89
162
|
return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
90
163
|
};
|
|
@@ -93,26 +166,37 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
93
166
|
agent.registerTools([
|
|
94
167
|
new FileReadTool(rootPath),
|
|
95
168
|
new FileWriteTool(rootPath, vectorStore, embedder, approvalCallback),
|
|
169
|
+
new FileEditTool(rootPath, vectorStore, embedder, approvalCallback),
|
|
96
170
|
new ShellExecuteTool(rootPath, approvalCallback),
|
|
97
171
|
new GrepTool(rootPath),
|
|
98
172
|
new RagSearchTool(hybridRetriever),
|
|
173
|
+
new WebSearchTool(),
|
|
99
174
|
]);
|
|
100
175
|
|
|
176
|
+
const initialState = {
|
|
177
|
+
evidence: { filesRead: filesToRead, symbolsObserved: [], dependenciesObserved: [], externalSchemasFound: {}, confidence: 'LOW' as any },
|
|
178
|
+
plan: { steps: [], currentStepIndex: 0, completedSteps: [] },
|
|
179
|
+
execution: { filesModified: [], commandsRun: [], lastError: null, consecutiveFailures: 0 },
|
|
180
|
+
verification: { contractItems: [], passed: [], failed: [] },
|
|
181
|
+
verdict: 'IN_PROGRESS' as any
|
|
182
|
+
};
|
|
183
|
+
|
|
101
184
|
const result = await agent.run(
|
|
102
185
|
{
|
|
103
|
-
diff:
|
|
186
|
+
diff: enrichedPrompt,
|
|
104
187
|
contextChunks: options.contextChunks || [],
|
|
105
188
|
},
|
|
106
189
|
{
|
|
190
|
+
initialState,
|
|
107
191
|
onToolCall: (toolName, args) => {
|
|
108
192
|
if (toolName === "write_file") {
|
|
109
|
-
spinner.text = `Writing
|
|
193
|
+
spinner.text = `Writing ${args.path}...`;
|
|
194
|
+
} else if (toolName === "read_file") {
|
|
195
|
+
spinner.text = `Inspecting ${args.path}...`;
|
|
110
196
|
} else if (toolName === "shell_execute") {
|
|
111
|
-
spinner.text = `
|
|
112
|
-
} else if (toolName === "rag_search") {
|
|
113
|
-
spinner.text = `Searching project memory for context...`;
|
|
197
|
+
spinner.text = `Running verification...`;
|
|
114
198
|
} else {
|
|
115
|
-
spinner.text = `
|
|
199
|
+
spinner.text = `Using ${toolName}...`;
|
|
116
200
|
}
|
|
117
201
|
},
|
|
118
202
|
onToolResult: (toolName, toolResult) => {
|
|
@@ -121,15 +205,15 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
121
205
|
try {
|
|
122
206
|
const match = toolResult.match(/Wrote to (.*) successfully/);
|
|
123
207
|
if (match && match[1]) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
208
|
+
const filePath = match[1];
|
|
209
|
+
const diffOut = execSync(`git diff --numstat ${filePath}`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
210
|
+
const diffParts = diffOut.split(/\s+/);
|
|
211
|
+
const additions = diffParts[0] || '0';
|
|
212
|
+
const deletions = diffParts[1] || '0';
|
|
213
|
+
console.log(` ${chalk.cyan('●')} Writing ${filePath}`);
|
|
214
|
+
console.log(` ${chalk.green(`+ ${additions} lines`)} · ${chalk.red(`- ${deletions} lines`)}\n`);
|
|
130
215
|
}
|
|
131
216
|
} catch (e) {
|
|
132
|
-
|
|
133
217
|
}
|
|
134
218
|
spinner.start("Thinking...");
|
|
135
219
|
}
|
|
@@ -137,38 +221,53 @@ export async function solveCommand(prompt: string, options: { autoApprove?: bool
|
|
|
137
221
|
}
|
|
138
222
|
);
|
|
139
223
|
|
|
140
|
-
|
|
224
|
+
let iterationCount = 1;
|
|
225
|
+
if (result.summary) {
|
|
226
|
+
const iterMatch = result.summary.match(/Iter (\d+)/);
|
|
227
|
+
if (iterMatch) iterationCount = parseInt(iterMatch[1], 10);
|
|
228
|
+
}
|
|
141
229
|
|
|
230
|
+
let filesChangedCount = 0;
|
|
142
231
|
try {
|
|
143
|
-
const statOut = execSync(`git diff --
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
232
|
+
const statOut = execSync(`git diff --shortstat`, { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] }).trim();
|
|
233
|
+
const filesMatch = statOut.match(/(\d+) file/);
|
|
234
|
+
if (filesMatch) {
|
|
235
|
+
filesChangedCount = parseInt(filesMatch[1], 10);
|
|
147
236
|
}
|
|
148
237
|
} catch (e) {}
|
|
149
238
|
|
|
150
|
-
|
|
151
|
-
|
|
239
|
+
spinner.stop();
|
|
240
|
+
const isIncomplete = result.verdict === 'INCOMPLETE' || result.state?.verdict === 'INCOMPLETE';
|
|
241
|
+
const isComplete = !isIncomplete;
|
|
152
242
|
|
|
243
|
+
if (isIncomplete) {
|
|
244
|
+
console.log(`\n ${chalk.red('✖')} Task incomplete · stopped or failed`);
|
|
245
|
+
} else {
|
|
246
|
+
console.log(`\n ────────────────────────────────────────`);
|
|
247
|
+
console.log(` ${chalk.green('✔')} Task complete · ${filesChangedCount} files modified · ${iterationCount} iterations`);
|
|
248
|
+
console.log(` ────────────────────────────────────────\n`);
|
|
249
|
+
}
|
|
153
250
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
251
|
+
if (isComplete) {
|
|
252
|
+
const packageJsonPath = path.join(cwd, "package.json");
|
|
253
|
+
if (fs.existsSync(packageJsonPath)) {
|
|
254
|
+
try {
|
|
255
|
+
const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf-8"));
|
|
256
|
+
if (pkg.scripts && pkg.scripts.build) {
|
|
257
|
+
console.log(`\n${chalk.cyan('=== Verification ===')}`);
|
|
258
|
+
const buildSpinner = ora("Running build command...").start();
|
|
259
|
+
try {
|
|
260
|
+
const env = { ...process.env };
|
|
261
|
+
delete env.NODE_ENV;
|
|
262
|
+
execSync("npm run build", { stdio: "pipe", cwd, env });
|
|
263
|
+
buildSpinner.succeed("Build completed successfully.");
|
|
264
|
+
} catch (e: any) {
|
|
265
|
+
buildSpinner.fail("Build failed.");
|
|
266
|
+
console.error(chalk.red(e.stdout ? e.stdout.toString() : e.message));
|
|
267
|
+
}
|
|
169
268
|
}
|
|
170
|
-
}
|
|
171
|
-
}
|
|
269
|
+
} catch (e) { }
|
|
270
|
+
}
|
|
172
271
|
}
|
|
173
272
|
} catch (err: any) {
|
|
174
273
|
spinner.fail("Agent encountered an error");
|
package/src/index.ts
CHANGED
|
@@ -4,8 +4,6 @@ import { Command } from "commander";
|
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import * as dotenv from "dotenv";
|
|
6
6
|
import * as os from "os";
|
|
7
|
-
|
|
8
|
-
// Suppress dotenv logging
|
|
9
7
|
process.env.DOTENV_CONFIG_QUIET = "true";
|
|
10
8
|
|
|
11
9
|
|
|
@@ -36,13 +34,10 @@ import { searchCommand } from "./commands/search";
|
|
|
36
34
|
import { reviewCommand } from "./commands/review";
|
|
37
35
|
import { issueCommand } from "./commands/issue";
|
|
38
36
|
import { graphCommand } from "./commands/graph";
|
|
39
|
-
|
|
40
|
-
import { fixNitbitsCommand } from "./commands/fix-nitbits";
|
|
41
|
-
import { analyzeCommand } from "./commands/analyze";
|
|
37
|
+
|
|
42
38
|
import { solveCommand } from "./commands/solve";
|
|
43
39
|
import { solveIssueCommand } from "./commands/solve-issue";
|
|
44
40
|
import { cacheCommand } from "./commands/cache";
|
|
45
|
-
import { configSet, configGet, configList } from "./commands/config";
|
|
46
41
|
|
|
47
42
|
const program = new Command();
|
|
48
43
|
|
|
@@ -53,10 +48,32 @@ program
|
|
|
53
48
|
.description("Developer Intelligence & PR Review Engine")
|
|
54
49
|
.version(version);
|
|
55
50
|
|
|
56
|
-
program.hook("preAction", (thisCommand, actionCommand) => {
|
|
51
|
+
program.hook("preAction", async (thisCommand, actionCommand) => {
|
|
57
52
|
if (actionCommand.opts().cache === false) {
|
|
58
53
|
process.env.VORTEX_DISABLE_CACHE = "true";
|
|
59
54
|
}
|
|
55
|
+
|
|
56
|
+
const cmdName = actionCommand.name();
|
|
57
|
+
if (['solve', 'solve-issue', 'review', 'suggest', 'analyze', 'search'].includes(cmdName)) {
|
|
58
|
+
const { default: ora } = await import("ora");
|
|
59
|
+
const { default: chalk } = await import("chalk");
|
|
60
|
+
|
|
61
|
+
console.log(`\n ${chalk.cyan('◆')} Vortex\n`);
|
|
62
|
+
const spinner = ora("Looking for model...").start();
|
|
63
|
+
|
|
64
|
+
if (process.env.GEMINI_API_KEY || process.env.OPENROUTER_API_KEY || process.env.GROQ_API_KEY) {
|
|
65
|
+
const priorityString = process.env.VORTEX_MODEL_PRIORITY;
|
|
66
|
+
const models = priorityString
|
|
67
|
+
? priorityString.split(",").map(s => s.trim()).filter(Boolean)
|
|
68
|
+
: ["nvidia/nemotron-3-ultra-550b-a55b:free", "nex-agi/nex-n2-pro:free", "openrouter/owl-alpha", "gemini-2.5-flash"];
|
|
69
|
+
const topModel = models[0] || "gemini";
|
|
70
|
+
spinner.succeed(`Model router active · priority: ${topModel}\n`);
|
|
71
|
+
} else {
|
|
72
|
+
spinner.fail(chalk.red("No model configured"));
|
|
73
|
+
console.log(`\n Run: ${chalk.cyan('vortex config set gemini "your_key_here"')}\n`);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
60
77
|
});
|
|
61
78
|
|
|
62
79
|
// ── Core Commands ──
|
|
@@ -116,50 +133,6 @@ program
|
|
|
116
133
|
|
|
117
134
|
// ── AI-Powered Commands ──
|
|
118
135
|
|
|
119
|
-
program
|
|
120
|
-
.command("suggest")
|
|
121
|
-
.description("Generate AI-powered code suggestions using repository patterns and historical intelligence")
|
|
122
|
-
.requiredOption("--file <path>", "Target file path")
|
|
123
|
-
.option("--apply", "Apply suggestions automatically")
|
|
124
|
-
.option("--deep", "Enable advanced contextual suggestions")
|
|
125
|
-
.option("--no-cache", "Disable LLM response caching")
|
|
126
|
-
.action(suggestCommand);
|
|
127
|
-
|
|
128
|
-
program
|
|
129
|
-
.command("fix-nitbits")
|
|
130
|
-
.description("Automatically fix formatting, comments, tests, CI issues, and repository-specific patterns")
|
|
131
|
-
.option("--safe", "Apply only deterministic safe fixes")
|
|
132
|
-
.option("--dry-run", "Preview fixes without modifying files")
|
|
133
|
-
.option("--files <paths>", "Comma-separated list of target files")
|
|
134
|
-
.action(fixNitbitsCommand);
|
|
135
|
-
|
|
136
|
-
program
|
|
137
|
-
.command("analyze")
|
|
138
|
-
.description("Analyze other contributors' PRs using repository history and architectural intelligence")
|
|
139
|
-
.requiredOption("--pr <number>", "Pull request number", Number)
|
|
140
|
-
.option("--deep", "Enable advanced PR intelligence analysis")
|
|
141
|
-
.option("--no-cache", "Disable LLM response caching")
|
|
142
|
-
.action(analyzeCommand);
|
|
143
|
-
|
|
144
|
-
const configCmd = program
|
|
145
|
-
.command("config")
|
|
146
|
-
.description("Manage global configuration and API keys");
|
|
147
|
-
|
|
148
|
-
configCmd
|
|
149
|
-
.command("set <provider> <key>")
|
|
150
|
-
.description("Set an API key for a specific provider (gemini or groq)")
|
|
151
|
-
.action(configSet);
|
|
152
|
-
|
|
153
|
-
configCmd
|
|
154
|
-
.command("get <key>")
|
|
155
|
-
.description("Get a global configuration value")
|
|
156
|
-
.action(configGet);
|
|
157
|
-
|
|
158
|
-
configCmd
|
|
159
|
-
.command("list")
|
|
160
|
-
.description("List all global configuration values")
|
|
161
|
-
.action(configList);
|
|
162
|
-
|
|
163
136
|
program.addCommand(cacheCommand);
|
|
164
137
|
|
|
165
138
|
program.parse();
|