@webappwiz/arbor 0.0.30 → 0.0.32

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.
Files changed (120) hide show
  1. package/README.md +2 -2
  2. package/add.d.ts +23 -0
  3. package/age.d.ts +2 -0
  4. package/arbor.d.ts +11 -0
  5. package/attachments.d.ts +36 -0
  6. package/claim.d.ts +11 -0
  7. package/config.d.ts +55 -0
  8. package/config.js +7 -0
  9. package/dev/assets.d.ts +11 -0
  10. package/dev.d.ts +48 -0
  11. package/escalate.d.ts +28 -0
  12. package/exit.d.ts +42 -0
  13. package/git.d.ts +82 -0
  14. package/index.d.ts +2 -0
  15. package/index.js +4763 -0
  16. package/journal.d.ts +29 -0
  17. package/list.d.ts +18 -0
  18. package/load-config.d.ts +15 -0
  19. package/log.d.ts +14 -0
  20. package/merge.d.ts +24 -0
  21. package/package.json +15 -32
  22. package/path.d.ts +13 -0
  23. package/plan.d.ts +83 -0
  24. package/remove.d.ts +19 -0
  25. package/repository.d.ts +25 -0
  26. package/retry.d.ts +17 -0
  27. package/shell.d.ts +17 -0
  28. package/show.d.ts +53 -0
  29. package/snapshot.d.ts +36 -0
  30. package/table.d.ts +5 -0
  31. package/todo.d.ts +202 -0
  32. package/todos.d.ts +75 -0
  33. package/wait.d.ts +22 -0
  34. package/worktree-service.d.ts +51 -0
  35. package/worktree.d.ts +95 -0
  36. package/AGENTS.md +0 -7
  37. package/add.test.ts +0 -125
  38. package/add.ts +0 -192
  39. package/age.ts +0 -11
  40. package/arbor.test.ts +0 -145
  41. package/arbor.ts +0 -429
  42. package/assets.d.ts +0 -9
  43. package/attachments.ts +0 -87
  44. package/build.ts +0 -98
  45. package/claim.test.ts +0 -89
  46. package/claim.ts +0 -73
  47. package/components.json +0 -21
  48. package/config.ts +0 -58
  49. package/dev/api.ts +0 -120
  50. package/dev/app.test.tsx +0 -645
  51. package/dev/app.tsx +0 -80
  52. package/dev/assets.ts +0 -16
  53. package/dev/build/main.txt +0 -68
  54. package/dev/build/shell.txt +0 -21
  55. package/dev/build/styles.txt +0 -2263
  56. package/dev/components/ui/badge.tsx +0 -51
  57. package/dev/components/ui/button.tsx +0 -57
  58. package/dev/components/ui/dialog.tsx +0 -154
  59. package/dev/components/ui/empty.tsx +0 -100
  60. package/dev/components/ui/input-group.tsx +0 -155
  61. package/dev/components/ui/input.tsx +0 -19
  62. package/dev/components/ui/progress.tsx +0 -80
  63. package/dev/components/ui/textarea.tsx +0 -17
  64. package/dev/components/ui/toast.tsx +0 -227
  65. package/dev/feed.ts +0 -69
  66. package/dev/files.tsx +0 -191
  67. package/dev/index.html +0 -21
  68. package/dev/lib/utils.ts +0 -7
  69. package/dev/main.tsx +0 -22
  70. package/dev/markdown.test.tsx +0 -133
  71. package/dev/markdown.tsx +0 -124
  72. package/dev/mentions.tsx +0 -319
  73. package/dev/shadcn.css +0 -649
  74. package/dev/styles.css +0 -107
  75. package/dev/tags.tsx +0 -44
  76. package/dev/tasks.tsx +0 -151
  77. package/dev/todos.tsx +0 -759
  78. package/dev.test.ts +0 -439
  79. package/dev.ts +0 -386
  80. package/e2e.test.ts +0 -256
  81. package/escalate.test.ts +0 -76
  82. package/escalate.ts +0 -112
  83. package/exit.test.ts +0 -71
  84. package/exit.ts +0 -74
  85. package/git.ts +0 -294
  86. package/index.ts +0 -9
  87. package/journal.ts +0 -89
  88. package/list.test.ts +0 -82
  89. package/list.ts +0 -137
  90. package/load-config.test.ts +0 -121
  91. package/load-config.ts +0 -62
  92. package/log.test.ts +0 -65
  93. package/log.ts +0 -40
  94. package/merge.test.ts +0 -332
  95. package/merge.ts +0 -283
  96. package/path.test.ts +0 -35
  97. package/path.ts +0 -38
  98. package/plan.test.ts +0 -318
  99. package/plan.ts +0 -302
  100. package/progress.test.ts +0 -54
  101. package/progress.ts +0 -31
  102. package/remove.test.ts +0 -68
  103. package/remove.ts +0 -88
  104. package/repository.ts +0 -83
  105. package/retry.test.ts +0 -43
  106. package/retry.ts +0 -48
  107. package/shell.ts +0 -44
  108. package/show.test.ts +0 -91
  109. package/show.ts +0 -151
  110. package/snapshot.test.ts +0 -33
  111. package/snapshot.ts +0 -84
  112. package/table.ts +0 -20
  113. package/testing.ts +0 -229
  114. package/todo.test.ts +0 -490
  115. package/todo.ts +0 -834
  116. package/wait.test.ts +0 -61
  117. package/wait.ts +0 -77
  118. package/worktree-service.test.ts +0 -158
  119. package/worktree-service.ts +0 -193
  120. package/worktree.ts +0 -239
