@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 +203 -45
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
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
|
|
1091
|
-
|
|
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
|
|
1104
|
-
return
|
|
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
|
|
1334
|
-
import { resolve as
|
|
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 =
|
|
1493
|
+
const abs = resolve3(ctx.workspace, args.data.path);
|
|
1365
1494
|
let content;
|
|
1366
1495
|
try {
|
|
1367
|
-
content = await
|
|
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
|
-
|
|
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
|
|
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(
|
|
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
|
|
1435
|
-
import { resolve as
|
|
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
|
|
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
|
|
1518
|
-
import { join as join2, resolve as
|
|
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 =
|
|
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
|
|
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
|
|
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 =
|
|
1801
|
+
const abs = resolve7(ctx.workspace, args.data.path);
|
|
1672
1802
|
await mkdir2(dirname(abs), { recursive: true });
|
|
1673
|
-
await writeFile3(abs,
|
|
1674
|
-
const lines =
|
|
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
|
|
1704
|
-
import { dirname as dirname2, resolve as
|
|
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
|
|
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(
|
|
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 ===
|
|
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
|
|
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
|
|
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
|
|
2057
|
-
import { resolve as
|
|
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 =
|
|
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
|
|
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((
|
|
2798
|
-
rl.question(prompt).then(
|
|
2799
|
-
rl.on("SIGINT", () =>
|
|
2800
|
-
rl.on("close", () =>
|
|
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
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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() :
|
|
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
|
}
|