toolroll 0.9.76 → 0.9.77

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +192 -0
  2. package/README.md +11 -4
  3. package/THIRD_PARTY_NOTICES.md +5 -5
  4. package/dist/agent-fence.js +28 -8
  5. package/dist/assignment.js +3 -0
  6. package/dist/browser/THIRD_PARTY_NOTICES.txt +5 -5
  7. package/dist/browser/workspace.css +2 -2
  8. package/dist/browser/workspace.js +31 -31
  9. package/dist/builder.d.ts +14 -0
  10. package/dist/builder.js +140 -41
  11. package/dist/chat-decide.js +4 -0
  12. package/dist/check-failure.d.ts +92 -0
  13. package/dist/check-failure.js +267 -0
  14. package/dist/check-progress.d.ts +28 -1
  15. package/dist/check-progress.js +105 -6
  16. package/dist/check-report.d.ts +52 -6
  17. package/dist/check-report.js +193 -8
  18. package/dist/checkout-cleanup.js +2 -2
  19. package/dist/cli.js +8 -5
  20. package/dist/coding-handoff.d.ts +1 -0
  21. package/dist/coding-handoff.js +20 -11
  22. package/dist/commit-extract.d.ts +6 -0
  23. package/dist/commit-extract.js +63 -8
  24. package/dist/console-chrome.d.ts +41 -0
  25. package/dist/console-chrome.js +166 -0
  26. package/dist/contracts/release-check.d.ts +12 -0
  27. package/dist/contracts/release-check.js +17 -0
  28. package/dist/contracts/verification-receipt.d.ts +20 -2
  29. package/dist/contracts/verification-receipt.js +15 -3
  30. package/dist/contracts/verification-view.d.ts +8 -0
  31. package/dist/controller-supervisor.d.ts +8 -0
  32. package/dist/controller-supervisor.js +33 -1
  33. package/dist/demo.js +3 -2
  34. package/dist/deploy-gate.js +5 -0
  35. package/dist/dispose.d.ts +3 -0
  36. package/dist/dispose.js +6 -3
  37. package/dist/doctor.d.ts +7 -0
  38. package/dist/doctor.js +10 -2
  39. package/dist/envelope.js +2 -1
  40. package/dist/evidence.js +4 -1
  41. package/dist/exec.d.ts +7 -0
  42. package/dist/exec.js +7 -1
  43. package/dist/git-floor.d.ts +16 -0
  44. package/dist/git-floor.js +36 -0
  45. package/dist/grant.js +5 -1
  46. package/dist/guides.js +4 -3
  47. package/dist/operate.d.ts +13 -0
  48. package/dist/operate.js +773 -319
  49. package/dist/prepared-evidence.d.ts +2 -1
  50. package/dist/prepared-evidence.js +8 -3
  51. package/dist/project-knowledge.js +6 -2
  52. package/dist/project-learning.js +2 -2
  53. package/dist/proof.js +6 -0
  54. package/dist/read-executor-worker.js +8 -4
  55. package/dist/read-executor.d.ts +18 -1
  56. package/dist/read-executor.js +8 -0
  57. package/dist/release-adapters.d.ts +48 -1
  58. package/dist/release-adapters.js +343 -10
  59. package/dist/release-authority.js +6 -2
  60. package/dist/release-auto.d.ts +38 -7
  61. package/dist/release-auto.js +131 -15
  62. package/dist/release-card.d.ts +15 -3
  63. package/dist/release-card.js +25 -9
  64. package/dist/release-ci-hold.d.ts +41 -0
  65. package/dist/release-ci-hold.js +53 -0
  66. package/dist/release-cli.js +21 -12
  67. package/dist/release-coverage.js +5 -2
  68. package/dist/release-recheck.d.ts +1 -1
  69. package/dist/release-recheck.js +6 -1
  70. package/dist/release.d.ts +108 -11
  71. package/dist/release.js +294 -39
  72. package/dist/review-context.js +8 -6
  73. package/dist/runner.js +2 -1
  74. package/dist/sandbox-runtime.d.ts +8 -0
  75. package/dist/sandbox-runtime.js +14 -2
  76. package/dist/scope.js +7 -1
  77. package/dist/secret-mint.d.ts +1 -0
  78. package/dist/secret-mint.js +14 -0
  79. package/dist/serve.js +101 -51
  80. package/dist/server/chat.js +5 -5
  81. package/dist/server/render-tasks.d.ts +2 -0
  82. package/dist/server/render-tasks.js +2 -1
  83. package/dist/server/request-context.d.ts +6 -0
  84. package/dist/server/runtime.d.ts +2 -0
  85. package/dist/server/task-views.js +7 -2
  86. package/dist/setup-guide.d.ts +2 -2
  87. package/dist/storage.d.ts +5 -0
  88. package/dist/storage.js +28 -1
  89. package/dist/store.d.ts +10 -0
  90. package/dist/store.js +25 -1
  91. package/dist/task-base.d.ts +4 -0
  92. package/dist/task-base.js +22 -2
  93. package/dist/teardown-git.d.ts +52 -3
  94. package/dist/teardown-git.js +143 -31
  95. package/dist/test-infra.js +1 -1
  96. package/dist/up-processes.d.ts +137 -0
  97. package/dist/up-processes.js +165 -0
  98. package/dist/up-role-child.d.ts +1 -0
  99. package/dist/up-role-child.js +77 -0
  100. package/dist/verification-evidence.d.ts +1 -0
  101. package/dist/verification-evidence.js +13 -1
  102. package/package.json +2 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,197 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.77 — 2026-10-29
