@xbghc/warden 0.5.0 → 0.6.1
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/README.md +24 -16
- package/dist/cli.js +46 -55
- package/dist/web/assets/index-DxPObMgS.js +197 -0
- package/dist/web/assets/index-pyKaNQqX.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +1 -1
- package/dist/web/assets/index-BRm8bZtC.css +0 -1
- package/dist/web/assets/index-CZMo0jXP.js +0 -194
package/README.md
CHANGED
|
@@ -14,7 +14,7 @@ with line comments you can copy back to the agent as a prompt.
|
|
|
14
14
|
- Comments follow the code: they move with a hunk that gets staged, re-attach after the agent edits the file, and are cleaned up once the change is committed.
|
|
15
15
|
- Auto-refresh — warden watches the repository and reloads itself when you edit, stage, commit or switch branches.
|
|
16
16
|
- Local issues (title, Markdown body, open/closed) that link comments.
|
|
17
|
-
- Branch-scoped todos,
|
|
17
|
+
- Branch-scoped todos, each copied to the clipboard on its own — one task at a time for the agent.
|
|
18
18
|
- Commit history browser: grouped by day, branch / tag labels, search by message, sha, author or
|
|
19
19
|
path (each a `git log` on the server, not a filter over the rows already loaded), and a
|
|
20
20
|
first-parent view that folds merged branches into their merge commits.
|
|
@@ -57,19 +57,30 @@ Several instances on the same repository can run at the same time.
|
|
|
57
57
|
|
|
58
58
|
Refs accept anything git can resolve (`main`, `v1.2`, `HEAD~3`, a sha). `@` is `HEAD`.
|
|
59
59
|
Worktrees are discovered with `git worktree list` and share the review state of the main repository.
|
|
60
|
+
One whose directory is gone (git lists it as *prunable*) is not offered, and a remembered target
|
|
61
|
+
inside a removed worktree falls back to the working tree on the next load. The comments and viewed
|
|
62
|
+
flags kept under that worktree's key stay in the state file and come back if a worktree is created
|
|
63
|
+
at the same path again.
|
|
64
|
+
|
|
65
|
+
There is no target switcher in the UI: the sidebar's two blocks *are* the working tree, a commit is
|
|
66
|
+
picked from the *Commits* panel, and the *Branch vs* and *Range* fields at the top of that panel open
|
|
67
|
+
the other two. The sidebar names whatever is under review in its block header and puts a
|
|
68
|
+
*返回工作区* link above it.
|
|
60
69
|
|
|
61
70
|
`working`, `staged` and `all` are the three **local views** of one worktree. While any of them is
|
|
62
71
|
selected the sidebar shows two blocks — Unstaged (`working`) and Staged (`staged`) — instead of a
|
|
63
72
|
single tree, and clicking a file switches to the view it belongs to. A file that is only partly
|
|
64
73
|
staged appears in both. Switching between the three keeps your comments, the draft you are typing
|
|
65
|
-
and the current selection; only the diff is reloaded.
|
|
74
|
+
and the current selection; only the diff is reloaded. `all` has no control of its own (a saved
|
|
75
|
+
`lastTarget` can still restore it; *Branch vs* `@` shows the same diff). Commit, range and `base`
|
|
76
|
+
targets keep the single tree.
|
|
66
77
|
|
|
67
78
|
`base:<ref>` is for a branch a coding agent has been working on, committing as it goes: one tree with
|
|
68
79
|
everything since the branch forked off `<ref>` — the commits plus whatever is still uncommitted or
|
|
69
80
|
untracked. The diff runs against the merge base, so commits that landed on `<ref>` after the fork are
|
|
70
|
-
not listed as reverted (which is what a plain `git diff <ref>` would do). The *Branch*
|
|
71
|
-
base for you — the main worktree's branch when a sibling worktree is under
|
|
72
|
-
`master` — and
|
|
81
|
+
not listed as reverted (which is what a plain `git diff <ref>` would do). The *Branch vs* field in the
|
|
82
|
+
Commits panel suggests the base for you — the main worktree's branch when a sibling worktree is under
|
|
83
|
+
review, otherwise `main` or `master` — and takes any ref. Unlike the three local views, `base` keeps its own
|
|
73
84
|
pool of comments and a commit never deletes them: the round under review is not over when the agent
|
|
74
85
|
commits, so a comment whose lines changed stays *orphaned*, snippet and all, until you have checked
|
|
75
86
|
the fix and delete or re-attach it.
|
|
@@ -147,8 +158,8 @@ bound to a hash of the file's diff; when the diff changes the flag is dropped an
|
|
|
147
158
|
|
|
148
159
|
## Auto-refresh
|
|
149
160
|
|
|
150
|
-
warden polls the worktree every 1.5 s while the page is open (`git status --porcelain
|
|
151
|
-
branch name, plus mtime and size of the paths status reports — enough to notice a second edit to an
|
|
161
|
+
warden polls the worktree every 1.5 s while the page is open (`git status --porcelain
|
|
162
|
+
--untracked-files=all`, HEAD, the branch name, plus mtime and size of the paths status reports — enough to notice a second edit to an
|
|
152
163
|
already-modified file) and pushes a change event over `GET /api/events` (SSE). The page then re-runs
|
|
153
164
|
the normal refresh: re-anchor, reload both file lists, reload the open diff. Your draft comment,
|
|
154
165
|
selection, current view and current file are left alone, and the diff is scrolled back to where you
|
|
@@ -161,19 +172,16 @@ Notes attached to a branch rather than to a line of code, for the things you not
|
|
|
161
172
|
that do not belong in a comment. They are not deleted when you commit. They sit in the right-hand
|
|
162
173
|
rail next to the comments, under the *Todo* tab: type a title and press Enter to add one, expand a
|
|
163
174
|
card to read or edit its Markdown body, filter by status, and switch between the current branch
|
|
164
|
-
and all branches
|
|
165
|
-
|
|
175
|
+
and all branches.
|
|
176
|
+
|
|
177
|
+
Each card has its own *复制* button, and there is deliberately no "copy all": a todo is one task to
|
|
178
|
+
hand to an agent, and the agent it goes to is already working in that branch. So the clipboard gets
|
|
179
|
+
the title and the body — no branch, repo, count or numbering:
|
|
166
180
|
|
|
167
181
|
```markdown
|
|
168
|
-
|
|
169
|
-
Branch: feature/x
|
|
170
|
-
Repo: /home/user/project
|
|
171
|
-
Count: 2
|
|
182
|
+
补充 OrderList 的空状态
|
|
172
183
|
|
|
173
|
-
## 1. 补充 OrderList 的空状态
|
|
174
184
|
描述正文(Markdown)…
|
|
175
|
-
|
|
176
|
-
## 2. useOrder 的依赖数组缺少 id
|
|
177
185
|
```
|
|
178
186
|
|
|
179
187
|
## nvim integration
|
package/dist/cli.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// bin/cli.ts
|
|
4
4
|
import path8 from "path";
|
|
5
5
|
import { spawn } from "child_process";
|
|
6
|
-
import { existsSync } from "fs";
|
|
6
|
+
import { existsSync as existsSync2 } from "fs";
|
|
7
7
|
import { fileURLToPath } from "url";
|
|
8
8
|
|
|
9
9
|
// packages/server/src/index.ts
|
|
@@ -3025,6 +3025,7 @@ function parseCommitLog(stdout) {
|
|
|
3025
3025
|
|
|
3026
3026
|
// packages/server/src/git.ts
|
|
3027
3027
|
import { execFile } from "child_process";
|
|
3028
|
+
import { existsSync } from "fs";
|
|
3028
3029
|
var ALLOWED_SUBCOMMANDS = /* @__PURE__ */ new Set(["rev-parse", "diff", "show", "log", "worktree", "ls-files", "status", "merge-base"]);
|
|
3029
3030
|
var FORBIDDEN_OPTION_PREFIXES = ["--output", "--ext-diff", "--textconv", "-c", "--config-env", "--exec-path", "--git-dir", "--work-tree"];
|
|
3030
3031
|
var DEFAULT_GIT_TIMEOUT_MS = 3e4;
|
|
@@ -3087,6 +3088,10 @@ async function runGit(args, opts) {
|
|
|
3087
3088
|
return;
|
|
3088
3089
|
}
|
|
3089
3090
|
if (err.code === "ENOENT") {
|
|
3091
|
+
if (opts.cwd && !existsSync(opts.cwd)) {
|
|
3092
|
+
reject(new GitError(`working directory no longer exists: ${opts.cwd}`, null, "", 400, "unknown_worktree"));
|
|
3093
|
+
return;
|
|
3094
|
+
}
|
|
3090
3095
|
reject(new GitError("git executable not found in PATH", null, "", 500, "git_missing"));
|
|
3091
3096
|
return;
|
|
3092
3097
|
}
|
|
@@ -3159,7 +3164,7 @@ async function listWorktrees(ctx) {
|
|
|
3159
3164
|
const out = [];
|
|
3160
3165
|
let cur = null;
|
|
3161
3166
|
const flush = () => {
|
|
3162
|
-
if (cur && cur.path) {
|
|
3167
|
+
if (cur && cur.path && !cur.prunable) {
|
|
3163
3168
|
out.push({
|
|
3164
3169
|
path: cur.path,
|
|
3165
3170
|
head: cur.head ?? "",
|
|
@@ -3185,6 +3190,7 @@ async function listWorktrees(ctx) {
|
|
|
3185
3190
|
else if (line.startsWith("branch ")) cur.branch = line.slice(7).replace(/^refs\/heads\//, "");
|
|
3186
3191
|
else if (line === "detached") cur.detached = true;
|
|
3187
3192
|
else if (line === "bare") cur.bare = true;
|
|
3193
|
+
else if (line.startsWith("prunable")) cur.prunable = true;
|
|
3188
3194
|
}
|
|
3189
3195
|
flush();
|
|
3190
3196
|
for (const wt of out) {
|
|
@@ -3387,11 +3393,11 @@ function parseUnifiedDiff(text) {
|
|
|
3387
3393
|
continue;
|
|
3388
3394
|
}
|
|
3389
3395
|
if (line.startsWith("--- ")) {
|
|
3390
|
-
c.fromMinus = stripPrefix(line.slice(4), "a/");
|
|
3396
|
+
c.fromMinus = stripPrefix(line.slice(4).replace(/\t$/, ""), "a/");
|
|
3391
3397
|
continue;
|
|
3392
3398
|
}
|
|
3393
3399
|
if (line.startsWith("+++ ")) {
|
|
3394
|
-
c.fromPlus = stripPrefix(line.slice(4), "b/");
|
|
3400
|
+
c.fromPlus = stripPrefix(line.slice(4).replace(/\t$/, ""), "b/");
|
|
3395
3401
|
continue;
|
|
3396
3402
|
}
|
|
3397
3403
|
if (line.startsWith("new file mode ")) {
|
|
@@ -3464,6 +3470,10 @@ function resolveTargetContext(repo, worktrees, key) {
|
|
|
3464
3470
|
}
|
|
3465
3471
|
return { key, target, cwd };
|
|
3466
3472
|
}
|
|
3473
|
+
function assertRepoPath(p) {
|
|
3474
|
+
if (!p || p.startsWith("/") || p.split("/").includes("..")) throw badRequest(`invalid path: ${p}`, "invalid_path");
|
|
3475
|
+
}
|
|
3476
|
+
var literal = (p) => `:(literal)${p}`;
|
|
3467
3477
|
async function hasHead(cwd) {
|
|
3468
3478
|
return refExists(cwd, "HEAD");
|
|
3469
3479
|
}
|
|
@@ -3491,6 +3501,7 @@ async function diffArgs(ctx) {
|
|
|
3491
3501
|
case "range": {
|
|
3492
3502
|
if (!await refExists(ctx.cwd, t.base)) throw badRequest(`unknown ref: ${t.base}`, "unknown_ref");
|
|
3493
3503
|
if (!await refExists(ctx.cwd, t.head)) throw badRequest(`unknown ref: ${t.head}`, "unknown_ref");
|
|
3504
|
+
if (!await mergeBase(ctx.cwd, t.base, t.head)) throw badRequest(`${t.base} and ${t.head} share no history`, "no_merge_base");
|
|
3494
3505
|
return [...DIFF_BASE_ARGS, `${t.base}...${t.head}`];
|
|
3495
3506
|
}
|
|
3496
3507
|
case "base":
|
|
@@ -3505,7 +3516,7 @@ async function listUntracked(cwd) {
|
|
|
3505
3516
|
return r.stdout.split("\0").filter(Boolean);
|
|
3506
3517
|
}
|
|
3507
3518
|
async function isUntracked(cwd, file) {
|
|
3508
|
-
const r = await runGit(["ls-files", "--others", "--exclude-standard", "-z", "--", file], { cwd });
|
|
3519
|
+
const r = await runGit(["ls-files", "--others", "--exclude-standard", "-z", "--", literal(file)], { cwd });
|
|
3509
3520
|
return r.stdout.split("\0").filter(Boolean).includes(file);
|
|
3510
3521
|
}
|
|
3511
3522
|
async function untrackedDiff(cwd, file) {
|
|
@@ -3551,15 +3562,14 @@ function toSummary(f) {
|
|
|
3551
3562
|
return rest;
|
|
3552
3563
|
}
|
|
3553
3564
|
async function getFileDiff(ctx, filePath, hints = {}) {
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
}
|
|
3565
|
+
assertRepoPath(filePath);
|
|
3566
|
+
if (hints.oldPath) assertRepoPath(hints.oldPath);
|
|
3557
3567
|
if (includesUntracked(ctx.target) && (hints.untracked || await isUntracked(ctx.cwd, filePath))) {
|
|
3558
3568
|
return untrackedDiff(ctx.cwd, filePath);
|
|
3559
3569
|
}
|
|
3560
3570
|
const args = await diffArgs(ctx);
|
|
3561
|
-
const pathspec = [filePath];
|
|
3562
|
-
if (hints.oldPath && hints.oldPath !== filePath) pathspec.push(hints.oldPath);
|
|
3571
|
+
const pathspec = [literal(filePath)];
|
|
3572
|
+
if (hints.oldPath && hints.oldPath !== filePath) pathspec.push(literal(hints.oldPath));
|
|
3563
3573
|
const r = await runGit([...args, "--", ...pathspec], { cwd: ctx.cwd });
|
|
3564
3574
|
const files = parseUnifiedDiff(r.stdout);
|
|
3565
3575
|
return files.find((f) => f.path === filePath) ?? files[0];
|
|
@@ -3583,9 +3593,7 @@ async function refForSide(ctx, side) {
|
|
|
3583
3593
|
}
|
|
3584
3594
|
}
|
|
3585
3595
|
async function getFullFile(ctx, filePath, side) {
|
|
3586
|
-
|
|
3587
|
-
throw badRequest(`invalid path: ${filePath}`, "invalid_path");
|
|
3588
|
-
}
|
|
3596
|
+
assertRepoPath(filePath);
|
|
3589
3597
|
const ref = await refForSide(ctx, side);
|
|
3590
3598
|
if (ref === void 0) {
|
|
3591
3599
|
try {
|
|
@@ -3751,6 +3759,8 @@ function ensureTarget(state, key) {
|
|
|
3751
3759
|
}
|
|
3752
3760
|
return t;
|
|
3753
3761
|
}
|
|
3762
|
+
var usable = (v) => !!v && typeof v === "object" && typeof v.id === "string";
|
|
3763
|
+
var usableComment = (v) => usable(v) && typeof v.anchor === "object" && !!v.anchor;
|
|
3754
3764
|
function normalise(raw2, repoRoot) {
|
|
3755
3765
|
const base = defaultState(repoRoot);
|
|
3756
3766
|
if (!raw2 || typeof raw2 !== "object") return base;
|
|
@@ -3761,7 +3771,7 @@ function normalise(raw2, repoRoot) {
|
|
|
3761
3771
|
if (!v || typeof v !== "object") continue;
|
|
3762
3772
|
targets[k] = {
|
|
3763
3773
|
viewed: typeof v.viewed === "object" && v.viewed ? v.viewed : {},
|
|
3764
|
-
comments: Array.isArray(v.comments) ? v.comments : [],
|
|
3774
|
+
comments: Array.isArray(v.comments) ? v.comments.filter(usableComment) : [],
|
|
3765
3775
|
...typeof v.head === "string" ? { head: v.head } : {}
|
|
3766
3776
|
};
|
|
3767
3777
|
}
|
|
@@ -3770,8 +3780,8 @@ function normalise(raw2, repoRoot) {
|
|
|
3770
3780
|
schemaVersion: 1,
|
|
3771
3781
|
repoRoot: r.repoRoot ?? repoRoot,
|
|
3772
3782
|
targets,
|
|
3773
|
-
issues: Array.isArray(r.issues) ? r.issues : [],
|
|
3774
|
-
todos: Array.isArray(r.todos) ? r.todos : [],
|
|
3783
|
+
issues: Array.isArray(r.issues) ? r.issues.filter(usable) : [],
|
|
3784
|
+
todos: Array.isArray(r.todos) ? r.todos.filter(usable) : [],
|
|
3775
3785
|
prefs: {
|
|
3776
3786
|
...defaultPrefs(),
|
|
3777
3787
|
...r.prefs ?? {},
|
|
@@ -3960,16 +3970,6 @@ function formatIssueExport({ repoRoot, issue, comments }) {
|
|
|
3960
3970
|
parts.push(...comments.map(formatCommentSection));
|
|
3961
3971
|
return parts.join("\n\n") + "\n";
|
|
3962
3972
|
}
|
|
3963
|
-
function formatTodosExport({ repoRoot, branch, todos }) {
|
|
3964
|
-
const head = ["# TODO", `Branch: ${branch}`, `Repo: ${repoRoot}`, `Count: ${todos.length}`];
|
|
3965
|
-
const sections = todos.map((t, i) => {
|
|
3966
|
-
const title = `## ${i + 1}. ${t.title}${t.status === "done" ? " (done)" : ""}`;
|
|
3967
|
-
const body = t.body.replace(/\r\n/g, "\n").trim();
|
|
3968
|
-
return body ? `${title}
|
|
3969
|
-
${body}` : title;
|
|
3970
|
-
});
|
|
3971
|
-
return [head.join("\n"), ...sections].join("\n\n") + "\n";
|
|
3972
|
-
}
|
|
3973
3973
|
|
|
3974
3974
|
// packages/server/src/nvim.ts
|
|
3975
3975
|
import os2 from "os";
|
|
@@ -4176,7 +4176,9 @@ var RepoWatcher = class {
|
|
|
4176
4176
|
}
|
|
4177
4177
|
async snapshot() {
|
|
4178
4178
|
const [statusRes, headRes, branch] = await Promise.all([
|
|
4179
|
-
|
|
4179
|
+
// Untracked files one by one: folded into their directory, an edit inside a directory the
|
|
4180
|
+
// agent just created would leave the directory's own mtime and size untouched.
|
|
4181
|
+
runGit(["status", "--porcelain", "-z", "--untracked-files=all"], { cwd: this.root }),
|
|
4180
4182
|
runGit(["rev-parse", "HEAD"], { cwd: this.root }).catch(() => ({ stdout: "" })),
|
|
4181
4183
|
// The same label getRepoInfo reports, so the branch identity the client filters todos by
|
|
4182
4184
|
// never flips between `HEAD` and a short sha depending on which of the two spoke last.
|
|
@@ -4285,13 +4287,6 @@ function createApp(opts) {
|
|
|
4285
4287
|
throw e;
|
|
4286
4288
|
}
|
|
4287
4289
|
};
|
|
4288
|
-
const decodeKey = (raw2) => {
|
|
4289
|
-
try {
|
|
4290
|
-
return decodeURIComponent(raw2);
|
|
4291
|
-
} catch {
|
|
4292
|
-
throw badRequest("malformed target key", "invalid_target");
|
|
4293
|
-
}
|
|
4294
|
-
};
|
|
4295
4290
|
const fileDiffWithHints = async (ctx, filePath, explicit) => {
|
|
4296
4291
|
const cached = listings.get(ctx.key);
|
|
4297
4292
|
const hint = cached && Date.now() - cached.at < LISTING_HINT_TTL_MS ? cached.files.find((f) => f.path === filePath) : void 0;
|
|
@@ -4318,8 +4313,13 @@ function createApp(opts) {
|
|
|
4318
4313
|
api.get("/ping", (c) => c.text(opts.instanceToken ?? ""));
|
|
4319
4314
|
api.get("/repo", async (c) => {
|
|
4320
4315
|
const state = await store.load();
|
|
4321
|
-
const info = await getRepoInfo(repo,
|
|
4316
|
+
const info = await getRepoInfo(repo, "working");
|
|
4322
4317
|
worktreeCache = { at: Date.now(), list: info.worktrees };
|
|
4318
|
+
const last = state.prefs.lastTarget;
|
|
4319
|
+
const target = last ? tryParseTargetKey(last) : void 0;
|
|
4320
|
+
const usable2 = !!target && (!target.worktree || info.worktrees.some((w) => w.path === target.worktree));
|
|
4321
|
+
if (last && usable2) info.defaultTarget = last;
|
|
4322
|
+
else if (last) await store.update((s) => void (s.prefs.lastTarget = "working"));
|
|
4323
4323
|
return c.json(info);
|
|
4324
4324
|
});
|
|
4325
4325
|
api.get("/state", async (c) => c.json(await store.load()));
|
|
@@ -4337,7 +4337,7 @@ function createApp(opts) {
|
|
|
4337
4337
|
return c.json(prefs);
|
|
4338
4338
|
});
|
|
4339
4339
|
api.get("/targets/:key/files", async (c) => {
|
|
4340
|
-
const key =
|
|
4340
|
+
const key = c.req.param("key");
|
|
4341
4341
|
const ctx = await targetCtx(key);
|
|
4342
4342
|
const diffs = await listTargetDiffs(ctx);
|
|
4343
4343
|
listings.set(key, { at: Date.now(), files: diffs });
|
|
@@ -4373,7 +4373,7 @@ function createApp(opts) {
|
|
|
4373
4373
|
return c.json(res);
|
|
4374
4374
|
});
|
|
4375
4375
|
api.get("/targets/:key/file", async (c) => {
|
|
4376
|
-
const key =
|
|
4376
|
+
const key = c.req.param("key");
|
|
4377
4377
|
const filePath = c.req.query("path");
|
|
4378
4378
|
if (!filePath) throw badRequest("missing path");
|
|
4379
4379
|
const ctx = await targetCtx(key);
|
|
@@ -4384,7 +4384,7 @@ function createApp(opts) {
|
|
|
4384
4384
|
return c.json(diff);
|
|
4385
4385
|
});
|
|
4386
4386
|
api.get("/targets/:key/file/full", async (c) => {
|
|
4387
|
-
const key =
|
|
4387
|
+
const key = c.req.param("key");
|
|
4388
4388
|
const filePath = c.req.query("path");
|
|
4389
4389
|
if (!filePath) throw badRequest("missing path");
|
|
4390
4390
|
const side = c.req.query("side") === "old" ? "old" : "new";
|
|
@@ -4394,7 +4394,7 @@ function createApp(opts) {
|
|
|
4394
4394
|
return c.json(res);
|
|
4395
4395
|
});
|
|
4396
4396
|
api.put("/targets/:key/viewed", async (c) => {
|
|
4397
|
-
const key =
|
|
4397
|
+
const key = c.req.param("key");
|
|
4398
4398
|
await targetCtx(key);
|
|
4399
4399
|
const body = await c.req.json();
|
|
4400
4400
|
if (!body.path) throw badRequest("missing path");
|
|
@@ -4408,12 +4408,12 @@ function createApp(opts) {
|
|
|
4408
4408
|
return c.json({ viewed });
|
|
4409
4409
|
});
|
|
4410
4410
|
api.get("/targets/:key/comments", async (c) => {
|
|
4411
|
-
const key =
|
|
4411
|
+
const key = c.req.param("key");
|
|
4412
4412
|
const state = await store.load();
|
|
4413
4413
|
return c.json({ comments: state.targets[commentScopeKey(key)]?.comments ?? [] });
|
|
4414
4414
|
});
|
|
4415
4415
|
api.post("/targets/:key/comments", async (c) => {
|
|
4416
|
-
const key =
|
|
4416
|
+
const key = c.req.param("key");
|
|
4417
4417
|
const ctx = await targetCtx(key);
|
|
4418
4418
|
const body = await c.req.json();
|
|
4419
4419
|
if (!body.filePath || typeof body.body !== "string" || !body.body.trim()) throw badRequest("filePath and body are required");
|
|
@@ -4446,7 +4446,7 @@ function createApp(opts) {
|
|
|
4446
4446
|
return c.json(comment, 201);
|
|
4447
4447
|
});
|
|
4448
4448
|
api.patch("/targets/:key/comments/:id", async (c) => {
|
|
4449
|
-
const key =
|
|
4449
|
+
const key = c.req.param("key");
|
|
4450
4450
|
const id = c.req.param("id");
|
|
4451
4451
|
const ctx = await targetCtx(key);
|
|
4452
4452
|
const body = await c.req.json();
|
|
@@ -4491,7 +4491,7 @@ function createApp(opts) {
|
|
|
4491
4491
|
return c.json(updated);
|
|
4492
4492
|
});
|
|
4493
4493
|
api.delete("/targets/:key/comments/:id", async (c) => {
|
|
4494
|
-
const key =
|
|
4494
|
+
const key = c.req.param("key");
|
|
4495
4495
|
const id = c.req.param("id");
|
|
4496
4496
|
const removed = await store.update((s) => {
|
|
4497
4497
|
const t = s.targets[commentScopeKey(key)];
|
|
@@ -4505,7 +4505,7 @@ function createApp(opts) {
|
|
|
4505
4505
|
return c.json({ ok: true });
|
|
4506
4506
|
});
|
|
4507
4507
|
api.post("/targets/:key/comments/reanchor", async (c) => {
|
|
4508
|
-
const key =
|
|
4508
|
+
const key = c.req.param("key");
|
|
4509
4509
|
const ctx = await targetCtx(key);
|
|
4510
4510
|
const scope = commentScopeKey(key);
|
|
4511
4511
|
const local = isLocalTarget(ctx.target);
|
|
@@ -4721,15 +4721,6 @@ function createApp(opts) {
|
|
|
4721
4721
|
});
|
|
4722
4722
|
return c.json(todo, 201);
|
|
4723
4723
|
});
|
|
4724
|
-
api.post("/todos/export", async (c) => {
|
|
4725
|
-
const body = await c.req.json();
|
|
4726
|
-
const branch = typeof body.branch === "string" ? body.branch.trim() : "";
|
|
4727
|
-
if (!branch) throw badRequest("branch is required");
|
|
4728
|
-
const s = await store.load();
|
|
4729
|
-
const todos = s.todos.filter((t) => t.branch === branch && (body.includeDone === true || t.status === "open"));
|
|
4730
|
-
const res = { text: formatTodosExport({ repoRoot: repo.root, branch, todos }), count: todos.length };
|
|
4731
|
-
return c.json(res);
|
|
4732
|
-
});
|
|
4733
4724
|
api.patch("/todos/:id", async (c) => {
|
|
4734
4725
|
const id = c.req.param("id");
|
|
4735
4726
|
const body = await c.req.json();
|
|
@@ -4978,7 +4969,7 @@ async function startServer(opts) {
|
|
|
4978
4969
|
}
|
|
4979
4970
|
|
|
4980
4971
|
// bin/cli.ts
|
|
4981
|
-
var VERSION = true ? "0.
|
|
4972
|
+
var VERSION = true ? "0.6.1" : "dev";
|
|
4982
4973
|
function parseArgs(argv) {
|
|
4983
4974
|
const args = { repoPath: process.cwd(), open: true, help: false, version: false };
|
|
4984
4975
|
for (let i = 0; i < argv.length; i++) {
|
|
@@ -5048,7 +5039,7 @@ function resolveWebDir() {
|
|
|
5048
5039
|
// bin/cli.ts (tsx dev) -> dist/web
|
|
5049
5040
|
].filter((p) => !!p);
|
|
5050
5041
|
for (const dir of candidates) {
|
|
5051
|
-
if (
|
|
5042
|
+
if (existsSync2(path8.join(dir, "index.html"))) return dir;
|
|
5052
5043
|
}
|
|
5053
5044
|
return void 0;
|
|
5054
5045
|
}
|