toolroll 0.9.7 → 0.9.9
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 +48 -1
- package/dist/assignment-adapters.d.ts +1 -0
- package/dist/assignment-adapters.js +2 -0
- package/dist/assignment-brief.d.ts +2 -0
- package/dist/assignment-brief.js +2 -1
- package/dist/assignment-presentation.js +3 -1
- package/dist/assignment-status.d.ts +1 -1
- package/dist/assignment-ui.js +5 -0
- package/dist/assignment.d.ts +15 -0
- package/dist/assignment.js +56 -9
- package/dist/browser/workspace.css +1 -1
- package/dist/browser/workspace.js +18 -18
- package/dist/browser-crew.js +4 -0
- package/dist/browser-workspace.d.ts +40 -0
- package/dist/build-review.d.ts +38 -0
- package/dist/build-review.js +341 -0
- package/dist/builder.d.ts +2 -0
- package/dist/builder.js +114 -8
- package/dist/chat-controls.d.ts +1 -1
- package/dist/chat-controls.js +1 -1
- package/dist/check-levels-ui.d.ts +5 -0
- package/dist/check-levels-ui.js +16 -2
- package/dist/check-progress.js +4 -3
- package/dist/checkout-cleanup.d.ts +40 -2
- package/dist/checkout-cleanup.js +129 -9
- package/dist/cli.js +2 -0
- package/dist/desktop-update.d.ts +52 -0
- package/dist/desktop-update.js +147 -5
- package/dist/dispatch.js +2 -1
- package/dist/dispose.d.ts +4 -3
- package/dist/dispose.js +12 -4
- package/dist/evidence.d.ts +6 -0
- package/dist/evidence.js +8 -0
- package/dist/exec.d.ts +5 -0
- package/dist/exec.js +30 -4
- package/dist/first-run.d.ts +1 -1
- package/dist/first-run.js +8 -6
- package/dist/invoke.js +5 -0
- package/dist/lead-context.d.ts +1 -1
- package/dist/lead-context.js +4 -2
- package/dist/lead-status.d.ts +10 -1
- package/dist/lead-status.js +21 -24
- package/dist/modes.d.ts +3 -1
- package/dist/modes.js +3 -1
- package/dist/needs-you.d.ts +55 -0
- package/dist/needs-you.js +83 -0
- package/dist/operate.d.ts +2 -0
- package/dist/operate.js +231 -36
- package/dist/plane-review.js +4 -0
- package/dist/process-custody.js +8 -0
- package/dist/process-recovery-eligibility.d.ts +1 -0
- package/dist/project-learning.d.ts +3 -3
- package/dist/project-learning.js +28 -17
- package/dist/proof.d.ts +3 -1
- package/dist/proof.js +4 -2
- package/dist/provider.js +16 -0
- package/dist/result-completion.d.ts +4 -0
- package/dist/result-completion.js +11 -0
- package/dist/retention-ui.d.ts +3 -1
- package/dist/retention-ui.js +4 -4
- package/dist/retention.d.ts +13 -6
- package/dist/retention.js +50 -12
- package/dist/review-context.d.ts +3 -0
- package/dist/review-context.js +45 -5
- package/dist/review-switch.d.ts +41 -0
- package/dist/review-switch.js +88 -0
- package/dist/reviewer.d.ts +24 -2
- package/dist/reviewer.js +49 -1
- package/dist/run-closeout.d.ts +89 -0
- package/dist/run-closeout.js +302 -0
- package/dist/serve.d.ts +15 -1
- package/dist/serve.js +158 -18
- package/dist/setup-guide.d.ts +2 -2
- package/dist/shared-deps.d.ts +94 -0
- package/dist/shared-deps.js +365 -0
- package/dist/storage.js +2 -0
- package/dist/store.d.ts +72 -1
- package/dist/store.js +258 -31
- package/dist/surface.js +5 -1
- package/dist/task-status.d.ts +22 -5
- package/dist/task-status.js +45 -24
- package/dist/telegram-progress.js +2 -1
- package/dist/telegram-status.js +3 -3
- package/dist/test-temp.d.ts +19 -0
- package/dist/test-temp.js +98 -0
- package/dist/toolroll-update-ui.js +7 -2
- package/dist/toolroll-update.d.ts +21 -1
- package/dist/toolroll-update.js +135 -5
- package/dist/work-index.d.ts +11 -0
- package/dist/work-index.js +103 -16
- package/dist/work-summary.d.ts +3 -1
- package/dist/work-summary.js +2 -1
- package/dist/workspace-ui.d.ts +3 -1
- package/dist/workspace-ui.js +10 -9
- package/dist/worktree.d.ts +16 -0
- package/dist/worktree.js +47 -0
- package/package.json +1 -1
package/dist/serve.js
CHANGED
|
@@ -71,8 +71,9 @@ import { hasDisguisedText } from './decision.js';
|
|
|
71
71
|
import { checkPublishing, completeAndOpenPullRequest, mergeAsPerson, pullRequestBlocker, publishingOf, saveMergeSettings, savePublishing, newestPullRequestOf, MERGE_METHODS } from './pull-request-flow.js';
|
|
72
72
|
import { leadBriefHtml, LEAD_CONTEXT_CSS } from './lead-context.js';
|
|
73
73
|
import { assignmentCatchUp } from './assignment-brief.js';
|
|
74
|
-
import { assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
74
|
+
import { assignmentActionHref, assignmentCardOf, assignmentStatusOf, assignmentSummaryHtml, assignmentWithEvidence, ASSIGNMENT_CSS } from './assignment-ui.js';
|
|
75
75
|
import { assignmentStageOf, pullRequestFactOf, stageOfCode, stageOfDispatch, statusDetailsHtml, statusIconSvg, statusWhyHtml, taskStatusOf, TASK_STATUS_CSS } from './task-status.js';
|
|
76
|
+
import { NEEDS } from './needs-you.js';
|
|
76
77
|
import { assignmentPresentationOf, historicalAssessmentReason, shortenedMaterialReason } from './assignment-presentation.js';
|
|
77
78
|
import { ledgerBody } from "./ledger-view.js";
|
|
78
79
|
import { EVIDENCE_PACK_CSS, evidencePack, evidencePackHtml, exportDay, ledgerExportChunks } from "./evidence-pack.js";
|
|
@@ -227,13 +228,14 @@ import { STORAGE_CSS, storageHtml } from "./storage-ui.js";
|
|
|
227
228
|
import { bytesWords, parseCleanup } from "./storage.js";
|
|
228
229
|
import { checkoutPlan, cleanCheckouts, discardCheckout, previewDigest } from "./checkout-cleanup.js";
|
|
229
230
|
import { UPDATES_CSS, newerThan, updateStepsHtml, updatesHtml, updatesScript } from "./toolroll-update-ui.js";
|
|
230
|
-
import { abandonRuntimeUpdate, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate, requestRuntimeUpdateCancel, runningWorkWords, runtimeUpdateStatus, runtimeUpdateTerminal, currentRuntime } from "./toolroll-update.js";
|
|
231
|
+
import { abandonRuntimeUpdate, launchRuntimeUpdate, markWhatsNewSeen, prepareRuntimeUpdate, requestRuntimeUpdateCancel, runningWorkWords, runtimeUpdateStatus, runtimeUpdateTerminal, currentRuntime, releaseStalledUpdate, waitingUpdate } from "./toolroll-update.js";
|
|
231
232
|
import { latestVersionNow } from "./releases.js";
|
|
232
|
-
import { RETENTION_KINDS, lastSweepAt, parsePeriod, retentionPlan } from "./retention.js";
|
|
233
|
+
import { RETENTION_KINDS, lastSweepAt, parsePeriod, periodChoices, retentionPlan } from "./retention.js";
|
|
233
234
|
import { EXPORT_CSS, dataExportHtml } from "./export-ui.js";
|
|
234
235
|
import { buildExport, exportZip } from "./export.js";
|
|
235
236
|
import { PROJECT_DELETE_CSS, projectDeleteConfirmHtml, projectSettingsHtml } from "./project-delete-ui.js";
|
|
236
237
|
import { parseProjectConcurrency, PROJECT_CONCURRENCY_DEFAULT, projectConcurrency, saveProjectConcurrency } from "./project-concurrency.js";
|
|
238
|
+
import { buildReviewOf, findingWords } from "./review-switch.js";
|
|
237
239
|
import { deleteProject, holdingsWords, projectHoldings, projectRunning } from "./project-delete.js";
|
|
238
240
|
import { POLICY_CSS, policyHtml } from "./policy-ui.js";
|
|
239
241
|
import { checkPolicy, parseList, policyParts } from "./policy.js";
|
|
@@ -1695,7 +1697,7 @@ export function createDecisionServer(options) {
|
|
|
1695
1697
|
}
|
|
1696
1698
|
const read = new Set(["/settings/flows", "/settings/skills", "/settings/knowledge", "/settings", "/settings/learning", "/recipes", "/recipes/run", "/recipes/start", "/recipes/new", "/recipes/edit", "/recipes/from-task", "/recipes/preview", "/recipes/export", "/", "/inbox", "/work", "/projects", "/people", "/ledger", "/ledger/export", "/next", "/board", "/tasks", "/tasks/new", "/runs", "/review", "/done", "/routines", "/menu"]);
|
|
1697
1699
|
const write = new Set(["/settings/flows/on", "/settings/skills/import", "/settings/skills/change", "/settings/skills/revise", "/settings/knowledge/change", "/settings/knowledge/refresh", "/settings/learning/change", "/recipes/prepare", "/recipes/preview", "/recipes/import", "/recipes/save", "/recipes/launch", "/projects/select", "/tasks/add", "/routines/add"]);
|
|
1698
|
-
const task = matchTaskPath(path, request.method === "GET" ? "(/evidence)?$" : "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|next|reopen|steer|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|stop|resume-arm|resume)$");
|
|
1700
|
+
const task = matchTaskPath(path, request.method === "GET" ? "(/evidence)?$" : "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|next|reopen|steer|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|confirm-stopped|stop|resume-arm|resume)$");
|
|
1699
1701
|
const resource = request.method === "GET"
|
|
1700
1702
|
? /^\/(?:r|d)\/[0-9]{1,15}(?:\/evidence\/[0-9]{1,15})?$/.test(path) || /^\/routines\/[0-9]{1,15}$/.test(path) || path === "/flows" || /^\/flows\/[0-9]{1,15}(\/insights|\/export|\/runs\/[0-9]{1,15}\/[0-9]{1,15})?$/.test(path) || path === "/flows/new" || /^\/flows\/new\/[a-z-]{1,40}$/.test(path)
|
|
1701
1703
|
: /^\/d\/[0-9]{1,15}\/answer$/.test(path) || /^\/routines\/[0-9]{1,15}\/(approve|refresh|pause|resume|run-now)$/.test(path) || path === "/flows/new" || /^\/flows\/new\/[a-z-]{1,40}$/.test(path) || path === "/flows/import" || /^\/flows\/[0-9]{1,15}\/(save|cards|archive|scripts)$/.test(path) || /^\/flows\/[0-9]{1,15}\/cards\/[0-9]{1,15}\/(move|decide|cancel|comment|assign|watch)$/.test(path) || /^\/flows\/[0-9]{1,15}\/triggers(\/[0-9]{1,15}\/(pause|resume|remove|check|press|renew|secret|share|unshare))?$/.test(path) || /^\/r\/[0-9]{1,15}\/(note|comment|revise|draft-repair|checks|add-tests)$/.test(path);
|
|
@@ -3884,6 +3886,11 @@ export function createDecisionServer(options) {
|
|
|
3884
3886
|
const databaseFile = store.databaseFile();
|
|
3885
3887
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name) || databaseFile === null)
|
|
3886
3888
|
return refuse(response, who, 403, "An instance operator updates Toolroll.", "/settings");
|
|
3889
|
+
// A record it cannot release is shown as it is; the page never fails over it.
|
|
3890
|
+
try {
|
|
3891
|
+
releaseStalledUpdate(dirname(databaseFile), clock());
|
|
3892
|
+
}
|
|
3893
|
+
catch { /* shown as last saved */ }
|
|
3887
3894
|
const status = runtimeUpdateStatus(dirname(databaseFile));
|
|
3888
3895
|
if (url.searchParams.get("fragment") === "steps")
|
|
3889
3896
|
return respond(response, 200, "text/html; charset=utf-8", status.journal ? updateStepsHtml(status.journal, status.running) : `<div id="update-live" data-done="1"></div>`);
|
|
@@ -3898,7 +3905,7 @@ export function createDecisionServer(options) {
|
|
|
3898
3905
|
if (url.pathname === "/settings/retention") {
|
|
3899
3906
|
if (who.via !== "cookie" || !store.isInstanceOperator(who.name))
|
|
3900
3907
|
return refuse(response, who, 403, "An instance operator sets retention.", "/settings");
|
|
3901
|
-
const view = { periods: store.retentionPeriods(), next: retentionPlan(store, evidenceRoot, now).counts, lastSweep: lastSweepAt(store), csrf: who.session.csrf };
|
|
3908
|
+
const view = { periods: store.retentionPeriods(), chosen: Object.keys(store.retentionChosen()), next: retentionPlan(store, evidenceRoot, now).counts, lastSweep: lastSweepAt(store), csrf: who.session.csrf };
|
|
3902
3909
|
return sendScreen(response, 200, screen("Retention", `<p><a href="/settings">Settings</a></p><h1>Retention</h1>${retentionHtml(view, { said: url.searchParams.get("said"), problem: url.searchParams.get("problem") })}`, { chrome: chromeFor(project, "settings") }));
|
|
3903
3910
|
}
|
|
3904
3911
|
// Settings → Storage: what task checkouts use, cleaning up now (the preview behind the password), and when a
|
|
@@ -3933,6 +3940,7 @@ export function createDecisionServer(options) {
|
|
|
3933
3940
|
const quick = liveQuickCommand(store, repo);
|
|
3934
3941
|
const html = checkSettingsHtml({ repo, name: projectName(repo), csrf: who.session.csrf, canChange: who.role === "approver" && !store.isDemo(),
|
|
3935
3942
|
level: projectCheckLevel(store, repo).level, full: store.liveVerifyCommand(repo), quick, suggestion: quick === null ? suggestQuickCommand(repo) : null,
|
|
3943
|
+
review: (({ on, source }) => ({ on, source }))(store.reviewSwitch(repo, clock())),
|
|
3936
3944
|
said: url.searchParams.get("said"), problem: url.searchParams.get("problem") });
|
|
3937
3945
|
return sendScreen(response, 200, screen("Checks", `<p><a href="/projects">Projects</a></p><h1>Checks</h1>${html}`, { chrome: chromeFor(project, "projects") }));
|
|
3938
3946
|
}
|
|
@@ -4887,6 +4895,8 @@ export function createDecisionServer(options) {
|
|
|
4887
4895
|
notices.unshift(DEMO_BANNER);
|
|
4888
4896
|
if (s.chrome.modeBanner)
|
|
4889
4897
|
notices.push(s.chrome.modeBanner.words);
|
|
4898
|
+
if (s.chrome.updateWaiting)
|
|
4899
|
+
notices.push(s.chrome.updateWaiting.words);
|
|
4890
4900
|
let crew = { crew: [], crewTruncated: false };
|
|
4891
4901
|
try {
|
|
4892
4902
|
const project = s.chrome.active === 'chat' ? null : s.chrome.project;
|
|
@@ -5016,14 +5026,20 @@ export function createDecisionServer(options) {
|
|
|
5016
5026
|
const weekAgo = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
5017
5027
|
const counts = [
|
|
5018
5028
|
{ key: "working", label: "Working now", value: agents.length, href: "/work?view=running" },
|
|
5019
|
-
|
|
5020
|
-
{ key: "
|
|
5029
|
+
// Each by its shared headline, as the Tasks list words it: a result whose checks failed waits on you, it isn't ready.
|
|
5030
|
+
{ key: "waiting", label: "Waiting on you", value: needs.items.filter(one => one.status.label !== "Ready for review").length, href: "/work?view=needs-you" },
|
|
5031
|
+
{ key: "ready", label: "Ready to review", value: needs.items.filter(one => one.status.label === "Ready for review").length, href: "/work?view=needs-you" },
|
|
5021
5032
|
{ key: "done", label: "Done this week", value: done.items.filter(one => (one.completion?.at ?? one.updatedAt) >= weekAgo).length, href: "/work?view=completed" },
|
|
5022
5033
|
];
|
|
5023
|
-
const tabOf = (
|
|
5024
|
-
|
|
5025
|
-
|
|
5034
|
+
const tabOf = (one) => one.assignmentState === "ready-to-check" ? one.status.label === "Ready for review" ? "ready" : "needs-you"
|
|
5035
|
+
: one.assignmentState === "needs-decision" ? "needs-you" : one.assignmentState === "working" || one.assignmentState === "checking" ? "running" : "finished";
|
|
5036
|
+
const catchUp = all.items.filter(one => tabOf(one) !== "finished" || one.updatedAt >= weekAgo).slice(0, 40).map(one => ({
|
|
5037
|
+
id: one.rootId, title: one.title, href: taskHref(one.rootId), project: one.repo === null ? null : projectName(one.repo), tab: tabOf(one),
|
|
5026
5038
|
label: one.status.label, tone: one.status.tone, detail: one.status.detail, at: one.updatedAt,
|
|
5039
|
+
action: (() => {
|
|
5040
|
+
const href = one.status.label === "Needs you" && one.primaryAction !== null ? browserWorkActionHref(one) : null;
|
|
5041
|
+
return href === null ? null : { label: one.primaryAction.label, href };
|
|
5042
|
+
})(),
|
|
5027
5043
|
}));
|
|
5028
5044
|
const windows = who.via === "cookie" && store.isInstanceOperator(who.name) ? limitsView(store.providerLimits(), [], { project: projectName, teammate: id => `Teammate ${id}` }, now) : null;
|
|
5029
5045
|
return {
|
|
@@ -5103,6 +5119,14 @@ export function createDecisionServer(options) {
|
|
|
5103
5119
|
return {};
|
|
5104
5120
|
return { update: { version: update.release.version, security: update.release.security, href: "/settings#updates", dismissHref: "/settings/updates/dismiss" } };
|
|
5105
5121
|
})(),
|
|
5122
|
+
...(() => {
|
|
5123
|
+
const databaseFile = store.databaseFile();
|
|
5124
|
+
// Settings → Updates says a `toolroll update` in full; every other page (and the app's update everywhere) names it once.
|
|
5125
|
+
if (!actor || databaseFile === null || !store.isInstanceOperator(actor))
|
|
5126
|
+
return {};
|
|
5127
|
+
const waiting = waitingUpdate(databaseFile, run => store.stopQuiescenceProblem(run) !== null, clock());
|
|
5128
|
+
return waiting === null || (!waiting.app && facts?.returnTo?.startsWith("/settings/updates")) ? {} : { updateWaiting: { words: waiting.short } };
|
|
5129
|
+
})(),
|
|
5106
5130
|
...(liveMode === null || liveModeTerms === null
|
|
5107
5131
|
? {}
|
|
5108
5132
|
: {
|
|
@@ -5187,7 +5211,7 @@ export function createDecisionServer(options) {
|
|
|
5187
5211
|
const reviewFactsFor = (runId) => reviewFactsOf(store.reviewRetryStateOf(runId), runner => {
|
|
5188
5212
|
const one = store.getRunner(runner)?.runner;
|
|
5189
5213
|
return one !== undefined && runnerAlive(one, clock());
|
|
5190
|
-
});
|
|
5214
|
+
}, buildReviewOf(store, runId)?.state === "pending");
|
|
5191
5215
|
/** Keep detailed receipt diagnostics; shared assignment reads own readiness. */
|
|
5192
5216
|
function freshAssignment(assignment, receipt) {
|
|
5193
5217
|
if (assignment === null)
|
|
@@ -7264,7 +7288,9 @@ export function createDecisionServer(options) {
|
|
|
7264
7288
|
if (!authenticateApprover(store, who.name, body.get("password") ?? "").ok)
|
|
7265
7289
|
return back("problem", "Enter your Toolroll password to change retention.");
|
|
7266
7290
|
const before = store.retentionPeriods();
|
|
7267
|
-
|
|
7291
|
+
// Only what the page offers (or the period already set): a hand-made value doesn't save.
|
|
7292
|
+
const offered = (kind, days) => days !== undefined && periodChoices(kind, before[kind]).includes(days) ? days : undefined;
|
|
7293
|
+
const chosen = RETENTION_KINDS.map(({ kind }) => ({ kind, days: body.has(kind) ? offered(kind, parsePeriod(body.get(kind) ?? "")) : before[kind] }));
|
|
7268
7294
|
if (chosen.some(one => one.days === undefined))
|
|
7269
7295
|
return back("problem", "Choose a period for each kind of data.");
|
|
7270
7296
|
const changed = chosen.filter(one => one.days !== before[one.kind]);
|
|
@@ -7354,6 +7380,12 @@ export function createDecisionServer(options) {
|
|
|
7354
7380
|
if (!authenticateApprover(store, who.name, body.get("password") ?? "", repo).ok)
|
|
7355
7381
|
return back("problem", "Enter your Toolroll password to change checks.");
|
|
7356
7382
|
const act = body.get("act");
|
|
7383
|
+
// The automatic review switch: the policy log keeps before → after.
|
|
7384
|
+
if (act === "review") {
|
|
7385
|
+
const on = body.get("on") === "1";
|
|
7386
|
+
store.setReviewSwitch(repo, on, who.name, now);
|
|
7387
|
+
return back("said", on ? "Automatic review is on." : "Automatic review is off.");
|
|
7388
|
+
}
|
|
7357
7389
|
if (act === "level") {
|
|
7358
7390
|
const level = body.get("level");
|
|
7359
7391
|
if (!isCheckLevel(level))
|
|
@@ -8772,7 +8804,38 @@ export function createDecisionServer(options) {
|
|
|
8772
8804
|
if (attendAct !== null) {
|
|
8773
8805
|
return attendMutation(response, who, attendAct.taskId, attendAct.verb, body, now);
|
|
8774
8806
|
}
|
|
8775
|
-
const act = matchTaskPath(url.pathname, "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|block|unblock|repair-dependency|next|reopen|steer|follow-up|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|stop|resume-arm|resume)$");
|
|
8807
|
+
const act = matchTaskPath(url.pathname, "/(hold|unhold|requeue|cancel|scope|approve|plan|plan-edit|block|unblock|repair-dependency|next|reopen|steer|follow-up|accept-proof|accept-revision|reject-revision|route|retry-review|complete|merge|confirm-stopped|stop|resume-arm|resume)$");
|
|
8808
|
+
if (act !== null && act.verb === "confirm-stopped") {
|
|
8809
|
+
// Confirming a finished build stopped is an approver's act behind the password, typed again — the same
|
|
8810
|
+
// record as `toolroll run settle`, refused while anything of the run may still be running.
|
|
8811
|
+
const back = taskHref(act.taskId);
|
|
8812
|
+
if (who.via !== "cookie" || who.role !== "approver")
|
|
8813
|
+
return refuse(response, who, 403, "Only an approver can confirm a build stopped.", back);
|
|
8814
|
+
const ref = store.lookupRef(act.taskId);
|
|
8815
|
+
if (ref === null || !visible(ref.repo))
|
|
8816
|
+
return refuse(response, who, 404, "no such task", "/tasks");
|
|
8817
|
+
const named = (body.get("run") ?? "").trim();
|
|
8818
|
+
const run = /^[0-9]{1,15}$/.test(named) ? store.getRun(Number(named)) : null;
|
|
8819
|
+
const runTask = run === null ? null : store.externalIdFor(run.taskRef);
|
|
8820
|
+
if (run === null || runTask === null || familyOf(runTask)?.root.id !== (familyOf(act.taskId)?.root.id ?? act.taskId)) {
|
|
8821
|
+
return taskScreen(response, who, act.taskId, "That build isn't part of this task.", 409);
|
|
8822
|
+
}
|
|
8823
|
+
if (!authenticateApprover(store, who.name, body.get("token") ?? "", ref.repo).ok || !store.accountCanAccess(who.name, ref.repo)) {
|
|
8824
|
+
return taskScreen(response, who, act.taskId, "That password didn't match. Nothing changed.", 403);
|
|
8825
|
+
}
|
|
8826
|
+
// When Toolroll can't check at all, the approver also says they checked: never a one-click confirmation.
|
|
8827
|
+
if (store.stopQuiescenceFact(run.id)?.kind === "unknown" && body.get("checked") !== "yes") {
|
|
8828
|
+
return taskScreen(response, who, act.taskId, `Make sure nothing from build #${run.id} is running, then tick the box to confirm.`, 409);
|
|
8829
|
+
}
|
|
8830
|
+
const settled = store.settleRunWitnessesByApprover({ runId: run.id, by: who.name, why: "Confirmed in the console that nothing from this build is running." }, now);
|
|
8831
|
+
if (!settled.ok) {
|
|
8832
|
+
return taskScreen(response, who, act.taskId, settled.reason === "alive" || settled.reason === "still-running"
|
|
8833
|
+
? `Something from build #${run.id} may still be running, so it can't be confirmed stopped yet.` : `Build #${run.id} can't be confirmed stopped.`, 409);
|
|
8834
|
+
}
|
|
8835
|
+
bustBadge();
|
|
8836
|
+
const to = resultReturnTarget(body.get("return"), run.id);
|
|
8837
|
+
return redirect(response, body.get("return") ? to : back);
|
|
8838
|
+
}
|
|
8776
8839
|
if (act !== null && act.verb === "merge") {
|
|
8777
8840
|
// Merging is a person's act behind their password, typed again: a browser session, an approver, the exact
|
|
8778
8841
|
// result's pull request. The flow re-reads GitHub before merging and records who merged in the ledger.
|
|
@@ -11423,6 +11486,7 @@ export function createDecisionServer(options) {
|
|
|
11423
11486
|
comments: store.liveDiffComments(run.id).filter(isRevisionFeedback),
|
|
11424
11487
|
pastComments: store.allDiffComments(run.id).filter(one => one.reviewerRun === null && (one.consumedBy !== null || one.supersededBy !== null)),
|
|
11425
11488
|
reviewerFindings: store.allDiffComments(run.id).filter(one => one.reviewerRun !== null),
|
|
11489
|
+
automaticReview: buildReviewOf(store, run.id),
|
|
11426
11490
|
revisions: store.revisionsFromRun(run.id).flatMap(one => {
|
|
11427
11491
|
const child = store.getTask(one.id);
|
|
11428
11492
|
if (child === null || !visible(store.lookupRef(one.id)?.repo ?? null))
|
|
@@ -11934,6 +11998,7 @@ function criterionMatrixHtml(matrix, options = {}) {
|
|
|
11934
11998
|
(state !== "pass" || detail.length === 0 ? "" : `<div class="requirement-evidence-group"><strong>Verification notes</strong><ul>${detail.map(line => `<li>${escape(line)}</li>`).join("")}</ul></div>`) +
|
|
11935
11999
|
reviewDetails +
|
|
11936
12000
|
(coverage === undefined ? "" : `<p class="meta"${coverage.state === "gap" ? "" : ` data-context-coverage="${escape(coverage.state)}"`}>Review context: ${escape(coverageStateWords(coverage))}</p>`) +
|
|
12001
|
+
(coverage?.assets === undefined ? "" : `<p class="meta" data-context-assets>${escape(coverage.assets)}</p>`) +
|
|
11937
12002
|
`<p class="meta">Requirement ID: <code>${escape(row.id)}</code></p></div></details></li>`;
|
|
11938
12003
|
}).join("");
|
|
11939
12004
|
return `<div class="result-section criterion-matrix"><strong>Requirements · ${matrix.length}</strong><ol class="requirement-list">${rows}</ol></div>`;
|
|
@@ -15493,6 +15558,9 @@ function shell(title, body, options = {}) {
|
|
|
15493
15558
|
: `<div class="banner"><span class="badge badge-running">mode</span>${escape(chrome.modeBanner.words)} \u00b7 <a href="/mode">the terms \u00b7 end it</a></div>`) +
|
|
15494
15559
|
(chrome.signIn ?? []).map(one => `<div class="banner sign-in-banner" data-sign-in="${escape(one.provider)}"><strong>${escape(one.title)}</strong> \u00b7 run <code>${escape(one.command)}</code> on this computer, then resume.${one.detail === "" ? "" : ` ${escape(one.detail)}`}` +
|
|
15495
15560
|
`<form method="post" action="${escape(one.resumeHref)}" class="inline"><input type="hidden" name="csrf" value="${escape(chrome.csrf ?? "")}"><button type="submit">${escape(one.resumeLabel)}</button></form></div>`).join("") +
|
|
15561
|
+
(chrome.updateWaiting === undefined
|
|
15562
|
+
? ""
|
|
15563
|
+
: `<div class="banner update-waiting" role="status">${escape(chrome.updateWaiting.words)} \u00b7 <a href="/settings/updates">Update status</a></div>`) +
|
|
15496
15564
|
(chrome.update === undefined
|
|
15497
15565
|
? ""
|
|
15498
15566
|
: `<div class="banner update-banner" data-update="${escape(chrome.update.version)}">${escape(updateNoticeWords(chrome.update))} \u00b7 <a href="${escape(chrome.update.href)}">What's new</a>` +
|
|
@@ -16506,7 +16574,7 @@ function taskChatApproval(focus, csrf) {
|
|
|
16506
16574
|
`<p class="chat-plan-outcome">${escape(oneLineOf(scope.goal, 200))}</p>` +
|
|
16507
16575
|
`<p class="chat-plan-facts">${facts.map(escape).join(" · ")}</p>` +
|
|
16508
16576
|
`<details class="chat-approval">` +
|
|
16509
|
-
`<summary data-primary-action><span class="button-link">
|
|
16577
|
+
`<summary data-primary-action><span class="button-link">Approve plan</span></summary>` +
|
|
16510
16578
|
`<form method="post" action="${taskHref(focus.executionId)}/approve" class="chat-approval-form approve-form">` +
|
|
16511
16579
|
`<input type="hidden" name="csrf" value="${escape(csrf)}">` +
|
|
16512
16580
|
`<input type="hidden" name="nonce" value="${escape(approval.nonce)}">` +
|
|
@@ -16537,7 +16605,7 @@ function taskChatLiveRegion(focus, csrf, fragment = false, inert = false, approv
|
|
|
16537
16605
|
const approvalContent = (inert && focus.approval !== null && focus.plan !== "requested"
|
|
16538
16606
|
? `<section class="card chat-action-card"><span class="eyebrow">approval ready</span><h2>Finish the current chat response first</h2><p class="meta">The secure approval step appears here as soon as this response lands.</p></section>`
|
|
16539
16607
|
: fragment && focus.approval !== null && focus.plan !== "requested"
|
|
16540
|
-
? `<section class="card chat-action-card chat-refresh-action"><a class="button-link" href="${escape(approvalHref)}" data-primary-action>
|
|
16608
|
+
? `<section class="card chat-action-card chat-refresh-action"><a class="button-link" href="${escape(approvalHref)}" data-primary-action>Approve plan</a></section>`
|
|
16541
16609
|
: taskChatApproval(focus, csrf));
|
|
16542
16610
|
const approvalCard = focus.approval !== null && focus.dispatch?.action !== "approve-scope"
|
|
16543
16611
|
? `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approvalContent}</details>` : approvalContent;
|
|
@@ -20439,7 +20507,7 @@ function taskBodyParts(data) {
|
|
|
20439
20507
|
`<p class="meta"><strong>Plan first</strong> drafts it from the repository, or <a href="#scope">write it yourself</a>.</p></div>`
|
|
20440
20508
|
: "";
|
|
20441
20509
|
const approvalHtml = dependencyChoiceNeeded || approveForm === "" ? "" : data.dispatch?.action === "approve-scope"
|
|
20442
|
-
? `<details class="task-plan-review"><summary data-primary-action><span class="button-link">
|
|
20510
|
+
? `<details class="task-plan-review"><summary data-primary-action><span class="button-link">Approve plan</span></summary>${approveForm}</details>`
|
|
20443
20511
|
: `<details class="task-secondary-approval"><summary>Updated approval terms</summary>${approveForm}</details>`;
|
|
20444
20512
|
const optionsHtml = `${identity}${dispatchStatus}${dependencyChoiceNeeded ? "" : actsBar}`;
|
|
20445
20513
|
const optionsOpen = data.assignment?.primaryAction?.code === "unhold" || data.assignment?.primaryAction?.code === "retry-task";
|
|
@@ -20640,6 +20708,10 @@ function taskBodyParts(data) {
|
|
|
20640
20708
|
status: data.assignment != null && assignmentOptions !== null ? assignmentCardOf(data.assignment, assignmentOptions) : null,
|
|
20641
20709
|
statusHtml,
|
|
20642
20710
|
approval: approvalHtml,
|
|
20711
|
+
confirmStopped: data.csrf !== "" && data.assignment?.primaryAction?.code === "confirm-stopped" && data.assignment.primaryAction.target.runId !== null
|
|
20712
|
+
? { action: `${taskHref(data.assignment.rootId)}/confirm-stopped`, run: data.assignment.primaryAction.target.runId, checked: needsCheck(data.assignment) } : null,
|
|
20713
|
+
rebuild: data.csrf !== "" && data.assignment?.primaryAction?.code === "retry-task" && data.assignment.need != null && "key" in data.assignment.need && data.assignment.need.key === "rebuild"
|
|
20714
|
+
? { action: `${taskHref(data.assignment.rootId)}/requeue` } : null,
|
|
20643
20715
|
// The plan, progress and plan changes are thread entries now; the rest still needs a person here.
|
|
20644
20716
|
lead: [
|
|
20645
20717
|
{ key: "history", html: data.history ?? "" }, { key: "control", html: controlHtml }, { key: "problem", html: problemHtml },
|
|
@@ -22080,6 +22152,13 @@ function resultPanelParts(detail, o) {
|
|
|
22080
22152
|
const status = presentation?.status ?? resultHeadlineOf(receiptStatusOf(receipt));
|
|
22081
22153
|
const stored = receiptStatusOf(receipt, null);
|
|
22082
22154
|
const humanReview = manualReviewOnly(proof === null ? null : { ...proof, verdict: proof.verdict ?? "" });
|
|
22155
|
+
// Needs you: the action that resolves it comes first; Request changes stays beside it, never alone.
|
|
22156
|
+
// On the result itself, "Review result" would link here: accepting it is what resolves it.
|
|
22157
|
+
const need = needActionOf(current, taskStatus, o.csrf, o.returnTo, {
|
|
22158
|
+
accepted: proof?.accepted != null, humanReview, run: run.id, action: `${taskHref(detail.taskId)}/accept-proof`,
|
|
22159
|
+
// What Store.finalResultReason refuses: a verified, attested, accepted or published result.
|
|
22160
|
+
rebuildable: proof?.accepted == null && proof?.verdict !== "verified" && proof?.verdict !== "attested" && !(detail.publication !== null && ["pushed", "opened"].includes(detail.publication.state))
|
|
22161
|
+
});
|
|
22083
22162
|
const directAssessment = proof?.matrix.some(row => row.assessment !== undefined) === true;
|
|
22084
22163
|
const awaitingGoalReview = directAssessment && proof?.reasons.length === 1 && proof.reasons[0] === GOAL_ASSESSMENT_PENDING;
|
|
22085
22164
|
const assessmentReasons = directAssessment ? new Set(proof.matrix.flatMap(row => row.review ? [`${row.review.author} ${row.review.judgement === "contradicts" ? "contradicts" : "needs more evidence for"} criterion "${row.id}": ${row.review.note}`] : [])) : new Set();
|
|
@@ -22115,6 +22194,16 @@ function resultPanelParts(detail, o) {
|
|
|
22115
22194
|
if (current == null)
|
|
22116
22195
|
attention.push(...receipt.caveats.filter(caveat => !historicalAssessmentReason(caveat) && !attention.some(problem => problem.includes(caveat))));
|
|
22117
22196
|
attention.push(...(handoff?.followUps ?? []).map(one => `Follow-up: ${one}`));
|
|
22197
|
+
// The automatic review: a HIGH that did not send the work back, or a review
|
|
22198
|
+
// that could not finish, is the person's to see. (Said once when the
|
|
22199
|
+
// current assignment already carries it.)
|
|
22200
|
+
const automatic = detail.automaticReview ?? null;
|
|
22201
|
+
if (automatic !== null && automatic.sentBackAs === null) {
|
|
22202
|
+
const reviewed = [...(automatic.state === "not-reviewed" ? [`Not reviewed: ${automatic.reason ?? "the automatic review did not finish"}.`] : []), ...automatic.high.map(one => `Review: ${findingWords(one)}`)];
|
|
22203
|
+
for (const line of reviewed)
|
|
22204
|
+
if (!attention.includes(line))
|
|
22205
|
+
attention.push(line);
|
|
22206
|
+
}
|
|
22118
22207
|
// Publication risks stay in the open (repair 2026-09-14); the routine
|
|
22119
22208
|
// publication fact lives with the build details below.
|
|
22120
22209
|
// With the shared status, the pull request is its own quiet row (its reason under Details).
|
|
@@ -22133,7 +22222,7 @@ function resultPanelParts(detail, o) {
|
|
|
22133
22222
|
const acceptable = personChecks.length > 0 && humanReview && o.csrf !== "" && (current != null || detail.assignment == null);
|
|
22134
22223
|
const youCheck = personChecks.length === 0 ? null : {
|
|
22135
22224
|
lines: [...new Set(personChecks)],
|
|
22136
|
-
accept: acceptable ? { action: `${taskHref(detail.taskId)}/accept-proof`, run: run.id, returnTo: o.returnTo } : null,
|
|
22225
|
+
accept: acceptable && need?.accept == null ? { action: `${taskHref(detail.taskId)}/accept-proof`, run: run.id, returnTo: o.returnTo } : null,
|
|
22137
22226
|
};
|
|
22138
22227
|
const youCheckHtml = youCheck === null ? "" :
|
|
22139
22228
|
`<div class="result-you-check" data-result-you-check="${youCheck.lines.length}"><ul>${youCheck.lines.map(one => `<li>${escape(one)}</li>`).join("")}</ul>` +
|
|
@@ -22147,7 +22236,8 @@ function resultPanelParts(detail, o) {
|
|
|
22147
22236
|
// account waits behind a disclosure in Summary so nothing is said twice.
|
|
22148
22237
|
const conclusion = plainConclusionOf(receipt.summary ?? (run.outcome === "no-change" ? "The agent found that no repository change was needed." : "The build finished without a concise handoff."));
|
|
22149
22238
|
const outcome = conciseOutcomeOf(conclusion);
|
|
22150
|
-
|
|
22239
|
+
// A failing pull request keeps its repair draft beside the need's action.
|
|
22240
|
+
const action = o.action === false ? "" : need !== null ? resultNeedAction(need, o, detail.canAnnotate) + (detail.ciFailing && o.csrf !== "" ? resultPrimaryAction(detail, o, prUrl) : "") : resultPrimaryAction(detail, o, prUrl);
|
|
22151
22241
|
// ---- summary: the deliverable first ----------------------------------
|
|
22152
22242
|
const summaryParts = [];
|
|
22153
22243
|
const report = receipt.report;
|
|
@@ -22189,6 +22279,10 @@ function resultPanelParts(detail, o) {
|
|
|
22189
22279
|
];
|
|
22190
22280
|
if (agentAccount.length > 0)
|
|
22191
22281
|
summaryParts.push(`<details class="result-notes" data-result-notes-agent><summary>What the agent reported</summary>${agentAccount.join("")}</details>`);
|
|
22282
|
+
// MEDIUM and LOW never block: suggested follow-ups, on request.
|
|
22283
|
+
if (automatic !== null && automatic.followUps.length > 0) {
|
|
22284
|
+
summaryParts.push(`<details class="result-notes" data-review-followups="${automatic.followUps.length}"><summary>Suggested follow-ups · ${automatic.followUps.length}</summary><ul class="result-changes">${automatic.followUps.map(one => `<li><span class="badge">${one.severity === "MEDIUM" ? "Medium" : "Low"}</span> <span class="mono">${escape(`${one.file}:${one.line}`)}</span> ${escape(one.scenario)}</li>`).join("")}</ul></details>`);
|
|
22285
|
+
}
|
|
22192
22286
|
const evidenceSources = [
|
|
22193
22287
|
...(report !== null ? [report.ok ? `verified report${report.truncated ? " (shortened)" : ""}` : "report (unverifiable)"] : []),
|
|
22194
22288
|
...(patch !== null ? [patchOk ? `sealed diff${patch.truncated ? " (shortened)" : ""}` : "diff (unverifiable)"] : []),
|
|
@@ -22431,10 +22525,56 @@ function resultPanelParts(detail, o) {
|
|
|
22431
22525
|
learning: detail.learning ?? "",
|
|
22432
22526
|
request: detail.skillTest ? null : requestParts.join("\n"),
|
|
22433
22527
|
canRequest: detail.canAnnotate && o.csrf !== "",
|
|
22528
|
+
need,
|
|
22434
22529
|
requestQuiet: detail.canAnnotate && o.csrf !== "" && detail.comments.length === 0 && (detail.pastComments?.length ?? 0) === 0 && detail.revisions.length === 0,
|
|
22435
22530
|
};
|
|
22436
22531
|
return { html, panel };
|
|
22437
22532
|
}
|
|
22533
|
+
/** A Needs you result's one action (needs-you.ts): a link to the act that resolves it, or Confirm it
|
|
22534
|
+
* stopped behind the password. Null under every other headline. */
|
|
22535
|
+
export function needActionOf(assignment, status, csrf, returnTo, result) {
|
|
22536
|
+
if (assignment === null || status === null || status.headline !== "Needs you" || status.need == null)
|
|
22537
|
+
return null;
|
|
22538
|
+
const action = assignment.primaryAction;
|
|
22539
|
+
const rebuild = { href: null, confirm: null, rebuild: { action: `${taskHref(assignment.rootId)}/requeue` } };
|
|
22540
|
+
// Built to an earlier plan: Build again, in place (the task page's requeue).
|
|
22541
|
+
if (action?.code === "retry-task" && status.need.key === "rebuild" && csrf !== "")
|
|
22542
|
+
return { label: status.need.action.label, ...rebuild };
|
|
22543
|
+
if (result !== undefined) {
|
|
22544
|
+
if (status.need.key === "review-result" && !result.accepted && csrf !== "")
|
|
22545
|
+
return { label: result.humanReview ? "Accept result" : "Accept with exception", href: null, confirm: null,
|
|
22546
|
+
accept: { action: result.action, run: result.run, returnTo, note: result.humanReview ? null : "Why is this safe to accept?" } };
|
|
22547
|
+
// The task page sends the person here: never back to it. What this page can do leads — Accept
|
|
22548
|
+
// a check only a person can make, or Build again a result that may run again.
|
|
22549
|
+
const here = (action?.code === "open-result" || action?.code === "inspect-run") && action.target.runId === result.run;
|
|
22550
|
+
if (here && csrf !== "" && !result.accepted && result.humanReview)
|
|
22551
|
+
return { label: "Accept result", href: null, confirm: null, accept: { action: result.action, run: result.run, returnTo, note: null } };
|
|
22552
|
+
if (here && csrf !== "" && result.rebuildable === true)
|
|
22553
|
+
return { label: NEEDS.rebuild.action.label, ...rebuild };
|
|
22554
|
+
if (status.need.key === "review-result" || here)
|
|
22555
|
+
return { label: "Open the task", href: taskHref(assignment.rootId), confirm: null };
|
|
22556
|
+
}
|
|
22557
|
+
const confirm = action?.code === "confirm-stopped" && action.target.runId !== null && csrf !== ""
|
|
22558
|
+
? { action: `${taskHref(assignment.rootId)}/confirm-stopped`, run: action.target.runId, returnTo, checked: needsCheck(assignment) } : null;
|
|
22559
|
+
return { label: status.need.action.label, href: assignmentActionHref(assignment) ?? taskHref(assignment.rootId), confirm };
|
|
22560
|
+
}
|
|
22561
|
+
/** A build Toolroll can't check at all: the approver ticks that they checked before confirming. */
|
|
22562
|
+
const needsCheck = (assignment) => assignment.need != null && "key" in assignment.need && assignment.need.key === "check-stopped";
|
|
22563
|
+
/** The server page's form of the same: the need's action first, Request changes as the quiet second. */
|
|
22564
|
+
function resultNeedAction(need, o, canRequest) {
|
|
22565
|
+
const control = need.accept != null
|
|
22566
|
+
? `<form method="post" action="${escape(need.accept.action)}" class="accept-result"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><input type="hidden" name="run" value="${need.accept.run}"><input type="hidden" name="return" value="${escape(need.accept.returnTo)}">` +
|
|
22567
|
+
(need.accept.note === null ? "" : `<input type="text" name="note" maxlength="500" required placeholder="${escape(need.accept.note)}" aria-label="${escape(need.accept.note)}">`) +
|
|
22568
|
+
`<button type="submit" data-primary-action data-accept-result style="min-height:44px">${escape(need.label)}</button></form>`
|
|
22569
|
+
: need.rebuild != null
|
|
22570
|
+
? `<form method="post" action="${escape(need.rebuild.action)}" class="rebuild"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><button type="submit" data-primary-action data-rebuild style="min-height:44px">${escape(need.label)}</button></form>`
|
|
22571
|
+
: need.confirm !== null
|
|
22572
|
+
? `<form method="post" action="${escape(need.confirm.action)}" id="confirm-stopped" class="confirm-stopped"><input type="hidden" name="csrf" value="${escape(o.csrf)}"><input type="hidden" name="run" value="${need.confirm.run}"><input type="hidden" name="return" value="${escape(need.confirm.returnTo)}">` +
|
|
22573
|
+
(need.confirm.checked === true ? `<label><input type="checkbox" name="checked" value="yes" required> Nothing from build #${need.confirm.run} is running</label>` : "") +
|
|
22574
|
+
`<label>Your password<input type="password" name="token" autocomplete="current-password" required></label><button type="submit" style="min-height:44px">${escape(need.label)}</button></form>`
|
|
22575
|
+
: `<a class="button-link" href="${escape(need.href ?? "#")}" data-primary-action>${escape(need.label)}</a>`;
|
|
22576
|
+
return `<div class="result-action" data-result-action="need">${control}${canRequest && o.csrf !== "" ? `<a class="result-feedback-link" href="#request-changes">Request changes</a>` : ""}</div>`;
|
|
22577
|
+
}
|
|
22438
22578
|
/** The outcome in one bounded sentence (repair 2026-09-14): the handoff's
|
|
22439
22579
|
* first sentence, cut at a word when it runs past 180 characters. The
|
|
22440
22580
|
* full conclusion stays available behind the Summary's disclosure. */
|
package/dist/setup-guide.d.ts
CHANGED
|
@@ -15,10 +15,10 @@ export declare function detectPreparation(repo: string): {
|
|
|
15
15
|
} | null;
|
|
16
16
|
/** Browser installation may write only this known file inside its project. */
|
|
17
17
|
export declare function previewProjectInstructions(repo: string): {
|
|
18
|
+
content?: never;
|
|
18
19
|
ok: false;
|
|
19
20
|
message: string;
|
|
20
21
|
plan?: never;
|
|
21
|
-
content?: never;
|
|
22
22
|
fingerprint?: never;
|
|
23
23
|
installed?: never;
|
|
24
24
|
} | {
|
|
@@ -30,10 +30,10 @@ export declare function previewProjectInstructions(repo: string): {
|
|
|
30
30
|
installed: boolean;
|
|
31
31
|
};
|
|
32
32
|
export declare function addProjectInstructions(repo: string, fingerprint: string): {
|
|
33
|
+
content?: never;
|
|
33
34
|
ok: false;
|
|
34
35
|
message: string;
|
|
35
36
|
plan?: never;
|
|
36
|
-
content?: never;
|
|
37
37
|
fingerprint?: never;
|
|
38
38
|
installed?: never;
|
|
39
39
|
} | import("./skills.js").InstallResult;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
export declare const DEPS_FOLDER = "deps";
|
|
2
|
+
/** A copy linked or made this recently is never removed by a clean-up: a lease may be about to link it. */
|
|
3
|
+
export declare const SHARED_GRACE_MS: number;
|
|
4
|
+
export type SharedCopy = {
|
|
5
|
+
key: string;
|
|
6
|
+
/** The copy's folder, holding node_modules and ready.json. */
|
|
7
|
+
dir: string;
|
|
8
|
+
repo: string;
|
|
9
|
+
/** sha256 of the lockfile and package.json it was installed from. */
|
|
10
|
+
lock: string;
|
|
11
|
+
node: string;
|
|
12
|
+
createdAt: string;
|
|
13
|
+
/** Last linked (ready.json's modification time). */
|
|
14
|
+
usedAt: string;
|
|
15
|
+
/** Its installed tree changed after it was made: never linked again. */
|
|
16
|
+
retired: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function sharedDepsRoot(databaseFile: string): string;
|
|
19
|
+
/** A setup whose whole effect is node_modules: `npm ci` or `npm install`, with plain flags only. */
|
|
20
|
+
export declare function installsOnly(command: string): boolean;
|
|
21
|
+
/**
|
|
22
|
+
* The checkout's dependency identity: its npm lockfile and package.json, hashed together. null when there is no
|
|
23
|
+
* lockfile, or the lockfile links local folders (workspaces, `file:`), whose links would point into the shared
|
|
24
|
+
* folder instead of the checkout.
|
|
25
|
+
*/
|
|
26
|
+
export declare function lockDigest(checkout: string): string | null;
|
|
27
|
+
/** The node a setup on this PATH would install with: version, platform and architecture (native modules differ). */
|
|
28
|
+
export declare function nodeIdentity(path?: string): string | null;
|
|
29
|
+
export declare function depsKey(repo: string, setupDigest: string, lock: string, node: string): string;
|
|
30
|
+
/** What a checkout would share: its key and the facts behind it, or null when it can't share. */
|
|
31
|
+
export declare function sharingFor(args: {
|
|
32
|
+
repo: string;
|
|
33
|
+
worktree: string;
|
|
34
|
+
setup: {
|
|
35
|
+
command: string;
|
|
36
|
+
digest: string;
|
|
37
|
+
};
|
|
38
|
+
}): {
|
|
39
|
+
key: string;
|
|
40
|
+
lock: string;
|
|
41
|
+
node: string;
|
|
42
|
+
} | null;
|
|
43
|
+
/** The shared node_modules for a key, when one is ready and unchanged since it was made. A changed one is retired. */
|
|
44
|
+
export declare function readyCopy(root: string, key: string): string | null;
|
|
45
|
+
/**
|
|
46
|
+
* The shared copy a checkout's node_modules links, or null (none, or its own install). A linked node_modules is a
|
|
47
|
+
* small real folder: one link per top-level entry of the shared copy and a note naming it. Tools that write a cache
|
|
48
|
+
* there (`node_modules/.vite`, `.cache`) write into the checkout's own folder, never the shared copy.
|
|
49
|
+
*/
|
|
50
|
+
export declare function linkedKey(worktree: string, root: string): string | null;
|
|
51
|
+
/** Remove a checkout's linked node_modules (its links and caches, never what they point to). True when there was one. */
|
|
52
|
+
export declare function detachShared(worktree: string, root: string): boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Put a shared copy into a checkout that has no node_modules: links (a junction per folder on Windows), else a
|
|
55
|
+
* copy-on-write clone the build owns. null: neither worked, and the caller runs the plain setup.
|
|
56
|
+
*/
|
|
57
|
+
export declare function linkInto(worktree: string, shared: string): "link" | "clone" | null;
|
|
58
|
+
/**
|
|
59
|
+
* After the approved setup succeeded in a checkout with no copy for its key: move its node_modules into
|
|
60
|
+
* `deps/<key>` (one rename), make it read-only and link it back. Returns how the checkout now has it, or null when it
|
|
61
|
+
* kept its own install (a different volume, a setup that rewrote the lockfile, a race lost cleanly).
|
|
62
|
+
*/
|
|
63
|
+
export declare function promoteInstall(args: {
|
|
64
|
+
root: string;
|
|
65
|
+
repo: string;
|
|
66
|
+
worktree: string;
|
|
67
|
+
key: string;
|
|
68
|
+
lock: string;
|
|
69
|
+
node: string;
|
|
70
|
+
setupDigest: string;
|
|
71
|
+
now: Date;
|
|
72
|
+
}): "link" | "clone" | null;
|
|
73
|
+
/** Every shared copy on disk, newest first. */
|
|
74
|
+
export declare function sharedCopies(root: string): SharedCopy[];
|
|
75
|
+
export type SharedUse = SharedCopy & {
|
|
76
|
+
checkouts: string[];
|
|
77
|
+
};
|
|
78
|
+
/** Which checkouts use each copy: those linking it, and those whose lockfile it was installed from. */
|
|
79
|
+
export declare function sharedUse(root: string, checkouts: readonly {
|
|
80
|
+
path: string;
|
|
81
|
+
repo: string;
|
|
82
|
+
}[]): SharedUse[];
|
|
83
|
+
/** Half-made copies under deps/ (a crash mid-promotion), old enough that nothing is still making them. */
|
|
84
|
+
export declare function sharedDebris(root: string, now: Date): string[];
|
|
85
|
+
/** What `storage clean` removes: copies no checkout uses and none linked within the grace period, plus debris. */
|
|
86
|
+
export declare function unusedShared(root: string, checkouts: readonly {
|
|
87
|
+
path: string;
|
|
88
|
+
repo: string;
|
|
89
|
+
}[], now: Date): {
|
|
90
|
+
copies: SharedUse[];
|
|
91
|
+
debris: string[];
|
|
92
|
+
};
|
|
93
|
+
/** Remove one copy or debris folder (read-only on disk, so made writable first). */
|
|
94
|
+
export declare function removeShared(path: string): boolean;
|