4
+
5
+ - **CI finishes in about a third of the time.** The unit suite runs in four
6
+ shards per Node version instead of one 14–21 minute job. The required
7
+ checks keep their names ("node 22 · ubuntu-latest", "node 24 ·
8
+ ubuntu-latest") and pass only when every shard passed on the tested commit
9
+ and the shards together ran every test file once, so main's branch
10
+ protection needs no change. A flaky shard reruns in minutes, not twenty.
11
+ - **The release check's real-model journeys start sooner.** They run in the
12
+ browser lanes as soon as the scripted journeys end, instead of also waiting
13
+ for the unit suite; the check's load budget is unchanged.
14
+ - **An automatic release checks while CI runs.** With "Approve automatically
15
+ when checks pass" on, the release files its task and runs its check beside
16
+ CI instead of after it. Toolroll approves it only once every pull request
17
+ check passed on the commit.
18
+ - **A release asks for your approval while CI runs.** `toolroll release` files
19
+ the release task and asks you right away instead of waiting for GitHub's
20
+ checks; after you approve, the release check runs beside CI. The approval
21
+ says "GitHub's checks are still running; nothing deploys until they pass."
22
+ Nothing is deployed, merged, tagged or published until every required check
23
+ passed on the exact commit, read again right before the deploy; if one
24
+ fails, the release stops before the deploy. A check that already failed
25
+ still stops the release before anyone is asked.
26
+ - **The console stays responsive while builds and checks run.** `toolroll up`
27
+ now runs two processes: the console (pages, sign-in and approvals) and the
28
+ worker (builds, checks, follow-ups and housekeeping). On a copy of a real
29
+ database, the slowest console answer during a heavy check fell from about
30
+ 5–6 seconds to under 0.3 seconds. A worker that crashes is started again
31
+ and recovers its interrupted work as a restarted `up` does; a stop still
32
+ lets running builds finish first. A running check's progress is saved at
33
+ most once a second, and its final result at once. `--single-process` keeps
34
+ the old one-process layout.
35
+ - **Console pages open faster and stay quick while work runs.** The sidebar,
36
+ the Tasks count and the crew list are read once per database change for each
37
+ person instead of up to four times per page, and before a page is drawn they
38
+ are read on a background thread so the console keeps answering meanwhile.
39
+ The chat landing's lists are kept the same way, and the task page reuses its
40
+ "behind main" line for 15 seconds instead of asking Git twice per view. On a
41
+ copy of a real database: /board 348 → 26 ms, /agents 320 → 4 ms, /chat 905 →
42
+ 19 ms, a task page 1.5 s → 0.2 s; with the database changing 40 times a
43
+ second, pages take 0.27–0.47 s and the console answers other requests in
44
+ about 1 ms (before: 0.35 s typical, up to 1.4 s). A page that re-reads after
45
+ a live update gets "unchanged" when nothing it shows changed.
46
+ - **Fixes from the first review of the above.** CI's required checks now
47
+ judge the shards with the verifier from the base branch, run none of the
48
+ change's code, and fail when a shard executed no tests, its counts don't
49
+ add up, or it skips tests the base always ran; a required check counts only
50
+ from the CI workflow, never a same-named job elsewhere. A release filed
51
+ while CI runs can't be approved by anyone until Toolroll itself saw CI pass,
52
+ and you're asked only then. A worker that crashes while a removed project is
53
+ still finishing recovers that project's work before anything new starts. A
54
+ deploy waits until the console and the worker have both exited. A worker or
55
+ console that refuses to start again stops `toolroll up` instead of
56
+ restarting forever. A check's final progress is kept until it's saved.
57
+
58
+
59
+ - **Sandboxed commands no longer hang on macOS 26.5.** The fence denies most
60
+ macOS system services and, by default, asked macOS to report each denial; on
61
+ macOS 26.5.2 about one denied lookup in 70 never returned while that report
62
+ was made, so a sandboxed `npm ci`, `npm pack` or any Node program setting up
63
+ DNS could hang forever (a release carry until its 20-minute timeout). Denials
64
+ are now made without the report; nothing the fence allows or denies changed.
65
+ - **The console is fast again.** 0.9.76's release-approval setting was read by
66
+ walking a project's whole action ledger once per task row, and every page's
67
+ sidebar runs that list four times, so each page blocked the console for about
68
+ 3 seconds (more with tabs open while anything builds). It now reads the
69
+ setting rows through an existing index: pages went from about 2 s to about
70
+ 0.4 s on a real database. Sizing build checkouts every half hour no longer
71
+ freezes the console either.
72
+ - **A release check's result is always saved.** The release check's streamed
73
+ part lines now carry a tag no Toolroll version misreads as a journey's own
74
+ line; an older console counted them twice, built a progress snapshot it then
75
+ refused, and the refusal stopped the check's log and result from being saved
76
+ (the check read as "unavailable"). A progress update that fails to save can
77
+ no longer stop sealing, and a result that can't be saved says so on the run.
78
+ - **A renamed or dropped CI job no longer hides its failure.** Every failed
79
+ attempt on a commit is judged before newer runs replace older ones, so a
80
+ job a newer run renamed or dropped can't erase a test failure from an
81
+ automatic approval. The Windows containment repeat job keeps one name
82
+ whatever its count.
83
+ - **Toolroll decides what a Full release check owes.** Before the check runs,
84
+ Toolroll works out from its own record of the last release which parts it
85
+ owes (units, scripted and real-model journeys, upgrades), seals that, and
86
+ tells the check, which runs at least those. Every owed part needs a result,
87
+ whatever the check's report says it selected. CI never stands in for a
88
+ release's unit suite, and a journey or upgrade run that listed nothing it
89
+ ran is no result.
90
+ - **Missing evidence is unknown, not clean.** What passed only on a retry is
91
+ sealed in the check's receipt. If a check's report is lost or damaged, its
92
+ retries are unknown and nothing approves itself; the receipt's report and
93
+ inventory files are checked byte for byte. A check that passed only when
94
+ rerun after the approved setup counts as a retry, and the setup rerun only
95
+ happens when the check never started.
96
+ - **Merging checks main on GitHub itself.** A release reads main's head and
97
+ compares the branch through GitHub's API, never a local ref, and reads
98
+ main again right before merging. Releases approve themselves only while
99
+ main's branch protection requires branches to be up to date before
100
+ merging; without it, a person's release says which setting to turn on.
101
+ Toolroll only reads that setting.
102
+ - **No lazy fetch on any supported git.** Protected Git reads (the release's
103
+ object reader and extraction, project knowledge and learning, the task
104
+ base, release coverage, review screenshots, grants) run through a frozen
105
+ copy of the repository's config on every git version, refuse every
106
+ network transport and are time-bounded, so an include, a partial clone or
107
+ a planted filter can't fetch, run or hang anything. The deployment script
108
+ reads a commit's files with no filter or attribute applied. CI's oldest-git
109
+ lane now tests git 2.41.0 exactly.
110
+ - **Main's protection must actually hold.** A release now reads main's whole
111
+ branch protection and rulesets and counts them only when they require
112
+ branches to be up to date, require the release's CI checks, and can't be
113
+ bypassed by the account that merges ("Do not allow bypassing the above
114
+ settings"; no ruleset bypass for a role, team, app or organization admin).
115
+ A person's release names exactly what is missing, and says which new CI
116
+ checks main's protection doesn't require yet.
117
+ - **Deployment reads only verified objects, with none of the repository's
118
+ config.** The deployment script hashes the commit and every tree as well
119
+ as every file against their ids, and runs git on a copy of the
120
+ repository's layout with a config of its own, so a corrupted tree, a
121
+ config change mid-read or a custom transport helper can't substitute files
122
+ or fetch anything.
123
+ - **An unreadable last release never shrinks a check.** When the last
124
+ release Toolroll recorded can't be verified, the release check doesn't
125
+ run and says so, and the release card's change list reads "couldn't be
126
+ read", instead of acting as if there were no previous release.
127
+ - **Setup recovery needs proof the check never started.** The approved setup
128
+ reruns a check only when Toolroll itself found the check's program missing
129
+ before starting it and the shell said exactly that and nothing else. Any
130
+ other failure is the check's own result for a person to judge, and a
131
+ release after a setup rerun says why the first attempt failed.
132
+ - **Dispatch and coding handoffs can't be held up by a project's Git
133
+ config.** The worktree binding reads Git's registry as plain files, and the
134
+ handoff's branch read is bounded and runs outside its database
135
+ transactions.
136
+ - **A release check that's cut short still leaves its evidence.** Every
137
+ part of the release check (typecheck, build, units, journeys, upgrades)
138
+ now prints its output as it runs, each line tagged with its part (and
139
+ upgrade version), instead of only at the very end. Each part's end is
140
+ recorded with its start time, exit code, signal, timeout and whether it
141
+ was stopped, and a failing upgrade version's own log is printed the moment
142
+ it fails. Release check 3261 was stored as "exit 1" with nothing but its
143
+ header: a signal had ended it, the plane recorded the signal as a plain
144
+ exit 1, and the parts' output was still waiting to be printed. A command
145
+ ended by a signal, or by Toolroll's own shutdown, now says so.
146
+ - **No complete result, no pass.** A release's Full check counts only with
147
+ its valid report (carrying that run's secret), a result for every part,
148
+ unit file, journey group and upgrade version it owed, and a successful
149
+ exit. A missing, malformed, cut-short or incomplete report is an error
150
+ ("no result"), never a pass.
151
+ - **Failures are classed, and only Toolroll's own infrastructure is rerun.**
152
+ The flat "two flakes" allowance is gone. A failing test, typecheck, build,
153
+ attack test or upgrade is never rerun to pass: fix the branch. A failure
154
+ Toolroll itself confirms as infrastructure (the check never started, the
155
+ worker lost the build, Toolroll's shutdown stopped it, or a GitHub job
156
+ whose runner failed before any of its steps ran) is checked again on the
157
+ same commit under the same approval, at most once per incident and twice
158
+ per commit, with no new approval. An unexplained failure blocks: a
159
+ diagnostic rerun may run, but its pass doesn't erase the failure, so you
160
+ decide. Real-model journeys are counted, never rerun until they pass, and
161
+ one that passed only on a retry is now reported (an automatic approval
162
+ won't approve it). `toolroll release` says which of these happened and
163
+ what to do next, instead of always asking for `--new`.
164
+ - **A password that starts with "--" works on the command line.** About one
165
+ minted password in 4096 began with "--", and `toolroll release publish-ok`
166
+ (and other commands) read it as a missing value. A `--token` or
167
+ `--password` value is now always the next argument, `--token=<value>`
168
+ works everywhere, and new passwords, invites and tokens never start with
169
+ "-".
170
+ - **CI tests the commit a release trusts.** Every pull-request job now checks
171
+ out the pull request's own head commit, not the merge commit GitHub makes,
172
+ and proves it before building.
173
+ - **Git 2.41 or newer is required, and tested.** Toolroll's hardened git reads
174
+ need `git --attr-source`, which arrived in git 2.41. `toolroll up`, `serve`
175
+ and `watch` won't start on an older git, and `toolroll doctor` names it,
176
+ each with the installed and the required version. Git 2.41 to 2.44 has no
177
+ `--no-lazy-fetch`: there Toolroll leaves it out and instead refuses a
178
+ project that is a partial clone for protected reads ("this project is a
179
+ partial clone; protected reads need git 2.45 or newer"), judged from the
180
+ frozen copy of the config git then runs with (never checked, then run),
181
+ and every read still refuses all network protocols.
182
+ Two CI jobs run the git attack and worktree tests on git 2.41 and 2.47, as
183
+ an unprivileged user, fail if any test is skipped, and are now required
184
+ for a release.
185
+ - **A release branch must contain main.** CI tests the pull request's own
186
+ commit, so `toolroll release` refuses a branch that doesn't contain main's
187
+ head ("the branch is behind main; update it and push"), at the gate before
188
+ anything is filed and again right before the merge.
189
+ - **Tests can't lean on a guessed git identity.** Each test's home tells git
190
+ never to guess a name and email from the host, so a test that commits
191
+ without setting one fails on a Mac just as it would on Linux CI.
192
+ - **Two timing-sensitive tests now wait for events.** The read-worker test and
193
+ the watch recovery test no longer fail when a busy machine runs slowly.
194
+
3
195
  ## 0.9.76 — 2026-10-28
4
196
 
5
197
  - **Releases can approve themselves when they're green.** A new per-project
package/README.md CHANGED
@@ -34,7 +34,7 @@ npx toolroll up
34
34
  ```
35
35
 
36
36
  That starts the app and a builder for your projects, then opens the console
37
- at http://127.0.0.1:4180. Runs on macOS or Linux with Node.js 22.13+ and git.
37
+ at http://127.0.0.1:4180. Runs on macOS or Linux with Node.js 22.13+ and git 2.41+.
38
38
  Try the sandbox first with `npx toolroll demo`: ask its scripted lead for a
39
39
  change in Chat, approve the plan, and watch it land Ready. It never calls a
40
40
  model, spends or reaches outside.
@@ -275,9 +275,16 @@ npx toolroll up --project-root ~/Projects # or: bunx toolroll up
275
275
 
276
276
  That is the install and the setup. It needs Node 22.13 or newer on the
277
277
  machine (Bun's runtime has no `node:sqlite`; `bunx` hands the shebang to
278
- Node, so it works too). `npm install -g toolroll` gives you the
279
- bare `toolroll` command for later (`standing-orders`, the older name, still
280
- works).
278
+ Node, so it works too), and git 2.41 or newer: Toolroll reads agent-written
279
+ checkouts with `git --attr-source`, which older git refuses, and `toolroll
280
+ up`, `serve`, `watch` and `doctor` say so when the installed git is older.
281
+ Ubuntu 24.04 ships 2.43, which works; Debian 12 ships 2.39: install a newer
282
+ one there. On git older than 2.45 (no `git --no-lazy-fetch`), a project that
283
+ is a partial clone (`git clone --filter=…`) is refused for Toolroll's
284
+ protected reads: use git 2.45 or newer for those, or a full clone. CI runs
285
+ the git tests on 2.41 and on 2.47.
286
+ `npm install -g toolroll` gives you the bare `toolroll` command for later
287
+ (`standing-orders`, the older name, still works).
281
288
 
282
289
  `up` prints your login once (and saves it beside the database as
283
290
  `up-login.txt`), opens the app in your browser, and connects this machine as
@@ -247,14 +247,14 @@ limitations under the License.
247
247
 
248
248
  ## shadcn/ui — MIT
249
249
 
250
- Button and Dialog composition in `src/browser/ui/primitives.tsx` adapts:
250
+ Dialog composition in `src/browser/ui/primitives.tsx` adapts:
251
251
  https://github.com/shadcn-ui/ui/tree/a87a63b2ca25143d26c8bd0903e4e9bc77b3f824/apps/v4/registry/new-york-v4/ui
252
252
 
253
253
  Source inspected 20 September 2026, revision
254
- `a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`, files `button.tsx` and
255
- `dialog.tsx`. Adaptations replace Tailwind/CVA with local class variants, use
256
- individual Radix packages, default buttons to type=button, and leave dialog
257
- close controls to the calling surface. Radix supplies focus and dismissal behavior.
254
+ `a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`, file `dialog.tsx`. Adaptations
255
+ replace Tailwind/CVA with local class variants, use individual Radix packages,
256
+ and leave dialog close controls to the calling surface. Radix supplies focus
257
+ and dismissal behavior.
258
258
 
259
259
  The rebuilt views (23 September 2026) add `src/browser/components/ui/`:
260
260
  button, badge, card, tabs, radio-group, select, switch, label, input,
@@ -25,7 +25,7 @@ import { INSTANCE_DIR } from "./console-instance.js";
25
25
  import { ACP_AGENTS, ACP_PROVIDER_IDS } from "./acp-agents.js";
26
26
  import { spawnSync } from "node:child_process";
27
27
  import { createHash } from "node:crypto";
28
- import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
28
+ import { closeSync, constants as fsConstants, existsSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
29
29
  import { createRequire } from "node:module";
30
30
  import { fileURLToPath } from "node:url";
31
31
  import { homedir, tmpdir } from "node:os";
@@ -53,6 +53,22 @@ function inside(child, parent) {
53
53
  }
54
54
  export class CheckoutBindingError extends Error {
55
55
  }
56
+ /** One small file read only as the regular file it is: never through a link, never blocking on a FIFO; null otherwise. */
57
+ function regularText(path) {
58
+ let fd = null;
59
+ try {
60
+ fd = openSync(path, fsConstants.O_RDONLY | fsConstants.O_NOFOLLOW | fsConstants.O_NONBLOCK);
61
+ const held = fstatSync(fd);
62
+ return held.isFile() && held.size <= 64 * 1024 ? readFileSync(fd, "utf8") : null;
63
+ }
64
+ catch {
65
+ return null;
66
+ }
67
+ finally {
68
+ if (fd !== null)
69
+ closeSync(fd);
70
+ }
71
+ }
56
72
  export const HARDENED_GIT_ARGS = ["--no-optional-locks", "-c", "core.fsmonitor=false", "-c", "core.hooksPath=/dev/null"];
57
73
  const GIT_ENV_NAMES = ["PATH", "HOME", "TMPDIR", "TMP", "TEMP", "SystemRoot", "LANG", "LC_ALL", "CODEX_HOME"];
58
74
  export function checkoutGitEnv(base = process.env) {
@@ -86,18 +102,20 @@ export function trustedWorktreeBinding(repository, worktree, poolRoots = []) {
86
102
  }
87
103
  if (!lstatSync(join(main, ".git")).isDirectory() || !lstatSync(registry).isDirectory())
88
104
  throw fail();
89
- // Query the trusted checkout, never the candidate's configurable Git pointer.
90
- const listed = spawnSync("git", [...HARDENED_GIT_ARGS, "worktree", "list", "--porcelain", "-z"], { cwd: main, env: checkoutGitEnv(), encoding: "utf8" });
91
- if (listed.status !== 0 || !listed.stdout.split("\0").includes(`worktree ${checkout}`))
92
- throw fail();
105
+ // The trusted checkout's own worktree registry, never the candidate's configurable Git pointer — read as files, with
106
+ // no git at all (eval-harness review 2, finding 6): the repository's config is shared with every build, so an
107
+ // include naming a FIFO could hold a git read (and dispatch) up; a FIFO or link planted as a registry file is never
108
+ // opened as one (regularText).
93
109
  for (const entry of readdirSync(registry)) {
94
110
  const path = join(registry, entry);
95
111
  if (!lstatSync(path).isDirectory() || !inside(real(path), real(registry)))
96
112
  continue;
97
- if (real(readFileSync(join(path, "gitdir"), "utf8").trim()) !== join(checkout, ".git"))
113
+ const pointer = regularText(join(path, "gitdir"));
114
+ if (pointer === null || real(pointer.trim()) !== join(checkout, ".git"))
98
115
  continue;
99
116
  const gitdir = real(path);
100
- if (worktreeGitDir(checkout) !== gitdir || real(join(gitdir, readFileSync(join(gitdir, "commondir"), "utf8").trim())) !== real(join(main, ".git")))
117
+ const common = regularText(join(gitdir, "commondir"));
118
+ if (common === null || worktreeGitDir(checkout) !== gitdir || real(join(gitdir, common.trim())) !== real(join(main, ".git")))
101
119
  throw fail();
102
120
  return { main, gitdir };
103
121
  }
@@ -670,7 +688,9 @@ export function fencedGitEnv(base = process.env) {
670
688
  /** The repository a git worktree belongs to (its `.git` file names the main repository's git folder). */
671
689
  function worktreeGitDir(worktree) {
672
690
  try {
673
- const pointer = /^gitdir:\s*(.+)$/m.exec(readFileSync(join(worktree, ".git"), "utf8"))?.[1]?.trim();
691
+ // The candidate's own pointer file: a FIFO or link there is never opened (review 2, finding 6).
692
+ const text = regularText(join(worktree, ".git"));
693
+ const pointer = text === null ? undefined : /^gitdir:\s*(.+)$/m.exec(text)?.[1]?.trim();
674
694
  return pointer === undefined ? null : real(isAbsolute(pointer) ? pointer : join(worktree, pointer));
675
695
  }
676
696
  catch {
@@ -140,6 +140,9 @@ function buildChecks(store, root, runId) {
140
140
  if (receipt.result.failure === "coverage-incomplete")
141
141
  return { status: "failed", exitCode: null, command: receipt.command.command, logArtifactId: receipt.log.artifactId, level,
142
142
  detail: "Checks didn't cover everything this release owed, or a required test went missing." };
143
+ if (receipt.result.failure === "no-result")
144
+ return { status: "failed", exitCode: null, command: receipt.command.command, logArtifactId: receipt.log.artifactId, level,
145
+ detail: "Checks gave no complete result, so they can't count as a pass." };
143
146
  // The sealed receipt names the grant that ran: the quick one, or the full one.
144
147
  const sealedLevel = typeof receipt.command?.repo === "string" && receipt.command.repo.startsWith("quick:") ? "quick" : "full";
145
148
  const words = sealedLevel === "quick" ? "Quick checks" : "Checks";
@@ -247,14 +247,14 @@ limitations under the License.
247
247
 
248
248
  ## shadcn/ui — MIT
249
249
 
250
- Button and Dialog composition in `src/browser/ui/primitives.tsx` adapts:
250
+ Dialog composition in `src/browser/ui/primitives.tsx` adapts:
251
251
  https://github.com/shadcn-ui/ui/tree/a87a63b2ca25143d26c8bd0903e4e9bc77b3f824/apps/v4/registry/new-york-v4/ui
252
252
 
253
253
  Source inspected 20 September 2026, revision
254
- `a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`, files `button.tsx` and
255
- `dialog.tsx`. Adaptations replace Tailwind/CVA with local class variants, use
256
- individual Radix packages, default buttons to type=button, and leave dialog
257
- close controls to the calling surface. Radix supplies focus and dismissal behavior.
254
+ `a87a63b2ca25143d26c8bd0903e4e9bc77b3f824`, file `dialog.tsx`. Adaptations
255
+ replace Tailwind/CVA with local class variants, use individual Radix packages,
256
+ and leave dialog close controls to the calling surface. Radix supplies focus
257
+ and dismissal behavior.
258
258
 
259
259
  The rebuilt views (23 September 2026) add `src/browser/components/ui/`:
260
260
  button, badge, card, tabs, radio-group, select, switch, label, input,