@webappwiz/arbor 0.0.26 โ†’ 0.0.27

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
@@ -32,9 +32,9 @@ rebase, and that is what `remove` is for.
32
32
  Creates the task: branch `task/<task>`, a worktree at
33
33
  `../<repo>-arbor/<task>`, and a state record.
34
34
 
35
- `--todo 3,5` takes up todos (see `arbor todo`): their text becomes the plan's
36
- `## Goal`, each followed by the path of every file attached to it, and no
37
- other task can take them while this one lives.
35
+ `--todo 3,5` takes up todos (see `arbor todo`): each one's subject and detail
36
+ become the plan's `## Goal`, followed by the path of every file attached to it,
37
+ and no other task can take them while this one lives.
38
38
 
39
39
  `--base <branch>` starts the task from that branch and lands it back there
40
40
  instead of trunk. It takes another task's branch too: `--base task/<other>`
@@ -75,10 +75,9 @@ there, and fast-forwards the base with `git merge --ff-only`. History stays
75
75
  linear.
76
76
 
77
77
  1. Refuses if the worktree is dirty, out of retry budget, or leased elsewhere,
78
- or while a person's reply waits unclaimed (`unread`: run `arbor replies`),
79
78
  or while a question under `## Blocked` is unchecked (`blocked`): whether
80
- nobody has answered it yet, or the agent has yet to act on an answer or a
81
- follow-up and check it off.
79
+ nobody has answered it yet, or the agent has yet to act on an answer and
80
+ check it off.
82
81
  2. Takes the merge lock, **blocking**, polling every 2s. Blocking is
83
82
  deliberate: telling an agent "busy, try later" invites it to go edit more
84
83
  code in a branch that is supposed to be frozen.
@@ -104,9 +103,10 @@ fails the branch is reset to where it was and trunk is never touched.
104
103
  There is deliberately no flag to skip the gate: a repo that wants none
105
104
  configures none.
106
105
 
107
- A merge onto trunk ends by recommending what to do next: one todo, the task's
108
- own follow-ups first and then the longest waiting, plus any todo older than
109
- `todoStalenessMs` (30 days) offered for removal instead. The todos the task
106
+ A merge onto trunk ends by recommending what to do next: the open todo highest
107
+ on the list that came up in the task that landed, since its context is
108
+ freshest, or failing that the open todo highest on the list, plus any todo older than `todoStalenessMs` (30 days) offered for
109
+ removal instead. The todos the task
110
110
  took up, with `--todo` or `todo take`, leave the list, since that work is now
111
111
  done; release one first to keep what is left of it.
112
112
 
@@ -183,12 +183,12 @@ in silence: it is the one thing that makes the work resumable.
183
183
 
184
184
  A `ARBOR.md` that is there gets checked against the shape the agent skill
185
185
  prescribes (`# <task>`, `## Goal`, `## Next` with something unchecked in it, a
186
- `## Blocked` with `- [ ] Q1.` items once escalated, and none left open after),
186
+ `## Blocked` with `- [ ] 1.` items once escalated, and none left open after),
187
187
  and anything off is printed under it.
188
188
  Warnings only, never a refusal: the agent that wrote the file is the one that
189
189
  runs `show` on it, and a rough plan still beats none.
190
190
 
191
- ### `arbor wait <task> [--timeout-secs 900] [--answered]`
191
+ ### `arbor wait <task> [--timeout-secs 900]`
192
192
 
193
193
  Blocks until a task stops moving, then prints where it stopped.
194
194
 
@@ -205,109 +205,11 @@ driving. Wait again, work alongside it, or ask the human.
205
205
  Like `show` and `path`, it takes no lease, so watching a task cannot knock its
206
206
  agent off it.
207
207
 
