oroute-cli 0.6.0 → 0.6.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/dist/oroute.cjs +188 -5
- package/package.json +1 -1
package/dist/oroute.cjs
CHANGED
|
@@ -241941,7 +241941,9 @@ var ALL_COMMANDS = [
|
|
|
241941
241941
|
// Code analysis (Features 7, 8, 10)
|
|
241942
241942
|
"/test-gen",
|
|
241943
241943
|
"/dead-code",
|
|
241944
|
-
"/deps"
|
|
241944
|
+
"/deps",
|
|
241945
|
+
// Intelligence features (#25, #30)
|
|
241946
|
+
"/review-score"
|
|
241945
241947
|
];
|
|
241946
241948
|
var COMMAND_DESCRIPTIONS = {
|
|
241947
241949
|
"/help": "Show all available commands",
|
|
@@ -242036,7 +242038,8 @@ var COMMAND_DESCRIPTIONS = {
|
|
|
242036
242038
|
"/find": "Semantic code search using AI",
|
|
242037
242039
|
"/test-gen": "Generate tests for current file context",
|
|
242038
242040
|
"/dead-code": "Detect unused imports/exports",
|
|
242039
|
-
"/deps": "Show dependency graph for a file"
|
|
242041
|
+
"/deps": "Show dependency graph for a file",
|
|
242042
|
+
"/review-score": "Risk score for latest commit changes"
|
|
242040
242043
|
};
|
|
242041
242044
|
var KNOWN_COMMANDS = new Set(ALL_COMMANDS);
|
|
242042
242045
|
function shell(cmd, cwd) {
|
|
@@ -242558,6 +242561,9 @@ function handleSlashCommand(input, ctx) {
|
|
|
242558
242561
|
case "/deps":
|
|
242559
242562
|
handleDeps(arg, ctx);
|
|
242560
242563
|
return "handled";
|
|
242564
|
+
case "/review-score":
|
|
242565
|
+
handleReviewScore(ctx);
|
|
242566
|
+
return "handled";
|
|
242561
242567
|
// -----------------------------------------------------------------------
|
|
242562
242568
|
// Misc
|
|
242563
242569
|
// -----------------------------------------------------------------------
|
|
@@ -243088,6 +243094,7 @@ function printHelp() {
|
|
|
243088
243094
|
console.log(`${GREEN} /verify${RESET} Verify changes (build + typecheck)`);
|
|
243089
243095
|
console.log(`${GREEN} /tdd${RESET} Test-driven development mode`);
|
|
243090
243096
|
console.log(`${GREEN} /e2e${RESET} E2E testing`);
|
|
243097
|
+
console.log(`${GREEN} /review-score${RESET} Risk score for latest commit changes`);
|
|
243091
243098
|
console.log();
|
|
243092
243099
|
console.log(`${BOLD} File / Code${RESET}`);
|
|
243093
243100
|
console.log(`${GRAY} ${"\u2500".repeat(50)}${RESET}`);
|
|
@@ -243339,6 +243346,20 @@ function handleCommit(arg, ctx) {
|
|
|
243339
243346
|
return;
|
|
243340
243347
|
}
|
|
243341
243348
|
}
|
|
243349
|
+
const reviewPrompt = buildCommitReviewPrompt(ctx.cwd);
|
|
243350
|
+
if (reviewPrompt) {
|
|
243351
|
+
console.log(`${CYAN} Auto-reviewing staged changes...${RESET}`);
|
|
243352
|
+
ctx.messages.push({ role: "user", content: reviewPrompt });
|
|
243353
|
+
ctx.setMessages(ctx.messages);
|
|
243354
|
+
console.log(`${GRAY} Review prompt injected. AI will review your changes in the next turn.${RESET}`);
|
|
243355
|
+
console.log(`${GRAY} After review, run /commit again to proceed with the commit.${RESET}`);
|
|
243356
|
+
const lastMsg = ctx.messages[ctx.messages.length - 2];
|
|
243357
|
+
const isReEntry = lastMsg && lastMsg.role === "assistant" && typeof lastMsg.content === "string" && (lastMsg.content.includes("LGTM") || lastMsg.content.includes("review"));
|
|
243358
|
+
if (!isReEntry) {
|
|
243359
|
+
return;
|
|
243360
|
+
}
|
|
243361
|
+
console.log(`${GREEN} Review complete. Proceeding with commit...${RESET}`);
|
|
243362
|
+
}
|
|
243342
243363
|
if (arg) {
|
|
243343
243364
|
const output2 = shell(`git commit -m "${arg.replace(/"/g, '\\"')}"`, ctx.cwd);
|
|
243344
243365
|
console.log(output2);
|
|
@@ -243514,6 +243535,77 @@ function handleDeps(arg, ctx) {
|
|
|
243514
243535
|
}
|
|
243515
243536
|
console.log();
|
|
243516
243537
|
}
|
|
243538
|
+
function handleReviewScore(ctx) {
|
|
243539
|
+
console.log();
|
|
243540
|
+
console.log(`${BOLD} Code Review Risk Score${RESET}`);
|
|
243541
|
+
console.log(`${GRAY} ${"\u2500".repeat(40)}${RESET}`);
|
|
243542
|
+
const diffStat = shell("git diff HEAD~1 --stat", ctx.cwd);
|
|
243543
|
+
if (!diffStat || diffStat.startsWith("Command failed")) {
|
|
243544
|
+
console.log(`${YELLOW} No previous commit to compare against.${RESET}`);
|
|
243545
|
+
return;
|
|
243546
|
+
}
|
|
243547
|
+
const diffContent = shell("git diff HEAD~1", ctx.cwd);
|
|
243548
|
+
const fileLines = diffStat.split("\n").filter((l) => l.includes("|"));
|
|
243549
|
+
const filesChanged = fileLines.length;
|
|
243550
|
+
const summaryLine = diffStat.split("\n").pop() ?? "";
|
|
243551
|
+
const insertMatch = summaryLine.match(/(\d+)\s+insertion/);
|
|
243552
|
+
const deleteMatch = summaryLine.match(/(\d+)\s+deletion/);
|
|
243553
|
+
const insertions = insertMatch ? parseInt(insertMatch[1], 10) : 0;
|
|
243554
|
+
const deletions = deleteMatch ? parseInt(deleteMatch[1], 10) : 0;
|
|
243555
|
+
const totalLines = insertions + deletions;
|
|
243556
|
+
const newFunctions = (diffContent.match(/^\+.*(?:function\s+\w+|(?:const|let)\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>)/gm) ?? []).length;
|
|
243557
|
+
const deletedFunctions = (diffContent.match(/^-.*(?:function\s+\w+|(?:const|let)\s+\w+\s*=\s*(?:async\s+)?(?:\([^)]*\)|[^=])\s*=>)/gm) ?? []).length;
|
|
243558
|
+
const modifiedImports = (diffContent.match(/^[+-].*import\s+/gm) ?? []).length;
|
|
243559
|
+
let riskLevel;
|
|
243560
|
+
let riskColor;
|
|
243561
|
+
if (filesChanged <= 3 && totalLines < 50) {
|
|
243562
|
+
riskLevel = "LOW";
|
|
243563
|
+
riskColor = GREEN;
|
|
243564
|
+
} else if (filesChanged <= 10 && totalLines <= 200) {
|
|
243565
|
+
riskLevel = "MEDIUM";
|
|
243566
|
+
riskColor = YELLOW;
|
|
243567
|
+
} else {
|
|
243568
|
+
riskLevel = "HIGH";
|
|
243569
|
+
riskColor = RED;
|
|
243570
|
+
}
|
|
243571
|
+
console.log(`${riskColor} Risk: ${riskLevel}${RESET}`);
|
|
243572
|
+
console.log();
|
|
243573
|
+
console.log(`${WHITE} Files changed: ${filesChanged}${RESET}`);
|
|
243574
|
+
console.log(`${WHITE} Lines changed: ${totalLines} (+${insertions} -${deletions})${RESET}`);
|
|
243575
|
+
console.log(`${WHITE} New functions: ${newFunctions}${RESET}`);
|
|
243576
|
+
console.log(`${WHITE} Deleted functions: ${deletedFunctions}${RESET}`);
|
|
243577
|
+
console.log(`${WHITE} Modified imports: ${modifiedImports}${RESET}`);
|
|
243578
|
+
console.log();
|
|
243579
|
+
if (fileLines.length > 0) {
|
|
243580
|
+
console.log(`${GRAY} Changed files:${RESET}`);
|
|
243581
|
+
for (const line of fileLines.slice(0, 15)) {
|
|
243582
|
+
console.log(`${GRAY} ${line.trim()}${RESET}`);
|
|
243583
|
+
}
|
|
243584
|
+
if (fileLines.length > 15) {
|
|
243585
|
+
console.log(`${GRAY} ... and ${fileLines.length - 15} more${RESET}`);
|
|
243586
|
+
}
|
|
243587
|
+
}
|
|
243588
|
+
console.log();
|
|
243589
|
+
}
|
|
243590
|
+
function buildCommitReviewPrompt(cwd) {
|
|
243591
|
+
const diff = shell("git diff --cached", cwd);
|
|
243592
|
+
if (!diff || diff.startsWith("Command failed")) return "";
|
|
243593
|
+
const truncated = diff.length > 3e3 ? diff.slice(0, 3e3) + "\n... (truncated)" : diff;
|
|
243594
|
+
return [
|
|
243595
|
+
"Review these staged changes for bugs, security issues, and style problems before I commit:",
|
|
243596
|
+
"",
|
|
243597
|
+
"```diff",
|
|
243598
|
+
truncated,
|
|
243599
|
+
"```",
|
|
243600
|
+
"",
|
|
243601
|
+
"Provide a brief review (3-5 bullet points max). Flag any:",
|
|
243602
|
+
"- Potential bugs or logic errors",
|
|
243603
|
+
"- Security concerns (exposed secrets, injection risks)",
|
|
243604
|
+
"- Style issues (unused imports, inconsistent naming)",
|
|
243605
|
+
"- Missing error handling",
|
|
243606
|
+
'If everything looks good, say "LGTM" with a one-line summary.'
|
|
243607
|
+
].join("\n");
|
|
243608
|
+
}
|
|
243517
243609
|
|
|
243518
243610
|
// src/hooks.ts
|
|
243519
243611
|
var fs16 = __toESM(require("node:fs"), 1);
|
|
@@ -243598,6 +243690,65 @@ function substituteEnvVars(command, env) {
|
|
|
243598
243690
|
|
|
243599
243691
|
// src/agent.ts
|
|
243600
243692
|
init_mcpLoader();
|
|
243693
|
+
|
|
243694
|
+
// src/errorMapper.ts
|
|
243695
|
+
var ERROR_PATTERNS = [
|
|
243696
|
+
// TypeScript: src/foo.ts(10,5): error TS2345
|
|
243697
|
+
/(?<file>[^\s()]+\.(?:ts|tsx|js|jsx|mjs|cjs))\((?<line>\d+),(?<col>\d+)\):\s*error\s+TS/g,
|
|
243698
|
+
// Node.js stack trace: at /path/to/file.ts:42:15
|
|
243699
|
+
/at\s+(?:.*?\s+\()?(?<file>[^\s()]+\.(?:ts|tsx|js|jsx|mjs|cjs|py|rb|go|rs|java|kt)):(?<line>\d+):(?<col>\d+)\)?/g,
|
|
243700
|
+
// ESLint / many linters: /path/to/file.ts:42:15
|
|
243701
|
+
/^(?<file>(?:\/|\.\.?\/|[A-Z]:\\)[^\s:]+\.(?:ts|tsx|js|jsx|mjs|cjs)):(?<line>\d+):(?<col>\d+)/gm,
|
|
243702
|
+
// Python traceback: File "/path/to/file.py", line 42
|
|
243703
|
+
/File\s+"(?<file>[^"]+)",\s+line\s+(?<line>\d+)/g,
|
|
243704
|
+
// Rust/Go: src/main.rs:42:15
|
|
243705
|
+
/(?<file>[^\s:]+\.(?:rs|go)):(?<line>\d+):(?<col>\d+)/g,
|
|
243706
|
+
// Generic: Error in src/auth.ts (line 42)
|
|
243707
|
+
/(?:Error|error|ERROR)\s+in\s+(?<file>[^\s(]+)\s+\(line\s+(?<line>\d+)\)/g,
|
|
243708
|
+
// Webpack/Vite: ERROR in ./src/App.tsx 42:15
|
|
243709
|
+
/ERROR\s+in\s+\.?(?<file>[^\s]+\.(?:ts|tsx|js|jsx))\s+(?<line>\d+):(?<col>\d+)/g,
|
|
243710
|
+
// Jest: at Object.<anonymous> (src/test.ts:42:15)
|
|
243711
|
+
/\((?<file>[^\s()]+\.(?:ts|tsx|js|jsx|test\.[tj]s)):(?<line>\d+):(?<col>\d+)\)/g,
|
|
243712
|
+
// Simple line reference: file.ts:42
|
|
243713
|
+
/(?<file>[^\s:]+\.(?:ts|tsx|js|jsx|mjs|cjs)):(?<line>\d+)(?::(?<col>\d+))?/g
|
|
243714
|
+
];
|
|
243715
|
+
function extractFileReferences(errorText) {
|
|
243716
|
+
const seen = /* @__PURE__ */ new Set();
|
|
243717
|
+
const results = [];
|
|
243718
|
+
for (const pattern of ERROR_PATTERNS) {
|
|
243719
|
+
pattern.lastIndex = 0;
|
|
243720
|
+
let match;
|
|
243721
|
+
while ((match = pattern.exec(errorText)) !== null) {
|
|
243722
|
+
const groups = match.groups;
|
|
243723
|
+
if (!groups) continue;
|
|
243724
|
+
const file = groups["file"] ?? "";
|
|
243725
|
+
const lineStr = groups["line"] ?? "";
|
|
243726
|
+
const colStr = groups["col"];
|
|
243727
|
+
if (!file || !lineStr) continue;
|
|
243728
|
+
const line = parseInt(lineStr, 10);
|
|
243729
|
+
if (isNaN(line) || line <= 0) continue;
|
|
243730
|
+
const key = `${file}:${line}`;
|
|
243731
|
+
if (seen.has(key)) continue;
|
|
243732
|
+
seen.add(key);
|
|
243733
|
+
const ref = colStr ? { file, line, column: parseInt(colStr, 10) } : { file, line };
|
|
243734
|
+
results.push(ref);
|
|
243735
|
+
}
|
|
243736
|
+
}
|
|
243737
|
+
return results;
|
|
243738
|
+
}
|
|
243739
|
+
function buildErrorContextHint(refs) {
|
|
243740
|
+
if (refs.length === 0) return "";
|
|
243741
|
+
const lines = refs.slice(0, 10).map(
|
|
243742
|
+
(r) => ` - ${r.file}:${r.line}${r.column ? `:${r.column}` : ""}`
|
|
243743
|
+
);
|
|
243744
|
+
return [
|
|
243745
|
+
"\n[Auto-detected error locations \u2014 read these files to understand and fix the errors]:",
|
|
243746
|
+
...lines,
|
|
243747
|
+
refs.length > 10 ? ` ... and ${refs.length - 10} more` : ""
|
|
243748
|
+
].filter(Boolean).join("\n");
|
|
243749
|
+
}
|
|
243750
|
+
|
|
243751
|
+
// src/agent.ts
|
|
243601
243752
|
var keepAliveHttpAgent = new http.Agent({ keepAlive: true });
|
|
243602
243753
|
var keepAliveHttpsAgent = new https.Agent({ keepAlive: true });
|
|
243603
243754
|
var TOOL_DEFINITIONS = [
|
|
@@ -244085,7 +244236,30 @@ When you finish a task, mentally verify:
|
|
|
244085
244236
|
2. Are there any files I modified that need updated imports?
|
|
244086
244237
|
3. Would this change break anything in related files?
|
|
244087
244238
|
4. Should I suggest running tests or type checking?
|
|
244088
|
-
5. Is there a logical next step the user might want
|
|
244239
|
+
5. Is there a logical next step the user might want?
|
|
244240
|
+
|
|
244241
|
+
# Import Auto-Cleanup (#27)
|
|
244242
|
+
After editing TypeScript files, check for unused imports at the top of the file and remove them.
|
|
244243
|
+
You can detect unused imports by checking if the imported name appears elsewhere in the file.
|
|
244244
|
+
- For named imports like \`import { Foo, Bar } from './mod'\`, check if Foo and Bar each appear at least once outside the import line.
|
|
244245
|
+
- If an import name is only used in the import statement itself, remove it.
|
|
244246
|
+
- If all names from an import are unused, remove the entire import line.` + loadProjectPromptOverride(cwd);
|
|
244247
|
+
}
|
|
244248
|
+
function loadProjectPromptOverride(cwd) {
|
|
244249
|
+
const promptPath = path16.join(cwd, ".oroute", "prompt.md");
|
|
244250
|
+
try {
|
|
244251
|
+
if (fs17.existsSync(promptPath)) {
|
|
244252
|
+
const content = fs17.readFileSync(promptPath, "utf-8").trim();
|
|
244253
|
+
if (content) {
|
|
244254
|
+
return `
|
|
244255
|
+
|
|
244256
|
+
# Project-Specific Instructions (from .oroute/prompt.md)
|
|
244257
|
+
${content}`;
|
|
244258
|
+
}
|
|
244259
|
+
}
|
|
244260
|
+
} catch {
|
|
244261
|
+
}
|
|
244262
|
+
return "";
|
|
244089
244263
|
}
|
|
244090
244264
|
function truncateResult(result) {
|
|
244091
244265
|
if (result.length <= MAX_TOOL_RESULT_CHARS) return result;
|
|
@@ -244219,7 +244393,16 @@ async function executeToolInner(tool, cwd, confirmWrite, confirmExec, config) {
|
|
|
244219
244393
|
if (result.stderr) console.log(`${RED}${result.stderr}${RESET}`);
|
|
244220
244394
|
if (result.exitCode === 0) printSuccess("Command completed");
|
|
244221
244395
|
else console.log(`${YELLOW} Exit code: ${result.exitCode}${RESET}`);
|
|
244222
|
-
|
|
244396
|
+
const resultJson = JSON.stringify(result);
|
|
244397
|
+
if (result.exitCode !== 0) {
|
|
244398
|
+
const errorText = (result.stderr ?? "") + "\n" + (result.stdout ?? "");
|
|
244399
|
+
const refs = extractFileReferences(errorText);
|
|
244400
|
+
if (refs.length > 0) {
|
|
244401
|
+
const hint = buildErrorContextHint(refs);
|
|
244402
|
+
return truncateResult(resultJson + hint);
|
|
244403
|
+
}
|
|
244404
|
+
}
|
|
244405
|
+
return truncateResult(resultJson);
|
|
244223
244406
|
}
|
|
244224
244407
|
case "edit_file": {
|
|
244225
244408
|
const input = tool.input;
|
|
@@ -244638,7 +244821,7 @@ ${mcpSummary}
|
|
|
244638
244821
|
turnRollbackBuffer.clear();
|
|
244639
244822
|
let continueLoop = true;
|
|
244640
244823
|
let retryCount = 0;
|
|
244641
|
-
const maxRetries =
|
|
244824
|
+
const maxRetries = 5;
|
|
244642
244825
|
let toolIterations = 0;
|
|
244643
244826
|
const MAX_TOOL_ITERATIONS = 20;
|
|
244644
244827
|
const MAX_TOOL_HARD_CAP = 25;
|