@polka-codes/cli 0.9.3 → 0.9.4

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 +181 -33
  2. package/package.json +1 -1
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.4";
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";
@@ -76672,21 +76672,27 @@ ${instance.prompt}`;
76672
76672
  const requestTimeoutSeconds = this.config.requestTimeoutSeconds ?? 90;
76673
76673
  let respMessages = [];
76674
76674
  for (let i = 0;i < retryCount; i++) {
76675
+ if (this.#aborted) {
76676
+ break;
76677
+ }
76675
76678
  respMessages = [];
76676
76679
  let timeout;
76680
+ let requestAbortController;
76681
+ requestAbortController = new AbortController;
76682
+ this.#abortController = requestAbortController;
76677
76683
  const resetTimeout = () => {
76678
76684
  if (timeout) {
76679
76685
  clearTimeout(timeout);
76680
76686
  }
76681
- if (requestTimeoutSeconds > 0) {
76687
+ if (requestTimeoutSeconds > 0 && requestAbortController) {
76682
76688
  timeout = setTimeout(() => {
76683
- console.debug(`No data received for ${requestTimeoutSeconds} seconds. Aborting request.`);
76684
- this.abort();
76689
+ console.debug(`Request timeout after ${requestTimeoutSeconds} seconds. Canceling current request attempt ${i + 1}/${retryCount}.`);
76690
+ requestAbortController?.abort();
76685
76691
  }, requestTimeoutSeconds * 1000);
76686
76692
  }
76687
76693
  };
76688
- this.#abortController = new AbortController;
76689
76694
  try {
76695
+ resetTimeout();
76690
76696
  const streamTextOptions = {
76691
76697
  model: this.ai,
76692
76698
  messages,
@@ -76708,7 +76714,7 @@ ${instance.prompt}`;
76708
76714
  onError: async (error81) => {
76709
76715
  console.error("Error in stream:", error81);
76710
76716
  },
76711
- abortSignal: this.#abortController.signal
76717
+ abortSignal: requestAbortController.signal
76712
76718
  };
76713
76719
  if (this.config.toolFormat === "native") {
76714
76720
  streamTextOptions.tools = this.#toolSet;
@@ -76721,11 +76727,19 @@ ${instance.prompt}`;
76721
76727
  });
76722
76728
  const resp = await stream.response;
76723
76729
  respMessages = resp.messages;
76730
+ if (timeout) {
76731
+ clearTimeout(timeout);
76732
+ timeout = undefined;
76733
+ }
76724
76734
  } catch (error81) {
76725
76735
  if (error81 instanceof Error && error81.name === "AbortError") {
76726
- break;
76736
+ if (this.#aborted) {
76737
+ break;
76738
+ }
76739
+ console.debug(`Request attempt ${i + 1} timed out, will retry`);
76740
+ } else {
76741
+ console.error("Error in stream:", error81);
76727
76742
  }
76728
- console.error("Error in stream:", error81);
76729
76743
  } finally {
76730
76744
  if (timeout) {
76731
76745
  clearTimeout(timeout);
@@ -76737,13 +76751,15 @@ ${instance.prompt}`;
76737
76751
  if (this.#aborted) {
76738
76752
  break;
76739
76753
  }
76740
- console.debug(`Retrying request ${i + 1} of ${retryCount}`);
76754
+ if (i < retryCount - 1) {
76755
+ console.debug(`Retrying request ${i + 2} of ${retryCount}`);
76756
+ }
76741
76757
  }
76742
76758
  if (respMessages.length === 0) {
76743
76759
  if (this.#aborted) {
76744
76760
  return [];
76745
76761
  }
76746
- throw new Error("No assistant message received");
76762
+ throw new Error("No assistant message received after all retry attempts");
76747
76763
  }
76748
76764
  this.#messages.push(...respMessages);
