@webappwiz/arbor 0.0.28 → 0.0.30
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/AGENTS.md +7 -0
- package/README.md +34 -12
- package/add.test.ts +125 -0
- package/add.ts +192 -0
- package/age.ts +11 -0
- package/arbor.test.ts +145 -0
- package/arbor.ts +429 -0
- package/assets.d.ts +9 -0
- package/attachments.ts +87 -0
- package/build.ts +98 -0
- package/claim.test.ts +89 -0
- package/claim.ts +73 -0
- package/components.json +21 -0
- package/config.ts +58 -0
- package/dev/api.ts +120 -0
- package/dev/app.test.tsx +645 -0
- package/dev/app.tsx +80 -0
- package/dev/assets.ts +16 -0
- package/dev/build/main.txt +68 -0
- package/dev/build/shell.txt +21 -0
- package/dev/build/styles.txt +2263 -0
- package/dev/components/ui/badge.tsx +51 -0
- package/dev/components/ui/button.tsx +57 -0
- package/dev/components/ui/dialog.tsx +154 -0
- package/dev/components/ui/empty.tsx +100 -0
- package/dev/components/ui/input-group.tsx +155 -0
- package/dev/components/ui/input.tsx +19 -0
- package/dev/components/ui/progress.tsx +80 -0
- package/dev/components/ui/textarea.tsx +17 -0
- package/dev/components/ui/toast.tsx +227 -0
- package/dev/feed.ts +69 -0
- package/dev/files.tsx +191 -0
- package/dev/index.html +21 -0
- package/dev/lib/utils.ts +7 -0
- package/dev/main.tsx +22 -0
- package/dev/markdown.test.tsx +133 -0
- package/dev/markdown.tsx +124 -0
- package/dev/mentions.tsx +319 -0
- package/dev/shadcn.css +649 -0
- package/dev/styles.css +107 -0
- package/dev/tags.tsx +44 -0
- package/dev/tasks.tsx +151 -0
- package/dev/todos.tsx +759 -0
- package/dev.test.ts +439 -0
- package/dev.ts +386 -0
- package/e2e.test.ts +256 -0
- package/escalate.test.ts +76 -0
- package/escalate.ts +112 -0
- package/exit.test.ts +71 -0
- package/exit.ts +74 -0
- package/git.ts +294 -0
- package/index.ts +9 -0
- package/journal.ts +89 -0
- package/list.test.ts +82 -0
- package/list.ts +137 -0
- package/load-config.test.ts +121 -0
- package/load-config.ts +62 -0
- package/log.test.ts +65 -0
- package/log.ts +40 -0
- package/merge.test.ts +332 -0
- package/merge.ts +283 -0
- package/package.json +32 -15
- package/path.test.ts +35 -0
- package/path.ts +38 -0
- package/plan.test.ts +318 -0
- package/plan.ts +302 -0
- package/progress.test.ts +54 -0
- package/progress.ts +31 -0
- package/remove.test.ts +68 -0
- package/remove.ts +88 -0
- package/repository.ts +83 -0
- package/retry.test.ts +43 -0
- package/retry.ts +48 -0
- package/shell.ts +44 -0
- package/show.test.ts +91 -0
- package/show.ts +151 -0
- package/snapshot.test.ts +33 -0
- package/snapshot.ts +84 -0
- package/table.ts +20 -0
- package/testing.ts +229 -0
- package/todo.test.ts +490 -0
- package/todo.ts +834 -0
- package/wait.test.ts +61 -0
- package/wait.ts +77 -0
- package/worktree-service.test.ts +158 -0
- package/worktree-service.ts +193 -0
- package/worktree.ts +239 -0
- package/add.d.ts +0 -23
- package/age.d.ts +0 -2
- package/arbor.d.ts +0 -11
- package/attachments.d.ts +0 -36
- package/claim.d.ts +0 -11
- package/config.d.ts +0 -55
- package/config.js +0 -7
- package/dev/assets.d.ts +0 -11
- package/dev.d.ts +0 -47
- package/escalate.d.ts +0 -28
- package/exit.d.ts +0 -42
- package/git.d.ts +0 -77
- package/index.d.ts +0 -2
- package/index.js +0 -4669
- package/journal.d.ts +0 -29
- package/list.d.ts +0 -18
- package/load-config.d.ts +0 -15
- package/log.d.ts +0 -14
- package/merge.d.ts +0 -24
- package/path.d.ts +0 -13
- package/plan.d.ts +0 -83
- package/remove.d.ts +0 -19
- package/repository.d.ts +0 -25
- package/retry.d.ts +0 -17
- package/shell.d.ts +0 -17
- package/show.d.ts +0 -53
- package/snapshot.d.ts +0 -31
- package/table.d.ts +0 -5
- package/todo.d.ts +0 -230
- package/wait.d.ts +0 -22
- package/worktree-service.d.ts +0 -51
- package/worktree.d.ts +0 -95
package/worktree.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import type { GitResult } from "./git";
|
|
2
|
+
import type { WorktreeService } from "./worktree-service";
|
|
3
|
+
|
|
4
|
+
/** The status a task's own record carries. */
|
|
5
|
+
export type RecordStatus = "working" | "merging" | "escalated";
|
|
6
|
+
|
|
7
|
+
/** What a task's record stores about who is driving it. */
|
|
8
|
+
export interface LeaseState {
|
|
9
|
+
pid: number;
|
|
10
|
+
hostname: string;
|
|
11
|
+
heartbeatAt: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Escalation {
|
|
15
|
+
reason: string;
|
|
16
|
+
at: string;
|
|
17
|
+
/**
|
|
18
|
+
* For a task handed over to be approved rather than to answer questions,
|
|
19
|
+
* the question in its plan that asks for the approval, `4`.
|
|
20
|
+
*/
|
|
21
|
+
review?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface TaskState {
|
|
25
|
+
task: string;
|
|
26
|
+
branch: string;
|
|
27
|
+
worktree: string;
|
|
28
|
+
/** The branch this task lands on. Absent in old records: trunk. */
|
|
29
|
+
base?: string;
|
|
30
|
+
status: RecordStatus;
|
|
31
|
+
lease: LeaseState | null;
|
|
32
|
+
mergeAttempts: number;
|
|
33
|
+
createdAt: string;
|
|
34
|
+
updatedAt: string;
|
|
35
|
+
escalations?: Escalation[];
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Everything a name can turn out to be. The record's own status when the
|
|
40
|
+
* task is intact, and otherwise the way in which it is not.
|
|
41
|
+
*/
|
|
42
|
+
export type WorktreeStatus =
|
|
43
|
+
| RecordStatus
|
|
44
|
+
| "absent" // nothing under this name, and no memory of one
|
|
45
|
+
| "removed" // discarded earlier; still remembered
|
|
46
|
+
| "orphaned" // a record whose directory is gone
|
|
47
|
+
| "stray" // a leftover branch, with no directory or record
|
|
48
|
+
| "unrecorded" // a directory with no record
|
|
49
|
+
| "unknown"; // a record that would not parse
|
|
50
|
+
|
|
51
|
+
/** What the service found on disk. */
|
|
52
|
+
export interface WorktreeSnapshot {
|
|
53
|
+
task: string;
|
|
54
|
+
branch: string;
|
|
55
|
+
path: string;
|
|
56
|
+
state: TaskState | null;
|
|
57
|
+
exists: boolean;
|
|
58
|
+
hasBranch: boolean;
|
|
59
|
+
removedAt: string | null;
|
|
60
|
+
corrupt: boolean;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* One task, whether or not it is still there. Commands ask the service for
|
|
65
|
+
* one of these and read its status rather than assembling the same handful of
|
|
66
|
+
* existence checks themselves.
|
|
67
|
+
*/
|
|
68
|
+
export class Worktree {
|
|
69
|
+
// scry-ignore classes-over-function-exports: the service is what hands out Worktrees, so the two are one design in two files, and an interface here would have one implementation and still expose the service's Git
|
|
70
|
+
constructor(
|
|
71
|
+
private readonly service: WorktreeService,
|
|
72
|
+
private readonly snapshot: WorktreeSnapshot,
|
|
73
|
+
) {}
|
|
74
|
+
|
|
75
|
+
get task(): string {
|
|
76
|
+
return this.snapshot.task;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
get branch(): string {
|
|
80
|
+
return this.snapshot.branch;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Where this task's work lands: its recorded base branch, else trunk. */
|
|
84
|
+
get base(): string {
|
|
85
|
+
return this.snapshot.state?.base ?? this.service.trunk;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
get path(): string {
|
|
89
|
+
return this.snapshot.path;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
get state(): TaskState | null {
|
|
93
|
+
return this.snapshot.state;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
get lease(): LeaseState | null {
|
|
97
|
+
return this.snapshot.state?.lease ?? null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
get leaseHeld(): boolean {
|
|
101
|
+
// The pid check matters because every arbor command is its own short-lived
|
|
102
|
+
// process: without it a tree would stay locked for the whole staleness
|
|
103
|
+
// window after a command that merely finished, and `add` would block the
|
|
104
|
+
// `merge` that follows it.
|
|
105
|
+
const { lease } = this;
|
|
106
|
+
const { ps, config } = this.service;
|
|
107
|
+
if (!lease) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
if (Date.now() - Date.parse(lease.heartbeatAt) >= config.leaseStalenessMs) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
return lease.hostname === ps.hostname ? ps.alive(lease.pid) : true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
get leaseStatus(): "held" | "stale" | "none" {
|
|
117
|
+
return !this.lease ? "none" : this.leaseHeld ? "held" : "stale";
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
get leaseOurs(): boolean {
|
|
121
|
+
const { ps } = this.service;
|
|
122
|
+
return this.lease?.pid === ps.pid && this.lease.hostname === ps.hostname;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
get leaseHeldByOther(): boolean {
|
|
126
|
+
return this.leaseHeld && !this.leaseOurs;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
get mergeAttempts(): number {
|
|
130
|
+
return this.snapshot.state?.mergeAttempts ?? 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
get exists(): boolean {
|
|
134
|
+
return this.snapshot.exists;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
get hasBranch(): boolean {
|
|
138
|
+
return this.snapshot.hasBranch;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
get removedAt(): string | null {
|
|
142
|
+
return this.snapshot.removedAt;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
get status(): WorktreeStatus {
|
|
146
|
+
const { state, exists, hasBranch, removedAt, corrupt } = this.snapshot;
|
|
147
|
+
if (corrupt) {
|
|
148
|
+
return "unknown";
|
|
149
|
+
}
|
|
150
|
+
if (state) {
|
|
151
|
+
return exists ? state.status : "orphaned";
|
|
152
|
+
}
|
|
153
|
+
if (exists) {
|
|
154
|
+
return "unrecorded";
|
|
155
|
+
}
|
|
156
|
+
if (hasBranch) {
|
|
157
|
+
return "stray";
|
|
158
|
+
}
|
|
159
|
+
return removedAt ? "removed" : "absent";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
get gone(): boolean {
|
|
163
|
+
return this.status === "absent" || this.status === "removed";
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merges changes into the record and writes it. Works on a name that has no
|
|
168
|
+
* record yet, which is how `add` writes the first one and how `claim`
|
|
169
|
+
* rebuilds one from a worktree found on disk.
|
|
170
|
+
*/
|
|
171
|
+
async save(changes: Partial<TaskState> = {}): Promise<Worktree> {
|
|
172
|
+
const now = new Date().toISOString();
|
|
173
|
+
const state: TaskState = {
|
|
174
|
+
task: this.task,
|
|
175
|
+
branch: this.branch,
|
|
176
|
+
worktree: this.path,
|
|
177
|
+
status: "working",
|
|
178
|
+
lease: null,
|
|
179
|
+
mergeAttempts: 0,
|
|
180
|
+
createdAt: now,
|
|
181
|
+
...(this.snapshot.state ?? {}),
|
|
182
|
+
...changes,
|
|
183
|
+
updatedAt: now,
|
|
184
|
+
};
|
|
185
|
+
await this.service.saveRecord(state);
|
|
186
|
+
return new Worktree(this.service, {
|
|
187
|
+
...this.snapshot,
|
|
188
|
+
state,
|
|
189
|
+
corrupt: false,
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
take(changes: Partial<TaskState> = {}): Promise<Worktree> {
|
|
194
|
+
const { ps } = this.service;
|
|
195
|
+
return this.save({
|
|
196
|
+
...changes,
|
|
197
|
+
lease: {
|
|
198
|
+
pid: ps.pid,
|
|
199
|
+
hostname: ps.hostname,
|
|
200
|
+
heartbeatAt: new Date().toISOString(),
|
|
201
|
+
},
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Re-reads from disk. `merge` needs this: the record is the truth. */
|
|
206
|
+
reload(): Promise<Worktree> {
|
|
207
|
+
return this.service.find(this.task);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Removes the directory, the branch, and the record; remembers the name. */
|
|
211
|
+
discard(): Promise<GitResult> {
|
|
212
|
+
return this.service.discard(this);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
commitsAhead(): Promise<number | null> {
|
|
216
|
+
return this.service.git.commitsAhead(this.base, this.branch);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
diffStat(): Promise<{ added: number; removed: number } | null> {
|
|
220
|
+
return this.service.git.diffStat(this.base, this.branch);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Committed and uncommitted paths this task touches; null without a branch. */
|
|
224
|
+
changedFiles(): Promise<string[] | null> {
|
|
225
|
+
return this.service.git.changedFiles(
|
|
226
|
+
this.base,
|
|
227
|
+
this.branch,
|
|
228
|
+
this.exists ? this.path : null,
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
uncommitted(): Promise<string[]> {
|
|
233
|
+
return this.service.git.porcelain(this.path);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
interruptedOps(): Promise<string[]> {
|
|
237
|
+
return this.service.git.interruptedOps(this.path);
|
|
238
|
+
}
|
|
239
|
+
}
|
package/add.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { type Logger } from "webappwiz/log";
|
|
2
|
-
import type { Fs } from "webappwiz/system";
|
|
3
|
-
import type { Config } from "./config.js";
|
|
4
|
-
import type { Shell } from "./shell.js";
|
|
5
|
-
import type { Todos } from "./todo.js";
|
|
6
|
-
import type { WorktreeService } from "./worktree-service.js";
|
|
7
|
-
export interface AddOptions {
|
|
8
|
-
/** Branch the task starts from and merges onto. Defaults to the trunk. */
|
|
9
|
-
base?: string;
|
|
10
|
-
/**
|
|
11
|
-
* The todos this task takes up: their words seed the plan's Goal, and
|
|
12
|
-
* nobody else can take them while the task lives.
|
|
13
|
-
*/
|
|
14
|
-
todos?: number[];
|
|
15
|
-
}
|
|
16
|
-
export declare function add({ service, shell, config, log, fs, todos, }: {
|
|
17
|
-
service: WorktreeService;
|
|
18
|
-
shell: Shell;
|
|
19
|
-
config: Config;
|
|
20
|
-
log: Logger;
|
|
21
|
-
fs: Fs;
|
|
22
|
-
todos: Todos;
|
|
23
|
-
}, task: string, { base, todos: ids }?: AddOptions): Promise<void>;
|
package/age.d.ts
DELETED
package/arbor.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type Deps } from "webappwiz/cmd";
|
|
2
|
-
import type { Fs } from "webappwiz/system";
|
|
3
|
-
import type { Assets } from "./dev/assets.js";
|
|
4
|
-
import { type Repository } from "./repository.js";
|
|
5
|
-
/** Everything `arbor` is run with, before the repository middleware adds to it. */
|
|
6
|
-
export interface ArborDeps extends Deps {
|
|
7
|
-
fs: Fs;
|
|
8
|
-
/** The built page `dev` serves as its preview. */
|
|
9
|
-
assets: Assets;
|
|
10
|
-
}
|
|
11
|
-
export declare const arbor: import("webappwiz/cmd").Cli<ArborDeps, ArborDeps & Repository>;
|
package/attachments.d.ts
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import { type IdProvider } from "webappwiz/id";
|
|
2
|
-
import type { Fs, Ps } from "webappwiz/system";
|
|
3
|
-
/** A file handed over, as bytes so a pasted image needs no temp file. */
|
|
4
|
-
export interface Attachment {
|
|
5
|
-
/** What it was called, kept in the stored name so the files stay apart. */
|
|
6
|
-
name: string;
|
|
7
|
-
bytes: Uint8Array;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
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
|
-
*/
|
|
15
|
-
export declare class Attachments {
|
|
16
|
-
readonly dir: string;
|
|
17
|
-
private readonly fs;
|
|
18
|
-
private readonly ids;
|
|
19
|
-
constructor(dir: string, fs: Fs, ids?: IdProvider);
|
|
20
|
-
/** Copies each file in, returning the absolute path each was stored at. */
|
|
21
|
-
store(files: Attachment[]): Promise<string[]>;
|
|
22
|
-
/** Whether `path` is one of this folder's files, and not a way out of it. */
|
|
23
|
-
owns(path: string): boolean;
|
|
24
|
-
/** Deletes the files given that are this folder's; others are left alone. */
|
|
25
|
-
remove(paths: string[]): Promise<void>;
|
|
26
|
-
/** Deletes the folder and everything in it. */
|
|
27
|
-
clear(): Promise<void>;
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Reads files named on the command line, relative to the current directory or
|
|
31
|
-
* absolute, refusing the whole command over one that cannot be read.
|
|
32
|
-
*/
|
|
33
|
-
export declare function readFiles({ fs, ps }: {
|
|
34
|
-
fs: Fs;
|
|
35
|
-
ps: Ps;
|
|
36
|
-
}, paths: string[]): Promise<Attachment[]>;
|
package/claim.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { type Logger } from "webappwiz/log";
|
|
2
|
-
import type { WorktreeService } from "./worktree-service.js";
|
|
3
|
-
/**
|
|
4
|
-
* The resume entry point: a fresh agent thread picking up existing work. An
|
|
5
|
-
* escalated task goes back to `working`, since someone is on it again; its
|
|
6
|
-
* merge budget stays as it was, which only `retry` refills.
|
|
7
|
-
*/
|
|
8
|
-
export declare function claim({ service, log }: {
|
|
9
|
-
service: WorktreeService;
|
|
10
|
-
log: Logger;
|
|
11
|
-
}, task: string): Promise<void>;
|
package/config.d.ts
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
export interface Config {
|
|
2
|
-
/**
|
|
3
|
-
* The integration branch name. `merge` rebases a task onto this branch and
|
|
4
|
-
* then fast-forwards it to the result; new worktrees start from it.
|
|
5
|
-
*/
|
|
6
|
-
trunk: string;
|
|
7
|
-
/** Directory holding one worktree per task, a sibling of the repo. */
|
|
8
|
-
worktreeRoot: string;
|
|
9
|
-
/** Command run by `add` in the new worktree, via `sh -c`. */
|
|
10
|
-
postCheckout: string | null;
|
|
11
|
-
/**
|
|
12
|
-
* Command run by `merge` after the rebase, before `preMerge`, via `sh -c`.
|
|
13
|
-
* A rebase can bring in a dependency the worktree has never installed, and
|
|
14
|
-
* whatever `preMerge` runs needs it.
|
|
15
|
-
*/
|
|
16
|
-
postRewrite: string | null;
|
|
17
|
-
/**
|
|
18
|
-
* Command run by `merge` after the rebase, via `sh -c`. The last gate before
|
|
19
|
-
* the branch lands: a nonzero exit rolls the branch back and leaves the base
|
|
20
|
-
* untouched. Tests are the obvious thing to put here, but arbor has no
|
|
21
|
-
* opinion; a repo with nothing to run leaves it null and merges on a green
|
|
22
|
-
* rebase alone.
|
|
23
|
-
*/
|
|
24
|
-
preMerge: string | null;
|
|
25
|
-
/**
|
|
26
|
-
* Command run by `merge` in the main tree after the base fast-forwards, via
|
|
27
|
-
* `sh -c`. The branch has already landed and the worktree is gone: a nonzero
|
|
28
|
-
* exit reports the failure but rolls nothing back.
|
|
29
|
-
*/
|
|
30
|
-
postMerge: string | null;
|
|
31
|
-
/** How long since its last heartbeat before a task's lease is up for grabs. */
|
|
32
|
-
leaseStalenessMs: number;
|
|
33
|
-
/** Failed `merge` attempts a task gets before it must escalate or be removed. */
|
|
34
|
-
mergeRetryCount: number;
|
|
35
|
-
/**
|
|
36
|
-
* How many removed task names to keep, so `remove` can say "already removed"
|
|
37
|
-
* rather than "never existed". A flat cap with no age policy: losing the
|
|
38
|
-
* oldest costs a nicer message and nothing else.
|
|
39
|
-
*/
|
|
40
|
-
removedCapacity: number;
|
|
41
|
-
/** How many entries `arbor log` keeps before the oldest fall off. */
|
|
42
|
-
logCapacity: number;
|
|
43
|
-
/**
|
|
44
|
-
* How long a todo can wait before `merge` stops recommending it and offers
|
|
45
|
-
* it for removal instead: work deferred that long has usually been done
|
|
46
|
-
* some other way, or stopped mattering.
|
|
47
|
-
*/
|
|
48
|
-
todoStalenessMs: number;
|
|
49
|
-
}
|
|
50
|
-
/**
|
|
51
|
-
* Identity, for the types. `export default defineConfig({ ... })` in
|
|
52
|
-
* `arbor.config.ts` gets the key names and their types checked, and completion
|
|
53
|
-
* while writing it; a bare object literal gets neither.
|
|
54
|
-
*/
|
|
55
|
-
export declare function defineConfig(config: Partial<Config>): Partial<Config>;
|
package/config.js
DELETED
package/dev/assets.d.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
/** The three files the browser asks for, as the server hands them over. */
|
|
2
|
-
export interface Assets {
|
|
3
|
-
/** The page, which asks for the other two. */
|
|
4
|
-
shell: string;
|
|
5
|
-
/** The React app, bundled for the browser. */
|
|
6
|
-
script: string;
|
|
7
|
-
/** The stylesheet, with Tailwind already compiled. */
|
|
8
|
-
styles: string;
|
|
9
|
-
}
|
|
10
|
-
/** The page this package ships. Serve each field's contents as-is. */
|
|
11
|
-
export declare const assets: Assets;
|
package/dev.d.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import { type AsyncResource } from "webappwiz/disposable";
|
|
2
|
-
import type { Logger } from "webappwiz/log";
|
|
3
|
-
import { type Fs, type PortProvider } from "webappwiz/system";
|
|
4
|
-
import type { Assets } from "./dev/assets.js";
|
|
5
|
-
import type { Journal } from "./journal.js";
|
|
6
|
-
import type { Todos } from "./todo.js";
|
|
7
|
-
import type { WorktreeService } from "./worktree-service.js";
|
|
8
|
-
/** Preferred, not required: `dev` moves up from here when it is taken. */
|
|
9
|
-
export declare const DEFAULT_PORT = 4269;
|
|
10
|
-
/** How far above the port asked for `dev` will look before giving up. */
|
|
11
|
-
export declare const PORT_SPAN = 20;
|
|
12
|
-
/** What `dev` lets a caller choose. */
|
|
13
|
-
export interface DevOptions {
|
|
14
|
-
/** Where to listen; the port `--port` asked for, and the span above it. */
|
|
15
|
-
ports?: PortProvider;
|
|
16
|
-
/**
|
|
17
|
-
* Host names besides this machine's own that the page may be reached by,
|
|
18
|
-
* like the hostname of a tunnel. Anything else is refused, which is what
|
|
19
|
-
* stops another site from reading the repo through a browser pointed at
|
|
20
|
-
* localhost. Who may use those names is the tunnel's business, not arbor's.
|
|
21
|
-
*/
|
|
22
|
-
hosts?: string[];
|
|
23
|
-
}
|
|
24
|
-
/** A running server, and the one thing a caller ever wants to do with it. */
|
|
25
|
-
export interface DevServer extends AsyncResource {
|
|
26
|
-
port: number;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
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.
|
|
33
|
-
*/
|
|
34
|
-
export declare function dev({ service, fs, journal, todos, log, assets, }: {
|
|
35
|
-
service: WorktreeService;
|
|
36
|
-
fs: Fs;
|
|
37
|
-
journal: Journal;
|
|
38
|
-
todos: Todos;
|
|
39
|
-
log: Logger;
|
|
40
|
-
assets: Assets;
|
|
41
|
-
}, { ports, hosts }?: DevOptions): Promise<DevServer>;
|
|
42
|
-
/**
|
|
43
|
-
* Where `dev` will listen, given the port asked for. A flag is outside input,
|
|
44
|
-
* so a port that cannot exist is a refusal with an exit code rather than the
|
|
45
|
-
* assertion `OpenPortProvider` would raise for a bug in here.
|
|
46
|
-
*/
|
|
47
|
-
export declare function devPorts(from: number): PortProvider;
|
package/escalate.d.ts
DELETED
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { type Logger } from "webappwiz/log";
|
|
2
|
-
import type { Fs, Lock } from "webappwiz/system";
|
|
3
|
-
import type { Git } from "./git.js";
|
|
4
|
-
import type { WorktreeService } from "./worktree-service.js";
|
|
5
|
-
export interface EscalateOptions {
|
|
6
|
-
/** Task to escalate. Defaults to the one `cwd` is a worktree for. */
|
|
7
|
-
task?: string;
|
|
8
|
-
/**
|
|
9
|
-
* Ask a person to approve merging, rather than to answer questions: adds
|
|
10
|
-
* the question that asks it to the plan, with the reason as its detail.
|
|
11
|
-
* Refused while another question is unchecked.
|
|
12
|
-
*/
|
|
13
|
-
review?: boolean;
|
|
14
|
-
}
|
|
15
|
-
/** The question a review asks. */
|
|
16
|
-
export declare const REVIEW_SUBJECT = "Ready to merge?";
|
|
17
|
-
/**
|
|
18
|
-
* The way out that is not "resolve the conflict badly to finish the task".
|
|
19
|
-
* When both sides restructured the same logic there is no correct merge, only
|
|
20
|
-
* a decision, and that belongs to a human.
|
|
21
|
-
*/
|
|
22
|
-
export declare function escalate({ service, git, lock, log, fs, }: {
|
|
23
|
-
service: WorktreeService;
|
|
24
|
-
git: Git;
|
|
25
|
-
lock: Lock;
|
|
26
|
-
log: Logger;
|
|
27
|
-
fs: Fs;
|
|
28
|
-
}, reason: string, cwd: string, { task, review }?: EscalateOptions): Promise<void>;
|
package/exit.d.ts
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type { Deps, Middleware } from "webappwiz/cmd";
|
|
2
|
-
/** Exit codes are the API: an agent branches on these, not on prose. */
|
|
3
|
-
export declare const EXIT: {
|
|
4
|
-
readonly usage: 1;
|
|
5
|
-
readonly conflict: 2;
|
|
6
|
-
readonly tests_failed: 3;
|
|
7
|
-
readonly lease_lost: 4;
|
|
8
|
-
readonly budget_exhausted: 5;
|
|
9
|
-
readonly lease_held: 6;
|
|
10
|
-
readonly dirty: 7;
|
|
11
|
-
readonly not_found: 8;
|
|
12
|
-
readonly hook_failed: 9;
|
|
13
|
-
readonly exists: 10;
|
|
14
|
-
readonly orphaned: 11;
|
|
15
|
-
readonly merge_failed: 12;
|
|
16
|
-
readonly already_removed: 13;
|
|
17
|
-
readonly timeout: 14;
|
|
18
|
-
readonly blocked: 16;
|
|
19
|
-
};
|
|
20
|
-
export type Reason = keyof typeof EXIT;
|
|
21
|
-
/**
|
|
22
|
-
* A refusal, carrying the reason to branch on, a message to show, and any
|
|
23
|
-
* data worth reporting with it. `exits` turns one into output and a status
|
|
24
|
-
* code.
|
|
25
|
-
*/
|
|
26
|
-
export declare class Exit extends Error {
|
|
27
|
-
readonly reason: Reason;
|
|
28
|
-
readonly data: Record<string, unknown>;
|
|
29
|
-
constructor(reason: Reason, message: string, data?: Record<string, unknown>);
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* How a command refuses. Every call site means "and go no further", which is
|
|
33
|
-
* what the `never` return buys: the code below a `fail` is unreachable, and
|
|
34
|
-
* TypeScript narrows accordingly.
|
|
35
|
-
*/
|
|
36
|
-
export declare function fail(reason: Reason, message: string, data?: Record<string, unknown>): never;
|
|
37
|
-
/**
|
|
38
|
-
* Decides what a refusal looks like (a machine-readable reason on stdout, a
|
|
39
|
-
* human explanation on stderr) and is the only place that ends the process.
|
|
40
|
-
* An agent branches on the first and reads the second.
|
|
41
|
-
*/
|
|
42
|
-
export declare function exits<C extends Deps>(): Middleware<C>;
|
package/git.d.ts
DELETED
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { type Fs, type Ps } from "webappwiz/system";
|
|
2
|
-
export interface GitResult {
|
|
3
|
-
code: number;
|
|
4
|
-
stdout: string;
|
|
5
|
-
stderr: string;
|
|
6
|
-
}
|
|
7
|
-
/**
|
|
8
|
-
* Every git call arbor makes, scoped to one repository. Agents are not allowed
|
|
9
|
-
* to run these themselves, since going through arbor is what makes concurrent
|
|
10
|
-
* work safe, so this is the only place git is spoken.
|
|
11
|
-
*/
|
|
12
|
-
/** What a `Git` works through; the real ones by default. */
|
|
13
|
-
export interface GitOptions {
|
|
14
|
-
ps?: Ps;
|
|
15
|
-
fs?: Fs;
|
|
16
|
-
}
|
|
17
|
-
export declare class Git {
|
|
18
|
-
/** The main worktree, where trunk lives. */
|
|
19
|
-
readonly root: string;
|
|
20
|
-
private readonly ps;
|
|
21
|
-
private readonly fs;
|
|
22
|
-
constructor(
|
|
23
|
-
/** The main worktree, where trunk lives. */
|
|
24
|
-
root: string, opts?: GitOptions);
|
|
25
|
-
run(cwd: string, ...args: string[]): Promise<GitResult>;
|
|
26
|
-
out(cwd: string, ...args: string[]): Promise<string>;
|
|
27
|
-
branchExists(branch: string): Promise<boolean>;
|
|
28
|
-
/**
|
|
29
|
-
* The branch `origin/HEAD` points at, or null when the repo has no such ref:
|
|
30
|
-
* no remote, or a clone that never fetched it.
|
|
31
|
-
*/
|
|
32
|
-
defaultBranch(): Promise<string | null>;
|
|
33
|
-
currentBranch(cwd: string): Promise<string>;
|
|
34
|
-
head(cwd: string): Promise<string>;
|
|
35
|
-
shortHead(cwd: string): Promise<string>;
|
|
36
|
-
porcelain(cwd: string): Promise<string[]>;
|
|
37
|
-
conflictedPaths(cwd: string): Promise<string[]>;
|
|
38
|
-
commitsAhead(trunk: string, branch: string): Promise<number | null>;
|
|
39
|
-
/** Lines added and removed on `branch` since it left `trunk`. */
|
|
40
|
-
diffStat(trunk: string, branch: string): Promise<{
|
|
41
|
-
added: number;
|
|
42
|
-
removed: number;
|
|
43
|
-
} | null>;
|
|
44
|
-
/**
|
|
45
|
-
* Every path `branch` touches since it left `trunk`, plus what is still
|
|
46
|
-
* uncommitted in `worktree` when there is one: an agent mid-task has usually
|
|
47
|
-
* committed nothing yet, and its edits are the overlap that matters most.
|
|
48
|
-
*/
|
|
49
|
-
changedFiles(trunk: string, branch: string, worktree: string | null): Promise<string[] | null>;
|
|
50
|
-
/**
|
|
51
|
-
* Every path a person might point an agent at in the tree at `cwd`: the
|
|
52
|
-
* files git tracks, the new ones it does not ignore, and each directory
|
|
53
|
-
* above them, which ends in `/`. Sorted, relative to the tree's root.
|
|
54
|
-
*/
|
|
55
|
-
paths(cwd: string): Promise<string[]>;
|
|
56
|
-
rebase(cwd: string, onto: string): Promise<GitResult>;
|
|
57
|
-
resetHard(cwd: string, commit: string): Promise<GitResult>;
|
|
58
|
-
checkout(branch: string): Promise<GitResult>;
|
|
59
|
-
mergeFfOnly(cwd: string, branch: string): Promise<GitResult>;
|
|
60
|
-
/**
|
|
61
|
-
* The worktree with `branch` checked out, or null when no worktree has it.
|
|
62
|
-
* Git allows one worktree per branch and that is the only place the branch
|
|
63
|
-
* can be advanced, so this is where a fast-forward has to happen.
|
|
64
|
-
*/
|
|
65
|
-
worktreeFor(branch: string): Promise<string | null>;
|
|
66
|
-
addWorktree(branch: string, path: string, from: string): Promise<GitResult>;
|
|
67
|
-
removeWorktree(path: string): Promise<GitResult>;
|
|
68
|
-
/** Drops metadata for worktrees whose directories are gone. */
|
|
69
|
-
pruneWorktrees(): Promise<GitResult>;
|
|
70
|
-
deleteBranch(branch: string): Promise<GitResult>;
|
|
71
|
-
/** The one `.git` every worktree of this repo shares. */
|
|
72
|
-
commonDir(): Promise<string>;
|
|
73
|
-
/** The git directory belonging to a worktree, or null if it has none. */
|
|
74
|
-
worktreeGitDir(worktree: string): Promise<string | null>;
|
|
75
|
-
/** Half-finished merges and rebases a resuming agent is standing in. */
|
|
76
|
-
interruptedOps(worktree: string): Promise<string[]>;
|
|
77
|
-
}
|
package/index.d.ts
DELETED