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/dispatch.js
CHANGED
|
@@ -16,6 +16,8 @@ import { REVIEW_TOKENS, resultStatusOf, reviewFactsOf } from "./workspace-ui.js"
|
|
|
16
16
|
import { replacedWords } from "./task-status.js";
|
|
17
17
|
import { failedAttemptSentence, latestFinishedAttempt } from "./needs-you.js";
|
|
18
18
|
import { buildReviewOf } from "./review-switch.js";
|
|
19
|
+
import { vanishedStopOf, vanishedStopSentence } from "./vanished-retry.js";
|
|
20
|
+
import { routeChangeOf } from "./route-change.js";
|
|
19
21
|
import { BUILT_IN, refArchived, parseCapabilityKey } from "./store.js";
|
|
20
22
|
import { shortWhen } from "./when-html.js";
|
|
21
23
|
export const DEFAULT_MAX_OPEN_DECISIONS = 5;
|
|
@@ -81,7 +83,7 @@ export function scopeApprovedForDispatch(store, taskRef, now) {
|
|
|
81
83
|
JOIN task_ref ON task_ref.id = ? AND task_scope.task_id = task_ref.external_id
|
|
82
84
|
WHERE task_scope.approved_digest = task_scope.digest AND task_scope.approved_at IS NOT NULL
|
|
83
85
|
AND ${authorityStandsSql("task_scope", "task_ref.repo", "?")}`)
|
|
84
|
-
.get(taskRef, now.toISOString(), now.toISOString(), now.toISOString());
|
|
86
|
+
.get(taskRef, now.toISOString(), now.toISOString(), now.toISOString(), now.toISOString());
|
|
85
87
|
return row !== undefined;
|
|
86
88
|
}
|
|
87
89
|
/** The phase the ordinary dispatch loop would choose before it resolves an
|
|
@@ -229,9 +231,9 @@ export function diagnoseTaskDispatch(store, taskId, now, readableRepos = null) {
|
|
|
229
231
|
return answer(code, code === "reviewing" ? "running" : "waiting", status.label, status.detail, { action: retry.state === "retryable" ? "retry-review" : "open-result", review });
|
|
230
232
|
}
|
|
231
233
|
if (!accepted && proof?.verdict === "refuted")
|
|
232
|
-
return answer("proof-refuted", "waiting", status.label, `${status.detail}
|
|
234
|
+
return answer("proof-refuted", "waiting", status.label, `${status.detail} Open the result to send it back, or accept it with a reason.`, { action: "open-result", review });
|
|
233
235
|
if (!accepted && proof?.verdict === "short")
|
|
234
|
-
return answer("needs-verification", "waiting", status.label, `${status.detail} Open the result
|
|
236
|
+
return answer("needs-verification", "waiting", status.label, `${status.detail} Open the result to see which requirements still need checking.`, { action: "open-result", review });
|
|
235
237
|
if (!accepted && proof === null && noChangeRecord !== true)
|
|
236
238
|
return answer("needs-verification", "waiting", status.label, status.detail, { action: "open-result", review });
|
|
237
239
|
return answer("complete", "terminal", status.label, review?.state === "succeeded" && review.attempt !== null && review.attempt > 1
|
|
@@ -248,12 +250,13 @@ export function diagnoseTaskDispatch(store, taskId, now, readableRepos = null) {
|
|
|
248
250
|
// The family's latest finished attempt's own recorded reason, or that none was recorded: never "review its incident".
|
|
249
251
|
const family = store.taskFamilyOf(taskId, ref.repo === null ? [] : [ref.repo], ref.repo === null);
|
|
250
252
|
const runs = family === null || family.problem !== null ? store.runsFor(ref.id) : family.versions.flatMap(version => store.runsFor(version.refId));
|
|
251
|
-
|
|
253
|
+
const stopped = vanishedStopOf(store, ref.id);
|
|
254
|
+
return answer("failed", "terminal", "Needs a retry", stopped === null ? failedAttemptSentence(latestFinishedAttempt(runs)?.reason) : vanishedStopSentence(stopped), { action: "retry-task" });
|
|
252
255
|
}
|
|
253
256
|
if (store.hasLiveClaim(ref.id, now))
|
|
254
|
-
return answer("running", "running", "Running now", "A
|
|
257
|
+
return answer("running", "running", "Running now", "A builder is working on it now.");
|
|
255
258
|
if (task.state === "running")
|
|
256
|
-
return answer("vanished-run", "waiting", "Build vanished", "The
|
|
259
|
+
return answer("vanished-run", "waiting", "Build vanished", "The build stopped unexpectedly. Retry it.", { action: "retry-task" });
|
|
257
260
|
if (updateAdmissionPaused(store.raw()))
|
|
258
261
|
return answer("updating", "waiting", "Waiting for app update", UPDATE_PAUSED);
|
|
259
262
|
const local = taskReadinessBlocker(store, ref.id, now);
|
|
@@ -270,7 +273,7 @@ export function diagnoseTaskDispatch(store, taskId, now, readableRepos = null) {
|
|
|
270
273
|
if (local.ownerKind === "decision")
|
|
271
274
|
return answer("waiting-decision", "waiting", "Waiting on your answer", "An agent parked a question; answering it resumes the task.", { action: "answer-decision" });
|
|
272
275
|
if (local.ownerKind === "incident")
|
|
273
|
-
return answer("waiting-incident", "waiting", "Needs a retry", "
|
|
276
|
+
return answer("waiting-incident", "waiting", "Needs a retry", "The last attempts failed the same way. Retry, or change the plan.", { action: "retry-task" });
|
|
274
277
|
if (local.ownerKind === "stop")
|
|
275
278
|
return answer("stopped", "waiting", "Paused", `${local.message.replace(/^held:\s*/, "")}. The attempt's work is preserved; resuming that exact attempt lifts this pause.`, { action: "resume-run" });
|
|
276
279
|
return answer("held", "waiting", "On hold", local.message.replace(/^held:\s*/, ""), { action: local.ownerKind === "operator" ? "unhold" : "inspect-hold" });
|
|
@@ -288,14 +291,14 @@ export function diagnoseTaskDispatch(store, taskId, now, readableRepos = null) {
|
|
|
288
291
|
const role = dispatchRoleFor(store, ref.id, now);
|
|
289
292
|
const scope = store.getScope(taskId);
|
|
290
293
|
if (ref.repo === null)
|
|
291
|
-
return answer("needs-project", "waiting", "Needs a project", "
|
|
294
|
+
return answer("needs-project", "waiting", "Needs a project", "Choose a project for this task so a builder can start it.", { action: "place-task", role });
|
|
292
295
|
if (role !== "planner") {
|
|
293
296
|
if (scope === null)
|
|
294
|
-
return answer("needs-scope", "waiting", "Needs a
|
|
297
|
+
return answer("needs-scope", "waiting", "Needs a plan", "Write the plan, or ask the planner to draft it.", { action: "write-scope", role });
|
|
295
298
|
if (scope.profileState === "unresolved")
|
|
296
299
|
return answer("needs-agent-profile", "waiting", "Needs an agent profile", scope.unresolvedReason ?? "Choose an available provider and model.", { action: "select-agent", role });
|
|
297
300
|
if (!scopeApprovedForDispatch(store, ref.id, now) || !approvalOf(scope).approved) {
|
|
298
|
-
return answer("needs-approval", "waiting", "Needs your approval", "Review
|
|
301
|
+
return answer("needs-approval", "waiting", "Needs your approval", routeChangeOf(store, taskId) ?? "Review the plan and approve it so a builder can start.", { action: "approve-scope", role });
|
|
299
302
|
}
|
|
300
303
|
}
|
|
301
304
|
const gap = missingCapability(store, ref.id, ref.repo, now);
|
|
@@ -315,14 +318,14 @@ export function diagnoseTaskDispatch(store, taskId, now, readableRepos = null) {
|
|
|
315
318
|
}
|
|
316
319
|
const available = alive.filter(one => store.liveClaimCount(one.name, now) < one.capacity);
|
|
317
320
|
if (available.length === 0)
|
|
318
|
-
return answer("worker-at-capacity", "retrying", "Waiting
|
|
321
|
+
return answer("worker-at-capacity", "retrying", "Waiting its turn", "Builders are busy; it starts when one is free.", { role });
|
|
319
322
|
const profile = approvedProfile(scope);
|
|
320
323
|
if (profile !== null) {
|
|
321
324
|
const exhausted = available.map(one => store.readQuotaState(one.name, profile.provider, profile.model, now)).filter(one => one?.state === "exhausted");
|
|
322
325
|
if (exhausted.length === available.length) {
|
|
323
326
|
const resets = exhausted.map(one => one?.resetAt ?? null).filter((one) => one !== null).sort();
|
|
324
327
|
const nextAt = resets[0] ?? null;
|
|
325
|
-
return answer("provider-quota", "retrying", "Provider quota exhausted", nextAt === null ? "The provider
|
|
328
|
+
return answer("provider-quota", "retrying", "Provider quota exhausted", nextAt === null ? "The provider gave no reset time. Choose another agent, or wait for the limit to reset." : `The next recorded reset is ${nextAt}.`, { action: nextAt === null ? "select-agent" : null, nextAt, role });
|
|
326
329
|
}
|
|
327
330
|
}
|
|
328
331
|
// The sign-in pause: the task's provider stopped working; nothing it would
|
|
@@ -343,11 +346,11 @@ export function diagnoseTaskDispatch(store, taskId, now, readableRepos = null) {
|
|
|
343
346
|
const sourceProblem = plannerSourceProblemOf(store, taskId);
|
|
344
347
|
if (sourceProblem !== null)
|
|
345
348
|
return answer("planner-source", "waiting", sourceProblem.title, sourceProblem.detail, { action: "write-scope", role });
|
|
346
|
-
return answer("planning-ready", "retrying", "
|
|
349
|
+
return answer("planning-ready", "retrying", "Waiting to plan", "A builder can draft the plan next.", { role });
|
|
347
350
|
}
|
|
348
351
|
if (role === "scout")
|
|
349
|
-
return answer("scouting-ready", "retrying", "
|
|
350
|
-
return answer("ready", "retrying", "Ready to run", "
|
|
352
|
+
return answer("scouting-ready", "retrying", "Waiting to research", "A builder can write the report next.", { role });
|
|
353
|
+
return answer("ready", "retrying", "Ready to run", "A builder is free and nothing is in the way.", { role });
|
|
351
354
|
}
|
|
352
355
|
export function diagnosisIsDispatchable(diagnosis) {
|
|
353
356
|
return diagnosis?.code === "ready" || diagnosis?.code === "planning-ready" || diagnosis?.code === "scouting-ready";
|
package/dist/dispose.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
// Assignment tracking grants no repair authority or attempts.
|
|
2
|
+
import { authorizeCheckOnly } from "./check-only.js";
|
|
3
|
+
import { proposeNewRoute } from "./route-change.js";
|
|
2
4
|
import { originalTaskBase, focusedTestCommandSupported } from "./observations.js";
|
|
3
5
|
/**
|
|
4
6
|
* The build disposition service (Parity II Phase 2, v4 Q2 / v6 W1): the
|
|
@@ -158,6 +160,8 @@ function disposeBuildOutcomeLocked(context, result) {
|
|
|
158
160
|
});
|
|
159
161
|
// A stated no-change publishes nothing.
|
|
160
162
|
if (result.noChange !== true && result.committed) {
|
|
163
|
+
// The approval that built this commit allows one automatic recheck of it, should its verdict go missing.
|
|
164
|
+
authorizeCheckOnly(store, runId, clock());
|
|
161
165
|
const grant = store.publicationGrantFor(repo);
|
|
162
166
|
const headSha = store.getRun(runId)?.headRevision ?? null;
|
|
163
167
|
if (grant !== null &&
|
|
@@ -357,6 +361,8 @@ function disposeBuildOutcomeLocked(context, result) {
|
|
|
357
361
|
*/
|
|
358
362
|
export function holdStaleApproval(store, args, now) {
|
|
359
363
|
const scope = store.getScope(args.taskId);
|
|
364
|
+
// Toolroll proposes the new exact route itself (route-change.ts): the person's one act is Approve, on one line.
|
|
365
|
+
const proposed = proposeNewRoute(store, args.taskRef, args.taskId, now);
|
|
360
366
|
store.holdOwned({
|
|
361
367
|
taskRef: args.taskRef,
|
|
362
368
|
ownerKind: "backoff",
|
|
@@ -370,8 +376,8 @@ export function holdStaleApproval(store, args, now) {
|
|
|
370
376
|
kind: "stale-approval",
|
|
371
377
|
pushClass: "attention",
|
|
372
378
|
link: `/t/${encodeURIComponent(args.taskId)}`,
|
|
373
|
-
subject: `${args.taskId}: its
|
|
374
|
-
body: `${args.message}\nNothing runs on it until
|
|
379
|
+
subject: `${args.taskId}: its agents changed — approve it again`,
|
|
380
|
+
body: `${proposed?.words ?? args.message}\nNothing runs on it until it's approved again.`,
|
|
375
381
|
}, now);
|
|
376
382
|
}
|
|
377
383
|
// ---- the bounded repair loop (v40, evidence-review-v1) --------------------
|
package/dist/easy-start.js
CHANGED
|
@@ -284,9 +284,8 @@ export function defaultEasyStartSeams(options) {
|
|
|
284
284
|
}
|
|
285
285
|
},
|
|
286
286
|
findRepo: async (dir) => {
|
|
287
|
-
const {
|
|
288
|
-
|
|
289
|
-
return top.code === 0 ? top.stdout.trim() : null;
|
|
287
|
+
const { checkoutTop } = await import("./teardown-git.js");
|
|
288
|
+
return checkoutTop(dir);
|
|
290
289
|
},
|
|
291
290
|
enrolled: async (repo) => {
|
|
292
291
|
const { loadRepos } = await import("./repos.js");
|
package/dist/eval-hooks.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type ts from "typescript-parser";
|
|
1
2
|
/** One required call: in this file, inside this top-level function, a call to this name, the name bound as `from`
|
|
2
3
|
* says (a module specifier its un-renamed named import comes from; `self`: the file's own top-level function; `local`:
|
|
3
4
|
* declared inside `inside`). `args`: the call's arguments, as text, must stay as the base has them. */
|
|
@@ -9,12 +10,17 @@ export type EvalHook = {
|
|
|
9
10
|
args?: true;
|
|
10
11
|
why: string;
|
|
11
12
|
};
|
|
13
|
+
/** `fn` may also be `outer>inner` (docs/plans/learning-loops.md, Protection 3): a `const inner = (…) => …` (or function
|
|
14
|
+
* expression) or `function inner`, bound once in the file and never reassigned, inside the top-level function `outer`
|
|
15
|
+
* and in no other function between them. `strict`: a base that holds the file but can't resolve the pin is a problem,
|
|
16
|
+
* never skipped (a base without the file at all is a project that isn't Toolroll, and holds none). */
|
|
12
17
|
/** One function whose body (its text, as the candidate has it) must stay as the base has it: a top-level function, or
|
|
13
18
|
* `Class#method`, a method of a top-level class. */
|
|
14
19
|
export type EvalPinned = {
|
|
15
20
|
file: string;
|
|
16
21
|
fn: string;
|
|
17
22
|
why: string;
|
|
23
|
+
strict?: true;
|
|
18
24
|
};
|
|
19
25
|
export declare const EVAL_HOOKS: readonly EvalHook[];
|
|
20
26
|
export declare const EVAL_PINNED: readonly EvalPinned[];
|
|
@@ -37,3 +43,52 @@ export declare const EVAL_HOOK_FILES: readonly string[];
|
|
|
37
43
|
* files the verified diff says exist at the candidate, so no bytes is a failed read, never a deletion.
|
|
38
44
|
*/
|
|
39
45
|
export declare function storeRewriteProblem(paths: readonly string[], candidate: ((file: string) => Buffer | null) | null, base: ((file: string) => Buffer | null) | null, pinned?: readonly string[]): string | null;
|
|
46
|
+
/** The calls that record a check's facts: at disposition, and as the check's command starts. */
|
|
47
|
+
export declare const LEARNING_HOOKS: readonly EvalHook[];
|
|
48
|
+
/** The bodies a release can't change by itself, every one strict: what a run is told from project memory, how a memory
|
|
49
|
+
* proposal and a decision are written, and where a check's conditions are captured. */
|
|
50
|
+
export declare const LEARNING_PINNED: readonly EvalPinned[];
|
|
51
|
+
/** Every file the learning manifest reads. */
|
|
52
|
+
export declare const LEARNING_HOOK_FILES: readonly string[];
|
|
53
|
+
/** Each protected table and its only writer modules (the v149 migration in store.ts aside). The decision tables'
|
|
54
|
+
* existing writers stay theirs: project-memory.ts records and retires, project-delete.ts deletes a whole project. */
|
|
55
|
+
export declare const LEARNING_WRITERS: Readonly<Record<string, readonly string[]>>;
|
|
56
|
+
/** One SQL-shaped construct in a file: its static text (a placeholder `${}` where an expression sits), the protected
|
|
57
|
+
* tables it writes, and the top-level declaration it's in ("" for none). `unreadable`: a write whose table name is
|
|
58
|
+
* split around an expression; `bare`: a string that is only a protected table's name (SQL assembled elsewhere). */
|
|
59
|
+
type LearningConstruct = {
|
|
60
|
+
text: string;
|
|
61
|
+
tables: string[];
|
|
62
|
+
inside: string;
|
|
63
|
+
unreadable: boolean;
|
|
64
|
+
bare: boolean;
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Every construct in the file that could write a protected table: each string literal, template (its static parts)
|
|
68
|
+
* and `+` concatenation (its literal operands), whatever it's passed to — `prepare`, `exec`, a renamed `prepare`, a
|
|
69
|
+
* helper. A write verb next to a protected table's name, a table name split around an expression next to a write
|
|
70
|
+
* word, and a string that is only a protected table's name are each found.
|
|
71
|
+
*/
|
|
72
|
+
export declare function learningConstructs(source: ts.SourceFile): LearningConstruct[];
|
|
73
|
+
/**
|
|
74
|
+
* Why a changed file writes a learning-loops table (or a decision merge or restore) from a place that may not, or null
|
|
75
|
+
* (docs/plans/learning-loops.md, Protection 4). Every changed JavaScript or TypeScript file but tests is parsed; a
|
|
76
|
+
* construct its writer module doesn't own is new unless the base's copy of that file already had exactly that text
|
|
77
|
+
* (counted by its sha256, as storeRewriteProblem does). A file that can't be read or parsed needs a person. It reads
|
|
78
|
+
* text the candidate wrote, never runs it: it raises the bar for review; a person reviewing these files is the backstop.
|
|
79
|
+
*/
|
|
80
|
+
export declare function learningWriteProblem(paths: readonly string[], candidate: ((file: string) => Buffer | null) | null, base: ((file: string) => Buffer | null) | null): string | null;
|
|
81
|
+
/**
|
|
82
|
+
* Why a candidate needs a person for learning loops, or null: a required call dropped, a pinned body changed (strict
|
|
83
|
+
* pins included), or a changed file writing a protected table from outside its writers. With `manifest.base`, only what
|
|
84
|
+
* the base holds is required, as evalHookProblem does — except a strict pin whose file the base holds.
|
|
85
|
+
*/
|
|
86
|
+
export declare function learningHookProblem(manifest: {
|
|
87
|
+
candidate: (file: string) => Buffer | null;
|
|
88
|
+
base?: (file: string) => Buffer | null;
|
|
89
|
+
}, changed: {
|
|
90
|
+
paths: readonly string[];
|
|
91
|
+
candidate: ((file: string) => Buffer | null) | null;
|
|
92
|
+
base: ((file: string) => Buffer | null) | null;
|
|
93
|
+
}): string | null;
|
|
94
|
+
export {};
|
package/dist/eval-hooks.js
CHANGED
|
@@ -36,6 +36,7 @@ export const EVAL_HOOKS = Object.freeze([
|
|
|
36
36
|
{ file: "src/release-auto.ts", inside: "changeProblem", calls: "evalHookProblem", from: "./eval-hooks.js", why: "this check itself" },
|
|
37
37
|
{ file: "src/release-auto.ts", inside: "changeProblem", calls: "autoPathProblem", from: "self", why: "policy paths need a person" },
|
|
38
38
|
{ file: "src/release-auto.ts", inside: "changeProblem", calls: "storeRewriteProblem", from: "./eval-hooks.js", why: "no changed file rewrites a pinned Store method" },
|
|
39
|
+
{ file: "src/release-auto.ts", inside: "changeProblem", calls: "learningHookProblem", from: "./eval-hooks.js", why: "learning loops' hooks, pins and writers (docs/plans/learning-loops.md)" },
|
|
39
40
|
// The invocation gateway (invoke.ts): every agent run an eval world spawns takes a slot.
|
|
40
41
|
{ file: "src/invoke.ts", inside: "invokeAgent", calls: "takeEvalDispatch", from: "./eval-gate.js", why: "every agent dispatch in an eval world takes a slot" },
|
|
41
42
|
// PR #263 review: what the run's billing basis and its unknown cost are computed from is held too, not only the call.
|
|
@@ -227,7 +228,7 @@ function boundAs(source, name, from, inside) {
|
|
|
227
228
|
if (from === "self")
|
|
228
229
|
return compiler.isFunctionDeclaration(only) && only.parent === source ? null : `${name} isn't its own top-level function`;
|
|
229
230
|
if (from === "local")
|
|
230
|
-
return only.pos >= inside.pos && only.end <= inside.end && !compiler.isParameter(only) ? null : `${name} isn't declared inside
|
|
231
|
+
return only.pos >= inside.pos && only.end <= inside.end && !compiler.isParameter(only) ? null : `${name} isn't declared inside its function`;
|
|
231
232
|
if (!compiler.isImportSpecifier(only) || only.propertyName !== undefined || only.isTypeOnly)
|
|
232
233
|
return `${name} isn't imported by its own name`;
|
|
233
234
|
const clause = only.parent.parent;
|
|
@@ -254,6 +255,46 @@ function heldFunction(source, name) {
|
|
|
254
255
|
const fn = topFunction(source, name);
|
|
255
256
|
return fn === null || boundAs(source, name, "self", fn) !== null ? null : fn;
|
|
256
257
|
}
|
|
258
|
+
/** `outer>inner`: the one function-like `inner` declared inside the held top-level `outer` (no other function between
|
|
259
|
+
* them), bound once in the file and never reassigned — a `function inner` or a `const inner =` arrow or function
|
|
260
|
+
* expression. Null otherwise. */
|
|
261
|
+
function nestedFunction(source, spec) {
|
|
262
|
+
const compiler = typescript();
|
|
263
|
+
const [outerName, innerName, ...rest] = spec.split(">");
|
|
264
|
+
if (outerName === undefined || innerName === undefined || rest.length > 0 || outerName === "" || innerName === "")
|
|
265
|
+
return null;
|
|
266
|
+
const outer = heldFunction(source, outerName);
|
|
267
|
+
if (outer === null || outer.body === undefined)
|
|
268
|
+
return null;
|
|
269
|
+
const all = bindingsOf(source, innerName);
|
|
270
|
+
if (all.length !== 1)
|
|
271
|
+
return null;
|
|
272
|
+
const only = all[0];
|
|
273
|
+
let fn = null;
|
|
274
|
+
if (compiler.isFunctionDeclaration(only) && only.body !== undefined)
|
|
275
|
+
fn = only;
|
|
276
|
+
else if (compiler.isVariableDeclaration(only) && only.initializer !== undefined && (compiler.isArrowFunction(only.initializer) || compiler.isFunctionExpression(only.initializer))
|
|
277
|
+
&& compiler.isVariableDeclarationList(only.parent) && (only.parent.flags & compiler.NodeFlags.Const) !== 0)
|
|
278
|
+
fn = only.initializer;
|
|
279
|
+
if (fn === null)
|
|
280
|
+
return null;
|
|
281
|
+
// Directly inside `outer`: the first function-like ancestor of the declaration is `outer` itself.
|
|
282
|
+
for (let at = only.parent; at !== undefined; at = at.parent) {
|
|
283
|
+
if (at === outer)
|
|
284
|
+
return fn;
|
|
285
|
+
if (compiler.isFunctionLike(at) || compiler.isClassLike(at))
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
/** A held function by its manifest name: top-level, `Class#method` or `outer>inner`. */
|
|
291
|
+
function heldNode(source, spec) {
|
|
292
|
+
if (spec.includes(">"))
|
|
293
|
+
return nestedFunction(source, spec);
|
|
294
|
+
if (spec.includes("#"))
|
|
295
|
+
return classMethod(source, spec);
|
|
296
|
+
return heldFunction(source, spec);
|
|
297
|
+
}
|
|
257
298
|
/** A reader that parses each file once. */
|
|
258
299
|
function parser(read) {
|
|
259
300
|
const seen = new Map();
|
|
@@ -265,7 +306,7 @@ function hookIn(tree, hook) {
|
|
|
265
306
|
const parsed = tree(hook.file);
|
|
266
307
|
if ("problem" in parsed)
|
|
267
308
|
return { missing: `${parsed.problem}, and it holds a required call (${hook.why})` };
|
|
268
|
-
const fn = heldFunction(parsed.source, hook.inside);
|
|
309
|
+
const fn = (hook.inside.includes(">") ? nestedFunction(parsed.source, hook.inside) : heldFunction(parsed.source, hook.inside));
|
|
269
310
|
if (fn === null || fn.body === undefined)
|
|
270
311
|
return { missing: `${hook.file} no longer has ${hook.inside} as one function, which must call ${hook.calls} (${hook.why})` };
|
|
271
312
|
const found = callsOf(fn.body, hook.calls);
|
|
@@ -281,7 +322,7 @@ function pinnedText(tree, pin) {
|
|
|
281
322
|
const parsed = tree(pin.file);
|
|
282
323
|
if ("problem" in parsed)
|
|
283
324
|
return null;
|
|
284
|
-
const fn =
|
|
325
|
+
const fn = heldNode(parsed.source, pin.fn);
|
|
285
326
|
if (fn === null)
|
|
286
327
|
return null;
|
|
287
328
|
// The static block's pin holds the class header too: its name, modifiers and heritage, as the base has them.
|
|
@@ -308,8 +349,12 @@ export function evalHookProblem(candidate, base, hooks = EVAL_HOOKS, pinned = EV
|
|
|
308
349
|
}
|
|
309
350
|
for (const pin of pinned) {
|
|
310
351
|
const before = was === null ? null : pinnedText(was, pin);
|
|
311
|
-
if (was !== null && before === null)
|
|
352
|
+
if (was !== null && before === null) {
|
|
353
|
+
// A strict pin the base holds the file of but can't resolve is a problem (learning loops, Protection 2), never a skip.
|
|
354
|
+
if (pin.strict === true && base(pin.file) !== null)
|
|
355
|
+
return `${pin.file} at the base has no ${pin.fn} as one function, which is pinned strictly (${pin.why})`;
|
|
312
356
|
continue;
|
|
357
|
+
}
|
|
313
358
|
const after = pinnedText(now, pin);
|
|
314
359
|
if (after === null)
|
|
315
360
|
return `${pin.file} no longer has ${pin.fn} as one function (${pin.why})`;
|
|
@@ -475,3 +520,181 @@ export function storeRewriteProblem(paths, candidate, base, pinned = PINNED_STOR
|
|
|
475
520
|
}
|
|
476
521
|
return null;
|
|
477
522
|
}
|
|
523
|
+
// ---- learning loops (docs/plans/learning-loops.md, "Safety", Protection 2-4) ----------------------------------------
|
|
524
|
+
/** The calls that record a check's facts: at disposition, and as the check's command starts. */
|
|
525
|
+
export const LEARNING_HOOKS = Object.freeze([
|
|
526
|
+
{ file: "src/builder.ts", inside: "settleProof", calls: "recordCheckFacts", from: "./check-facts.js", why: "every settled check records its facts" },
|
|
527
|
+
{ file: "src/builder.ts", inside: "settleProof>runVerification", calls: "captureCheckConditions", from: "./check-facts.js", why: "a check's conditions are captured as its command starts" },
|
|
528
|
+
]);
|
|
529
|
+
/** The bodies a release can't change by itself, every one strict: what a run is told from project memory, how a memory
|
|
530
|
+
* proposal and a decision are written, and where a check's conditions are captured. */
|
|
531
|
+
export const LEARNING_PINNED = Object.freeze([
|
|
532
|
+
{ file: "src/project-knowledge.ts", fn: "knowledgeContext", strict: true, why: "the decisions and references a run is given, and their snapshot" },
|
|
533
|
+
{ file: "src/project-learning.ts", fn: "learningContext", strict: true, why: "the lessons a run is given, and their snapshot" },
|
|
534
|
+
{ file: "src/memory-pass.ts", fn: "decideProposal", strict: true, why: "a memory proposal's one writer" },
|
|
535
|
+
{ file: "src/project-memory.ts", fn: "recordDecision", strict: true, why: "how a decision is recorded and what it supersedes" },
|
|
536
|
+
{ file: "src/project-memory.ts", fn: "retireDecision", strict: true, why: "how a decision is retired" },
|
|
537
|
+
{ file: "src/builder.ts", fn: "settleProof>runVerification", strict: true, why: "the check's command and the conditions it starts under" },
|
|
538
|
+
]);
|
|
539
|
+
/** Every file the learning manifest reads. */
|
|
540
|
+
export const LEARNING_HOOK_FILES = Object.freeze([...new Set([...LEARNING_HOOKS.map(hook => hook.file), ...LEARNING_PINNED.map(pin => pin.file)])]);
|
|
541
|
+
/** Each protected table and its only writer modules (the v149 migration in store.ts aside). The decision tables'
|
|
542
|
+
* existing writers stay theirs: project-memory.ts records and retires, project-delete.ts deletes a whole project. */
|
|
543
|
+
export const LEARNING_WRITERS = Object.freeze({
|
|
544
|
+
check_fact: ["src/check-facts.ts"], check_result: ["src/check-facts.ts"],
|
|
545
|
+
local_observation: ["src/flaky.ts"], ci_flaky_observation: ["src/flaky-ci.ts"], ci_pairing_rejected: ["src/flaky-ci.ts"],
|
|
546
|
+
learning_watch: ["src/flaky.ts", "src/lesson-notes.ts"],
|
|
547
|
+
lesson_note: ["src/lesson-notes.ts"], lesson_note_tally: ["src/lesson-notes.ts"],
|
|
548
|
+
memory_use: ["src/memory-use.ts"],
|
|
549
|
+
memory_tidy_proposal: ["src/memory-tidy.ts"], decision_merge: ["src/memory-tidy.ts"],
|
|
550
|
+
project_decision: ["src/memory-tidy.ts", "src/project-memory.ts", "src/project-delete.ts"],
|
|
551
|
+
decision_change: ["src/memory-tidy.ts", "src/project-memory.ts", "src/project-delete.ts"],
|
|
552
|
+
});
|
|
553
|
+
/** store.ts's v149 migration: the only place outside the writers that may name these tables in SQL. */
|
|
554
|
+
const LEARNING_MIGRATION = Object.freeze({ "src/store.ts": ["V149_SCHEMA", "V149_TABLES", "V149_REPO_TABLES"] });
|
|
555
|
+
const PROTECTED = Object.keys(LEARNING_WRITERS);
|
|
556
|
+
const tableWrite = (table) => new RegExp(String.raw `(?:\b(?:(?:INSERT(?:\s+OR\s+\w+)?|REPLACE)\s+INTO|UPDATE(?:\s+OR\s+\w+)?|DELETE\s+FROM)\s+(?:main\s*\.\s*)?["\`\[]?${table}["\`\]]?(?!\w)`
|
|
557
|
+
+ String.raw `|\b(?:DROP\s+(?:TABLE|TRIGGER|INDEX|VIEW)(?:\s+IF\s+EXISTS)?|ALTER\s+TABLE)\s+(?:main\s*\.\s*)?["\`\[]?${table})`, "i");
|
|
558
|
+
const TABLE_WRITES = PROTECTED.map(table => ({ table, write: tableWrite(table) }));
|
|
559
|
+
const WRITE_WORD = /\b(?:INSERT|UPDATE|DELETE|REPLACE|DROP|ALTER)\b/i;
|
|
560
|
+
const BARE_NAME = new RegExp(String.raw `^\s*(?:main\s*\.\s*)?["\`\[]?(?:${PROTECTED.join("|")})["\`\]]?\s*$`, "i");
|
|
561
|
+
/**
|
|
562
|
+
* Every construct in the file that could write a protected table: each string literal, template (its static parts)
|
|
563
|
+
* and `+` concatenation (its literal operands), whatever it's passed to — `prepare`, `exec`, a renamed `prepare`, a
|
|
564
|
+
* helper. A write verb next to a protected table's name, a table name split around an expression next to a write
|
|
565
|
+
* word, and a string that is only a protected table's name are each found.
|
|
566
|
+
*/
|
|
567
|
+
export function learningConstructs(source) {
|
|
568
|
+
const compiler = typescript();
|
|
569
|
+
const found = [];
|
|
570
|
+
const topOf = (node) => {
|
|
571
|
+
let at = node;
|
|
572
|
+
while (at.parent !== undefined && !compiler.isSourceFile(at.parent))
|
|
573
|
+
at = at.parent;
|
|
574
|
+
if (compiler.isFunctionDeclaration(at) || compiler.isClassDeclaration(at))
|
|
575
|
+
return at.name?.text ?? "";
|
|
576
|
+
if (compiler.isVariableStatement(at))
|
|
577
|
+
return at.declarationList.declarations.map(one => compiler.isIdentifier(one.name) ? one.name.text : "").join(",");
|
|
578
|
+
return "";
|
|
579
|
+
};
|
|
580
|
+
const fragmentRisk = (parts) => {
|
|
581
|
+
// Around each placeholder: the identifier characters just before and just after it.
|
|
582
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
583
|
+
const before = /[A-Za-z0-9_]*$/.exec(parts[i])[0].toLowerCase(), after = /^[A-Za-z0-9_]*/.exec(parts[i + 1])[0].toLowerCase();
|
|
584
|
+
if (before === "" && after === "")
|
|
585
|
+
continue;
|
|
586
|
+
if (PROTECTED.some(table => (before !== "" && table.startsWith(before) && table !== before) || (after !== "" && table.endsWith(after) && table !== after)
|
|
587
|
+
|| (before !== "" && after !== "" && table.startsWith(before) && table.endsWith(after))))
|
|
588
|
+
return true;
|
|
589
|
+
}
|
|
590
|
+
return false;
|
|
591
|
+
};
|
|
592
|
+
const consider = (node, parts) => {
|
|
593
|
+
const text = parts.join("${}");
|
|
594
|
+
const tables = TABLE_WRITES.filter(one => one.write.test(text)).map(one => one.table);
|
|
595
|
+
const unreadable = parts.length > 1 && WRITE_WORD.test(text) && fragmentRisk(parts);
|
|
596
|
+
const bare = parts.length === 1 && BARE_NAME.test(text);
|
|
597
|
+
if (tables.length > 0 || unreadable || bare)
|
|
598
|
+
found.push({ text: text.replace(/\s+/g, " ").trim(), tables, inside: topOf(node), unreadable, bare });
|
|
599
|
+
};
|
|
600
|
+
const isPlus = (node) => compiler.isBinaryExpression(node) && node.operatorToken.kind === compiler.SyntaxKind.PlusToken;
|
|
601
|
+
const flatten = (node, into) => {
|
|
602
|
+
let at = node;
|
|
603
|
+
while (compiler.isParenthesizedExpression(at))
|
|
604
|
+
at = at.expression;
|
|
605
|
+
if (isPlus(at)) {
|
|
606
|
+
flatten(at.left, into);
|
|
607
|
+
flatten(at.right, into);
|
|
608
|
+
}
|
|
609
|
+
else
|
|
610
|
+
into.push(at);
|
|
611
|
+
};
|
|
612
|
+
const visit = (node) => {
|
|
613
|
+
if (isPlus(node) && !(node.parent !== undefined && (isPlus(node.parent) || (compiler.isParenthesizedExpression(node.parent) && node.parent.parent !== undefined && isPlus(node.parent.parent))))) {
|
|
614
|
+
const operands = [];
|
|
615
|
+
flatten(node, operands);
|
|
616
|
+
if (operands.some(one => compiler.isStringLiteralLike(one) || compiler.isTemplateExpression(one))) {
|
|
617
|
+
const parts = [""];
|
|
618
|
+
for (const one of operands) {
|
|
619
|
+
if (compiler.isStringLiteralLike(one))
|
|
620
|
+
parts[parts.length - 1] += one.text;
|
|
621
|
+
else if (compiler.isTemplateExpression(one)) {
|
|
622
|
+
parts[parts.length - 1] += one.head.text;
|
|
623
|
+
for (const span of one.templateSpans)
|
|
624
|
+
parts.push(span.literal.text);
|
|
625
|
+
}
|
|
626
|
+
else
|
|
627
|
+
parts.push("");
|
|
628
|
+
}
|
|
629
|
+
if (parts.length > 1 || operands.length > 1)
|
|
630
|
+
consider(node, parts);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
if (compiler.isStringLiteralLike(node))
|
|
634
|
+
consider(node, [node.text]);
|
|
635
|
+
else if (compiler.isTemplateExpression(node))
|
|
636
|
+
consider(node, [node.head.text, ...node.templateSpans.map(span => span.literal.text)]);
|
|
637
|
+
compiler.forEachChild(node, visit);
|
|
638
|
+
};
|
|
639
|
+
visit(source);
|
|
640
|
+
return found;
|
|
641
|
+
}
|
|
642
|
+
/** Whether this construct may stand in this file: its writer module for every table it writes, or the migration's own
|
|
643
|
+
* declarations in store.ts. An unreadable or bare construct stands only in a writer of some protected table. */
|
|
644
|
+
function learningAllowed(file, one) {
|
|
645
|
+
if ((LEARNING_MIGRATION[file] ?? []).some(name => one.inside.split(",").includes(name)))
|
|
646
|
+
return true;
|
|
647
|
+
if (one.tables.length > 0)
|
|
648
|
+
return one.tables.every(table => LEARNING_WRITERS[table].includes(file));
|
|
649
|
+
return Object.values(LEARNING_WRITERS).some(writers => writers.includes(file));
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Why a changed file writes a learning-loops table (or a decision merge or restore) from a place that may not, or null
|
|
653
|
+
* (docs/plans/learning-loops.md, Protection 4). Every changed JavaScript or TypeScript file but tests is parsed; a
|
|
654
|
+
* construct its writer module doesn't own is new unless the base's copy of that file already had exactly that text
|
|
655
|
+
* (counted by its sha256, as storeRewriteProblem does). A file that can't be read or parsed needs a person. It reads
|
|
656
|
+
* text the candidate wrote, never runs it: it raises the bar for review; a person reviewing these files is the backstop.
|
|
657
|
+
*/
|
|
658
|
+
export function learningWriteProblem(paths, candidate, base) {
|
|
659
|
+
const scanned = paths.filter(path => /\.(?:[cm]?[jt]sx?)$/.test(path) && !/\.test\.[cm]?[jt]sx?$/.test(path) && !path.startsWith("test/")
|
|
660
|
+
&& !path.startsWith("node_modules/") && !path.includes("/node_modules/"));
|
|
661
|
+
if (scanned.length === 0)
|
|
662
|
+
return null;
|
|
663
|
+
if (candidate === null || base === null)
|
|
664
|
+
return "the changed files can't be read";
|
|
665
|
+
const key = (text) => createHash("sha256").update(text).digest("hex");
|
|
666
|
+
for (const path of scanned) {
|
|
667
|
+
const bytes = candidate(path);
|
|
668
|
+
if (bytes === null)
|
|
669
|
+
return `${path} can't be read`;
|
|
670
|
+
const now = parse(path, bytes);
|
|
671
|
+
if ("problem" in now)
|
|
672
|
+
return now.problem;
|
|
673
|
+
const before = base(path);
|
|
674
|
+
const was = before === null ? null : parse(path, before);
|
|
675
|
+
const kept = new Map();
|
|
676
|
+
if (was !== null && !("problem" in was))
|
|
677
|
+
for (const one of learningConstructs(was.source))
|
|
678
|
+
kept.set(key(one.text), (kept.get(key(one.text)) ?? 0) + 1);
|
|
679
|
+
for (const one of learningConstructs(now.source)) {
|
|
680
|
+
if (learningAllowed(path, one))
|
|
681
|
+
continue;
|
|
682
|
+
const left = kept.get(key(one.text)) ?? 0;
|
|
683
|
+
if (left > 0) {
|
|
684
|
+
kept.set(key(one.text), left - 1);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
const what = one.unreadable ? "builds a learning-loops table's name around an expression in SQL" : one.bare ? "names a learning-loops table outside its writers" : "writes learning-loops rows outside their writers";
|
|
688
|
+
return `${path} ${what} (\`${one.text.slice(0, 80)}\`)`;
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
return null;
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Why a candidate needs a person for learning loops, or null: a required call dropped, a pinned body changed (strict
|
|
695
|
+
* pins included), or a changed file writing a protected table from outside its writers. With `manifest.base`, only what
|
|
696
|
+
* the base holds is required, as evalHookProblem does — except a strict pin whose file the base holds.
|
|
697
|
+
*/
|
|
698
|
+
export function learningHookProblem(manifest, changed) {
|
|
699
|
+
return evalHookProblem(manifest.candidate, manifest.base, LEARNING_HOOKS, LEARNING_PINNED) ?? learningWriteProblem(changed.paths, changed.candidate, changed.base);
|
|
700
|
+
}
|
package/dist/evals.js
CHANGED
|
@@ -27,6 +27,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
27
27
|
import { closeSync, constants, lstatSync, mkdirSync, mkdtempSync, openSync, readdirSync, realpathSync, rmSync, writeSync } from "node:fs";
|
|
28
28
|
import { dirname, join, relative, sep } from "node:path";
|
|
29
29
|
import { homedir, hostname } from "node:os";
|
|
30
|
+
import { sameMachineHost } from "./machine-identity.js";
|
|
30
31
|
import { fileURLToPath } from "node:url";
|
|
31
32
|
import { createRequire } from "node:module";
|
|
32
33
|
import { readVerifiedCommit, verifiedBlobs } from "./commit-extract.js";
|
|
@@ -840,7 +841,7 @@ export function recoverEvalCampaigns(store, now, options = {}) {
|
|
|
840
841
|
const out = [];
|
|
841
842
|
for (const campaign of store.handle.prepare("SELECT id, runner_host, runner_pid, runner_started FROM eval_campaign WHERE state = 'running' ORDER BY admitted_at").all()) {
|
|
842
843
|
const id = String(campaign["id"]);
|
|
843
|
-
if (String(campaign["runner_host"])
|
|
844
|
+
if (!sameMachineHost(String(campaign["runner_host"]), host)) {
|
|
844
845
|
out.push({ id, recovered: false, why: `its runner is on ${String(campaign["runner_host"])}` });
|
|
845
846
|
continue;
|
|
846
847
|
}
|