@vortex-ai/cli 0.1.2
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 +15 -0
- package/.turbo/turbo-check-types.log +5 -0
- package/.vortex-bm25.json +1 -0
- package/.vortex.db +0 -0
- package/README.md +155 -0
- package/dist/chunk-C6P2IO65.mjs +41 -0
- package/dist/chunk-FC2JQ4EC.mjs +119 -0
- package/dist/chunk-PNFSQC3M.mjs +4792 -0
- package/dist/chunk-PXYQG752.mjs +99 -0
- package/dist/dist-2OLJAZT5.mjs +1244 -0
- package/dist/dist-BPCHQHDN.mjs +5 -0
- package/dist/dist-DAPSACZG.mjs +5 -0
- package/dist/index.js +886 -0
- package/dist/index.mjs +880 -0
- package/dist/multipart-parser-DVTUXEEQ.mjs +371 -0
- package/dist/src-IOKO4UCP.mjs +1408 -0
- package/package.json +43 -0
- package/src/commands/analyze.ts +74 -0
- package/src/commands/cache.ts +33 -0
- package/src/commands/fix-nitbits.ts +63 -0
- package/src/commands/graph.ts +59 -0
- package/src/commands/init.ts +37 -0
- package/src/commands/issue.ts +79 -0
- package/src/commands/review.ts +218 -0
- package/src/commands/search.ts +67 -0
- package/src/commands/solve-issue.ts +69 -0
- package/src/commands/solve.ts +177 -0
- package/src/commands/suggest.ts +54 -0
- package/src/commands/watch.ts +56 -0
- package/src/index.ts +147 -0
- package/tsconfig.json +5 -0
- package/vortex-ai-0.1.0.tgz +0 -0
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vortex-ai/cli",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Vortex CLI - The main entry point",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"vortex": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"build": "tsup src/index.ts --format cjs,esm --external @xenova/transformers",
|
|
11
|
+
"dev": "tsx src/index.ts",
|
|
12
|
+
"watch": "tsup src/index.ts --format cjs,esm --watch",
|
|
13
|
+
"lint": "eslint .",
|
|
14
|
+
"check-types": "tsc --noEmit"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@octokit/rest": "^22.0.1",
|
|
18
|
+
"@types/marked-terminal": "^6.1.1",
|
|
19
|
+
"@vortex/engine": "workspace:*",
|
|
20
|
+
"@vortex/github": "workspace:*",
|
|
21
|
+
"@vortex/git": "workspace:*",
|
|
22
|
+
"@vortex/shared": "workspace:*",
|
|
23
|
+
"@vortex/retrieval": "workspace:*",
|
|
24
|
+
"@vortex/db": "workspace:*",
|
|
25
|
+
"@xenova/transformers": "^2.17.2",
|
|
26
|
+
"boxen": "7",
|
|
27
|
+
"chalk": "^5.6.2",
|
|
28
|
+
"chokidar": "^5.0.0",
|
|
29
|
+
"commander": "^12.1.0",
|
|
30
|
+
"dotenv": "^17.4.2",
|
|
31
|
+
"marked": "^15.0.12",
|
|
32
|
+
"marked-terminal": "^7.3.0",
|
|
33
|
+
"ora": "^9.4.0",
|
|
34
|
+
"parse-diff": "^0.12.0",
|
|
35
|
+
"zod": "^4.4.3"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@vortex/typescript-config": "workspace:*",
|
|
39
|
+
"tsup": "^8.0.2",
|
|
40
|
+
"tsx": "^4.21.0",
|
|
41
|
+
"typescript": "5.9.2"
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { IntelligenceAgent } from "@vortex/engine";
|
|
2
|
+
import { createGithubClient } from "@vortex/github";
|
|
3
|
+
import { getGithubRepoInfo } from "@vortex/git";
|
|
4
|
+
|
|
5
|
+
export async function analyzeCommand(options: {
|
|
6
|
+
pr: number;
|
|
7
|
+
deep?: boolean;
|
|
8
|
+
}) {
|
|
9
|
+
const { default: ora } = await import("ora");
|
|
10
|
+
const { default: chalk } = await import("chalk");
|
|
11
|
+
const { default: boxen } = await import("boxen");
|
|
12
|
+
const { marked } = await import("marked");
|
|
13
|
+
const { default: TerminalRenderer } = await import("marked-terminal");
|
|
14
|
+
|
|
15
|
+
marked.setOptions({
|
|
16
|
+
renderer: new TerminalRenderer() as any,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
console.log(
|
|
20
|
+
chalk.blue.bold(`\nAnalyzing External PR #${options.pr}\n`)
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
24
|
+
console.error(
|
|
25
|
+
chalk.red("Please set GITHUB_TOKEN environment variable.")
|
|
26
|
+
);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const repoInfo = getGithubRepoInfo(process.cwd());
|
|
31
|
+
const owner = process.env.GITHUB_OWNER || repoInfo?.owner;
|
|
32
|
+
const repo = process.env.GITHUB_REPO || repoInfo?.repo;
|
|
33
|
+
|
|
34
|
+
if (!owner || !repo) {
|
|
35
|
+
console.error(
|
|
36
|
+
chalk.red(
|
|
37
|
+
"Could not determine GitHub repository. Please run this command inside a git repository or set GITHUB_OWNER and GITHUB_REPO."
|
|
38
|
+
)
|
|
39
|
+
);
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const spinner = ora(
|
|
44
|
+
`Fetching diff for ${owner}/${repo}#${options.pr}...`
|
|
45
|
+
).start();
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
const github = createGithubClient(process.env.GITHUB_TOKEN);
|
|
49
|
+
const diff = await github.fetchPullRequestDiff(owner, repo, options.pr);
|
|
50
|
+
|
|
51
|
+
spinner.text = "Generating AI analysis...";
|
|
52
|
+
|
|
53
|
+
const agent = new IntelligenceAgent();
|
|
54
|
+
const review = await agent.generateReview(diff);
|
|
55
|
+
|
|
56
|
+
spinner.succeed(chalk.green("Analysis complete!\n"));
|
|
57
|
+
|
|
58
|
+
const parsedReview = await marked.parse(review);
|
|
59
|
+
|
|
60
|
+
const formatted = boxen(parsedReview.trim(), {
|
|
61
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
62
|
+
margin: { top: 1, bottom: 1 },
|
|
63
|
+
borderStyle: "double",
|
|
64
|
+
borderColor: "magenta",
|
|
65
|
+
title: chalk.magenta.bold(" External PR Analysis "),
|
|
66
|
+
titleAlignment: "center",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
console.log(formatted);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
spinner.fail(chalk.red("Failed to analyze PR"));
|
|
72
|
+
console.error(err);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { LLMCacheManager } from "@vortex/engine";
|
|
3
|
+
|
|
4
|
+
export const cacheCommand = new Command("cache")
|
|
5
|
+
.description("Manage the LLM response cache");
|
|
6
|
+
|
|
7
|
+
cacheCommand
|
|
8
|
+
.command("stats")
|
|
9
|
+
.description("View LLM cache statistics")
|
|
10
|
+
.action(async () => {
|
|
11
|
+
try {
|
|
12
|
+
const stats = await LLMCacheManager.getStats();
|
|
13
|
+
console.log("\nLLM Cache Statistics\n");
|
|
14
|
+
console.log(`Entries: ${stats.entries.toLocaleString()}`);
|
|
15
|
+
console.log(`Hits: ${stats.hits.toLocaleString()}`);
|
|
16
|
+
const storageMB = (stats.storage / 1024 / 1024).toFixed(2);
|
|
17
|
+
console.log(`Storage: ${storageMB} MB\n`);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error("Failed to retrieve cache stats:", err);
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
cacheCommand
|
|
24
|
+
.command("clear")
|
|
25
|
+
.description("Clear all entries from the LLM cache")
|
|
26
|
+
.action(async () => {
|
|
27
|
+
try {
|
|
28
|
+
await LLMCacheManager.clearCache();
|
|
29
|
+
console.log("✅ LLM Cache cleared successfully.");
|
|
30
|
+
} catch (err) {
|
|
31
|
+
console.error("Failed to clear LLM cache:", err);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { IntelligenceAgent } from "@vortex/engine";
|
|
2
|
+
import * as fs from "fs";
|
|
3
|
+
|
|
4
|
+
export async function fixNitbitsCommand(options: {
|
|
5
|
+
safe?: boolean;
|
|
6
|
+
dryRun?: boolean;
|
|
7
|
+
files?: string;
|
|
8
|
+
}) {
|
|
9
|
+
const { default: ora } = await import("ora");
|
|
10
|
+
const { default: chalk } = await import("chalk");
|
|
11
|
+
|
|
12
|
+
console.log(chalk.blue.bold("\nFixing Repository Nitbits\n"));
|
|
13
|
+
|
|
14
|
+
if (!options.files) {
|
|
15
|
+
console.error(
|
|
16
|
+
chalk.red("Please provide --files to fix (comma-separated list).")
|
|
17
|
+
);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const files = options.files.split(",").map((file: string) => file.trim());
|
|
22
|
+
const agent = new IntelligenceAgent();
|
|
23
|
+
|
|
24
|
+
let fixedCount = 0;
|
|
25
|
+
let failedCount = 0;
|
|
26
|
+
|
|
27
|
+
for (const file of files) {
|
|
28
|
+
if (!fs.existsSync(file)) {
|
|
29
|
+
console.warn(chalk.yellow(`⚠️ File not found: ${file}`));
|
|
30
|
+
failedCount++;
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const spinner = ora(`Auto-fixing ${file}...`).start();
|
|
35
|
+
|
|
36
|
+
try {
|
|
37
|
+
const content = fs.readFileSync(file, "utf8");
|
|
38
|
+
const fixedContent = await agent.autoFix(content);
|
|
39
|
+
|
|
40
|
+
if (options.dryRun) {
|
|
41
|
+
spinner.succeed(chalk.cyan(`[DRY RUN] ${file}`));
|
|
42
|
+
console.log(chalk.gray(`\n--- FIXED OUTPUT ---\n`));
|
|
43
|
+
console.log(fixedContent);
|
|
44
|
+
console.log(chalk.gray(`\n--- END ---\n`));
|
|
45
|
+
} else {
|
|
46
|
+
fs.writeFileSync(file, fixedContent, "utf8");
|
|
47
|
+
spinner.succeed(chalk.green(`Fixed and saved ${file}`));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
fixedCount++;
|
|
51
|
+
} catch (err) {
|
|
52
|
+
spinner.fail(chalk.red(`Failed to fix ${file}`));
|
|
53
|
+
console.error(err);
|
|
54
|
+
failedCount++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log(
|
|
59
|
+
chalk.dim(
|
|
60
|
+
`\n Summary: ${fixedCount} fixed, ${failedCount} failed out of ${files.length} files\n`
|
|
61
|
+
)
|
|
62
|
+
);
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { GraphRetriever } from "@vortex/retrieval";
|
|
2
|
+
import { initDatabase, findProjectRoot } from "@vortex/db";
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
|
|
5
|
+
export async function graphCommand(options: { file?: string; detailed?: boolean }) {
|
|
6
|
+
const { default: chalk } = await import("chalk");
|
|
7
|
+
const { default: boxen } = await import("boxen");
|
|
8
|
+
|
|
9
|
+
console.log(chalk.blue.bold("\nGenerating Dependency Graph...\n"));
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
await initDatabase();
|
|
13
|
+
const graphRetriever = new GraphRetriever();
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
let fileFilter = options.file;
|
|
17
|
+
if (!fileFilter) {
|
|
18
|
+
const projectRoot = findProjectRoot(process.cwd());
|
|
19
|
+
const relPath = path.relative(projectRoot, process.cwd());
|
|
20
|
+
if (relPath && relPath !== ".") {
|
|
21
|
+
fileFilter = relPath;
|
|
22
|
+
console.log(chalk.dim(`Directory filter auto-applied: ${fileFilter}`));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const asciiTree = await graphRetriever.generateAsciiTree(fileFilter);
|
|
27
|
+
const mermaidCode = await graphRetriever.generateMermaidGraph(fileFilter, options.detailed);
|
|
28
|
+
|
|
29
|
+
console.log(
|
|
30
|
+
boxen(asciiTree.trim(), {
|
|
31
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
32
|
+
margin: { top: 1, bottom: 1 },
|
|
33
|
+
borderStyle: "round",
|
|
34
|
+
borderColor: "cyan",
|
|
35
|
+
title: chalk.bold(` Dependency Graph `),
|
|
36
|
+
titleAlignment: "center",
|
|
37
|
+
})
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (options.file) {
|
|
41
|
+
console.log(chalk.green(`✅ Graph generated successfully for file: ${options.file}`));
|
|
42
|
+
} else {
|
|
43
|
+
console.log(chalk.green("✅ Project dependency graph generated successfully."));
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
const mermaidConfig = {
|
|
48
|
+
theme: "dark",
|
|
49
|
+
maxTextSize: 900000
|
|
50
|
+
};
|
|
51
|
+
const state = { code: mermaidCode, mermaid: JSON.stringify(mermaidConfig, null, 2), autoSync: true, updateDiagram: true };
|
|
52
|
+
const base64 = Buffer.from(JSON.stringify(state)).toString('base64');
|
|
53
|
+
const liveUrl = `https://mermaid.live/edit#base64:${base64}`;
|
|
54
|
+
|
|
55
|
+
console.log(chalk.dim(`\nWant a visual flowchart? Open this URL:\n${liveUrl}`));
|
|
56
|
+
} catch (err) {
|
|
57
|
+
console.error(chalk.red("Failed to generate graph:"), err);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { Indexer } from "@vortex/engine";
|
|
2
|
+
|
|
3
|
+
export async function initCommand(options: any) {
|
|
4
|
+
const { default: ora } = await import("ora");
|
|
5
|
+
const { default: chalk } = await import("chalk");
|
|
6
|
+
|
|
7
|
+
console.log(chalk.blue.bold("\nInitializing Vortex Intelligence Layer\n"));
|
|
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
|
+
}
|
|
13
|
+
|
|
14
|
+
const spinner = ora("Scanning repository and building indices...").start();
|
|
15
|
+
|
|
16
|
+
const indexer = new Indexer();
|
|
17
|
+
try {
|
|
18
|
+
const stats = await indexer.indexRepository(process.cwd());
|
|
19
|
+
|
|
20
|
+
spinner.succeed(chalk.green("Initialization complete!\n"));
|
|
21
|
+
|
|
22
|
+
console.log(chalk.white.bold(" Index Statistics:"));
|
|
23
|
+
console.log(chalk.gray(` Files processed: ${stats.filesProcessed}`));
|
|
24
|
+
console.log(chalk.gray(` Chunks indexed: ${stats.chunksIndexed}`));
|
|
25
|
+
console.log(chalk.cyan(` BM25 documents: ${stats.bm25Documents}`));
|
|
26
|
+
console.log(chalk.gray(` Vector store: SQLite (Prisma)`));
|
|
27
|
+
console.log(chalk.gray(` BM25 index: .vortex-bm25.json\n`));
|
|
28
|
+
|
|
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
|
+
} catch (err) {
|
|
34
|
+
spinner.fail(chalk.red("Failed to initialize"));
|
|
35
|
+
console.error(err);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { Indexer, IntelligenceAgent } from "@vortex/engine";
|
|
2
|
+
import { createGithubClient } from "@vortex/github";
|
|
3
|
+
import { getGithubRepoInfo } from "@vortex/git";
|
|
4
|
+
|
|
5
|
+
export async function issueCommand(options: any) {
|
|
6
|
+
const { default: ora } = await import("ora");
|
|
7
|
+
const { default: chalk } = await import("chalk");
|
|
8
|
+
const { default: boxen } = await import("boxen");
|
|
9
|
+
const { marked } = await import("marked");
|
|
10
|
+
const { default: TerminalRenderer } = await import("marked-terminal");
|
|
11
|
+
|
|
12
|
+
marked.setOptions({
|
|
13
|
+
renderer: new TerminalRenderer() as any
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
console.log(chalk.blue(`\nAnalyzing Issue #${options.id}`));
|
|
17
|
+
|
|
18
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
19
|
+
console.log(chalk.yellow("⚠️ No GITHUB_TOKEN found. Using anonymous access (subject to rate limits)."));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const repoInfo = getGithubRepoInfo(process.cwd());
|
|
23
|
+
const owner = process.env.GITHUB_OWNER || repoInfo?.owner;
|
|
24
|
+
const repo = process.env.GITHUB_REPO || repoInfo?.repo;
|
|
25
|
+
|
|
26
|
+
if (!owner || !repo) {
|
|
27
|
+
console.error(chalk.red("Could not determine GitHub repository. Please run this command inside a git repository or set GITHUB_OWNER and GITHUB_REPO."));
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const spinner = ora(`Fetching Issue #${options.id} from ${owner}/${repo}...`).start();
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
const github = createGithubClient(process.env.GITHUB_TOKEN);
|
|
35
|
+
const issue = await github.fetchIssue(owner, repo, options.id);
|
|
36
|
+
const comments = await github.fetchIssueComments(owner, repo, options.id);
|
|
37
|
+
|
|
38
|
+
spinner.text = `Issue fetched. Searching local vector database for relevant code...`;
|
|
39
|
+
|
|
40
|
+
const indexer = new Indexer();
|
|
41
|
+
const relevantContext = await indexer.search(issue.title, 5);
|
|
42
|
+
|
|
43
|
+
spinner.text = "Analyzing issue and relevant codebase context with Vortex Intelligence...";
|
|
44
|
+
const agent = new IntelligenceAgent();
|
|
45
|
+
|
|
46
|
+
const analysis = await agent.generateIssueAnalysis(
|
|
47
|
+
issue.title,
|
|
48
|
+
issue.body,
|
|
49
|
+
comments,
|
|
50
|
+
relevantContext
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
spinner.succeed("Issue Analysis complete!\n");
|
|
54
|
+
|
|
55
|
+
const parsedAnalysis = await marked.parse(analysis);
|
|
56
|
+
|
|
57
|
+
const formatted = boxen(parsedAnalysis.trim(), {
|
|
58
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
59
|
+
margin: { top: 1, bottom: 1 },
|
|
60
|
+
borderStyle: 'double',
|
|
61
|
+
borderColor: 'green',
|
|
62
|
+
title: chalk.green.bold(' AI Issue Analyzer '),
|
|
63
|
+
titleAlignment: 'center'
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
console.log(formatted);
|
|
67
|
+
|
|
68
|
+
if (relevantContext.length > 0) {
|
|
69
|
+
console.log(chalk.cyan.dim(" Relevant Code Files Discovered"));
|
|
70
|
+
relevantContext.forEach((res: any, i: number) => {
|
|
71
|
+
console.log(chalk.gray(` │ [${i + 1}] ${res.file.replace(process.cwd(), '')} ➔ ${res.symbolPath || '(anonymous)'} (${res.score ? (res.score * 100).toFixed(1) + '%' : 'N/A'})`));
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
} catch (err) {
|
|
76
|
+
spinner.fail("Failed to analyze issue");
|
|
77
|
+
console.error(err);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import {
|
|
2
|
+
IntelligenceAgent,
|
|
3
|
+
Indexer,
|
|
4
|
+
MemoryService,
|
|
5
|
+
ToolRegistry,
|
|
6
|
+
GrepTool,
|
|
7
|
+
FileReadTool,
|
|
8
|
+
} from "@vortex/engine";
|
|
9
|
+
import { createGithubClient } from "@vortex/github";
|
|
10
|
+
|
|
11
|
+
export async function reviewCommand(options: any) {
|
|
12
|
+
const { default: ora } = await import("ora");
|
|
13
|
+
const { default: chalk } = await import("chalk");
|
|
14
|
+
const { default: boxen } = await import("boxen");
|
|
15
|
+
const { marked } = await import("marked");
|
|
16
|
+
const { default: TerminalRenderer } = await import("marked-terminal");
|
|
17
|
+
|
|
18
|
+
marked.setOptions({
|
|
19
|
+
renderer: new TerminalRenderer() as any,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
if (options.pr) {
|
|
23
|
+
console.log(chalk.blue.bold(`\nMulti-Agent Review for PR #${options.pr}\n`));
|
|
24
|
+
} else {
|
|
25
|
+
console.log(chalk.blue.bold(`\nMulti-Agent Review for Local Changes\n`));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (!process.env.GITHUB_TOKEN) {
|
|
29
|
+
console.log(
|
|
30
|
+
chalk.yellow(
|
|
31
|
+
"⚠️ No GITHUB_TOKEN found. Using anonymous access (subject to rate limits)."
|
|
32
|
+
)
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const repoInfo = await import("@vortex/git").then((m) =>
|
|
37
|
+
m.getGithubRepoInfo(process.cwd())
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
const owner = process.env.GITHUB_OWNER || repoInfo?.owner;
|
|
41
|
+
const repo = process.env.GITHUB_REPO || repoInfo?.repo;
|
|
42
|
+
|
|
43
|
+
if (!owner || !repo) {
|
|
44
|
+
console.error(
|
|
45
|
+
chalk.red(
|
|
46
|
+
"Could not determine GitHub repository. Please run this command inside a git repository or set GITHUB_OWNER and GITHUB_REPO."
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const spinner = ora(
|
|
53
|
+
options.pr ? `Fetching diff for ${owner}/${repo}#${options.pr}...` : `Fetching local git diff...`
|
|
54
|
+
).start();
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
|
|
58
|
+
let diff: string;
|
|
59
|
+
if (options.pr) {
|
|
60
|
+
const github = createGithubClient(process.env.GITHUB_TOKEN);
|
|
61
|
+
diff = await github.fetchPullRequestDiff(owner, repo, options.pr);
|
|
62
|
+
} else {
|
|
63
|
+
const { execSync } = await import("child_process");
|
|
64
|
+
diff = execSync("git diff HEAD").toString();
|
|
65
|
+
if (!diff.trim()) {
|
|
66
|
+
spinner.fail("No local changes found to review. Make some changes or specify a --pr.");
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
spinner.text = "Extracting architectural queries from diff...";
|
|
73
|
+
const agent = new IntelligenceAgent();
|
|
74
|
+
const indexer = new Indexer();
|
|
75
|
+
|
|
76
|
+
const queries = await agent.extractSearchQueriesFromDiff(diff);
|
|
77
|
+
|
|
78
|
+
const allChunks: any[] = [];
|
|
79
|
+
if (queries.length > 0) {
|
|
80
|
+
spinner.text = `Hybrid searching for: ${queries.join(", ")}...`;
|
|
81
|
+
for (const query of queries) {
|
|
82
|
+
const results = await indexer.hybridSearch(query, 3);
|
|
83
|
+
allChunks.push(...results);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
const uniqueChunks = Array.from(
|
|
89
|
+
new Map(allChunks.map((c) => [c.id, c])).values()
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
spinner.text = "Checking memory for relevant past reviews...";
|
|
94
|
+
const memoryService = new MemoryService();
|
|
95
|
+
const memories = await memoryService.recallRelevantMemories(
|
|
96
|
+
`PR review ${queries.join(" ")}`,
|
|
97
|
+
3
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
spinner.text = `Running multi-agent review (Security + Architecture + Synthesis)...`;
|
|
102
|
+
const review = await agent.generateMultiAgentReview(
|
|
103
|
+
diff,
|
|
104
|
+
uniqueChunks,
|
|
105
|
+
memories
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
spinner.succeed(
|
|
109
|
+
chalk.green(
|
|
110
|
+
`Review complete in ${(review.durationMs / 1000).toFixed(1)}s!\n`
|
|
111
|
+
)
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
const parsedReview = await marked.parse(review.markdownReport);
|
|
116
|
+
|
|
117
|
+
const verdictColor =
|
|
118
|
+
review.verdict === "SAFE_TO_MERGE"
|
|
119
|
+
? chalk.green
|
|
120
|
+
: review.verdict === "REQUIRES_CHANGES"
|
|
121
|
+
? chalk.red
|
|
122
|
+
: chalk.yellow;
|
|
123
|
+
|
|
124
|
+
const borderColor =
|
|
125
|
+
review.verdict === "SAFE_TO_MERGE"
|
|
126
|
+
? "green"
|
|
127
|
+
: review.verdict === "REQUIRES_CHANGES"
|
|
128
|
+
? "red"
|
|
129
|
+
: "yellow";
|
|
130
|
+
|
|
131
|
+
const formatted = boxen(parsedReview.trim(), {
|
|
132
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
133
|
+
margin: { top: 1, bottom: 1 },
|
|
134
|
+
borderStyle: "double",
|
|
135
|
+
borderColor: borderColor as any,
|
|
136
|
+
title: chalk.bold(` Multi-Agent Code Review `),
|
|
137
|
+
titleAlignment: "center",
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
console.log(formatted);
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
console.log(
|
|
144
|
+
boxen(verdictColor.bold(` ${review.verdict} `), {
|
|
145
|
+
padding: { left: 2, right: 2 },
|
|
146
|
+
borderStyle: "round",
|
|
147
|
+
borderColor: borderColor as any,
|
|
148
|
+
textAlignment: "center",
|
|
149
|
+
})
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
const { security, architecture } = review.agentOutputs;
|
|
154
|
+
|
|
155
|
+
console.log(
|
|
156
|
+
chalk.dim(
|
|
157
|
+
`\n Security: ${security.riskLevel} (${security.findings.length} findings)`
|
|
158
|
+
)
|
|
159
|
+
);
|
|
160
|
+
security.findings.forEach((f: any, i: number) => {
|
|
161
|
+
const severityColor =
|
|
162
|
+
f.severity === "critical" || f.severity === "high"
|
|
163
|
+
? chalk.red
|
|
164
|
+
: f.severity === "medium"
|
|
165
|
+
? chalk.yellow
|
|
166
|
+
: chalk.gray;
|
|
167
|
+
console.log(severityColor(` [${f.severity.toUpperCase()}] ${f.title}`));
|
|
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
|
+
});
|
|
184
|
+
|
|
185
|
+
if (uniqueChunks.length > 0) {
|
|
186
|
+
console.log(chalk.cyan.dim("\n Cross-Referenced Architecture:"));
|
|
187
|
+
uniqueChunks.forEach((res: any, i: number) => {
|
|
188
|
+
const sources = res.sources ? res.sources.join("+") : "vector";
|
|
189
|
+
console.log(
|
|
190
|
+
chalk.gray(
|
|
191
|
+
` │ [${i + 1}] ${res.file.replace(process.cwd(), "")} ➔ ${res.symbolPath || "(anonymous)"} [${sources}]`
|
|
192
|
+
)
|
|
193
|
+
);
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (memories.length > 0) {
|
|
198
|
+
console.log(chalk.yellow.dim("\n Past Review Memories Used:"));
|
|
199
|
+
memories.forEach((mem: string, i: number) => {
|
|
200
|
+
console.log(chalk.gray(` │ [${i + 1}] ${mem.slice(0, 120)}...`));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
if (options.pr) {
|
|
206
|
+
try {
|
|
207
|
+
await memoryService.storeReviewMemory(options.pr, owner, repo, review);
|
|
208
|
+
} catch (err) {
|
|
209
|
+
console.warn(chalk.yellow("\n⚠️ Failed to store review in memory:"), err);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
console.log("");
|
|
214
|
+
} catch (err) {
|
|
215
|
+
spinner.fail("Failed to review PR");
|
|
216
|
+
console.error(err);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Indexer, IntelligenceAgent, MemoryService, ToolRegistry, GrepTool, FileReadTool } from "@vortex/engine";
|
|
2
|
+
|
|
3
|
+
export async function searchCommand(options: any) {
|
|
4
|
+
const { default: ora } = await import("ora");
|
|
5
|
+
const { default: chalk } = await import("chalk");
|
|
6
|
+
const { default: boxen } = await import("boxen");
|
|
7
|
+
const { marked } = await import("marked");
|
|
8
|
+
const { default: TerminalRenderer } = await import("marked-terminal");
|
|
9
|
+
|
|
10
|
+
marked.setOptions({
|
|
11
|
+
renderer: new TerminalRenderer() as any
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const spinner = ora(`Searching codebase for: "${options.query}"...`).start();
|
|
15
|
+
const indexer = new Indexer();
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
spinner.text = "Running hybrid search (vector + BM25 + cross-encoder)...";
|
|
19
|
+
const results = await indexer.hybridSearch(options.query, parseInt(options.limit, 10));
|
|
20
|
+
|
|
21
|
+
if (results.length === 0) {
|
|
22
|
+
spinner.fail("No relevant code found.");
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
spinner.text = `Found ${results.length} relevant code chunks. Analyzing with Gemini...`;
|
|
27
|
+
|
|
28
|
+
const memoryService = new MemoryService();
|
|
29
|
+
const memories = await memoryService.recallRelevantMemories(options.query, 3);
|
|
30
|
+
|
|
31
|
+
const agent = new IntelligenceAgent();
|
|
32
|
+
const answer = await agent.answerQueryWithContext(options.query, results);
|
|
33
|
+
|
|
34
|
+
spinner.succeed("Analysis complete!\n");
|
|
35
|
+
|
|
36
|
+
const parsedAnswer = await marked.parse(answer);
|
|
37
|
+
|
|
38
|
+
const formatted = boxen(parsedAnswer.trim(), {
|
|
39
|
+
padding: { top: 1, bottom: 1, left: 2, right: 2 },
|
|
40
|
+
margin: { top: 1, bottom: 1 },
|
|
41
|
+
borderStyle: 'double',
|
|
42
|
+
borderColor: 'cyan',
|
|
43
|
+
title: chalk.cyan.bold(' Vortex AI Engine (Hybrid Search) '),
|
|
44
|
+
titleAlignment: 'center'
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
console.log(formatted);
|
|
48
|
+
|
|
49
|
+
console.log(chalk.cyan.dim(" Reference Material (Hybrid Retrieval)"));
|
|
50
|
+
results.forEach((res: any, i: number) => {
|
|
51
|
+
const sources = res.sources ? res.sources.join("+") : "vector";
|
|
52
|
+
const scoreStr = res.score ? (res.score * 100).toFixed(1) + '%' : 'N/A';
|
|
53
|
+
console.log(chalk.gray(` │ [${i + 1}] ${res.file.replace(process.cwd(), '')} ➔ ${res.symbolPath || '(anonymous)'} (${scoreStr}) [${sources}]`));
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
if (memories.length > 0) {
|
|
57
|
+
console.log(chalk.yellow.dim("\n Relevant Memories"));
|
|
58
|
+
memories.forEach((mem: string, i: number) => {
|
|
59
|
+
console.log(chalk.gray(` │ [${i + 1}] ${mem.slice(0, 120)}...`));
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
} catch (err) {
|
|
64
|
+
spinner.fail("Search failed");
|
|
65
|
+
console.error(err);
|
|
66
|
+
}
|
|
67
|
+
}
|