package/journal.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ import { type Fs } from "webappwiz/system";
2
+ /** One thing that was done to a task, and how it went. */
3
+ export interface Entry {
4
+ at: string;
5
+ action: string;
6
+ /** The task it was about, or null when the command names none. */
7
+ task: string | null;
8
+ /** The refusal reason, or null when the command succeeded. */
9
+ reason: string | null;
10
+ }
11
+ /**
12
+ * What has been done in this repo, oldest first. Entries outlive their tasks:
13
+ * `merge` and `remove` take the record with them, so this is the only thing that
14
+ * remembers a task existed at all.
15
+ */
16
+ /** What a `Journal` is stored through; the real filesystem by default. */
17
+ export interface JournalOptions {
18
+ fs?: Fs;
19
+ }
20
+ export declare class Journal {
21
+ private readonly path;
22
+ private readonly capacity;
23
+ private readonly fs;
24
+ constructor(path: string, capacity: number, opts?: JournalOptions);
25
+ record<T>(action: string, task: string | null, run: () => Promise<T>): Promise<T>;
26
+ tail(count: number): Promise<Entry[]>;
27
+ private append;
28
+ private entries;
29
+ }
package/list.d.ts ADDED
@@ -0,0 +1,18 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Fs } from "webappwiz/system";
3
+ import type { WorktreeService } from "./worktree-service.js";
4
+ export interface ListOptions {
5
+ /** Print the rows as JSON instead of a table. */
6
+ json?: boolean;
7
+ /**
8
+ * Add each task's changed and planned files, which is what an agent checks
9
+ * its own plan against before starting. Off by default: it costs git calls
10
+ * and a file read per task.
11
+ */
12
+ files?: boolean;
13
+ }
14
+ export declare function list({ service, log, fs }: {
15
+ service: WorktreeService;
16
+ log: Logger;
17
+ fs: Fs;
18
+ }, { json, files }?: ListOptions): Promise<void>;
@@ -0,0 +1,15 @@
1
+ import { type Fs } from "webappwiz/system";
2
+ import type { Config } from "./config.js";
3
+ import { Git } from "./git.js";
4
+ export interface LoadConfigOptions {
5
+ /** What `arbor.config.ts` is looked for through; the real one by default. */
6
+ fs?: Fs;
7
+ /** What the trunk is detected through; the repo's own git by default. */
8
+ git?: Git;
9
+ }
10
+ /**
11
+ * The settings arbor runs a repo with: what `arbor.config.ts` names, then the
12
+ * branch `origin/HEAD` points at for the trunk, then arbor's own defaults.
13
+ * Throws when the config file is there but cannot be imported.
14
+ */
15
+ export declare function loadConfig(root: string, opts?: LoadConfigOptions): Promise<Config>;
package/log.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Journal } from "./journal.js";
3
+ /** Enough to cover a session's worth of work without scrolling. */
4
+ export declare const DEFAULT_COUNT = 20;
5
+ export interface LogOptions {
6
+ /** How many of the most recent entries to show. */
7
+ count?: number;
8
+ /** Print the entries as JSON instead of a table. */
9
+ json?: boolean;
10
+ }
11
+ export declare function log({ journal, log }: {
12
+ journal: Journal;
13
+ log: Logger;
14
+ }, { count, json }?: LogOptions): Promise<void>;
package/merge.d.ts ADDED
@@ -0,0 +1,24 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Fs, Lock } from "webappwiz/system";
3
+ import type { Config } from "./config.js";
4
+ import type { Git } from "./git.js";
5
+ import type { Shell } from "./shell.js";
6
+ import type { Todos } from "./todos.js";
7
+ import type { WorktreeService } from "./worktree-service.js";
8
+ /**
9
+ * Lands the current worktree's branch on its base branch, trunk unless the
10
+ * task was created with `--base`. Never a merge commit: it rebases onto the
11
+ * base, runs the gate there, and fast-forwards the base with
12
+ * `git merge --ff-only`, in whichever worktree has the base checked out.
13
+ * History stays linear.
14
+ */
15
+ export declare function merge({ service, git, lock, shell, config, log, todos, fs, }: {
16
+ service: WorktreeService;
17
+ git: Git;
18
+ lock: Lock;
19
+ shell: Shell;
20
+ config: Config;
21
+ log: Logger;
22
+ todos: Todos;
23
+ fs: Fs;
24
+ }, cwd: string): Promise<void>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webappwiz/arbor",
3
- "version": "0.0.30",
3
+ "version": "0.0.32",
4
4
  "description": "Runs several AI coding agents on one repository at once, each in its own git worktree",
