@polka-codes/cli 0.9.3 → 0.9.5

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/index.js +260 -75
  2. package/package.json +4 -4
package/dist/index.js CHANGED
@@ -39731,7 +39731,7 @@ var {
39731
39731
  Help
39732
39732
  } = import__.default;
39733
39733
  // package.json
39734
- var version = "0.9.3";
39734
+ var version = "0.9.5";
39735
39735
 
39736
39736
  // ../../node_modules/@inquirer/core/dist/esm/lib/key.js
39737
39737
  var isUpKey = (key) => key.name === "up" || key.name === "k" || key.ctrl && key.name === "p";
@@ -52567,7 +52567,17 @@ var toolInfo7 = {
52567
52567
  return true;
52568
52568
  }
52569
52569
  return val;
52570
- }, exports_external.boolean().optional().default(true)).describe("Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.").meta({ usageValue: "true or false (optional)" })
52570
+ }, exports_external.boolean().optional().default(true)).describe("Whether to list files recursively. Use true for recursive listing, false or omit for top-level only.").meta({ usageValue: "true or false (optional)" }),
52571
+ includeIgnored: exports_external.preprocess((val) => {
52572
+ if (typeof val === "string") {
52573
+ const lower = val.toLowerCase();
52574
+ if (lower === "false")
52575
+ return false;
52576
+ if (lower === "true")
52577
+ return true;
52578
+ }
52579
+ return val;
52580
+ }, exports_external.boolean().optional().default(false)).describe("Whether to include ignored files. Use true to include files ignored by .gitignore.").meta({ usageValue: "true or false (optional)" })
52571
52581
  }).meta({
52572
52582
  examples: [
52573
52583
  {
@@ -52588,8 +52598,8 @@ var handler7 = async (provider, args) => {
52588
52598
  message: "Not possible to list files. Abort."
52589
52599
  };
52590
52600
  }
52591
- const { path, maxCount, recursive } = toolInfo7.parameters.parse(args);
52592
- const [files, limitReached] = await provider.listFiles(path, recursive, maxCount);
52601
+ const { path, maxCount, recursive, includeIgnored } = toolInfo7.parameters.parse(args);
52602
+ const [files, limitReached] = await provider.listFiles(path, recursive, maxCount, includeIgnored);
52593
52603
  return {
52594
52604
  type: "Reply" /* Reply */,
52595
52605
  message: `<list_files_path>${path}</list_files_path>
@@ -52618,7 +52628,17 @@ var toolInfo8 = {
52618
52628
  return [];
52619
52629
  const values = Array.isArray(val) ? val : [val];
52620
52630
  return values.flatMap((i) => typeof i === "string" ? i.split(",") : []).filter((s) => s.length > 0);
52621
- }, exports_external.array(exports_external.string())).describe("The path of the file to read").meta({ usageValue: "Comma separated paths here" })
52631
+ }, exports_external.array(exports_external.string())).describe("The path of the file to read").meta({ usageValue: "Comma separated paths here" }),
52632
+ includeIgnored: exports_external.preprocess((val) => {
52633
+ if (typeof val === "string") {
52634
+ const lower = val.toLowerCase();
52635
+ if (lower === "false")
52636
+ return false;
52637
+ if (lower === "true")
52638
+ return true;
52639
+ }
52640
+ return val;
52641
+ }, exports_external.boolean().optional().default(false)).describe("Whether to include ignored files. Use true to include files ignored by .gitignore.").meta({ usageValue: "true or false (optional)" })
52622
52642
  }).meta({
52623
52643
  examples: [
52624
52644
  {
@@ -52644,10 +52664,10 @@ var handler8 = async (provider, args) => {
52644
52664
  message: "Not possible to read file. Abort."
52645
52665
  };
52646
52666
  }
52647
- const { path: paths } = toolInfo8.parameters.parse(args);
52667
+ const { path: paths, includeIgnored } = toolInfo8.parameters.parse(args);
52648
52668
  const resp = [];
52649
52669
  for (const path of paths) {
52650
- const fileContent = await provider.readFile(path);
52670
+ const fileContent = await provider.readFile(path, includeIgnored);
52651
52671
  if (!fileContent) {
52652
52672
  resp.push(`<read_file_file_content path="${path}" file_not_found="true" />`);
52653
52673
  } else {
@@ -52957,7 +52977,7 @@ var handler11 = async (provider, args) => {
52957
52977
  }
52958
52978
  try {
52959
52979
  const { path, diff } = toolInfo11.parameters.parse(args);
52960
- const fileContent = await provider.readFile(path);
52980
+ const fileContent = await provider.readFile(path, false);
52961
52981
  if (fileContent == null) {
52962
52982
  return {
52963
52983
  type: "Error" /* Error */,
@@ -76672,23 +76692,30 @@ ${instance.prompt}`;
76672
76692
  const requestTimeoutSeconds = this.config.requestTimeoutSeconds ?? 90;
76673
76693
  let respMessages = [];
76674
76694
  for (let i = 0;i < retryCount; i++) {
76695
+ if (this.#aborted) {
76696
+ break;
76697
+ }
76675
76698
  respMessages = [];
76676
76699
  let timeout;
76700
+ let requestAbortController;
76701
+ requestAbortController = new AbortController;
76702
+ this.#abortController = requestAbortController;
76677
76703
  const resetTimeout = () => {
76678
76704
  if (timeout) {
76679
76705
  clearTimeout(timeout);
76680
76706
  }
76681
- if (requestTimeoutSeconds > 0) {
76707
+ if (requestTimeoutSeconds > 0 && requestAbortController) {
76682
76708
  timeout = setTimeout(() => {
76683
- console.debug(`No data received for ${requestTimeoutSeconds} seconds. Aborting request.`);
76684
- this.abort();
76709
+ console.debug(`Request timeout after ${requestTimeoutSeconds} seconds. Canceling current request attempt ${i + 1}/${retryCount}.`);
76710
+ requestAbortController?.abort();
76685
76711
  }, requestTimeoutSeconds * 1000);
76686
76712
  }
76687
76713
  };
76688
- this.#abortController = new AbortController;
76689
76714
  try {
76715
+ resetTimeout();
76690
76716
  const streamTextOptions = {
76691
76717
  model: this.ai,
76718
+ temperature: 0,
76692
76719
  messages,
76693
76720
  providerOptions: this.config.parameters?.providerOptions,
76694
76721
  onChunk: async ({ chunk }) => {
@@ -76708,7 +76735,7 @@ ${instance.prompt}`;
76708
76735
  onError: async (error81) => {
76709
76736
  console.error("Error in stream:", error81);
76710
76737
  },
76711
- abortSignal: this.#abortController.signal
76738
+ abortSignal: requestAbortController.signal
76712
76739
  };
76713
76740
  if (this.config.toolFormat === "native") {
76714
76741
  streamTextOptions.tools = this.#toolSet;
@@ -76721,11 +76748,19 @@ ${instance.prompt}`;
76721
76748
  });
76722
76749
  const resp = await stream.response;
76723
76750
  respMessages = resp.messages;
76751
+ if (timeout) {
76752
+ clearTimeout(timeout);
76753
+ timeout = undefined;
76754
+ }
76724
76755
  } catch (error81) {
76725
76756
  if (error81 instanceof Error && error81.name === "AbortError") {
76726
- break;
76757
+ if (this.#aborted) {
76758
+ break;
76759
+ }
76760
+ console.debug(`Request attempt ${i + 1} timed out, will retry`);
76761
+ } else {
76762
+ console.error("Error in stream:", error81);
76727
76763
  }
76728
- console.error("Error in stream:", error81);
76729
76764
  } finally {
76730
76765
  if (timeout) {
76731
76766
  clearTimeout(timeout);
@@ -76737,16 +76772,26 @@ ${instance.prompt}`;
76737
76772
  if (this.#aborted) {
76738
76773
  break;
76739
76774
  }
76740
- console.debug(`Retrying request ${i + 1} of ${retryCount}`);
76775
+ if (i < retryCount - 1) {
76776
+ console.debug(`Retrying request ${i + 2} of ${retryCount}`);
76777
+ }
76741
76778
  }
76742
76779
  if (respMessages.length === 0) {
76743
76780
  if (this.#aborted) {
76744
76781
  return [];
76745
76782
  }
76746
- throw new Error("No assistant message received");
76783
+ throw new Error("No assistant message received after all retry attempts");
76747
76784
  }
76748
76785
  this.#messages.push(...respMessages);
76749
76786
  if (this.config.toolFormat === "native") {
76787
+ const assistantText = respMessages.map((msg) => {
76788
+ if (typeof msg.content === "string") {
76789
+ return msg.content;
76790
+ }
76791
+ return msg.content.map((part) => part.type === "text" || part.type === "reasoning" ? part.text : "").join("");
76792
+ }).join(`
76793
+ `);
76794
+ await this.#callback({ kind: "EndRequest" /* EndRequest */, agent: this, message: assistantText });
76750
76795
  return respMessages.flatMap((msg) => {
76751
76796
  if (msg.role === "assistant") {
76752
76797
  const content = msg.content;
@@ -76754,7 +76799,7 @@ ${instance.prompt}`;
76754
76799
  return [{ type: "text", content }];
76755
76800
  }
76756
76801
  return content.flatMap((part) => {
76757
- if (part.type === "text") {
76802
+ if (part.type === "text" || part.type === "reasoning") {
76758
76803
  return [{ type: "text", content: part.text }];
76759
76804
  }
76760
76805
  if (part.type === "tool-call") {
@@ -78151,37 +78196,48 @@ var prompt5 = `
78151
78196
 
78152
78197
  You are a senior software engineer reviewing code changes.
78153
78198
 
78199
+ ## Critical Instructions
78200
+ **ONLY review the actual changes shown in the diff.** Do not comment on existing code that wasn't modified.
78201
+
78154
78202
  ## Viewing Changes
78155
- - Use **git_diff** to inspect code.
78156
- - **Pull request**: use the provided commit range.
78157
- - **Local changes**: diff staged or unstaged files.
78203
+ - **Use git_diff** to inspect the actual code changes for each relevant file.
78204
+ - **Pull request**: use the provided commit range for the git_diff tool.
78205
+ - **Local changes**: diff staged or unstaged files using the git_diff tool.
78158
78206
  - If a pull request is present you may receive:
78159
78207
  - <pr_title>
78160
78208
  - <pr_description>
78161
78209
  - <commit_messages>
78162
78210
  - A <review_instructions> tag tells you the focus of the review.
78211
+ - File status information is provided in <file_status> - use this to understand which files were modified, added, deleted, or renamed.
78163
78212
 
78164
- ## Focus Areas
78165
- - Readability and maintainability
78166
- - Correctness, edge cases, potential bugs
78167
- - Performance implications
78168
- - Clarity of intent
78169
- - Best-practice adherence
78213
+ ## Review Guidelines
78214
+ Focus exclusively on the changed lines (+ additions, - deletions, modified lines):
78215
+ - **Specific issues**: Point to exact problems in the changed code with line references
78216
+ - **Actionable fixes**: Provide concrete solutions, not vague suggestions
78217
+ - **Clear reasoning**: Explain why each issue matters and how to fix it
78218
+ - **Avoid generic advice**: No generic suggestions like "add more tests", "improve documentation", or "follow best practices" unless directly related to a specific problem in the diff
78219
+
78220
+ ## What NOT to review
78221
+ - Existing unchanged code
78222
+ - Overall project structure or architecture (unless directly impacted by changes)
78223
+ - Generic best practices unrelated to the specific changes
78224
+ - Missing features or functionality not part of this diff
78170
78225
 
78171
78226
  ## Output Format
78172
78227
  Do **not** include praise or positive feedback. Ignore generated files such as lock files.
78228
+ Only include reviews for actual issues found in the changed code.
78173
78229
 
78174
78230
  Return your review as a JSON object inside a \`\`\`json block, wrapped like:
78175
78231
  <tool_attempt_completion>
78176
78232
  <tool_parameter_result>
78177
78233
  \`\`\`json
78178
78234
  {
78179
- "overview": "Summary of overall concerns.",
78235
+ "overview": "Summary of specific issues found in the diff changes, or 'No issues found' if the changes look good.",
78180
78236
  "specificReviews": [
78181
78237
  {
78182
78238
  "file": "path/filename.ext",
78183
78239
  "lines": "N or N-M",
78184
- "review": "Describe the issue and actionable fix or improvement."
78240
+ "review": "Specific issue with the changed code and exact actionable fix."
78185
78241
  }
78186
78242
  ]
78187
78243
  }
@@ -78209,14 +78265,21 @@ ${params.pullRequestDescription}
78209
78265
  parts.push(`<commit_messages>
78210
78266
  ${params.commitMessages}
78211
78267
  </commit_messages>`);
78268
+ }
78269
+ if (params.changedFiles && params.changedFiles.length > 0) {
78270
+ const fileList = params.changedFiles.map((file3) => `${file3.status}: ${file3.path}`).join(`
78271
+ `);
78272
+ parts.push(`<file_status>
78273
+ ${fileList}
78274
+ </file_status>`);
78212
78275
  }
78213
78276
  let instructions = "";
78214
78277
  if (params.commitRange) {
78215
- instructions = `Review the pull request. Get the diff using the git_diff tool with the commit range '${params.commitRange}'.`;
78278
+ instructions = `Review the pull request. Use the git_diff tool with commit range '${params.commitRange}' to inspect the actual code changes. File status information is already provided above.`;
78216
78279
  } else if (params.staged) {
78217
- instructions = "Review the staged changes. Get the diff using the git_diff tool with staged: true.";
78280
+ instructions = "Review the staged changes. Use the git_diff tool with staged: true to inspect the actual code changes. File status information is already provided above.";
78218
78281
  } else {
78219
- instructions = "Review the unstaged changes. Get the diff using the git_diff tool.";
78282
+ instructions = "Review the unstaged changes. Use the git_diff tool to inspect the actual code changes. File status information is already provided above.";
78220
78283
  }
78221
78284
  parts.push(`<review_instructions>
78222
78285
  ${instructions}
@@ -78251,6 +78314,7 @@ ${output}`,
78251
78314
  var executeTool = async (definition, ai, params, usageMeter) => {
78252
78315
  const resp = await generateText({
78253
78316
  model: ai,
78317
+ temperature: 0,
78254
78318
  system: definition.prompt,
78255
78319
  messages: [
78256
78320
  {
@@ -86294,21 +86358,6 @@ var OpenRouterChatLanguageModel = class {
86294
86358
  return;
86295
86359
  }
86296
86360
  const delta = choice.delta;
86297
- if (delta.content != null) {
86298
- if (!textStarted) {
86299
- textId = openrouterResponseId || generateId2();
86300
- controller.enqueue({
86301
- type: "text-start",
86302
- id: textId
86303
- });
86304
- textStarted = true;
86305
- }
86306
- controller.enqueue({
86307
- type: "text-delta",
86308
- delta: delta.content,
86309
- id: textId || generateId2()
86310
- });
86311
- }
86312
86361
  const emitReasoningChunk = (chunkText) => {
86313
86362
  if (!reasoningStarted) {
86314
86363
  reasoningId = openrouterResponseId || generateId2();
@@ -86324,9 +86373,6 @@ var OpenRouterChatLanguageModel = class {
86324
86373
  id: reasoningId || generateId2()
86325
86374
  });
86326
86375
  };
86327
- if (delta.reasoning != null) {
86328
- emitReasoningChunk(delta.reasoning);
86329
- }
86330
86376
  if (delta.reasoning_details && delta.reasoning_details.length > 0) {
86331
86377
  for (const detail of delta.reasoning_details) {
86332
86378
  switch (detail.type) {
@@ -86353,6 +86399,23 @@ var OpenRouterChatLanguageModel = class {
86353
86399
  }
86354
86400
  }
86355
86401
  }
86402
+ } else if (delta.reasoning != null) {
86403
+ emitReasoningChunk(delta.reasoning);
86404
+ }
86405
+ if (delta.content != null) {
86406
+ if (!textStarted) {
86407
+ textId = openrouterResponseId || generateId2();
86408
+ controller.enqueue({
86409
+ type: "text-start",
86410
+ id: textId
86411
+ });
86412
+ textStarted = true;
86413
+ }
86414
+ controller.enqueue({
86415
+ type: "text-delta",
86416
+ delta: delta.content,
86417
+ id: textId || generateId2()
86418
+ });
86356
86419
  }
86357
86420
  if (delta.tool_calls != null) {
86358
86421
  for (const toolCallDelta of delta.tool_calls) {
@@ -86452,7 +86515,7 @@ var OpenRouterChatLanguageModel = class {
86452
86515
  var _a16;
86453
86516
  if (finishReason === "tool-calls") {
86454
86517
  for (const toolCall of toolCalls) {
86455
- if (!toolCall.sent) {
86518
+ if (toolCall && !toolCall.sent) {
86456
86519
  controller.enqueue({
86457
86520
  type: "tool-call",
86458
86521
  toolCallId: (_a16 = toolCall.id) != null ? _a16 : generateId2(),
@@ -103767,13 +103830,14 @@ var getModel = (config5, debugLogging = false) => {
103767
103830
  console.dir(requestBody, { depth: null });
103768
103831
  }
103769
103832
  if (TRACING_FILE) {
103770
- appendFileSync(TRACING_FILE, JSON.stringify({
103833
+ appendFileSync(TRACING_FILE, `${JSON.stringify({
103771
103834
  type: "request",
103772
103835
  timestamp: new Date().toISOString(),
103773
103836
  url: url4,
103774
103837
  headers: options?.headers,
103775
103838
  body: requestBody
103776
- }, null, 2));
103839
+ }, null, 2)}
103840
+ `);
103777
103841
  }
103778
103842
  const res = await fetch(url4, options);
103779
103843
  if (debugLogging) {
@@ -103795,11 +103859,12 @@ var getModel = (config5, debugLogging = false) => {
103795
103859
  console.log("<- Stream chunk:", text2.replace(/\n/g, "\\n"));
103796
103860
  }
103797
103861
  if (TRACING_FILE) {
103798
- appendFileSync(TRACING_FILE, JSON.stringify({
103862
+ appendFileSync(TRACING_FILE, `${JSON.stringify({
103799
103863
  type: "response-chunk",
103800
103864
  timestamp: new Date().toISOString(),
103801
103865
  chunk: text2
103802
- }, null, 2));
103866
+ }, null, 2)}
103867
+ `);
103803
103868
  }
103804
103869
  }
103805
103870
  }
@@ -103816,13 +103881,14 @@ var getModel = (config5, debugLogging = false) => {
103816
103881
  console.dir(responseBody, { depth: null });
103817
103882
  }
103818
103883
  if (TRACING_FILE) {
103819
- appendFileSync(TRACING_FILE, JSON.stringify({
103884
+ appendFileSync(TRACING_FILE, `${JSON.stringify({
103820
103885
  type: "response",
103821
103886
  timestamp: new Date().toISOString(),
103822
103887
  status: res.status,
103823
103888
  headers: Object.fromEntries(res.headers.entries()),
103824
103889
  body: responseBody
103825
- }, null, 2));
103890
+ }, null, 2)}
103891
+ `);
103826
103892
  }
103827
103893
  return new Response(full, {
103828
103894
  headers: res.headers,
@@ -108235,13 +108301,16 @@ async function extendPatterns(basePatterns, dirPath) {
108235
108301
  function createIgnore(patterns) {
108236
108302
  return import_ignore.default().add(patterns);
108237
108303
  }
108238
- async function listFiles(dirPath, recursive, maxCount, cwd, excludeFiles) {
108239
- let rootPatterns = [...DEFAULT_IGNORES, ...excludeFiles || []];
108240
- try {
108241
- const rootGitignore = await fs.readFile(join2(cwd, ".gitignore"), "utf8");
108242
- const lines = rootGitignore.split(/\r?\n/).filter(Boolean);
108243
- rootPatterns = [...rootPatterns, ...lines];
108244
- } catch {}
108304
+ async function listFiles(dirPath, recursive, maxCount, cwd, excludeFiles, includeIgnored) {
108305
+ let rootPatterns = [...excludeFiles || []];
108306
+ if (!includeIgnored) {
108307
+ rootPatterns.push(...DEFAULT_IGNORES);
108308
+ try {
108309
+ const rootGitignore = await fs.readFile(join2(cwd, ".gitignore"), "utf8");
108310
+ const lines = rootGitignore.split(/\r?\n/).filter(Boolean);
108311
+ rootPatterns = [...rootPatterns, ...lines];
108312
+ } catch {}
108313
+ }
108245
108314
  const results = [];
108246
108315
  const processedDirs = new Set;
108247
108316
  const queue = [
@@ -108254,7 +108323,7 @@ async function listFiles(dirPath, recursive, maxCount, cwd, excludeFiles) {
108254
108323
  while (queue.length > 0) {
108255
108324
  const { path: currentPath, patterns: parentPatterns, relPath: currentRelPath } = queue.shift();
108256
108325
  processedDirs.add(currentRelPath);
108257
- const mergedPatterns = await extendPatterns(parentPatterns, currentPath);
108326
+ const mergedPatterns = includeIgnored ? parentPatterns : await extendPatterns(parentPatterns, currentPath);
108258
108327
  const folderIg = createIgnore(mergedPatterns);
108259
108328
  const entries = await fs.readdir(currentPath, { withFileTypes: true });
108260
108329
  entries.sort((a, b) => a.name.localeCompare(b.name));
@@ -108350,8 +108419,8 @@ async function searchFiles(path, regex, filePattern, cwd, excludeFiles) {
108350
108419
  var getProvider = (_agentName, _config, options = {}) => {
108351
108420
  const ig = import_ignore2.default().add(options.excludeFiles ?? []);
108352
108421
  const provider2 = {
108353
- readFile: async (path) => {
108354
- if (ig.ignores(path)) {
108422
+ readFile: async (path, includeIgnored) => {
108423
+ if (!includeIgnored && ig.ignores(path)) {
108355
108424
  throw new Error(`Not allow to access file ${path}`);
108356
108425
  }
108357
108426
  try {
@@ -108379,8 +108448,8 @@ var getProvider = (_agentName, _config, options = {}) => {
108379
108448
  }
108380
108449
  return await rename(sourcePath, targetPath);
108381
108450
  },
108382
- listFiles: async (path, recursive, maxCount) => {
108383
- return await listFiles(path, recursive, maxCount, process.cwd(), options.excludeFiles);
108451
+ listFiles: async (path, recursive, maxCount, includeIgnored) => {
108452
+ return await listFiles(path, recursive, maxCount, process.cwd(), options.excludeFiles, includeIgnored);
108384
108453
  },
108385
108454
  executeCommand: (command, _needApprove) => {
108386
108455
  return new Promise((resolve3, reject) => {
@@ -109204,6 +109273,9 @@ function getProviderOptions(provider3, modelId, thinkingBudgetTokens, supportThi
109204
109273
  };
109205
109274
  break;
109206
109275
  case "openrouter" /* OpenRouter */:
109276
+ if (modelId.startsWith("anthropic/")) {
109277
+ break;
109278
+ }
109207
109279
  providerOptions.openrouter = {
109208
109280
  reasoning: {
109209
109281
  max_tokens: thinkingBudgetTokens
@@ -110522,6 +110594,89 @@ var prCommand = new Command("pr").description("Create a GitHub pull request").ar
110522
110594
  // src/commands/review.ts
110523
110595
  import { execSync as execSync3 } from "node:child_process";
110524
110596
  var import_lodash9 = __toESM(require_lodash(), 1);
110597
+ function parseGitStatus(statusOutput) {
110598
+ const statusLines = statusOutput.split(`
110599
+ `).filter((line) => line);
110600
+ const files = [];
110601
+ for (const line of statusLines) {
110602
+ const indexStatus = line[0];
110603
+ const workingTreeStatus = line[1];
110604
+ const filepath = line.slice(3);
110605
+ const statuses = [];
110606
+ if (indexStatus !== " " && indexStatus !== "?") {
110607
+ switch (indexStatus) {
110608
+ case "A":
110609
+ statuses.push("Added (staged)");
110610
+ break;
110611
+ case "M":
110612
+ statuses.push("Modified (staged)");
110613
+ break;
110614
+ case "D":
110615
+ statuses.push("Deleted (staged)");
110616
+ break;
110617
+ case "R":
110618
+ statuses.push("Renamed (staged)");
110619
+ break;
110620
+ case "C":
110621
+ statuses.push("Copied (staged)");
110622
+ break;
110623
+ default:
110624
+ statuses.push("Changed (staged)");
110625
+ }
110626
+ }
110627
+ if (workingTreeStatus !== " ") {
110628
+ switch (workingTreeStatus) {
110629
+ case "M":
110630
+ statuses.push("Modified (unstaged)");
110631
+ break;
110632
+ case "D":
110633
+ statuses.push("Deleted (unstaged)");
110634
+ break;
110635
+ case "?":
110636
+ statuses.push("Untracked");
110637
+ break;
110638
+ default:
110639
+ statuses.push("Changed (unstaged)");
110640
+ }
110641
+ }
110642
+ if (statuses.length > 0) {
110643
+ files.push({ path: filepath, status: statuses.join(", ") });
110644
+ }
110645
+ }
110646
+ return files;
110647
+ }
110648
+ function parseGitDiffNameStatus(diffOutput) {
110649
+ const lines = diffOutput.split(`
110650
+ `).filter((line) => line.trim());
110651
+ return lines.map((line) => {
110652
+ const [status, ...pathParts] = line.split("\t");
110653
+ const path = pathParts.join("\t");
110654
+ let statusDescription;
110655
+ switch (status[0]) {
110656
+ case "A":
110657
+ statusDescription = "Added";
110658
+ break;
110659
+ case "M":
110660
+ statusDescription = "Modified";
110661
+ break;
110662
+ case "D":
110663
+ statusDescription = "Deleted";
110664
+ break;
110665
+ case "R":
110666
+ statusDescription = "Renamed";
110667
+ break;
110668
+ case "C":
110669
+ statusDescription = "Copied";
110670
+ break;
110671
+ case "T":
110672
+ statusDescription = "Type changed";
110673
+ break;
110674
+ default:
110675
+ statusDescription = "Unknown";
110676
+ }
110677
+ return { path, status: statusDescription };
110678
+ });
110679
+ }
110525
110680
  var reviewCommand = new Command("review").description("Review a GitHub pull request or local changes").option("--pr <pr>", "The pull request number or URL to review").option("--json", "Output the review in JSON format", false).action(async (options, command) => {
110526
110681
  const parentOptions = command.parent?.opts() ?? {};
110527
110682
  const { providerConfig, config: config6 } = parseOptions(parentOptions);
@@ -110604,12 +110759,21 @@ async function reviewPR(prIdentifier, spinner, sharedAiOptions, isJsonOutput) {
110604
110759
  const commitMessages = prDetails.commits.map((c) => c.messageBody).join(`
110605
110760
  ---
110606
110761
  `);
110762
+ spinner.text = "Getting file changes...";
110763
+ let changedFiles = [];
110764
+ try {
110765
+ const diffNameStatus = execSync3(`git diff --name-status --no-color ${defaultBranch}...HEAD`, { encoding: "utf-8" });
110766
+ changedFiles = parseGitDiffNameStatus(diffNameStatus);
110767
+ } catch (_error) {
110768
+ console.warn("Warning: Could not retrieve file changes list");
110769
+ }
110607
110770
  spinner.text = "Generating review...";
110608
110771
  const result = await reviewDiff(sharedAiOptions, {
110609
110772
  commitRange: `${defaultBranch}...HEAD`,
110610
110773
  pullRequestTitle: prDetails.title,
110611
110774
  pullRequestDescription: prDetails.body,
110612
- commitMessages
110775
+ commitMessages,
110776
+ changedFiles
110613
110777
  });
110614
110778
  spinner.succeed("Review generated successfully");
110615
110779
  if (isJsonOutput) {
@@ -110619,10 +110783,19 @@ async function reviewPR(prIdentifier, spinner, sharedAiOptions, isJsonOutput) {
110619
110783
  }
110620
110784
  }
110621
110785
  async function reviewLocal(spinner, sharedAiOptions, isJsonOutput) {
110622
- const hasStagedChanges = execSync3('git diff --staged --quiet || echo "staged"', { encoding: "utf-8" }).trim() === "staged";
110786
+ const gitStatus = execSync3("git status --porcelain=v1", { encoding: "utf-8" }).trim();
110787
+ const statusLines = gitStatus.split(`
110788
+ `).filter((line) => line);
110789
+ const hasStagedChanges = statusLines.some((line) => "MARC".includes(line[0]));
110790
+ const hasUnstagedChanges = statusLines.some((line) => "MARCDU".includes(line[1]));
110791
+ const changedFiles = parseGitStatus(gitStatus);
110623
110792
  if (hasStagedChanges) {
110624
110793
  spinner.text = "Generating review for staged changes...";
110625
- const result2 = await reviewDiff(sharedAiOptions, { staged: true });
110794
+ const stagedFiles = changedFiles.filter((file4) => file4.status.includes("staged"));
110795
+ const result2 = await reviewDiff(sharedAiOptions, {
110796
+ staged: true,
110797
+ changedFiles: stagedFiles
110798
+ });
110626
110799
  spinner.succeed("Review generated successfully");
110627
110800
  if (isJsonOutput) {
110628
110801
  console.log(JSON.stringify(result2, null, 2));
@@ -110631,10 +110804,13 @@ async function reviewLocal(spinner, sharedAiOptions, isJsonOutput) {
110631
110804
  }
110632
110805
  return;
110633
110806
  }
110634
- const hasUnstagedChanges = execSync3('git diff --quiet || echo "unstaged"', { encoding: "utf-8" }).trim() === "unstaged";
110635
110807
  if (hasUnstagedChanges) {
110636
110808
  spinner.text = "Generating review for unstaged changes...";
110637
- const result2 = await reviewDiff(sharedAiOptions, { staged: false });
110809
+ const unstagedFiles = changedFiles.filter((file4) => file4.status.includes("unstaged") || file4.status.includes("Untracked"));
110810
+ const result2 = await reviewDiff(sharedAiOptions, {
110811
+ staged: false,
110812
+ changedFiles: unstagedFiles
110813
+ });
110638
110814
  spinner.succeed("Review generated successfully");
110639
110815
  if (isJsonOutput) {
110640
110816
  console.log(JSON.stringify(result2, null, 2));
@@ -110659,9 +110835,18 @@ async function reviewLocal(spinner, sharedAiOptions, isJsonOutput) {
110659
110835
  spinner.succeed(`No changes to review. You are on the default branch ('${defaultBranch}').`);
110660
110836
  process.exit(0);
110661
110837
  }
110838
+ spinner.text = "Getting file changes...";
110839
+ let branchChangedFiles = [];
110840
+ try {
110841
+ const diffNameStatus = execSync3(`git diff --name-status --no-color ${defaultBranch}...${currentBranch}`, { encoding: "utf-8" });
110842
+ branchChangedFiles = parseGitDiffNameStatus(diffNameStatus);
110843
+ } catch (_error) {
110844
+ console.warn("Warning: Could not retrieve file changes list");
110845
+ }
110662
110846
  spinner.text = `Generating review for changes between '${defaultBranch}' and '${currentBranch}'...`;
110663
110847
  const result = await reviewDiff(sharedAiOptions, {
110664
- commitRange: `${defaultBranch}...${currentBranch}`
110848
+ commitRange: `${defaultBranch}...${currentBranch}`,
110849
+ changedFiles: branchChangedFiles
110665
110850
  });
110666
110851
  spinner.succeed("Review generated successfully");
110667
110852
  if (isJsonOutput) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polka-codes/cli",
3
- "version": "0.9.3",
3
+ "version": "0.9.5",
4
4
  "license": "AGPL-3.0",
5
5
  "author": "github@polka.codes",
6
6
  "type": "module",
@@ -24,9 +24,9 @@
24
24
  "@ai-sdk/provider": "2.0.0-beta.1",
25
25
  "@ai-sdk/provider-utils": "3.0.0-beta.5",
26
26
  "@inquirer/prompts": "^7.2.3",
27
- "@openrouter/ai-sdk-provider": "^1.0.0-beta.3",
28
- "@polka-codes/cli-shared": "0.9.1",
29
- "@polka-codes/core": "0.9.1",
27
+ "@openrouter/ai-sdk-provider": "^1.0.0-beta.6",
28
+ "@polka-codes/cli-shared": "0.9.4",
29
+ "@polka-codes/core": "0.9.4",
30
30
  "ai": "5.0.0-beta.24",
31
31
  "commander": "^13.0.0",
32
32
  "dotenv": "^16.4.7",