208
- `--answered` waits for something else: until every open question under the
209
- task's `## Blocked` has a reply (or none is open), then claims everything new
210
- the way `arbor replies` does and prints it. This is how an agent that
211
- escalated waits for its human, with the same timeout and the same `timeout`
212
- refusal, which names the questions still unanswered. A reply someone has open
213
- to edit does not count yet. A task that is gone ends the wait too, since
214
- nothing is left to answer.
215
-
216
- ```
217
- alpha has new
218
- Q2 ๐ŸŽจ Does the header wrap to two lines?
219
- โ†’ yes
220
- ```
221
-
222
- ### `arbor inbox [--replied] [--json]`
223
-
224
- Every question waiting on a person, across all tasks: each unchecked
225
- `- [ ] Q9.` item under a task's `## Blocked` with no reply yet, grouped by
226
- task. A question leaves the inbox once it is answered. `--replied` brings back
227
- the ones answered but not yet checked off by their agent, with the reply under
228
- each, follow-ups too: one still waiting for its agent says so, and can be
229
- changed on the page until the agent claims it. Takes no lease.
230
-
231
- ```
232
- alpha
233
- Q3 ๐Ÿงน Keep or drop the old flag?
234
- It has been off since March.
235
-
236
- beta (in a live session: answer it there)
237
- Q1 ๐Ÿ—„๏ธ When should the migration run?
238
- (a) Now
239
- (b) After the backfill
240
-
241
- 1 replied, not yet acted on: arbor inbox --replied
242
- ```
243
-
244
- A question's line is its subject. Lines indented under it are its body,
245
- markdown with code blocks and images (`![shot](/abs/path.png)`, which the page
246
- shows inline). Choices come last in the body: `- (a) ...` lines take one or
247
- none, `- [a] ...` lines take any that apply.
248
-
249
- ```markdown
250
- - [ ] Q3. ๐Ÿ” How should existing sessions move to the new tokens?
251
- Sessions are keyed by the old cookie.
252
- ![login screen](/abs/path/login.png)
253
- - (a) Sign everyone out once
254
- - (b) Migrate each session on its next request
255
- - [ ] Q4. ๐Ÿ”” Where should failures notify?
256
- - [a] Email
257
- - [b] Slack
258
- - [c] Push
259
- ```
260
-
261
- ### `arbor replies [task]`
262
-
263
- How an agent reads what its human answered, and the only way it does. A
264
- person answers from the page (`arbor dev`), never the CLI, so agents have no
265
- way to answer each other: every answer an agent reads through arbor came from
266
- a person. For anything no question asked, the person tells the agent in its
267
- chat.
268
-
269
- Claiming takes every reply waiting for the task, writes each into `ARBOR.md`,
270
- and prints them with the questions still unanswered. The first answer to a
271
- question goes on its line after ` โ†’ `, its picks spelled out so the line alone
272
- says what was picked: ` โ†’ b (Migrate each session on its next request)`, or
273
- ` โ†’ a (Email), c (Push): and log it` with words after the picks. Once the
274
- agent has read an answer, the person can follow it up; a follow-up goes on a
275
- line of its own under the question, and unchecks it, whatever the agent did
276
- about the answer before:
277
-
278
- ```markdown
279
- - [ ] Q3. ๐Ÿ” How should existing sessions move to the new tokens? โ†’ b (Migrate each session on its next request)
280
- Sessions are keyed by the old cookie.
281
- โ†’ Sign out the admins, though.
282
- ```
283
-
284
- The box stays unchecked: checking it off is the agent's word that it has
285
- acted on everything under it. Once claimed, a reply can no longer change or be
286
- taken back, so the agent never acts on words that change under it. A reply the
287
- person has open to edit on the page is left for the next call. Run from a
288
- task worktree, the task is that one.
289
-
290
- ```
291
- alpha replied
292
- Q3 ๐Ÿงน Keep or drop the old flag?
293
- โ†’ drop
294
- unanswered: Q4
295
- ```
296
-
297
- Until claimed, a reply waits in `.git/arbor/replies/<task>/Q3.json`, its files
298
- beside it in `Q3/`, and the claimed line names each file by absolute path so
299
- the agent can open it from its own tree. They go when the task is merged or
300
- removed.
301
-
302
- A reply is refused (`lease_held`) while the task's agent is in a live
303
- session: it is waiting in its chat, not reading its plan, so answer it there.
304
-
305
208
  ### `arbor log [--count 20] [--json]`
306
209
 
