toolroll 0.9.92 → 0.9.94
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 +87 -0
- package/dist/activity-line.d.ts +7 -4
- package/dist/activity-line.js +9 -7
- package/dist/agent-fence.d.ts +4 -0
- package/dist/agent-fence.js +4 -1
- package/dist/approval-authority.d.ts +7 -1
- package/dist/approval-authority.js +11 -2
- package/dist/approval-password.d.ts +31 -0
- package/dist/approval-password.js +26 -0
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +3 -3
- package/dist/assignment.js +16 -3
- package/dist/boot-identity.d.ts +2 -1
- package/dist/boot-identity.js +6 -4
- package/dist/bounded-spawn.d.ts +57 -0
- package/dist/bounded-spawn.js +220 -0
- package/dist/browser/terminal.js +42 -42
- package/dist/browser/workspace.css +2 -2
- package/dist/browser/workspace.js +26 -26
- package/dist/browser-crew.js +2 -2
- package/dist/browser-shell.d.ts +1 -1
- package/dist/browser-shell.js +2 -2
- package/dist/browser-workspace.d.ts +48 -7
- package/dist/browser-workspace.js +3 -3
- package/dist/builder-restart.d.ts +21 -0
- package/dist/builder-restart.js +50 -0
- package/dist/builder.d.ts +4 -0
- package/dist/builder.js +73 -25
- package/dist/chat-acceptance.js +1 -1
- package/dist/chat-actions.d.ts +12 -2
- package/dist/chat-actions.js +25 -2
- package/dist/chat-channel.js +11 -0
- package/dist/chat-decide.d.ts +7 -2
- package/dist/chat-decide.js +37 -9
- package/dist/chat-quiet.js +3 -3
- package/dist/chat-voice.d.ts +1 -1
- package/dist/chat-voice.js +2 -2
- package/dist/check-facts.d.ts +126 -0
- package/dist/check-facts.js +318 -0
- package/dist/check-failure.d.ts +22 -3
- package/dist/check-failure.js +57 -13
- package/dist/check-levels-ui.d.ts +4 -0
- package/dist/check-levels-ui.js +10 -1
- package/dist/check-load.d.ts +85 -0
- package/dist/check-load.js +147 -0
- package/dist/check-only-standing.d.ts +36 -0
- package/dist/check-only-standing.js +39 -0
- package/dist/check-only.d.ts +15 -0
- package/dist/check-only.js +99 -0
- package/dist/check-report.d.ts +7 -0
- package/dist/check-report.js +4 -3
- package/dist/check-stamp.d.ts +46 -0
- package/dist/check-stamp.js +93 -0
- package/dist/checkout-cleanup.d.ts +36 -5
- package/dist/checkout-cleanup.js +166 -14
- package/dist/cli-help.d.ts +4 -1
- package/dist/cli-help.js +47 -30
- package/dist/cli.js +16 -1
- package/dist/code-extract.d.ts +44 -0
- package/dist/code-extract.js +88 -0
- package/dist/code-map-acts.d.ts +24 -0
- package/dist/code-map-acts.js +53 -0
- package/dist/code-map-admission.d.ts +52 -0
- package/dist/code-map-admission.js +295 -0
- package/dist/code-map-build.d.ts +63 -0
- package/dist/code-map-build.js +415 -0
- package/dist/code-map-cli.d.ts +153 -0
- package/dist/code-map-cli.js +103 -0
- package/dist/code-map-main.d.ts +1 -0
- package/dist/code-map-main.js +48 -0
- package/dist/code-map-read.d.ts +106 -0
- package/dist/code-map-read.js +171 -0
- package/dist/code-map-report.d.ts +37 -0
- package/dist/code-map-report.js +95 -0
- package/dist/code-map-shape.d.ts +172 -0
- package/dist/code-map-shape.js +327 -0
- package/dist/code-map-store.d.ts +136 -0
- package/dist/code-map-store.js +233 -0
- package/dist/coding-provider.d.ts +2 -0
- package/dist/coding-provider.js +3 -2
- package/dist/coding-update.js +3 -2
- package/dist/coding-workspace.js +4 -4
- package/dist/console-chrome.js +2 -2
- package/dist/containment.d.ts +5 -0
- package/dist/containment.js +14 -1
- package/dist/contracts/chat-actions.d.ts +9 -0
- package/dist/contracts/chat-actions.js +2 -0
- package/dist/contracts/cli.d.ts +52 -0
- package/dist/contracts/cli.js +4 -0
- package/dist/contracts/console-api.d.ts +12 -5
- package/dist/contracts/console-api.js +12 -5
- package/dist/contracts/gateway-tools.d.ts +17 -0
- package/dist/contracts/gateway-tools.js +3 -1
- package/dist/contracts/lead-tools.d.ts +61 -0
- package/dist/contracts/lead-tools.js +10 -1
- package/dist/contracts/project-knowledge.d.ts +38 -5
- package/dist/contracts/project-knowledge.js +11 -5
- package/dist/contracts/project-memory.d.ts +3 -1
- package/dist/contracts/project-memory.js +3 -2
- package/dist/contracts/release-check.d.ts +25 -1
- package/dist/contracts/release-check.js +19 -0
- package/dist/contracts/storage.d.ts +14 -0
- package/dist/contracts/storage.js +3 -1
- package/dist/contracts/store-json.d.ts +2 -0
- package/dist/contracts/youiyoux-request.d.ts +1 -1
- package/dist/contracts/youiyoux-request.js +1 -1
- package/dist/desktop-access.js +6 -2
- package/dist/desktop-update.d.ts +2 -2
- package/dist/desktop-update.js +13 -9
- package/dist/dispatch.js +18 -15
- package/dist/dispose.js +8 -2
- package/dist/easy-start.js +2 -3
- package/dist/eval-hooks.d.ts +55 -0
- package/dist/eval-hooks.js +227 -4
- package/dist/evals.js +2 -1
- package/dist/fast-start.d.ts +136 -0
- package/dist/fast-start.js +431 -0
- package/dist/fast-terms.d.ts +9 -0
- package/dist/fast-terms.js +23 -0
- package/dist/fenced-check-coverage.d.ts +62 -0
- package/dist/fenced-check-coverage.js +54 -0
- package/dist/first-run.js +1 -1
- package/dist/flaky-ci.d.ts +31 -0
- package/dist/flaky-ci.js +149 -0
- package/dist/flaky-cli.d.ts +60 -0
- package/dist/flaky-cli.js +60 -0
- package/dist/flaky.d.ts +83 -0
- package/dist/flaky.js +264 -0
- package/dist/flow-card-task.js +3 -0
- package/dist/flow-triggers.d.ts +1 -1
- package/dist/flow-triggers.js +1 -1
- package/dist/github-import.d.ts +62 -0
- package/dist/github-import.js +146 -0
- package/dist/guides.js +15 -10
- package/dist/invoke.d.ts +5 -1
- package/dist/invoke.js +49 -14
- package/dist/job-object-helper.ps1 +10 -3
- package/dist/keymap.d.ts +41 -0
- package/dist/keymap.js +51 -0
- package/dist/knowledge-cli.js +1 -1
- package/dist/knowledge-ui.d.ts +21 -4
- package/dist/knowledge-ui.js +75 -7
- package/dist/laptop-background.d.ts +16 -0
- package/dist/laptop-background.js +108 -2
- package/dist/lead-context.js +1 -1
- package/dist/lead-contract.d.ts +1 -1
- package/dist/lead-contract.js +2 -2
- package/dist/lead-progress.js +3 -0
- package/dist/lead-status.d.ts +2 -0
- package/dist/lead-status.js +6 -2
- package/dist/lead-tools.js +51 -2
- package/dist/lead.js +1 -1
- package/dist/lesson-notes.d.ts +81 -0
- package/dist/lesson-notes.js +336 -0
- package/dist/limits-ui.js +1 -1
- package/dist/local-owner.d.ts +1 -1
- package/dist/local-owner.js +11 -0
- package/dist/machine-check.d.ts +16 -0
- package/dist/machine-check.js +26 -0
- package/dist/machine-identity.d.ts +75 -0
- package/dist/machine-identity.js +170 -0
- package/dist/mcp-core.js +11 -1
- package/dist/memory-cli.d.ts +300 -1
- package/dist/memory-cli.js +62 -9
- package/dist/memory-pass.d.ts +16 -0
- package/dist/memory-pass.js +67 -3
- package/dist/memory-tidy.d.ts +181 -0
- package/dist/memory-tidy.js +523 -0
- package/dist/memory-use.d.ts +35 -0
- package/dist/memory-use.js +109 -0
- package/dist/merge-checklist.d.ts +55 -0
- package/dist/merge-checklist.js +182 -0
- package/dist/needs-you.d.ts +32 -13
- package/dist/needs-you.js +108 -49
- package/dist/nightly-github.d.ts +2 -0
- package/dist/nightly-github.js +4 -4
- package/dist/operate-remote-arguments.js +6 -1
- package/dist/operate-remote.js +3 -1
- package/dist/operate.d.ts +6 -1
- package/dist/operate.js +540 -70
- package/dist/phase-routing.js +2 -2
- package/dist/plain-schema.d.ts +23 -0
- package/dist/plain-schema.js +94 -0
- package/dist/plain-words.d.ts +20 -0
- package/dist/plain-words.js +66 -0
- package/dist/planner.js +2 -1
- package/dist/process-liveness.d.ts +2 -0
- package/dist/process-liveness.js +61 -17
- package/dist/project-delete-ui.d.ts +7 -2
- package/dist/project-delete-ui.js +12 -1
- package/dist/project-delete.js +13 -2
- package/dist/project-knowledge.d.ts +18 -11
- package/dist/project-knowledge.js +39 -9
- package/dist/project-learning.d.ts +2 -1
- package/dist/project-learning.js +15 -45
- package/dist/project-memory.d.ts +5 -0
- package/dist/project-memory.js +22 -2
- package/dist/proof.js +30 -5
- package/dist/provider-auth.js +1 -1
- package/dist/publish.js +9 -1
- package/dist/pull-request-flow.js +5 -0
- package/dist/push.d.ts +26 -3
- package/dist/push.js +55 -15
- package/dist/release-adapters.d.ts +9 -0
- package/dist/release-adapters.js +87 -1
- package/dist/release-approval.d.ts +191 -0
- package/dist/release-approval.js +382 -0
- package/dist/release-auto.js +26 -17
- package/dist/release-card.d.ts +74 -6
- package/dist/release-card.js +179 -23
- package/dist/release-carry.d.ts +7 -2
- package/dist/release-carry.js +39 -9
- package/dist/release-cli.d.ts +4 -0
- package/dist/release-cli.js +38 -23
- package/dist/release-coverage.js +1 -1
- package/dist/release-next.d.ts +48 -0
- package/dist/release-next.js +161 -0
- package/dist/release.d.ts +50 -1
- package/dist/release.js +118 -11
- package/dist/remote-destination.d.ts +36 -0
- package/dist/remote-destination.js +217 -0
- package/dist/remote-main.d.ts +48 -0
- package/dist/remote-main.js +84 -5
- package/dist/repository-context.d.ts +3 -2
- package/dist/repository-context.js +112 -70
- package/dist/restart-certification.d.ts +1 -0
- package/dist/restart-certification.js +8 -6
- package/dist/result-acts.d.ts +29 -21
- package/dist/result-acts.js +36 -25
- package/dist/result-follow-ups.d.ts +2 -2
- package/dist/result-follow-ups.js +18 -4
- package/dist/result-review.d.ts +1 -1
- package/dist/route-change-check.d.ts +4 -0
- package/dist/route-change-check.js +13 -0
- package/dist/route-change.d.ts +22 -0
- package/dist/route-change.js +89 -0
- package/dist/route-hooks.js +3 -2
- package/dist/route-results.d.ts +6 -1
- package/dist/route-results.js +14 -5
- package/dist/run-closeout.d.ts +1 -1
- package/dist/run-closeout.js +7 -5
- package/dist/runner-process.js +3 -2
- package/dist/runner.js +2 -1
- package/dist/scope.d.ts +6 -1
- package/dist/scope.js +22 -9
- package/dist/serve.d.ts +1 -1
- package/dist/serve.js +24 -18
- package/dist/server/chat.js +14 -12
- package/dist/server/chrome.d.ts +1 -1
- package/dist/server/chrome.js +26 -35
- package/dist/server/console.css +23 -0
- package/dist/server/guards.js +4 -0
- package/dist/server/home.d.ts +6 -0
- package/dist/server/home.js +21 -13
- package/dist/server/http.js +1 -1
- package/dist/server/kit.js +1 -1
- package/dist/server/merge-ready-view.d.ts +10 -0
- package/dist/server/merge-ready-view.js +30 -0
- package/dist/server/pages-assets.d.ts +1 -0
- package/dist/server/pages-assets.js +8 -5
- package/dist/server/pages.js +103 -82
- package/dist/server/render-chat.js +2 -2
- package/dist/server/render-code-map.d.ts +30 -0
- package/dist/server/render-code-map.js +137 -0
- package/dist/server/render-flaky.d.ts +20 -0
- package/dist/server/render-flaky.js +59 -0
- package/dist/server/render-history.js +1 -1
- package/dist/server/render-new-task.d.ts +26 -1
- package/dist/server/render-new-task.js +30 -5
- package/dist/server/render-pages.d.ts +1 -175
- package/dist/server/render-pages.js +38 -313
- package/dist/server/render-projects.d.ts +2 -1
- package/dist/server/render-projects.js +9 -7
- package/dist/server/render-release.d.ts +19 -0
- package/dist/server/render-release.js +84 -0
- package/dist/server/render-results.d.ts +2 -2
- package/dist/server/render-results.js +71 -73
- package/dist/server/render-tasks.d.ts +13 -2
- package/dist/server/render-tasks.js +156 -139
- package/dist/server/retired-routes.d.ts +17 -0
- package/dist/server/retired-routes.js +53 -0
- package/dist/server/route-table.d.ts +4 -1
- package/dist/server/route-table.js +52 -13
- package/dist/server/settings.js +129 -22
- package/dist/server/task-views.js +61 -26
- package/dist/server/tasks.d.ts +17 -6
- package/dist/server/tasks.js +273 -338
- package/dist/server/welcome.js +1 -1
- package/dist/settings-groups-ui.js +1 -1
- package/dist/setup-guide.d.ts +2 -2
- package/dist/stop-proof.d.ts +67 -0
- package/dist/stop-proof.js +295 -0
- package/dist/storage-sweep-process.js +1 -1
- package/dist/storage-sweep.d.ts +5 -3
- package/dist/storage-sweep.js +30 -9
- package/dist/storage-ui.js +28 -17
- package/dist/storage.d.ts +27 -2
- package/dist/storage.js +39 -2
- package/dist/store.d.ts +148 -34
- package/dist/store.js +626 -177
- package/dist/surface.js +46 -5
- package/dist/sweep-quarantine.d.ts +1 -0
- package/dist/sweep-quarantine.js +6 -4
- package/dist/tamper-scan.d.ts +4 -0
- package/dist/tamper-scan.js +90 -0
- package/dist/task-aliases.d.ts +5 -0
- package/dist/task-aliases.js +9 -0
- package/dist/task-control.js +4 -1
- package/dist/task-outcome-cli.d.ts +2 -0
- package/dist/task-outcome-cli.js +36 -17
- package/dist/task-sizing.d.ts +5 -0
- package/dist/task-sizing.js +2 -0
- package/dist/task-source.d.ts +7 -0
- package/dist/task-source.js +14 -0
- package/dist/task-status.d.ts +9 -0
- package/dist/task-status.js +40 -49
- package/dist/teardown-git.d.ts +16 -0
- package/dist/teardown-git.js +63 -5
- package/dist/telegram-progress.js +5 -3
- package/dist/telegram-status.js +6 -4
- package/dist/test-infra.d.ts +8 -0
- package/dist/test-infra.js +18 -0
- package/dist/tidy.js +1 -1
- package/dist/toolroll-update-ui.js +2 -2
- package/dist/toolroll-update.js +18 -5
- package/dist/tui-contract.d.ts +24 -0
- package/dist/tui-source.js +67 -8
- package/dist/tui.js +51 -51
- package/dist/vanished-retry.d.ts +30 -0
- package/dist/vanished-retry.js +174 -0
- package/dist/work-index.d.ts +2 -0
- package/dist/work-index.js +82 -70
- package/dist/work-summary.d.ts +2 -2
- package/dist/work-summary.js +2 -0
- package/dist/workspace-ui.d.ts +4 -2
- package/dist/workspace-ui.js +28 -22
- package/dist/worktree.d.ts +12 -3
- package/dist/worktree.js +68 -13
- package/package.json +1 -1
- package/dist/server/render-next.d.ts +0 -63
- package/dist/server/render-next.js +0 -109
package/dist/checkout-cleanup.js
CHANGED
|
@@ -5,14 +5,22 @@
|
|
|
5
5
|
* 2 days or a week after that, or never (only a clean-up by hand). A clean-up by hand doesn't wait for the setting.
|
|
6
6
|
* Only the working copy goes: its branch, and so every commit, stays, and a later lease of the branch makes a new one.
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* branch. A release check's checkout (a deploy installs from it) goes once its task is complete, with its dependency
|
|
8
|
+
* Kept until idle (below): one whose task isn't finished (on hold, failed, a revision under way) and a result still
|
|
9
|
+
* waiting for review. Always kept: a checkout in use, one with changes (files Toolroll wrote itself aside), and one
|
|
10
|
+
* with commits on no branch. A release check's checkout (a deploy installs from it) goes once its task is complete, with its dependency
|
|
11
11
|
* install and journey output, except the one deployed now and the project's newest complete one still waiting for its
|
|
12
12
|
* deploy (no deploy since it was complete; at most a week). A superseded or cancelled gate goes at once, whatever its
|
|
13
13
|
* task's state (staged releases keep the rollback copy). A checkout no task names (adopted after a crash) goes only
|
|
14
14
|
* in a clean-up by hand. Every removal is in the ledger.
|
|
15
15
|
*
|
|
16
|
+
* Idle checkouts go too (the idle limit, 14 days by default): one whose whole task family has been idle that long (no
|
|
17
|
+
* run, no person's act, no open lease) goes even with its result waiting for review or its task unfinished, in the
|
|
18
|
+
* storage sweep's child only, never in a clean-up by hand, and only when `WorktreePool.proveRecreatable` says nothing is lost.
|
|
19
|
+
* Nothing needs that working copy: a result page, Accept, Send back and Run checks read saved evidence and the branch,
|
|
20
|
+
* and a retry, revision or resume leases the branch, which makes a new one. Excluded, because that can't be proven: a
|
|
21
|
+
* release gate (a deploy installs from its checkout), work from an external tracker, and a checkout no task names. A
|
|
22
|
+
* checkout with uncommitted changes is never removed by this; it is listed for a person.
|
|
23
|
+
*
|
|
16
24
|
* A kept checkout waiting for review or with changes drops its dependencies and build output once its run ends
|
|
17
25
|
* (`slimKeptCheckouts`); the next run's setup restores them.
|
|
18
26
|
*/
|
|
@@ -21,7 +29,8 @@ import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
|
21
29
|
import { basename, dirname, join } from "node:path";
|
|
22
30
|
import { taskBranches } from "./names.js";
|
|
23
31
|
import { COMPLETION_ACTION } from "./result-completion.js";
|
|
24
|
-
import { CLEANUP_CHOICES, bytesWords, diskBytes, diskBytesAsync } from "./storage.js";
|
|
32
|
+
import { CLEANUP_CHOICES, IDLE_CHOICES, bytesWords, diskBytes, diskBytesAsync } from "./storage.js";
|
|
33
|
+
import { checkoutIdle } from "./store.js";
|
|
25
34
|
import { worktreePath } from "./worktree.js";
|
|
26
35
|
const DAY_MS = 86_400_000;
|
|
27
36
|
/** A release candidate's checkout holds the build a deploy installs: the newest complete one waits at most a week for it. */
|
|
@@ -218,6 +227,114 @@ export function finishedCheckouts(store, now, repo) {
|
|
|
218
227
|
return [{ row, taskId: status.taskId, finishedAt: status.finishedAt }];
|
|
219
228
|
});
|
|
220
229
|
}
|
|
230
|
+
/** Toolroll's own bookkeeping about a task: none of it is anyone using the task. Every other ledger entry counts as activity. */
|
|
231
|
+
const NOT_ACTIVITY = ["assignment status baseline", "assignment status observed", "task archived", "checkout removed", "checkout slimmed", "checkout processes stopped",
|
|
232
|
+
"run processes stopped", "orphan process stopped", "process observation failed", "process witness settled", "branch deleted", "landed work cleaned up"];
|
|
233
|
+
/**
|
|
234
|
+
* When each task family last saw a run, a person's act or a lease, read in one pass. A revision builds on what it
|
|
235
|
+
* revises, so a family is idle together. `blocked`: an open run, an unreleased claim, a release gate, or a task only an
|
|
236
|
+
* external tracker knows, in the family.
|
|
237
|
+
*/
|
|
238
|
+
export function idleFacts(store) {
|
|
239
|
+
const db = store.handle;
|
|
240
|
+
const refs = db.prepare(`SELECT r.id AS ref, r.external_id AS id, r.revision_of AS revision_of, t.state AS state, t.created_at AS created_at, t.updated_at AS updated_at,
|
|
241
|
+
(SELECT MAX(s.candidate) FROM task_scope s WHERE s.task_id = r.external_id) AS candidate
|
|
242
|
+
FROM task_ref r LEFT JOIN task t ON t.id = r.external_id AND r.backend = 'built-in'`).all();
|
|
243
|
+
const parentOf = new Map(refs.filter(row => row["revision_of"] !== null).map(row => [String(row["id"]), String(row["revision_of"])]));
|
|
244
|
+
const rootOf = (id) => {
|
|
245
|
+
const seen = new Set([id]);
|
|
246
|
+
let root = id;
|
|
247
|
+
for (let parent = parentOf.get(root); parent !== undefined && !seen.has(parent); parent = parentOf.get(root)) {
|
|
248
|
+
seen.add(parent);
|
|
249
|
+
root = parent;
|
|
250
|
+
}
|
|
251
|
+
return root;
|
|
252
|
+
};
|
|
253
|
+
const families = new Map();
|
|
254
|
+
const rootByRef = new Map();
|
|
255
|
+
const rootsById = new Map();
|
|
256
|
+
const note = (root, at, blocked = false) => {
|
|
257
|
+
if (root === undefined)
|
|
258
|
+
return;
|
|
259
|
+
const fact = families.get(root) ?? { activeAt: "", blocked: false };
|
|
260
|
+
if (typeof at === "string" && at > fact.activeAt)
|
|
261
|
+
fact.activeAt = at;
|
|
262
|
+
if (blocked)
|
|
263
|
+
fact.blocked = true;
|
|
264
|
+
families.set(root, fact);
|
|
265
|
+
};
|
|
266
|
+
for (const row of refs) {
|
|
267
|
+
const id = String(row["id"]), root = rootOf(id);
|
|
268
|
+
rootByRef.set(Number(row["ref"]), root);
|
|
269
|
+
rootsById.set(id, root);
|
|
270
|
+
note(root, row["created_at"]);
|
|
271
|
+
note(root, row["updated_at"], row["state"] === null || row["candidate"] !== null);
|
|
272
|
+
}
|
|
273
|
+
const byTaskRef = (at, ref, blocked = false) => note(rootByRef.get(Number(ref)), at, blocked);
|
|
274
|
+
const runRoot = new Map();
|
|
275
|
+
const branches = [];
|
|
276
|
+
for (const row of db.prepare("SELECT id, task_ref, branch, started_at, finished_at FROM run").all()) {
|
|
277
|
+
const root = rootByRef.get(Number(row["task_ref"]));
|
|
278
|
+
runRoot.set(Number(row["id"]), root);
|
|
279
|
+
note(root, row["started_at"], row["finished_at"] === null);
|
|
280
|
+
note(root, row["finished_at"]);
|
|
281
|
+
if (root !== undefined && row["branch"] !== null)
|
|
282
|
+
branches.push([String(row["branch"]), root]);
|
|
283
|
+
}
|
|
284
|
+
for (const row of db.prepare("SELECT task_ref, acquired_at, heartbeat_at, released_at FROM claim").all()) {
|
|
285
|
+
for (const at of [row["acquired_at"], row["heartbeat_at"], row["released_at"]])
|
|
286
|
+
byTaskRef(at, row["task_ref"]);
|
|
287
|
+
byTaskRef(null, row["task_ref"], row["released_at"] === null);
|
|
288
|
+
}
|
|
289
|
+
for (const row of db.prepare("SELECT task_ref, held_at AS at FROM hold UNION ALL SELECT task_ref, created_at FROM task_steer UNION ALL SELECT task_ref, at FROM task_act").all())
|
|
290
|
+
byTaskRef(row["at"], row["task_ref"]);
|
|
291
|
+
for (const row of db.prepare("SELECT run, created_at AS at FROM run_note UNION ALL SELECT run, created_at FROM diff_comment UNION ALL SELECT run, created_at FROM decision UNION ALL SELECT run, answered_at FROM decision").all()) {
|
|
292
|
+
note(runRoot.get(Number(row["run"])), row["at"]);
|
|
293
|
+
}
|
|
294
|
+
for (const row of db.prepare(`SELECT task_id, run_id, MAX(at) AS at FROM action_ledger WHERE (task_id IS NOT NULL OR run_id IS NOT NULL)
|
|
295
|
+
AND action NOT IN (SELECT value FROM json_each(?)) GROUP BY task_id, run_id`).all(JSON.stringify(NOT_ACTIVITY))) {
|
|
296
|
+
if (row["task_id"] !== null)
|
|
297
|
+
note(rootsById.get(String(row["task_id"])), row["at"]);
|
|
298
|
+
if (row["run_id"] !== null)
|
|
299
|
+
note(runRoot.get(Number(row["run_id"])), row["at"]);
|
|
300
|
+
}
|
|
301
|
+
const byRef = new Map();
|
|
302
|
+
for (const [ref, root] of rootByRef)
|
|
303
|
+
byRef.set(ref, families.get(root));
|
|
304
|
+
// A branch answers for every family that used it: the newest activity and any block among them.
|
|
305
|
+
const byBranch = new Map();
|
|
306
|
+
const share = (branch, root) => {
|
|
307
|
+
const one = families.get(root), seen = byBranch.get(branch);
|
|
308
|
+
byBranch.set(branch, seen === undefined ? { ...one } : { activeAt: seen.activeAt > one.activeAt ? seen.activeAt : one.activeAt, blocked: seen.blocked || one.blocked });
|
|
309
|
+
};
|
|
310
|
+
for (const row of refs)
|
|
311
|
+
for (const branch of taskBranches(String(row["id"])))
|
|
312
|
+
share(branch, rootOf(String(row["id"])));
|
|
313
|
+
for (const [branch, root] of branches)
|
|
314
|
+
share(branch, root);
|
|
315
|
+
return { byRef, byBranch };
|
|
316
|
+
}
|
|
317
|
+
/** The idle limit in days as it stands: null when idle cleanup is off (the limit, or automatic cleanup altogether). */
|
|
318
|
+
function idleDays(store) {
|
|
319
|
+
return store.checkoutCleanup() === "never" ? null : IDLE_CHOICES.find(one => one.value === checkoutIdle(store)).days;
|
|
320
|
+
}
|
|
321
|
+
/** When this checkout's task last saw activity, when that is past the limit and only its task's standing keeps it; else null. */
|
|
322
|
+
function idleSince(pool, row, statuses, facts, days, now) {
|
|
323
|
+
if (days === null || row.taskRef === null || row.branch === "unknown" || row.releasedAt === null || row.runner !== null || pool.inUse(row.path).held)
|
|
324
|
+
return null;
|
|
325
|
+
const keep = statusOf(row, statuses)?.keep;
|
|
326
|
+
if (keep !== "waiting for review" && keep !== "task not finished")
|
|
327
|
+
return null;
|
|
328
|
+
const own = facts.byRef.get(row.taskRef), shared = facts.byBranch.get(row.branch);
|
|
329
|
+
if (own === undefined || own.blocked || shared?.blocked === true)
|
|
330
|
+
return null;
|
|
331
|
+
const since = [own.activeAt, shared?.activeAt, row.createdAt, row.leasedAt, row.releasedAt].filter((one) => typeof one === "string").sort().at(-1);
|
|
332
|
+
return Date.parse(since) + days * DAY_MS <= now.getTime() ? since : null;
|
|
333
|
+
}
|
|
334
|
+
/** Every commit a task's runs recorded on this checkout or its branch: what `proveRecreatable` must find on a branch. */
|
|
335
|
+
function recordedHeads(store, row) {
|
|
336
|
+
return store.handle.prepare("SELECT DISTINCT head_revision AS head FROM run WHERE (branch = ? OR worktree = ?) AND head_revision IS NOT NULL").all(row.branch, row.path).map(one => String(one["head"]));
|
|
337
|
+
}
|
|
221
338
|
/** Why a checkout stays before anyone looks at its files, or null: it may go (as far as its task and the setting say). */
|
|
222
339
|
function keptFor(pool, row, statuses, now, cleanup) {
|
|
223
340
|
const status = statusOf(row, statuses);
|
|
@@ -266,6 +383,8 @@ export async function checkoutPlan(store, pool, now, options) {
|
|
|
266
383
|
const rows = store.listWorktrees().filter(row => existsSync(row.path) && (options.repo === undefined || row.repo === options.repo));
|
|
267
384
|
const sizes = await diskBytesAsync(rows.map(row => row.path));
|
|
268
385
|
const statuses = taskStatuses(store, now);
|
|
386
|
+
const days = idleDays(store);
|
|
387
|
+
const facts = idleFacts(store);
|
|
269
388
|
const items = await eachAtMost(rows, INSPECT_AT_ONCE, async (row) => {
|
|
270
389
|
const kept = keptFor(pool, row, statuses, now, options.manual ? "manual" : cleanup);
|
|
271
390
|
let why = kept.why;
|
|
@@ -273,14 +392,23 @@ export async function checkoutPlan(store, pool, now, options) {
|
|
|
273
392
|
const found = await pool.inspect(row.path);
|
|
274
393
|
why = found === "clean" ? null : found;
|
|
275
394
|
}
|
|
276
|
-
|
|
395
|
+
const since = kept.why === null ? null : idleSince(pool, row, statuses, facts, days, now);
|
|
396
|
+
if (since !== null) {
|
|
397
|
+
const found = await pool.inspect(row.path);
|
|
398
|
+
const proven = found === "clean" ? await pool.proveRecreatable(row, recordedHeads(store, row)) : found;
|
|
399
|
+
why = proven !== "clean" ? proven : options.manual ? "idle" : null;
|
|
400
|
+
}
|
|
401
|
+
return { path: row.path, repo: row.repo, branch: row.branch, taskId: kept.taskId, bytes: sizes.get(row.path) ?? 0, releasedAt: row.releasedAt, why,
|
|
402
|
+
...(kept.dueAt === undefined ? {} : { dueAt: kept.dueAt }), ...(since === null ? {} : { idleSince: since }) };
|
|
277
403
|
});
|
|
278
404
|
const go = items.filter(one => one.why === null).sort((a, b) => b.bytes - a.bytes);
|
|
279
405
|
const stay = items.filter(one => one.why !== null).sort((a, b) => (a.why ?? "").localeCompare(b.why ?? "") || b.bytes - a.bytes);
|
|
406
|
+
const idle = items.filter(one => one.idleSince !== undefined && (one.why === null || one.why === "idle"));
|
|
280
407
|
return {
|
|
281
|
-
at: now.toISOString(), cleanup, manual: options.manual, go, stay,
|
|
408
|
+
at: now.toISOString(), cleanup, idle: checkoutIdle(store), manual: options.manual, go, stay,
|
|
282
409
|
count: items.length, totalBytes: items.reduce((sum, one) => sum + one.bytes, 0), freeBytes: go.reduce((sum, one) => sum + one.bytes, 0),
|
|
283
410
|
waitingReview: stay.filter(one => one.why === "waiting for review").length, withChanges: stay.filter(one => one.why === "has changes").length,
|
|
411
|
+
idleCount: idle.length, idleBytes: idle.reduce((sum, one) => sum + one.bytes, 0),
|
|
284
412
|
};
|
|
285
413
|
}
|
|
286
414
|
/** Names exactly what a preview would remove, so a confirmation removes that and nothing else. */
|
|
@@ -290,7 +418,9 @@ export function previewDigest(plan) {
|
|
|
290
418
|
/**
|
|
291
419
|
* Remove what may go: the worker's pass (`manual` false: under the setting, at most AUTO_MAX, one repository) or a
|
|
292
420
|
* clean-up by hand (no cap, every repository). Each checkout is asked again right before it goes; each removal is in
|
|
293
|
-
* the ledger.
|
|
421
|
+
* the ledger. Idle checkouts go only with `idle`, the storage sweep's child (never the service's own pass, whose git
|
|
422
|
+
* and du must not hold up its heartbeats): each is judged again from a fresh read of its task and its family's
|
|
423
|
+
* activity, `spare` (live work), the OS's list of what runs in the folder, and `proveRecreatable`, inside its reservation.
|
|
294
424
|
*/
|
|
295
425
|
export async function cleanCheckouts(store, pool, now, options) {
|
|
296
426
|
const cleanup = store.checkoutCleanup();
|
|
@@ -299,31 +429,51 @@ export async function cleanCheckouts(store, pool, now, options) {
|
|
|
299
429
|
// `only`: the checkouts a person saw in the preview; nothing they didn't see goes.
|
|
300
430
|
const rows = store.listWorktrees().filter(row => existsSync(row.path) && (options.repo === undefined || row.repo === options.repo) && (options.only === undefined || options.only.has(row.path)));
|
|
301
431
|
let statuses = taskStatuses(store, now());
|
|
432
|
+
const days = options.idle === undefined ? null : idleDays(store);
|
|
433
|
+
let facts = days === null ? { byRef: new Map(), byBranch: new Map() } : idleFacts(store);
|
|
434
|
+
// The checkouts going for being idle, and since when: judged at every look, so a task coming back drops out.
|
|
435
|
+
const idle = new Map();
|
|
436
|
+
const mayGo = (row) => {
|
|
437
|
+
idle.delete(row.path);
|
|
438
|
+
if (keptFor(pool, row, statuses, now(), options.manual ? "manual" : cleanup).why === null)
|
|
439
|
+
return options.spare?.(row.path) !== true;
|
|
440
|
+
const since = idleSince(pool, row, statuses, facts, days, now());
|
|
441
|
+
if (since === null)
|
|
442
|
+
return false;
|
|
443
|
+
idle.set(row.path, since);
|
|
444
|
+
return options.spare?.(row.path) !== true;
|
|
445
|
+
};
|
|
446
|
+
const refresh = () => { statuses = taskStatuses(store, now()); if (days !== null)
|
|
447
|
+
facts = idleFacts(store); };
|
|
302
448
|
const sizes = new Map();
|
|
303
449
|
const wanted = (row, fresh) => {
|
|
304
450
|
// Right before a checkout goes, where its task stands is read afresh, and its size taken for the ledger.
|
|
305
451
|
// `spare`: live work uses it (storage-sweep.ts), asked at both looks.
|
|
306
452
|
if (fresh)
|
|
307
|
-
|
|
308
|
-
const go =
|
|
453
|
+
refresh();
|
|
454
|
+
const go = mayGo(row);
|
|
309
455
|
if (go && fresh) {
|
|
310
456
|
sizes.set(row.path, diskBytes([row.path]).get(row.path) ?? 0);
|
|
311
457
|
// du can be slow. The reservation excludes a concurrent checkout lease, and this last read also catches
|
|
312
458
|
// an open run/claim or task change that arrived while size was measured in the sweep child.
|
|
313
|
-
|
|
459
|
+
refresh();
|
|
460
|
+
return mayGo(row) && !(idle.has(row.path) && options.idle?.occupied(row.path) === true);
|
|
314
461
|
}
|
|
315
462
|
return go;
|
|
316
463
|
};
|
|
317
|
-
const
|
|
464
|
+
const proof = async (row) => idle.has(row.path) ? pool.proveRecreatable(row, recordedHeads(store, row)) : "clean";
|
|
465
|
+
const max = options.manual ? Infinity : AUTO_MAX;
|
|
466
|
+
const pruned = await pool.prune(rows, wanted, max, undefined, proof);
|
|
318
467
|
const removed = [];
|
|
319
468
|
for (const row of pruned.removed) {
|
|
320
469
|
const taskId = row.taskRef === null ? null : store.externalIdFor(row.taskRef);
|
|
321
470
|
const bytes = sizes.get(row.path) ?? 0;
|
|
322
|
-
|
|
471
|
+
const since = idle.get(row.path);
|
|
472
|
+
removed.push({ path: row.path, repo: row.repo, branch: row.branch, taskId, bytes, releasedAt: row.releasedAt, why: null, ...(since === undefined ? {} : { idleSince: since }) });
|
|
323
473
|
store.recordAction({ at: now().toISOString(), actor: options.actor, repo: row.repo, taskId, runId: null, action: "checkout removed", outcome: "removed",
|
|
324
|
-
source: options.manual ? "request" : "work", detail: `${basename(row.path)} (released ${row.releasedAt?.slice(0, 10) ?? "?"}, about ${bytesWords(bytes)}); branch ${row.branch} kept` });
|
|
474
|
+
source: options.manual ? "request" : "work", detail: `${basename(row.path)} (${since === undefined ? "" : `idle since ${since.slice(0, 10)}, `}released ${row.releasedAt?.slice(0, 10) ?? "?"}, about ${bytesWords(bytes)}); branch ${row.branch} kept` });
|
|
325
475
|
}
|
|
326
|
-
return { removed, kept: pruned.kept, freed: removed.reduce((sum, one) => sum + one.bytes, 0) };
|
|
476
|
+
return { removed, kept: pruned.kept, freed: removed.reduce((sum, one) => sum + one.bytes, 0), ...(pruned.removed.length >= max ? { more: true } : {}) };
|
|
327
477
|
}
|
|
328
478
|
/** Checkouts this process already looked at since they were let go (path and release time): looked at once. */
|
|
329
479
|
const looked = new Set();
|
|
@@ -381,6 +531,8 @@ export function whyWords(item) {
|
|
|
381
531
|
case "release candidate": return item.dueAt === undefined ? "the deployed release candidate" : `newest release candidate, kept for its deploy until ${item.dueAt.slice(0, 10)}`;
|
|
382
532
|
case "has changes": return "has changes";
|
|
383
533
|
case "has commits": return "has commits on no branch";
|
|
534
|
+
case "not on its branch": return "isn't on its task's branch";
|
|
535
|
+
case "idle": return `idle since ${item.idleSince?.slice(0, 10) ?? "a while"}; the next sweep removes it`;
|
|
384
536
|
case "unreadable": return "git couldn't read it";
|
|
385
537
|
case "git refused": return "git wouldn't remove it";
|
|
386
538
|
case "not due yet": return `due ${item.dueAt?.slice(0, 10) ?? "later"}`;
|
package/dist/cli-help.d.ts
CHANGED
|
@@ -2,4 +2,7 @@
|
|
|
2
2
|
* The top-level usage text, on its own so `toolroll --help` can print it
|
|
3
3
|
* without loading every command family (see cli-fast.ts).
|
|
4
4
|
*/
|
|
5
|
-
export declare const HELP = "toolroll \u2014
|
|
5
|
+
export declare const HELP = "toolroll \u2014 hand coding work to agents, walk away, come back to pull requests\n\nUsage\n toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal\n (--web: the browser; --project-root <dir>: where projects live)\n in a repository without a terminal, under an agent or with --json: only says what it would do\n toolroll open [--web] open this computer's Toolroll again, signed in\n toolroll welcome the three steps to a first result (--json: the model)\n toolroll password set add a password (typed twice, at your terminal)\n toolroll scan [path...] report what is in flight (also bare `toolroll` outside a repository)\n toolroll [path...] the same report for the paths given\n toolroll pulls report what is waiting on a person\n toolroll graph report which work graph is already here\n toolroll repos list connected repositories, and how to adjust\n toolroll repos add <path> add a project; it is in the console at once (no path: the repo you are in)\n toolroll repos remove <path>\n take it out of the console; its tasks and results stay\n toolroll repos add-from-github <owner/name> --root <dir>\n preview, then clone and connect (--yes)\n toolroll link put `toolroll` on your PATH\n toolroll unlink take it off again\n toolroll update update to the latest release: verified, drained, undoable\n (preview first; --yes, --now, --at HH:MM, --rollback)\n toolroll release <branch> release Toolroll itself: checks, your approval, deploy, merge, tag, publish\n (rerun the same command to continue; --help)\n toolroll skills install --claude-code [--dir <path>]\n install this binary's guides for Claude Code (preview first)\n toolroll skills install teach a repo's agents this queue exists (preview first)\n toolroll demo a seeded throwaway sandbox \u2014 see it working in 90 seconds\n toolroll up app + builder for every saved project \u2014 the normal start\n toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)\n toolroll status running, queued, ready results, release check and plan windows\n toolroll integrations which integrations work, and what to do about the ones that don't (--json)\n toolroll lead token mint your lead agent's sign-in: its own work pings nobody\n toolroll lead say \"<text>\" (the lead) one short message in its person's chat\n\nTasks \u2014 `toolroll task` prints the whole list,\nand any task command + --help prints it too\n toolroll approver add <name>\n add a person who can approve work\n toolroll task add <title> queue work\n toolroll task show <id> one task: its plan, builds and what it waits for\n toolroll task approve <id>\n the yes \u2014 nothing builds without one\n toolroll task accept <id> --finish\n accept the result you read (--despite \"<why>\": accept it with a reason, without finishing)\n toolroll task send-back <id> --feedback \"<what to change>\"\n send the result back for changes\n toolroll task retry <id> build it again\n toolroll task ready <id> can I merge it? each check, and what clears a blocker\n toolroll chat --as <you> --token <t>\n talk to your lead: one conversation across every\n project; it proposes, you confirm (--say \"\u2026\" for one turn)\n toolroll flows list | show <id> | create | edit | trigger | script | card | archive\n set up and inspect flows (writes preview until --yes)\n\nMachine and agent commands (the builder, agent tools, recovery):\n toolroll internals lists them; each keeps working as before\n\nWith nothing connected it reports everything it can find below the working\ndirectory. Once you connect repositories it reports those instead.\n\nOptions\n --all report everything discoverable, ignoring connected repos\n --depth <n> directory levels to descend (default: 6)\n --hidden include dot-directories, which are skipped by default\n --dirty also read each working tree for uncommitted files\n --local branches only: no network, no pull requests, no issues\n --json emit a machine-readable envelope instead of a report\n -o <file> also write that envelope to a file (requires --json)\n -h, --help show this\n\nNothing is written, installed, or configured. Every repository is read\nthrough git with --no-optional-locks, so a scan never contends for the\nindex lock with an editor you have open.\n\n--dirty is off by default because reading a working tree is unbounded\nwork: on a large or cloud-synced repo `git status` can take minutes,\nwhere listing branches takes milliseconds.\n\nlink shows what it would do and changes nothing; add --yes to apply it,\nor --to <dir> to choose where. It never uses sudo, never leaves your\nhome directory, and never touches a file it did not create.";
|
|
6
|
+
/** The commands people rarely type: the builder's own loop, the machine contract and recovery. They all keep working;
|
|
7
|
+
* `toolroll internals` (or `toolroll internals --help`) lists them so top-level help stays about the work. */
|
|
8
|
+
export declare const INTERNALS_HELP = "toolroll internals \u2014 machine and agent commands\n\n toolroll contract the machine contract: envelope version + capabilities\n (--commands dumps the declared command guide)\n toolroll skills list the guides this exact binary serves\n toolroll skills get <name> print one guide (version-matched, never stale)\n toolroll health recent server latency, write waits and live streams\n toolroll ready what could be started right now\n toolroll check-progress <run> current or final approved check progress\n toolroll task wait <id> [--timeout <seconds>]\n wait for this attempt to finish or need a person\n toolroll task scope <id> --goal <text>\n write a task's plan from the command line\n toolroll claim <id> --runner <name>\n toolroll heartbeat <lease> / worker release <lease> / reap\n toolroll tick --runner <name> --token <t> --repo <path>\n one unattended pass over the ready set\n toolroll serve --repo <path> run only the console\n (--editor vscode with --runner: file links\n open in VS Code on the device you browse from)\n toolroll watch / daemon install run only the builder\n toolroll doctor check that Toolroll's service holds no key a build could read\n toolroll reconcile recover what the last stretch left behind\n toolroll release --recover-deploy [--stage <dir>] [--yes]\n toolroll release --release-gate <id> [--yes]\n the release tool's recovery steps (toolroll release <branch> runs them for you)";
|
package/dist/cli-help.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* without loading every command family (see cli-fast.ts).
|
|
4
4
|
*/
|
|
5
5
|
import { DEFAULT_MAX_DEPTH } from "./scan.js";
|
|
6
|
-
export const HELP = `toolroll —
|
|
6
|
+
export const HELP = `toolroll — hand coding work to agents, walk away, come back to pull requests
|
|
7
7
|
|
|
8
8
|
Usage
|
|
9
9
|
toolroll at your terminal: start Toolroll here (or attach) and open it in the terminal
|
|
@@ -26,52 +26,41 @@ Usage
|
|
|
26
26
|
toolroll unlink take it off again
|
|
27
27
|
toolroll update update to the latest release: verified, drained, undoable
|
|
28
28
|
(preview first; --yes, --now, --at HH:MM, --rollback)
|
|
29
|
-
toolroll release <branch> release Toolroll itself:
|
|
30
|
-
(
|
|
31
|
-
toolroll contract the machine contract: envelope version + capabilities
|
|
32
|
-
(--commands dumps the declared command guide)
|
|
29
|
+
toolroll release <branch> release Toolroll itself: checks, your approval, deploy, merge, tag, publish
|
|
30
|
+
(rerun the same command to continue; --help)
|
|
33
31
|
toolroll skills install --claude-code [--dir <path>]
|
|
34
32
|
install this binary's guides for Claude Code (preview first)
|
|
35
33
|
toolroll skills install teach a repo's agents this queue exists (preview first)
|
|
36
|
-
toolroll skills list the guides this exact binary serves
|
|
37
|
-
toolroll skills get <name> print one guide (version-matched, never stale)
|
|
38
34
|
toolroll demo a seeded throwaway sandbox — see it working in 90 seconds
|
|
39
35
|
toolroll up app + builder for every saved project — the normal start
|
|
40
36
|
toolroll onboard make your agent Toolroll's lead: add this repo, install its skill (--yes), offer starter flows (--starter)
|
|
41
37
|
toolroll status running, queued, ready results, release check and plan windows
|
|
42
|
-
toolroll health recent server latency, write waits and live streams
|
|
43
38
|
toolroll integrations which integrations work, and what to do about the ones that don't (--json)
|
|
44
|
-
toolroll lead token mint your lead agent's
|
|
39
|
+
toolroll lead token mint your lead agent's sign-in: its own work pings nobody
|
|
45
40
|
toolroll lead say "<text>" (the lead) one short message in its person's chat
|
|
46
41
|
|
|
47
|
-
|
|
48
|
-
and any
|
|
42
|
+
Tasks — \`toolroll task\` prints the whole list,
|
|
43
|
+
and any task command + --help prints it too
|
|
49
44
|
toolroll approver add <name>
|
|
50
|
-
|
|
51
|
-
toolroll ready what could be dispatched right now
|
|
52
|
-
toolroll check-progress <run> current or final approved check progress
|
|
53
|
-
toolroll task wait <id> [--timeout <seconds>]
|
|
54
|
-
wait for this attempt to finish or need a person
|
|
45
|
+
add a person who can approve work
|
|
55
46
|
toolroll task add <title> queue work
|
|
56
|
-
toolroll task
|
|
57
|
-
state what success is; approval binds to it
|
|
47
|
+
toolroll task show <id> one task: its plan, builds and what it waits for
|
|
58
48
|
toolroll task approve <id>
|
|
59
49
|
the yes — nothing builds without one
|
|
60
|
-
toolroll
|
|
61
|
-
|
|
62
|
-
toolroll
|
|
63
|
-
|
|
64
|
-
toolroll
|
|
65
|
-
|
|
50
|
+
toolroll task accept <id> --finish
|
|
51
|
+
accept the result you read (--despite "<why>": accept it with a reason, without finishing)
|
|
52
|
+
toolroll task send-back <id> --feedback "<what to change>"
|
|
53
|
+
send the result back for changes
|
|
54
|
+
toolroll task retry <id> build it again
|
|
55
|
+
toolroll task ready <id> can I merge it? each check, and what clears a blocker
|
|
66
56
|
toolroll chat --as <you> --token <t>
|
|
67
57
|
talk to your lead: one conversation across every
|
|
68
58
|
project; it proposes, you confirm (--say "…" for one turn)
|
|
69
|
-
toolroll
|
|
70
|
-
(
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
toolroll
|
|
74
|
-
toolroll reconcile recover what the last stretch left behind
|
|
59
|
+
toolroll flows list | show <id> | create | edit | trigger | script | card | archive
|
|
60
|
+
set up and inspect flows (writes preview until --yes)
|
|
61
|
+
|
|
62
|
+
Machine and agent commands (the builder, agent tools, recovery):
|
|
63
|
+
toolroll internals lists them; each keeps working as before
|
|
75
64
|
|
|
76
65
|
With nothing connected it reports everything it can find below the working
|
|
77
66
|
directory. Once you connect repositories it reports those instead.
|
|
@@ -97,3 +86,31 @@ where listing branches takes milliseconds.
|
|
|
97
86
|
link shows what it would do and changes nothing; add --yes to apply it,
|
|
98
87
|
or --to <dir> to choose where. It never uses sudo, never leaves your
|
|
99
88
|
home directory, and never touches a file it did not create.`;
|
|
89
|
+
/** The commands people rarely type: the builder's own loop, the machine contract and recovery. They all keep working;
|
|
90
|
+
* `toolroll internals` (or `toolroll internals --help`) lists them so top-level help stays about the work. */
|
|
91
|
+
export const INTERNALS_HELP = `toolroll internals — machine and agent commands
|
|
92
|
+
|
|
93
|
+
toolroll contract the machine contract: envelope version + capabilities
|
|
94
|
+
(--commands dumps the declared command guide)
|
|
95
|
+
toolroll skills list the guides this exact binary serves
|
|
96
|
+
toolroll skills get <name> print one guide (version-matched, never stale)
|
|
97
|
+
toolroll health recent server latency, write waits and live streams
|
|
98
|
+
toolroll ready what could be started right now
|
|
99
|
+
toolroll check-progress <run> current or final approved check progress
|
|
100
|
+
toolroll task wait <id> [--timeout <seconds>]
|
|
101
|
+
wait for this attempt to finish or need a person
|
|
102
|
+
toolroll task scope <id> --goal <text>
|
|
103
|
+
write a task's plan from the command line
|
|
104
|
+
toolroll claim <id> --runner <name>
|
|
105
|
+
toolroll heartbeat <lease> / worker release <lease> / reap
|
|
106
|
+
toolroll tick --runner <name> --token <t> --repo <path>
|
|
107
|
+
one unattended pass over the ready set
|
|
108
|
+
toolroll serve --repo <path> run only the console
|
|
109
|
+
(--editor vscode with --runner: file links
|
|
110
|
+
open in VS Code on the device you browse from)
|
|
111
|
+
toolroll watch / daemon install run only the builder
|
|
112
|
+
toolroll doctor check that Toolroll's service holds no key a build could read
|
|
113
|
+
toolroll reconcile recover what the last stretch left behind
|
|
114
|
+
toolroll release --recover-deploy [--stage <dir>] [--yes]
|
|
115
|
+
toolroll release --release-gate <id> [--yes]
|
|
116
|
+
the release tool's recovery steps (toolroll release <branch> runs them for you)`;
|
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,8 @@ import { detectGraphs, chooseBackend, setupOptions, LABELS as GRAPH_LABELS, } fr
|
|
|
37
37
|
import { openStore, openStoreReadOnly, databasePath } from "./store.js";
|
|
38
38
|
import { renderReport, renderPulls, renderGraph } from "./render.js";
|
|
39
39
|
import { DEFAULT_MAX_DEPTH } from "./scan.js";
|
|
40
|
-
import { HELP } from "./cli-help.js";
|
|
40
|
+
import { HELP, INTERNALS_HELP } from "./cli-help.js";
|
|
41
|
+
import { withTaskAlias } from "./task-aliases.js";
|
|
41
42
|
import { quickAnswer } from "./cli-fast.js";
|
|
42
43
|
import { BIN_NAME, chooseTarget, planLink, applyLink, planUnlink, applyUnlink, } from "./link.js";
|
|
43
44
|
const USAGE_EXIT = 2;
|
|
@@ -113,6 +114,8 @@ export const OPERATE_COMMANDS = new Set([
|
|
|
113
114
|
"evals",
|
|
114
115
|
// Only `lead token` reaches here; the central service's lead verbs answer first.
|
|
115
116
|
"lead",
|
|
117
|
+
// Only `release approve` reaches here: every other `release` form is release-cli.ts, routed before this set.
|
|
118
|
+
"release",
|
|
116
119
|
"onboard",
|
|
117
120
|
"status",
|
|
118
121
|
"ready",
|
|
@@ -135,7 +138,9 @@ export const OPERATE_COMMANDS = new Set([
|
|
|
135
138
|
"review",
|
|
136
139
|
"assignment",
|
|
137
140
|
"knowledge",
|
|
141
|
+
"map",
|
|
138
142
|
"memory",
|
|
143
|
+
"flaky",
|
|
139
144
|
"models",
|
|
140
145
|
"claim",
|
|
141
146
|
"heartbeat",
|
|
@@ -303,6 +308,8 @@ function routinesMoved(argv, write) {
|
|
|
303
308
|
return 0;
|
|
304
309
|
}
|
|
305
310
|
async function dispatch(argv, write, mainOptions) {
|
|
311
|
+
// People's names for task commands (task send-back, retry, recheck) are the commands they name, before anything else.
|
|
312
|
+
argv = withTaskAlias(argv);
|
|
306
313
|
// v115: routines became scheduled flows. Every old `routine …` invocation answers with one line pointing there —
|
|
307
314
|
// hidden from help and the contract, before anything remote or local opens, changing nothing.
|
|
308
315
|
if (argv[0] === "routine")
|
|
@@ -319,6 +326,11 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
319
326
|
// Command families below load when their command runs, so one command never pays for the others.
|
|
320
327
|
if (argv[0] === 'health')
|
|
321
328
|
return (await import("./health-cli.js")).runHealthCommand(argv.slice(1), write, mainOptions.connect ?? mainOptions.remote);
|
|
329
|
+
// The machine and agent commands, listed apart so top-level help stays about the work (each still runs by name).
|
|
330
|
+
if (argv[0] === 'internals' && argv.slice(1).every(arg => arg === '--help' || arg === '-h')) {
|
|
331
|
+
write(INTERNALS_HELP);
|
|
332
|
+
return 0;
|
|
333
|
+
}
|
|
322
334
|
// This computer's own service, read here and never on a central server.
|
|
323
335
|
if (argv[0] === 'doctor')
|
|
324
336
|
return (await import("./doctor.js")).runDoctorCommand(argv.slice(1), write);
|
|
@@ -344,6 +356,9 @@ async function dispatch(argv, write, mainOptions) {
|
|
|
344
356
|
}
|
|
345
357
|
if (first === "update")
|
|
346
358
|
return (await import("./toolroll-update-cli.js")).runUpdateCommand(rest, write, mainOptions.update);
|
|
359
|
+
// `toolroll release approve`: a person's approval, with the operate commands' own credential prompt and context.
|
|
360
|
+
if (first === "release" && rest[0] === "approve")
|
|
361
|
+
return runOperate("release", rest, write, mainOptions.operate ?? {});
|
|
347
362
|
if (first === "release") {
|
|
348
363
|
const { runReleaseCommand } = await import("./release-cli.js");
|
|
349
364
|
return runReleaseCommand(rest, write, { releaseLease: lease => runOperate("worker", ["release", ...lease], write, mainOptions.operate ?? {}), ...mainOptions.release });
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The static read of one TS/JS file and the module resolution over a set of paths held in memory: nothing here touches
|
|
3
|
+
* the filesystem, a repository or a checker. Repository context (repository-context.ts) reads a checkout through it; the
|
|
4
|
+
* code map's parser child (code-map-main.ts) reads blobs through it. The file name decides the parse mode.
|
|
5
|
+
*/
|
|
6
|
+
import type ts from 'typescript-parser';
|
|
7
|
+
export type Compiler = typeof ts;
|
|
8
|
+
export type EdgeKind = 'imports' | 're-exports' | 'dynamic-import';
|
|
9
|
+
export type DeclarationKind = 'function' | 'class' | 'interface' | 'type' | 'enum' | 'method' | 'variable' | 'default';
|
|
10
|
+
export type Declaration = {
|
|
11
|
+
name: string;
|
|
12
|
+
kind: Exclude<DeclarationKind, 'default'>;
|
|
13
|
+
line: number;
|
|
14
|
+
endLine: number;
|
|
15
|
+
};
|
|
16
|
+
export type ExportedName = {
|
|
17
|
+
name: string;
|
|
18
|
+
kind: DeclarationKind;
|
|
19
|
+
line: number;
|
|
20
|
+
};
|
|
21
|
+
export type Specifier = {
|
|
22
|
+
specifier: string;
|
|
23
|
+
kind: EdgeKind;
|
|
24
|
+
line: number;
|
|
25
|
+
};
|
|
26
|
+
export type Extracted = {
|
|
27
|
+
declarations: Declaration[];
|
|
28
|
+
exports: ExportedName[];
|
|
29
|
+
specifiers: Specifier[];
|
|
30
|
+
parseFailed: boolean;
|
|
31
|
+
};
|
|
32
|
+
/** Declarations, the names the file exports (under the name they are exported as) and the module specifiers it names. `require` isn't counted: it may
|
|
33
|
+
* be shadowed, and a guessed call isn't a proven relationship. */
|
|
34
|
+
export declare function extractFile(compiler: Compiler, fileName: string, text: string): Extracted;
|
|
35
|
+
/** The compiler options a project's tsconfig text gives, read as JSONC and converted, non-string options included.
|
|
36
|
+
* `extends` isn't followed. */
|
|
37
|
+
export declare function compilerOptionsOf(compiler: Compiler, configPath: string, text: string | undefined, root: string): {
|
|
38
|
+
options: ts.CompilerOptions;
|
|
39
|
+
problem: 'extends' | 'unreadable' | null;
|
|
40
|
+
};
|
|
41
|
+
/** Resolve a specifier to one of `paths` (absolute, in memory); null when it names nothing there. */
|
|
42
|
+
export declare function resolveAll(compiler: Compiler, root: string, paths: ReadonlyMap<string, {
|
|
43
|
+
text?: string;
|
|
44
|
+
}>, directories: ReadonlySet<string>, options: ts.CompilerOptions, normal: (file: string) => string): (specifier: string, fromFile: string) => string | null;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
const declarationKind = (compiler, node) => compiler.isFunctionDeclaration(node) ? 'function' : compiler.isClassDeclaration(node) ? 'class' : compiler.isInterfaceDeclaration(node) ? 'interface'
|
|
2
|
+
: compiler.isTypeAliasDeclaration(node) ? 'type' : compiler.isEnumDeclaration(node) ? 'enum' : compiler.isMethodDeclaration(node) ? 'method'
|
|
3
|
+
: compiler.isVariableDeclaration(node) ? 'variable' : null;
|
|
4
|
+
function exportedAtTop(compiler, node) {
|
|
5
|
+
const statement = compiler.isVariableDeclaration(node) ? node.parent?.parent : node;
|
|
6
|
+
if (!statement || !compiler.isSourceFile(statement.parent))
|
|
7
|
+
return false;
|
|
8
|
+
return (compiler.canHaveModifiers(statement) ? compiler.getModifiers(statement) : undefined)?.some(one => one.kind === compiler.SyntaxKind.ExportKeyword) ?? false;
|
|
9
|
+
}
|
|
10
|
+
/** Declarations, the names the file exports (under the name they are exported as) and the module specifiers it names. `require` isn't counted: it may
|
|
11
|
+
* be shadowed, and a guessed call isn't a proven relationship. */
|
|
12
|
+
export function extractFile(compiler, fileName, text) {
|
|
13
|
+
const tree = compiler.createSourceFile(fileName, text, compiler.ScriptTarget.Latest, true);
|
|
14
|
+
const parseFailed = !!tree.parseDiagnostics?.length;
|
|
15
|
+
const line = (pos) => tree.getLineAndCharacterOfPosition(Math.min(pos, text.length)).line + 1;
|
|
16
|
+
const declarations = [], specifiers = [], exports = [];
|
|
17
|
+
const named = [];
|
|
18
|
+
const visit = (node) => {
|
|
19
|
+
const kind = declarationKind(compiler, node);
|
|
20
|
+
if (kind !== null) {
|
|
21
|
+
const name = node.name;
|
|
22
|
+
const at = line(node.getStart(tree));
|
|
23
|
+
const out = exportedAtTop(compiler, node);
|
|
24
|
+
const isDefault = out && (compiler.canHaveModifiers(node) ? compiler.getModifiers(node) : undefined)?.some(one => one.kind === compiler.SyntaxKind.DefaultKeyword);
|
|
25
|
+
if (name && compiler.isIdentifier(name)) {
|
|
26
|
+
declarations.push({ name: name.text, kind, line: at, endLine: line(node.end) });
|
|
27
|
+
if (out)
|
|
28
|
+
exports.push({ name: name.text, kind, line: at });
|
|
29
|
+
}
|
|
30
|
+
else if (!name && isDefault)
|
|
31
|
+
exports.push({ name: 'default', kind: 'default', line: at });
|
|
32
|
+
}
|
|
33
|
+
if (compiler.isExportAssignment(node) && !node.isExportEquals) {
|
|
34
|
+
if (compiler.isIdentifier(node.expression))
|
|
35
|
+
named.push({ local: node.expression.text, as: 'default' });
|
|
36
|
+
else
|
|
37
|
+
exports.push({ name: 'default', kind: 'default', line: line(node.getStart(tree)) });
|
|
38
|
+
}
|
|
39
|
+
if (compiler.isImportDeclaration(node) && compiler.isStringLiteral(node.moduleSpecifier))
|
|
40
|
+
specifiers.push({ specifier: node.moduleSpecifier.text, kind: 'imports', line: line(node.getStart(tree)) });
|
|
41
|
+
if (compiler.isExportDeclaration(node)) {
|
|
42
|
+
if (node.moduleSpecifier && compiler.isStringLiteral(node.moduleSpecifier))
|
|
43
|
+
specifiers.push({ specifier: node.moduleSpecifier.text, kind: 're-exports', line: line(node.getStart(tree)) });
|
|
44
|
+
else if (!node.moduleSpecifier && node.exportClause && compiler.isNamedExports(node.exportClause)) {
|
|
45
|
+
for (const one of node.exportClause.elements)
|
|
46
|
+
named.push({ local: (one.propertyName ?? one.name).text, as: one.name.text });
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
if (compiler.isCallExpression(node) && node.arguments.length === 1 && compiler.isStringLiteral(node.arguments[0]) && node.expression.kind === compiler.SyntaxKind.ImportKeyword) {
|
|
50
|
+
specifiers.push({ specifier: node.arguments[0].text, kind: 'dynamic-import', line: line(node.getStart(tree)) });
|
|
51
|
+
}
|
|
52
|
+
compiler.forEachChild(node, visit);
|
|
53
|
+
};
|
|
54
|
+
visit(tree);
|
|
55
|
+
// `export { a as b }` and `export default a`: the local declaration, under the name it is exported as.
|
|
56
|
+
for (const { local, as } of named) {
|
|
57
|
+
const found = declarations.find(one => one.name === local && one.kind !== 'method');
|
|
58
|
+
if (found !== undefined && !exports.some(one => one.name === as))
|
|
59
|
+
exports.push({ name: as, kind: as === 'default' ? 'default' : found.kind, line: found.line });
|
|
60
|
+
}
|
|
61
|
+
return { declarations, exports, specifiers, parseFailed };
|
|
62
|
+
}
|
|
63
|
+
/** The compiler options a project's tsconfig text gives, read as JSONC and converted, non-string options included.
|
|
64
|
+
* `extends` isn't followed. */
|
|
65
|
+
export function compilerOptionsOf(compiler, configPath, text, root) {
|
|
66
|
+
const base = { module: compiler.ModuleKind.NodeNext, moduleResolution: compiler.ModuleResolutionKind.NodeNext, allowJs: true, resolveJsonModule: true };
|
|
67
|
+
if (text === undefined)
|
|
68
|
+
return { options: base, problem: null };
|
|
69
|
+
const parsed = compiler.parseConfigFileTextToJson(configPath, text);
|
|
70
|
+
if (parsed.error || !parsed.config?.compilerOptions)
|
|
71
|
+
return { options: base, problem: 'unreadable' };
|
|
72
|
+
const converted = compiler.convertCompilerOptionsFromJson(parsed.config.compilerOptions, root);
|
|
73
|
+
return { options: { ...base, ...converted.options }, problem: parsed.config.extends ? 'extends' : null };
|
|
74
|
+
}
|
|
75
|
+
/** Resolve a specifier to one of `paths` (absolute, in memory); null when it names nothing there. */
|
|
76
|
+
export function resolveAll(compiler, root, paths, directories, options, normal) {
|
|
77
|
+
const host = {
|
|
78
|
+
fileExists: file => paths.has(normal(file)),
|
|
79
|
+
readFile: file => paths.get(normal(file))?.text,
|
|
80
|
+
directoryExists: dir => directories.has(normal(dir)),
|
|
81
|
+
getCurrentDirectory: () => root,
|
|
82
|
+
realpath: file => normal(file),
|
|
83
|
+
};
|
|
84
|
+
return (specifier, fromFile) => {
|
|
85
|
+
const resolved = compiler.resolveModuleName(specifier, fromFile, options, host).resolvedModule?.resolvedFileName;
|
|
86
|
+
return resolved && paths.has(normal(resolved)) ? normal(resolved) : null;
|
|
87
|
+
};
|
|
88
|
+
}
|