create-walle 0.10.2 → 0.10.3

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 (488) hide show
  1. package/README.md +3 -1
  2. package/lib/release-schema.json +2 -2
  3. package/lib/runtime-closure-policy.json +8 -7
  4. package/package.json +1 -1
  5. package/template/coding-task-manager/api-prompts.js +45 -7
  6. package/template/coding-task-manager/bin/claude-hook.js +55 -1
  7. package/template/coding-task-manager/bin/ctm-relay-client.js +35 -12
  8. package/template/coding-task-manager/bin/land.js +2069 -0
  9. package/template/coding-task-manager/bin/memory-census.js +4 -2
  10. package/template/coding-task-manager/bin/memory-sync-verify.js +151 -0
  11. package/template/coding-task-manager/bin/probe-refusal-scope.js +530 -0
  12. package/template/coding-task-manager/db.js +156 -32
  13. package/template/coding-task-manager/lib/acp/acp-client.js +383 -0
  14. package/template/coding-task-manager/lib/acp/dsh-context-truth.js +176 -0
  15. package/template/coding-task-manager/lib/acp/dsh-launch-model.js +302 -0
  16. package/template/coding-task-manager/lib/acp/dsh-mcp-servers.js +194 -0
  17. package/template/coding-task-manager/lib/acp/dsh-prompt-lane.js +616 -0
  18. package/template/coding-task-manager/lib/acp/dsh-route-patch.js +308 -0
  19. package/template/coding-task-manager/lib/acp/dsh-session-runtime.js +522 -0
  20. package/template/coding-task-manager/lib/acp/dsh-transcript.js +395 -0
  21. package/template/coding-task-manager/lib/acp/dsh-usage-log.js +162 -0
  22. package/template/coding-task-manager/lib/acp/ndjson-stdio-transport.js +225 -0
  23. package/template/coding-task-manager/lib/adopted-host-redial.js +47 -11
  24. package/template/coding-task-manager/lib/adopted-launch-environment.js +123 -0
  25. package/template/coding-task-manager/lib/agent-auto-update-runtime.js +0 -3
  26. package/template/coding-task-manager/lib/agent-auto-update.js +26 -10
  27. package/template/coding-task-manager/lib/agent-bundle/build.js +657 -0
  28. package/template/coding-task-manager/lib/agent-bundle/codex-install.js +558 -0
  29. package/template/coding-task-manager/lib/agent-bundle/drift.js +120 -0
  30. package/template/coding-task-manager/lib/agent-bundle/gc.js +522 -0
  31. package/template/coding-task-manager/lib/agent-bundle/inventory.js +346 -0
  32. package/template/coding-task-manager/lib/agent-bundle/push.js +717 -0
  33. package/template/coding-task-manager/lib/agent-bundle/skill-transfer.js +724 -0
  34. package/template/coding-task-manager/lib/agent-bundle/spawn.js +119 -0
  35. package/template/coding-task-manager/lib/agent-bundle/store.js +568 -0
  36. package/template/coding-task-manager/lib/agent-capabilities.js +142 -12
  37. package/template/coding-task-manager/lib/agent-cli-cache.js +30 -5
  38. package/template/coding-task-manager/lib/agent-definitions.js +19 -0
  39. package/template/coding-task-manager/lib/agent-hooks-codex-toml.js +28 -16
  40. package/template/coding-task-manager/lib/agent-hooks-codex-workspace.js +9 -1
  41. package/template/coding-task-manager/lib/agent-hooks-installer.js +23 -8
  42. package/template/coding-task-manager/lib/agent-hooks-prune.js +37 -11
  43. package/template/coding-task-manager/lib/agent-hooks-shim.js +105 -0
  44. package/template/coding-task-manager/lib/agent-presets.js +84 -1
  45. package/template/coding-task-manager/lib/agent-upgrade-jobs.js +12 -1
  46. package/template/coding-task-manager/lib/agent-version-service.js +74 -11
  47. package/template/coding-task-manager/lib/all-sessions-query.js +6 -2
  48. package/template/coding-task-manager/lib/api/agent-bundles.js +436 -0
  49. package/template/coding-task-manager/lib/api/backup-retention.js +83 -0
  50. package/template/coding-task-manager/lib/api/conversation-sync-routes.js +124 -0
  51. package/template/coding-task-manager/lib/api/diagnostics.js +549 -69
  52. package/template/coding-task-manager/lib/api/fleet.js +118 -0
  53. package/template/coding-task-manager/lib/api/hub-bridge.js +243 -0
  54. package/template/coding-task-manager/lib/api/ingress.js +9 -1
  55. package/template/coding-task-manager/lib/api/landing.js +403 -0
  56. package/template/coding-task-manager/lib/api/machines.js +691 -2
  57. package/template/coding-task-manager/lib/api/memory-sync-routes.js +335 -0
  58. package/template/coding-task-manager/lib/api/misc-ops.js +8 -3
  59. package/template/coding-task-manager/lib/api/remote-sessions.js +1326 -0
  60. package/template/coding-task-manager/lib/api/session-attach.js +623 -0
  61. package/template/coding-task-manager/lib/api/session-messages.js +1384 -0
  62. package/template/coding-task-manager/lib/api/sessions.js +77 -51
  63. package/template/coding-task-manager/lib/api/setup-network.js +544 -16
  64. package/template/coding-task-manager/lib/api/setup-wizard-handlers.js +884 -0
  65. package/template/coding-task-manager/lib/api/setup.js +829 -10
  66. package/template/coding-task-manager/lib/api/walle-bridge.js +9 -1
  67. package/template/coding-task-manager/lib/attachment-reconciler-wiring.js +5 -2
  68. package/template/coding-task-manager/lib/auth-rate-limit.js +23 -0
  69. package/template/coding-task-manager/lib/auth-rules.js +292 -21
  70. package/template/coding-task-manager/lib/background-stop.js +369 -0
  71. package/template/coding-task-manager/lib/best-of-attempts.js +4 -1
  72. package/template/coding-task-manager/lib/best-of-launch-plan.js +52 -0
  73. package/template/coding-task-manager/lib/box-keep-awake.js +89 -0
  74. package/template/coding-task-manager/lib/broadcast-reason-stats.js +152 -0
  75. package/template/coding-task-manager/lib/broadcast-serialize.js +5 -0
  76. package/template/coding-task-manager/lib/build-info.js +4 -2
  77. package/template/coding-task-manager/lib/claude-model-truth.js +13 -2
  78. package/template/coding-task-manager/lib/claude-transcript-terminal-renderer.js +17 -13
  79. package/template/coding-task-manager/lib/client-diag-handler.js +64 -2
  80. package/template/coding-task-manager/lib/codex-app-server-client.js +14 -16
  81. package/template/coding-task-manager/lib/codex-configuration-history.js +55 -0
  82. package/template/coding-task-manager/lib/codex-harness-qualification.js +43 -20
  83. package/template/coding-task-manager/lib/codex-rollout-history.js +743 -0
  84. package/template/coding-task-manager/lib/coding-agent-models.js +27 -12
  85. package/template/coding-task-manager/lib/coding-cli-skill-installer.js +44 -14
  86. package/template/coding-task-manager/lib/coding-session-model-adapters.js +43 -2
  87. package/template/coding-task-manager/lib/coding-session-model-owner.js +1 -1
  88. package/template/coding-task-manager/lib/coding-session-model-public.js +7 -1
  89. package/template/coding-task-manager/lib/coding-session-model-runtime.js +23 -4
  90. package/template/coding-task-manager/lib/coding-session-model-scan.js +31 -0
  91. package/template/coding-task-manager/lib/coding-session-model-state.js +7 -0
  92. package/template/coding-task-manager/lib/conversation-freshness-diagnostics.js +280 -0
  93. package/template/coding-task-manager/lib/conversation-item-clock.js +261 -0
  94. package/template/coding-task-manager/lib/conversation-source-registry.js +5 -0
  95. package/template/coding-task-manager/lib/conversation-store-v2.js +44 -0
  96. package/template/coding-task-manager/lib/conversation-sync.js +355 -0
  97. package/template/coding-task-manager/lib/create/resolve-launch-params.js +96 -12
  98. package/template/coding-task-manager/lib/create/worker-role-profile.js +4 -4
  99. package/template/coding-task-manager/lib/create-intent-lease.js +51 -0
  100. package/template/coding-task-manager/lib/ctm-session-context-api.js +97 -6
  101. package/template/coding-task-manager/lib/db-domain-tables.js +1 -0
  102. package/template/coding-task-manager/lib/db-domains.js +1 -0
  103. package/template/coding-task-manager/lib/db-owner-dispatch-probe.js +32 -0
  104. package/template/coding-task-manager/lib/db-owner-request-counts.js +178 -0
  105. package/template/coding-task-manager/lib/db-owner-worker-client.js +68 -14
  106. package/template/coding-task-manager/lib/db-schema-source-files.js +5 -0
  107. package/template/coding-task-manager/lib/deferred-restore-attempt.js +6 -2
  108. package/template/coding-task-manager/lib/feature-manifest.js +4 -0
  109. package/template/coding-task-manager/lib/fleet-conversation-route.js +97 -0
  110. package/template/coding-task-manager/lib/fleet-fanout.js +78 -0
  111. package/template/coding-task-manager/lib/fleet-hub.js +762 -0
  112. package/template/coding-task-manager/lib/fleet-origin-follower.js +252 -0
  113. package/template/coding-task-manager/lib/fleet-resolver-effects.js +147 -0
  114. package/template/coding-task-manager/lib/fleet-resolver-tick.js +148 -0
  115. package/template/coding-task-manager/lib/fleet-resolver.js +454 -0
  116. package/template/coding-task-manager/lib/fleet-session-list-routes.js +114 -0
  117. package/template/coding-task-manager/lib/fleet-session-lists.js +672 -0
  118. package/template/coding-task-manager/lib/fleet-update.js +798 -0
  119. package/template/coding-task-manager/lib/fs-watcher-root-watch.js +141 -0
  120. package/template/coding-task-manager/lib/fs-watcher.js +58 -42
  121. package/template/coding-task-manager/lib/harness-history-transition.js +50 -9
  122. package/template/coding-task-manager/lib/headless-term-service.js +84 -8
  123. package/template/coding-task-manager/lib/hermetic-agent-launch-guard.js +10 -0
  124. package/template/coding-task-manager/lib/history-transition-read-op.js +175 -8
  125. package/template/coding-task-manager/lib/host-cpu-categories.js +310 -0
  126. package/template/coding-task-manager/lib/host-fseventsd-sampler.js +399 -0
  127. package/template/coding-task-manager/lib/host-handoff.js +16 -19
  128. package/template/coding-task-manager/lib/host-stats.js +150 -3
  129. package/template/coding-task-manager/lib/hosted-sync-worker.js +43 -320
  130. package/template/coding-task-manager/lib/hub-bridge-access.js +798 -0
  131. package/template/coding-task-manager/lib/hub-bridge-box.js +409 -0
  132. package/template/coding-task-manager/lib/hub-bridge-mcp.js +380 -0
  133. package/template/coding-task-manager/lib/hub-bridge-mesh.js +313 -0
  134. package/template/coding-task-manager/lib/hub-bridge-ops.js +721 -0
  135. package/template/coding-task-manager/lib/hub-bridge.js +398 -0
  136. package/template/coding-task-manager/lib/idle-notify.js +491 -0
  137. package/template/coding-task-manager/lib/instance-id-ownership-manifest.js +8 -0
  138. package/template/coding-task-manager/lib/instance-manifest.js +5 -1
  139. package/template/coding-task-manager/lib/instance-sync-status.js +18 -8
  140. package/template/coding-task-manager/lib/landing/agent-rule.txt +1 -0
  141. package/template/coding-task-manager/lib/landing/batch-runner.js +366 -0
  142. package/template/coding-task-manager/lib/landing/config.js +536 -0
  143. package/template/coding-task-manager/lib/landing/ctm-view.js +256 -0
  144. package/template/coding-task-manager/lib/landing/gate.js +360 -0
  145. package/template/coding-task-manager/lib/landing/git.js +344 -0
  146. package/template/coding-task-manager/lib/landing/lease.js +247 -0
  147. package/template/coding-task-manager/lib/landing/merge-delta.js +380 -0
  148. package/template/coding-task-manager/lib/landing/proof.js +223 -0
  149. package/template/coding-task-manager/lib/landing/queue.js +998 -0
  150. package/template/coding-task-manager/lib/landing/radar.js +489 -0
  151. package/template/coding-task-manager/lib/landing/resolve-cli.js +424 -0
  152. package/template/coding-task-manager/lib/landing/state.js +263 -0
  153. package/template/coding-task-manager/lib/landing/stop.js +49 -0
  154. package/template/coding-task-manager/lib/landing/sync-cli.js +475 -0
  155. package/template/coding-task-manager/lib/landing/sync.js +456 -0
  156. package/template/coding-task-manager/lib/landing/train.js +885 -0
  157. package/template/coding-task-manager/lib/landing/window.js +57 -0
  158. package/template/coding-task-manager/lib/launch-environment.js +121 -0
  159. package/template/coding-task-manager/lib/launch-model-override.js +36 -10
  160. package/template/coding-task-manager/lib/launch-plan-pool.js +2 -1
  161. package/template/coding-task-manager/lib/launch-plan-warm-cache.js +6 -0
  162. package/template/coding-task-manager/lib/lead-model-observer.js +57 -1
  163. package/template/coding-task-manager/lib/lead-model-options.js +160 -3
  164. package/template/coding-task-manager/lib/lead-model-restore-reconciliation.js +106 -24
  165. package/template/coding-task-manager/lib/lead-model-switch-runtime.js +352 -68
  166. package/template/coding-task-manager/lib/lead-model-switch.js +114 -10
  167. package/template/coding-task-manager/lib/lead-model-transition-runtime.js +14 -2
  168. package/template/coding-task-manager/lib/lead-model-transition-store.js +24 -6
  169. package/template/coding-task-manager/lib/lead-route-preflight.js +179 -0
  170. package/template/coding-task-manager/lib/lead-switch-history-mode.js +110 -0
  171. package/template/coding-task-manager/lib/lead-wire-agent.js +7 -0
  172. package/template/coding-task-manager/lib/lifecycle-wake-experiment.js +9 -2
  173. package/template/coding-task-manager/lib/live-defer-announce.js +45 -0
  174. package/template/coding-task-manager/lib/machine-registry.js +750 -5
  175. package/template/coding-task-manager/lib/macos-capabilities.js +32 -15
  176. package/template/coding-task-manager/lib/main-thread-lint-exemptions.json +433 -53
  177. package/template/coding-task-manager/lib/mainloop-observability.js +14 -9
  178. package/template/coding-task-manager/lib/media-draft-owner.js +16 -3
  179. package/template/coding-task-manager/lib/memory-join.js +337 -0
  180. package/template/coding-task-manager/lib/memory-sync-verify.js +393 -0
  181. package/template/coding-task-manager/lib/memory-sync.js +565 -0
  182. package/template/coding-task-manager/lib/migrations/v57-delegate-pref-mirror-opencode.js +68 -0
  183. package/template/coding-task-manager/lib/migrations/v58-subagent-launch-native-subagents.js +42 -0
  184. package/template/coding-task-manager/lib/migrations/v59-coding-session-model-state-dsh.js +73 -0
  185. package/template/coding-task-manager/lib/migrations/v60-delegate-pref-mirror-dsh.js +78 -0
  186. package/template/coding-task-manager/lib/migrations/v61-conversation-item-clock.js +71 -0
  187. package/template/coding-task-manager/lib/model-availability.js +727 -0
  188. package/template/coding-task-manager/lib/model-execution-recipe-metadata.js +4 -6
  189. package/template/coding-task-manager/lib/model-host-receipt.js +6 -3
  190. package/template/coding-task-manager/lib/model-id-rule.js +19 -0
  191. package/template/coding-task-manager/lib/model-proxy/anthropic-face.js +4 -0
  192. package/template/coding-task-manager/lib/model-proxy/auto-mode-review.js +110 -23
  193. package/template/coding-task-manager/lib/model-proxy/chat-response-translators.js +3 -0
  194. package/template/coding-task-manager/lib/model-proxy/generation-probe.js +60 -17
  195. package/template/coding-task-manager/lib/model-proxy/historical-tool-declarations.js +37 -0
  196. package/template/coding-task-manager/lib/model-proxy/lead-request-validation.js +1 -0
  197. package/template/coding-task-manager/lib/model-proxy/lead-token-admin.js +130 -0
  198. package/template/coding-task-manager/lib/model-proxy/refusal-scope.js +151 -0
  199. package/template/coding-task-manager/lib/model-proxy/request-budget-store.js +11 -2
  200. package/template/coding-task-manager/lib/model-proxy/tokens.js +69 -5
  201. package/template/coding-task-manager/lib/model-proxy/upstream-outcome.js +9 -0
  202. package/template/coding-task-manager/lib/native-agent-model-args.js +25 -22
  203. package/template/coding-task-manager/lib/native-history-control.js +6 -4
  204. package/template/coding-task-manager/lib/native-lead-settings-client.js +44 -4
  205. package/template/coding-task-manager/lib/oauth-proxy-supervisor.js +173 -36
  206. package/template/coding-task-manager/lib/online-backup-worker-client.js +2 -0
  207. package/template/coding-task-manager/lib/opencode-cli.js +480 -0
  208. package/template/coding-task-manager/lib/opencode-launch-model.js +471 -0
  209. package/template/coding-task-manager/lib/opencode-v1-plugin/model-carrier.mjs +35 -0
  210. package/template/coding-task-manager/lib/opencode-v2-plugin/server.js +118 -0
  211. package/template/coding-task-manager/lib/owner-action-manifest-data.json +1 -1
  212. package/template/coding-task-manager/lib/park-composer-draft.js +54 -0
  213. package/template/coding-task-manager/lib/park-fresh-relaunch.js +41 -10
  214. package/template/coding-task-manager/lib/perf-phase-trace.js +72 -0
  215. package/template/coding-task-manager/lib/perf-summary.js +7 -0
  216. package/template/coding-task-manager/lib/perf-tracker.js +63 -2
  217. package/template/coding-task-manager/lib/project-config.js +7 -0
  218. package/template/coding-task-manager/lib/prompt-purge-v2.js +52 -1
  219. package/template/coding-task-manager/lib/provider-login-broker.js +72 -15
  220. package/template/coding-task-manager/lib/provider-login-tab.js +10 -2
  221. package/template/coding-task-manager/lib/read-pool-client.js +89 -2
  222. package/template/coding-task-manager/lib/read-pool-worker-handle.js +29 -4
  223. package/template/coding-task-manager/lib/recent-catalog-query.js +15 -0
  224. package/template/coding-task-manager/lib/recent-session-catalog-response.js +67 -10
  225. package/template/coding-task-manager/lib/recovery-evidence.js +26 -11
  226. package/template/coding-task-manager/lib/relay-client.js +57 -3
  227. package/template/coding-task-manager/lib/relink-audit.js +34 -5
  228. package/template/coding-task-manager/lib/remote-machine-catalog.js +321 -26
  229. package/template/coding-task-manager/lib/remote-machine-create.js +576 -36
  230. package/template/coding-task-manager/lib/remote-machine-link.js +546 -59
  231. package/template/coding-task-manager/lib/remote-machine-proxy.js +143 -9
  232. package/template/coding-task-manager/lib/remote-session-forward.js +65 -7
  233. package/template/coding-task-manager/lib/remote-terminal-target.js +9 -2
  234. package/template/coding-task-manager/lib/restart-guard.js +18 -6
  235. package/template/coding-task-manager/lib/restore-seed-modes.js +87 -0
  236. package/template/coding-task-manager/lib/restored-scrollback-hydration-runtime.js +22 -2
  237. package/template/coding-task-manager/lib/resume-admission.js +17 -3
  238. package/template/coding-task-manager/lib/review-repo-registry.js +58 -19
  239. package/template/coding-task-manager/lib/root-delivery-runtime.js +2 -2
  240. package/template/coding-task-manager/lib/root-prompt-flow.js +1 -1
  241. package/template/coding-task-manager/lib/runtime-contract.js +3 -1
  242. package/template/coding-task-manager/lib/schema-indexes.js +1 -0
  243. package/template/coding-task-manager/lib/session-context-locator.js +742 -0
  244. package/template/coding-task-manager/lib/session-create/attachment.js +2 -2
  245. package/template/coding-task-manager/lib/session-create/create-phases.js +118 -0
  246. package/template/coding-task-manager/lib/session-create/delegate-worker.js +86 -16
  247. package/template/coding-task-manager/lib/session-create/dsh-session.js +930 -0
  248. package/template/coding-task-manager/lib/session-create/lead-route-launch.js +186 -13
  249. package/template/coding-task-manager/lib/session-create/native-lead-lifetime.js +55 -3
  250. package/template/coding-task-manager/lib/session-create/opencode-lead-provider.js +50 -0
  251. package/template/coding-task-manager/lib/session-create/prepared.js +162 -38
  252. package/template/coding-task-manager/lib/session-create/provider-process.js +160 -28
  253. package/template/coding-task-manager/lib/session-create/response.js +7 -7
  254. package/template/coding-task-manager/lib/session-create/structured-session.js +27 -0
  255. package/template/coding-task-manager/lib/session-create/subagent-pick-table.js +3 -1
  256. package/template/coding-task-manager/lib/session-create/subagent-pick.js +458 -9
  257. package/template/coding-task-manager/lib/session-create/transcript.js +1 -1
  258. package/template/coding-task-manager/lib/session-create/worker-budget-setup.js +21 -2
  259. package/template/coding-task-manager/lib/session-create/worker-continuation.js +7 -1
  260. package/template/coding-task-manager/lib/session-create/worker-launch-sandbox.js +39 -10
  261. package/template/coding-task-manager/lib/session-create/worker-observability.js +2 -2
  262. package/template/coding-task-manager/lib/session-empty.js +13 -10
  263. package/template/coding-task-manager/lib/session-host-manager.js +60 -0
  264. package/template/coding-task-manager/lib/session-jobs.js +3 -1
  265. package/template/coding-task-manager/lib/session-lifecycle.js +10 -4
  266. package/template/coding-task-manager/lib/session-model-action-preconditions.js +6 -2
  267. package/template/coding-task-manager/lib/session-model-intent-contract.js +11 -8
  268. package/template/coding-task-manager/lib/session-park.js +738 -0
  269. package/template/coding-task-manager/lib/session-path-grant.js +13 -4
  270. package/template/coding-task-manager/lib/session-status-ledger.js +106 -4
  271. package/template/coding-task-manager/lib/session-transport.js +17 -0
  272. package/template/coding-task-manager/lib/session-workspace-projection.js +54 -5
  273. package/template/coding-task-manager/lib/session-workspace-tracking-runtime.js +213 -1
  274. package/template/coding-task-manager/lib/skill-autocomplete-query.js +120 -3
  275. package/template/coding-task-manager/lib/skill-autocomplete.js +15 -1
  276. package/template/coding-task-manager/lib/sqlite-file-set.js +39 -0
  277. package/template/coding-task-manager/lib/standup-search-adapter.js +16 -2
  278. package/template/coding-task-manager/lib/state-sync/client-ledger.js +12 -0
  279. package/template/coding-task-manager/lib/state-sync/frame-emitter.js +319 -100
  280. package/template/coding-task-manager/lib/storage/coding-session-model-store.js +4 -1
  281. package/template/coding-task-manager/lib/storage/delegate-pref-mirror-store.js +32 -9
  282. package/template/coding-task-manager/lib/structured-session-boot-sweep.js +235 -0
  283. package/template/coding-task-manager/lib/submission-ack.js +90 -2
  284. package/template/coding-task-manager/lib/systemd-scope.js +115 -0
  285. package/template/coding-task-manager/lib/telemetry-receiver.js +39 -0
  286. package/template/coding-task-manager/lib/terminal-attach-geometry.js +985 -0
  287. package/template/coding-task-manager/lib/terminal-bootstrap.js +30 -0
  288. package/template/coding-task-manager/lib/terminal-draft-operation.js +357 -0
  289. package/template/coding-task-manager/lib/terminal-input-admission.js +17 -1
  290. package/template/coding-task-manager/lib/terminal-input-prepared.js +563 -0
  291. package/template/coding-task-manager/lib/transcript-cache-temps.js +171 -0
  292. package/template/coding-task-manager/lib/transcript-frame-cache.js +8 -3
  293. package/template/coding-task-manager/lib/transcript-source.js +36 -4
  294. package/template/coding-task-manager/lib/transcript-store.js +9 -0
  295. package/template/coding-task-manager/lib/typing-interaction-trace.js +106 -3
  296. package/template/coding-task-manager/lib/usage-ledger/backfill.js +40 -5
  297. package/template/coding-task-manager/lib/usage-ledger/client.js +4 -1
  298. package/template/coding-task-manager/lib/usage-ledger/dsh-parse.js +152 -0
  299. package/template/coding-task-manager/lib/usage-ledger/dsh-read.js +73 -0
  300. package/template/coding-task-manager/lib/usage-ledger/ingest.js +34 -6
  301. package/template/coding-task-manager/lib/usage-ledger/live.js +35 -8
  302. package/template/coding-task-manager/lib/usage-ledger/read.js +29 -20
  303. package/template/coding-task-manager/lib/usage-ledger/schema.js +61 -1
  304. package/template/coding-task-manager/lib/usage-ledger/task-coordinator.js +4 -2
  305. package/template/coding-task-manager/lib/wake-skip-reasons.js +1 -0
  306. package/template/coding-task-manager/lib/walle/turn.js +1 -1
  307. package/template/coding-task-manager/lib/walle-history-serve.js +838 -0
  308. package/template/coding-task-manager/lib/walle-kill-authority.js +2 -2
  309. package/template/coding-task-manager/lib/walle-log-rotation.js +164 -1
  310. package/template/coding-task-manager/lib/walle-mcp-auto-config.js +56 -2
  311. package/template/coding-task-manager/lib/walle-row-history.js +495 -0
  312. package/template/coding-task-manager/lib/walle-supervisor.js +33 -14
  313. package/template/coding-task-manager/lib/watch-worker-client.js +203 -0
  314. package/template/coding-task-manager/lib/worker-terminal-output.js +5 -2
  315. package/template/coding-task-manager/lib/ws/hub-bridge.js +35 -0
  316. package/template/coding-task-manager/lib/ws/lifecycle.js +6 -11
  317. package/template/coding-task-manager/lib/ws/misc.js +1 -1
  318. package/template/coding-task-manager/lib/ws/state-sync.js +106 -4
  319. package/template/coding-task-manager/lib/ws/terminal-attach.js +91 -0
  320. package/template/coding-task-manager/oauth-proxy.js +112 -115
  321. package/template/coding-task-manager/package-lock.json +10 -94
  322. package/template/coding-task-manager/package.json +4 -3
  323. package/template/coding-task-manager/protocol/message-table.json +10 -0
  324. package/template/coding-task-manager/protocol/messages.js +10 -0
  325. package/template/coding-task-manager/protocol/ws-schema.json +20 -0
  326. package/template/coding-task-manager/protocol/ws-schema.md +2 -0
  327. package/template/coding-task-manager/public/css/app.css +170 -0
  328. package/template/coding-task-manager/public/css/walle-session.css +4 -0
  329. package/template/coding-task-manager/public/index.html +473 -243
  330. package/template/coding-task-manager/public/js/agent-command.js +86 -0
  331. package/template/coding-task-manager/public/js/app/backups-panel.js +111 -5
  332. package/template/coding-task-manager/public/js/app/coding-agent-upgrade.js +103 -4
  333. package/template/coding-task-manager/public/js/app/machines-page.js +1328 -8
  334. package/template/coding-task-manager/public/js/app/models-tab.js +113 -38
  335. package/template/coding-task-manager/public/js/app/restart-banner.js +53 -0
  336. package/template/coding-task-manager/public/js/app/worktrees-panel.js +184 -2
  337. package/template/coding-task-manager/public/js/background-work.js +196 -27
  338. package/template/coding-task-manager/public/js/coding-session-model-presentation.js +138 -10
  339. package/template/coding-task-manager/public/js/core/active-session-groups.js +179 -0
  340. package/template/coding-task-manager/public/js/core/pending-create.js +238 -0
  341. package/template/coding-task-manager/public/js/core/recent-machine-source.js +228 -0
  342. package/template/coding-task-manager/public/js/core/session-commands.js +180 -14
  343. package/template/coding-task-manager/public/js/dashboard-client-function-order.json +8 -0
  344. package/template/coding-task-manager/public/js/document-review-links.js +9 -1
  345. package/template/coding-task-manager/public/js/file-resolution-client.js +52 -1
  346. package/template/coding-task-manager/public/js/history-app-seam.js +22 -2
  347. package/template/coding-task-manager/public/js/machine-pool-shared.js +558 -3
  348. package/template/coding-task-manager/public/js/message-renderer.js +4 -3
  349. package/template/coding-task-manager/public/js/model-picker.js +3 -1
  350. package/template/coding-task-manager/public/js/native-subagent-review.js +15 -2
  351. package/template/coding-task-manager/public/js/new-session-agent-grid.js +346 -0
  352. package/template/coding-task-manager/public/js/pasted-content.js +236 -0
  353. package/template/coding-task-manager/public/js/pe-private-content-lifetime.js +39 -7
  354. package/template/coding-task-manager/public/js/private-model-lifetime.js +6 -2
  355. package/template/coding-task-manager/public/js/prompts.js +11 -10
  356. package/template/coding-task-manager/public/js/protocol.js +10 -0
  357. package/template/coding-task-manager/public/js/restart-banner-policy.js +29 -1
  358. package/template/coding-task-manager/public/js/session-model-picker.js +447 -57
  359. package/template/coding-task-manager/public/js/session-search-utils.js +18 -2
  360. package/template/coding-task-manager/public/js/session-usage-view.js +2 -1
  361. package/template/coding-task-manager/public/js/session-workspace-tracking.js +51 -1
  362. package/template/coding-task-manager/public/js/state-sync-client.js +10 -1
  363. package/template/coding-task-manager/public/js/stream-view.js +6 -5
  364. package/template/coding-task-manager/public/js/terminal/conv-only-session.js +287 -0
  365. package/template/coding-task-manager/public/js/terminal/runtime-09.js +2 -0
  366. package/template/coding-task-manager/public/js/terminal/runtime-10.js +5 -5
  367. package/template/coding-task-manager/public/js/terminal/runtime-12.js +8 -1
  368. package/template/coding-task-manager/public/js/terminal/runtime-16.js +4 -0
  369. package/template/coding-task-manager/public/js/terminal/runtime-20.js +1 -1
  370. package/template/coding-task-manager/public/js/terminal-attachment-draft.js +67 -13
  371. package/template/coding-task-manager/public/js/terminal-image-orchestration.js +52 -6
  372. package/template/coding-task-manager/public/js/terminal-wheel.js +23 -3
  373. package/template/coding-task-manager/public/js/walle/chat-conversation.js +4 -4
  374. package/template/coding-task-manager/public/js/walle.js +6 -0
  375. package/template/coding-task-manager/public/js/ws/live-dispatch.js +10 -0
  376. package/template/coding-task-manager/public/m/app.css +140 -0
  377. package/template/coding-task-manager/public/m/app.js +507 -68
  378. package/template/coding-task-manager/public/m/index.html +17 -0
  379. package/template/coding-task-manager/public/m/machines.js +838 -4
  380. package/template/coding-task-manager/public/m/private-api.js +31 -1
  381. package/template/coding-task-manager/public/usage.html +1 -0
  382. package/template/coding-task-manager/scripts/dev-route-fixture/synthetic-route-upstream.js +104 -0
  383. package/template/coding-task-manager/scripts/dev-route-fixture.js +339 -0
  384. package/template/coding-task-manager/scripts/opencode-lead-route-probe.js +195 -0
  385. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/fake-anthropic.js +168 -0
  386. package/template/coding-task-manager/scripts/opencode-v2-contract-probe/probe-plugin/server.js +95 -0
  387. package/template/coding-task-manager/scripts/opencode-v2-contract-probe.js +673 -0
  388. package/template/coding-task-manager/server.js +1950 -11679
  389. package/template/coding-task-manager/session-integrity.js +64 -4
  390. package/template/coding-task-manager/workers/db-owner-worker.js +19 -2
  391. package/template/coding-task-manager/workers/online-backup-worker.js +1 -0
  392. package/template/coding-task-manager/workers/read-pool-model-collector.js +51 -3
  393. package/template/coding-task-manager/workers/read-pool-worker.js +76 -6
  394. package/template/coding-task-manager/workers/session-host-pool-process.js +6 -6
  395. package/template/coding-task-manager/workers/session-host-process.js +5 -5
  396. package/template/coding-task-manager/workers/watch-worker.js +105 -0
  397. package/template/coding-task-manager/workers/worktree-worker.js +168 -33
  398. package/template/ctm-relay/lib/protocol.js +14 -1
  399. package/template/package-lock.json +2 -2
  400. package/template/package.json +1 -1
  401. package/template/shared/backup-retention.js +82 -0
  402. package/template/shared/conversation-admission.js +2 -0
  403. package/template/shared/conversation-provenance.js +3 -7
  404. package/template/shared/conversation-storage-v2-contract.js +5 -0
  405. package/template/shared/linked-worktree.js +51 -0
  406. package/template/shared/macos-fda-target.js +57 -0
  407. package/template/shared/orphan-parents.js +55 -0
  408. package/template/shared/release-schema.json +2 -2
  409. package/template/shared/storage/contract.js +3 -0
  410. package/template/wall-e/adapters/imessage.js +12 -0
  411. package/template/wall-e/agent.js +71 -4
  412. package/template/wall-e/api-walle.js +21 -1
  413. package/template/wall-e/bin/train-gemma-e4b-tooluse.js +2 -2
  414. package/template/wall-e/bin/walle-mcp-stdio.js +5 -5
  415. package/template/wall-e/brain/brain-join.js +859 -0
  416. package/template/wall-e/brain/brain-sync-membership.js +20 -0
  417. package/template/wall-e/brain/brain-sync-store.js +131 -77
  418. package/template/wall-e/brain/brain-sync-v2-schema.js +203 -0
  419. package/template/wall-e/brain/brain-sync-v2.js +578 -0
  420. package/template/wall-e/brain/delegate-model-prefs-store.js +13 -4
  421. package/template/wall-e/brain/deterministic-id.js +51 -0
  422. package/template/wall-e/brain/memories.js +2 -2
  423. package/template/wall-e/brain/migrations.js +40 -6
  424. package/template/wall-e/brain/model-identity-migration.js +1 -1
  425. package/template/wall-e/brain/owner-call-whitelist.js +23 -7
  426. package/template/wall-e/brain/schema.js +45 -85
  427. package/template/wall-e/brain.js +171 -35
  428. package/template/wall-e/chat.js +3 -3
  429. package/template/wall-e/coding/agent-shell-env.js +7 -2
  430. package/template/wall-e/coding/prompt-bundle.js +66 -21
  431. package/template/wall-e/config/main-loop-exemptions.json +2 -2
  432. package/template/wall-e/http/admin-api.js +37 -0
  433. package/template/wall-e/http/gws-api.js +4 -0
  434. package/template/wall-e/http/helper-rules-api.js +7 -1
  435. package/template/wall-e/http/model-admin.js +8 -1
  436. package/template/wall-e/http/model-delegate-pref-admin.js +3 -3
  437. package/template/wall-e/http/model-proxy-providers.js +174 -1
  438. package/template/wall-e/http/model-route-health.js +157 -0
  439. package/template/wall-e/http/sync-api.js +278 -464
  440. package/template/wall-e/jobs/job-runner.js +28 -1
  441. package/template/wall-e/lib/brain-owner-op-priority.js +9 -1
  442. package/template/wall-e/lib/ctm-data-dir.js +26 -0
  443. package/template/wall-e/lib/derivation-home.js +147 -0
  444. package/template/wall-e/lib/helper-rules/evaluate.js +50 -9
  445. package/template/wall-e/lib/helper-rules/grade.js +51 -8
  446. package/template/wall-e/lib/helper-rules/lead-call-hook.js +38 -5
  447. package/template/wall-e/lib/helper-rules/store.js +4 -2
  448. package/template/wall-e/lib/landing-mcp-tools.js +170 -0
  449. package/template/wall-e/lib/mcp-client-config.js +133 -14
  450. package/template/wall-e/lib/mcp-gateway-migrate.js +28 -64
  451. package/template/wall-e/lib/mcp-integration.js +58 -13
  452. package/template/wall-e/lib/parent-death.js +7 -1
  453. package/template/wall-e/lib/scheduler-worker-jobs.js +6 -4
  454. package/template/wall-e/llm/delegate-lead-prefs.js +16 -1
  455. package/template/wall-e/llm/delegate-vendor-default.js +12 -5
  456. package/template/wall-e/llm/model-roles.js +1 -1
  457. package/template/wall-e/llm/native-delegate-selection.js +56 -8
  458. package/template/wall-e/llm/worker-delegate-args.js +72 -7
  459. package/template/wall-e/llm/worker-mcp-routing.js +66 -17
  460. package/template/wall-e/loops/brain-custom-writes.js +19 -0
  461. package/template/wall-e/loops/ingest.js +17 -1
  462. package/template/wall-e/loops/think.js +19 -4
  463. package/template/wall-e/mcp-contract.js +222 -6
  464. package/template/wall-e/mcp-protocol-contract.js +40 -5
  465. package/template/wall-e/mcp-server.js +128 -29
  466. package/template/wall-e/mcp-worker-schemas.js +5 -5
  467. package/template/wall-e/memory/ctm-context-client.js +85 -0
  468. package/template/wall-e/memory/ctm-session-context.js +9 -3
  469. package/template/wall-e/memory/hub-bridge-client.js +388 -0
  470. package/template/wall-e/memory/replica-origin.js +56 -0
  471. package/template/wall-e/memory/session-pool.js +135 -0
  472. package/template/wall-e/runtime/live-capabilities.js +8 -3
  473. package/template/wall-e/server.js +4 -2
  474. package/template/wall-e/shared/release-schema.json +2 -2
  475. package/template/wall-e/shared/sqlite-file-set.js +39 -0
  476. package/template/wall-e/shared/storage/contract.js +3 -0
  477. package/template/wall-e/skills/_templates/delegate-worker/VERSION +1 -1
  478. package/template/wall-e/skills/_templates/delegate-worker/released-sha256.json +7 -1
  479. package/template/wall-e/skills/_templates/delegate-worker/templates/claude/SKILL.md +4 -4
  480. package/template/wall-e/skills/_templates/delegate-worker/templates/codex/SKILL.md +6 -6
  481. package/template/wall-e/skills/_templates/fleet-sync/VERSION +1 -0
  482. package/template/wall-e/skills/_templates/fleet-sync/released-sha256.json +16 -0
  483. package/template/wall-e/skills/_templates/fleet-sync/templates/claude/SKILL.md +64 -0
  484. package/template/wall-e/skills/_templates/fleet-sync/templates/codex/SKILL.md +64 -0
  485. package/template/wall-e/tools/chrome-automation.js +6 -0
  486. package/template/wall-e/training/setup-cuda-train-env.sh +2 -2
  487. package/template/wall-e/workers/brain-owner-worker.js +18 -4
  488. package/template/wall-e/workers/mcp-gateway-process.js +5 -5
