@xbghc/warden 0.16.1 → 0.16.2
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/cli.js
CHANGED
|
@@ -4066,6 +4066,15 @@ async function listUntracked(cwd) {
|
|
|
4066
4066
|
const r = await runGit(["ls-files", "--others", "--exclude-standard", "-z"], { cwd });
|
|
4067
4067
|
return r.stdout.split("\0").filter(Boolean);
|
|
4068
4068
|
}
|
|
4069
|
+
async function listUnmerged(cwd, file) {
|
|
4070
|
+
const r = await runGit(["ls-files", "--unmerged", "-z", ...file ? ["--", literal(file)] : []], { cwd });
|
|
4071
|
+
return [...new Set(r.stdout.split("\0").flatMap((e) => e.includes(" ") ? [e.slice(e.indexOf(" ") + 1)] : []))];
|
|
4072
|
+
}
|
|
4073
|
+
async function conflictDiff(ctx, file) {
|
|
4074
|
+
const base = await hasHead(ctx.cwd) ? "HEAD" : EMPTY_TREE_SHA;
|
|
4075
|
+
const f = parseUnifiedDiff(await runDiff(ctx, [...DIFF_BASE_ARGS, base, "--", literal(file)])).find((d) => d.path === file);
|
|
4076
|
+
return f ? { ...f, conflicted: true } : void 0;
|
|
4077
|
+
}
|
|
4069
4078
|
async function isUntracked(cwd, file) {
|
|
4070
4079
|
const r = await runGit(["ls-files", "--others", "--exclude-standard", "-z", "--", literal(file)], { cwd });
|
|
4071
4080
|
return r.stdout.split("\0").filter(Boolean).includes(file);
|
|
@@ -4099,6 +4108,15 @@ async function mapLimit(items, limit, fn) {
|
|
|
4099
4108
|
async function listTargetDiffs(ctx) {
|
|
4100
4109
|
const args = await diffArgs(ctx);
|
|
4101
4110
|
const files = parseUnifiedDiff(await runDiff(ctx, [...args, "--"]));
|
|
4111
|
+
if (ctx.target.kind === "working") {
|
|
4112
|
+
for (const path13 of await listUnmerged(ctx.cwd)) {
|
|
4113
|
+
const d = await conflictDiff(ctx, path13);
|
|
4114
|
+
if (!d) continue;
|
|
4115
|
+
const at = files.findIndex((f) => f.path === path13);
|
|
4116
|
+
if (at >= 0) files[at] = d;
|
|
4117
|
+
else files.push(d);
|
|
4118
|
+
}
|
|
4119
|
+
}
|
|
4102
4120
|
if (includesUntracked(ctx.target)) {
|
|
4103
4121
|
const untracked = await listUntracked(ctx.cwd);
|
|
4104
4122
|
const extra = await mapLimit(untracked, 8, (f) => untrackedDiff(ctx.cwd, f).catch(() => void 0));
|
|
@@ -4117,6 +4135,7 @@ async function getFileDiff(ctx, filePath, hints = {}) {
|
|
|
4117
4135
|
if (includesUntracked(ctx.target) && (hints.untracked || await isUntracked(ctx.cwd, filePath))) {
|
|
4118
4136
|
return untrackedDiff(ctx.cwd, filePath);
|
|
4119
4137
|
}
|
|
4138
|
+
if (ctx.target.kind === "working" && (await listUnmerged(ctx.cwd, filePath)).includes(filePath)) return conflictDiff(ctx, filePath);
|
|
4120
4139
|
const args = await diffArgs(ctx);
|
|
4121
4140
|
const pathspec = [literal(filePath)];
|
|
4122
4141
|
if (hints.oldPath && hints.oldPath !== filePath) pathspec.push(literal(hints.oldPath));
|
|
@@ -4263,6 +4282,12 @@ function matchesAt(lines, start, hashes) {
|
|
|
4263
4282
|
}
|
|
4264
4283
|
return true;
|
|
4265
4284
|
}
|
|
4285
|
+
var DISTINCTIVE_CHARS = 24;
|
|
4286
|
+
function distinctive(lines, start, count) {
|
|
4287
|
+
let chars = 0;
|
|
4288
|
+
for (let i = start; i < start + count; i++) chars += lines[i].content.replace(/\s+/g, "").length;
|
|
4289
|
+
return chars >= DISTINCTIVE_CHARS;
|
|
4290
|
+
}
|
|
4266
4291
|
function contextScore(lines, start, count, anchor) {
|
|
4267
4292
|
let score = 0;
|
|
4268
4293
|
const before = anchor.contextBefore;
|
|
@@ -4291,7 +4316,10 @@ function locateAnchor(diff, side, anchor) {
|
|
|
4291
4316
|
if (matchesAt(lines, i, anchor.lineHashes)) candidates.push(i);
|
|
4292
4317
|
}
|
|
4293
4318
|
if (candidates.length === 0) return void 0;
|
|
4294
|
-
if (candidates.length === 1)
|
|
4319
|
+
if (candidates.length === 1) {
|
|
4320
|
+
const c = candidates[0];
|
|
4321
|
+
return contextScore(lines, c, n, anchor) > 0 || distinctive(lines, c, n) ? c : void 0;
|
|
4322
|
+
}
|
|
4295
4323
|
let best = [];
|
|
4296
4324
|
let bestScore = -1;
|
|
4297
4325
|
for (const c of candidates) {
|
|
@@ -4832,9 +4860,16 @@ async function commentsIn(state, worktreeRoot, mainRoot) {
|
|
|
4832
4860
|
return out;
|
|
4833
4861
|
}
|
|
4834
4862
|
async function takeFeedback(store, opts) {
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4863
|
+
const waiting = (await commentsIn(await store.load(), opts.worktreeRoot, opts.mainRoot)).filter(awaitsAgent);
|
|
4864
|
+
if (opts.peek) return waiting;
|
|
4865
|
+
return markHandedOver(
|
|
4866
|
+
store,
|
|
4867
|
+
waiting.map((c) => c.id)
|
|
4868
|
+
);
|
|
4869
|
+
}
|
|
4870
|
+
async function markHandedOver(store, commentIds) {
|
|
4871
|
+
return store.update((s) => {
|
|
4872
|
+
const ids = new Set(commentIds);
|
|
4838
4873
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
4839
4874
|
const taken = [];
|
|
4840
4875
|
for (const t of Object.values(s.targets)) {
|
|
@@ -5465,6 +5500,7 @@ function createApp(opts) {
|
|
|
5465
5500
|
if (body.skipDebug !== void 0 && typeof body.skipDebug !== "boolean") throw badRequest("skipDebug must be a boolean", "bad_selection");
|
|
5466
5501
|
const diff = await fileDiffWithHints(ctx, body.path);
|
|
5467
5502
|
if (!diff) throw badRequest(`file ${body.path} is not part of ${key}`, "no_diff");
|
|
5503
|
+
if (diff.conflicted) throw new HttpError(409, `${body.path} is in conflict; resolve it and git add it in the terminal`, "conflicted");
|
|
5468
5504
|
if (diff.contentHash !== body.contentHash)
|
|
5469
5505
|
throw new HttpError(409, "the diff changed since it was loaded; refresh and pick the lines again", "diff_changed");
|
|
5470
5506
|
if (body.skipDebug) await annotateDebug(ctx, [diff]);
|
|
@@ -5519,20 +5555,20 @@ function createApp(opts) {
|
|
|
5519
5555
|
const body = await c.req.json();
|
|
5520
5556
|
const scope = commentScopeKey(key);
|
|
5521
5557
|
const state = await store.load();
|
|
5522
|
-
const
|
|
5523
|
-
if (!
|
|
5558
|
+
const existing2 = state.targets[scope]?.comments.find((x) => x.id === id);
|
|
5559
|
+
if (!existing2) throw notFound("comment not found");
|
|
5524
5560
|
let patch = {};
|
|
5525
5561
|
if (typeof body.body === "string") {
|
|
5526
5562
|
if (!body.body.trim()) throw badRequest("body must not be empty");
|
|
5527
5563
|
patch.body = body.body;
|
|
5528
5564
|
}
|
|
5529
5565
|
if (body.startLine !== void 0 || body.side !== void 0) {
|
|
5530
|
-
const side = body.side ??
|
|
5531
|
-
const start = Number(body.startLine ??
|
|
5532
|
-
const end = Number(body.endLine ?? body.startLine ??
|
|
5566
|
+
const side = body.side ?? existing2.side;
|
|
5567
|
+
const start = Number(body.startLine ?? existing2.startLine);
|
|
5568
|
+
const end = Number(body.endLine ?? body.startLine ?? existing2.endLine);
|
|
5533
5569
|
if (side !== "old" && side !== "new") throw badRequest("side must be old|new");
|
|
5534
|
-
const diff = await fileDiffWithHints(ctx,
|
|
5535
|
-
if (!diff) throw badRequest(`file ${
|
|
5570
|
+
const diff = await fileDiffWithHints(ctx, existing2.filePath);
|
|
5571
|
+
if (!diff) throw badRequest(`file ${existing2.filePath} is not part of ${key}`, "no_diff");
|
|
5536
5572
|
const anchored = buildAnchor(diff, side, start, end);
|
|
5537
5573
|
if (!anchored) throw badRequest("selection is not fully visible in the diff on that side", "bad_selection");
|
|
5538
5574
|
patch = {
|
|
@@ -5542,7 +5578,7 @@ function createApp(opts) {
|
|
|
5542
5578
|
endLine: anchored.endLine,
|
|
5543
5579
|
codeSnippet: anchored.snippet,
|
|
5544
5580
|
anchor: anchored.anchor,
|
|
5545
|
-
status:
|
|
5581
|
+
status: existing2.exportedAt ? "exported" : "active",
|
|
5546
5582
|
// Re-attaching in a view moves the comment to it.
|
|
5547
5583
|
targetKey: key
|
|
5548
5584
|
};
|
|
@@ -5551,7 +5587,12 @@ function createApp(opts) {
|
|
|
5551
5587
|
const t = ensureTarget(s, scope);
|
|
5552
5588
|
const idx = t.comments.findIndex((x) => x.id === id);
|
|
5553
5589
|
if (idx < 0) throw notFound("comment not found");
|
|
5554
|
-
const
|
|
5590
|
+
const prev = t.comments[idx];
|
|
5591
|
+
const next = { ...prev, ...patch, updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
|
|
5592
|
+
if (patch.body !== void 0 && patch.body !== prev.body && next.exportedAt) {
|
|
5593
|
+
delete next.exportedAt;
|
|
5594
|
+
if (next.status === "exported") next.status = "active";
|
|
5595
|
+
}
|
|
5555
5596
|
t.comments[idx] = next;
|
|
5556
5597
|
return next;
|
|
5557
5598
|
});
|
|
@@ -5678,30 +5719,34 @@ function createApp(opts) {
|
|
|
5678
5719
|
const res = { comments: result };
|
|
5679
5720
|
return c.json(res);
|
|
5680
5721
|
});
|
|
5681
|
-
|
|
5682
|
-
const
|
|
5683
|
-
if (!Array.isArray(body.commentIds)) throw badRequest("commentIds required");
|
|
5684
|
-
const ids = body.commentIds.filter((x) => typeof x === "string");
|
|
5685
|
-
let handed = [];
|
|
5686
|
-
const res = await store.update((s) => {
|
|
5722
|
+
const handOver = async (ids) => {
|
|
5723
|
+
const handed = await store.update((s) => {
|
|
5687
5724
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5688
|
-
const
|
|
5725
|
+
const out = [];
|
|
5689
5726
|
for (const id of ids) {
|
|
5690
5727
|
const found = findComment(s, id);
|
|
5691
5728
|
if (!found) continue;
|
|
5692
5729
|
const next = { ...found.comment, exportedAt: now, updatedAt: now };
|
|
5693
5730
|
if (next.status === "active") next.status = "exported";
|
|
5694
5731
|
s.targets[found.targetKey].comments[found.index] = next;
|
|
5695
|
-
|
|
5732
|
+
out.push(next);
|
|
5696
5733
|
}
|
|
5697
|
-
|
|
5698
|
-
return {
|
|
5699
|
-
text: formatCommentsExport({ repoRoot: repo.root, comments: selected, replyCommand: opts.replyCommand }),
|
|
5700
|
-
count: selected.length,
|
|
5701
|
-
commentIds: selected.map((x) => x.id)
|
|
5702
|
-
};
|
|
5734
|
+
return out;
|
|
5703
5735
|
});
|
|
5704
5736
|
checkpointHandoff(handed);
|
|
5737
|
+
return handed;
|
|
5738
|
+
};
|
|
5739
|
+
const existing = (state, ids) => ids.flatMap((id) => findComment(state, id)?.comment ?? []);
|
|
5740
|
+
api.post("/comments/export", async (c) => {
|
|
5741
|
+
const body = await c.req.json();
|
|
5742
|
+
if (!Array.isArray(body.commentIds)) throw badRequest("commentIds required");
|
|
5743
|
+
const ids = body.commentIds.filter((x) => typeof x === "string");
|
|
5744
|
+
const comments = body.preview ? existing(await store.load(), ids) : await handOver(ids);
|
|
5745
|
+
const res = {
|
|
5746
|
+
text: formatCommentsExport({ repoRoot: repo.root, comments, replyCommand: opts.replyCommand }),
|
|
5747
|
+
count: comments.length,
|
|
5748
|
+
commentIds: comments.map((x) => x.id)
|
|
5749
|
+
};
|
|
5705
5750
|
return c.json(res);
|
|
5706
5751
|
});
|
|
5707
5752
|
const knownRoot = async (rootParam) => {
|
|
@@ -5776,26 +5821,17 @@ function createApp(opts) {
|
|
|
5776
5821
|
});
|
|
5777
5822
|
api.post("/todos/:id/export", async (c) => {
|
|
5778
5823
|
const id = c.req.param("id");
|
|
5779
|
-
const
|
|
5780
|
-
const
|
|
5781
|
-
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
handed.push(next);
|
|
5791
|
-
}
|
|
5792
|
-
return {
|
|
5793
|
-
text: formatTodoExport({ todo, comments: handed, replyCommand: opts.replyCommand }),
|
|
5794
|
-
count: handed.length,
|
|
5795
|
-
commentIds: handed.map((x) => x.id)
|
|
5796
|
-
};
|
|
5797
|
-
});
|
|
5798
|
-
checkpointHandoff(handed);
|
|
5824
|
+
const body = await c.req.json().catch(() => ({})) ?? {};
|
|
5825
|
+
const state = await store.load();
|
|
5826
|
+
const todo = state.todos.find((t) => t.id === id);
|
|
5827
|
+
if (!todo) throw notFound("todo not found");
|
|
5828
|
+
const ids = todo.commentIds ?? [];
|
|
5829
|
+
const comments = body.preview ? existing(state, ids) : await handOver(ids);
|
|
5830
|
+
const res = {
|
|
5831
|
+
text: formatTodoExport({ todo, comments, replyCommand: opts.replyCommand }),
|
|
5832
|
+
count: comments.length,
|
|
5833
|
+
commentIds: comments.map((x) => x.id)
|
|
5834
|
+
};
|
|
5799
5835
|
return c.json(res);
|
|
5800
5836
|
});
|
|
5801
5837
|
api.delete("/todos/:id", async (c) => {
|
|
@@ -6233,18 +6269,21 @@ async function feedback(args, replyCommand) {
|
|
|
6233
6269
|
const unknown = args.find((a) => a !== "--peek");
|
|
6234
6270
|
if (unknown) throw new Error(`unknown argument: ${unknown}`);
|
|
6235
6271
|
const { store, root, commonRoot } = await openStore();
|
|
6236
|
-
const
|
|
6237
|
-
const comments = await takeFeedback(store, { worktreeRoot: root, mainRoot: commonRoot, peek });
|
|
6272
|
+
const comments = await takeFeedback(store, { worktreeRoot: root, mainRoot: commonRoot, peek: true });
|
|
6238
6273
|
if (comments.length === 0) {
|
|
6239
6274
|
console.log("No review comments are waiting for you.");
|
|
6240
6275
|
return;
|
|
6241
6276
|
}
|
|
6242
|
-
|
|
6243
|
-
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
|
|
6277
|
+
const text = formatCommentsExport({ repoRoot: root, comments, replyCommand });
|
|
6278
|
+
await new Promise((resolve, reject) => process.stdout.write(text, (e) => e ? reject(e) : resolve()));
|
|
6279
|
+
if (args.includes("--peek")) return;
|
|
6280
|
+
await markHandedOver(
|
|
6281
|
+
store,
|
|
6282
|
+
comments.map((c) => c.id)
|
|
6283
|
+
);
|
|
6284
|
+
await takeCheckpoint(store, root, root === commonRoot ? void 0 : root, { handoff: true }).catch((e) => {
|
|
6285
|
+
console.error(`warden feedback: no checkpoint taken: ${e instanceof Error ? e.message : e}`);
|
|
6286
|
+
});
|
|
6248
6287
|
}
|
|
6249
6288
|
async function reply(args) {
|
|
6250
6289
|
const [id, ...words] = args;
|
|
@@ -6271,7 +6310,7 @@ async function runAgentCommand(argv, replyCommand) {
|
|
|
6271
6310
|
}
|
|
6272
6311
|
|
|
6273
6312
|
// bin/cli.ts
|
|
6274
|
-
var VERSION = true ? "0.16.
|
|
6313
|
+
var VERSION = true ? "0.16.2" : "dev";
|
|
6275
6314
|
function parseArgs(argv) {
|
|
6276
6315
|
const args = { repoPath: process.cwd(), open: true, updateCheck: true, help: false, version: false };
|
|
6277
6316
|
for (let i = 0; i < argv.length; i++) {
|