307
210
  The last N things done here (`add`, `claim`, `merge`, `remove`, `escalate`,
308
- `retry`, `replies`, `todo add`, `todo update`, `todo take`, `todo release`,
309
- `todo remove`, and from the
310
- page `reply`, `withdraw`, `defer`, `skip` and `approve`), oldest first, each with the task and how it ended (`ok`, or
211
+ `retry`, `todo add`, `todo update`, `todo take`, `todo release` and
212
+ `todo remove`), oldest first, each with the task and how it ended (`ok`, or
311
213
  the refusal reason).
312
214
 
313
215
  ```
@@ -324,52 +226,27 @@ the only thing that remembers a task landed at all. The last 1000 are kept
324
226
 
325
227
  ### `arbor dev [--port 4269] [--allow-hosts <names>]`
326
228
 
327
- The inbox, what you sent, the todos, and the tasks in a browser, on
328
- `http://localhost:4269`, reloading themselves as anything changes. Built for a
329
- phone first. The inbox holds only what waits on you: each unanswered question,
330
- grouped by task, a task only when it has one. A task whose agent is in a live
331
- session is left out, since that agent is answered in its chat. With a keyboard,
332
- J opens the next question, and a hint under the list says so when the page
333
- sees a mouse or trackpad.
334
-
335
- Tapping a question opens it with its body, images full size on a tap, its
336
- choices, a View button for its task, and a reply box that takes pasted or
337
- picked files. Beside the box, **Defer** makes the question a todo, its images
338
- carried along, and answers it "Deferred to todo 7: leave it out of this
339
- task."; **Skip** answers "Skip this: go ahead without it.". A task escalated
340
- with `arbor escalate --review` asks `โœ… Ready to merge?` like any other
341
- question, with **Approve** ("Approved: merge it.") beside a box to request
342
- changes.
343
-
344
- Once answered, a question moves to Sent, one line each with where it stands
345
- in a word: Waiting (for its agent, still yours to change or withdraw),
346
- Editing, Read (by its agent), or Done (checked off). Opening one still
347
- waiting holds it, so its agent cannot claim it half-changed, and closing it
348
- lets go; the hold also lapses on its own after five minutes. One its agent
349
- has read shows what was said so far and a box to follow it up. Each stays
350
- until its task lands or goes.
351
-
352
- A line across the top always says something, so nothing under it moves: the
353
- question you last replied to while its agent has yet to read it, with
354
- Withdraw, or else how many questions need you and how many replies wait for
355
- their agents.
229
+ The todos and the tasks in a browser, on `http://localhost:4269`, reloading
230
+ themselves as anything changes. Built for a phone first.
231
+
232
+ Todos are cards in list order: drag one to reorder the list (a short press
233
+ on a phone, or Space on its grip and the arrow keys), or tap it to reword,
234
+ attach files to, or remove. Typing `@` in a todo offers the files and
235
+ directories in the main tree, tracked or new but not ignored, and writes the
236
+ one picked as `@path/from/root`; picking a directory keeps the list open on
237
+ what is inside.
356
238
 
357
239
  Tasks lists every task with its progress through its plan, flagging only an
358
240
  escalated or broken status; tapping one opens its details and whole plan.
359
241
 
360
- Todos open to reword, attach files to, or remove. Typing `@` in a reply or a
361
- todo offers the files and directories in the task's tree (the main tree's for
362
- a todo), tracked or new but not ignored, and writes the one picked as
363
- `@path/from/root`; picking a directory keeps the list open on what is inside.
364
242
  On a phone the tabs sit along the bottom, in reach of a thumb; on anything
365
243
  wider they run down a sidebar. If the server stops answering, the header says
366
244
  it is offline, since what the page shows may be stale.
367
245
 
368
- Answering is done here and nowhere else, so an agent with a shell cannot
369
- answer another: see `arbor replies`. Todos are the CLI's too, through the same
370
- functions (`arbor todo add`, `update` and `remove`). Merging, removing tasks
371
- and claiming stay in the CLI, so a page that should not have been reachable
372
- can at worst leave a reply and change todos. It takes no lease.
246
+ Todo writes are the same functions as `arbor todo add`, `update` and
247
+ `remove`. Questions are answered in the agent's chat, not here. Merging,
248
+ removing tasks and claiming stay in the CLI, so a page that should not have
249
+ been reachable can at worst change todos. It takes no lease.
373
250
 