@@ -0,0 +1,380 @@
1
+ 'use strict';
2
+ // WP-3a (docs/landing-queue-gate-once-merge-delta-2026-10-29.html §5 D-3/D-3c, §6.5, §8 WP-3a,
3
+ // §9 S-9, §10 T-4/T-6): merge-delta selection, in SHADOW mode.
4
+ //
5
+ // For a merge train T = commit-tree(tree, [B, P]) the CAV'14 two-sided rule (§3 P-1, Theorem 1) says
6
+ // a test that neither side of the merge changes keeps the result it already had on that side, so the
7
+ // safe Node test set is S = sel(B→T) ∩ sel(P→T). This module COMPUTES S and records it on the job's
8
+ // `gate.intersection` telemetry — nothing else. The gate keeps running today's selection
9
+ // (`--plan-base B`); WP-3b is the work package that would ENFORCE S, and it is gated on OQ-2, which
10
+ // this shadow record is the instrument for (§5 Open questions OQ-2).
11
+ //
12
+ // Every precondition of §5 D-3 is a FALLBACK here, never a failure: a missing proof for tree(B), a
13
+ // broken proof chain, a broad plan on either side, a planner that cannot run, or LAND_MERGE_DELTA=0
14
+ // all return `{ mode: 'fallback', reason, detail }`, and the train gates exactly as it did before
15
+ // this module existed. Nothing here can change a gate argv, a verdict or a landing outcome.
16
+ //
17
+ // `planner` is injectable and defaults to the real spawn (`node <trainDir>/bin/test-plan.js`), so
18
+ // the unit tests never start a child process (tests/ is a no-spawn tier: bin/tier-placement-lint.js
19
+ // rejects a spawning test in tests/). The default resolves the planner FROM THE TRAIN WORKTREE,
20
+ // which is what bin/test-gate.sh already does ($SCRIPT_ROOT/bin/test-plan.js, test-gate.sh:1282):
21
+ // the planner belongs to the repository whose tests are being planned, and the train worktree is a
22
+ // checked-out copy of exactly that repository.
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+ const crypto = require('crypto');
27
+ const { spawn } = require('child_process');
28
+
29
+ const proof = require('./proof');
30
+ const queue = require('./queue');
31
+ const state = require('./state');
32
+ const { childEnv, revParse } = require('./git');
33
+
34
+ // The planner is a `test-plan.js` on a repo that is already loaded (it parses the whole impact graph
35
+ // in the worst case); 5 minutes is a safety net against a wedged child, not a budget.
36
+ const PLANNER_TIMEOUT_MS = 5 * 60 * 1000;
37
+
38
+ // bin/test-plan.js's disposition for "this file must run in this gate" (its `dispositions[]` rows).
39
+ // A broad plan marks every file `scheduled-broad-lane` instead, which is exactly why the caller
40
+ // refuses to intersect two plans that are not both narrow (§5 D-3(c)).
41
+ const RUN_DISPOSITION = 'run';
42
+
43
+ // The runners' own failure summary, verbatim (bin/test-runner-adapter.js:921-941):
44
+ //
45
+ // [test-suite] FAILED (2 file(s)):
46
+ // - tests/foo.test.js: <reasons joined by '; '>
47
+ // - tests/bar.test.js: killed after 300000ms (possible synchronous spin)
48
+ //
49
+ // `test-suite` is the CTM/`coding-task-manager` label and `run-hermetic` the root/wall-e one
50
+ // (test-runner-adapter.js:850), so the label is matched generically. The ` - <file>: …` rows are
51
+ // the run's failing test files, relative to the repository the runner ran in.
52
+ const FAILED_BLOCK_RE = /^\s*\[[^\]]+\] FAILED \(\d+ file\(s\)\):\s*$/;
53
+ const FAILED_ROW_RE = /^\s{2}-\s+(\S+?):\s*(.*)$/;
54
+
55
+ // The three reads §5 D-3(a)/(b) need, in one object so the unit tests never spawn git (tests/ is a
56
+ // no-spawn tier). Production is `realProofs`:
57
+ // treeOf — `git rev-parse <base>^{tree}`; THROWS on an unresolvable revision, which the
58
+ // caller records as `plan_error` rather than guessing at a tree.
59
+ // treeHasProof — §5 D-3(a): has tree(B) ever passed a gate under this gates hash?
60
+ // proofChain — §5 D-3c: is every commit of mb..P proved?
61
+ const realProofs = {
62
+ treeOf: (cwd, rev) => revParse(cwd, `${rev}^{tree}`),
63
+ treeHasProof: (root, query) => proof.treeHasProof(root, query),
64
+ proofChain: (cwd, root, query) => proof.proofChain(cwd, root, query),
65
+ };
66
+
67
+ // --- the planner (§6.5) -------------------------------------------------------------------------
68
+
69
+ function plannerArgs(trainDir, base) {
70
+ return [
71
+ path.join(trainDir, 'bin', 'test-plan.js'),
72
+ '--root', trainDir,
73
+ '--mode', 'snapshot',
74
+ '--base', base,
75
+ '--json',
76
+ ];
77
+ }
78
+
79
+ function firstLine(text) {
80
+ return String(text === undefined || text === null ? '' : text)
81
+ .split('\n').map((line) => line.trim()).filter(Boolean)[0] || null;
82
+ }
83
+
84
+ // The default planner: one `node <trainDir>/bin/test-plan.js --mode snapshot --base <base> --json`
85
+ // child, resolved from the train worktree. It NEVER throws and never rejects — a failure is
86
+ // `{ ok: false, detail }`, which the caller turns into the `plan_error` fallback — and it reports its
87
+ // own wall time in milliseconds so two runs can be summed (`plannerMs`, AC-3c) without a second clock
88
+ // outside this module.
89
+ //
90
+ // Returns `{ ok: true, plan: <parsed JSON>, ms }` | `{ ok: false, detail: <first stderr line>, ms }`.
91
+ function runPlanner({ trainDir, base }) {
92
+ return new Promise((resolve) => {
93
+ const started = Date.now();
94
+ const done = (result) => resolve({ ...result, ms: Date.now() - started });
95
+ let child;
96
+ try {
97
+ child = spawn(process.execPath, plannerArgs(trainDir, base), {
98
+ cwd: trainDir,
99
+ env: childEnv(),
100
+ stdio: ['ignore', 'pipe', 'pipe'],
101
+ });
102
+ } catch (error) {
103
+ done({ ok: false, detail: error && error.message ? error.message : String(error) });
104
+ return;
105
+ }
106
+ let stdout = '';
107
+ let stderr = '';
108
+ let settled = false;
109
+ const finish = (result) => {
110
+ if (settled) return;
111
+ settled = true;
112
+ clearTimeout(timer);
113
+ done(result);
114
+ };
115
+ const timer = setTimeout(() => {
116
+ try {
117
+ child.kill('SIGKILL');
118
+ } catch (error) {
119
+ /* expected: the planner exited between the deadline firing and this kill */
120
+ }
121
+ finish({ ok: false, detail: `test-plan.js did not finish within ${PLANNER_TIMEOUT_MS}ms` });
122
+ }, PLANNER_TIMEOUT_MS);
123
+ child.stdout.setEncoding('utf8');
124
+ child.stderr.setEncoding('utf8');
125
+ child.stdout.on('data', (chunk) => { stdout += chunk; });
126
+ child.stderr.on('data', (chunk) => { stderr += chunk; });
127
+ child.on('error', (error) => finish({ ok: false, detail: error && error.message ? error.message : String(error) }));
128
+ child.on('close', (status) => {
129
+ if (status !== 0) {
130
+ finish({ ok: false, detail: firstLine(stderr) || `test-plan.js exited ${status}` });
131
+ return;
132
+ }
133
+ try {
134
+ finish({ ok: true, plan: JSON.parse(stdout) });
135
+ } catch (error) {
136
+ finish({ ok: false, detail: `unparseable plan JSON: ${error && error.message ? error.message : String(error)}` });
137
+ }
138
+ });
139
+ });
140
+ }
141
+
142
+ // sel(X→T) for one plan: the repo-relative files a narrow plan says must run. A plan that is not an
143
+ // object, or that carries no `dispositions[]`, yields the empty set — the caller has already refused
144
+ // a broad plan, so "no run rows" is a real (empty) selection, never a reason to fall back.
145
+ function runFiles(plan) {
146
+ const rows = plan && Array.isArray(plan.dispositions) ? plan.dispositions : [];
147
+ const files = new Set();
148
+ for (const row of rows) {
149
+ if (row && row.disposition === RUN_DISPOSITION && typeof row.file === 'string' && row.file) files.add(row.file);
150
+ }
151
+ return files;
152
+ }
153
+
154
+ // --- the selection (§5 D-3, §6.5) ----------------------------------------------------------------
155
+
156
+ // §5 D-3: S = sel(B→T) ∩ sel(P→T), computed in the train worktree T and recorded — never enforced.
157
+ //
158
+ // Returns `{ mode: 'merge-delta', files, sizes: { fromBase, fromTip, intersection }, plannerMs }`
159
+ // on success, or `{ mode: 'fallback', reason, detail }` for every §5 D-3 precondition miss:
160
+ //
161
+ // disabled — LAND_MERGE_DELTA === '0' (the D-3 kill switch; §6 "No env flag except the D-3
162
+ // kill-switch"). Checked first: the switch is a cheap short-circuit, and it must
163
+ // hold even when the proofs below are missing.
164
+ // no_base_proof — (a) no PASS proof exists for tree(B) under this gates hash, so the base side of
165
+ // the merge was never tested and Theorem 1's induction has no first step.
166
+ // chain_broken — (b) some commit on `rev-list --first-parent mb..P` has no proof for
167
+ // (tree(c), planBaseTree(c)) (§5 D-3c).
168
+ // broad_plan — (c) either plan is a fallback to the broad lane, whose "run" set is the whole
169
+ // inventory and whose intersection would be a full run wearing a narrow plan's name.
170
+ // plan_error — (c) the planner could not run or did not print JSON (detail = its first stderr
171
+ // line), or a git read behind (a)/(b) failed.
172
+ //
173
+ // `base`/`tip` are revisions (commit oids in the runner's call), `trainDir` is the train worktree,
174
+ // `root` the landing state directory. `planner` and `proofs` are injected by the unit tests;
175
+ // production uses `runPlanner` and `realProofs`.
176
+ async function mergeDeltaSelection({
177
+ trainDir, root, base, tip, gatesHash, env = process.env, planner = runPlanner, proofs = realProofs,
178
+ } = {}) {
179
+ if (env && env.LAND_MERGE_DELTA === '0') return { mode: 'fallback', reason: 'disabled', detail: null };
180
+
181
+ const baseTree = proofs.treeOf(trainDir, base);
182
+ if (!proofs.treeHasProof(root, { tree: baseTree, gatesHash })) {
183
+ return { mode: 'fallback', reason: 'no_base_proof', detail: `no PASS proof for tree ${baseTree} (base ${base})` };
184
+ }
185
+
186
+ const chain = proofs.proofChain(trainDir, root, { base, tip, gatesHash });
187
+ if (!chain.ok) {
188
+ return {
189
+ mode: 'fallback',
190
+ reason: 'chain_broken',
191
+ detail: `${chain.missing.length} commit(s) of ${chain.checked} unproven: ${chain.missing.join(', ')}`,
192
+ };
193
+ }
194
+
195
+ const plans = [];
196
+ let plannerMs = 0;
197
+ for (const side of [base, tip]) {
198
+ // The two passes are sequential on the ONE host this queue owns (§11 "Host contention remains"):
199
+ // they are cheap next to a gate, but there is no reason to add load to a machine whose median
200
+ // load is already 19.9 on 16 cores.
201
+ // eslint-disable-next-line no-await-in-loop
202
+ const result = await planner({ trainDir, root, base: side, env });
203
+ plannerMs += result && Number.isFinite(result.ms) ? result.ms : 0;
204
+ if (!result || !result.ok) {
205
+ return { mode: 'fallback', reason: 'plan_error', detail: (result && result.detail) || 'planner returned no result' };
206
+ }
207
+ plans.push(result.plan);
208
+ }
209
+ const broadSide = plans.findIndex((plan) => plan && plan.fallback === true);
210
+ if (broadSide !== -1) {
211
+ return {
212
+ mode: 'fallback',
213
+ reason: 'broad_plan',
214
+ detail: `plan(${broadSide === 0 ? 'B' : 'P'}) fell back to the broad lane`,
215
+ plannerMs,
216
+ };
217
+ }
218
+
219
+ const fromBase = runFiles(plans[0]);
220
+ const fromTip = runFiles(plans[1]);
221
+ const files = [...fromBase].filter((file) => fromTip.has(file)).sort();
222
+ return {
223
+ mode: 'merge-delta',
224
+ files,
225
+ sizes: { fromBase: fromBase.size, fromTip: fromTip.size, intersection: files.length },
226
+ plannerMs,
227
+ };
228
+ }
229
+
230
+ // --- the shadow record (§6.6, §8 WP-3a) ----------------------------------------------------------
231
+
232
+ // The gate telemetry shape `gateSelectionTelemetry` builds (bin/land.js), with every field
233
+ // "unknown". A shadow record only exists AFTER a gate ran, so a gate that wrote no `--selection-out`
234
+ // (a repository whose `gates[].run` is a plain script, or this repository's own queue fixtures) still
235
+ // gets a telemetry object — one that says "the gate ran and said nothing", which is the truth.
236
+ const UNKNOWN_TELEMETRY = Object.freeze({
237
+ mode: null, planBase: null, selected: null, executed: null, cached: null,
238
+ missReasons: null, fallbackReason: null,
239
+ });
240
+
241
+ // `gate.intersection` is written on the 3.9 kB job record, so it carries the SHAPE of the measurement
242
+ // and not the measurement: |S| can be hundreds of files, and every job file and every metrics row
243
+ // would pay for the list forever. The full S goes to `<root>/logs/<jobId>.merge-delta.json` and the
244
+ // job keeps `sHash = sha256(S.join('\n'))` — enough to compare two jobs' selections, or to re-read the
245
+ // list from the log directory.
246
+ function sha256(text) {
247
+ return crypto.createHash('sha256').update(text, 'utf8').digest('hex');
248
+ }
249
+
250
+ function readLogText(file) {
251
+ if (!file) return null;
252
+ try {
253
+ return fs.readFileSync(file, 'utf8');
254
+ } catch (error) {
255
+ /* expected: the log is written by the runner; if it is gone there is simply no evidence to parse */
256
+ return null;
257
+ }
258
+ }
259
+
260
+ // The Node test files a runner reported as FAIL, or null when the log carries no runner failure block
261
+ // at all. NULL IS NOT "NONE": an empty array means "a block was found and every failing file is in
262
+ // S", which is the OQ-2 answer this WP exists to collect; null means "the log cannot say" (the gate
263
+ // failed in a lint phase, or the file was pruned).
264
+ function failingFiles(logText) {
265
+ // One parser for the runner's failure block: train.failedFilesFromLog (landing-throughput WP-2)
266
+ // reads the same `[<label>] FAILED (N file(s)):` block and ` - <file>: …` rows; `raw` is the
267
+ // files exactly as printed (S is compared in the log's own terms), `sawBlock` the null/[] split.
268
+ const parsed = require('./train').failedFilesFromLog(logText);
269
+ return parsed.sawBlock ? [...parsed.raw].sort() : null;
270
+ }
271
+
272
+ // §8 WP-3a: after a FAIL, the failing Node test files that are NOT in S. `[]` is a real, encouraging
273
+ // answer (every file the gate failed is one S said to run); null means the log could not be read.
274
+ function outsideFailing(failing, files) {
275
+ if (!failing) return null;
276
+ const selected = new Set(files || []);
277
+ return failing.filter((file) => !selected.has(file)).sort();
278
+ }
279
+
280
+ // Attach the shadow record to this run's `gate` telemetry (§6.6). Returns the telemetry object the
281
+ // job record and the terminal metrics row carry. `shadow` is the value `runGate`'s `onTrainReady`
282
+ // returned; `telemetry` is `gateSelectionTelemetry(result.selection)` (possibly null).
283
+ function recordIntersection(telemetry, shadow, { root, jobId, trainId = null, verdict, logFile } = {}) {
284
+ const files = shadow && shadow.mode === 'merge-delta' && Array.isArray(shadow.files) ? shadow.files : null;
285
+ if (files) {
286
+ queue.ensureLogsDir(root);
287
+ // WP-1 §9 S-4: keyed by the TRAIN id (the batch id for a batch, the job id for a singleton), so a
288
+ // batch has one shadow record the whole train shares.
289
+ state.writeJsonAtomic(path.join(root, 'logs', `${trainId || jobId}.merge-delta.json`), {
290
+ contract: state.CONTRACT,
291
+ jobId,
292
+ trainId,
293
+ mode: shadow.mode,
294
+ sizes: shadow.sizes || null,
295
+ plannerMs: Number.isFinite(shadow.plannerMs) ? shadow.plannerMs : null,
296
+ files,
297
+ });
298
+ }
299
+ const intersection = {
300
+ shadow: true,
301
+ mode: shadow && shadow.mode ? shadow.mode : null,
302
+ reason: shadow && shadow.reason ? shadow.reason : null,
303
+ sizes: shadow && shadow.sizes ? shadow.sizes : null,
304
+ plannerMs: shadow && Number.isFinite(shadow.plannerMs) ? shadow.plannerMs : null,
305
+ sHash: files ? sha256(files.join('\n')) : null,
306
+ // Only a FAIL can have a failing file: a PASS ran S inside a green gate, and a stopped gate ran no
307
+ // verdict at all.
308
+ outsideFailing: verdict === 'FAIL' ? outsideFailing(failingFiles(readLogText(logFile)), files) : null,
309
+ };
310
+ return { ...(telemetry || UNKNOWN_TELEMETRY), intersection };
311
+ }
312
+
313
+ // --- the report (§8 AC-3b, §5 OQ-2) --------------------------------------------------------------
314
+
315
+ // Every job whose `gate.intersection` this module wrote — i.e. every merge train gated since WP-3a.
316
+ function shadowRows(root) {
317
+ return queue.listJobs(root)
318
+ .map((job) => job && job.gate && job.gate.intersection)
319
+ .filter((row) => row && typeof row === 'object');
320
+ }
321
+
322
+ // AC-3b/the OQ-2 data: n trains, n per mode (and per fallback reason), the median |S| as a percentage
323
+ // of |sel(B→T)|, the median planner time, and the count of FAILED trains with a failing file OUTSIDE
324
+ // S. The last number is the one that decides WP-3b: §5 OQ-2 enforces only when no failing test outside
325
+ // S was found across the sample AND the median |S|/|sel(B→T)| is ≤ 0.5.
326
+ function mergeDeltaReport(root) {
327
+ const rows = shadowRows(root);
328
+ const modes = {};
329
+ const ratios = [];
330
+ const plannerMs = [];
331
+ let outsideFailingTrains = 0;
332
+ for (const row of rows) {
333
+ const key = row.mode === 'merge-delta' ? 'merge-delta' : `fallback:${row.reason || 'unknown'}`;
334
+ modes[key] = (modes[key] || 0) + 1;
335
+ if (row.sizes && Number.isFinite(row.sizes.fromBase) && row.sizes.fromBase > 0 && Number.isFinite(row.sizes.intersection)) {
336
+ ratios.push((row.sizes.intersection / row.sizes.fromBase) * 100);
337
+ }
338
+ if (Number.isFinite(row.plannerMs)) plannerMs.push(row.plannerMs);
339
+ if (Array.isArray(row.outsideFailing) && row.outsideFailing.length > 0) outsideFailingTrains += 1;
340
+ }
341
+ const percent = queue.median(ratios);
342
+ return {
343
+ shadow: true,
344
+ trains: rows.length,
345
+ modes,
346
+ // §5 OQ-2's decision inputs, spelled the same way the decision rule reads them.
347
+ medianPercentOfBase: percent === null ? null : Number(percent.toFixed(1)),
348
+ medianPlannerMs: queue.median(plannerMs),
349
+ outsideFailingTrains,
350
+ };
351
+ }
352
+
353
+ function reportLines(report) {
354
+ const modes = Object.entries(report.modes).sort(([a], [b]) => a.localeCompare(b));
355
+ const percent = report.medianPercentOfBase === null ? '?' : `${report.medianPercentOfBase}%`;
356
+ const planner = report.medianPlannerMs === null ? '?' : `${report.medianPlannerMs}ms`;
357
+ return [
358
+ `merge-delta shadow: ${report.trains} train(s)`,
359
+ ...(modes.length ? modes.map(([mode, count]) => ` ${mode}: ${count}`) : [' (no merge train has been gated with shadow telemetry yet)']),
360
+ `median |S| / |sel(B→T)|: ${percent}`,
361
+ `median plannerMs: ${planner}`,
362
+ `failed trains with a failing file outside S: ${report.outsideFailingTrains}`,
363
+ ];
364
+ }
365
+
366
+ module.exports = {
367
+ FAILED_BLOCK_RE,
368
+ FAILED_ROW_RE,
369
+ realProofs,
370
+ plannerArgs,
371
+ runPlanner,
372
+ runFiles,
373
+ mergeDeltaSelection,
374
+ recordIntersection,
375
+ failingFiles,
376
+ outsideFailing,
377
+ shadowRows,
378
+ mergeDeltaReport,
379
+ reportLines,
380
+ };
@@ -0,0 +1,223 @@
1
+ 'use strict';
2
+ // WP-2 (docs/landing-queue-gate-once-merge-delta-2026-10-29.html §5 D-1/D-3c/D-4, §6.3, §6.4,
3
+ // §8 WP-2, §9 S-5..S-8): the proof store.
4
+ //
5
+ // D-1: a gate PASS is a PROOF, and it is keyed by the PAIR (tree, planBaseTree) plus the gates
6
+ // hash — not by the tree alone. The commit's own gate proves the delta against its parent (`bin/
7
+ // pre-commit-hook.sh` passes that parent as `--plan-base`), so a proof the author's commit gate
8
+ // produced is exactly as good as one the landing runner produced, and the queue can land the tip
9
+ // without re-running the identical gate (S-5: today the hook's PASS is checked and thrown away).
10
+ //
11
+ // The legacy tree-keyed store `verdicts/<tree>.json` (lib/landing/gate.js) stays readable and is
12
+ // still written by the runner; this file is the second, narrower key and never writes that one.
13
+ //
14
+ // Only PASS is ever written (§5 D-1: "INCONCLUSIVE, FAIL and a PASS that contains a FLAKY line are
15
+ // never recorded as proofs"), and a proof is refused outright when the diff against its plan base
16
+ // touches a dependency manifest: the train gate symlinks the MAIN checkout's node_modules in
17
+ // (gate.js `linkCachePaths`), so a tree whose dependencies differ from the author's was tested
18
+ // against something the author never installed.
19
+ //
20
+ // Keyed entries live at `verdicts/<tree>.<planBaseTree>.json` under the landing state root — the
21
+ // same `<common-dir>/landing` directory `lib/landing/state.js` owns. Nothing here throws for a
22
+ // state file that is merely absent or torn: a missing proof is a cache miss, never a failure.
23
+
24
+ const fs = require('fs');
25
+ const path = require('path');
26
+
27
+ const state = require('./state');
28
+ const { landingError, runGitRaw, isAncestor } = require('./git');
29
+
30
+ // Git object names, both 40-hex (sha1) and 64-hex (sha256) repositories. The name is used as a
31
+ // path component, so it is validated rather than interpolated blindly.
32
+ const OID_RE = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
33
+
34
+ // The one terminal line `bin/test-gate.sh` prints on a green run (bin/lib-test-gate-summary.sh
35
+ // `print_gate_verdict`); the pre-commit hook greps it with `grep -Fqx`, so it is matched as a whole
36
+ // line here too.
37
+ const PASS_LINE = '[test-gate] VERDICT: PASS';
38
+
39
+ // The FLAKY evidence lines the runners actually print, verbatim, case-sensitively:
40
+ // bin/test-runner-adapter.js:869 `[test-suite] FLAKY (passed on isolated rerun): …`
41
+ // `[run-hermetic] FLAKY (passed on isolated rerun): …`
42
+ // bin/test-browser-selection-rerun.js:260 `[browser-selection] FLAKY (rerun passed alone): …`
43
+ // bin/test-browser-selection.js:793 `[browser-selection] VERDICT: PASS (with N FLAKY file(s), …)`
44
+ // A bare `FLAKY` substring would also fire on a TAP line that merely NAMES a flaky-related file
45
+ // (`flaky-report`, `flaky-ledger`), and a rerun-pass is a real flake — the proof must not claim it.
46
+ const FLAKY_RE = /FLAKY \(passed on isolated rerun\)|FLAKY \(rerun passed alone\)|\d+ FLAKY file\(s\)/;
47
+
48
+ // §5 D-1: "Dependency installs differ between the author's worktree and the train's symlinked
49
+ // node_modules". Manifest and lock files, at the repository root or in a package subdirectory.
50
+ const DEPENDENCY_RE = /(^|\/)(?:package(?:-lock)?|npm-shrinkwrap)\.json$/;
51
+
52
+ function isOid(value) {
53
+ return typeof value === 'string' && OID_RE.test(value);
54
+ }
55
+
56
+ // §6.3: the proof file for the exact pair. Both names must be object ids — they are path
57
+ // components, so a caller that hands over a revision like `main` or `../../etc/passwd` is a bug,
58
+ // never a path traversal.
59
+ function proofFile(root, tree, planBaseTree) {
60
+ if (!isOid(tree) || !isOid(planBaseTree)) {
61
+ throw landingError('proof_invalid', `a proof is keyed by two object ids, got tree=${JSON.stringify(tree)} planBaseTree=${JSON.stringify(planBaseTree)}`, { tree, planBaseTree, root });
62
+ }
63
+ return path.join(root, 'verdicts', `${tree}.${planBaseTree}.json`);
64
+ }
65
+
66
+ // The recorded proof for exactly this (tree, planBaseTree, gatesHash), or null. Null covers every
67
+ // "not a proof" case on purpose: absent file, unreadable/torn JSON, a different tree or base, a
68
+ // different gates hash (§6.2: changing `gates` invalidates every proof), and a non-PASS record.
69
+ function readProof(root, { tree, planBaseTree, gatesHash } = {}) {
70
+ if (!isOid(tree) || !isOid(planBaseTree) || typeof gatesHash !== 'string' || !gatesHash) return null;
71
+ const raw = state.readJsonSafe(proofFile(root, tree, planBaseTree));
72
+ if (!raw || raw.tree !== tree || raw.planBaseTree !== planBaseTree) return null;
73
+ if (raw.gatesHash !== gatesHash) return null;
74
+ if (raw.verdict !== 'PASS') return null;
75
+ return raw;
76
+ }
77
+
78
+ // §6.3 entry: { contract, tree, planBaseTree, gatesHash, verdict: 'PASS', source: 'commit'|'train',
79
+ // commit, seconds, at, logPath, selection }. Written tmp+rename (state.writeJsonAtomic), so a
80
+ // reader never sees a half-written proof. Only PASS is a proof; anything else throws.
81
+ function recordProof(root, entry) {
82
+ const record = entry || {};
83
+ if (record.verdict !== 'PASS') {
84
+ throw landingError('proof_not_pass', `refusing to record a ${JSON.stringify(record.verdict === undefined ? null : record.verdict)} proof (only PASS is a proof)`, {
85
+ tree: record.tree === undefined ? null : record.tree,
86
+ planBaseTree: record.planBaseTree === undefined ? null : record.planBaseTree,
87
+ });
88
+ }
89
+ const file = proofFile(root, record.tree, record.planBaseTree);
90
+ state.writeJsonAtomic(file, { contract: state.CONTRACT, ...record });
91
+ return file;
92
+ }
93
+
94
+ // Every proof file name in the store, sorted (readdir order is filesystem-dependent, so a reader
95
+ // that picks the first match must sort first to be deterministic). A store that does not exist yet is
96
+ // an empty store: `verdicts/` is created lazily by the first write.
97
+ function proofNames(root) {
98
+ try {
99
+ return fs.readdirSync(path.join(root, 'verdicts')).sort();
100
+ } catch (error) {
101
+ /* expected: nothing has ever been proved in this repository — an empty store, not an error */
102
+ return [];
103
+ }
104
+ }
105
+
106
+ // §5 D-3(a), §6.3: is there ANY proof for this tree, against any plan base? The base side of a merge
107
+ // (tree(B)) is not proved against `P` — it is proved against whatever its own commit gate used — so
108
+ // the D-3(a) question is "has this tree ever passed a gate under these gates", not "is this exact
109
+ // pair recorded". Both layouts count: the legacy tree-keyed `verdicts/<tree>.json` (§6.3 "stays
110
+ // readable, never written") and the pair-keyed `verdicts/<tree>.<planBaseTree>.json`. The gates hash
111
+ // must still match (§6.2: changing `gates` invalidates every proof) and only PASS is a proof (D-1).
112
+ function treeHasProof(root, { tree, gatesHash } = {}) {
113
+ if (!isOid(tree) || typeof gatesHash !== 'string' || !gatesHash) return null;
114
+ for (const name of proofNames(root)) {
115
+ if (name !== `${tree}.json` && !name.startsWith(`${tree}.`)) continue;
116
+ const raw = state.readJsonSafe(path.join(root, 'verdicts', name));
117
+ if (!raw || raw.tree !== tree) continue;
118
+ if (raw.gatesHash !== gatesHash) continue;
119
+ if (raw.verdict !== 'PASS') continue;
120
+ return raw;
121
+ }
122
+ return null;
123
+ }
124
+
125
+ // --- the chain (§5 D-3c, §5 D-4) -----------------------------------------------------------------
126
+
127
+ // `git rev-list --first-parent <base>..<tip>` — the commits a fast-forward of `base` to `tip`
128
+ // would add, oldest-history-last (order is irrelevant here: each commit is decided on its own).
129
+ function firstParentRange(cwd, base, tip) {
130
+ const args = ['rev-list', '--first-parent', `${base}..${tip}`];
131
+ const res = runGitRaw(cwd, args);
132
+ if (res.status !== 0) {
133
+ throw landingError('git_failed', `git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`, { cwd, args, status: res.status });
134
+ }
135
+ return res.stdout.split('\n').map((line) => line.trim()).filter(Boolean);
136
+ }
137
+
138
+ function treeOf(cwd, commit) {
139
+ const res = runGitRaw(cwd, ['rev-parse', '--verify', '--quiet', `${commit}^{tree}`]);
140
+ if (res.status !== 0) return null;
141
+ const value = res.stdout.trim();
142
+ return isOid(value) ? value : null;
143
+ }
144
+
145
+ // The parent oids of ONE commit, first parent first (`git rev-list --parents -n 1 <c>` prints
146
+ // `<c> <p1> <p2> …`).
147
+ function parentsOf(cwd, commit) {
148
+ const args = ['rev-list', '--parents', '-n', '1', commit];
149
+ const res = runGitRaw(cwd, args);
150
+ if (res.status !== 0) {
151
+ throw landingError('git_failed', `git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`, { cwd, args, status: res.status });
152
+ }
153
+ const tokens = res.stdout.split('\n')[0].trim().split(/\s+/).filter(Boolean);
154
+ return tokens.slice(1);
155
+ }
156
+
157
+ // One commit's link in the chain: it holds iff some parent `p` has a proof for (tree(c), tree(p)).
158
+ // `p` qualifies when it is c's FIRST parent — which, walking first-parents, is either the next
159
+ // commit on the chain or (for the oldest one) an ancestor of `base` — or when it is an ancestor of
160
+ // `base` itself. That second case is what makes an author's merge-of-main commit provable: its
161
+ // second parent IS the target, so the proof the hook recorded for it is accepted (D-2).
162
+ function commitProven(cwd, root, commit, base, gatesHash) {
163
+ const tree = treeOf(cwd, commit);
164
+ if (!tree) return false;
165
+ const parents = parentsOf(cwd, commit);
166
+ for (let index = 0; index < parents.length; index += 1) {
167
+ const parent = parents[index];
168
+ if (index > 0 && !isAncestor(cwd, parent, base)) continue;
169
+ if (readProof(root, { tree, planBaseTree: treeOf(cwd, parent), gatesHash })) return true;
170
+ }
171
+ return false;
172
+ }
173
+
174
+ // §5 D-4, generalized to the per-commit induction §5 D-3c describes. `base` is the target commit
175
+ // the fast-forward starts from, `tip` the commit being landed. The chain holds iff EVERY commit
176
+ // `base..tip` (first-parent order) has a proof against a qualifying parent; `missing` lists the
177
+ // commits that do not. An empty range is trivially proven — nothing is being added.
178
+ //
179
+ // `checked` is the number of commits walked (0 for an empty range): the D-4 one-commit case and the
180
+ // runner's own progress line both need it, and recomputing the range outside would be a second git
181
+ // call for a number this walk already knows.
182
+ function proofChain(cwd, root, { base, tip, gatesHash } = {}) {
183
+ if (!base || !tip) return { ok: false, missing: [], checked: 0 };
184
+ const commits = firstParentRange(cwd, base, tip);
185
+ const missing = commits.filter((commit) => !commitProven(cwd, root, commit, base, gatesHash));
186
+ return { ok: missing.length === 0, missing, checked: commits.length };
187
+ }
188
+
189
+ // --- the refusal (§5 D-1, §6.4) ------------------------------------------------------------------
190
+
191
+ // Why a gate log is NOT a proof, in the order that decides it. `null` means "recordable".
192
+ // 'no_pass' — no line exactly `[test-gate] VERDICT: PASS`
193
+ // 'flaky' — the log carries a runner's FLAKY evidence line (FLAKY_RE)
194
+ // 'dependency_change' — `git diff --name-only <planBase> <tree>` touches a dependency manifest
195
+ // The dependency check is skipped when either side is unreadable: a caller that cannot name both
196
+ // sides has nothing to diff, and the ledger check must never be the thing that fabricates a refusal.
197
+ function proofRefusal(cwd, { planBase, tree, logText } = {}) {
198
+ const text = String(logText === undefined || logText === null ? '' : logText);
199
+ const green = text.split('\n').map((line) => line.replace(/\r$/, '')).some((line) => line === PASS_LINE);
200
+ if (!green) return 'no_pass';
201
+ if (FLAKY_RE.test(text)) return 'flaky';
202
+ if (!planBase || !tree) return null;
203
+ const args = ['diff', '--name-only', planBase, tree];
204
+ const res = runGitRaw(cwd, args);
205
+ if (res.status !== 0) {
206
+ throw landingError('git_failed', `git ${args.join(' ')} exited ${res.status}: ${String(res.stderr || '').trim()}`, { cwd, args, status: res.status });
207
+ }
208
+ const touched = res.stdout.split('\n').map((line) => line.trim()).filter(Boolean);
209
+ if (touched.some((file) => DEPENDENCY_RE.test(file))) return 'dependency_change';
210
+ return null;
211
+ }
212
+
213
+ module.exports = {
214
+ PASS_LINE,
215
+ FLAKY_RE,
216
+ DEPENDENCY_RE,
217
+ proofFile,
218
+ readProof,
219
+ recordProof,
220
+ treeHasProof,
221
+ proofChain,
222
+ proofRefusal,
223
+ };