5
5
  "license": "MIT",
6
6
  "author": "Jared Johnson",
@@ -10,14 +10,6 @@
10
10
  "directory": "packages/arbor"
11
11
  },
12
12
  "type": "module",
13
- "module": "index.ts",
14
- "bin": {
15
- "arbor": "./index.ts"
16
- },
17
- "exports": {
18
- ".": "./index.ts",
19
- "./config": "./config.ts"
20
- },
21
13
  "imports": {
22
14
  "#dev/*": "./dev/*"
23
15
  },
@@ -28,31 +20,22 @@
28
20
  "access": "public"
29
21
  },
30
22
  "dependencies": {
31
- "webappwiz": "0.0.29",
23
+ "webappwiz": "^0.0.32",
32
24
  "zod": "^4.4.3"
33
25
  },
34
- "devDependencies": {
35
- "@base-ui/react": "^1.8.0",
36
- "@dnd-kit/core": "^6.3.1",
37
- "@dnd-kit/modifiers": "^9.0.0",
38
- "@dnd-kit/sortable": "^10.0.0",
39
- "@dnd-kit/utilities": "^3.2.2",
40
- "@tailwindcss/oxide": "^4.3.3",
41
- "@testing-library/react": "^16.3.1",
42
- "@types/react": "^19.2.18",
43
- "@types/react-dom": "^19.2.4",
44
- "@webappwiz/react": "0.0.29",
45
- "class-variance-authority": "^0.7.1",
46
- "clsx": "^2.1.1",
47
- "lucide-react": "^1.49.0",
48
- "markdown-to-jsx": "^9.10.3",
49
- "react": "^19.2.8",
50
- "react-dom": "^19.2.8",
51
- "tailwind-merge": "^3.7.0",
52
- "tailwindcss": "^4.3.3",
53
- "tw-animate-css": "^1.4.0"
26
+ "main": "./index.js",
27
+ "types": "./index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./index.d.ts",
31
+ "default": "./index.js"
32
+ },
33
+ "./config": {
34
+ "types": "./config.d.ts",
35
+ "default": "./config.js"
36
+ }
54
37
  },
55
- "scripts": {
56
- "build": "bun run build.ts"
38
+ "bin": {
39
+ "arbor": "./index.js"
57
40
  }
58
41
  }