374
251
  It listens on 127.0.0.1 only and refuses a request whose `Host` is not this
375
252
  machine, and any write from another origin. To use it from another device,
@@ -381,7 +258,7 @@ arbor dev --allow-hosts myrepo-arbor.example.dev
381
258
  ```
382
259
 
383
260
  arbor has no login of its own. Whoever can reach an allowed host can read the
384
- repo's plans and reply, so the tunnel has to be the one asking who you are
261
+ repo's plans and change its todos, so the tunnel has to be the one asking who you are
385
262
  (Cloudflare Access, a Tailscale tailnet). Never expose it through a tunnel
386
263
  with no login in front.
387
264
 
@@ -413,13 +290,37 @@ printing a path you cannot `cd` into.
413
290
  The explicit "this needs a human" exit. Records the reason, drops the lease, and
414
291
  leaves the worktree **exactly** as it is so the human sees what the agent saw.
415
292
 
293
+ Questions go under `## Blocked` in the task's `ARBOR.md`, numbered `1.`,
294
+ `2.`, โ€ฆ (a plan numbered the old way, `Q1.`, still reads). A question's line
295
+ is its subject. Lines indented under it are its body, markdown with code
296
+ blocks and images (`![shot](/abs/path.png)`). Choices come last in the body:
297
+ `- (a) ...` lines take one or none, `- [a] ...` lines take any that apply.
298
+
299
+ ```markdown
300
+ - [ ] 3. ๐Ÿ” How should existing sessions move to the new tokens?
301
+ Sessions are keyed by the old cookie.
302
+ ![login screen](/abs/path/login.png)
303
+ - (a) Sign everyone out once
304
+ - (b) Migrate each session on its next request
305
+ - [ ] 4. ๐Ÿ”” Where should failures notify?
306
+ - [a] Email
307
+ - [b] Slack
308
+ - [c] Push
309
+ ```
310
+
311
+ The person answers in the agent's chat, and the agent writes each answer on
312
+ its question's line after ` โ†’ `, then checks it off once it has acted on it:
313
+
314
+ ```markdown
315
+ - [x] 3. ๐Ÿ” How should existing sessions move to the new tokens? โ†’ b (Migrate each session on its next request)
316
+ ```
317
+
416
318
  `--review` asks for approval to merge rather than for answers: it adds
417
- `- [ ] Q5. โœ… Ready to merge?` under `## Blocked`, the reason indented under
418
- it as what to look at, and the page shows the task as one card with Approve
419
- and Request changes. Refused (`blocked`) while another question is unchecked,
420
- so a review is the last thing standing between the task and trunk. The agent
421
- waits with `arbor wait --answered`, then merges on "Approved: merge it." or
422
- acts on the changes asked for.
319
+ `- [ ] 5. โœ… Ready to merge?` under `## Blocked`, the reason indented under
320
+ it as what to look at. Refused (`blocked`) while another question is
321
+ unchecked, so a review is the last thing standing between the task and trunk.
322
+ The agent merges once the person approves in chat, or acts on the changes
323
+ asked for.
423
324
 
424
325
  This exists so an agent has a way out that is not "resolve the conflict badly to
425
326
  finish the task". Agents are reliable at mechanical conflicts (both sides added
@@ -427,13 +328,23 @@ imports, a signature changed on one side and its callers on the other) and
427
328
  unreliable when both sides restructured the same logic, because then there is no
428
329
  correct merge, only a decision.
429
330
 
430
- ### `arbor todo add <text> [--file <path>]`, `arbor todo list [--json]`, `arbor todo update <id> [text] [--file <path>] [--remove-file <name>]`, `arbor todo take <id...>`, `arbor todo release <id...>`, `arbor todo remove <id>`
331
+ ### `arbor todo add <subject> [text] [--position <n>] [--file <path>]`, `arbor todo list [--json]`, `arbor todo show <id> [--json]`, `arbor todo update <id> [text] [--subject <subject>] [--position <n>] [--file <path>] [--remove-file <name>]`, `arbor todo take <id...>`, `arbor todo release <id...>`, `arbor todo remove <id>`
431
332
 
