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
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/** Optional repository context. Callers admit project access; this module grants no authority.
|
|
2
2
|
* The index is disposable. Every read hashes the current bounded tracked source set;
|
|
3
3
|
* a missing/changed/unreadable index falls back to ordinary text search, never a task gate. */
|
|
4
|
-
import { checkoutGitSync } from './teardown-git.js';
|
|
4
|
+
import { checkoutGitSync, viewedGitSync } from './teardown-git.js';
|
|
5
5
|
import { createHash, randomUUID } from 'node:crypto';
|
|
6
6
|
import { existsSync, lstatSync, mkdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
7
7
|
import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
|
|
8
8
|
import { scanForSecrets } from './evidence.js';
|
|
9
|
+
import { compilerOptionsOf, extractFile, resolveAll } from './code-extract.js';
|
|
9
10
|
const VERSION = 1;
|
|
10
11
|
const MAX_FILES = 1200, MAX_FILE_BYTES = 256_000, MAX_SOURCE_BYTES = 12_000_000;
|
|
11
12
|
const CODE = /\.(?:[cm]?[jt]sx?)$/i;
|
|
@@ -15,31 +16,23 @@ const sha = (value) => createHash('sha256').update(value).digest('hex');
|
|
|
15
16
|
// Read-only and bounded, and always the hardened checkout git (a crew worktree is agent-written, and a page read or a
|
|
16
17
|
// lead's refresh reads it alike): no filter, hook, program or network, its config and info frozen in a view of the
|
|
17
18
|
// plane's own before they're inspected (teardown-git.ts checkoutGitSync).
|
|
18
|
-
|
|
19
|
+
// Bound to the project's record, so a pointer or commondir can't turn the read to another repository.
|
|
20
|
+
const git = (repo, args, binding) => checkoutGitSync(repo, args, { timeoutMs: 10_000, maxBuffer: 2_000_000, binding }).trimEnd();
|
|
19
21
|
const inside = (root, path) => { const r = relative(root, path); return r !== '..' && !r.startsWith('..' + sep) && !isAbsolute(r); };
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
const
|
|
27
|
-
const
|
|
28
|
-
if (!/^[a-f0-9]{40,64}$/.test(baseRevision) || git(repo, ['rev-parse', '--verify', `${baseRevision}^{commit}`]) !== baseRevision)
|
|
29
|
-
throw Error('The requested context base is unavailable.');
|
|
30
|
-
if (request.audience === 'crew' && baseRevision !== head)
|
|
31
|
-
throw Error('Crew context must start at its exact task base.');
|
|
32
|
-
const gitDir = realpathSync(git(repo, ['rev-parse', '--absolute-git-dir']));
|
|
33
|
-
if (request.project && realpathSync(git(repo, ['rev-parse', '--path-format=absolute', '--git-common-dir'])) !== realpathSync(git(request.project, ['rev-parse', '--path-format=absolute', '--git-common-dir'])))
|
|
34
|
-
throw Error('The worktree belongs to a different project.');
|
|
35
|
-
const files = git(repo, ['ls-files', '-z', '--cached']).split('\0').filter(Boolean).sort();
|
|
36
|
-
const sources = [], warnings = [];
|
|
22
|
+
/** Whether a tracked path is one the index reads, by its name alone. */
|
|
23
|
+
const skippedPath = (file) => !TEXT.test(file) || /(?:^|\/)(?:node_modules|vendor|dist|build|coverage|\.git)(?:\/|$)/.test(file);
|
|
24
|
+
const unsafePath = (file) => file.split('/').some(p => p === '..') || /[\x00-\x1f]/.test(file);
|
|
25
|
+
const unreadableText = (data, text) => data.length > MAX_FILE_BYTES || /[\x00-\x08\x0b\x0c\x0e-\x1f\x7f\ufffd]/u.test(text) || scanForSecrets(text).length > 0;
|
|
26
|
+
/** The working tree's tracked files, for a lead or a person looking at the project's own checkout. */
|
|
27
|
+
function workingTreeSources(repo, binding) {
|
|
28
|
+
const files = git(repo, ['ls-files', '-z', '--cached'], binding).split('\0').filter(Boolean).sort();
|
|
29
|
+
const sources = [];
|
|
37
30
|
let omitted = 0, omittedBytes = 0, bytes = 0;
|
|
38
31
|
for (const file of [...new Set(files)]) {
|
|
39
|
-
if (
|
|
32
|
+
if (skippedPath(file))
|
|
40
33
|
continue;
|
|
41
34
|
const path = resolve(repo, file);
|
|
42
|
-
if (!inside(repo, path) ||
|
|
35
|
+
if (!inside(repo, path) || unsafePath(file)) {
|
|
43
36
|
omitted++;
|
|
44
37
|
continue;
|
|
45
38
|
}
|
|
@@ -57,7 +50,7 @@ function capture(request) {
|
|
|
57
50
|
}
|
|
58
51
|
const data = readFileSync(path);
|
|
59
52
|
const text = data.toString('utf8');
|
|
60
|
-
if (data
|
|
53
|
+
if (unreadableText(data, text)) {
|
|
61
54
|
omitted++;
|
|
62
55
|
omittedBytes += data.length;
|
|
63
56
|
continue;
|
|
@@ -69,13 +62,91 @@ function capture(request) {
|
|
|
69
62
|
omitted++;
|
|
70
63
|
}
|
|
71
64
|
}
|
|
72
|
-
|
|
65
|
+
return { sources, omitted, omittedBytes };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* A crew's sources: the task base's tree and raw blobs, never the working tree an agent writes. A dirty tracked file,
|
|
69
|
+
* a symlink swapped in or a replace ref is not read; no filter runs on a raw blob.
|
|
70
|
+
*/
|
|
71
|
+
function baseCommitSources(repo, base, binding) {
|
|
72
|
+
const read = (args, maxBuffer, input) => {
|
|
73
|
+
const ran = viewedGitSync(repo, ['--no-replace-objects', ...args], { timeoutMs: 10_000, maxBuffer, binding, buffer: true, ...(input === undefined ? {} : { input }) });
|
|
74
|
+
if (ran.status !== 0)
|
|
75
|
+
throw Error('The task base could not be read.');
|
|
76
|
+
return ran.stdout;
|
|
77
|
+
};
|
|
78
|
+
const wanted = [];
|
|
79
|
+
let omitted = 0, omittedBytes = 0, bytes = 0;
|
|
80
|
+
for (const entry of read(['ls-tree', '-r', '-l', '-z', base], 16_000_000).toString('utf8').split('\0')) {
|
|
81
|
+
const match = /^(\d{6}) (\w+) ([0-9a-f]{40,64}) +(\d+|-)\t(.+)$/s.exec(entry);
|
|
82
|
+
if (!match)
|
|
83
|
+
continue;
|
|
84
|
+
const [, mode, kind, oid, sized, file] = match;
|
|
85
|
+
if (skippedPath(file))
|
|
86
|
+
continue;
|
|
87
|
+
const size = sized === '-' ? 0 : Number(sized);
|
|
88
|
+
if (unsafePath(file) || kind !== 'blob' || (mode !== '100644' && mode !== '100755')) {
|
|
89
|
+
omitted++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (wanted.length >= MAX_FILES || size > MAX_FILE_BYTES || bytes + size > MAX_SOURCE_BYTES) {
|
|
93
|
+
omitted++;
|
|
94
|
+
omittedBytes += size;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
wanted.push({ file, oid });
|
|
98
|
+
bytes += size;
|
|
99
|
+
}
|
|
100
|
+
wanted.sort((a, b) => (a.file < b.file ? -1 : a.file > b.file ? 1 : 0));
|
|
101
|
+
const sources = [];
|
|
102
|
+
if (wanted.length === 0)
|
|
103
|
+
return { sources, omitted, omittedBytes };
|
|
104
|
+
const out = read(['cat-file', '--batch'], MAX_SOURCE_BYTES + wanted.length * 128 + 4096, Buffer.from(wanted.map(w => w.oid).join('\n') + '\n'));
|
|
105
|
+
let at = 0;
|
|
106
|
+
for (const { file, oid } of wanted) {
|
|
107
|
+
const end = out.indexOf(0x0a, at);
|
|
108
|
+
const header = end < 0 ? null : /^([0-9a-f]{40,64}) blob (\d+)$/.exec(out.subarray(at, end).toString('latin1'));
|
|
109
|
+
if (end < 0 || header === null || header[1] !== oid)
|
|
110
|
+
throw Error('The task base could not be read.');
|
|
111
|
+
const size = Number(header[2]);
|
|
112
|
+
const data = out.subarray(end + 1, end + 1 + size);
|
|
113
|
+
at = end + 1 + size + 1;
|
|
114
|
+
const text = data.toString('utf8');
|
|
115
|
+
if (unreadableText(data, text)) {
|
|
116
|
+
omitted++;
|
|
117
|
+
omittedBytes += data.length;
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
sources.push({ file, text, sha256: sha(data) });
|
|
121
|
+
}
|
|
122
|
+
return { sources, omitted, omittedBytes };
|
|
123
|
+
}
|
|
124
|
+
function capture(request) {
|
|
125
|
+
const repo = realpathSync(request.repo);
|
|
126
|
+
if (repo !== resolve(request.repo))
|
|
127
|
+
throw Error('The project location changed.');
|
|
128
|
+
// The project's record, never the checkout's own pointer files: the admitted project, or the one a crew worktree
|
|
129
|
+
// belongs to. Only a person's local look at an unrecorded folder has none.
|
|
130
|
+
const binding = { repo: request.project ?? null };
|
|
131
|
+
if (realpathSync(git(repo, ['rev-parse', '--show-toplevel'], binding)) !== repo)
|
|
132
|
+
throw Error('Choose the project checkout root.');
|
|
133
|
+
const head = git(repo, ['rev-parse', '--verify', 'HEAD'], binding);
|
|
134
|
+
const baseRevision = request.baseRevision ?? head;
|
|
135
|
+
if (!/^[a-f0-9]{40,64}$/.test(baseRevision) || git(repo, ['rev-parse', '--verify', `${baseRevision}^{commit}`], binding) !== baseRevision)
|
|
136
|
+
throw Error('The requested context base is unavailable.');
|
|
137
|
+
if (request.audience === 'crew' && baseRevision !== head)
|
|
138
|
+
throw Error('Crew context must start at its exact task base.');
|
|
139
|
+
const gitDir = realpathSync(git(repo, ['rev-parse', '--absolute-git-dir'], binding));
|
|
140
|
+
const crew = request.audience === 'crew';
|
|
141
|
+
const { sources, omitted, omittedBytes } = crew ? baseCommitSources(repo, baseRevision, binding) : workingTreeSources(repo, binding);
|
|
142
|
+
if (git(repo, ['rev-parse', '--verify', 'HEAD'], binding) !== head)
|
|
73
143
|
throw Error('The checkout changed while context was being read.');
|
|
144
|
+
const warnings = [];
|
|
74
145
|
if (omitted)
|
|
75
146
|
warnings.push(`${omitted} source files were omitted because they were unavailable, excluded, sensitive or beyond the read limits.`);
|
|
76
147
|
warnings.push('Tracked files only. Static imports do not establish every runtime dependency or sufficient test coverage.');
|
|
77
148
|
const sourceHash = sha(JSON.stringify(sources.map(({ file, sha256 }) => [file, sha256])));
|
|
78
|
-
return { checkout: { repo, gitDir, head, baseRevision, sourceHash, source: 'working-tree' }, sources, omitted, omittedBytes, warnings };
|
|
149
|
+
return { checkout: { repo, gitDir, head, baseRevision, sourceHash, source: crew ? 'base-commit' : 'working-tree' }, sources, omitted, omittedBytes, warnings };
|
|
79
150
|
}
|
|
80
151
|
function cacheFile(root, checkout) {
|
|
81
152
|
const absolute = resolve(root);
|
|
@@ -135,62 +206,33 @@ async function buildIndex(snapshot) {
|
|
|
135
206
|
dir = dirname(dir);
|
|
136
207
|
}
|
|
137
208
|
}
|
|
138
|
-
let options = { module: compiler.ModuleKind.NodeNext, moduleResolution: compiler.ModuleResolutionKind.NodeNext, allowJs: true, resolveJsonModule: true };
|
|
139
209
|
const config = byPath.get(join(snapshot.checkout.repo, 'tsconfig.json'));
|
|
210
|
+
const { options, problem } = compilerOptionsOf(compiler, join(snapshot.checkout.repo, 'tsconfig.json'), config?.text, snapshot.checkout.repo);
|
|
140
211
|
const warnings = [];
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (parsed.config.extends)
|
|
147
|
-
warnings.push('Inherited tsconfig settings are not indexed; unresolved aliases remain omitted.');
|
|
148
|
-
}
|
|
149
|
-
else
|
|
150
|
-
warnings.push('TypeScript configuration could not be read; relative imports still work.');
|
|
151
|
-
}
|
|
212
|
+
if (problem === 'extends')
|
|
213
|
+
warnings.push('Inherited tsconfig settings are not indexed; unresolved aliases remain omitted.');
|
|
214
|
+
else if (problem === 'unreadable')
|
|
215
|
+
warnings.push('TypeScript configuration could not be read; relative imports still work.');
|
|
216
|
+
const resolveOne = resolveAll(compiler, snapshot.checkout.repo, byPath, directories, options, resolve);
|
|
152
217
|
const nodes = [], relationships = [];
|
|
153
218
|
let unresolved = 0, parseFailures = 0;
|
|
154
|
-
const host = {
|
|
155
|
-
fileExists: file => byPath.has(resolve(file)),
|
|
156
|
-
readFile: file => byPath.get(resolve(file))?.text,
|
|
157
|
-
directoryExists: dir => directories.has(resolve(dir)),
|
|
158
|
-
getCurrentDirectory: () => snapshot.checkout.repo,
|
|
159
|
-
realpath: file => resolve(file),
|
|
160
|
-
};
|
|
161
219
|
for (const source of snapshot.sources.filter(s => CODE.test(s.file))) {
|
|
162
220
|
const fileName = resolve(snapshot.checkout.repo, source.file);
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
221
|
+
const read = extractFile(compiler, fileName, source.text);
|
|
222
|
+
if (read.parseFailed)
|
|
165
223
|
parseFailures++;
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
224
|
+
for (const one of read.declarations)
|
|
225
|
+
if (nodes.length < MAX_NODES)
|
|
226
|
+
nodes.push({ file: source.file, name: one.name, line: one.line, endLine: one.endLine });
|
|
227
|
+
for (const one of read.specifiers) {
|
|
228
|
+
const resolved = resolveOne(one.specifier, fileName);
|
|
229
|
+
if (resolved === null) {
|
|
170
230
|
unresolved++;
|
|
171
|
-
|
|
231
|
+
continue;
|
|
172
232
|
}
|
|
173
233
|
if (relationships.length < MAX_EDGES)
|
|
174
|
-
relationships.push({ from: source.file, to: byPath.get(
|
|
175
|
-
}
|
|
176
|
-
const visit = (node) => {
|
|
177
|
-
if (nodes.length < MAX_NODES && (compiler.isFunctionDeclaration(node) || compiler.isClassDeclaration(node) || compiler.isInterfaceDeclaration(node) || compiler.isTypeAliasDeclaration(node) || compiler.isEnumDeclaration(node) || compiler.isMethodDeclaration(node) || compiler.isVariableDeclaration(node))) {
|
|
178
|
-
const name = node.name;
|
|
179
|
-
if (name && compiler.isIdentifier(name))
|
|
180
|
-
nodes.push({ file: source.file, name: name.text, line: line(node.getStart(tree)), endLine: line(node.end) });
|
|
181
|
-
}
|
|
182
|
-
if (compiler.isImportDeclaration(node) && compiler.isStringLiteral(node.moduleSpecifier))
|
|
183
|
-
relationship(node.moduleSpecifier.text, 'imports', node.getStart(tree));
|
|
184
|
-
if (compiler.isExportDeclaration(node) && node.moduleSpecifier && compiler.isStringLiteral(node.moduleSpecifier))
|
|
185
|
-
relationship(node.moduleSpecifier.text, 're-exports', node.getStart(tree));
|
|
186
|
-
if (compiler.isCallExpression(node) && node.arguments.length === 1 && compiler.isStringLiteral(node.arguments[0])) {
|
|
187
|
-
if (node.expression.kind === compiler.SyntaxKind.ImportKeyword)
|
|
188
|
-
relationship(node.arguments[0].text, 'dynamic-import', node.getStart(tree));
|
|
189
|
-
// require may be shadowed; do not present a guessed call as a proven relationship.
|
|
190
|
-
}
|
|
191
|
-
compiler.forEachChild(node, visit);
|
|
192
|
-
};
|
|
193
|
-
visit(tree);
|
|
234
|
+
relationships.push({ from: source.file, to: byPath.get(resolved).file, kind: one.kind, file: source.file, line: one.line, provenance: 'explicit' });
|
|
235
|
+
}
|
|
194
236
|
}
|
|
195
237
|
if (unresolved)
|
|
196
238
|
warnings.push(`${unresolved} external or unresolved imports were omitted.`);
|
|
@@ -25,6 +25,7 @@ import { dirname, join } from "node:path";
|
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
26
|
import { existsSync, readFileSync, realpathSync, readdirSync, statSync } from "node:fs";
|
|
27
27
|
import { bootIdentity, provenDeadByBootChange } from "./boot-identity.js";
|
|
28
|
+
import { sameMachineHost } from "./machine-identity.js";
|
|
28
29
|
import { containmentStatus, currentContainment } from "./containment.js";
|
|
29
30
|
import { daemonStatus } from "./daemon.js";
|
|
30
31
|
import { isAlive } from "./runner.js";
|
|
@@ -70,8 +71,8 @@ export async function recordRestartBaseline(inputs) {
|
|
|
70
71
|
.map(row => ({ run: Number(row["run"]), taskRef: Number(row["task_ref"]), requestedAt: String(row["requested_at"]), problem: store.stopQuiescenceProblem(Number(row["run"])) }));
|
|
71
72
|
const openRuns = raw.prepare("SELECT id, task_ref, role, runner, lease_id FROM run WHERE outcome IS NULL ORDER BY id").all()
|
|
72
73
|
.map(row => ({ id: Number(row["id"]), taskRef: Number(row["task_ref"]), role: String(row["role"]), runner: String(row["runner"]), leaseId: String(row["lease_id"]) }));
|
|
73
|
-
const witnesses = raw.prepare("SELECT run, pid, host, boot_id, containment, exited_at FROM run_process WHERE exited_at IS NULL ORDER BY id").all()
|
|
74
|
-
.map(row => ({ run: Number(row["run"]), pid: row["pid"] === null ? null : Number(row["pid"]), host: String(row["host"]), bootId: row["boot_id"] === null ? null : String(row["boot_id"]), containment: row["containment"] === null ? null : String(row["containment"]), open: true }));
|
|
74
|
+
const witnesses = raw.prepare("SELECT run, pid, host, boot_id, containment, exited_at, machine_id FROM run_process WHERE exited_at IS NULL ORDER BY id").all()
|
|
75
|
+
.map(row => ({ run: Number(row["run"]), pid: row["pid"] === null ? null : Number(row["pid"]), host: String(row["host"]), bootId: row["boot_id"] === null ? null : String(row["boot_id"]), containment: row["containment"] === null ? null : String(row["containment"]), open: true, machineId: row["machine_id"] == null ? null : String(row["machine_id"]) }));
|
|
75
76
|
return {
|
|
76
77
|
version: 2,
|
|
77
78
|
runnerName: inputs.runnerName ?? null,
|
|
@@ -123,7 +124,8 @@ export async function verifyRestartRecovery(inputs) {
|
|
|
123
124
|
const sameDatabase = baseline.version === 2 && baseline.database.identity === current.database.identity;
|
|
124
125
|
checks.push({ name: "database", ok: sameDatabase, detail: sameDatabase ? "same database file identity" : "database identity differs or baseline predates identity verification; no recovery mutations performed" });
|
|
125
126
|
// 1. Boot identity, as the OS reports it.
|
|
126
|
-
|
|
127
|
+
// This machine under any of its own names (machine-identity.ts): a reboot onto another network can move the hostname.
|
|
128
|
+
const sameHost = sameMachineHost(baseline.host, current.host);
|
|
127
129
|
const bootChanged = !sameHost || baseline.boot.id === null || current.boot.id === null
|
|
128
130
|
? null
|
|
129
131
|
: provenDeadByBootChange({ host: current.host, bootId: baseline.boot.id }, { host: current.host, bootId: current.boot.id });
|
|
@@ -153,7 +155,7 @@ export async function verifyRestartRecovery(inputs) {
|
|
|
153
155
|
}
|
|
154
156
|
const baselineBeats = new Map(baseline.database.runners.map(one => [one.name, one.heartbeatAt]));
|
|
155
157
|
const liveRunners = current.database.runners.filter(one => !one.retired);
|
|
156
|
-
const fresh = liveRunners.filter(one => one.host
|
|
158
|
+
const fresh = liveRunners.filter(one => sameMachineHost(one.host, current.host) && one.alive && (baseline.runnerName === null || one.name === baseline.runnerName) && baselineBeats.has(one.name) && one.heartbeatAt > baselineBeats.get(one.name));
|
|
157
159
|
checks.push({
|
|
158
160
|
name: "heartbeat",
|
|
159
161
|
ok: fresh.length > 0,
|
|
@@ -187,8 +189,8 @@ export async function verifyRestartRecovery(inputs) {
|
|
|
187
189
|
? `every run open at the baseline has ended (${baseline.database.openRuns.map(run => `#${run.id}`).join(", ")})`
|
|
188
190
|
: `still open from before: ${staleOpen.map(run => `#${run.id} (${run.role}, ${run.runner})`).join(", ")} — recovery has not run, or its lease is still live`,
|
|
189
191
|
});
|
|
190
|
-
const oldWitnesses = baseline.database.witnesses.filter(one => one.bootId !== null && current.boot.id !== null && one.host
|
|
191
|
-
const survivors = oldWitnesses.filter(one => !provenDeadByBootChange({ host: one.host, bootId: one.bootId }, { host:
|
|
192
|
+
const oldWitnesses = baseline.database.witnesses.filter(one => one.bootId !== null && current.boot.id !== null && sameMachineHost(one.host, current.host, one.machineId));
|
|
193
|
+
const survivors = oldWitnesses.filter(one => !provenDeadByBootChange({ host: one.host, bootId: one.bootId }, { host: one.host, bootId: current.boot.id }));
|
|
192
194
|
checks.push({
|
|
193
195
|
name: "old-boot-custody",
|
|
194
196
|
ok: bootChanged !== true || survivors.length === 0,
|
package/dist/result-acts.d.ts
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
/** The result page's acts (2026-10-02), chosen in one place:
|
|
2
|
-
* act, the one that resolves the result's state,
|
|
3
|
-
* most one outline act beside it. A result that can't be accepted says why
|
|
4
|
-
* in
|
|
1
|
+
/** The result page's acts (2026-10-02; docs/plans/plain-toolroll.md P1.3), chosen in one place: three verbs, each a
|
|
2
|
+
* relabel over the post it has always made. Exactly one ink act at most, the one that resolves the result's state,
|
|
3
|
+
* and never navigation; at most one outline act beside it. A result that can't be accepted says why in one line
|
|
4
|
+
* before them. Run checks stays in the Checks row, and a person's own checks in the Checks list: neither is an act here.
|
|
5
5
|
*
|
|
6
|
-
* everything met → Accept
|
|
7
|
-
* a "You check this one" unanswered → the
|
|
8
|
-
* a check you marked Not right →
|
|
9
|
-
* checks queued or running → Checks running
|
|
10
|
-
* checks didn't run, a check exists → Run checks, then Accept without checks
|
|
6
|
+
* everything met → Accept
|
|
7
|
+
* a "You check this one" unanswered → (the check itself, above), then Accept without your check
|
|
8
|
+
* a check you marked Not right → Send back, then Accept anyway
|
|
9
|
+
* checks queued or running → (Checks running, in its row), then Accept without checks
|
|
10
|
+
* checks didn't run, a check exists → (Run checks, in its row), then Accept without checks
|
|
11
11
|
* report and changes disagree,
|
|
12
|
-
* or the proof is refuted →
|
|
12
|
+
* or the proof is refuted → Send back, then Accept (as allowed);
|
|
13
13
|
* never Accept as ink, even with no form here
|
|
14
|
-
* a Needs you form resolves it → that form (Build again
|
|
15
|
-
* saved notes or a failing PR →
|
|
16
|
-
* a failed build → Retry (its note starts with what to change)
|
|
17
|
-
*
|
|
14
|
+
* a Needs you form resolves it → that form (Retry: Build again; Check again)
|
|
15
|
+
* saved notes or a failing PR → Send back (the revision from the notes, or the repair of the PR)
|
|
16
|
+
* a failed build → Retry (its note starts with what to change); never Accept: a failed build
|
|
17
|
+
* is not a result to accept
|
|
18
18
|
* a failed task's delivered result → Retry, then Accept anyway (in outline, with a reason) where it may be
|
|
19
|
-
* accepted
|
|
19
|
+
* accepted
|
|
20
20
|
*
|
|
21
|
-
* Accepting finishes the task in one request
|
|
22
|
-
*
|
|
23
|
-
* which act is ink; the page recomputes it as the person answers each check. */
|
|
21
|
+
* Accepting finishes the task in one request: the person's acceptance, when one is owed, and the completion
|
|
22
|
+
* together. This only decides which act is ink; the page recomputes it as the person answers each check. */
|
|
24
23
|
import type { ProofVerdict } from "./proof.js";
|
|
25
24
|
/** The words over the reason field an Accept of a refuted result requires. */
|
|
26
25
|
export declare const ACCEPT_NEEDS_REASON = "Accepting needs a reason";
|
|
@@ -37,7 +36,7 @@ export declare function evidenceProblemOf(verdict: ProofVerdict | null, reasons:
|
|
|
37
36
|
* when nothing refutes it (a person's acceptance settles it). The result page
|
|
38
37
|
* shows it before Request changes, its one ink act. */
|
|
39
38
|
export declare function cantAcceptYetOf(verdict: ProofVerdict | null, reasons: readonly string[], accepted: boolean): string | null;
|
|
40
|
-
export type ResultActKind = "retry" | "accept" | "accept-anyway" | "next-check" | "checks-running" | "run-checks" | "request-changes" | "rebuild" | "
|
|
39
|
+
export type ResultActKind = "retry" | "accept" | "accept-anyway" | "next-check" | "checks-running" | "run-checks" | "request-changes" | "rebuild" | "recheck-stopped" | "revise" | "draft-repair";
|
|
41
40
|
export type ResultActFacts = {
|
|
42
41
|
/** An Accept this page can post (Accept and finish, or the person's acceptance alone); `ready`: every requirement met and checks passed. */
|
|
43
42
|
accept: {
|
|
@@ -52,7 +51,7 @@ export type ResultActFacts = {
|
|
|
52
51
|
/** The feedback form is on this page. */
|
|
53
52
|
canRequest: boolean;
|
|
54
53
|
/** A Needs you form that resolves it here. */
|
|
55
|
-
need: "rebuild" | "
|
|
54
|
+
need: "rebuild" | "recheck-stopped" | null;
|
|
56
55
|
/** Saved notes ready to become a revision, or a failing pull request's repair. */
|
|
57
56
|
next: "revise" | "draft-repair" | null;
|
|
58
57
|
/** A failed build: `retry` when the task can be retried from here; `acceptAnyway` when it delivered a result that
|
|
@@ -74,12 +73,21 @@ export type ResultActs = {
|
|
|
74
73
|
/** The one line before the acts: why it can't be accepted yet. */
|
|
75
74
|
line: string | null;
|
|
76
75
|
};
|
|
76
|
+
/** The three verbs, and the posts each one makes (docs/plans/plain-toolroll.md P1.3): Accept is today's Accept (and
|
|
77
|
+
* finish), or Accept anyway with a reason; Send back is today's request for changes and its revision from saved notes
|
|
78
|
+
* for an unpublished result, or the repair of a published one; Retry is today's Retry, or Build again. Check again
|
|
79
|
+
* keeps its own words (its act stands on the build, not on the result). */
|
|
80
|
+
export type ResultVerb = "Accept" | "Send back" | "Retry";
|
|
81
|
+
export declare const RESULT_VERB: Readonly<Record<ResultActKind, ResultVerb | null>>;
|
|
82
|
+
/** What Send back does, said on its own confirm line: the rule is fixed by whether the result is already published. */
|
|
83
|
+
export declare const SEND_BACK_UNPUBLISHED = "Sends it back with your notes. The same task builds again once you approve the revision.";
|
|
84
|
+
export declare const SEND_BACK_PUBLISHED = "It's already published, so this files a fix as a new version for you to approve.";
|
|
77
85
|
export declare function resultActsOf(facts: ResultActFacts): ResultActs;
|
|
78
86
|
/** The words a result's Accept wears and the one line before it, given the
|
|
79
87
|
* server's words for everything but the person's own checks (`base`) and how
|
|
80
88
|
* those stand on this page: an unanswered check names itself; a check marked
|
|
81
89
|
* Not right says so; otherwise the base words stand. */
|
|
82
|
-
export type AcceptLabel = "Accept
|
|
90
|
+
export type AcceptLabel = "Accept" | "Accept without checks" | "Accept without your check" | "Accept anyway";
|
|
83
91
|
export declare function acceptWithChecksOf(base: {
|
|
84
92
|
label: AcceptLabel;
|
|
85
93
|
ready: boolean;
|
package/dist/result-acts.js
CHANGED
|
@@ -1,26 +1,25 @@
|
|
|
1
|
-
/** The result page's acts (2026-10-02), chosen in one place:
|
|
2
|
-
* act, the one that resolves the result's state,
|
|
3
|
-
* most one outline act beside it. A result that can't be accepted says why
|
|
4
|
-
* in
|
|
1
|
+
/** The result page's acts (2026-10-02; docs/plans/plain-toolroll.md P1.3), chosen in one place: three verbs, each a
|
|
2
|
+
* relabel over the post it has always made. Exactly one ink act at most, the one that resolves the result's state,
|
|
3
|
+
* and never navigation; at most one outline act beside it. A result that can't be accepted says why in one line
|
|
4
|
+
* before them. Run checks stays in the Checks row, and a person's own checks in the Checks list: neither is an act here.
|
|
5
5
|
*
|
|
6
|
-
* everything met → Accept
|
|
7
|
-
* a "You check this one" unanswered → the
|
|
8
|
-
* a check you marked Not right →
|
|
9
|
-
* checks queued or running → Checks running
|
|
10
|
-
* checks didn't run, a check exists → Run checks, then Accept without checks
|
|
6
|
+
* everything met → Accept
|
|
7
|
+
* a "You check this one" unanswered → (the check itself, above), then Accept without your check
|
|
8
|
+
* a check you marked Not right → Send back, then Accept anyway
|
|
9
|
+
* checks queued or running → (Checks running, in its row), then Accept without checks
|
|
10
|
+
* checks didn't run, a check exists → (Run checks, in its row), then Accept without checks
|
|
11
11
|
* report and changes disagree,
|
|
12
|
-
* or the proof is refuted →
|
|
12
|
+
* or the proof is refuted → Send back, then Accept (as allowed);
|
|
13
13
|
* never Accept as ink, even with no form here
|
|
14
|
-
* a Needs you form resolves it → that form (Build again
|
|
15
|
-
* saved notes or a failing PR →
|
|
16
|
-
* a failed build → Retry (its note starts with what to change)
|
|
17
|
-
*
|
|
14
|
+
* a Needs you form resolves it → that form (Retry: Build again; Check again)
|
|
15
|
+
* saved notes or a failing PR → Send back (the revision from the notes, or the repair of the PR)
|
|
16
|
+
* a failed build → Retry (its note starts with what to change); never Accept: a failed build
|
|
17
|
+
* is not a result to accept
|
|
18
18
|
* a failed task's delivered result → Retry, then Accept anyway (in outline, with a reason) where it may be
|
|
19
|
-
* accepted
|
|
19
|
+
* accepted
|
|
20
20
|
*
|
|
21
|
-
* Accepting finishes the task in one request
|
|
22
|
-
*
|
|
23
|
-
* which act is ink; the page recomputes it as the person answers each check. */
|
|
21
|
+
* Accepting finishes the task in one request: the person's acceptance, when one is owed, and the completion
|
|
22
|
+
* together. This only decides which act is ink; the page recomputes it as the person answers each check. */
|
|
24
23
|
/** The words over the reason field an Accept of a refuted result requires. */
|
|
25
24
|
export const ACCEPT_NEEDS_REASON = "Accepting needs a reason";
|
|
26
25
|
export const FAILED_CHECK = /^the repository's approved verification command exited (-?[0-9]+)/;
|
|
@@ -42,34 +41,46 @@ export function cantAcceptYetOf(verdict, reasons, accepted) {
|
|
|
42
41
|
? "Can't accept yet: the project's check failed on these changes."
|
|
43
42
|
: ACCEPT_NEEDS_REASON;
|
|
44
43
|
}
|
|
44
|
+
export const RESULT_VERB = {
|
|
45
|
+
accept: "Accept", "accept-anyway": "Accept",
|
|
46
|
+
"request-changes": "Send back", revise: "Send back", "draft-repair": "Send back",
|
|
47
|
+
retry: "Retry", rebuild: "Retry",
|
|
48
|
+
"recheck-stopped": null,
|
|
49
|
+
// Never in the act row: Run checks lives in the Checks row, and a person's own checks in the Checks list.
|
|
50
|
+
"next-check": null, "checks-running": null, "run-checks": null,
|
|
51
|
+
};
|
|
52
|
+
/** What Send back does, said on its own confirm line: the rule is fixed by whether the result is already published. */
|
|
53
|
+
export const SEND_BACK_UNPUBLISHED = "Sends it back with your notes. The same task builds again once you approve the revision.";
|
|
54
|
+
export const SEND_BACK_PUBLISHED = "It's already published, so this files a fix as a new version for you to approve.";
|
|
45
55
|
export function resultActsOf(facts) {
|
|
46
56
|
const request = facts.canRequest ? "request-changes" : null;
|
|
47
57
|
const accept = facts.accept === null ? null : "accept";
|
|
48
58
|
if (facts.failed != null) {
|
|
49
|
-
const checks = facts.runChecks && !facts.checksRunning ? "run-checks" : facts.checksRunning ? "checks-running" : null;
|
|
50
59
|
const anyway = facts.failed.acceptAnyway === true ? "accept-anyway" : null;
|
|
51
|
-
return facts.failed.retry ? { primary: "retry", secondary: anyway
|
|
60
|
+
return facts.failed.retry ? { primary: "retry", secondary: anyway, line: null } : { primary: null, secondary: anyway, line: null };
|
|
52
61
|
}
|
|
53
62
|
if (facts.need !== null)
|
|
54
63
|
return { primary: facts.need, secondary: request, line: null };
|
|
64
|
+
// Saved notes or a failing pull request: Send back is ink, and Accept the quiet other way.
|
|
55
65
|
if (facts.next !== null)
|
|
56
|
-
return { primary: facts.next, secondary: accept
|
|
66
|
+
return { primary: facts.next, secondary: accept, line: null };
|
|
57
67
|
// Can't be accepted yet: Accept is never the ink act, even when the feedback form isn't here.
|
|
58
68
|
if (facts.blocked !== null)
|
|
59
69
|
return { primary: request, secondary: accept, line: facts.blocked };
|
|
60
|
-
// The person's own checks come first
|
|
70
|
+
// The person's own checks come first, in the Checks list above: nothing here is ink until each is answered.
|
|
61
71
|
if (accept !== null && (facts.unanswered ?? 0) > 0)
|
|
62
|
-
return { primary:
|
|
72
|
+
return { primary: null, secondary: accept, line: null };
|
|
63
73
|
if (accept !== null && (facts.notRight ?? 0) > 0)
|
|
64
74
|
return { primary: request, secondary: accept, line: null };
|
|
75
|
+
// Checks running, or not run: their row says so and offers Run checks; Accept waits in outline.
|
|
65
76
|
if (facts.checksRunning)
|
|
66
|
-
return { primary:
|
|
77
|
+
return { primary: null, secondary: accept ?? request, line: null };
|
|
67
78
|
if (facts.accept === null)
|
|
68
79
|
return { primary: null, secondary: request, line: null };
|
|
69
80
|
if (facts.accept.ready)
|
|
70
81
|
return { primary: "accept", secondary: request, line: null };
|
|
71
82
|
if (facts.runChecks)
|
|
72
|
-
return { primary:
|
|
83
|
+
return { primary: null, secondary: "accept", line: null };
|
|
73
84
|
return { primary: "accept", secondary: request, line: null };
|
|
74
85
|
}
|
|
75
86
|
export function acceptWithChecksOf(base, checks) {
|
|
@@ -28,7 +28,7 @@ export type FollowUpCheck = {
|
|
|
28
28
|
head: string;
|
|
29
29
|
actor: string;
|
|
30
30
|
at: string;
|
|
31
|
-
why: "person" | "pull-request" | "batch";
|
|
31
|
+
why: "person" | "pull-request" | "batch" | "recheck";
|
|
32
32
|
/** The approved command's digest when checks were asked for: only that exact command runs. */
|
|
33
33
|
digest: string;
|
|
34
34
|
state: "waiting" | "running" | "passed" | "failed" | "not-run";
|
|
@@ -55,7 +55,7 @@ export declare function requestFollowUpChecks(store: Store, input: {
|
|
|
55
55
|
runId: number;
|
|
56
56
|
level: RunLevel;
|
|
57
57
|
actor: string;
|
|
58
|
-
why?: "person" | "pull-request";
|
|
58
|
+
why?: "person" | "pull-request" | "recheck";
|
|
59
59
|
}, now: Date): {
|
|
60
60
|
ok: true;
|
|
61
61
|
request: number;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { widerThan } from "./check-access.js";
|
|
2
|
+
import { recheckStanding } from "./check-only-standing.js";
|
|
3
|
+
import { actualCheckAccess, checkoutLaunchOf, checkoutRunner, ranAtLine, widestRan } from "./invoke.js";
|
|
2
4
|
import { mkdtempSync, rmSync } from "node:fs";
|
|
3
5
|
import { tmpdir } from "node:os";
|
|
4
6
|
import { join } from "node:path";
|
|
@@ -72,7 +74,7 @@ export function followUpChecksOf(store, runId, now = new Date(), root) {
|
|
|
72
74
|
const started = rows.find(row => row.action === CHECKS_STARTED && row.outcome === String(request.id)) ?? null;
|
|
73
75
|
const finished = rows.find(row => row.action === CHECKS_FINISHED && row.outcome.startsWith(`${request.id}:`)) ?? null;
|
|
74
76
|
const base = { request: request.id, runId, level: request.outcome, head, digest, actor: request.actor, at: request.at,
|
|
75
|
-
why: why === "pull-request" ? "pull-request" : why === "batch" ? "batch" : "person", exitCode: null, logArtifactId: null, note: null,
|
|
77
|
+
why: why === "pull-request" ? "pull-request" : why === "batch" ? "batch" : why === "recheck" ? "recheck" : "person", exitCode: null, logArtifactId: null, note: null,
|
|
76
78
|
...(why === "batch" ? { base: /^[a-f0-9]{40}$/.test(recordedBase) ? recordedBase : null } : {}) };
|
|
77
79
|
if (finished !== null) {
|
|
78
80
|
const status = finished.outcome.slice(String(request.id).length + 1);
|
|
@@ -200,6 +202,18 @@ export async function runFollowUpCheck(store, root, request, deps = {}) {
|
|
|
200
202
|
finish("not-run", "The result's commit changed after checks were asked for.");
|
|
201
203
|
return followUpChecksOf(store, runId, now(), root).find(one => one.request === request) ?? null;
|
|
202
204
|
}
|
|
205
|
+
// An automatic recheck runs only on its check-only authorization, read again now (check-only.ts): no record, no run.
|
|
206
|
+
const authorized = wanted.why === "recheck" ? recheckStanding(store, { request, runId, head: target.head, digest: wanted.digest }) : null;
|
|
207
|
+
if (wanted.why === "recheck" && !authorized?.ok) {
|
|
208
|
+
finish("not-run", authorized?.problem ?? "Its authorization couldn't be read.");
|
|
209
|
+
return followUpChecksOf(store, runId, now(), root).find(one => one.request === request) ?? null;
|
|
210
|
+
}
|
|
211
|
+
/** Every command of an automatic recheck, refused at the moment it would run wider than the build's own access. */
|
|
212
|
+
const launched = (inner) => !authorized?.ok ? inner : (file, args, options) => {
|
|
213
|
+
if (widerThan(actualCheckAccess(store, checkoutLaunchOf(store, target.repo, [runId]).access), authorized.access))
|
|
214
|
+
return Promise.resolve({ code: 126, stdout: "", stderr: "", notFound: true, timedOut: false });
|
|
215
|
+
return inner(file, args, options);
|
|
216
|
+
};
|
|
203
217
|
const folder = mkdtempSync(join(deps.scratch ?? tmpdir(), "toolroll-checks-"));
|
|
204
218
|
const checkout = join(folder, "checkout");
|
|
205
219
|
const log = [];
|
|
@@ -220,7 +234,7 @@ export async function runFollowUpCheck(store, root, request, deps = {}) {
|
|
|
220
234
|
let ready = true;
|
|
221
235
|
if (setup !== null) {
|
|
222
236
|
const shell = approvedCommandShell(setup.command);
|
|
223
|
-
const prepared = await runWithIsolatedDatabase(checkoutRunner(store, target.repo, checkout, runner, [runId], level => ranAt.push(level)), shell.file, shell.args, { cwd: checkout, timeoutMs: setup.timeoutMs, processGroup: true, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST });
|
|
237
|
+
const prepared = await runWithIsolatedDatabase(launched(checkoutRunner(store, target.repo, checkout, runner, [runId], level => ranAt.push(level))), shell.file, shell.args, { cwd: checkout, timeoutMs: setup.timeoutMs, processGroup: true, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST });
|
|
224
238
|
note("Approved project setup", setup.command, prepared);
|
|
225
239
|
if (prepared.code !== 0 || prepared.timedOut || prepared.notFound) {
|
|
226
240
|
ready = false;
|
|
@@ -235,7 +249,7 @@ export async function runFollowUpCheck(store, root, request, deps = {}) {
|
|
|
235
249
|
}
|
|
236
250
|
else {
|
|
237
251
|
const shell = approvedCommandShell(live.command);
|
|
238
|
-
const ran = await runWithIsolatedDatabase(checkoutRunner(store, target.repo, checkout, runner, [runId], level => ranAt.push(level)), shell.file, shell.args, { cwd: checkout, timeoutMs: live.timeoutMs, processGroup: true, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST });
|
|
252
|
+
const ran = await runWithIsolatedDatabase(launched(checkoutRunner(store, target.repo, checkout, runner, [runId], level => ranAt.push(level))), shell.file, shell.args, { cwd: checkout, timeoutMs: live.timeoutMs, processGroup: true, envAllowlist: SETUP_ENV_ALLOWLIST, omitEnv: SETUP_ENV_DENYLIST });
|
|
239
253
|
note(`${CHECK_LEVEL_WORDS[target.level]} check`, live.command, ran);
|
|
240
254
|
result = ran.timedOut ? { ran: false, failure: "The check ran out of time." } : ran.notFound ? { ran: false, failure: "The check couldn't start." } : { ran: true, exitCode: ran.code };
|
|
241
255
|
}
|
package/dist/result-review.d.ts
CHANGED
|
@@ -129,7 +129,7 @@ export type SharedResultFacts = {
|
|
|
129
129
|
* else from the run record — `headSource` says which. */
|
|
130
130
|
base: string | null;
|
|
131
131
|
head: string | null;
|
|
132
|
-
headSource: "
|
|
132
|
+
headSource: "saved diff" | "build record" | null;
|
|
133
133
|
/** Signed criteria passed, from the machine's matrix; null = no rubric. */
|
|
134
134
|
checks: {
|
|
135
135
|
passed: number;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Scope } from "./scope.js";
|
|
2
|
+
import type { Store } from "./store.js";
|
|
3
|
+
/** Whether a line on record for this exact plan describes some other route. An Approve under it is a yes to the wrong words. */
|
|
4
|
+
export declare function routeChangeProblem(store: Store, taskId: string, scope: Scope): boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/** The approval's one question about a "what changed" line (route-change.ts), apart so scope.ts loads it without the rest. */
|
|
2
|
+
import { routeDigestOf, routeFromJson } from "./phase-routing.js";
|
|
3
|
+
/** Whether a line on record for this exact plan describes some other route. An Approve under it is a yes to the wrong words. */
|
|
4
|
+
export function routeChangeProblem(store, taskId, scope) {
|
|
5
|
+
const ref = store.lookupRef(taskId);
|
|
6
|
+
if (ref === null)
|
|
7
|
+
return false;
|
|
8
|
+
const row = store.handle.prepare("SELECT to_route_digest FROM route_change WHERE task_ref = ? AND scope_digest = ?").get(ref.id, scope.digest);
|
|
9
|
+
if (row === undefined)
|
|
10
|
+
return false;
|
|
11
|
+
const route = routeFromJson(scope.proposedRouteJson ?? null);
|
|
12
|
+
return route === null || routeDigestOf(route) !== row["to_route_digest"];
|
|
13
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type PhaseRoute } from "./phase-routing.js";
|
|
2
|
+
import { type Scope } from "./scope.js";
|
|
3
|
+
import { type Store } from "./store.js";
|
|
4
|
+
/** The sentence for what changed between the seal and the proposal. */
|
|
5
|
+
export declare function routeChangeWords(sealed: {
|
|
6
|
+
route: PhaseRoute | null;
|
|
7
|
+
profile: Scope["approvedProfile"] | null | undefined;
|
|
8
|
+
authMode?: string | null;
|
|
9
|
+
}, next: {
|
|
10
|
+
route: PhaseRoute;
|
|
11
|
+
authMode?: string | null;
|
|
12
|
+
}): string;
|
|
13
|
+
/** File the same words on today's route, and keep the line. Null: nothing to propose (a release, nothing changed, or no
|
|
14
|
+
* route Toolroll can name), and the task stays as it is. It seals nothing. */
|
|
15
|
+
export declare function proposeNewRoute(store: Store, taskRef: number, taskId: string, now: Date): {
|
|
16
|
+
words: string;
|
|
17
|
+
} | null;
|
|
18
|
+
/** The line for this task's waiting plan, or null. Only while it describes exactly the plan up for approval. */
|
|
19
|
+
export declare function routeChangeOf(store: Store, taskId: string): string | null;
|
|
20
|
+
/** Each sweep: an unapproved row from before routing gets its route, and the same one Approve. A standing old approval
|
|
21
|
+
* is left alone (the stale check reaches it if its route ever differs). */
|
|
22
|
+
export declare function routeLegacyPlans(store: Store, now: Date): number;
|