toolroll 0.9.61 → 0.9.62
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/CHANGELOG.md +17 -0
- package/dist/cli.js +28 -11
- package/dist/landed-cleanup.d.ts +23 -10
- package/dist/landed-cleanup.js +188 -81
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +3 -3
- package/dist/operate.js +67 -16
- package/dist/project-admission.d.ts +10 -6
- package/dist/project-admission.js +33 -11
- package/dist/project-delete-ui.d.ts +1 -0
- package/dist/project-delete-ui.js +4 -1
- package/dist/remote-main.d.ts +57 -0
- package/dist/remote-main.js +179 -0
- package/dist/server/render-tasks.d.ts +3 -0
- package/dist/server/render-tasks.js +3 -0
- package/dist/server/settings.js +3 -2
- package/dist/server/task-views.js +2 -0
- package/dist/storage-sweep-process.d.ts +7 -0
- package/dist/storage-sweep-process.js +20 -3
- package/dist/storage-sweep-schedule.d.ts +30 -0
- package/dist/storage-sweep-schedule.js +164 -0
- package/dist/storage-sweep.d.ts +6 -5
- package/dist/storage-sweep.js +10 -70
- package/dist/storage-ui.d.ts +2 -0
- package/dist/storage-ui.js +7 -4
- package/dist/store.d.ts +39 -3
- package/dist/store.js +96 -5
- package/dist/surface.js +4 -2
- package/dist/task-base.d.ts +35 -0
- package/dist/task-base.js +51 -0
- package/dist/toolroll-update.js +3 -2
- package/dist/worktree.d.ts +54 -2
- package/dist/worktree.js +108 -13
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.62 — 2026-10-12
|
|
4
|
+
|
|
5
|
+
- **Release checks in minutes.** The upgrade path runs on a sample, CI's
|
|
6
|
+
passing run on the exact commit is reused, and the first failure stops it.
|
|
7
|
+
- **Storage sweep waits for quiet.** It runs when nothing is building, and
|
|
8
|
+
Settings → Storage says when it last ran and why it waits.
|
|
9
|
+
- **Landing proof you can trust.** A task completes on its own only for a
|
|
10
|
+
merged pull request or the project's recorded remote main, never a local
|
|
11
|
+
branch a build could move. Projects added before this need it once: you
|
|
12
|
+
(the owner) or an instance operator's lead runs
|
|
13
|
+
`toolroll repos add <project> --remote <url>`. Until then, their tasks wait
|
|
14
|
+
for you.
|
|
15
|
+
- **Cleanup follow-ups.** Only branches Toolroll created are removed, counted
|
|
16
|
+
from when it made them; scout branches stay, and duplicate teardowns merge.
|
|
17
|
+
- **Fresh base.** A task with no commits starts from current main; the task
|
|
18
|
+
page shows its base.
|
|
19
|
+
|
|
3
20
|
## 0.9.61 — 2026-10-12
|
|
4
21
|
|
|
5
22
|
- **Agents board.** One place to check on running agents: Needs you,
|
package/dist/cli.js
CHANGED
|
@@ -506,7 +506,14 @@ async function runReposCommand(argv, write, onboard) {
|
|
|
506
506
|
const tokenAt = argv.indexOf("--token");
|
|
507
507
|
const tokenFlag = tokenAt === -1 ? undefined : argv[tokenAt + 1];
|
|
508
508
|
const bare = argv.filter((argument, index) => argument !== "--json" && (tokenAt === -1 || (index !== tokenAt && index !== tokenAt + 1)));
|
|
509
|
-
const
|
|
509
|
+
const remoteAt = bare.indexOf("--remote");
|
|
510
|
+
const remoteUrl = remoteAt === -1 ? undefined : bare[remoteAt + 1];
|
|
511
|
+
const [action, ...paths] = bare.filter((_, i) => remoteAt === -1 || (i !== remoteAt && i !== remoteAt + 1));
|
|
512
|
+
if (remoteAt !== -1 && (action !== "add" || bare.filter(v => v === "--remote").length !== 1 || remoteUrl === undefined || remoteUrl.startsWith("-") || paths.length > 1)) {
|
|
513
|
+
const message = "Use repos add <project> --remote <url> for one project.";
|
|
514
|
+
write(json ? envelopeJson({ ok: false, command: "repos", reason: "usage", message }) : message);
|
|
515
|
+
return USAGE_EXIT;
|
|
516
|
+
}
|
|
510
517
|
const databaseFile = databasePath(process.env, homedir());
|
|
511
518
|
const file = registryBeside(databaseFile);
|
|
512
519
|
if (tokenAt !== -1 && (tokenFlag === undefined || tokenFlag.startsWith("-"))) {
|
|
@@ -529,11 +536,18 @@ async function runReposCommand(argv, write, onboard) {
|
|
|
529
536
|
return 1;
|
|
530
537
|
}
|
|
531
538
|
if (action === undefined) {
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
539
|
+
const store = openStore(databaseFile);
|
|
540
|
+
try {
|
|
541
|
+
const projects = loaded.repos.map(repo => ({ repo, remoteUrl: store.projectRemote(repo) }));
|
|
542
|
+
if (json) {
|
|
543
|
+
write(envelopeJson({ ok: true, command: "repos", count: loaded.repos.length, repos: loaded.repos, projects, file }));
|
|
544
|
+
return 0;
|
|
545
|
+
}
|
|
546
|
+
return listRepos(loaded.repos, file, write, new Map(projects.map(p => [p.repo, p.remoteUrl])));
|
|
547
|
+
}
|
|
548
|
+
finally {
|
|
549
|
+
store.close();
|
|
535
550
|
}
|
|
536
|
-
return listRepos(loaded.repos, file, write);
|
|
537
551
|
}
|
|
538
552
|
if (action !== "add" && action !== "remove") {
|
|
539
553
|
const message = `unknown command \`repos ${action}\` — try \`repos\`, \`repos add\`, \`repos remove\`, or \`repos add-from-github <owner/name>\``;
|
|
@@ -545,7 +559,7 @@ async function runReposCommand(argv, write, onboard) {
|
|
|
545
559
|
const lines = [];
|
|
546
560
|
const said = {};
|
|
547
561
|
const say = json ? (line) => lines.push(line) : write;
|
|
548
|
-
const code = await withAdmission({ databaseFile, registryFile: file, ...(leadToken === undefined ? {} : { leadToken }) }, say, said, admission => action === "add" ? addToRepos(admission, targets, say, said) : removeFromRepos(admission, targets, say, said));
|
|
562
|
+
const code = await withAdmission({ databaseFile, registryFile: file, ...(leadToken === undefined ? {} : { leadToken }) }, say, said, admission => action === "add" ? addToRepos(admission, targets, say, said, remoteUrl) : removeFromRepos(admission, targets, say, said));
|
|
549
563
|
if (json) {
|
|
550
564
|
write(envelopeJson({
|
|
551
565
|
ok: code === 0,
|
|
@@ -582,7 +596,7 @@ async function withAdmission(where, write, said, body) {
|
|
|
582
596
|
store.close();
|
|
583
597
|
}
|
|
584
598
|
}
|
|
585
|
-
function listRepos(repos, file, write) {
|
|
599
|
+
function listRepos(repos, file, write, remotes) {
|
|
586
600
|
if (repos.length === 0) {
|
|
587
601
|
write("No repositories connected. Toolroll reports on everything it can find.");
|
|
588
602
|
write("");
|
|
@@ -592,8 +606,10 @@ function listRepos(repos, file, write) {
|
|
|
592
606
|
return 0;
|
|
593
607
|
}
|
|
594
608
|
write(`${repos.length === 1 ? "1 repository" : `${repos.length} repositories`} connected:`);
|
|
595
|
-
for (const repo of repos)
|
|
609
|
+
for (const repo of repos) {
|
|
596
610
|
write(` ${repo}${existsSync(repo) ? "" : " (missing)"}`);
|
|
611
|
+
write(` Recorded remote: ${remotes.get(repo) ?? "None"}`);
|
|
612
|
+
}
|
|
597
613
|
write("");
|
|
598
614
|
write(" toolroll repos add <path> add another; it is in the console at once");
|
|
599
615
|
write(" toolroll repos remove <path> take one out of the console; its tasks stay");
|
|
@@ -606,7 +622,7 @@ function listRepos(repos, file, write) {
|
|
|
606
622
|
function admissionExit(reason) {
|
|
607
623
|
return reason === "not-git" || reason === "unavailable" ? USAGE_EXIT : 1;
|
|
608
624
|
}
|
|
609
|
-
async function addToRepos(admission, targets, write, said) {
|
|
625
|
+
async function addToRepos(admission, targets, write, said, remoteUrl) {
|
|
610
626
|
// Every folder is proved before anything is written: one that is not a
|
|
611
627
|
// repository fails here, not later and further away.
|
|
612
628
|
let refused = false;
|
|
@@ -624,7 +640,7 @@ async function addToRepos(admission, targets, write, said) {
|
|
|
624
640
|
const running = consoleRunning(admission.store, now);
|
|
625
641
|
const projects = [];
|
|
626
642
|
for (const path of targets) {
|
|
627
|
-
const admitted = await admitProject(admission.store, { registryFile: admission.registryFile, path, actor: admission.actor, now });
|
|
643
|
+
const admitted = await admitProject(admission.store, { registryFile: admission.registryFile, path, actor: admission.actor, now, captureOrigin: true, ...(remoteUrl === undefined ? {} : { remoteUrl }) });
|
|
628
644
|
if (!admitted.ok) {
|
|
629
645
|
// The updater's own taxonomy survives (verification finding 4): a held
|
|
630
646
|
// lock or a malformed registry is an operational fact, never "usage".
|
|
@@ -633,8 +649,9 @@ async function addToRepos(admission, targets, write, said) {
|
|
|
633
649
|
write(admitted.message);
|
|
634
650
|
return admissionExit(admitted.reason);
|
|
635
651
|
}
|
|
636
|
-
projects.push({ repo: admitted.repo, name: admitted.name, added: admitted.added });
|
|
652
|
+
projects.push({ repo: admitted.repo, name: admitted.name, added: admitted.added, remoteUrl: admitted.remote.url });
|
|
637
653
|
write(addedWords(admitted.name, admitted.added, running));
|
|
654
|
+
write(`Recorded remote: ${admitted.remote.url ?? "None. Use repos add <project> --remote <url> to record one."}`);
|
|
638
655
|
}
|
|
639
656
|
said.projects = projects;
|
|
640
657
|
return 0;
|
package/dist/landed-cleanup.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ExecResult } from "./exec.js";
|
|
2
|
+
import type { RemoteMainRead, RemoteMains } from "./remote-main.js";
|
|
2
3
|
import type { Store } from "./store.js";
|
|
3
4
|
import { type Runner, type WorktreePool } from "./worktree.js";
|
|
4
5
|
/** `storage clean --landed` takes only what has waited at least this long. */
|
|
@@ -13,10 +14,12 @@ export declare const AUTO_ITEMS = 40;
|
|
|
13
14
|
export type Git = (cwd: string, args: readonly string[]) => Promise<ExecResult>;
|
|
14
15
|
export declare function gitOf(runner?: Runner): Git;
|
|
15
16
|
export type LandedHow = "merged" | "in-main" | "content-in-main";
|
|
17
|
+
/** `trusted`: it may complete a task automatically (a recorded merge, or the recorded remote's main). */
|
|
16
18
|
export type LandedProof = {
|
|
17
19
|
landed: true;
|
|
18
20
|
how: LandedHow;
|
|
19
21
|
reason: string;
|
|
22
|
+
trusted: boolean;
|
|
20
23
|
} | {
|
|
21
24
|
landed: false;
|
|
22
25
|
why: string;
|
|
@@ -28,10 +31,17 @@ export type PullRequestFact = {
|
|
|
28
31
|
number: number | null;
|
|
29
32
|
};
|
|
30
33
|
/**
|
|
31
|
-
* Whether this commit's work landed, read from what Toolroll recorded and the local repository
|
|
32
|
-
*
|
|
34
|
+
* Whether this commit's work landed, read from what Toolroll recorded and the local repository. Fails closed: every
|
|
35
|
+
* doubt is "not proven", with the reason in a few words. Only a recorded merge is trusted to complete a task: a build
|
|
36
|
+
* can write the local main.
|
|
33
37
|
*/
|
|
34
38
|
export declare function landedProof(git: Git, repo: string, head: string | null, pullRequest?: PullRequestFact | null): Promise<LandedProof>;
|
|
39
|
+
/**
|
|
40
|
+
* The proof automatic completion trusts: a recorded merge of this exact commit, or its work in the main of the
|
|
41
|
+
* project's recorded remote, compared only in the private directory that main was fetched into. `remote` is read only
|
|
42
|
+
* when no recorded merge settles it.
|
|
43
|
+
*/
|
|
44
|
+
export declare function remoteLandedProof(remote: () => Promise<RemoteMainRead>, repo: string, head: string | null, pullRequest?: PullRequestFact | null): Promise<LandedProof>;
|
|
35
45
|
export type LandedTarget = {
|
|
36
46
|
kind: "checkout" | "branch";
|
|
37
47
|
target: string;
|
|
@@ -54,6 +64,8 @@ export type LandedCandidate = {
|
|
|
54
64
|
completes: boolean;
|
|
55
65
|
how: LandedHow;
|
|
56
66
|
reason: string;
|
|
67
|
+
/** Proven by a recorded merge or the recorded remote's main: only then may it complete. */
|
|
68
|
+
trusted: boolean;
|
|
57
69
|
targets: LandedTarget[];
|
|
58
70
|
keeps: LandedKeep[];
|
|
59
71
|
};
|
|
@@ -62,7 +74,7 @@ export type LandedCandidate = {
|
|
|
62
74
|
* `limit` caps how many results are read against git (reconcile's bound; results read before and unchanged since
|
|
63
75
|
* don't count); `before` takes only results finished before it (the one-time pass's seven days).
|
|
64
76
|
*/
|
|
65
|
-
export declare function landedResults(store: Store, git: Git, repo: string, root: string, now: Date, options?: {
|
|
77
|
+
export declare function landedResults(store: Store, git: Git, remotes: RemoteMains, repo: string, root: string, now: Date, options?: {
|
|
66
78
|
limit?: number;
|
|
67
79
|
before?: string;
|
|
68
80
|
memo?: boolean;
|
|
@@ -104,9 +116,10 @@ export type LandedStep = {
|
|
|
104
116
|
/**
|
|
105
117
|
* Finish what teardowns intended: pending items, oldest first (checkouts before branches). Each is checked again
|
|
106
118
|
* right before it goes, and recorded right after. Doubt leaves it pending for the next pass; a branch that moved, or
|
|
107
|
-
* a result that has newer work, is kept for good. `
|
|
119
|
+
* a result that has newer work, is kept for good. `limit` caps the items settled (removed, already gone or kept), so
|
|
120
|
+
* items left waiting never crowd out later ones. `only` limits it to these teardowns.
|
|
108
121
|
*/
|
|
109
|
-
export declare function finishLanded(store: Store, pool: WorktreePool, git: Git, now: () => Date, options?: {
|
|
122
|
+
export declare function finishLanded(store: Store, pool: WorktreePool, git: Git, remotes: RemoteMains, now: () => Date, options?: {
|
|
110
123
|
repo?: string;
|
|
111
124
|
limit?: number;
|
|
112
125
|
only?: ReadonlySet<number>;
|
|
@@ -115,7 +128,7 @@ export declare function finishLanded(store: Store, pool: WorktreePool, git: Git,
|
|
|
115
128
|
* Reconcile's part: finish teardowns a crash cut short, then find newly landed results (a bounded few), mark them
|
|
116
129
|
* complete and tear them down.
|
|
117
130
|
*/
|
|
118
|
-
export declare function reconcileLanded(store: Store, pool: WorktreePool, git: Git, repo: string, root: string, now: () => Date): Promise<{
|
|
131
|
+
export declare function reconcileLanded(store: Store, pool: WorktreePool, git: Git, remotes: RemoteMains, repo: string, root: string, now: () => Date): Promise<{
|
|
119
132
|
completed: string[];
|
|
120
133
|
steps: LandedStep[];
|
|
121
134
|
}>;
|
|
@@ -142,15 +155,15 @@ export type LandedPlan = {
|
|
|
142
155
|
};
|
|
143
156
|
/**
|
|
144
157
|
* What `storage clean --landed` would do now, across every project: landed results finished at least a week ago,
|
|
145
|
-
* and Toolroll
|
|
146
|
-
*
|
|
158
|
+
* and branches Toolroll created (toolroll/, toolroll-plan/, standing-orders/) at least a week ago that no task uses
|
|
159
|
+
* and that landed in main. Reads only.
|
|
147
160
|
*/
|
|
148
|
-
export declare function landedBacklog(store: Store, git: Git, root: string, now: Date, options?: {
|
|
161
|
+
export declare function landedBacklog(store: Store, git: Git, remotes: RemoteMains, root: string, now: Date, options?: {
|
|
149
162
|
repos?: readonly string[];
|
|
150
163
|
}): Promise<LandedPlan>;
|
|
151
164
|
/** Act on a preview: record every teardown first, then carry them out. Refuses nothing it previewed silently: what
|
|
152
165
|
* changed since is reported as kept. */
|
|
153
|
-
export declare function cleanLandedBacklog(store: Store, pool: WorktreePool, git: Git, root: string, now: () => Date, plan: LandedPlan, actor: string): Promise<{
|
|
166
|
+
export declare function cleanLandedBacklog(store: Store, pool: WorktreePool, git: Git, remotes: RemoteMains, root: string, now: () => Date, plan: LandedPlan, actor: string): Promise<{
|
|
154
167
|
completed: string[];
|
|
155
168
|
steps: LandedStep[];
|
|
156
169
|
refused: LandedKeep[];
|