432
333
  Work deferred for later. When something outside the task comes up (a bug next
433
334
  door, a follow-up the reviewer asked for, a question that turns out to be its
434
335
  own project), the agent notes it with `arbor todo add` and carries on instead
435
336
  of growing the task. Run from a worktree, `add` records the task it came up
436
- in, which is what lets `merge` recommend a task's own follow-ups first.
337
+ in. A todo is a subject, one line that `list` and `merge` show, and an optional
338
+ detail below it that `show` prints in full. Agents lead a subject with one
339
+ emoji for what it is about (`๐Ÿ› Upload retries forever on a 413`), as they do a
340
+ question, so the list reads at a glance.
341
+
342
+ The list is in order: position 1 is the top, and after a task's own todos,
343
+ the open todo highest on it is the one `merge` recommends next. A new one goes to the bottom, or with
344
+ `--position 2` in at 2, pushing those from there down. `update --position`
345
+ moves one, and removing one closes the gap, so positions always run 1, 2, 3.
346
+ Whoever keeps the list, a person on the page or an agent, decides what
347
+ matters most by moving it up.
437
348
 
438
349
  Todos live in `.git/arbor/todos/`, one file each, so every worktree sees a new
439
350
  one at once, with nothing to commit and no two agents rewriting the same file.
@@ -445,12 +356,13 @@ run from a worktree, adds them to the task already under way, all or none.
445
356
  A task can hold any number. `release` puts them back on the list, from a
446
357
  worktree only its own task's: a task landing with one half done rewords it to
447
358
  what is left with `update`, then releases it, so the merge leaves it open.
448
- `update` rewords one and keeps its number; `remove` drops one done some other
449
- way or no longer wanted.
359
+ `update` rewords one, its detail as the argument and its subject with
360
+ `--subject`, and keeps its number; `remove` drops one done some other way or
361
+ no longer wanted.
450
362
 
451
363
  `--file a.png,notes.md` attaches files of any kind, stored beside the todo in
452
- `.git/arbor/todos/<id>/` the way a reply's are. `update --remove-file` drops
453
- one, named by path or by its stored file name (`todo list` shows them). They
364
+ `.git/arbor/todos/<id>/`. `update --remove-file` drops
365
+ one, named by path or by its stored file name (`todo show` lists them). They
454
366
  go when the todo does.
455
367
 
456
368
  ### `arbor retry <task>`
@@ -478,16 +390,15 @@ The agent's control flow runs on these.
478
390
  | 3 | `tests_failed` | The gate (`postRewrite`, `preMerge`) failed after the rebase. Branch rolled back, trunk untouched. Fix and merge again. |
479
391
  | 4 | `lease_lost` | Another agent took the tree mid-merge. **Stop. Do not retry.** |
480
392
  | 5 | `budget_exhausted` | Out of merge attempts. `arbor escalate`, and a human can grant another budget with `arbor retry`; or `arbor remove` and redo against current trunk. |
481
- | 6 | `lease_held` | Another agent is driving this tree. For a reply from the page: answer that agent in its chat. |
393
+ | 6 | `lease_held` | Another agent is driving this tree. |
482
394
  | 7 | `dirty` | Uncommitted changes. Commit before merging. |
483
- | 8 | `not_found` | No such task, or not run from a task worktree; for a reply from the page, no such open question. |
395
+ | 8 | `not_found` | No such task, or not run from a task worktree. |
484
396
  | 9 | `hook_failed` | `postCheckout` failed (worktree still exists; fix and re-run the hook), or `postMerge` failed (the branch already landed; nothing rolled back). |
485
397
  | 10 | `exists` | Task already exists. `arbor claim` it, or `arbor remove` first. |
486
398
  | 11 | `orphaned` | Record with no worktree. `arbor remove` it. |
487
399
  | 12 | `merge_failed` | The base could not be fast-forwarded (usually uncommitted changes in the worktree holding it). |
488
400
  | 13 | `already_removed` | This task was removed earlier; nothing left to remove. |
