@gaberrb/polypus 0.4.5 → 0.4.6

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/index.js CHANGED
@@ -137,6 +137,10 @@ var en = {
137
137
  "run.autocorrect": "\u21BB tool failed \u2014 auto-correcting with extra context",
138
138
  "run.cancelled": "\u25A0 cancelled",
139
139
  "run.jsonNeedsTask": "--json requires a task argument (headless mode has no interactive REPL).",
140
+ "review.approveAll": "approve all",
141
+ "review.reject": "reject",
142
+ "review.pickHunks": "pick hunks\u2026",
143
+ "review.selectHunks": "Select the hunks to apply (space to toggle, enter to confirm)",
140
144
  // repl
141
145
  "repl.welcome": "Polypus interactive session.",
142
146
  "repl.welcomeHint": " Type /help for commands, /exit to quit.",
@@ -365,6 +369,10 @@ var ptBR = {
365
369
  "run.autocorrect": "\u21BB tool falhou \u2014 autocorrigindo com contexto extra",
366
370
  "run.cancelled": "\u25A0 cancelado",
367
371
  "run.jsonNeedsTask": "--json exige um argumento de tarefa (o modo headless n\xE3o tem REPL interativo).",
372
+ "review.approveAll": "aprovar tudo",
373
+ "review.reject": "rejeitar",
374
+ "review.pickHunks": "escolher hunks\u2026",
375
+ "review.selectHunks": "Selecione os hunks a aplicar (espa\xE7o alterna, enter confirma)",
368
376
  "repl.welcome": "Sess\xE3o interativa do Polypus.",
369
377
  "repl.welcomeHint": " Digite /help para comandos, /exit para sair.",
370
378
  "repl.modeChanged": "modo \u2192 {mode}",
@@ -963,6 +971,10 @@ function createProvider(agent) {
963
971
  return { config: agent, provider, toolMode: resolveToolMode(agent) };
964
972
  }
965
973
 
974
+ // src/core/permissions/modes.ts
975
+ import { readFile as readFile2 } from "fs/promises";
976
+ import { resolve as resolve2 } from "path";
977
+
966
978
  // src/core/permissions/allowlist.ts
967
979
  import { isAbsolute, relative, resolve, sep } from "path";
968
980
  function globToRegExp(glob) {
@@ -1058,6 +1070,111 @@ function scanSecrets(text2) {
1058
1070
  return findings;
1059
1071
  }
1060
1072
 
1073
+ // src/core/permissions/diff.ts
1074
+ var CONTEXT = 3;
1075
+ function splitLines(text2) {
1076
+ return text2 === "" ? [] : text2.split("\n");
1077
+ }
1078
+ function lcsOps(a, b) {
1079
+ const n = a.length;
1080
+ const m = b.length;
1081
+ const dp = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
1082
+ for (let i2 = n - 1; i2 >= 0; i2--) {
1083
+ for (let j2 = m - 1; j2 >= 0; j2--) {
1084
+ dp[i2][j2] = a[i2] === b[j2] ? dp[i2 + 1][j2 + 1] + 1 : Math.max(dp[i2 + 1][j2], dp[i2][j2 + 1]);
1085
+ }
1086
+ }
1087
+ const out = [];
1088
+ let i = 0;
1089
+ let j = 0;
1090
+ while (i < n && j < m) {
1091
+ if (a[i] === b[j]) {
1092
+ out.push({ type: " ", text: a[i] });
1093
+ i++;
1094
+ j++;
1095
+ } else if (dp[i + 1][j] >= dp[i][j + 1]) {
1096
+ out.push({ type: "-", text: a[i] });
1097
+ i++;
1098
+ } else {
1099
+ out.push({ type: "+", text: b[j] });
1100
+ j++;
1101
+ }
1102
+ }
1103
+ while (i < n) out.push({ type: "-", text: a[i++] });
1104
+ while (j < m) out.push({ type: "+", text: b[j++] });
1105
+ return out;
1106
+ }
1107
+ function computeHunks(oldText, newText) {
1108
+ const a = splitLines(oldText);
1109
+ const b = splitLines(newText);
1110
+ const ops = lcsOps(a, b);
1111
+ const isChange = ops.map((o) => o.type !== " ");
1112
+ const keep = new Array(ops.length).fill(false);
1113
+ for (let k2 = 0; k2 < ops.length; k2++) {
1114
+ if (isChange[k2]) {
1115
+ for (let c = Math.max(0, k2 - CONTEXT); c <= Math.min(ops.length - 1, k2 + CONTEXT); c++) {
1116
+ keep[c] = true;
1117
+ }
1118
+ }
1119
+ }
1120
+ const hunks = [];
1121
+ let oldLine = 0;
1122
+ let newLine = 0;
1123
+ let k = 0;
1124
+ while (k < ops.length) {
1125
+ const op = ops[k];
1126
+ if (!keep[k]) {
1127
+ if (op.type !== "+") oldLine++;
1128
+ if (op.type !== "-") newLine++;
1129
+ k++;
1130
+ continue;
1131
+ }
1132
+ const oldStart = oldLine;
1133
+ const newStart = newLine;
1134
+ const lines = [];
1135
+ let oldCount = 0;
1136
+ let newCount = 0;
1137
+ while (k < ops.length && keep[k]) {
1138
+ const cur = ops[k];
1139
+ lines.push(cur);
1140
+ if (cur.type !== "+") {
1141
+ oldLine++;
1142
+ oldCount++;
1143
+ }
1144
+ if (cur.type !== "-") {
1145
+ newLine++;
1146
+ newCount++;
1147
+ }
1148
+ k++;
1149
+ }
1150
+ hunks.push({ oldStart, oldCount, newStart, newCount, lines });
1151
+ }
1152
+ return hunks;
1153
+ }
1154
+ function applyHunks(oldText, hunks, approved) {
1155
+ const a = splitLines(oldText);
1156
+ const out = [];
1157
+ let oldIdx = 0;
1158
+ hunks.forEach((hunk, idx) => {
1159
+ while (oldIdx < hunk.oldStart) out.push(a[oldIdx++]);
1160
+ if (approved.has(idx)) {
1161
+ for (const l of hunk.lines) if (l.type !== "-") out.push(l.text);
1162
+ } else {
1163
+ for (const l of hunk.lines) if (l.type !== "+") out.push(l.text);
1164
+ }
1165
+ oldIdx = hunk.oldStart + hunk.oldCount;
1166
+ });
1167
+ while (oldIdx < a.length) out.push(a[oldIdx++]);
1168
+ return out.join("\n");
1169
+ }
1170
+ function hunkLabel(hunk) {
1171
+ const added = hunk.lines.filter((l) => l.type === "+").length;
1172
+ const removed = hunk.lines.filter((l) => l.type === "-").length;
1173
+ const firstChange = hunk.lines.find((l) => l.type !== " ");
1174
+ const preview = firstChange ? firstChange.text.trim().slice(0, 50) : "";
1175
+ return `@@ -${hunk.oldStart + 1},${hunk.oldCount} +${hunk.newStart + 1},${hunk.newCount} @@ (+${added}/-${removed}) ${preview}`;
1176
+ }
1177
+
1061
1178
  // src/core/permissions/modes.ts
1062
1179
  var PermissionEngine = class {
1063
1180
  constructor(opts) {
@@ -1075,7 +1192,14 @@ var PermissionEngine = class {
1075
1192
  async authorizeWrite(target, preview, content) {
1076
1193
  const d = checkPath(this.opts.policy, target);
1077
1194
  if (!d.allowed) return { allowed: false, reason: d.reason };
1078
- const findings = scanSecrets(content ?? preview ?? "");
1195
+ let oldContent = "";
1196
+ try {
1197
+ oldContent = await readFile2(resolve2(this.opts.policy.workspace, target), "utf8");
1198
+ } catch {
1199
+ }
1200
+ const hunks = content !== void 0 ? computeHunks(oldContent, content) : [];
1201
+ const added = hunks.flatMap((h) => h.lines.filter((l) => l.type === "+").map((l) => l.text)).join("\n");
1202
+ const findings = scanSecrets(hunks.length > 0 ? added : content ?? preview ?? "");
1079
1203
  if (findings.length > 0) {
1080
1204
  const first = findings[0];
1081
1205
  return {
@@ -1087,8 +1211,13 @@ var PermissionEngine = class {
1087
1211
  return { allowed: false, reason: "plan mode: file modifications are disabled" };
1088
1212
  }
1089
1213
  if (this.opts.mode === "bypass") return { allowed: true };
1090
- const ok = await this.ask({ kind: "write", summary: `write ${d.rel}`, preview });
1091
- return ok ? { allowed: true } : { allowed: false, reason: "rejected by user" };
1214
+ const res = await this.ask({ kind: "write", summary: `write ${d.rel}`, preview, hunks });
1215
+ if (res === true) return { allowed: true };
1216
+ if (res === false) return { allowed: false, reason: "rejected by user" };
1217
+ const approved = new Set(res);
1218
+ if (approved.size === 0) return { allowed: false, reason: "rejected by user" };
1219
+ if (approved.size === hunks.length) return { allowed: true };
1220
+ return { allowed: true, content: applyHunks(oldContent, hunks, approved) };
1092
1221
  }
1093
1222
  async authorizeCommand(command) {
1094
1223
  const screen = screenCommand(command);
@@ -1100,8 +1229,8 @@ var PermissionEngine = class {
1100
1229
  }
1101
1230
  if (this.opts.mode === "bypass") return { allowed: true };
1102
1231
  if (isCommandPreApproved(this.opts.allowedCommands, command)) return { allowed: true };
1103
- const ok = await this.ask({ kind: "command", summary: `run: ${command}` });
1104
- return ok ? { allowed: true } : { allowed: false, reason: "rejected by user" };
1232
+ const res = await this.ask({ kind: "command", summary: `run: ${command}` });
1233
+ return res === true ? { allowed: true } : { allowed: false, reason: "rejected by user" };
1105
1234
  }
1106
1235
  async ask(req) {
1107
1236
  if (!this.opts.confirm) return false;
@@ -1330,8 +1459,8 @@ function makeDriver(kind, tools) {
1330
1459
  }
1331
1460
 
1332
1461
  // src/core/tools/edit-file.ts
1333
- import { readFile as readFile2, writeFile as writeFile2 } from "fs/promises";
1334
- import { resolve as resolve2 } from "path";
1462
+ import { readFile as readFile3, writeFile as writeFile2 } from "fs/promises";
1463
+ import { resolve as resolve3 } from "path";
1335
1464
  import { z as z2 } from "zod";
1336
1465
  var Args = z2.object({
1337
1466
  path: z2.string().min(1),
@@ -1361,10 +1490,10 @@ var editFileTool = {
1361
1490
  output: "edit_file needs three arguments: 'path', 'search' (exact text to find), and 'replace'. Resend the tool call with all three filled in."
1362
1491
  };
1363
1492
  }
1364
- const abs = resolve2(ctx.workspace, args.data.path);
1493
+ const abs = resolve3(ctx.workspace, args.data.path);
1365
1494
  let content;
1366
1495
  try {
1367
- content = await readFile2(abs, "utf8");
1496
+ content = await readFile3(abs, "utf8");
1368
1497
  } catch (err) {
1369
1498
  return { ok: false, output: `Could not read file to edit: ${err.message}` };
1370
1499
  }
@@ -1383,11 +1512,11 @@ var editFileTool = {
1383
1512
  args.data.path,
1384
1513
  `- ${firstLine(args.data.search)}
1385
1514
  + ${firstLine(args.data.replace)}`,
1386
- args.data.replace
1515
+ updated
1387
1516
  );
1388
1517
  if (!decision.allowed) return { ok: false, output: `Edit denied: ${decision.reason}` };
1389
1518
  try {
1390
- await writeFile2(abs, updated, "utf8");
1519
+ await writeFile2(abs, decision.content ?? updated, "utf8");
1391
1520
  return { ok: true, output: `Edited ${args.data.path}.` };
1392
1521
  } catch (err) {
1393
1522
  return { ok: false, output: `Could not write edit: ${err.message}` };
@@ -1400,7 +1529,7 @@ function firstLine(s) {
1400
1529
 
1401
1530
  // src/core/tools/list-dir.ts
1402
1531
  import { readdir } from "fs/promises";
1403
- import { resolve as resolve3 } from "path";
1532
+ import { resolve as resolve4 } from "path";
1404
1533
  import { z as z3 } from "zod";
1405
1534
  var Args2 = z3.object({ path: z3.string().default(".") });
1406
1535
  var listDirTool = {
@@ -1421,7 +1550,7 @@ var listDirTool = {
1421
1550
  return { ok: false, output: `List denied: ${decision.reason}` };
1422
1551
  }
1423
1552
  try {
1424
- const entries = await readdir(resolve3(ctx.workspace, path), { withFileTypes: true });
1553
+ const entries = await readdir(resolve4(ctx.workspace, path), { withFileTypes: true });
1425
1554
  const lines = entries.map((e) => e.isDirectory() ? `${e.name}/` : e.name).sort();
1426
1555
  return { ok: true, output: lines.length ? lines.join("\n") : "(empty)" };
1427
1556
  } catch (err) {
@@ -1431,8 +1560,8 @@ var listDirTool = {
1431
1560
  };
1432
1561
 
1433
1562
  // src/core/tools/read-file.ts
1434
- import { readFile as readFile3 } from "fs/promises";
1435
- import { resolve as resolve4 } from "path";
1563
+ import { readFile as readFile4 } from "fs/promises";
1564
+ import { resolve as resolve5 } from "path";
1436
1565
  import { z as z4 } from "zod";
1437
1566
  var Args3 = z4.object({ path: z4.string().min(1) });
1438
1567
  var MAX_CHARS = 6e4;
@@ -1453,7 +1582,7 @@ var readFileTool = {
1453
1582
  const decision = ctx.permissions.authorizeRead(args.data.path);
1454
1583
  if (!decision.allowed) return { ok: false, output: `Read denied: ${decision.reason}` };
1455
1584
  try {
1456
- const content = await readFile3(resolve4(ctx.workspace, args.data.path), "utf8");
1585
+ const content = await readFile4(resolve5(ctx.workspace, args.data.path), "utf8");
1457
1586
  const truncated = content.length > MAX_CHARS;
1458
1587
  return {
1459
1588
  ok: true,
@@ -1514,8 +1643,8 @@ function clamp(s) {
1514
1643
  }
1515
1644
 
1516
1645
  // src/core/tools/search-file.ts
1517
- import { readdir as readdir2, readFile as readFile4, stat } from "fs/promises";
1518
- import { join as join2, resolve as resolve5 } from "path";
1646
+ import { readdir as readdir2, readFile as readFile5, stat } from "fs/promises";
1647
+ import { join as join2, resolve as resolve6 } from "path";
1519
1648
  import { z as z6 } from "zod";
1520
1649
  var Args5 = z6.object({
1521
1650
  query: z6.string().min(1),
@@ -1567,7 +1696,7 @@ var searchTool = {
1567
1696
  }
1568
1697
  const globRe = glob ? globToRegExp(glob) : void 0;
1569
1698
  const limit = max_results ?? DEFAULT_MAX_RESULTS;
1570
- const root = resolve5(ctx.workspace, path);
1699
+ const root = resolve6(ctx.workspace, path);
1571
1700
  const matches = [];
1572
1701
  let truncated = false;
1573
1702
  const walk = async (dir) => {
@@ -1593,7 +1722,7 @@ var searchTool = {
1593
1722
  try {
1594
1723
  const info = await stat(abs);
1595
1724
  if (info.size > MAX_FILE_BYTES) continue;
1596
- const content = await readFile4(abs, "utf8");
1725
+ const content = await readFile5(abs, "utf8");
1597
1726
  if (content.includes(NUL)) continue;
1598
1727
  const lines = content.split("\n");
1599
1728
  for (let i = 0; i < lines.length; i++) {
@@ -1638,7 +1767,7 @@ var FINISH_TOOL = {
1638
1767
 
1639
1768
  // src/core/tools/write-file.ts
1640
1769
  import { mkdir as mkdir2, writeFile as writeFile3 } from "fs/promises";
1641
- import { dirname, resolve as resolve6 } from "path";
1770
+ import { dirname, resolve as resolve7 } from "path";
1642
1771
  import { z as z7 } from "zod";
1643
1772
  var Args6 = z7.object({ path: z7.string().min(1), content: z7.string() });
1644
1773
  var writeFileTool = {
@@ -1667,11 +1796,12 @@ var writeFileTool = {
1667
1796
  const preview = previewContent(args.data.content);
1668
1797
  const decision = await ctx.permissions.authorizeWrite(args.data.path, preview, args.data.content);
1669
1798
  if (!decision.allowed) return { ok: false, output: `Write denied: ${decision.reason}` };
1799
+ const finalContent = decision.content ?? args.data.content;
1670
1800
  try {
1671
- const abs = resolve6(ctx.workspace, args.data.path);
1801
+ const abs = resolve7(ctx.workspace, args.data.path);
1672
1802
  await mkdir2(dirname(abs), { recursive: true });
1673
- await writeFile3(abs, args.data.content, "utf8");
1674
- const lines = args.data.content.split("\n").length;
1803
+ await writeFile3(abs, finalContent, "utf8");
1804
+ const lines = finalContent.split("\n").length;
1675
1805
  return { ok: true, output: `Wrote ${args.data.path} (${lines} lines).` };
1676
1806
  } catch (err) {
1677
1807
  return { ok: false, output: `Could not write file: ${err.message}` };
@@ -1700,8 +1830,8 @@ function getTool(name) {
1700
1830
  }
1701
1831
 
1702
1832
  // src/core/agent/correction.ts
1703
- import { readFile as readFile5, readdir as readdir3 } from "fs/promises";
1704
- import { dirname as dirname2, resolve as resolve7 } from "path";
1833
+ import { readFile as readFile6, readdir as readdir3 } from "fs/promises";
1834
+ import { dirname as dirname2, resolve as resolve8 } from "path";
1705
1835
  function truncationGuidance(toolName) {
1706
1836
  const fileHint = toolName === "write_file" || toolName === "edit_file" ? " Write large files in parts: create the file with the first chunk via write_file, then append the rest with edit_file in the next steps." : "";
1707
1837
  return [
@@ -1801,7 +1931,7 @@ ${text2}` : null;
1801
1931
  }
1802
1932
  async function readWorkspaceFile(workspace, path) {
1803
1933
  try {
1804
- return await readFile5(resolve7(workspace, path), "utf8");
1934
+ return await readFile6(resolve8(workspace, path), "utf8");
1805
1935
  } catch {
1806
1936
  return null;
1807
1937
  }
@@ -1859,11 +1989,11 @@ async function occurrenceLines(workspace, path, search) {
1859
1989
  return out;
1860
1990
  }
1861
1991
  async function listNearest(workspace, path) {
1862
- let dir = dirname2(resolve7(workspace, path));
1992
+ let dir = dirname2(resolve8(workspace, path));
1863
1993
  for (let i = 0; i < 8; i++) {
1864
1994
  try {
1865
1995
  const entries = await readdir3(dir, { withFileTypes: true });
1866
- const rel = dir === resolve7(workspace) ? "." : dir;
1996
+ const rel = dir === resolve8(workspace) ? "." : dir;
1867
1997
  const names = entries.slice(0, 40).map((e) => e.isDirectory() ? `${e.name}/` : e.name);
1868
1998
  return `${rel}:
1869
1999
  ${names.join(" ") || "(empty)"}`;
@@ -1885,14 +2015,14 @@ function formatSchema(spec) {
1885
2015
  }
1886
2016
 
1887
2017
  // src/core/agent/project-context.ts
1888
- import { readFile as readFile6 } from "fs/promises";
2018
+ import { readFile as readFile7 } from "fs/promises";
1889
2019
  import { join as join3 } from "path";
1890
2020
  var INSTRUCTION_FILES = [join3(".poly", "agents.md"), "AGENTS.md"];
1891
2021
  var MAX_CHARS2 = 8e3;
1892
2022
  async function loadProjectInstructions(workspace) {
1893
2023
  for (const rel of INSTRUCTION_FILES) {
1894
2024
  try {
1895
- const raw = (await readFile6(join3(workspace, rel), "utf8")).trim();
2025
+ const raw = (await readFile7(join3(workspace, rel), "utf8")).trim();
1896
2026
  if (!raw) continue;
1897
2027
  return raw.length > MAX_CHARS2 ? raw.slice(0, MAX_CHARS2) + "\n\u2026(truncated)" : raw;
1898
2028
  } catch {
@@ -2053,8 +2183,8 @@ ${guidance}`;
2053
2183
  }
2054
2184
 
2055
2185
  // src/core/context/mentions.ts
2056
- import { readdir as readdir4, readFile as readFile7, stat as stat2 } from "fs/promises";
2057
- import { resolve as resolve8 } from "path";
2186
+ import { readdir as readdir4, readFile as readFile8, stat as stat2 } from "fs/promises";
2187
+ import { resolve as resolve9 } from "path";
2058
2188
  var MAX_FILE_CHARS = 1e4;
2059
2189
  var MENTION_RE = /(?:^|\s)@([\w./-]+)/g;
2060
2190
  async function resolveMentions(task, policy) {
@@ -2070,7 +2200,7 @@ async function resolveMentions(task, policy) {
2070
2200
  ${t("mentions.notFound", { path: token })}`);
2071
2201
  continue;
2072
2202
  }
2073
- const abs = resolve8(policy.workspace, token);
2203
+ const abs = resolve9(policy.workspace, token);
2074
2204
  try {
2075
2205
  const info = await stat2(abs);
2076
2206
  if (info.isDirectory()) {
@@ -2080,7 +2210,7 @@ ${t("mentions.notFound", { path: token })}`);
2080
2210
  ${listing || "(empty)"}`);
2081
2211
  injected.push(decision.rel);
2082
2212
  } else {
2083
- const raw = await readFile7(abs, "utf8");
2213
+ const raw = await readFile8(abs, "utf8");
2084
2214
  const content = raw.length > MAX_FILE_CHARS ? raw.slice(0, MAX_FILE_CHARS) + "\n\u2026[truncated]" : raw;
2085
2215
  blocks.push(`## @${decision.rel}
2086
2216
  \`\`\`
@@ -2794,10 +2924,10 @@ async function readLineTTY(prompt) {
2794
2924
  stdin.resume();
2795
2925
  stdin.on("data", onData);
2796
2926
  try {
2797
- const line = await new Promise((resolve10) => {
2798
- rl.question(prompt).then(resolve10, () => resolve10(null));
2799
- rl.on("SIGINT", () => resolve10(null));
2800
- rl.on("close", () => resolve10(null));
2927
+ const line = await new Promise((resolve11) => {
2928
+ rl.question(prompt).then(resolve11, () => resolve11(null));
2929
+ rl.on("SIGINT", () => resolve11(null));
2930
+ rl.on("close", () => resolve11(null));
2801
2931
  });
2802
2932
  return line === null ? null : store.expand(line);
2803
2933
  } finally {
@@ -3549,11 +3679,39 @@ function listenForCancel(controller) {
3549
3679
  return { pause: detach, resume: attach, dispose: detach };
3550
3680
  }
3551
3681
  async function confirmAction(req) {
3682
+ if (req.kind === "write" && req.hunks && req.hunks.length > 0) {
3683
+ renderDiff(req.hunks);
3684
+ const options = [
3685
+ { value: "approve", label: t("review.approveAll") },
3686
+ { value: "reject", label: t("review.reject") },
3687
+ ...req.hunks.length > 1 ? [{ value: "hunks", label: t("review.pickHunks") }] : []
3688
+ ];
3689
+ const choice = await p2.select({ message: t("run.confirm", { summary: req.summary }), options });
3690
+ if (p2.isCancel(choice) || choice === "reject") return false;
3691
+ if (choice === "approve") return true;
3692
+ const selected = await p2.multiselect({
3693
+ message: t("review.selectHunks"),
3694
+ options: req.hunks.map((h, i) => ({ value: i, label: hunkLabel(h) })),
3695
+ required: false
3696
+ });
3697
+ if (p2.isCancel(selected)) return false;
3698
+ return selected;
3699
+ }
3552
3700
  if (req.preview) console.log(pc8.dim(req.preview));
3553
3701
  const answer = await p2.confirm({ message: t("run.confirm", { summary: req.summary }) });
3554
3702
  if (p2.isCancel(answer)) return false;
3555
3703
  return answer === true;
3556
3704
  }
3705
+ function renderDiff(hunks) {
3706
+ for (const h of hunks) {
3707
+ console.log(pc8.cyan(`@@ -${h.oldStart + 1},${h.oldCount} +${h.newStart + 1},${h.newCount} @@`));
3708
+ for (const l of h.lines) {
3709
+ if (l.type === "+") console.log(pc8.green(`+${l.text}`));
3710
+ else if (l.type === "-") console.log(pc8.red(`-${l.text}`));
3711
+ else console.log(pc8.dim(` ${l.text}`));
3712
+ }
3713
+ }
3714
+ }
3557
3715
  function renderEvents(spinner3) {
3558
3716
  return {
3559
3717
  onStep() {
@@ -3942,7 +4100,7 @@ async function resolveOpenRouterKey() {
3942
4100
  }
3943
4101
 
3944
4102
  // src/cli/commands/prd.ts
3945
- import { writeFile as writeFile5, readFile as readFile8 } from "fs/promises";
4103
+ import { writeFile as writeFile5, readFile as readFile9 } from "fs/promises";
3946
4104
  import { execFile } from "child_process";
3947
4105
  import { promisify as promisify2 } from "util";
3948
4106
  import pc11 from "picocolors";
@@ -4034,13 +4192,13 @@ async function withRetry(fn, opts = {}) {
4034
4192
 
4035
4193
  // src/cli/commands/cli-io.ts
4036
4194
  import { readFileSync, existsSync as existsSync2 } from "fs";
4037
- import { resolve as resolve9 } from "path";
4195
+ import { resolve as resolve10 } from "path";
4038
4196
  var GUIDE_MAX = 12e3;
4039
4197
  function readProjectGuide(files) {
4040
4198
  const parts = [];
4041
4199
  for (const file of files) {
4042
4200
  try {
4043
- const path = resolve9(process.cwd(), file);
4201
+ const path = resolve10(process.cwd(), file);
4044
4202
  if (existsSync2(path)) parts.push(`# ${file}
4045
4203
  ${readFileSync(path, "utf8").trim()}`);
4046
4204
  } catch {
@@ -4081,7 +4239,7 @@ async function prd(issueRef, opts) {
4081
4239
  }
4082
4240
  async function loadIssue(issueRef, input) {
4083
4241
  if (input) {
4084
- const raw = input === "-" ? await readStdin() : await readFile8(input, "utf8");
4242
+ const raw = input === "-" ? await readStdin() : await readFile9(input, "utf8");
4085
4243
  return normalize2(JSON.parse(stripBom(raw)));
4086
4244
  }
4087
4245
  const num = numericRef(issueRef);
@@ -4100,7 +4258,7 @@ function normalize2(raw) {
4100
4258
  }
4101
4259
 
4102
4260
  // src/cli/commands/review.ts
4103
- import { writeFile as writeFile6, readFile as readFile9 } from "fs/promises";
4261
+ import { writeFile as writeFile6, readFile as readFile10 } from "fs/promises";
4104
4262
  import { execFile as execFile2 } from "child_process";
4105
4263
  import { promisify as promisify3 } from "util";
4106
4264
  import pc12 from "picocolors";
@@ -4176,7 +4334,7 @@ async function review(prRef, opts) {
4176
4334
  }
4177
4335
  }
4178
4336
  async function loadDiff(num, input) {
4179
- if (input) return input === "-" ? readStdin() : readFile9(input, "utf8");
4337
+ if (input) return input === "-" ? readStdin() : readFile10(input, "utf8");
4180
4338
  const { stdout: stdout2 } = await exec3("gh", ["pr", "diff", num]);
4181
4339
  return stdout2;
4182
4340
  }