@xbghc/warden 0.7.0 → 0.9.0

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 CHANGED
@@ -6,6 +6,7 @@ with line comments you can copy back to the agent as a prompt.
6
6
  - Runs as a single local process per repository (`127.0.0.1` only, no auth, no database).
7
7
  - Diff sources: working tree, staged, working tree vs HEAD, a branch since it forked off its base (commits and
8
8
  uncommitted work together), any commit, any two refs, and git worktrees.
9
+ - Worktrees are made and taken down from the page, one per agent branch, with the path ready to paste.
9
10
  - Side-by-side **Unstaged** and **Staged** file lists: staged means reviewed. Stage from the UI by dragging
10
11
  over the lines you have read (or a hunk, or a file), and unstage the same way from the Staged view.
11
12
  - GitHub-style unified / side-by-side diff with syntax highlighting, collapsed file tree, lazy per-file loading, context expansion, virtual scrolling.
@@ -60,31 +61,36 @@ Several instances on the same repository can run at the same time.
60
61
  | Worktree, everything since base | `worktree:<path>:base:<ref>` | same, run inside the worktree |
61
62
 
62
63
  Refs accept anything git can resolve (`main`, `v1.2`, `HEAD~3`, a sha). `@` is `HEAD`.
63
- Worktrees are discovered with `git worktree list` and share the review state of the main repository.
64
+ Worktrees are discovered with `git worktree list` (and made in the *Worktrees* tab, see below) and
65
+ share the review state of the main repository.
64
66
  One whose directory is gone (git lists it as *prunable*) is not offered, and a remembered target
65
67
  inside a removed worktree falls back to the working tree on the next load. The comments and viewed
66
68
  flags kept under that worktree's key stay in the state file and come back if a worktree is created
67
69
  at the same path again.
68
70
 
69
- There is no target switcher in the UI: the sidebar's two blocks *are* the working tree, a commit is
70
- picked from the *Commits* panel, and the *Branch vs* and *Range* fields at the top of that panel open
71
- the other two. The sidebar names whatever is under review in its block header and puts a
71
+ The sidebar is the navigation, and the only navigation: its three tabs — *变更*, *提交*, *Worktree* —
72
+ each fill the left column with the controls for what the middle is showing, and the top bar carries
73
+ none of it. There is no target switcher: the *变更* tab's two blocks *are* the working tree, a commit
74
+ is picked from the *提交* list, and the *审阅整条分支* and *对比两个 ref* forms below that tab's filters
75
+ open the other two. The sidebar names whatever is under review in its block header and puts a
72
76
  *返回工作区* link above it.
73
77
 
74
78
  `working`, `staged` and `all` are the three **local views** of one worktree. While any of them is
75
- selected the sidebar shows two blocks — Unstaged (`working`) and Staged (`staged`) — instead of a
79
+ selected the sidebar shows two blocks — 未暂存 (`working`) and 已暂存 (`staged`) — instead of a
76
80
  single tree, and clicking a file switches to the view it belongs to. A file that is only partly
77
- staged appears in both. Switching between the three keeps your comments, the draft you are typing
78
- and the current selection; only the diff is reloaded. `all` has no control of its own (a saved
79
- `lastTarget` can still restore it; *Branch vs* `@` shows the same diff). Commit, range and `base`
80
- targets keep the single tree.
81
+ staged appears in both. Above them sits the one figure in the app: how many of the files under review
82
+ are fully staged, which is what "staged means reviewed" amounts to. Switching between the three keeps
83
+ your comments, the draft you are typing and the current selection; only the diff is reloaded. `all`
84
+ has no control of its own (a saved `lastTarget` can still restore it; *审阅整条分支* against `@` shows
85
+ the same diff). Commit, range and `base` targets keep the single tree, and their figure counts 已读
86
+ instead, since nothing can be staged there.
81
87
 
82
88
  `base:<ref>` is for a branch a coding agent has been working on, committing as it goes: one tree with
83
89
  everything since the branch forked off `<ref>` — the commits plus whatever is still uncommitted or
84
90
  untracked. The diff runs against the merge base, so commits that landed on `<ref>` after the fork are