489
- | 14 | `timeout` | `arbor wait` gave up: the task is still working or merging, or with `--answered`, a question is still unanswered. |
490
- | 15 | `unread` | `arbor merge` refused: a person's reply waits unclaimed. `arbor replies`, act on it, check it off, merge again. |
401
+ | 14 | `timeout` | `arbor wait` gave up: the task is still working or merging. |
491
402
  | 16 | `blocked` | `arbor merge` refused: a question under `## Blocked` is unchecked, unanswered or not yet acted on. Also `escalate --review` while one is. |
492
403
 
493
404
  Every failure prints a one-line JSON object on **stdout** (`{"reason": ...}`,
package/add.d.ts CHANGED
@@ -8,7 +8,7 @@ export interface AddOptions {
8
8
  /** Branch the task starts from and merges onto. Defaults to the trunk. */
9
9
  base?: string;
10
10
  /**
11
- * The todos this task takes up: their text seeds the plan's Goal, and
11
+ * The todos this task takes up: their words seed the plan's Goal, and
12
12
  * nobody else can take them while the task lives.
13
13
  */
14
14
  todos?: number[];
package/attachments.d.ts CHANGED
@@ -7,10 +7,10 @@ export interface Attachment {
7
7
  bytes: Uint8Array;
8
8
  }
9
9
  /**
10
- * The folder of files that belong to one record, a reply's or a todo's, kept
11
- * beside it: `replies/<task>/Q2.json` has `replies/<task>/Q2/`, and
12
- * `todos/7.json` has `todos/7/`. Under `.git/arbor`, so nothing here is ever
13
- * committed and every tree can open what it holds.
10
+ * The folder of files that belong to a todo: todo 7's beside its record, in
11
+ * `todos/7/`.
12
+ * Under `.git/arbor`, so nothing here is ever committed and every tree can
13
+ * open what it holds.
14
14
  */
15
15
  export declare class Attachments {
16
16
  readonly dir: string;
package/dev.d.ts CHANGED
@@ -3,7 +3,6 @@ import type { Logger } from "webappwiz/log";
3
3
  import { type Fs, type PortProvider } from "webappwiz/system";
4
4
  import type { Assets } from "./dev/assets.js";
5
5
  import type { Journal } from "./journal.js";
6
- import type { Replies } from "./replies.js";
7
6
  import type { Todos } from "./todo.js";
8
7
  import type { WorktreeService } from "./worktree-service.js";
9
8
  /** Preferred, not required: `dev` moves up from here when it is taken. */
@@ -27,20 +26,16 @@ export interface DevServer extends AsyncResource {
27
26
  port: number;
28
27
  }
29
28
  /**
30
- * Serves what `list`, `show`, `inbox` and `todo list` print, as one page that
31
- * refetches when the repo changes. It is the only place a person answers a
32
- * question: follows one up, defers or skips it, or approves a merge. The CLI
33
- * has no command for any of them, so no agent is tempted to answer another.
34
- * Anything that moves a task (merge, remove, claim) stays in the CLI, so a
35
- * page that should not have been reachable can at worst leave a reply or
36
- * change a todo.
29
+ * Serves what `todo list`, `list` and `show` print as one page that refetches
30
+ * when the repo changes, where a person adds, edits, reorders and removes
31
+ * todos. Anything that moves a task (merge, remove, claim) stays in the CLI,
32
+ * so a page that should not have been reachable can at worst change a todo.
37
33
  */
38
- export declare function dev({ service, fs, journal, todos, replies, log, assets, }: {
34
+ export declare function dev({ service, fs, journal, todos, log, assets, }: {
39
35
  service: WorktreeService;
40
36
  fs: Fs;
41
37
  journal: Journal;
42
38
  todos: Todos;
43
- replies: Replies;
44
39
  log: Logger;
45
40
  assets: Assets;
46
41
  }, { ports, hosts }?: DevOptions): Promise<DevServer>;
package/exit.d.ts CHANGED
@@ -15,7 +15,6 @@ export declare const EXIT: {
15
15
  readonly merge_failed: 12;
16
16
  readonly already_removed: 13;
17
17
  readonly timeout: 14;
18
- readonly unread: 15;
19
18
  readonly blocked: 16;
20
19
  };
21
20
  export type Reason = keyof typeof EXIT;