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.
Files changed (2) hide show
  1. package/dist/oroute.cjs +188 -5
  2. 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
- return truncateResult(JSON.stringify(result));
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 = 3;
244824
+ const maxRetries = 5;
244642
244825
  let toolIterations = 0;
244643
244826
  const MAX_TOOL_ITERATIONS = 20;
244644
244827
  const MAX_TOOL_HARD_CAP = 25;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oroute-cli",
3
- "version": "0.6.0",
3
+ "version": "0.6.1",
4
4
  "description": "O'Route CLI — AI API auto-routing for 13 models from your terminal",
5
5
  "type": "module",
6
6
  "bin": {