@webappwiz/arbor 0.0.31 → 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.
- package/README.md +2 -2
- package/add.d.ts +1 -1
- package/dev.d.ts +16 -16
- package/git.d.ts +6 -1
- package/index.js +250 -199
- package/merge.d.ts +1 -1
- package/package.json +2 -2
- package/remove.d.ts +1 -1
- package/repository.d.ts +1 -1
- package/snapshot.d.ts +2 -1
- package/todo.d.ts +44 -106
- package/todos.d.ts +75 -0
- package/worktree-service.d.ts +1 -1
- package/worktree.d.ts +46 -46
package/merge.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { Fs, Lock } from "webappwiz/system";
|
|
|
3
3
|
import type { Config } from "./config.js";
|
|
4
4
|
import type { Git } from "./git.js";
|
|
5
5
|
import type { Shell } from "./shell.js";
|
|
6
|
-
import {
|
|
6
|
+
import type { Todos } from "./todos.js";
|
|
7
7
|
import type { WorktreeService } from "./worktree-service.js";
|
|
8
8
|
/**
|
|
9
9
|
* Lands the current worktree's branch on its base branch, trunk unless the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webappwiz/arbor",
|
|
3
|
-
"version": "0.0.
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"access": "public"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"webappwiz": "^0.0.
|
|
23
|
+
"webappwiz": "^0.0.32",
|
|
24
24
|
"zod": "^4.4.3"
|
|
25
25
|
},
|
|
26
26
|
"main": "./index.js",
|
package/remove.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Logger } from "webappwiz/log";
|
|
2
|
-
import type { Todos } from "./
|
|
2
|
+
import type { Todos } from "./todos.js";
|
|
3
3
|
import type { WorktreeService } from "./worktree-service.js";
|
|
4
4
|
export interface RemoveOptions {
|
|
5
5
|
/** Discard the tree even when another agent holds its lease. */
|
package/repository.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { Config } from "./config.js";
|
|
|
4
4
|
import { Git } from "./git.js";
|
|
5
5
|
import { Journal } from "./journal.js";
|
|
6
6
|
import { Shell } from "./shell.js";
|
|
7
|
-
import { Todos } from "./
|
|
7
|
+
import { Todos } from "./todos.js";
|
|
8
8
|
import { WorktreeService } from "./worktree-service.js";
|
|
9
9
|
/** What a command gets to work with, once there is a repository to work in. */
|
|
10
10
|
export interface Repository {
|
package/snapshot.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Fs } from "webappwiz/system";
|
|
2
2
|
import { type Details } from "./show.js";
|
|
3
|
-
import type { TodoState
|
|
3
|
+
import type { TodoState } from "./todo.js";
|
|
4
|
+
import type { Todos } from "./todos.js";
|
|
4
5
|
import type { WorktreeService } from "./worktree-service.js";
|
|
5
6
|
/**
|
|
6
7
|
* Everything one page shows: `todo list`, and `list` and `show` for each
|
package/todo.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { type IdProvider } from "webappwiz/id";
|
|
2
1
|
import { type Logger } from "webappwiz/log";
|
|
3
|
-
import {
|
|
4
|
-
import { type Attachment, Attachments } from "./attachments.js";
|
|
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
5
|
/** What a todo is on disk: one file apiece, so two agents never rewrite one. */
|
|
6
6
|
export interface TodoState {
|
|
7
7
|
id: number;
|
|
@@ -21,37 +21,6 @@ export interface TodoState {
|
|
|
21
21
|
/** What it belongs to, sorted: the areas or goals it adds up to with others. */
|
|
22
22
|
tags: string[];
|
|
23
23
|
}
|
|
24
|
-
/** A tag in use, with how many todos have it. */
|
|
25
|
-
export interface TagState {
|
|
26
|
-
tag: string;
|
|
27
|
-
todos: number;
|
|
28
|
-
}
|
|
29
|
-
/** `tags` deduped and sorted, refusing any not written the one way. */
|
|
30
|
-
export declare function tagList(tags: string[]): string[];
|
|
31
|
-
/** A todo's words, place or files changed: new ones added, some of the old kept. */
|
|
32
|
-
export interface TodoChange {
|
|
33
|
-
/** A new line; the old one stays when this is absent. */
|
|
34
|
-
subject?: string;
|
|
35
|
-
/** New detail, empty for none; the old stays when this is absent. */
|
|
36
|
-
text?: string;
|
|
37
|
-
/** Where to move it in the list; it stays put when this is absent. */
|
|
38
|
-
position?: number;
|
|
39
|
-
/** Files to add. */
|
|
40
|
-
files?: Attachment[];
|
|
41
|
-
/** The paths of the files it has now to keep; absent keeps them all. */
|
|
42
|
-
keep?: string[];
|
|
43
|
-
/** Its tags from now on; the old stay when this is absent. */
|
|
44
|
-
tags?: string[];
|
|
45
|
-
}
|
|
46
|
-
/** What a new todo has besides its subject, all of it optional. */
|
|
47
|
-
export interface TodoNew {
|
|
48
|
-
/** Whatever more there is to say about it. */
|
|
49
|
-
text?: string;
|
|
50
|
-
files?: Attachment[];
|
|
51
|
-
/** Where it goes in the list, pushing those from there down; the bottom by default. */
|
|
52
|
-
position?: number;
|
|
53
|
-
tags?: string[];
|
|
54
|
-
}
|
|
55
24
|
/**
|
|
56
25
|
* Work deferred for later: what an agent notes when something outside its
|
|
57
26
|
* task comes up, so it can move on instead of growing the task. Kept under the
|
|
@@ -85,77 +54,46 @@ export declare class Todo {
|
|
|
85
54
|
update({ subject, text, position, files, keep, tags, }: TodoChange): Promise<Todo>;
|
|
86
55
|
remove(): Promise<void>;
|
|
87
56
|
}
|
|
88
|
-
/**
|
|
89
|
-
export interface
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
ids?: IdProvider;
|
|
57
|
+
/** A tag in use, with how many todos have it. */
|
|
58
|
+
export interface TagState {
|
|
59
|
+
tag: string;
|
|
60
|
+
todos: number;
|
|
93
61
|
}
|
|
94
|
-
/**
|
|
95
|
-
export declare
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
*/
|
|
111
|
-
lock: Lock, opts?: TodosOptions);
|
|
112
|
-
/** Where a todo's files live, beside its record. */
|
|
113
|
-
attachments(id: number): Attachments;
|
|
114
|
-
/** Whether `path` is a file some todo holds, and not a way out of here. */
|
|
115
|
-
owns(path: string): boolean;
|
|
116
|
-
add(subject: string, from: string | null, { text, files, position, tags }?: TodoNew): Promise<Todo>;
|
|
117
|
-
/**
|
|
118
|
-
* Top of the list first. A file that will not parse is skipped rather than
|
|
119
|
-
* fatal, and positions are counted again from 1 as they are read, so a
|
|
120
|
-
* gap or a tie left by a crash, or a todo saved before they had any (it
|
|
121
|
-
* goes below those that do, oldest first), never shows.
|
|
122
|
-
*/
|
|
123
|
-
all(): Promise<Todo[]>;
|
|
124
|
-
find(id: number): Promise<Todo>;
|
|
125
|
-
/** Every tag a todo on the list has, by name, with how many have it. */
|
|
126
|
-
tags(): Promise<TagState[]>;
|
|
127
|
-
/** The todos `task` has taken; what a merge or remove settles. */
|
|
128
|
-
takenBy(task: string): Promise<Todo[]>;
|
|
129
|
-
/**
|
|
130
|
-
* Puts a todo at `position`, 1 at the top, past the bottom meaning the
|
|
131
|
-
* bottom, and numbers the rest around it.
|
|
132
|
-
*/
|
|
133
|
-
move(id: number, position: number): Promise<Todo>;
|
|
134
|
-
/**
|
|
135
|
-
* @internal Rewrites one todo as it stands on disk now, under the lock, so
|
|
136
|
-
* a change to its words or who has it never undoes a move made meanwhile.
|
|
137
|
-
*/
|
|
138
|
-
revise(id: number, change: (state: TodoState) => TodoState): Promise<Todo>;
|
|
139
|
-
/** @internal Writes one todo. Rename makes the swap atomic for readers. */
|
|
140
|
-
save(state: TodoState): Promise<Todo>;
|
|
141
|
-
/** @internal Removes one, moving those below it up to close the gap. */
|
|
142
|
-
delete(id: number): Promise<void>;
|
|
143
|
-
private locked;
|
|
144
|
-
/** One todo as it stands on disk; call it under the lock. */
|
|
145
|
-
private state;
|
|
146
|
-
/**
|
|
147
|
-
* Slots `state` in at `position` among the rest and saves whichever moved.
|
|
148
|
-
* Call it under the lock.
|
|
149
|
-
*/
|
|
150
|
-
private place;
|
|
151
|
-
/** Every todo in list order, with the positions it has on disk. */
|
|
152
|
-
private stored;
|
|
153
|
-
/** Saves each todo in `order` whose position on disk is not its place there. */
|
|
154
|
-
private renumber;
|
|
155
|
-
private path;
|
|
156
|
-
private lastId;
|
|
157
|
-
private read;
|
|
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[];
|
|
158
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
|
+
};
|
|
159
97
|
/** What a finished task should be followed by, and what should just go. */
|
|
160
98
|
export interface Recommendation {
|
|
161
99
|
/** The todo to pick up next, or null when there is nothing fresh left. */
|
|
@@ -178,8 +116,8 @@ export interface TodoListOptions {
|
|
|
178
116
|
json?: boolean;
|
|
179
117
|
/** Only those no task has taken: the ones free to pick up. */
|
|
180
118
|
open?: boolean;
|
|
181
|
-
/** Only those with
|
|
182
|
-
|
|
119
|
+
/** Only those with any of these tags; every todo when empty. */
|
|
120
|
+
tags?: string[];
|
|
183
121
|
}
|
|
184
122
|
export interface TodoFileOptions {
|
|
185
123
|
/** Files to attach, relative to the current directory or absolute. */
|
|
@@ -201,7 +139,7 @@ export declare function todoAdd(deps: {
|
|
|
201
139
|
export declare function todoList({ todos, log }: {
|
|
202
140
|
todos: Todos;
|
|
203
141
|
log: Logger;
|
|
204
|
-
}, { json, open,
|
|
142
|
+
}, { json, open, tags }?: TodoListOptions): Promise<void>;
|
|
205
143
|
export interface TodoShowOptions {
|
|
206
144
|
/** Print the todo as JSON instead of prose. */
|
|
207
145
|
json?: boolean;
|
package/todos.d.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { type IdProvider } from "webappwiz/id";
|
|
2
|
+
import { type Fs, type Lock } from "webappwiz/system";
|
|
3
|
+
import { Attachments } from "./attachments.js";
|
|
4
|
+
import { type TagState, Todo, type TodoNew, type TodoState } from "./todo.js";
|
|
5
|
+
/** Every todo in the repo, one JSON file each under `.git/arbor/todos`. */
|
|
6
|
+
export declare class Todos {
|
|
7
|
+
readonly dir: string;
|
|
8
|
+
/**
|
|
9
|
+
* Held while numbering a new todo, so two agents never share an id, and
|
|
10
|
+
* while writing one, so a move renumbering the rest never loses to a
|
|
11
|
+
* write of a position it just changed.
|
|
12
|
+
*/
|
|
13
|
+
private readonly lock;
|
|
14
|
+
private readonly fs;
|
|
15
|
+
private readonly ids;
|
|
16
|
+
constructor(dir: string,
|
|
17
|
+
/**
|
|
18
|
+
* Held while numbering a new todo, so two agents never share an id, and
|
|
19
|
+
* while writing one, so a move renumbering the rest never loses to a
|
|
20
|
+
* write of a position it just changed.
|
|
21
|
+
*/
|
|
22
|
+
lock: Lock, opts?: TodosOptions);
|
|
23
|
+
/** Where a todo's files live, beside its record. */
|
|
24
|
+
attachments(id: number): Attachments;
|
|
25
|
+
/** Whether `path` is a file some todo holds, and not a way out of here. */
|
|
26
|
+
owns(path: string): boolean;
|
|
27
|
+
add(subject: string, from: string | null, { text, files, position, tags }?: TodoNew): Promise<Todo>;
|
|
28
|
+
/**
|
|
29
|
+
* Top of the list first. A file that will not parse is skipped rather than
|
|
30
|
+
* fatal, and positions are counted again from 1 as they are read, so a
|
|
31
|
+
* gap or a tie left by a crash, or a todo saved before they had any (it
|
|
32
|
+
* goes below those that do, oldest first), never shows.
|
|
33
|
+
*/
|
|
34
|
+
all(): Promise<Todo[]>;
|
|
35
|
+
find(id: number): Promise<Todo>;
|
|
36
|
+
/** Every tag a todo on the list has, by name, with how many have it. */
|
|
37
|
+
tags(): Promise<TagState[]>;
|
|
38
|
+
/** The todos `task` has taken; what a merge or remove settles. */
|
|
39
|
+
takenBy(task: string): Promise<Todo[]>;
|
|
40
|
+
/**
|
|
41
|
+
* Puts a todo at `position`, 1 at the top, past the bottom meaning the
|
|
42
|
+
* bottom, and numbers the rest around it.
|
|
43
|
+
*/
|
|
44
|
+
move(id: number, position: number): Promise<Todo>;
|
|
45
|
+
/**
|
|
46
|
+
* @internal Rewrites one todo as it stands on disk now, under the lock, so
|
|
47
|
+
* a change to its words or who has it never undoes a move made meanwhile.
|
|
48
|
+
*/
|
|
49
|
+
revise(id: number, change: (state: TodoState) => TodoState): Promise<Todo>;
|
|
50
|
+
/** @internal Writes one todo. Rename makes the swap atomic for readers. */
|
|
51
|
+
save(state: TodoState): Promise<Todo>;
|
|
52
|
+
/** @internal Removes one, moving those below it up to close the gap. */
|
|
53
|
+
delete(id: number): Promise<void>;
|
|
54
|
+
private locked;
|
|
55
|
+
/** One todo as it stands on disk; call it under the lock. */
|
|
56
|
+
private state;
|
|
57
|
+
/**
|
|
58
|
+
* Slots `state` in at `position` among the rest and saves whichever moved.
|
|
59
|
+
* Call it under the lock.
|
|
60
|
+
*/
|
|
61
|
+
private place;
|
|
62
|
+
/** Every todo in list order, with the positions it has on disk. */
|
|
63
|
+
private stored;
|
|
64
|
+
/** Saves each todo in `order` whose position on disk is not its place there. */
|
|
65
|
+
private renumber;
|
|
66
|
+
private path;
|
|
67
|
+
private lastId;
|
|
68
|
+
private read;
|
|
69
|
+
}
|
|
70
|
+
/** What `Todos` is stored through; the real filesystem by default. */
|
|
71
|
+
export interface TodosOptions {
|
|
72
|
+
fs?: Fs;
|
|
73
|
+
/** Names stored files apart; a test counts. */
|
|
74
|
+
ids?: IdProvider;
|
|
75
|
+
}
|
package/worktree-service.d.ts
CHANGED
|
@@ -29,7 +29,7 @@ export declare class WorktreeService {
|
|
|
29
29
|
/** Where a task's worktree lives: a sibling of the repo, never inside it. */
|
|
30
30
|
pathFor(task: string): string;
|
|
31
31
|
branchFor(task: string): string;
|
|
32
|
-
taskFor(branch: string): string | null;
|
|
32
|
+
taskFor(branch: string | null): string | null;
|
|
33
33
|
recordPath(task: string): string;
|
|
34
34
|
/** Always answers; the returned worktree's status says what was found. */
|
|
35
35
|
find(task: string): Promise<Worktree>;
|
package/worktree.d.ts
CHANGED
|
@@ -1,51 +1,5 @@
|
|
|
1
1
|
import type { GitResult } from "./git.js";
|
|
2
2
|
import type { WorktreeService } from "./worktree-service.js";
|
|
3
|
-
/** The status a task's own record carries. */
|
|
4
|
-
export type RecordStatus = "working" | "merging" | "escalated";
|
|
5
|
-
/** What a task's record stores about who is driving it. */
|
|
6
|
-
export interface LeaseState {
|
|
7
|
-
pid: number;
|
|
8
|
-
hostname: string;
|
|
9
|
-
heartbeatAt: string;
|
|
10
|
-
}
|
|
11
|
-
export interface Escalation {
|
|
12
|
-
reason: string;
|
|
13
|
-
at: string;
|
|
14
|
-
/**
|
|
15
|
-
* For a task handed over to be approved rather than to answer questions,
|
|
16
|
-
* the question in its plan that asks for the approval, `4`.
|
|
17
|
-
*/
|
|
18
|
-
review?: string;
|
|
19
|
-
}
|
|
20
|
-
export interface TaskState {
|
|
21
|
-
task: string;
|
|
22
|
-
branch: string;
|
|
23
|
-
worktree: string;
|
|
24
|
-
/** The branch this task lands on. Absent in old records: trunk. */
|
|
25
|
-
base?: string;
|
|
26
|
-
status: RecordStatus;
|
|
27
|
-
lease: LeaseState | null;
|
|
28
|
-
mergeAttempts: number;
|
|
29
|
-
createdAt: string;
|
|
30
|
-
updatedAt: string;
|
|
31
|
-
escalations?: Escalation[];
|
|
32
|
-
}
|
|
33
|
-
/**
|
|
34
|
-
* Everything a name can turn out to be. The record's own status when the
|
|
35
|
-
* task is intact, and otherwise the way in which it is not.
|
|
36
|
-
*/
|
|
37
|
-
export type WorktreeStatus = RecordStatus | "absent" | "removed" | "orphaned" | "stray" | "unrecorded" | "unknown";
|
|
38
|
-
/** What the service found on disk. */
|
|
39
|
-
export interface WorktreeSnapshot {
|
|
40
|
-
task: string;
|
|
41
|
-
branch: string;
|
|
42
|
-
path: string;
|
|
43
|
-
state: TaskState | null;
|
|
44
|
-
exists: boolean;
|
|
45
|
-
hasBranch: boolean;
|
|
46
|
-
removedAt: string | null;
|
|
47
|
-
corrupt: boolean;
|
|
48
|
-
}
|
|
49
3
|
/**
|
|
50
4
|
* One task, whether or not it is still there. Commands ask the service for
|
|
51
5
|
* one of these and read its status rather than assembling the same handful of
|
|
@@ -93,3 +47,49 @@ export declare class Worktree {
|
|
|
93
47
|
uncommitted(): Promise<string[]>;
|
|
94
48
|
interruptedOps(): Promise<string[]>;
|
|
95
49
|
}
|
|
50
|
+
/** The status a task's own record carries. */
|
|
51
|
+
export type RecordStatus = "working" | "merging" | "escalated";
|
|
52
|
+
/** What a task's record stores about who is driving it. */
|
|
53
|
+
export interface LeaseState {
|
|
54
|
+
pid: number;
|
|
55
|
+
hostname: string;
|
|
56
|
+
heartbeatAt: string;
|
|
57
|
+
}
|
|
58
|
+
export interface Escalation {
|
|
59
|
+
reason: string;
|
|
60
|
+
at: string;
|
|
61
|
+
/**
|
|
62
|
+
* For a task handed over to be approved rather than to answer questions,
|
|
63
|
+
* the question in its plan that asks for the approval, `4`.
|
|
64
|
+
*/
|
|
65
|
+
review?: string;
|
|
66
|
+
}
|
|
67
|
+
export interface TaskState {
|
|
68
|
+
task: string;
|
|
69
|
+
branch: string;
|
|
70
|
+
worktree: string;
|
|
71
|
+
/** The branch this task lands on. Absent in old records: trunk. */
|
|
72
|
+
base?: string;
|
|
73
|
+
status: RecordStatus;
|
|
74
|
+
lease: LeaseState | null;
|
|
75
|
+
mergeAttempts: number;
|
|
76
|
+
createdAt: string;
|
|
77
|
+
updatedAt: string;
|
|
78
|
+
escalations?: Escalation[];
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Everything a name can turn out to be. The record's own status when the
|
|
82
|
+
* task is intact, and otherwise the way in which it is not.
|
|
83
|
+
*/
|
|
84
|
+
export type WorktreeStatus = RecordStatus | "absent" | "removed" | "orphaned" | "stray" | "unrecorded" | "unknown";
|
|
85
|
+
/** What the service found on disk. */
|
|
86
|
+
export interface WorktreeSnapshot {
|
|
87
|
+
task: string;
|
|
88
|
+
branch: string;
|
|
89
|
+
path: string;
|
|
90
|
+
state: TaskState | null;
|
|
91
|
+
exists: boolean;
|
|
92
|
+
hasBranch: boolean;
|
|
93
|
+
removedAt: string | null;
|
|
94
|
+
corrupt: boolean;
|
|
95
|
+
}
|