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.
Files changed (97) hide show
  1. package/CHANGELOG.md +48 -1
  2. package/dist/assignment-adapters.d.ts +1 -0
  3. package/dist/assignment-adapters.js +2 -0
  4. package/dist/assignment-brief.d.ts +2 -0
  5. package/dist/assignment-brief.js +2 -1
  6. package/dist/assignment-presentation.js +3 -1
  7. package/dist/assignment-status.d.ts +1 -1
  8. package/dist/assignment-ui.js +5 -0
  9. package/dist/assignment.d.ts +15 -0
  10. package/dist/assignment.js +56 -9
  11. package/dist/browser/workspace.css +1 -1
  12. package/dist/browser/workspace.js +18 -18
  13. package/dist/browser-crew.js +4 -0
  14. package/dist/browser-workspace.d.ts +40 -0
  15. package/dist/build-review.d.ts +38 -0
  16. package/dist/build-review.js +341 -0
  17. package/dist/builder.d.ts +2 -0
  18. package/dist/builder.js +114 -8
  19. package/dist/chat-controls.d.ts +1 -1
  20. package/dist/chat-controls.js +1 -1
  21. package/dist/check-levels-ui.d.ts +5 -0
  22. package/dist/check-levels-ui.js +16 -2
  23. package/dist/check-progress.js +4 -3
  24. package/dist/checkout-cleanup.d.ts +40 -2
  25. package/dist/checkout-cleanup.js +129 -9
  26. package/dist/cli.js +2 -0
  27. package/dist/desktop-update.d.ts +52 -0
  28. package/dist/desktop-update.js +147 -5
  29. package/dist/dispatch.js +2 -1
  30. package/dist/dispose.d.ts +4 -3
  31. package/dist/dispose.js +12 -4
  32. package/dist/evidence.d.ts +6 -0
  33. package/dist/evidence.js +8 -0
  34. package/dist/exec.d.ts +5 -0
  35. package/dist/exec.js +30 -4
  36. package/dist/first-run.d.ts +1 -1
  37. package/dist/first-run.js +8 -6
  38. package/dist/invoke.js +5 -0
  39. package/dist/lead-context.d.ts +1 -1
  40. package/dist/lead-context.js +4 -2
  41. package/dist/lead-status.d.ts +10 -1
  42. package/dist/lead-status.js +21 -24
  43. package/dist/modes.d.ts +3 -1
  44. package/dist/modes.js +3 -1
  45. package/dist/needs-you.d.ts +55 -0
  46. package/dist/needs-you.js +83 -0
  47. package/dist/operate.d.ts +2 -0
  48. package/dist/operate.js +231 -36
  49. package/dist/plane-review.js +4 -0
  50. package/dist/process-custody.js +8 -0
  51. package/dist/process-recovery-eligibility.d.ts +1 -0
  52. package/dist/project-learning.d.ts +3 -3
  53. package/dist/project-learning.js +28 -17
  54. package/dist/proof.d.ts +3 -1
  55. package/dist/proof.js +4 -2
  56. package/dist/provider.js +16 -0
  57. package/dist/result-completion.d.ts +4 -0
  58. package/dist/result-completion.js +11 -0
  59. package/dist/retention-ui.d.ts +3 -1
  60. package/dist/retention-ui.js +4 -4
  61. package/dist/retention.d.ts +13 -6
  62. package/dist/retention.js +50 -12
  63. package/dist/review-context.d.ts +3 -0
  64. package/dist/review-context.js +45 -5
  65. package/dist/review-switch.d.ts +41 -0
  66. package/dist/review-switch.js +88 -0
  67. package/dist/reviewer.d.ts +24 -2
  68. package/dist/reviewer.js +49 -1
  69. package/dist/run-closeout.d.ts +89 -0
  70. package/dist/run-closeout.js +302 -0
  71. package/dist/serve.d.ts +15 -1
  72. package/dist/serve.js +158 -18
  73. package/dist/setup-guide.d.ts +2 -2
  74. package/dist/shared-deps.d.ts +94 -0
  75. package/dist/shared-deps.js +365 -0
  76. package/dist/storage.js +2 -0
  77. package/dist/store.d.ts +72 -1
  78. package/dist/store.js +258 -31
  79. package/dist/surface.js +5 -1
  80. package/dist/task-status.d.ts +22 -5
  81. package/dist/task-status.js +45 -24
  82. package/dist/telegram-progress.js +2 -1
  83. package/dist/telegram-status.js +3 -3
  84. package/dist/test-temp.d.ts +19 -0
  85. package/dist/test-temp.js +98 -0
  86. package/dist/toolroll-update-ui.js +7 -2
  87. package/dist/toolroll-update.d.ts +21 -1
  88. package/dist/toolroll-update.js +135 -5
  89. package/dist/work-index.d.ts +11 -0
  90. package/dist/work-index.js +103 -16
  91. package/dist/work-summary.d.ts +3 -1
  92. package/dist/work-summary.js +2 -1
  93. package/dist/workspace-ui.d.ts +3 -1
  94. package/dist/workspace-ui.js +10 -9
  95. package/dist/worktree.d.ts +16 -0
  96. package/dist/worktree.js +47 -0
  97. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  /** Cheap lead-facing status. Every fact comes from indexed SQLite records;
2
2
  * this module never opens a repository, retained evidence, or a live log. */