package/path.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import type { Logger } from "webappwiz/log";
2
+ import type { WorktreeService } from "./worktree-service.js";
3
+ /**
4
+ * Where a task lives, or (with no task) the main tree. Nothing but the path
5
+ * on stdout, so it composes: `cd "$(arbor path)"`, `zed -a "$(arbor path x)"`.
6
+ * Moving between trees is `cd` and nothing else, and the main tree is the one
7
+ * path a process standing in a worktree cannot otherwise name: git's
8
+ * `--show-toplevel` hands back the worktree it is already in.
9
+ */
10
+ export declare function path({ service, log }: {
11
+ service: WorktreeService;
12
+ log: Logger;
13
+ }, task?: string): Promise<void>;
package/plan.d.ts ADDED
@@ -0,0 +1,83 @@
1
+ /** The plan each task keeps at its worktree root, excluded from git. */
2
+ export declare const PLAN_FILE = "ARBOR.md";
3
+ /**
4
+ * The paths an `ARBOR.md` says its task will touch: each top-level bullet
5
+ * under `## Files`, taken as written. Prose around the list is left out, and a
6
+ * plan with no such section plans nothing.
7
+ */
8
+ export declare function plannedFiles(text: string): string[];
9
+ /**
10
+ * One `- [ ] 9.` item under `## Blocked`: something only a person can do.
11
+ * The item's line is its subject; lines indented under it are its body, and
12
+ * `- (a) ...` or `- [a] ...` lines among them are the answers it offers.
13
+ */
14
+ export interface Question {
15
+ /** As the plan numbers it, `9`: the name a reply goes by. */
16
+ number: string;
17
+ /** Checked off, which the agent does once it has acted on the reply. */
18
+ done: boolean;
19
+ /** The item's own line, without its checkbox, number or reply. */
20
+ text: string;
21
+ /**
22
+ * The markdown indented under it, dedented, choices left out: detail,
23
+ * code blocks, `![shot](/abs/path.png)` images. Empty when there is none.
24
+ */
25
+ body: string;
26
+ /** Whatever follows ` → `, or null when nobody has answered yet. */
27
+ reply: string | null;
28
+ /**
29
+ * What a person added once the reply was read, oldest first: each an
30
+ * indented `→ ` line under the question.
31
+ */
32
+ followUps: string[];
33
+ /**
34
+ * The answers it offers, in order. Empty for a question answered in words
35
+ * alone. A reply can always add words to its picks, or answer in words
36
+ * instead.
37
+ */
38
+ choices: Choice[];
39
+ /**
40
+ * `one` for `- (a)` choices, at most one of which a reply picks; `any` for
41
+ * `- [a]` choices, where it picks all that apply. Null without choices.
42
+ */
43
+ pick: "one" | "any" | null;
44
+ }
45
+ /** One answer a question offers: `- (b) Migrate on next login`. */
46
+ export interface Choice {
47
+ /** The letter it goes by: `b`. */
48
+ key: string;
49
+ text: string;
50
+ }
51
+ /**
52
+ * Every numbered item under `## Blocked`, open or checked off, each with the
53
+ * indented lines under it. The reply is read off the item's own line: an
54
+ * answer is one line, written by `arbor reply` or by hand after the arrow.
55
+ */
56
+ export declare function questions(text: string): Question[];
57
+ /**
58
+ * The plan with a new open question at the end of `## Blocked`, the section
59
+ * made if missing, numbered after the highest there. Returns the plan and
60
+ * the number it went by.
61
+ */
62
+ export declare function withQuestion(text: string, subject: string, body?: string): {
63
+ plan: string;
64
+ number: string;
65
+ };
66
+ /**
67
+ * A question's number however a person typed it: `9`, `9.`, `Q9` and `q9:`
68
+ * all mean `9`. Null for anything that is not a number at all.
69
+ */
70
+ export declare function questionNumber(raw: string): string | null;
71
+ export interface PlanOptions {
72
+ /** The task name the title is expected to match. */
73
+ task: string;
74
+ /** Whether the task has escalated, which makes `## Blocked` required. */
75
+ escalated?: boolean;
76
+ }
77
+ /**
78
+ * Every way a task's `ARBOR.md` departs from the shape the agent skill
79
+ * prescribes, phrased for the agent that wrote it. Advisory only: a resumable
80
+ * plan is the point, and a malformed one still beats none, so nothing here
81
+ * blocks a merge.
82
+ */
83
+ export declare function checkPlan(text: string, { task, escalated }: PlanOptions): string[];
package/remove.d.ts ADDED
@@ -0,0 +1,19 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Todos } from "./todos.js";
3
+ import type { WorktreeService } from "./worktree-service.js";
4
+ export interface RemoveOptions {
5
+ /** Discard the tree even when another agent holds its lease. */
6
+ force?: boolean;
7
+ }
8
+ /**
9
+ * Discards a whole task: `git worktree remove` plus the branch and the
10
+ * record.
11
+ *
12
+ * Throwing a task away and redoing it against current trunk is usually cheaper
13
+ * than a hard rebase, so this is meant to be used freely.
14
+ */
15
+ export declare function remove({ service, log, todos, }: {
16
+ service: WorktreeService;
17
+ log: Logger;
18
+ todos: Todos;
19
+ }, task: string, { force }?: RemoveOptions): Promise<void>;
@@ -0,0 +1,25 @@
1
+ import type { Deps, Middleware } from "webappwiz/cmd";
2
+ import { type Fs, type Lock } from "webappwiz/system";
3
+ import type { Config } from "./config.js";
4
+ import { Git } from "./git.js";
5
+ import { Journal } from "./journal.js";
6
+ import { Shell } from "./shell.js";
7
+ import { Todos } from "./todos.js";
8
+ import { WorktreeService } from "./worktree-service.js";
9
+ /** What a command gets to work with, once there is a repository to work in. */
10
+ export interface Repository {
11
+ config: Config;
12
+ git: Git;
13
+ service: WorktreeService;
14
+ lock: Lock;
15
+ shell: Shell;
16
+ journal: Journal;
17
+ todos: Todos;
18
+ }
19
+ /**
20
+ * Runs the action against the repository `cwd` sits in, and refuses to run it
21
+ * at all when that is not a repository.
22
+ */
23
+ export declare function repository<C extends Deps & {
24
+ fs: Fs;
25
+ }>(at?: string): Middleware<C, C & Repository>;
package/retry.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Config } from "./config.js";
3
+ import type { WorktreeService } from "./worktree-service.js";
4
+ /**
5
+ * The way back from `budget_exhausted`. A task that has spent its merge
6
+ * attempts gets another `mergeRetryCount` of them, instead of `remove` and redo.
7
+ *
8
+ * Only from `escalated`, and deliberately: the budget's whole job is to make an
9
+ * agent stop and hand the task over. An agent that could grant itself more
10
+ * would be back to grinding against a moving trunk forever, so the price of a
11
+ * fresh budget is that a human has looked at the tree first.
12
+ */
13
+ export declare function retry({ service, config, log, }: {
14
+ service: WorktreeService;
15
+ config: Config;
16
+ log: Logger;
17
+ }, task: string): Promise<void>;
package/shell.d.ts ADDED
@@ -0,0 +1,17 @@
1
+ import { type Ps, type SpawnCaptureResult, type SpawnOptions, type SpawnResult } from "webappwiz/system";
2
+ /** What a `Shell` spawns through; the real process by default. */
3
+ export interface ShellOptions {
4
+ ps?: Ps;
5
+ }
6
+ /**
7
+ * Runs the commands a repo configures (the lifecycle hooks) as shell strings
8
+ * rather than argv, since that is how a repo writes them.
9
+ */
10
+ export declare class Shell {
11
+ private readonly ps;
12
+ constructor(opts?: ShellOptions);
13
+ /** Captures output, for commands whose failure has to be reported back. */
14
+ run(command: string, cwd: string, opts?: SpawnOptions): Promise<SpawnCaptureResult>;
15
+ /** Inherits stdio, for commands the agent should watch as they run. */
16
+ stream(command: string, cwd: string, opts?: SpawnOptions): Promise<SpawnResult>;
17
+ }
package/show.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import { type Fs } from "webappwiz/system";
3
+ import type { Worktree } from "./worktree.js";
4
+ import type { WorktreeService } from "./worktree-service.js";
5
+ export interface Details {
6
+ task: string;
7
+ status: string;
8
+ branch: string;
9
+ base: string;
10
+ worktree: string;
11
+ lease: "held" | "stale" | "none";
12
+ ahead: number | null;
13
+ added: number | null;
14
+ removed: number | null;
15
+ age: string | null;
16
+ escalation: string | null;
17
+ /**
18
+ * While escalated to be approved, the question that asks for it, `4`;
19
+ * null otherwise.
20
+ */
21
+ review: string | null;
22
+ plan: string | null;
23
+ /** How the `ARBOR.md` departs from the shape the skill prescribes. */
24
+ planProblems: string[];
25
+ }
26
+ export interface ShowOptions {
27
+ /** Print the details as JSON instead of prose. */
28
+ json?: boolean;
29
+ }
30
+ /**
31
+ * One task in full: what `list` shows for it, plus the `ARBOR.md` its agent
32
+ * left at the worktree root. Reading a tree this way takes no lease, so it
33
+ * cannot knock the agent driving it off its own work.
34
+ */
35
+ export declare function show({ service, fs, log }: {
36
+ service: WorktreeService;
37
+ fs: Fs;
38
+ log: Logger;
39
+ }, task: string, opts?: ShowOptions): Promise<void>;
40
+ /** Configures how {@link TaskDetails} gathers a task's info. */
41
+ export interface TaskDetailsOptions {
42
+ /** What the worktree is read through; the real filesystem by default. */
43
+ fs?: Fs;
44
+ }
45
+ /**
46
+ * Everything there is to say about one task. Split out so `dev` can render the
47
+ * same fields this prints, rather than assembling its own and drifting.
48
+ */
49
+ export declare class TaskDetails {
50
+ private readonly fs;
51
+ constructor(opts?: TaskDetailsOptions);
52
+ get(worktree: Worktree): Promise<Details>;
53
+ }
package/snapshot.d.ts ADDED
@@ -0,0 +1,36 @@
1
+ import type { Fs } from "webappwiz/system";
2
+ import { type Details } from "./show.js";
3
+ import type { TodoState } from "./todo.js";
4
+ import type { Todos } from "./todos.js";
5
+ import type { WorktreeService } from "./worktree-service.js";
6
+ /**
7
+ * Everything one page shows: `todo list`, and `list` and `show` for each
8
+ * task.
9
+ */
10
+ export interface Snapshot {
11
+ /** The repository's directory name, so a page among many says whose it is. */
12
+ repo: string;
13
+ /** Where the repository sits, with the home directory as `~`. */
14
+ path: string;
15
+ /** Past this age a todo is offered for removal rather than recommended. */
16
+ todoStalenessMs: number;
17
+ todos: TodoState[];
18
+ tasks: Details[];
19
+ }
20
+ /**
21
+ * Reads the whole repo the way the CLI's read commands do. Takes no lease, so
22
+ * the page can sit open on a live tree without disturbing the agent in it.
23
+ */
24
+ export declare function snapshot({ service, todos, fs, home, }: {
25
+ service: WorktreeService;
26
+ todos: Todos;
27
+ fs: Fs;
28
+ /** The home directory to show as `~`, when there is one. */
29
+ home?: string;
30
+ }): Promise<Snapshot>;
31
+ /**
32
+ * What the page is actually showing, minus the fields that move on their own.
33
+ * `age` ticks every minute, and hashing it would push to every open page for
34
+ * nothing.
35
+ */
36
+ export declare function fingerprint({ todos, tasks }: Snapshot): string;
package/table.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Columns padded to their widest cell. Padding goes by visible width, not
3
+ * string length: cells carry color codes.
4
+ */
5
+ export declare function table(header: string[], rows: string[][]): string;
package/todo.d.ts ADDED
@@ -0,0 +1,202 @@
1
+ import { type Logger } from "webappwiz/log";
2
+ import type { Fs, Ps } from "webappwiz/system";
3
+ import { type Attachment, type Attachments } from "./attachments.js";
4
+ import type { Todos } from "./todos.js";
5
+ /** What a todo is on disk: one file apiece, so two agents never rewrite one. */
6
+ export interface TodoState {
7
+ id: number;
8
+ /** What is left to do, in a line. */
9
+ subject: string;
10
+ /** Whatever more there is to say about it, or empty when the line is enough. */
11
+ text: string;
12
+ /** Where it stands in the list, 1 at the top: the one to pick up first. */
13
+ position: number;
14
+ /** The task it came up in, or null when a person added it from the main tree. */
15
+ from: string | null;
16
+ createdAt: string;
17
+ /** The task working on it now, or null while it waits to be picked up. */
18
+ takenBy: string | null;
19
+ /** Absolute paths of the files attached, in `todos/<id>/`. */
20
+ files: string[];
21
+ /** What it belongs to, sorted: the areas or goals it adds up to with others. */
22
+ tags: string[];
23
+ }
24
+ /**
25
+ * Work deferred for later: what an agent notes when something outside its
26
+ * task comes up, so it can move on instead of growing the task. Kept under the
27
+ * shared `.git`, so every worktree sees a new one at once, with nothing to
28
+ * commit and nothing to merge.
29
+ */
30
+ export declare class Todo {
31
+ private readonly todos;
32
+ readonly state: TodoState;
33
+ constructor(todos: Todos, state: TodoState);
34
+ get id(): number;
35
+ get subject(): string;
36
+ get text(): string;
37
+ get position(): number;
38
+ get from(): string | null;
39
+ get takenBy(): string | null;
40
+ get createdAt(): Date;
41
+ /** How long it has waited, in milliseconds. */
42
+ get waited(): number;
43
+ /** Marks it as the work of `task`, refusing one another task already has. */
44
+ take(task: string): Promise<Todo>;
45
+ /** Puts it back on the list, as when the task that took it is removed. */
46
+ release(): Promise<Todo>;
47
+ get files(): string[];
48
+ get tags(): string[];
49
+ get attachments(): Attachments;
50
+ /**
51
+ * Says what is left to do in other words, with other files, or higher or
52
+ * lower in the list, keeping its id and history.
53
+ */
54
+ update({ subject, text, position, files, keep, tags, }: TodoChange): Promise<Todo>;
55
+ remove(): Promise<void>;
56
+ }
57
+ /** A tag in use, with how many todos have it. */
58
+ export interface TagState {
59
+ tag: string;
60
+ todos: number;
61
+ }
62
+ /** `tags` deduped and sorted, refusing any not written the one way. */
63
+ export declare function tagList(tags: string[]): string[];
64
+ /** A todo's words, place or files changed: new ones added, some of the old kept. */
65
+ export interface TodoChange {
66
+ /** A new line; the old one stays when this is absent. */
67
+ subject?: string;
68
+ /** New detail, empty for none; the old stays when this is absent. */
69
+ text?: string;
70
+ /** Where to move it in the list; it stays put when this is absent. */
71
+ position?: number;
72
+ /** Files to add. */
73
+ files?: Attachment[];
74
+ /** The paths of the files it has now to keep; absent keeps them all. */
75
+ keep?: string[];
76
+ /** Its tags from now on; the old stay when this is absent. */
77
+ tags?: string[];
78
+ }
79
+ /** What a new todo has besides its subject, all of it optional. */
80
+ export interface TodoNew {
81
+ /** Whatever more there is to say about it. */
82
+ text?: string;
83
+ files?: Attachment[];
84
+ /** Where it goes in the list, pushing those from there down; the bottom by default. */
85
+ position?: number;
86
+ tags?: string[];
87
+ }
88
+ /**
89
+ * A subject and its detail, trimmed. A subject running past one line keeps
90
+ * only the first, the rest leading the detail, so the list stays one line a
91
+ * todo however it was written.
92
+ */
93
+ export declare function wording(subject: string, text: string): {
94
+ subject: string;
95
+ text: string;
96
+ };
97
+ /** What a finished task should be followed by, and what should just go. */
98
+ export interface Recommendation {
99
+ /** The todo to pick up next, or null when there is nothing fresh left. */
100
+ next: Todo | null;
101
+ /** Todos left waiting so long they probably no longer apply. */
102
+ stale: Todo[];
103
+ }
104
+ /**
105
+ * Which todo to take up after `task`: one that came up in it first, since
106
+ * whoever just finished it knows that context best, then one sharing a tag
107
+ * in `settled`, those of the todos it finished, the same area of work, then the open one highest
108
+ * on the list, which is how whoever keeps the list says what matters most.
109
+ * Todos past `staleness` are never recommended, only offered for removal.
110
+ */
111
+ export declare function recommend(todos: Todos, task: string | null, staleness: number, settled?: string[]): Promise<Recommendation>;
112
+ /** The lines `merge` ends with, or none when there is nothing to say. */
113
+ export declare function recommendation({ next, stale }: Recommendation): string[];
114
+ export interface TodoListOptions {
115
+ /** Print the todos as JSON instead of a table. */
116
+ json?: boolean;
117
+ /** Only those no task has taken: the ones free to pick up. */
118
+ open?: boolean;
119
+ /** Only those with any of these tags; every todo when empty. */
120
+ tags?: string[];
121
+ }
122
+ export interface TodoFileOptions {
123
+ /** Files to attach, relative to the current directory or absolute. */
124
+ files?: string[];
125
+ }
126
+ export interface TodoAddOptions extends TodoFileOptions {
127
+ /** Whatever more there is to say than the subject. */
128
+ text?: string;
129
+ /** Where it goes in the list, 1 at the top; the bottom by default. */
130
+ position?: number;
131
+ tags?: string[];
132
+ }
133
+ export declare function todoAdd(deps: {
134
+ todos: Todos;
135
+ log: Logger;
136
+ fs: Fs;
137
+ ps: Ps;
138
+ }, subject: string, from: string | null, { text, files, position, tags }?: TodoAddOptions): Promise<Todo>;
139
+ export declare function todoList({ todos, log }: {
140
+ todos: Todos;
141
+ log: Logger;
142
+ }, { json, open, tags }?: TodoListOptions): Promise<void>;
143
+ export interface TodoShowOptions {
144
+ /** Print the todo as JSON instead of prose. */
145
+ json?: boolean;
146
+ }
147
+ /** One todo in full: what `todo list` shows of it, its files, and its detail. */
148
+ export declare function todoShow({ todos, log }: {
149
+ todos: Todos;
150
+ log: Logger;
151
+ }, id: number, { json }?: TodoShowOptions): Promise<void>;
152
+ export interface TodoUpdateOptions extends TodoFileOptions {
153
+ /** A new line; the old one stays when this is absent or empty. */
154
+ subject?: string;
155
+ /** New detail; the old stays when this is absent or empty. */
156
+ text?: string;
157
+ /** Where to move it in the list, 1 at the top; it stays put when absent. */
158
+ position?: number;
159
+ /** Attached files to drop, by path or by the name they were stored under. */
160
+ removeFiles?: string[];
161
+ /** Tags to add. */
162
+ tags?: string[];
163
+ /** Tags to drop. */
164
+ removeTags?: string[];
165
+ }
166
+ export declare function todoUpdate(deps: {
167
+ todos: Todos;
168
+ log: Logger;
169
+ fs: Fs;
170
+ ps: Ps;
171
+ }, id: number, { subject, text, position, files, removeFiles, tags, removeTags, }?: TodoUpdateOptions): Promise<Todo>;
172
+ /**
173
+ * Takes up todos for `task` after it started, as when one turns out to be
174
+ * part of the work. Every id is checked before any is taken, so one that is
175
+ * gone or another task's refuses them all.
176
+ */
177
+ export declare function todoTake({ todos, log }: {
178
+ todos: Todos;
179
+ log: Logger;
180
+ }, ids: number[], task: string | null): Promise<Todo[]>;
181
+ /**
182
+ * Puts todos back on the list, as when a task is landing without finishing
183
+ * them: say what is left with `arbor todo update` first. From a worktree it
184
+ * gives back only its own task's todos.
185
+ */
186
+ export declare function todoRelease({ todos, log }: {
187
+ todos: Todos;
188
+ log: Logger;
189
+ }, ids: number[], task: string | null): Promise<Todo[]>;
190
+ export declare function todoRemove({ todos, log }: {
191
+ todos: Todos;
192
+ log: Logger;
193
+ }, id: number): Promise<void>;
194
+ export interface TodoTagsOptions {
195
+ /** Print the tags as JSON instead of a table. */
196
+ json?: boolean;
197
+ }
198
+ /** Every tag in use, with how many todos have it: the names to reuse first. */
199
+ export declare function todoTags({ todos, log }: {
200
+ todos: Todos;
201
+ log: Logger;
202
+ }, { json }?: TodoTagsOptions): Promise<void>;