85
- not listed as reverted (which is what a plain `git diff <ref>` would do). The *Branch vs* field in the
86
- Commits panel suggests the base for you — the main worktree's branch when a sibling worktree is under
87
- review, otherwise `main` or `master` — and takes any ref. Beside it the panel names the fork point (the
91
+ not listed as reverted (which is what a plain `git diff <ref>` would do). The *相对于* field under
92
+ *审阅整条分支* suggests the base for you — the main worktree's branch when a sibling worktree is under
93
+ review, otherwise `main` or `master` — and takes any ref. Under it the form names the fork point (the
88
94
  merge base with that ref, `GET /api/fork-point`) with how many commits the branch is ahead of it and how
89
95
  many landed on the base since, and the list marks that commit with a *分叉自* chip and a rule above it:
90
96
  what sits above is the branch's own work. Unlike the three local views, `base` keeps its own
@@ -92,6 +98,29 @@ pool of comments and a commit never deletes them: the round under review is not
92
98
  commits, so a comment whose lines changed stays *orphaned*, snippet and all, until you have checked
93
99
  the fix and delete or re-attach it.
94
100
 
101
+ ## Worktrees
102
+
103
+ Agents do their best work each in a worktree of its own, and the sidebar's *Worktree* tab is
104
+ where those are made and taken down without a trip to the terminal:
105
+
106
+ - *新建 worktree*, the form in the sidebar, takes a branch and a base. A name that is not a branch yet becomes one from the
107
+ base (`main` or `master` unless you say otherwise; any ref goes) — `git worktree add -b <branch>
108
+ <path> <base>`; an existing branch is checked out as it is, unless another worktree already has it.
109
+ The path is suggested as a sibling of the main worktree named `<repo>-<branch>` (slashes become
110
+ dashes) and can be edited, within limits: it has to sit under the main worktree's parent directory,
111
+ outside every existing worktree, and be new or an empty directory.
112
+ - Each row names the checkout, its branch, and whether it is clean or how many paths `git status`
113
+ reports. *查看* switches the review to it (the kind of target carries over, as with the selector
114
+ in the top bar), *复制路径* is for the agent's prompt.
115
+ - *删除* always asks first, in the row it would remove, and runs `git worktree remove` without
116
+ `--force`: a worktree with uncommitted changes is not removed until you confirm again, since those
117
+ changes go with it, and whatever else git refuses without `--force` is put to you the same way.
118
+ *一并删除分支* is offered in that same confirmation, ticked by default, and takes the branch by
119
+ `git branch -d`: one that is not merged is kept and the toast says why. The comments and viewed
120
+ flags kept under the worktree's key stay in the state file, as before.
121
+ - A worktree whose directory was deleted behind git's back is listed struck through; *清理* drops that
122
+ one entry (`git worktree remove` handles it; nothing is pruned wholesale).
123
+
95
124
  ## Keyboard
96
125
 
97
126
  | Key | Action |
@@ -101,7 +130,7 @@ the fix and delete or re-attach it.
101
130
  | `n` / `p` | Next / previous hunk |
102
131
  | `s` | Stage (Unstaged view) or unstage (Staged view) the picked lines |
103
132
  | `Ctrl+Enter` | Save the comment being edited |
104
- | `Esc` | Drop the picked lines / cancel editing / close a panel / cancel re-attach mode |
133
+ | `Esc` | Drop the picked lines / cancel editing / cancel re-attach mode / shut the rail / back to 变更 |
105
134
 
106
135
  ## Staging
107
136
 
@@ -132,12 +161,18 @@ Limits, each reported as a plain error rather than a half-applied patch:
132
161
 
133
162
  ## Comments and export
134
163
 
135
- Press the `+` that appears next to a line (drag to cover several lines) and write Markdown.
164
+ Press the `+` that appears next to a line (drag to cover several lines) and write Markdown. What you
165
+ write lands in the right-hand rail, which is shut until it has something to hold: it costs 360px of
166
+ the code column, which is most of it once the diff is side by side on a laptop. Writing a comment,
167
+ focusing one or re-attaching one opens it; so does the *评论* switch at the right of the top bar,
168
+ which carries the count and turns violet while any comment is still waiting to go back to the agent.
169
+ `Esc` shuts it again.
170
+
136
171
  Each comment belongs to the `old` or `new` side of the diff and has a status:
137
172
 
138
- - `active` — not yet exported
139
- - `exported` — copied at least once (excluded from "copy all" unless *含已导出* is checked)
140
- - `orphaned` — the code it referred to no longer exists in the current diff
173
+ - `active` (待导出) — not yet exported
174
+ - `exported` (已导出) — copied at least once (excluded from "copy all" unless *含已导出* is checked)
175
+ - `orphaned` (已失联) — the code it referred to no longer exists in the current diff
141
176
 
142
177
  "复制评论" copies every active comment of the current target; each comment also has a "复制此条" button.
143
178
  The clipboard format is fixed so an agent can read it directly:
@@ -187,7 +222,7 @@ them deletes on a moved HEAD.
187
222
  Comment markers in the diff belong to one view; the rail's *全部* tab lists the whole pool and
188
223
  *此文件* lists every comment on the open file regardless of which view it currently sits in.
189
224
 
190
- "Viewed" is per view, so a half-staged file can be marked read on one side and not the other. It is
225
+ *已读* is per view, so a half-staged file can be marked read on one side and not the other. It is
191
226
  bound to a hash of the file's diff; when the diff changes the flag is dropped and the file is marked
192
227
  *已变化*.
193
228
 
@@ -205,9 +240,9 @@ refreshes by hand.
205
240
 
206
241
  Notes attached to a branch rather than to a line of code, for the things you notice while reviewing
207
242
  that do not belong in a comment. They are not deleted when you commit. They sit in the right-hand
208
- rail next to the comments, under the *Todo* tab, as a task list that works the way Google Tasks does:
243
+ rail next to the comments, under the *待办* tab, as a task list that works the way Google Tasks does:
209
244
 
210
- - *添加 Todo* opens an empty row at the top; type the title and press Enter, and the next row opens
245
+ - *添加待办* opens an empty row at the top; type the title and press Enter, and the next row opens
211
246
  right under it. Esc or Backspace on an empty row drops it.
212
247
  - Titles and details are edited where they are: click into the text. Enter at the end of a title
213
248
  starts the next todo under it; Backspace on an emptied title deletes the todo. The details field
@@ -220,10 +255,10 @@ rail next to the comments, under the *Todo* tab, as a task list that works the w
220
255
  - The picker at the top is the list selector: the current branch, any other branch that has todos,
221
256
  or all of them with the branch on each row. A new todo goes to the branch being shown.
222
257
 
223
- Issues (the *Issues* tab in the top bar) are the same list: closing an issue is ticking it, a
258
+ Issues (the rail's third tab) are the same list: closing an issue is ticking it, a
224
259
  *已关闭* section holds the closed ones, and the comments linked to an issue sit under its open row
225
260
  like subtasks, each with *跳转* and *解除关联*. Tick comments in the rail and the *创建 Issue* button
226
- opens the drawer with the add row ready and the comments attached on creation; for an existing
261
+ switches to that tab with the add row ready and the comments attached on creation; for an existing
227
262
  issue, open its row and use *关联选中的评论*.
228
263
 
229
264
  Each row has its own *复制* button, and there is deliberately no "copy all": a todo is one task to
@@ -280,18 +315,24 @@ move into the matching scope the first time the file is read.
280
315
  Single user, local only. The server binds to `127.0.0.1` and executes git only through
281
316
  `execFile('git', [...])`. Reads go through an argument whitelist (`rev-parse`, `diff`, `show`, `log`,
282
317
  `worktree list`, `ls-files`, `status`, `merge-base`, `rev-list`) that refuses option-looking refs and any write-capable
283
- flag; requests that would need anything else get HTTP 400. The one write is `POST /api/targets/:key/stage`,
284
- which runs `git apply --cached` (with `--reverse` for the Staged view) on a patch the server itself builds
285
- from the diff it just produced — the patch is never taken from the request, only the line indices are,
286
- and they are checked against the diff's hash first. Nothing writes to the working tree, HEAD or the
287
- refs, and the review state lives outside the repository.
318
+ flag; requests that would need anything else get HTTP 400. The writes are few and each composes its
319
+ own arguments. `POST /api/targets/:key/stage` runs `git apply --cached` (with `--reverse` for the
320
+ Staged view) on a patch the server itself builds from the diff it just produced — the patch is never
321
+ taken from the request, only the line indices are, and they are checked against the diff's hash
322
+ first. `POST /api/worktrees` and `POST /api/worktrees/remove` run `git worktree add`, `git worktree
323
+ remove` and `git branch -d` with a branch name git has validated, a ref that resolves, and a
324
+ path confined to the main worktree's parent directory. Nothing writes to the working tree or HEAD of
325
+ an existing checkout; refs change only when a worktree is made (its new branch) or removed (its merged
326
+ branch, on request), and the review state lives outside the repository. A mutating request the
327
+ browser labels as coming from another site (`Sec-Fetch-Site: cross-site`) is refused with 403, so a
328
+ page from elsewhere cannot drive the server through the browser it is open in.
288
329
 
289
330
  ## Development
290
331
 
291
332
  ```sh
292
333
  pnpm install
293
334
  pnpm dev # API server on :4100 (tsx watch) + Vite dev server on :5173 with /api proxied
294
- pnpm test # vitest: diff parser, staging patches, anchoring, comment scopes, watcher, todos, export, state, HTTP API
335
+ pnpm test # vitest: diff parser, staging patches, anchoring, comment scopes, watcher, todos, worktrees, export, state, HTTP API
295
336
  pnpm typecheck
296
337
  pnpm build # dist/web (Vite) + dist/cli.js (tsup, zero runtime dependencies)
297
338
  node dist/cli.js path/to/repo
@@ -302,7 +343,7 @@ Layout:
302
343
  ```
303
344
  bin/cli.ts argument parsing, start server, open browser
304
345
  packages/shared types + target key / comment scope helpers (bundled into both sides)
305
- packages/server Hono API, git wrapper, diff parser, staging patches, targets, anchoring, watcher, state, export, nvim
346
+ packages/server Hono API, git wrapper, diff parser, staging patches, targets, worktrees, anchoring, watcher, state, export, nvim
306
347
  packages/web React + Vite UI (shiki highlighting, @tanstack/react-virtual, react-markdown)
307
348
  test/ API integration tests on a generated git repository
308
349
  ```
@@ -321,3 +362,7 @@ to `main` and every pull request.
321
362
  ## License
322
363
 
323
364
  MIT
365
+
366
+ ## Storybook
367
+
368
+ Run `pnpm storybook` for isolated review and layout components at http://127.0.0.1:6006. Stories use in-memory fixtures and include interaction checks. Run `pnpm build:storybook` to build the static preview.