76749
76765
  if (this.config.toolFormat === "native") {
@@ -76754,7 +76770,7 @@ ${instance.prompt}`;
76754
76770
  return [{ type: "text", content }];
76755
76771
  }
76756
76772
  return content.flatMap((part) => {
76757
- if (part.type === "text") {
76773
+ if (part.type === "text" || part.type === "reasoning") {
76758
76774
  return [{ type: "text", content: part.text }];
76759
76775
  }
76760
76776
  if (part.type === "tool-call") {
@@ -78151,37 +78167,48 @@ var prompt5 = `
78151
78167
 
78152
78168
  You are a senior software engineer reviewing code changes.
78153
78169
 
78170
+ ## Critical Instructions
78171
+ **ONLY review the actual changes shown in the diff.** Do not comment on existing code that wasn't modified.
78172
+
78154
78173
  ## 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.
78174
+ - **Use git_diff** to inspect the actual code changes for each relevant file.
78175
+ - **Pull request**: use the provided commit range for the git_diff tool.
78176
+ - **Local changes**: diff staged or unstaged files using the git_diff tool.
78158
78177
  - If a pull request is present you may receive:
78159
78178
  - <pr_title>
78160
78179
  - <pr_description>
78161
78180
  - <commit_messages>
78162
78181
  - A <review_instructions> tag tells you the focus of the review.
78182
+ - File status information is provided in <file_status> - use this to understand which files were modified, added, deleted, or renamed.
78183
+
78184
+ ## Review Guidelines
78185
+ Focus exclusively on the changed lines (+ additions, - deletions, modified lines):
78186
+ - **Specific issues**: Point to exact problems in the changed code with line references
78187
+ - **Actionable fixes**: Provide concrete solutions, not vague suggestions
78188
+ - **Clear reasoning**: Explain why each issue matters and how to fix it
78189
+ - **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
78163
78190
 
78164
- ## Focus Areas
78165
- - Readability and maintainability
78166
- - Correctness, edge cases, potential bugs
78167
- - Performance implications
78168
- - Clarity of intent
78169
- - Best-practice adherence
78191
+ ## What NOT to review
78192
+ - Existing unchanged code
78193
+ - Overall project structure or architecture (unless directly impacted by changes)
78194
+ - Generic best practices unrelated to the specific changes
78195
+ - Missing features or functionality not part of this diff
78170
78196
 
78171
78197
  ## Output Format
78172
78198
  Do **not** include praise or positive feedback. Ignore generated files such as lock files.
78199
+ Only include reviews for actual issues found in the changed code.
78173
78200
 
78174
78201
  Return your review as a JSON object inside a \`\`\`json block, wrapped like:
78175
78202
  <tool_attempt_completion>
78176
78203
  <tool_parameter_result>
78177
78204
  \`\`\`json
78178
78205
  {
78179
- "overview": "Summary of overall concerns.",
78206
+ "overview": "Summary of specific issues found in the diff changes, or 'No issues found' if the changes look good.",
78180
78207
  "specificReviews": [
78181
78208
  {
78182
78209
  "file": "path/filename.ext",
78183
78210
  "lines": "N or N-M",
78184
- "review": "Describe the issue and actionable fix or improvement."
78211
+ "review": "Specific issue with the changed code and exact actionable fix."
78185
78212
  }
78186
78213
  ]
78187
78214
  }
@@ -78209,14 +78236,21 @@ ${params.pullRequestDescription}
78209
78236
  parts.push(`<commit_messages>
78210
78237
  ${params.commitMessages}
78211
78238
  </commit_messages>`);
78239
+ }
78240
+ if (params.changedFiles && params.changedFiles.length > 0) {
78241
+ const fileList = params.changedFiles.map((file3) => `${file3.status}: ${file3.path}`).join(`
78242
+ `);
78243
+ parts.push(`<file_status>
78244
+ ${fileList}
78245
+ </file_status>`);
78212
78246
  }
78213
78247
  let instructions = "";
78214
78248
  if (params.commitRange) {
78215
- instructions = `Review the pull request. Get the diff using the git_diff tool with the commit range '${params.commitRange}'.`;
78249
+ 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
78250
  } else if (params.staged) {
78217
- instructions = "Review the staged changes. Get the diff using the git_diff tool with staged: true.";
78251
+ 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
78252
  } else {
78219
- instructions = "Review the unstaged changes. Get the diff using the git_diff tool.";
78253
+ instructions = "Review the unstaged changes. Use the git_diff tool to inspect the actual code changes. File status information is already provided above.";
78220
78254
  }
78221
78255
  parts.push(`<review_instructions>
78222
78256
  ${instructions}
@@ -103767,13 +103801,14 @@ var getModel = (config5, debugLogging = false) => {
103767
103801
  console.dir(requestBody, { depth: null });
103768
103802
  }
103769
103803
  if (TRACING_FILE) {
103770
- appendFileSync(TRACING_FILE, JSON.stringify({
103804
+ appendFileSync(TRACING_FILE, `${JSON.stringify({
103771
103805
  type: "request",
103772
103806
  timestamp: new Date().toISOString(),
103773
103807
  url: url4,
103774
103808
  headers: options?.headers,
103775
103809
  body: requestBody
103776
- }, null, 2));
103810
+ }, null, 2)}
103811
+ `);
103777
103812
  }
103778
103813
  const res = await fetch(url4, options);
103779
103814
  if (debugLogging) {
@@ -110522,6 +110557,89 @@ var prCommand = new Command("pr").description("Create a GitHub pull request").ar
110522
110557
  // src/commands/review.ts
110523
110558
  import { execSync as execSync3 } from "node:child_process";
110524
110559
  var import_lodash9 = __toESM(require_lodash(), 1);
110560
+ function parseGitStatus(statusOutput) {
110561
+ const statusLines = statusOutput.split(`
110562
+ `).filter((line) => line);
110563
+ const files = [];
110564
+ for (const line of statusLines) {
110565
+ const indexStatus = line[0];
110566
+ const workingTreeStatus = line[1];
110567
+ const filepath = line.slice(3);
110568
+ const statuses = [];
110569
+ if (indexStatus !== " " && indexStatus !== "?") {
110570
+ switch (indexStatus) {
110571
+ case "A":
110572
+ statuses.push("Added (staged)");
110573
+ break;
110574
+ case "M":
110575
+ statuses.push("Modified (staged)");
110576
+ break;
110577
+ case "D":
110578
+ statuses.push("Deleted (staged)");
110579
+ break;
110580
+ case "R":
110581
+ statuses.push("Renamed (staged)");
110582
+ break;
110583
+ case "C":
110584
+ statuses.push("Copied (staged)");
110585
+ break;
110586
+ default:
110587
+ statuses.push("Changed (staged)");
110588
+ }
110589
+ }
110590
+ if (workingTreeStatus !== " ") {
110591
+ switch (workingTreeStatus) {
110592
+ case "M":
110593
+ statuses.push("Modified (unstaged)");
110594
+ break;
110595
+ case "D":
110596
+ statuses.push("Deleted (unstaged)");
110597
+ break;
110598
+ case "?":
110599
+ statuses.push("Untracked");
110600
+ break;
110601
+ default:
110602
+ statuses.push("Changed (unstaged)");
110603
+ }
110604
+ }
110605
+ if (statuses.length > 0) {
110606
+ files.push({ path: filepath, status: statuses.join(", ") });
110607
+ }
110608
+ }
110609
+ return files;
110610
+ }
110611
+ function parseGitDiffNameStatus(diffOutput) {
110612
+ const lines = diffOutput.split(`
110613
+ `).filter((line) => line.trim());
110614
+ return lines.map((line) => {
110615
+ const [status, ...pathParts] = line.split("\t");
110616
+ const path = pathParts.join("\t");
110617
+ let statusDescription;
110618
+ switch (status[0]) {
110619
+ case "A":
110620
+ statusDescription = "Added";
110621
+ break;
110622
+ case "M":
110623
+ statusDescription = "Modified";
110624
+ break;
110625
+ case "D":
110626
+ statusDescription = "Deleted";
110627
+ break;
110628
+ case "R":
110629
+ statusDescription = "Renamed";
110630
+ break;
110631
+ case "C":
110632
+ statusDescription = "Copied";
110633
+ break;
110634
+ case "T":
110635
+ statusDescription = "Type changed";
110636
+ break;
110637
+ default:
110638
+ statusDescription = "Unknown";
110639
+ }
110640
+ return { path, status: statusDescription };
110641
+ });
110642
+ }
110525
110643
  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
110644
  const parentOptions = command.parent?.opts() ?? {};
110527
110645
  const { providerConfig, config: config6 } = parseOptions(parentOptions);
@@ -110604,12 +110722,21 @@ async function reviewPR(prIdentifier, spinner, sharedAiOptions, isJsonOutput) {
110604
110722
  const commitMessages = prDetails.commits.map((c) => c.messageBody).join(`
110605
110723
  ---
110606
110724
  `);
110725
+ spinner.text = "Getting file changes...";
110726
+ let changedFiles = [];
110727
+ try {
110728
+ const diffNameStatus = execSync3(`git diff --name-status --no-color ${defaultBranch}...HEAD`, { encoding: "utf-8" });
110729
+ changedFiles = parseGitDiffNameStatus(diffNameStatus);
110730
+ } catch (_error) {
110731
+ console.warn("Warning: Could not retrieve file changes list");
110732
+ }
110607
110733
  spinner.text = "Generating review...";
110608
110734
  const result = await reviewDiff(sharedAiOptions, {
110609
110735
  commitRange: `${defaultBranch}...HEAD`,
110610
110736
  pullRequestTitle: prDetails.title,
110611
110737
  pullRequestDescription: prDetails.body,
110612
- commitMessages
110738
+ commitMessages,
110739
+ changedFiles
110613
110740
  });
110614
110741
  spinner.succeed("Review generated successfully");
110615
110742
  if (isJsonOutput) {
@@ -110619,10 +110746,19 @@ async function reviewPR(prIdentifier, spinner, sharedAiOptions, isJsonOutput) {
110619
110746
  }
110620
110747
  }
110621
110748
  async function reviewLocal(spinner, sharedAiOptions, isJsonOutput) {
110622
- const hasStagedChanges = execSync3('git diff --staged --quiet || echo "staged"', { encoding: "utf-8" }).trim() === "staged";
110749
+ const gitStatus = execSync3("git status --porcelain=v1", { encoding: "utf-8" }).trim();
110750
+ const statusLines = gitStatus.split(`
110751
+ `).filter((line) => line);
110752
+ const hasStagedChanges = statusLines.some((line) => "MARC".includes(line[0]));
110753
+ const hasUnstagedChanges = statusLines.some((line) => "MARCDU".includes(line[1]));
110754
+ const changedFiles = parseGitStatus(gitStatus);
110623
110755
  if (hasStagedChanges) {
110624
110756
  spinner.text = "Generating review for staged changes...";
110625
- const result2 = await reviewDiff(sharedAiOptions, { staged: true });
110757
+ const stagedFiles = changedFiles.filter((file4) => file4.status.includes("staged"));
110758
+ const result2 = await reviewDiff(sharedAiOptions, {
110759
+ staged: true,
110760
+ changedFiles: stagedFiles
110761
+ });
110626
110762
  spinner.succeed("Review generated successfully");
110627
110763
  if (isJsonOutput) {
110628
110764
  console.log(JSON.stringify(result2, null, 2));
@@ -110631,10 +110767,13 @@ async function reviewLocal(spinner, sharedAiOptions, isJsonOutput) {
110631
110767
  }
110632
110768
  return;
110633
110769
  }
110634
- const hasUnstagedChanges = execSync3('git diff --quiet || echo "unstaged"', { encoding: "utf-8" }).trim() === "unstaged";
110635
110770
  if (hasUnstagedChanges) {
110636
110771
  spinner.text = "Generating review for unstaged changes...";
110637
- const result2 = await reviewDiff(sharedAiOptions, { staged: false });
110772
+ const unstagedFiles = changedFiles.filter((file4) => file4.status.includes("unstaged") || file4.status.includes("Untracked"));
110773
+ const result2 = await reviewDiff(sharedAiOptions, {
110774
+ staged: false,
110775
+ changedFiles: unstagedFiles
110776
+ });
110638
110777
  spinner.succeed("Review generated successfully");
110639
110778
  if (isJsonOutput) {
110640
110779
  console.log(JSON.stringify(result2, null, 2));
@@ -110659,9 +110798,18 @@ async function reviewLocal(spinner, sharedAiOptions, isJsonOutput) {
110659
110798
  spinner.succeed(`No changes to review. You are on the default branch ('${defaultBranch}').`);
110660
110799
  process.exit(0);
110661
110800
  }
110801
+ spinner.text = "Getting file changes...";
110802
+ let branchChangedFiles = [];
110803
+ try {
110804
+ const diffNameStatus = execSync3(`git diff --name-status --no-color ${defaultBranch}...${currentBranch}`, { encoding: "utf-8" });
110805
+ branchChangedFiles = parseGitDiffNameStatus(diffNameStatus);
110806
+ } catch (_error) {
110807
+ console.warn("Warning: Could not retrieve file changes list");
110808
+ }
110662
110809
  spinner.text = `Generating review for changes between '${defaultBranch}' and '${currentBranch}'...`;
110663
110810
  const result = await reviewDiff(sharedAiOptions, {
110664
- commitRange: `${defaultBranch}...${currentBranch}`
110811
+ commitRange: `${defaultBranch}...${currentBranch}`,
110812
+ changedFiles: branchChangedFiles
110665
110813
  });
110666
110814
  spinner.succeed("Review generated successfully");
110667
110815
  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.4",
4
4
  "license": "AGPL-3.0",
5
5
  "author": "github@polka.codes",
6
6
  "type": "module",