3
- import { COMPLETION_ACTION } from "./result-completion.js";
3
+ import { familyCompleted } from "./result-completion.js";
4
4
  import { windowLabel } from "./provider-limits.js";
5
5
  import { openAuthPauses, signInCommand, signInReason } from "./provider-auth.js";
6
6
  import { BUILT_IN, checkSuitesOf } from "./store.js";
7
- import { workIndexPage } from "./work-index.js";
7
+ import { workIndexPage, workReadyForReview } from "./work-index.js";
8
8
  const maybeNumber = (value) => value === null || value === undefined || !Number.isInteger(Number(value)) ? null : Number(value);
9
9
  const CHECK_COLUMNS = "rc.status AS check_status, rc.exit_code AS check_exit_code, rc.suites_json AS check_suites_json";
10
10
  function checkOf(row) {
@@ -90,9 +90,9 @@ export function taskWaitSnapshot(store, taskId, now, watchedRun) {
90
90
  if (state !== "done")
91
91
  return answer("Ready", "Review result", true, 0, "ready");
92
92
  }
93
- const completion = result === undefined ? undefined : store.handle.prepare(`SELECT 1 AS hit FROM action_ledger INDEXED BY work_completion
94
- WHERE task_id = ? AND action = ? AND source = 'work' AND run_id = ? LIMIT 1`).get(taskId, COMPLETION_ACTION, Number(result["id"]));
95
- if (state === "done" && completion !== undefined)
93
+ // A completed family is Complete from any of its tasks.
94
+ const completed = state === "done" && familyCompleted(store, taskId);
95
+ if (completed)
96
96
  return answer("Complete", "No action needed", true, 0, "complete");
97
97
  if (state === "done")
98
98
  return answer("Ready", "Review result", true, 0, "ready");
@@ -113,7 +113,7 @@ export function taskWaitSnapshot(store, taskId, now, watchedRun) {
113
113
  return answer("Needs a person", next, true, 1, "needs-person");
114
114
  }
115
115
  if (task["plan"] === "drafted")
116
- return answer("Needs a person", "Review plan", true, 1, "needs-person");
116
+ return answer("Needs a person", "Approve plan", true, 1, "needs-person");
117
117
  if (Number(task["strikes"] ?? 0) >= 3)
118
118
  return answer("Needs a person", "Review repeated failures", true, 1, "needs-person");
119
119
  // An admitted attempt is already the work being watched. Its immutable
@@ -168,6 +168,15 @@ function queueReason(row) {
168
168
  return "ready for a worker";
169
169
  }
170
170
  /** Whole-installation status, deliberately aggregated and bounded. */
171
+ /** The one-off repair on start. An older build left finished runs' process
172
+ * exits unrecorded and counted completed families as waiting; nothing about
173
+ * a status is stored, so recording every exit that can now be proven and
174
+ * re-deriving each open task's headline from the shared projection clears
175
+ * them. What can't be proven stays as it was. */
176
+ export function repairStaleStatuses(store, now) {
177
+ const exitsRecorded = store.recordFinishedRunExits(now);
178
+ return { exitsRecorded, readyForReview: workReadyForReview(store, now, { principal: "operator", repos: null, includeUnplaced: true }, 0).count };
179
+ }
171
180
  export function installationStatus(store, now) {
172
181
  const runningRows = store.handle.prepare(`SELECT run.id, run.role, run.phase, ref.external_id AS task
173
182
  FROM run INDEXED BY work_unfinished
@@ -208,22 +217,10 @@ export function installationStatus(store, now) {
208
217
  if (queuedTasks.length < 8)
209
218
  queuedTasks.push({ task: String(row["id"]), reason });
210
219
  }
211
- const waitingCount = Number(store.handle.prepare(`SELECT COUNT(*) AS n FROM task INDEXED BY task_by_state
212
- JOIN task_ref AS ref INDEXED BY sqlite_autoindex_task_ref_1 ON ref.backend = ? AND ref.external_id = task.id
213
- WHERE task.state = 'done' AND EXISTS (SELECT 1 FROM run INDEXED BY work_result
214
- WHERE run.task_ref = ref.id AND run.finished_at IS NOT NULL AND run.role IN ('builder','scout'))
215
- AND NOT EXISTS (SELECT 1 FROM action_ledger INDEXED BY work_completion
216
- WHERE action_ledger.task_id = task.id AND action_ledger.action = ? AND action_ledger.source = 'work'
217
- AND action_ledger.run_id = (SELECT id FROM run INDEXED BY work_result WHERE task_ref = ref.id AND finished_at IS NOT NULL AND role IN ('builder','scout') ORDER BY id DESC LIMIT 1))`)
218
- .get(BUILT_IN, COMPLETION_ACTION)?.["n"] ?? 0);
219
- const waitingRows = store.handle.prepare(`SELECT task.id AS task, result.id, ${CHECK_COLUMNS}
220
- FROM task INDEXED BY task_done_recent
221
- JOIN task_ref AS ref INDEXED BY sqlite_autoindex_task_ref_1 ON ref.backend = ? AND ref.external_id = task.id
222
- JOIN run AS result ON result.id = (SELECT id FROM run INDEXED BY work_result WHERE task_ref = ref.id AND finished_at IS NOT NULL AND role IN ('builder','scout') ORDER BY id DESC LIMIT 1)
223
- LEFT JOIN run_check AS rc ON rc.run = result.id
224
- WHERE task.state = 'done' AND NOT EXISTS (SELECT 1 FROM action_ledger INDEXED BY work_completion
225
- WHERE action_ledger.task_id = task.id AND action_ledger.action = ? AND action_ledger.source = 'work' AND action_ledger.run_id = result.id)
226
- ORDER BY task.updated_at DESC, task.id DESC LIMIT 5`).all(BUILT_IN, COMPLETION_ACTION);
220
+ // One per family, from the same projection as the Tasks list: a completed
221
+ // family (root or any revision) and a superseded version never count.
222
+ const ready = workReadyForReview(store, now, { principal: "operator", repos: null, includeUnplaced: true }, 5);
223
+ const checkRow = (run) => store.handle.prepare(`SELECT ${CHECK_COLUMNS} FROM run_check AS rc WHERE rc.run = ?`).get(run);
227
224
  // Run ids grow with time, so the newest finished release check is the
228
225
  // first finished row down the partial index.
229
226
  const release = store.handle.prepare(`SELECT run.id, run.finished_at, ${CHECK_COLUMNS}, ref.external_id AS task
@@ -240,7 +237,7 @@ export function installationStatus(store, now) {
240
237
  reasons: [...reasons].map(([reason, count]) => ({ reason, count })).sort((a, b) => b.count - a.count || a.reason.localeCompare(b.reason)),
241
238
  tasks: queuedTasks,
242
239
  },
243
- waitingForReview: { count: waitingCount, results: waitingRows.map(row => ({ task: String(row["task"]), run: Number(row["id"]), check: checkOf(row) })) },
240
+ waitingForReview: { count: ready.count, results: ready.results.map(one => ({ task: one.taskId, run: one.run, check: checkOf(one.run === null ? undefined : checkRow(one.run)) })) },
244
241
  releaseCheck: release === undefined ? null : { task: String(release["task"]), run: Number(release["id"]), finishedAt: release["finished_at"] == null ? null : String(release["finished_at"]), check: checkOf(release) },
245
242
  planWindows: store.handle.prepare(`SELECT provider, window, used_percent, window_minutes, resets_at, reached, plan, observed_at
246
243
  FROM provider_limit INDEXED BY sqlite_autoindex_provider_limit_1 ORDER BY provider, window`).all().map(row => ({
@@ -271,7 +268,7 @@ export function renderInstallationStatus(status) {
271
268
  }
272
269
  lines.push(status.running.count === 0 ? "Building: none" : `Building: ${status.running.count} — ${status.running.tasks.map(one => `${one.task} (#${one.run}, ${one.phase})`).join(", ")}${status.running.count > status.running.tasks.length ? ", …" : ""}`);
273
270
  lines.push(status.queued.count === 0 ? "Queued: none" : `Queued: ${status.queued.count} — ${status.queued.tasks.map(one => `${one.task} (${one.reason})`).join(", ")}${status.queued.count > status.queued.tasks.length ? ", …" : ""}`);
274
- lines.push(status.waitingForReview.count === 0 ? "Ready for review: none" : `Ready for review: ${status.waitingForReview.count} — ${status.waitingForReview.results.map(one => `${one.task} (#${one.run})`).join(", ")}${status.waitingForReview.count > status.waitingForReview.results.length ? ", …" : ""}`);
271
+ lines.push(status.waitingForReview.count === 0 ? "Ready for review: none" : `Ready for review: ${status.waitingForReview.count} — ${status.waitingForReview.results.map(one => one.run === null ? one.task : `${one.task} (#${one.run})`).join(", ")}${status.waitingForReview.count > status.waitingForReview.results.length ? ", …" : ""}`);
275
272
  if (status.releaseCheck === null) {
276
273
  lines.push("Release check: none recorded");
277
274
  }
package/dist/modes.d.ts CHANGED
@@ -29,7 +29,9 @@ export type ModeTerms = {
29
29
  planAuto: boolean;
30
30
  /** Attended mint without the per-mint password — signer only (D8). */
31
31
  quickMint: boolean;
32
- /** Historical signed field; new modes leave it false and no worker consumes it. */
32
+ /** Historical signed field; new modes leave it false and no worker consumes
33
+ * it. Automatic review is the project's review switch (review-switch.ts),
34
+ * on by default while a hands-off mode is active. */
33
35
  reviewAuto: boolean;
34
36
  /** Historical signed retry term, retained for digest and audit compatibility. */
35
37
  reviewRetryAuto: boolean;
package/dist/modes.js CHANGED
@@ -157,7 +157,9 @@ export function modeWords(terms) {
157
157
  terms.planAuto
158
158
  ? "plans for your pre-authorized filings auto-approve only when the goal, exclusions, paths, acceptance criteria, risk, budget, and agent route remain exactly unchanged; provide a goal, paths and acceptance criteria upfront; amendments and unresolved questions still wait for you"
159
159
  : "planner-generated plans wait for your approval",
160
- "finished work and saved checks go to the lead or user; no separate model review runs",
160
+ terms.name === "hands-off"
161
+ ? "automatic review is on for this project unless you turn it off (`toolroll review off --repo <path>`): each finished build whose check passes (or that finishes, with checks Off) gets one read-only review by the project's review agent; a HIGH finding sends it back once as a revision filed under this mode, a HIGH on that revision comes to you, MEDIUM and LOW findings come to you as suggested follow-ups, and a review that fails or times out never holds the work — it reaches you marked not reviewed"
162
+ : "finished work and saved checks go to the lead or user; automatic review runs only where a project turns it on (`toolroll review on --repo <path>`)",
161
163
  ...(terms.reviewAuto || terms.reviewRetryAuto
162
164
  ? ["historical review grants are retained on record but no longer schedule work"] : []),
163
165
  terms.perAttemptBudgetMicrousd === null
@@ -0,0 +1,55 @@
1
+ /** Every reason a task, result or flow card can wait on a person, in one
2
+ * place: the plain sentence it shows and the one action that resolves it
3
+ * (task-status.ts reads it for every surface). A reason no person can act
4
+ * on is not Needs you: it is a Waiting reason, worded as what it waits for.
5
+ *
6
+ * The words never name the machine's internals; `plainSentence` keeps a
7
+ * recorded reason only when it is free of them. */
8
+ /** What a person is asked for. */
9
+ export type NeedKey = "approval" | "card" | "answer" | "questions" | "sign-in" | "confirm-stopped" | "check-stopped" | "hold" | "choose-project" | "define-task" | "fix-request" | "choose-agent" | "fix-dependency" | "add-requirement" | "connect-builder" | "start-builder" | "vanished" | "review-result" | "rebuild" | "earlier-version" | "other";
10
+ /** What the work waits for when no person can act. */
11
+ export type WaitKey = "build-stopping" | "other-computer" | "card-reply" | "card-time" | "card-ci";
12
+ /** The action's code: the WorkAction that owns the act, or one of the acts a surface renders itself. */
13
+ export type NeedActionCode = "approve-scope" | "approve-card" | "answer-decision" | "inspect-decisions" | "sign-in" | "confirm-stopped" | "inspect-hold" | "place-task" | "write-scope" | "select-agent" | "repair-dependency" | "repair-capability" | "start-worker" | "reconcile-run" | "open-result" | "retry-task" | "inspect-task";
14
+ export type NeedAction = {
15
+ code: NeedActionCode;
16
+ label: string;
17
+ };
18
+ /** `build`: the run a sentence names; `provider`: the agent that signed out. */
19
+ export type NeedContext = {
20
+ build?: number | null;
21
+ provider?: string | null;
22
+ };
23
+ /** `useReason`: which recorded reasons say what is needed better than the default words. */
24
+ type Need = {
25
+ sentence: (context: NeedContext) => string;
26
+ action: NeedAction;
27
+ useReason?: (reason: string) => boolean;
28
+ /** How a kept reason reads: by default the reason alone. */
29
+ withReason?: (reason: string) => string;
30
+ };
31
+ export declare const NEEDS: Readonly<Record<NeedKey, Need>>;
32
+ export declare const WAITS: Readonly<Record<WaitKey, (context: NeedContext) => string>>;
33
+ /** Words that name the machine's internals; no Needs you or Waiting sentence carries them. */
34
+ export declare const BANNED_WORDS: readonly ["witness", "unproven", "quiescence", "quiescent", "digest", "scope digest"];
35
+ export declare const hasInternalWords: (text: string) => boolean;
36
+ /** The sentence for a need: its recorded reason when the need keeps one and
37
+ * it reads plainly, otherwise the need's own words. */
38
+ export declare function needSentence(key: NeedKey, reason: string | null, context?: NeedContext): string;
39
+ export declare function waitSentence(key: WaitKey | undefined, reason: string | null, context?: NeedContext): string;
40
+ /** A finished run Toolroll can't prove stopped (store.stopQuiescenceFact):
41
+ * a person can confirm it only when nothing of it may still be running.
42
+ * `unknown`: Toolroll can't check, so the person confirms they checked. */
43
+ export declare function processNeedOf(fact: {
44
+ run: number;
45
+ kind: "open" | "alive" | "elsewhere" | "unprovable" | "unknown";
46
+ } | null): {
47
+ need: "confirm-stopped" | "check-stopped";
48
+ build: number;
49
+ } | {
50
+ wait: WaitKey;
51
+ build: number;
52
+ } | null;
53
+ /** Dispatch and work-index codes that wait on a person, as their need. */
54
+ export declare const CODE_NEED: Readonly<Record<string, NeedKey>>;
55
+ export {};
@@ -0,0 +1,83 @@
1
+ /** Every reason a task, result or flow card can wait on a person, in one
2
+ * place: the plain sentence it shows and the one action that resolves it
3
+ * (task-status.ts reads it for every surface). A reason no person can act
4
+ * on is not Needs you: it is a Waiting reason, worded as what it waits for.
5
+ *
6
+ * The words never name the machine's internals; `plainSentence` keeps a
7
+ * recorded reason only when it is free of them. */
8
+ const always = () => true;
9
+ const build = (context) => context.build == null ? "the last build" : `build #${context.build}`;
10
+ const Build = (context) => context.build == null ? "The last build" : `Build #${context.build}`;
11
+ export const NEEDS = {
12
+ approval: { sentence: () => "Review the plan and approve it to start.", action: { code: "approve-scope", label: "Approve plan" } },
13
+ card: { sentence: () => "This card is waiting for your approval.", action: { code: "approve-card", label: "Approve card" } },
14
+ answer: { sentence: () => "Answer the question so the work can continue.", action: { code: "answer-decision", label: "Answer the question" }, useReason: always },
15
+ questions: { sentence: () => "Too many questions are open. Answer one so this task can start.", action: { code: "inspect-decisions", label: "Answer questions" } },
16
+ "sign-in": { sentence: context => `${context.provider ?? "Your agent"} needs you to sign in again. The task starts on its own after.`, action: { code: "sign-in", label: "Sign in again" }, useReason: reason => /sign in|API key/i.test(reason) },
17
+ "confirm-stopped": { sentence: context => `Toolroll can't confirm ${build(context)} stopped. Nothing from it is running.`, action: { code: "confirm-stopped", label: "Confirm it stopped" } },
18
+ // Toolroll can't look at all: never "nothing is running"; the person checks, says so, then confirms.
19
+ "check-stopped": { sentence: context => `Toolroll can't check whether ${build(context)} stopped. Make sure nothing from it is running, then confirm.`, action: { code: "confirm-stopped", label: "Confirm it stopped" } },
20
+ hold: { sentence: () => "A hold is keeping this task from starting. Review it to release it.", action: { code: "inspect-hold", label: "Review hold" } },
21
+ "choose-project": { sentence: () => "Choose a project so a builder can start.", action: { code: "place-task", label: "Choose a project" } },
22
+ "define-task": { sentence: () => "Say what the task should do so it can be planned.", action: { code: "write-scope", label: "Define the task" } },
23
+ "fix-request": { sentence: () => "The request can't be planned as written. Edit it so the planner can start.", action: { code: "write-scope", label: "Edit the request" } },
24
+ "choose-agent": { sentence: () => "Choose an agent to build this.", action: { code: "select-agent", label: "Choose an agent" } },
25
+ "fix-dependency": { sentence: () => "A task this one waits on didn't finish. Fix or remove it so this one can start.", action: { code: "repair-dependency", label: "Fix the required task" },
26
+ useReason: reason => /didn't finish|did not finish|before it finished/i.test(reason), withReason: reason => `${reason.replace(/[.\s]*$/, ".")} Fix or remove it so this one can start.` },
27
+ "add-requirement": { sentence: () => "This project is missing something the task needs. Add it so a builder can start.", action: { code: "repair-capability", label: "Add the requirement" } },
28
+ "connect-builder": { sentence: () => "No builder works on this project yet. Connect one so the task can start.", action: { code: "start-worker", label: "Connect a builder" } },
29
+ "start-builder": { sentence: () => "The builder for this project is offline. Start it and the task begins on its own.", action: { code: "start-worker", label: "Start the builder" } },
30
+ vanished: { sentence: context => `${Build(context)} stopped without finishing. Check it, then retry.`, action: { code: "reconcile-run", label: "Check the build" } },
31
+ "review-result": { sentence: () => "Check the result, then accept it or ask for changes.", action: { code: "open-result", label: "Review result" } },
32
+ // Accepting it would not help: it was built to terms that are no longer the plan.
33
+ rebuild: { sentence: context => `${Build(context)} was made to an earlier plan. Build it again to the current plan.`, action: { code: "retry-task", label: "Build again" } },
34
+ "earlier-version": { sentence: () => "An earlier version of this task still needs you. Finish it first.", action: { code: "inspect-task", label: "Open earlier version" } },
35
+ other: { sentence: () => "Open the task to see what it needs from you.", action: { code: "inspect-task", label: "Open the task" }, useReason: always },
36
+ };
37
+ export const WAITS = {
38
+ "build-stopping": context => `Waiting for ${build(context)} to stop. Nothing is needed from you.`,
39
+ "other-computer": context => `Waiting for the computer that ran ${build(context)} to confirm it stopped.`,
40
+ "card-reply": () => "Waiting for a reply.",
41
+ "card-time": () => "Waiting before moving on.",
42
+ "card-ci": () => "Waiting for the pull request's checks.",
43
+ };
44
+ /** Words that name the machine's internals; no Needs you or Waiting sentence carries them. */
45
+ export const BANNED_WORDS = ["witness", "unproven", "quiescence", "quiescent", "digest", "scope digest"];
46
+ const BANNED = new RegExp(`\\b(?:${BANNED_WORDS.join("|")})\\b`, "i");
47
+ export const hasInternalWords = (text) => BANNED.test(text);
48
+ /** The sentence for a need: its recorded reason when the need keeps one and
49
+ * it reads plainly, otherwise the need's own words. */
50
+ export function needSentence(key, reason, context = {}) {
51
+ const need = NEEDS[key];
52
+ const plain = reason?.trim() || null;
53
+ if (need.useReason === undefined || plain === null || !need.useReason(plain) || hasInternalWords(plain))
54
+ return need.sentence(context);
55
+ return need.withReason === undefined ? plain : need.withReason(plain);
56
+ }
57
+ export function waitSentence(key, reason, context = {}) {
58
+ if (key !== undefined)
59
+ return WAITS[key](context);
60
+ const plain = reason?.trim() || null;
61
+ return plain !== null && !hasInternalWords(plain) ? plain : "Waiting for the work to continue on its own.";
62
+ }
63
+ /** A finished run Toolroll can't prove stopped (store.stopQuiescenceFact):
64
+ * a person can confirm it only when nothing of it may still be running.
65
+ * `unknown`: Toolroll can't check, so the person confirms they checked. */
66
+ export function processNeedOf(fact) {
67
+ if (fact === null)
68
+ return null;
69
+ if (fact.kind === "unprovable")
70
+ return { need: "confirm-stopped", build: fact.run };
71
+ if (fact.kind === "unknown")
72
+ return { need: "check-stopped", build: fact.run };
73
+ return { wait: fact.kind === "elsewhere" ? "other-computer" : "build-stopping", build: fact.run };
74
+ }
75
+ /** Dispatch and work-index codes that wait on a person, as their need. */
76
+ export const CODE_NEED = {
77
+ "signed-out": "sign-in", "needs-approval": "approval", "waiting-decision": "answer", "decision-queue": "answer",
78
+ "needs-project": "choose-project", "needs-scope": "define-task", "planner-source": "fix-request", "needs-agent-profile": "choose-agent",
79
+ "terminal-dependency": "fix-dependency", "missing-requirement": "add-requirement", "no-worker-registered": "connect-builder",
80
+ "no-worker-online": "start-builder", "vanished-run": "vanished", "needs-verification": "review-result", "proof-refuted": "review-result",
81
+ "no-build-record": "review-result", "verification-needed": "review-result", "evidence-mismatch": "review-result",
82
+ "record-incomplete": "review-result", "evidence-damaged": "review-result",
83
+ };
package/dist/operate.d.ts CHANGED
@@ -37,6 +37,8 @@ export type OperateOptions = {
37
37
  releaseIo?: ReleaseIo;
38
38
  /** Injected by tests: the bin whose real path says how Toolroll was installed. */
39
39
  installBin?: string;
40
+ /** Injected by tests: where `storage` looks for leftover test temp folders (default: the temp folder and /tmp). */
41
+ tempRoots?: readonly string[];
40
42
  openDatabase?: (file: string) => Store;
41
43
  now?: Date;
42
44
  /**