toolroll 0.5.0

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 (626) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1113 -0
  3. package/THIRD_PARTY_NOTICES.md +449 -0
  4. package/dist/accent-colors.d.ts +48 -0
  5. package/dist/accent-colors.js +122 -0
  6. package/dist/action-ledger.d.ts +26 -0
  7. package/dist/action-ledger.js +75 -0
  8. package/dist/agent-fence.d.ts +42 -0
  9. package/dist/agent-fence.js +183 -0
  10. package/dist/agentconfig.d.ts +216 -0
  11. package/dist/agentconfig.js +454 -0
  12. package/dist/api-tokens.d.ts +16 -0
  13. package/dist/api-tokens.js +27 -0
  14. package/dist/approval-policy.d.ts +69 -0
  15. package/dist/approval-policy.js +137 -0
  16. package/dist/approval-rules-ui.d.ts +25 -0
  17. package/dist/approval-rules-ui.js +39 -0
  18. package/dist/assignment-adapters.d.ts +180 -0
  19. package/dist/assignment-adapters.js +239 -0
  20. package/dist/assignment-brief.d.ts +71 -0
  21. package/dist/assignment-brief.js +129 -0
  22. package/dist/assignment-delivery.d.ts +56 -0
  23. package/dist/assignment-delivery.js +160 -0
  24. package/dist/assignment-presentation.d.ts +26 -0
  25. package/dist/assignment-presentation.js +80 -0
  26. package/dist/assignment-status.d.ts +62 -0
  27. package/dist/assignment-status.js +152 -0
  28. package/dist/assignment-ui.d.ts +71 -0
  29. package/dist/assignment-ui.js +103 -0
  30. package/dist/assignment.d.ts +222 -0
  31. package/dist/assignment.js +399 -0
  32. package/dist/attest.d.ts +56 -0
  33. package/dist/attest.js +153 -0
  34. package/dist/backend.d.ts +97 -0
  35. package/dist/backend.js +166 -0
  36. package/dist/backup-ui.d.ts +22 -0
  37. package/dist/backup-ui.js +59 -0
  38. package/dist/backup.d.ts +84 -0
  39. package/dist/backup.js +421 -0
  40. package/dist/beads.d.ts +34 -0
  41. package/dist/beads.js +135 -0
  42. package/dist/bin.d.ts +2 -0
  43. package/dist/bin.js +22 -0
  44. package/dist/board.d.ts +150 -0
  45. package/dist/board.js +210 -0
  46. package/dist/boot-identity.d.ts +63 -0
  47. package/dist/boot-identity.js +99 -0
  48. package/dist/browser/THIRD_PARTY_NOTICES.txt +2295 -0
  49. package/dist/browser/workspace.css +4 -0
  50. package/dist/browser/workspace.js +225 -0
  51. package/dist/browser-crew.d.ts +11 -0
  52. package/dist/browser-crew.js +50 -0
  53. package/dist/browser-shell.d.ts +8 -0
  54. package/dist/browser-shell.js +60 -0
  55. package/dist/browser-workspace.d.ts +780 -0
  56. package/dist/browser-workspace.js +47 -0
  57. package/dist/budget-alerts.d.ts +19 -0
  58. package/dist/budget-alerts.js +46 -0
  59. package/dist/builder.d.ts +370 -0
  60. package/dist/builder.js +3262 -0
  61. package/dist/capscan.d.ts +35 -0
  62. package/dist/capscan.js +113 -0
  63. package/dist/chat-acceptance.d.ts +71 -0
  64. package/dist/chat-acceptance.js +120 -0
  65. package/dist/chat-actions.d.ts +265 -0
  66. package/dist/chat-actions.js +1273 -0
  67. package/dist/chat-channel.d.ts +109 -0
  68. package/dist/chat-channel.js +652 -0
  69. package/dist/chat-continuity.d.ts +32 -0
  70. package/dist/chat-continuity.js +317 -0
  71. package/dist/chat-controls.d.ts +113 -0
  72. package/dist/chat-controls.js +53 -0
  73. package/dist/chat-delivery-state.d.ts +159 -0
  74. package/dist/chat-delivery-state.js +309 -0
  75. package/dist/chat-delivery.d.ts +38 -0
  76. package/dist/chat-delivery.js +693 -0
  77. package/dist/chat-display.d.ts +1 -0
  78. package/dist/chat-display.js +18 -0
  79. package/dist/chat-evidence.d.ts +123 -0
  80. package/dist/chat-evidence.js +207 -0
  81. package/dist/chat-flow.d.ts +44 -0
  82. package/dist/chat-flow.js +215 -0
  83. package/dist/chat-inbox.d.ts +57 -0
  84. package/dist/chat-inbox.js +123 -0
  85. package/dist/chat-polish.d.ts +16 -0
  86. package/dist/chat-polish.js +135 -0
  87. package/dist/chat-review.d.ts +46 -0
  88. package/dist/chat-review.js +98 -0
  89. package/dist/chat-rooms.d.ts +123 -0
  90. package/dist/chat-rooms.js +216 -0
  91. package/dist/chat-task-actions.d.ts +44 -0
  92. package/dist/chat-task-actions.js +79 -0
  93. package/dist/check-progress.d.ts +51 -0
  94. package/dist/check-progress.js +290 -0
  95. package/dist/child-database.d.ts +12 -0
  96. package/dist/child-database.js +30 -0
  97. package/dist/claim.d.ts +688 -0
  98. package/dist/claim.js +1740 -0
  99. package/dist/cli.d.ts +137 -0
  100. package/dist/cli.js +1461 -0
  101. package/dist/codex-limits.d.ts +15 -0
  102. package/dist/codex-limits.js +145 -0
  103. package/dist/coding-context.d.ts +55 -0
  104. package/dist/coding-context.js +92 -0
  105. package/dist/coding-handoff.d.ts +57 -0
  106. package/dist/coding-handoff.js +277 -0
  107. package/dist/coding-provider.d.ts +72 -0
  108. package/dist/coding-provider.js +424 -0
  109. package/dist/coding-shipping-ui.d.ts +4 -0
  110. package/dist/coding-shipping-ui.js +12 -0
  111. package/dist/coding-types.d.ts +62 -0
  112. package/dist/coding-types.js +1 -0
  113. package/dist/coding-ui.d.ts +17 -0
  114. package/dist/coding-ui.js +364 -0
  115. package/dist/coding-update.d.ts +15 -0
  116. package/dist/coding-update.js +149 -0
  117. package/dist/coding-workspace.d.ts +124 -0
  118. package/dist/coding-workspace.js +768 -0
  119. package/dist/container-state.d.ts +5 -0
  120. package/dist/container-state.js +62 -0
  121. package/dist/containment.d.ts +197 -0
  122. package/dist/containment.js +559 -0
  123. package/dist/contest.d.ts +318 -0
  124. package/dist/contest.js +753 -0
  125. package/dist/control-setup.d.ts +35 -0
  126. package/dist/control-setup.js +40 -0
  127. package/dist/control-ui.d.ts +29 -0
  128. package/dist/control-ui.js +53 -0
  129. package/dist/controller-service.d.ts +1 -0
  130. package/dist/controller-service.js +19 -0
  131. package/dist/controller-supervisor.d.ts +30 -0
  132. package/dist/controller-supervisor.js +104 -0
  133. package/dist/converse.d.ts +304 -0
  134. package/dist/converse.js +849 -0
  135. package/dist/coordinator-proposals.d.ts +29 -0
  136. package/dist/coordinator-proposals.js +137 -0
  137. package/dist/coordinator.d.ts +186 -0
  138. package/dist/coordinator.js +447 -0
  139. package/dist/credentials-ui.d.ts +25 -0
  140. package/dist/credentials-ui.js +48 -0
  141. package/dist/daemon.d.ts +209 -0
  142. package/dist/daemon.js +604 -0
  143. package/dist/decision.d.ts +120 -0
  144. package/dist/decision.js +388 -0
  145. package/dist/demo.d.ts +55 -0
  146. package/dist/demo.js +992 -0
  147. package/dist/desktop-access.d.ts +28 -0
  148. package/dist/desktop-access.js +88 -0
  149. package/dist/desktop-bundle.d.ts +16 -0
  150. package/dist/desktop-bundle.js +74 -0
  151. package/dist/desktop-host.d.ts +54 -0
  152. package/dist/desktop-host.js +508 -0
  153. package/dist/desktop-update-gate.d.ts +12 -0
  154. package/dist/desktop-update-gate.js +91 -0
  155. package/dist/desktop-update-recovery.d.ts +14 -0
  156. package/dist/desktop-update-recovery.js +243 -0
  157. package/dist/desktop-update.d.ts +110 -0
  158. package/dist/desktop-update.js +740 -0
  159. package/dist/discord-api.d.ts +20 -0
  160. package/dist/discord-api.js +138 -0
  161. package/dist/discord-chat.d.ts +16 -0
  162. package/dist/discord-chat.js +381 -0
  163. package/dist/discord-settings.d.ts +7 -0
  164. package/dist/discord-settings.js +75 -0
  165. package/dist/discord.d.ts +10 -0
  166. package/dist/discord.js +169 -0
  167. package/dist/discover.d.ts +75 -0
  168. package/dist/discover.js +150 -0
  169. package/dist/dispatch.d.ts +100 -0
  170. package/dist/dispatch.js +311 -0
  171. package/dist/dispose.d.ts +161 -0
  172. package/dist/dispose.js +803 -0
  173. package/dist/email-settings.d.ts +34 -0
  174. package/dist/email-settings.js +50 -0
  175. package/dist/envelope.d.ts +47 -0
  176. package/dist/envelope.js +105 -0
  177. package/dist/evidence-pack.d.ts +185 -0
  178. package/dist/evidence-pack.js +258 -0
  179. package/dist/evidence.d.ts +345 -0
  180. package/dist/evidence.js +782 -0
  181. package/dist/exec.d.ts +285 -0
  182. package/dist/exec.js +1841 -0
  183. package/dist/exhaustion.d.ts +85 -0
  184. package/dist/exhaustion.js +141 -0
  185. package/dist/export-ui.d.ts +4 -0
  186. package/dist/export-ui.js +17 -0
  187. package/dist/export.d.ts +48 -0
  188. package/dist/export.js +305 -0
  189. package/dist/flow-actions.d.ts +77 -0
  190. package/dist/flow-actions.js +203 -0
  191. package/dist/flow-code.d.ts +50 -0
  192. package/dist/flow-code.js +159 -0
  193. package/dist/flow-draft.d.ts +38 -0
  194. package/dist/flow-draft.js +80 -0
  195. package/dist/flow-engine.d.ts +66 -0
  196. package/dist/flow-engine.js +416 -0
  197. package/dist/flow-insights.d.ts +95 -0
  198. package/dist/flow-insights.js +149 -0
  199. package/dist/flow-live.d.ts +24 -0
  200. package/dist/flow-live.js +150 -0
  201. package/dist/flow-people.d.ts +24 -0
  202. package/dist/flow-people.js +91 -0
  203. package/dist/flow-replies.d.ts +48 -0
  204. package/dist/flow-replies.js +113 -0
  205. package/dist/flow-scripts.d.ts +38 -0
  206. package/dist/flow-scripts.js +73 -0
  207. package/dist/flow-secrets.d.ts +13 -0
  208. package/dist/flow-secrets.js +45 -0
  209. package/dist/flow-sort.d.ts +82 -0
  210. package/dist/flow-sort.js +153 -0
  211. package/dist/flow-steps.d.ts +40 -0
  212. package/dist/flow-steps.js +408 -0
  213. package/dist/flow-triggers.d.ts +244 -0
  214. package/dist/flow-triggers.js +959 -0
  215. package/dist/flows-ui.d.ts +20 -0
  216. package/dist/flows-ui.js +175 -0
  217. package/dist/flows.d.ts +229 -0
  218. package/dist/flows.js +968 -0
  219. package/dist/fonts.d.ts +19 -0
  220. package/dist/fonts.js +19 -0
  221. package/dist/gaps.d.ts +35 -0
  222. package/dist/gaps.js +101 -0
  223. package/dist/gate-failure.d.ts +40 -0
  224. package/dist/gate-failure.js +66 -0
  225. package/dist/git.d.ts +54 -0
  226. package/dist/git.js +94 -0
  227. package/dist/google-mail.d.ts +58 -0
  228. package/dist/google-mail.js +151 -0
  229. package/dist/grant.d.ts +136 -0
  230. package/dist/grant.js +238 -0
  231. package/dist/graph.d.ts +164 -0
  232. package/dist/graph.js +383 -0
  233. package/dist/guides.d.ts +22 -0
  234. package/dist/guides.js +363 -0
  235. package/dist/held.d.ts +150 -0
  236. package/dist/held.js +799 -0
  237. package/dist/invoke.d.ts +112 -0
  238. package/dist/invoke.js +780 -0
  239. package/dist/issues.d.ts +41 -0
  240. package/dist/issues.js +131 -0
  241. package/dist/job-object-helper.ps1 +252 -0
  242. package/dist/jsonl-discriminants.d.ts +11 -0
  243. package/dist/jsonl-discriminants.js +93 -0
  244. package/dist/keys.d.ts +104 -0
  245. package/dist/keys.js +229 -0
  246. package/dist/kits-ui.d.ts +17 -0
  247. package/dist/kits-ui.js +46 -0
  248. package/dist/kits.d.ts +86 -0
  249. package/dist/kits.js +215 -0
  250. package/dist/knowledge-cli.d.ts +101 -0
  251. package/dist/knowledge-cli.js +92 -0
  252. package/dist/knowledge-ui.d.ts +24 -0
  253. package/dist/knowledge-ui.js +40 -0
  254. package/dist/lead-context.d.ts +8 -0
  255. package/dist/lead-context.js +56 -0
  256. package/dist/lead-follow.d.ts +22 -0
  257. package/dist/lead-follow.js +167 -0
  258. package/dist/lead-status.d.ts +75 -0
  259. package/dist/lead-status.js +285 -0
  260. package/dist/ledger-chain.d.ts +46 -0
  261. package/dist/ledger-chain.js +187 -0
  262. package/dist/ledger-csv.d.ts +4 -0
  263. package/dist/ledger-csv.js +11 -0
  264. package/dist/ledger-view.d.ts +24 -0
  265. package/dist/ledger-view.js +58 -0
  266. package/dist/limits-ui.d.ts +26 -0
  267. package/dist/limits-ui.js +67 -0
  268. package/dist/link.d.ts +72 -0
  269. package/dist/link.js +217 -0
  270. package/dist/live.d.ts +96 -0
  271. package/dist/live.js +366 -0
  272. package/dist/liveness.d.ts +30 -0
  273. package/dist/liveness.js +42 -0
  274. package/dist/log.d.ts +7 -0
  275. package/dist/log.js +25 -0
  276. package/dist/mailbox.d.ts +57 -0
  277. package/dist/mailbox.js +150 -0
  278. package/dist/maintenance.d.ts +11 -0
  279. package/dist/maintenance.js +35 -0
  280. package/dist/mate-cli.d.ts +52 -0
  281. package/dist/mate-cli.js +345 -0
  282. package/dist/mate-contract.d.ts +10 -0
  283. package/dist/mate-contract.js +30 -0
  284. package/dist/mate-doors.d.ts +69 -0
  285. package/dist/mate-doors.js +548 -0
  286. package/dist/mate-progress.d.ts +29 -0
  287. package/dist/mate-progress.js +121 -0
  288. package/dist/mate-tools.d.ts +94 -0
  289. package/dist/mate-tools.js +1878 -0
  290. package/dist/mate.d.ts +92 -0
  291. package/dist/mate.js +435 -0
  292. package/dist/mcp-connect.d.ts +103 -0
  293. package/dist/mcp-connect.js +252 -0
  294. package/dist/mcp.d.ts +27 -0
  295. package/dist/mcp.js +651 -0
  296. package/dist/memory-cli.d.ts +466 -0
  297. package/dist/memory-cli.js +111 -0
  298. package/dist/memory-pass.d.ts +160 -0
  299. package/dist/memory-pass.js +409 -0
  300. package/dist/metrics.d.ts +3 -0
  301. package/dist/metrics.js +56 -0
  302. package/dist/mobile-viewport.d.ts +3 -0
  303. package/dist/mobile-viewport.js +37 -0
  304. package/dist/model-catalog.d.ts +118 -0
  305. package/dist/model-catalog.js +376 -0
  306. package/dist/models-cli.d.ts +102 -0
  307. package/dist/models-cli.js +66 -0
  308. package/dist/models-ui.d.ts +34 -0
  309. package/dist/models-ui.js +68 -0
  310. package/dist/modes.d.ts +80 -0
  311. package/dist/modes.js +182 -0
  312. package/dist/monitoring-settings.d.ts +44 -0
  313. package/dist/monitoring-settings.js +173 -0
  314. package/dist/monitoring-ui.d.ts +20 -0
  315. package/dist/monitoring-ui.js +51 -0
  316. package/dist/monitoring.d.ts +34 -0
  317. package/dist/monitoring.js +221 -0
  318. package/dist/names.d.ts +67 -0
  319. package/dist/names.js +102 -0
  320. package/dist/observations.d.ts +40 -0
  321. package/dist/observations.js +211 -0
  322. package/dist/oidc.d.ts +71 -0
  323. package/dist/oidc.js +182 -0
  324. package/dist/onboard.d.ts +108 -0
  325. package/dist/onboard.js +325 -0
  326. package/dist/openrouter-models.d.ts +30 -0
  327. package/dist/openrouter-models.js +120 -0
  328. package/dist/operate.d.ts +118 -0
  329. package/dist/operate.js +11321 -0
  330. package/dist/peek-cli.d.ts +97 -0
  331. package/dist/peek-cli.js +215 -0
  332. package/dist/peek.d.ts +134 -0
  333. package/dist/peek.js +578 -0
  334. package/dist/phase-routing.d.ts +307 -0
  335. package/dist/phase-routing.js +658 -0
  336. package/dist/plan-auto.d.ts +14 -0
  337. package/dist/plan-auto.js +92 -0
  338. package/dist/plan.d.ts +186 -0
  339. package/dist/plan.js +401 -0
  340. package/dist/planner-source.d.ts +251 -0
  341. package/dist/planner-source.js +460 -0
  342. package/dist/planner.d.ts +75 -0
  343. package/dist/planner.js +992 -0
  344. package/dist/policy-ui.d.ts +20 -0
  345. package/dist/policy-ui.js +41 -0
  346. package/dist/policy.d.ts +111 -0
  347. package/dist/policy.js +231 -0
  348. package/dist/prepared-evidence.d.ts +16 -0
  349. package/dist/prepared-evidence.js +96 -0
  350. package/dist/pricing.d.ts +45 -0
  351. package/dist/pricing.js +77 -0
  352. package/dist/principal.d.ts +42 -0
  353. package/dist/principal.js +82 -0
  354. package/dist/probe.d.ts +46 -0
  355. package/dist/probe.js +79 -0
  356. package/dist/process-custody.d.ts +17 -0
  357. package/dist/process-custody.js +91 -0
  358. package/dist/process-liveness.d.ts +10 -0
  359. package/dist/process-liveness.js +81 -0
  360. package/dist/process-recovery-anchor.d.ts +75 -0
  361. package/dist/process-recovery-anchor.js +188 -0
  362. package/dist/process-recovery-coalition.d.ts +25 -0
  363. package/dist/process-recovery-coalition.js +105 -0
  364. package/dist/process-recovery-eligibility.d.ts +178 -0
  365. package/dist/process-recovery-eligibility.js +170 -0
  366. package/dist/process-recovery-native.d.ts +172 -0
  367. package/dist/process-recovery-native.js +592 -0
  368. package/dist/process-recovery-provenance.d.ts +163 -0
  369. package/dist/process-recovery-provenance.js +292 -0
  370. package/dist/process-recovery-services.d.ts +56 -0
  371. package/dist/process-recovery-services.js +191 -0
  372. package/dist/process-recovery-settlement.d.ts +32 -0
  373. package/dist/process-recovery-settlement.js +132 -0
  374. package/dist/process-recovery.d.ts +41 -0
  375. package/dist/process-recovery.js +91 -0
  376. package/dist/process-tree.d.ts +41 -0
  377. package/dist/process-tree.js +264 -0
  378. package/dist/project-access.d.ts +14 -0
  379. package/dist/project-access.js +34 -0
  380. package/dist/project-cli.d.ts +18 -0
  381. package/dist/project-cli.js +104 -0
  382. package/dist/project-delete-ui.d.ts +22 -0
  383. package/dist/project-delete-ui.js +48 -0
  384. package/dist/project-delete.d.ts +55 -0
  385. package/dist/project-delete.js +413 -0
  386. package/dist/project-knowledge.d.ts +111 -0
  387. package/dist/project-knowledge.js +241 -0
  388. package/dist/project-learning.d.ts +100 -0
  389. package/dist/project-learning.js +438 -0
  390. package/dist/project-memory.d.ts +81 -0
  391. package/dist/project-memory.js +264 -0
  392. package/dist/project-skills.d.ts +152 -0
  393. package/dist/project-skills.js +660 -0
  394. package/dist/project-tools.d.ts +219 -0
  395. package/dist/project-tools.js +796 -0
  396. package/dist/project.d.ts +61 -0
  397. package/dist/project.js +125 -0
  398. package/dist/prompt.d.ts +22 -0
  399. package/dist/prompt.js +72 -0
  400. package/dist/proof.d.ts +513 -0
  401. package/dist/proof.js +1140 -0
  402. package/dist/proposal.d.ts +82 -0
  403. package/dist/proposal.js +210 -0
  404. package/dist/provider-connection.d.ts +22 -0
  405. package/dist/provider-connection.js +98 -0
  406. package/dist/provider-limits.d.ts +35 -0
  407. package/dist/provider-limits.js +95 -0
  408. package/dist/provider.d.ts +279 -0
  409. package/dist/provider.js +935 -0
  410. package/dist/publish.d.ts +107 -0
  411. package/dist/publish.js +650 -0
  412. package/dist/pulls.d.ts +118 -0
  413. package/dist/pulls.js +240 -0
  414. package/dist/push.d.ts +95 -0
  415. package/dist/push.js +353 -0
  416. package/dist/quality.d.ts +8 -0
  417. package/dist/quality.js +6 -0
  418. package/dist/recipe-ui.d.ts +18 -0
  419. package/dist/recipe-ui.js +152 -0
  420. package/dist/recipes.d.ts +81 -0
  421. package/dist/recipes.js +332 -0
  422. package/dist/remote.d.ts +67 -0
  423. package/dist/remote.js +122 -0
  424. package/dist/render.d.ts +64 -0
  425. package/dist/render.js +587 -0
  426. package/dist/report-summary.d.ts +13 -0
  427. package/dist/report-summary.js +15 -0
  428. package/dist/repos.d.ts +65 -0
  429. package/dist/repos.js +277 -0
  430. package/dist/repository-context-ui.d.ts +2 -0
  431. package/dist/repository-context-ui.js +10 -0
  432. package/dist/repository-context.d.ts +67 -0
  433. package/dist/repository-context.js +376 -0
  434. package/dist/restart-certification.d.ts +134 -0
  435. package/dist/restart-certification.js +226 -0
  436. package/dist/result-actions.d.ts +28 -0
  437. package/dist/result-actions.js +270 -0
  438. package/dist/result-completion.d.ts +18 -0
  439. package/dist/result-completion.js +66 -0
  440. package/dist/result-review.d.ts +213 -0
  441. package/dist/result-review.js +430 -0
  442. package/dist/retention-ui.d.ts +12 -0
  443. package/dist/retention-ui.js +39 -0
  444. package/dist/retention.d.ts +72 -0
  445. package/dist/retention.js +288 -0
  446. package/dist/review-context.d.ts +243 -0
  447. package/dist/review-context.js +1043 -0
  448. package/dist/review-evidence.d.ts +33 -0
  449. package/dist/review-evidence.js +56 -0
  450. package/dist/reviewer.d.ts +97 -0
  451. package/dist/reviewer.js +202 -0
  452. package/dist/routine.d.ts +228 -0
  453. package/dist/routine.js +764 -0
  454. package/dist/runner.d.ts +266 -0
  455. package/dist/runner.js +399 -0
  456. package/dist/scan.d.ts +32 -0
  457. package/dist/scan.js +92 -0
  458. package/dist/scope.d.ts +695 -0
  459. package/dist/scope.js +1176 -0
  460. package/dist/scout-report.d.ts +42 -0
  461. package/dist/scout-report.js +108 -0
  462. package/dist/scout.d.ts +69 -0
  463. package/dist/scout.js +351 -0
  464. package/dist/serve.d.ts +313 -0
  465. package/dist/serve.js +21776 -0
  466. package/dist/session-brief.d.ts +8 -0
  467. package/dist/session-brief.js +24 -0
  468. package/dist/session-cli.d.ts +13 -0
  469. package/dist/session-cli.js +284 -0
  470. package/dist/session-contract.d.ts +183 -0
  471. package/dist/session-contract.js +122 -0
  472. package/dist/session-http.d.ts +19 -0
  473. package/dist/session-http.js +154 -0
  474. package/dist/session-server.d.ts +12 -0
  475. package/dist/session-server.js +37 -0
  476. package/dist/session-service.d.ts +22 -0
  477. package/dist/session-service.js +157 -0
  478. package/dist/setup-guide.d.ts +39 -0
  479. package/dist/setup-guide.js +93 -0
  480. package/dist/sign-in-guard.d.ts +45 -0
  481. package/dist/sign-in-guard.js +74 -0
  482. package/dist/skills-ui.d.ts +11 -0
  483. package/dist/skills-ui.js +58 -0
  484. package/dist/skills.d.ts +97 -0
  485. package/dist/skills.js +276 -0
  486. package/dist/slack-api.d.ts +55 -0
  487. package/dist/slack-api.js +229 -0
  488. package/dist/slack-chat.d.ts +28 -0
  489. package/dist/slack-chat.js +413 -0
  490. package/dist/slack-settings.d.ts +7 -0
  491. package/dist/slack-settings.js +75 -0
  492. package/dist/slack-state.d.ts +13 -0
  493. package/dist/slack-state.js +9 -0
  494. package/dist/slack.d.ts +13 -0
  495. package/dist/slack.js +167 -0
  496. package/dist/spend-ui.d.ts +28 -0
  497. package/dist/spend-ui.js +95 -0
  498. package/dist/spend.d.ts +142 -0
  499. package/dist/spend.js +313 -0
  500. package/dist/sqlite-runtime.d.ts +3 -0
  501. package/dist/sqlite-runtime.js +6 -0
  502. package/dist/sso-settings.d.ts +33 -0
  503. package/dist/sso-settings.js +103 -0
  504. package/dist/sso-ui.d.ts +22 -0
  505. package/dist/sso-ui.js +39 -0
  506. package/dist/storage.d.ts +15 -0
  507. package/dist/storage.js +84 -0
  508. package/dist/store.d.ts +7754 -0
  509. package/dist/store.js +23279 -0
  510. package/dist/structured-output.d.ts +58 -0
  511. package/dist/structured-output.js +103 -0
  512. package/dist/style-asset.d.ts +7 -0
  513. package/dist/style-asset.js +41 -0
  514. package/dist/subscription-chat.d.ts +46 -0
  515. package/dist/subscription-chat.js +275 -0
  516. package/dist/summary.d.ts +33 -0
  517. package/dist/summary.js +71 -0
  518. package/dist/supervisor.mjs +297 -0
  519. package/dist/surface.d.ts +63 -0
  520. package/dist/surface.js +352 -0
  521. package/dist/sync.d.ts +94 -0
  522. package/dist/sync.js +279 -0
  523. package/dist/task-composer.d.ts +13 -0
  524. package/dist/task-composer.js +52 -0
  525. package/dist/task-control.d.ts +165 -0
  526. package/dist/task-control.js +214 -0
  527. package/dist/task-outcome-cli.d.ts +10 -0
  528. package/dist/task-outcome-cli.js +92 -0
  529. package/dist/task-text.d.ts +30 -0
  530. package/dist/task-text.js +41 -0
  531. package/dist/team-cli.d.ts +15 -0
  532. package/dist/team-cli.js +618 -0
  533. package/dist/team-contract.d.ts +92 -0
  534. package/dist/team-contract.js +1 -0
  535. package/dist/team-http.d.ts +17 -0
  536. package/dist/team-http.js +144 -0
  537. package/dist/team-leads.d.ts +81 -0
  538. package/dist/team-leads.js +565 -0
  539. package/dist/team-runtime.d.ts +27 -0
  540. package/dist/team-runtime.js +264 -0
  541. package/dist/team-ui.d.ts +3 -0
  542. package/dist/team-ui.js +9 -0
  543. package/dist/team-updates.d.ts +8 -0
  544. package/dist/team-updates.js +57 -0
  545. package/dist/teammate-admin.d.ts +53 -0
  546. package/dist/teammate-admin.js +111 -0
  547. package/dist/teammate-desk.d.ts +60 -0
  548. package/dist/teammate-desk.js +154 -0
  549. package/dist/teammate-memory.d.ts +64 -0
  550. package/dist/teammate-memory.js +196 -0
  551. package/dist/teammate-question.d.ts +59 -0
  552. package/dist/teammate-question.js +175 -0
  553. package/dist/teammate-tools.d.ts +108 -0
  554. package/dist/teammate-tools.js +271 -0
  555. package/dist/teammate-week.d.ts +68 -0
  556. package/dist/teammate-week.js +132 -0
  557. package/dist/teammate-work.d.ts +76 -0
  558. package/dist/teammate-work.js +280 -0
  559. package/dist/teammates-ui.d.ts +20 -0
  560. package/dist/teammates-ui.js +166 -0
  561. package/dist/teammates.d.ts +180 -0
  562. package/dist/teammates.js +253 -0
  563. package/dist/teams-api.d.ts +33 -0
  564. package/dist/teams-api.js +204 -0
  565. package/dist/teams-chat.d.ts +26 -0
  566. package/dist/teams-chat.js +215 -0
  567. package/dist/teams-settings.d.ts +12 -0
  568. package/dist/teams-settings.js +57 -0
  569. package/dist/teams.d.ts +21 -0
  570. package/dist/teams.js +124 -0
  571. package/dist/telegram-flow.d.ts +49 -0
  572. package/dist/telegram-flow.js +110 -0
  573. package/dist/telegram-mate.d.ts +137 -0
  574. package/dist/telegram-mate.js +707 -0
  575. package/dist/telegram-progress.d.ts +14 -0
  576. package/dist/telegram-progress.js +129 -0
  577. package/dist/telegram-settings.d.ts +9 -0
  578. package/dist/telegram-settings.js +36 -0
  579. package/dist/telegram-status.d.ts +75 -0
  580. package/dist/telegram-status.js +265 -0
  581. package/dist/telegram-team.d.ts +85 -0
  582. package/dist/telegram-team.js +359 -0
  583. package/dist/telegram.d.ts +230 -0
  584. package/dist/telegram.js +1931 -0
  585. package/dist/templates.d.ts +65 -0
  586. package/dist/templates.js +118 -0
  587. package/dist/tool-launcher.d.ts +1 -0
  588. package/dist/tool-launcher.js +101 -0
  589. package/dist/tools-ui.d.ts +30 -0
  590. package/dist/tools-ui.js +40 -0
  591. package/dist/transitions-recipes.d.ts +1 -0
  592. package/dist/transitions-recipes.js +203 -0
  593. package/dist/tree-proof.d.ts +34 -0
  594. package/dist/tree-proof.js +58 -0
  595. package/dist/verification-evidence.d.ts +44 -0
  596. package/dist/verification-evidence.js +238 -0
  597. package/dist/version.d.ts +2 -0
  598. package/dist/version.js +10 -0
  599. package/dist/webhooks.d.ts +91 -0
  600. package/dist/webhooks.js +283 -0
  601. package/dist/work-index.d.ts +68 -0
  602. package/dist/work-index.js +384 -0
  603. package/dist/work-summary.d.ts +57 -0
  604. package/dist/work-summary.js +116 -0
  605. package/dist/workspace-motion.d.ts +4 -0
  606. package/dist/workspace-motion.js +133 -0
  607. package/dist/workspace-revision.d.ts +19 -0
  608. package/dist/workspace-revision.js +92 -0
  609. package/dist/workspace-ui.d.ts +204 -0
  610. package/dist/workspace-ui.js +411 -0
  611. package/dist/worktree-notices.d.ts +13 -0
  612. package/dist/worktree-notices.js +17 -0
  613. package/dist/worktree.d.ts +249 -0
  614. package/dist/worktree.js +720 -0
  615. package/package.json +122 -0
  616. package/scripts/canary-assertions.mjs +42 -0
  617. package/scripts/crash-canary.mjs +253 -0
  618. package/scripts/fixtures/crash-process.mjs +74 -0
  619. package/scripts/fixtures/pilot-scenarios.mjs +85 -0
  620. package/scripts/fixtures/restart-service.mjs +27 -0
  621. package/scripts/launchd-certification.mjs +143 -0
  622. package/scripts/pilot.mjs +121 -0
  623. package/scripts/proof-preflight.mjs +140 -0
  624. package/scripts/provider-canary.mjs +379 -0
  625. package/scripts/recovery-canary.mjs +135 -0
  626. package/scripts/restart-certification.mjs +98 -0
package/package.json ADDED
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "toolroll",
3
+ "version": "0.5.0",
4
+ "description": "A control plane for unattended coding agents — queue tasks, walk away, come back to pull requests. Agents build in leased worktrees, never touch your default branch, and interrupt you only for decisions that need a human.",
5
+ "directories": {
6
+ "doc": "docs"
7
+ },
8
+ "scripts": {
9
+ "build": "tsc -p tsconfig.build.json && node scripts/browser-build.mjs && node scripts/postbuild.mjs",
10
+ "test": "vitest run",
11
+ "dev": "tsx src/cli.ts",
12
+ "typecheck": "tsc --noEmit && tsc -p tsconfig.browser.json",
13
+ "certify:provider": "npm run build && node scripts/provider-canary.mjs",
14
+ "certify:recovery": "npm run build && node scripts/recovery-canary.mjs",
15
+ "certify:crash": "npm run build && node scripts/crash-canary.mjs",
16
+ "certify:pilot": "npm run build && node scripts/pilot.mjs",
17
+ "prepublishOnly": "npm run typecheck && npm run build",
18
+ "nul-scan": "node scripts/nul-scan.mjs",
19
+ "proof-preflight": "node scripts/proof-preflight.mjs",
20
+ "fixture:v47": "node scripts/derive-v47-fixture.mjs",
21
+ "build:desktop": "node scripts/build-desktop.mjs",
22
+ "certify:restart": "npm run build && node scripts/restart-certification.mjs",
23
+ "certify:launchd": "npm run build && node scripts/launchd-certification.mjs",
24
+ "test:native-containment": "vitest run src/containment-native.test.ts src/containment.test.ts",
25
+ "build:browser": "node scripts/browser-build.mjs",
26
+ "e2e:flows": "npm run build && node scripts/flows-e2e.mjs",
27
+ "e2e:app": "npm run build && node scripts/app-e2e.mjs",
28
+ "e2e:app:parallel": "npm run build && node scripts/e2e-parallel.mjs scripts/app-e2e.mjs",
29
+ "e2e": "npm run build && node scripts/flows-e2e.mjs && node scripts/app-e2e.mjs"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "git+https://github.com/ap9000/toolroll.git"
34
+ },
35
+ "keywords": [
36
+ "agents",
37
+ "orchestration",
38
+ "claude-code",
39
+ "codex",
40
+ "git-worktree",
41
+ "ai-agents",
42
+ "autonomous-agents",
43
+ "agent-orchestration",
44
+ "control-plane",
45
+ "cli",
46
+ "task-queue",
47
+ "llm",
48
+ "openrouter",
49
+ "telegram",
50
+ "unattended"
51
+ ],
52
+ "license": "MIT",
53
+ "type": "module",
54
+ "bugs": {
55
+ "url": "https://github.com/ap9000/toolroll/issues"
56
+ },
57
+ "homepage": "https://github.com/ap9000/toolroll#readme",
58
+ "bin": {
59
+ "toolroll": "dist/bin.js",
60
+ "standing-orders": "dist/bin.js"
61
+ },
62
+ "engines": {
63
+ "node": ">=22.13.0"
64
+ },
65
+ "devDependencies": {
66
+ "@tailwindcss/cli": "4.3.3",
67
+ "@types/mailparser": "3.4.6",
68
+ "@types/node": "26.2.0",
69
+ "@types/react": "19.3.0",
70
+ "@types/react-dom": "19.3.0",
71
+ "esbuild": "0.28.2",
72
+ "happy-dom": "20.12.0",
73
+ "tailwindcss": "4.3.3",
74
+ "tsx": "4.23.12",
75
+ "typescript": "7.0.2",
76
+ "vitest": "4.1.10"
77
+ },
78
+ "files": [
79
+ "dist",
80
+ "scripts/provider-canary.mjs",
81
+ "scripts/canary-assertions.mjs",
82
+ "scripts/recovery-canary.mjs",
83
+ "scripts/crash-canary.mjs",
84
+ "scripts/fixtures/crash-process.mjs",
85
+ "scripts/pilot.mjs",
86
+ "scripts/fixtures/pilot-scenarios.mjs",
87
+ "scripts/proof-preflight.mjs",
88
+ "scripts/restart-certification.mjs",
89
+ "scripts/launchd-certification.mjs",
90
+ "scripts/fixtures/restart-service.mjs",
91
+ "THIRD_PARTY_NOTICES.md"
92
+ ],
93
+ "dependencies": {
94
+ "@radix-ui/react-collapsible": "1.1.20",
95
+ "@radix-ui/react-dialog": "1.1.23",
96
+ "@radix-ui/react-dropdown-menu": "2.1.24",
97
+ "@radix-ui/react-label": "2.1.15",
98
+ "@radix-ui/react-radio-group": "1.4.7",
99
+ "@radix-ui/react-select": "2.3.7",
100
+ "@radix-ui/react-separator": "1.1.15",
101
+ "@radix-ui/react-slot": "1.3.3",
102
+ "@radix-ui/react-switch": "1.3.7",
103
+ "@radix-ui/react-tabs": "1.1.21",
104
+ "@radix-ui/react-tooltip": "1.2.16",
105
+ "@slack/socket-mode": "3.0.1",
106
+ "@xyflow/react": "12.11.6",
107
+ "class-variance-authority": "0.7.1",
108
+ "clsx": "2.1.1",
109
+ "discord.js": "14.27.0",
110
+ "imapflow": "2.0.6",
111
+ "lucide-react": "1.47.0",
112
+ "mailparser": "3.9.28",
113
+ "nodemailer": "10.0.10",
114
+ "react": "19.3.0",
115
+ "react-dom": "19.3.0",
116
+ "sonner": "2.0.8",
117
+ "tailwind-merge": "3.7.0",
118
+ "typescript-parser": "npm:typescript@6.0.3",
119
+ "use-stick-to-bottom": "1.1.6",
120
+ "yaml": "2.9.1"
121
+ }
122
+ }
@@ -0,0 +1,42 @@
1
+ import assert from "node:assert/strict";
2
+ import { writeFileSync } from "node:fs";
3
+
4
+ /** Disposable certification projects use the same scoped lead and public CLI
5
+ * as a real agent. The token stays in a private file, outside the certificate. */
6
+ export async function createFixtureLead(cli, { repo, auth, tokenFile }) {
7
+ const created = await cli(["coordinator", "mint", "certification-lead", "--repo", repo, ...auth]);
8
+ assert.equal(typeof created.token, "string", "missing scoped lead credential");
9
+ writeFileSync(tokenFile, created.token, { mode: 0o600, flag: "wx" });
10
+ return tokenFile;
11
+ }
12
+
13
+ export function assertReadyAssignment(assignment, expected = {}) {
14
+ assert.equal(assignment?.state, "ready-to-check", "finished work must be Ready for its lead");
15
+ assert(assignment.receipt, "the exact saved result is missing");
16
+ assert.match(assignment.receipt.digest, /^[a-f0-9]{64}$/, "the result has no exact receipt");
17
+ assert.equal(assignment.receipt.checks.status, "passed", "the approved project check did not pass");
18
+ assert.equal(assignment.receipt.checks.exitCode, 0);
19
+ if (expected.head !== undefined) assert.equal(assignment.receipt.head, expected.head, "wrong candidate");
20
+ if (expected.runId !== undefined) assert.equal(assignment.receipt.runId, expected.runId, "wrong result");
21
+ }
22
+
23
+ /** Call only after the fixture's actual output and changes were inspected.
24
+ * Completing a receipt must retain check outcomes and cannot execute work. */
25
+ export async function completeFixtureAssignment(cli, task, tokenFile, expected = {}) {
26
+ const auth = ["--token-file", tokenFile];
27
+ await cli(["assignment", "claim", task, ...auth]);
28
+ const ready = (await cli(["assignment", "show", task, ...auth])).result;
29
+ assertReadyAssignment(ready, expected);
30
+ const before = await cli(["task", "show", task]);
31
+ const complete = (await cli(["assignment", "check", task, "--digest", ready.receipt.digest, ...auth])).result;
32
+ assert.equal(complete.state, "complete");
33
+ assert.equal(complete.completion.digest, ready.receipt.digest);
34
+ assert.deepEqual(complete.receipt, ready.receipt, "completion changed the saved work or checks");
35
+ const repeated = (await cli(["assignment", "check", task, "--digest", ready.receipt.digest, ...auth])).result;
36
+ assert.deepEqual(repeated.completion, complete.completion, "repeated completion was not idempotent");
37
+ const after = await cli(["task", "show", task]);
38
+ assert.deepEqual(after.runs, before.runs, "marking Complete executed or rewrote a run");
39
+ assert.deepEqual(after.proofMatrix, before.proofMatrix, "marking Complete rewrote assessment history");
40
+ assert.equal(after.proofVerdict, before.proofVerdict, "marking Complete changed check/assessment outcome");
41
+ return { state: complete.state, runId: complete.receipt.runId, head: complete.receipt.head, checks: complete.receipt.checks, digest: complete.receipt.digest };
42
+ }
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env node
2
+ /** Kill actual CLI workers at real process barriers; no clock injection or model calls.
3
+ * Recovery retains the original work, then exposes Ready for explicit lead completion. */
4
+ import assert from "node:assert/strict";
5
+ import { spawn, execFileSync } from "node:child_process";
6
+ import { createHash } from "node:crypto";
7
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync, mkdtempSync, realpathSync, readdirSync } from "node:fs";
8
+ import { join, dirname, resolve } from "node:path";
9
+ import { tmpdir } from "node:os";
10
+ import { fileURLToPath } from "node:url";
11
+ import { DatabaseSync } from "node:sqlite";
12
+ import { createFixtureLead, completeFixtureAssignment } from "./canary-assertions.mjs";
13
+
14
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
15
+ const fixture = join(root, "scripts/fixtures/crash-process.mjs");
16
+ const bin = join(root, "dist/bin.js");
17
+ const stages = ["planning", "setup", "building", "after-commit", "verification"];
18
+ let selected = stages;
19
+ let rounds = 1;
20
+ let concurrency = 6;
21
+ let stopBeforeCrash = false;
22
+ let autoApprove = false;
23
+ let output = resolve("output/certification/crash.json");
24
+ for (let i = 2; i < process.argv.length; i++) {
25
+ const arg = process.argv[i];
26
+ if (arg === "--stage") selected = [process.argv[++i]];
27
+ else if (arg === "--rounds") rounds = Number(process.argv[++i]);
28
+ else if (arg === "--concurrency") concurrency = Number(process.argv[++i]);
29
+ else if (arg === "--stop-before-crash") stopBeforeCrash = true;
30
+ else if (arg === "--auto-approve") autoApprove = true;
31
+ else if (arg === "--output") output = resolve(process.argv[++i]);
32
+ else if (arg === "--help") { console.log("npm run certify:crash -- [--stage planning|setup|building|after-commit|verification] [--auto-approve (planning only)] [--stop-before-crash (building only)] [--rounds 1] [--concurrency 6] [--output file]"); process.exit(0); }
33
+ else throw new Error(`unknown argument ${arg}`);
34
+ }
35
+ assert(process.platform !== "win32", "SIGKILL fixture requires macOS or Linux; physical Windows remains a separate gate");
36
+ assert(selected.every(stage => stages.includes(stage)) && Number.isInteger(rounds) && rounds >= 1 && rounds <= 20, "invalid stages/rounds");
37
+ assert(Number.isInteger(concurrency) && concurrency >= 1 && concurrency <= 20, "concurrency is 1..20");
38
+ assert(!stopBeforeCrash || selected.length === 1 && selected[0] === "building", "--stop-before-crash requires --stage building");
39
+ assert(!autoApprove || selected.length === 1 && selected[0] === "planning", "--auto-approve requires --stage planning");
40
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
41
+ const base = realpathSync(mkdtempSync(join(tmpdir(), "standing-orders-crash-canary-")));
42
+ const gitBinary = execFileSync("/bin/sh", ["-c", "command -v git"], { encoding: "utf8" }).trim();
43
+ const revision = execFileSync(gitBinary, ["rev-parse", "HEAD"], { cwd: root, encoding: "utf8" }).trim();
44
+ const hash = () => {
45
+ const h = createHash("sha256");
46
+ const visit = path => { for (const name of readdirSync(path, { withFileTypes: true }).sort((a,b) => a.name.localeCompare(b.name))) {
47
+ const p = join(path, name.name); if (name.isDirectory()) visit(p); else h.update(p.slice(root.length)).update(readFileSync(p));
48
+ }};
49
+ visit(join(root, "dist"));
50
+ for (const file of ["scripts/crash-canary.mjs", "scripts/fixtures/crash-process.mjs", "scripts/canary-assertions.mjs", "package.json"]) h.update(file).update(readFileSync(join(root, file)));
51
+ return h.digest("hex");
52
+ };
53
+ const runtime = hash();
54
+ const started = Date.now();
55
+ const cases = [];
56
+ const children = new Set();
57
+ const alive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } };
58
+ function start(args, options) {
59
+ const child = spawn(process.execPath, [bin, ...args], { ...options, stdio: ["ignore", "pipe", "pipe"] });
60
+ children.add(child);
61
+ let stdout = "", stderr = "";
62
+ child.stdout.on("data", c => { stdout += c; }); child.stderr.on("data", c => { stderr += c; });
63
+ const done = new Promise((resolvePromise, reject) => { child.on("error", reject); child.on("close", (code, signal) => { children.delete(child); resolvePromise({ code, signal, stdout, stderr }); }); });
64
+ return { child, done };
65
+ }
66
+ async function until(predicate, ms, label) {
67
+ const end = Date.now() + ms;
68
+ while (Date.now() < end) { const value = predicate(); if (value) return value; await sleep(25); }
69
+ throw new Error(`timed out waiting for ${label}`);
70
+ }
71
+ async function one(stage, round) {
72
+ const dir = join(base, `${stage}-${round}`); mkdirSync(dir);
73
+ const repo = join(dir, "repo"), control = join(dir, "control"), tools = join(dir, "bin"), home = join(dir, "home");
74
+ for (const path of [repo, control, tools, home]) mkdirSync(path);
75
+ const db = join(dir, "orders.db"), pool = join(dir, "pool");
76
+ const quote = s => "'" + s.replaceAll("'", "'\\''") + "'";
77
+ // The approved commands are short wrappers in the control directory: a
78
+ // proof's check ref is capped at 300 bytes, and the literal node + fixture
79
+ // + control path exceeds that from a long checkout. `exec` keeps the
80
+ // fixture's own pid as the checkpoint process.
81
+ const wrapper = (name, kind) => {
82
+ const path = join(tools, name);
83
+ writeFileSync(path, `#!/bin/sh\nexec ${quote(process.execPath)} ${quote(fixture)} ${kind} ${quote(control)} "$@"\n`);
84
+ chmodSync(path, 0o700);
85
+ return quote(path);
86
+ };
87
+ const check = wrapper("check", "check");
88
+ const acceptance = [
89
+ { id: "c1", statement: "The result file contains recovered and a newline.", evidence: ["changed-path"] },
90
+ { id: "c2", statement: "The approved check passes.", evidence: ["check"] },
91
+ ];
92
+ writeFileSync(join(control, "config.json"), JSON.stringify({ stage, check, git: gitBinary, acceptance }));
93
+ for (const kind of ["claude", "git"]) {
94
+ const path = join(tools, kind);
95
+ writeFileSync(path, `#!${process.execPath}\nprocess.env.SO_CRASH_KIND=${JSON.stringify(kind)}; await import(${JSON.stringify(fixture)});\n`);
96
+ chmodSync(path, 0o700);
97
+ }
98
+ const env = { PATH: `${tools}:${process.env.PATH}`, HOME: home, XDG_CONFIG_HOME: join(home, ".config"), TMPDIR: dir, SO_CRASH_CONTROL: control, NO_COLOR: "1" };
99
+ const opts = { cwd: repo, env };
100
+ const git = args => execFileSync(gitBinary, args, { cwd: repo, env, encoding: "utf8" }).trim();
101
+ git(["init", "-q", "-b", "main"]); git(["config", "user.email", "fixture@example.invalid"]); git(["config", "user.name", "Crash Fixture"]);
102
+ writeFileSync(join(repo, "README.md"), "Disposable crash fixture.\n"); git(["add", "."]); git(["commit", "-qm", "seed"]);
103
+ const cli = async (args, accepted = [0]) => {
104
+ const result = await start([...args, "--db", db, "--json"], opts).done;
105
+ assert(accepted.includes(result.code), `${args.slice(0,2).join(" ")}: ${result.stdout} ${result.stderr}`);
106
+ return JSON.parse(result.stdout);
107
+ };
108
+ const rows = sql => { const c = new DatabaseSync(db, { readOnly: true }); try { return c.prepare(sql).all(); } finally { c.close(); } };
109
+ const events = () => existsSync(join(control,"events.jsonl")) ? readFileSync(join(control,"events.jsonl"),"utf8").trim().split("\n").filter(Boolean).map(x => JSON.parse(x)) : [];
110
+ const record = { stage, round, passed: false, checks: {}, retainedAt: dir };
111
+ let worker, successor, checkpoint;
112
+ try {
113
+ await cli(["approver", "add", "fixture", "--password", "fixture-only-password"]);
114
+ const auth = ["--as", "fixture", "--token", "fixture-only-password"];
115
+ const registered = await cli(["runner", "register", "worker", "--repo", repo, ...auth]);
116
+ writeFileSync(join(control,"token"), registered.token, { mode: 0o600 });
117
+ for (const phase of ["plan", "build", "repair"]) await cli(["config", "set", phase, "--provider", "claude", "--model", "fixture", ...auth]);
118
+ await cli(["verify", "set", "--repo", repo, "--command", check, "--timeout-seconds", "240", "--yes", ...auth]);
119
+ if (stage === "setup") await cli(["setup", "set", "--repo", repo, "--command", wrapper("setup", "setup"), "--timeout-seconds", "240", "--yes", ...auth]);
120
+ await cli(["task", "add", "Write result.txt containing recovered and a newline.", "--id", "work", "--repo", repo]);
121
+ if (stage === "planning") {
122
+ if (autoApprove) {
123
+ await cli(["mode", "set", "--repo", repo, "--name", "standard", "--days", "1", "--auto-approve", "true", "--plan-auto", ...auth]);
124
+ await cli(["task", "scope", "work", "--goal", "Write the result and verify it.", "--not", "No publication.", "--touches", "result.txt", "--acceptance", acceptance.map(c => `${c.statement}|${c.evidence.join(",")}`).join(";")]);
125
+ const filed = await cli(["task", "show", "work"]);
126
+ assert.equal(filed.scope.approvedAt, null);
127
+ record.checks.filedScopeDigest = filed.scope.digest;
128
+ }
129
+ const requested = await cli(["task", "plan", "work", "--provider", "claude", "--model", "fixture", ...auth]);
130
+ if (autoApprove) assert.equal(requested.autoPlan, true);
131
+ } else {
132
+ await cli(["task", "scope", "work", "--goal", "Write the result and verify it.", "--acceptance", acceptance.map(c => `${c.statement}|${c.evidence.join(",")}`).join(";")]);
133
+ const preview = await cli(["task", "show", "work"]);
134
+ await cli(["task", "approve", "work", "--yes", "--digest", preview.scope.digest, ...auth]);
135
+ }
136
+ const workerArgs = ["watch", "--runner", "worker", "--token-file", join(control,"token"), "--repo", repo, "--pool", pool, "--tick-every", "200", "--reconcile-every", "500", "--bridge-every", "3600000", "--stop-grace", "1000", "--db", db, "--json"];
137
+ worker = start([...workerArgs, "--for", "180000"], opts);
138
+ worker.done.then(result => writeFileSync(join(control,"worker-result.json"), JSON.stringify(result)));
139
+ checkpoint = await until(() => existsSync(join(control,"checkpoint.json")) && JSON.parse(readFileSync(join(control,"checkpoint.json"),"utf8")), 25000, `${stage} checkpoint`);
140
+ record.checks.checkpoint = checkpoint;
141
+ record.checks.preCrashRuns = rows("SELECT id,role,phase,outcome FROM run");
142
+ if (["after-commit", "verification"].includes(stage)) record.checks.committedBeforeCrash = execFileSync(gitBinary, ["rev-parse", "HEAD"], { cwd: checkpoint.cwd, encoding: "utf8" }).trim();
143
+ if (stopBeforeCrash) {
144
+ // Freeze only the known worker handle: the independent provider keeps
145
+ // writing, while the public CLI can save stop intent in SQLite. The
146
+ // worker cannot observe or act on the stop before the deliberate crash.
147
+ worker.child.kill("SIGSTOP");
148
+ const target = record.checks.preCrashRuns.find(run => run.role === "builder" && run.outcome === null);
149
+ assert(target, "no exact builder attempt at checkpoint");
150
+ record.checks.stopTarget = target.id;
151
+ await cli(["task", "stop", "work", "--run", String(target.id), ...auth]);
152
+ assert.equal(rows("SELECT settled_at FROM run_stop")[0]?.settled_at, null, "an unobserved stop was already called settled");
153
+ }
154
+ worker.child.kill("SIGKILL");
155
+ const killed = await worker.done;
156
+ assert.equal(killed.signal, "SIGKILL");
157
+ record.checks.workerKilled = true;
158
+ record.checks.childSurvived = alive(checkpoint.pid);
159
+ assert(record.checks.childSurvived, "external checkpoint process did not survive the worker");
160
+ if (stage === "after-commit") writeFileSync(join(control,"released"), "commit subprocess already finished its write\n");
161
+ const lease = rows("SELECT expires_at FROM watch_lease")[0];
162
+ const busy = await start([...workerArgs, "--for", "100"], opts).done;
163
+ assert.equal(JSON.parse(busy.stdout).reason, "watch-busy");
164
+ record.checks.beforeExpiry = "refused";
165
+ console.log(`${stage}/${round}: worker killed; waiting for its real watch lease to expire`);
166
+ while (Date.now() <= Date.parse(lease.expires_at) + 100) await sleep(Math.min(1000, Date.parse(lease.expires_at) + 110 - Date.now()));
167
+ // Leave the external process alive during takeover: expiry must never
168
+ // by itself grant a second writer access to that checkout.
169
+ successor = start([...workerArgs, "--for", "800"], opts);
170
+ const probe = await Promise.race([successor.done, sleep(6000).then(() => null)]);
171
+ if (probe === null) { successor.child.kill("SIGTERM"); await successor.done; }
172
+ writeFileSync(join(control,"takeover-result.json"), JSON.stringify(await successor.done));
173
+ record.checks.overlappingWriters = events().filter(e => e.name === "overlapping-writer");
174
+ if (stopBeforeCrash) {
175
+ assert(alive(checkpoint.pid), "orphan must remain live while testing recovery");
176
+ assert.equal(rows("SELECT settled_at FROM run_stop")[0]?.settled_at, null, "dead worker was mistaken for dead provider");
177
+ const refused = await cli(["task", "resume", "work", "--run", String(record.checks.stopTarget), ...auth], [3]);
178
+ assert.equal(refused.ok, false, "resume admitted work over the live orphan");
179
+ record.checks.resumeWhileOrphanLives = refused.reason;
180
+ assert.equal(rows("SELECT resumed_at FROM run_stop")[0]?.resumed_at, null);
181
+ }
182
+ writeFileSync(join(control,"released"), "release fixture process\n");
183
+ await until(() => !alive(checkpoint.pid), 5000, `${stage} external process exit`);
184
+ const before = await cli(["task", "show", "work"]);
185
+ record.checks.afterTakeover = { state: before.task.state, dispatch: before.dispatch, runs: before.runs.map(r => ({ id:r.id,role:r.role,outcome:r.outcome,reason:r.reason })) };
186
+ if (stopBeforeCrash) {
187
+ // A normal recovery pass can now establish quiescence. Only the
188
+ // operator's subsequent exact-attempt resume clears the stop hold.
189
+ writeFileSync(join(control,"quiescent-recovery.json"), JSON.stringify(await start([...workerArgs, "--for", "800"], opts).done));
190
+ await cli(["task", "resume", "work", "--run", String(record.checks.stopTarget), ...auth]);
191
+ assert.notEqual(rows("SELECT resumed_at FROM run_stop")[0]?.resumed_at, null);
192
+ }
193
+ writeFileSync(join(control,"recovery-result.json"), JSON.stringify(await start([...workerArgs, "--for", autoApprove ? "15000" : "2500"], opts).done));
194
+ let after = await cli(["task", "show", "work"]);
195
+ const runsBeforeDuplicate = rows("SELECT id FROM run ORDER BY id");
196
+ const duplicate = await cli(["tick", "--runner", "worker", "--token", registered.token, "--repo", repo, "--pool", pool], [3]);
197
+ assert.equal(duplicate.reason, stage === "planning" && !autoApprove ? "nothing-dispatched" : "empty");
198
+ assert.deepEqual(rows("SELECT id FROM run ORDER BY id"), runsBeforeDuplicate);
199
+ record.checks.duplicateDispatch = stage === "planning" && !autoApprove ? "refused-unapproved" : "refused-empty";
200
+ if (autoApprove) {
201
+ assert.equal(after.scope.digest, record.checks.filedScopeDigest);
202
+ assert.equal(after.scope.approvedDigest, record.checks.filedScopeDigest);
203
+ assert.equal(after.scope.approvalBasis, "mode");
204
+ assert.equal(after.task.state, "done");
205
+ const approvals = rows("SELECT action,outcome FROM action_ledger WHERE action='plan auto-approval'");
206
+ assert.deepEqual(approvals.map(row => row.outcome), ["approved"]);
207
+ record.checks.autoApproval = { approvals, humanActionsAfterPlanningStarted: 0, criteria: after.proofMatrix };
208
+ }
209
+ record.checks.final = { state: after.task.state, scope: !!after.scope, proof: after.proofVerdict, dispatch: after.dispatch,
210
+ runs: after.runs.map(r => ({ id:r.id, role:r.role, outcome:r.outcome, reason:r.reason, parent:r.parentRun, head:r.headRevision })) };
211
+ record.checks.events = events();
212
+ record.checks.openRuns = rows("SELECT id,role FROM run WHERE outcome IS NULL");
213
+ const failures = [];
214
+ if (record.checks.overlappingWriters.length) failures.push("a successor provider started while an earlier writer still lived");
215
+ if (record.checks.openRuns.length) failures.push("interrupted runs remain open after restart");
216
+ if (stage === "planning" && !after.scope) failures.push("the interrupted planning request did not resume");
217
+ if (stage !== "planning" && after.task.state !== "done") failures.push("the preserved draft/commit did not finish");
218
+ if (stage !== "planning" || autoApprove) {
219
+ const finalHead = execFileSync(gitBinary, ["rev-parse", "standing-orders/work"], { cwd: repo, encoding: "utf8" }).trim();
220
+ assert.equal(execFileSync(gitBinary, ["show", `${finalHead}:result.txt`], { cwd: repo, encoding: "utf8" }), "recovered\n");
221
+ assert.equal(events().filter(e => e.name === "commit").length, 1, "recovery must not create a duplicate commit");
222
+ if (record.checks.committedBeforeCrash) assert.equal(finalHead, record.checks.committedBeforeCrash, "recovery must retain the original commit");
223
+ record.checks.finalHead = finalHead;
224
+ const build = after.runs.find(run => run.role === "builder" && run.headRevision === finalHead && run.outcome !== null);
225
+ assert(build, "the retained candidate has no finished builder");
226
+ const leadFile = await createFixtureLead(cli, { repo, auth, tokenFile: join(control, "lead.token") });
227
+ record.checks.completion = await completeFixtureAssignment(cli, "work", leadFile, { head: finalHead, runId: build.id });
228
+ assert.equal(after.runs.filter(run => run.role === "reviewer").length, 0, "recovery started a retired reviewer");
229
+ }
230
+ record.failures = failures;
231
+ record.passed = failures.length === 0;
232
+ } catch (error) { record.error = error.stack; }
233
+ finally {
234
+ writeFileSync(join(control,"released"), "fixture cleanup\n");
235
+ for (const handle of [worker,successor]) if (handle && handle.child.exitCode === null && handle.child.signalCode === null) { handle.child.kill("SIGKILL"); await handle.done; }
236
+ if (checkpoint && alive(checkpoint.pid)) { try { process.kill(checkpoint.pid,"SIGKILL"); } catch {} }
237
+ writeFileSync(join(dir,"result.json"), JSON.stringify(record,null,2));
238
+ }
239
+ cases.push(record);
240
+ console.log(`${stage}/${round}: ${record.passed ? "PASS" : "FAIL"} ${record.failures?.join("; ") ?? record.error?.split("\n")[0] ?? ""}`);
241
+ }
242
+ const pending = Array.from({ length: rounds }, (_, i) => selected.map(stage => ({ stage, round: i + 1 }))).flat();
243
+ await Promise.all(Array.from({ length: Math.min(concurrency, pending.length) }, async () => {
244
+ while (pending.length) { const next = pending.shift(); await one(next.stage, next.round); }
245
+ }));
246
+ const certificate = { version:1, passed: cases.every(c=>c.passed) && hash()===runtime, sourceCommit:revision, runtimeSha256:runtime, runtimeUnchanged:hash()===runtime,
247
+ stopBeforeCrash, autoApprove,
248
+ platform:process.platform, node:process.versions.node, durationSeconds:(Date.now()-started)/1000, cases, retainedAt:base,
249
+ scope:"Actual public-CLI watch processes killed with SIGKILL at deterministic planning, setup, build, commit and verification checkpoints; normal 90-second lease expiry; real git, SQLite, subprocesses and successor watch. Ready work is explicitly inspected and marked Complete without another provider run or rewriting checks. Provider responses are fixtures, not model calls.",
250
+ exclusions:["real-provider recovery", "Windows", "reboot", "detached descendants", "power-loss filesystem durability"] };
251
+ mkdirSync(dirname(output), { recursive:true }); writeFileSync(output, JSON.stringify(certificate,null,2)+"\n");
252
+ console.log(`Certificate: ${output}`);
253
+ if (!certificate.passed) process.exitCode=1;
@@ -0,0 +1,74 @@
1
+ /** Deterministic external processes for the real CLI crash harness. Never calls a model. */
2
+ import { appendFileSync, existsSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { spawnSync } from "node:child_process";
5
+ import { randomUUID } from "node:crypto";
6
+
7
+ const kind = process.env.SO_CRASH_KIND ?? process.argv[2];
8
+ const control = process.env.SO_CRASH_CONTROL ?? process.argv[3];
9
+ if (!control) throw new Error("This process only runs inside a crash fixture.");
10
+ const config = JSON.parse(readFileSync(join(control, "config.json"), "utf8"));
11
+ const event = (name, data = {}) => appendFileSync(join(control, "events.jsonl"), JSON.stringify({ name, pid: process.pid, at: Date.now(), cwd: process.cwd(), ...data }) + "\n");
12
+ const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));
13
+ const alive = pid => { try { process.kill(pid, 0); return true; } catch { return false; } };
14
+ const checkpoint = async (stage, writer = false) => {
15
+ if (stage !== config.stage || existsSync(join(control, "released"))) return;
16
+ writeFileSync(join(control, "checkpoint.json"), JSON.stringify({ stage, pid: process.pid, cwd: process.cwd(), at: Date.now() }));
17
+ while (!existsSync(join(control, "released"))) {
18
+ if (writer) writeFileSync(join(process.cwd(), "result.txt"), "recovered\n");
19
+ await sleep(25);
20
+ }
21
+ };
22
+ if (kind === "git") {
23
+ const args = process.argv.slice(2);
24
+ const result = spawnSync(config.git, args, { stdio: "inherit" });
25
+ if (result.error) throw result.error;
26
+ if (result.status === 0 && args[0] === "commit") {
27
+ event("commit");
28
+ await checkpoint("after-commit");
29
+ }
30
+ process.exit(result.status ?? 1);
31
+ }
32
+ if (kind === "check" || kind === "setup") {
33
+ event(`${kind}-start`);
34
+ await checkpoint(kind === "check" ? "verification" : "setup", true);
35
+ if (kind === "check" && readFileSync("result.txt", "utf8") !== "recovered\n") process.exit(1);
36
+ event(`${kind}-end`);
37
+ console.log("fixture check passed");
38
+ process.exit(0);
39
+ }
40
+ const args = process.argv.slice(2);
41
+ if (args.includes("--version")) { console.log("crash-fixture (not a real provider)"); process.exit(0); }
42
+ const prompt = args[args.indexOf("-p") + 1] ?? "";
43
+ const session = args.includes("--resume") ? args[args.indexOf("--resume") + 1] : randomUUID();
44
+ const output = event => console.log(JSON.stringify(event));
45
+ output({ type: "system", subtype: "init", session_id: session });
46
+ const speak = value => output({ type: "result", subtype: "success", is_error: false, session_id: session, result: typeof value === "string" ? value : JSON.stringify(value), num_turns: 1 });
47
+ const phase = /STANDING-ORDERS-DONE-[a-f0-9]{16}\.json/.test(prompt) ? "build" : "plan";
48
+ const initial = existsSync(join(control, "checkpoint.json")) ? JSON.parse(readFileSync(join(control, "checkpoint.json"), "utf8")) : null;
49
+ if (["build", "plan"].includes(phase) && initial !== null && initial.pid !== process.pid && !existsSync(join(control, "released")) && alive(initial.pid)) {
50
+ event("overlapping-writer", { original: initial.pid, stage: initial.stage });
51
+ }
52
+ event("provider-start", { phase, session });
53
+ if (phase === "plan") {
54
+ await checkpoint("planning");
55
+ const file = /STANDING-ORDERS-PLAN-[a-f0-9]{16}\.json/.exec(prompt)?.[0];
56
+ if (!file) throw new Error("Missing plan nonce");
57
+ writeFileSync(file, JSON.stringify({ goal: "Write the result and verify it.", outOfScope: "No publication.", touches: ["result.txt"], acceptance: config.acceptance,
58
+ plan: "## Approach\nWrite the result.\n## Milestones\n- Write result.txt.\n## Dependencies\n- None.\n## Risks\n- Interrupted work.\n## Proof\n- c1: Inspect result.txt.\n- c2: Run the approved check." }));
59
+ speak("Plan prepared.");
60
+ } else {
61
+ const path = "result.txt";
62
+ const existed = existsSync(path);
63
+ if (!existed) writeFileSync(path, "recovered\n");
64
+ await checkpoint("building", true);
65
+ const rubricFile = /STANDING-ORDERS-RUBRIC-[a-f0-9]{16}\.json/.exec(prompt)?.[0];
66
+ const rubric = JSON.parse(readFileSync(rubricFile, "utf8"));
67
+ const proofFile = /STANDING-ORDERS-PROOF-[a-f0-9]{16}\.json/.exec(prompt)?.[0];
68
+ const doneFile = /STANDING-ORDERS-DONE-[a-f0-9]{16}\.json/.exec(prompt)?.[0];
69
+ const tracked = spawnSync(config.git, ["ls-files", "--error-unmatch", path], { encoding: "utf8" }).status === 0;
70
+ writeFileSync(proofFile, JSON.stringify({ version: 1, criteria: rubric.map(one => ({ id: one.id, statement: one.statement, verdict: "met", how: "Fixture result and approved check.", evidence: one.evidence.map(kind => ({ kind, ref: kind === "check" ? config.check : path })) })), checks: [{ command: config.check, exitCode: 0, summary: "Fixture expectation; the worker independently runs this command." }], changed: [path], screenshots: [], caveats: [] }));
71
+ writeFileSync(doneFile, JSON.stringify({ version: 2, status: existed && tracked ? "no-change" : "completed", conclusion: "The requested result is present.", changes: ["result.txt contains recovered."], verification: [], followUps: [] }));
72
+ speak("Fixture finished.");
73
+ }
74
+ event("provider-end", { phase });
@@ -0,0 +1,85 @@
1
+ /** Fixed, reproducible pilot assignments. Verification files are outside the allowed change set. */
2
+ export const scenarioIds = ['pagination', 'dedupe', 'catalog', 'endpoint', 'dependency-format', 'dependency-sort', 'revision-csv', 'revision-config', 'ui-filter', 'ui-form'];
3
+ const prelude = `import assert from 'node:assert/strict';\n`;
4
+ const success = `\nconsole.log('PASS: every specified behavior and edge case was checked');\n`;
5
+ export function scenario(id, playwright) {
6
+ const cases = {
7
+ pagination: {
8
+ category: 'backend bug', paths: ['paginate.js'],
9
+ goal: 'Fix paginate(items, offset, limit): offset defaults to 0 and limit defaults to 10, zero limit returns [], clamp negative offset to zero, return a slice without mutating items.',
10
+ files: { 'paginate.js': 'export const paginate = (items, offset, limit) => items.splice(offset || 0, limit || 10);\n' },
11
+ check: `import {paginate} from './paginate.js'; const a=[1,2,3,4]; assert.deepEqual(paginate(a,1,2),[2,3]); assert.deepEqual(a,[1,2,3,4]); assert.deepEqual(paginate(a,0,0),[]); assert.deepEqual(paginate(a,-2,2),[1,2]); assert.deepEqual(paginate(a),a); assert.deepEqual(paginate(a,9,2),[]);`,
12
+ },
13
+ dedupe: {
14
+ category: 'backend bug', paths: ['dedupe.js'],
15
+ goal: 'Fix dedupe(rows): keep the first row for each exact id, preserve first-occurrence order, distinguish numeric and string ids, return a new array and never mutate input.',
16
+ files: { 'dedupe.js': 'export const dedupe = rows => Object.values(Object.fromEntries(rows.map(r => [r.id,r])));\n' },
17
+ check: `import {dedupe} from './dedupe.js'; const rows=[{id:2,v:'a'},{id:1,v:'b'},{id:2,v:'c'},{id:'2',v:'d'}]; const before=JSON.stringify(rows); assert.deepEqual(dedupe(rows),[rows[0],rows[1],rows[3]]); assert.equal(JSON.stringify(rows),before); assert.notEqual(dedupe(rows),rows); assert.deepEqual(dedupe([]),[]);`,
18
+ },
19
+ catalog: {
20
+ category: 'multiple files', paths: ['slug.js', 'catalog.js'],
21
+ goal: 'Add slug.js exporting slug(text), trimming/lowercasing ASCII text and collapsing runs of non-alphanumeric characters to one hyphen, with no edge hyphens. Add catalog.js exporting catalog(titles), returning {title,slug} records and rejecting duplicate resulting slugs with an Error.',
22
+ files: {},
23
+ check: `import {slug} from './slug.js'; import {catalog} from './catalog.js'; assert.equal(slug(' Red & Blue!! '),'red-blue'); assert.equal(slug('---'),''); assert.deepEqual(catalog(['Red Book','Blue']),[{title:'Red Book',slug:'red-book'},{title:'Blue',slug:'blue'}]); assert.throws(()=>catalog(['Red Book','RED BOOK'])); assert.deepEqual(catalog([]),[]);`,
24
+ },
25
+ endpoint: {
26
+ category: 'multiple files', paths: ['validate.js', 'endpoint.js'],
27
+ goal: 'Add validate.js exporting validName(value), true only for nonempty trimmed strings of at most 20 characters. Add endpoint.js exporting createItem(body), returning {status:400,body:{error:"invalid name"}} for an absent or invalid name, otherwise {status:201,body:{name:trimmedName}}. Accept a missing or null body without throwing.',
28
+ files: {},
29
+ check: `import {validName} from './validate.js'; import {createItem} from './endpoint.js'; assert.equal(validName(' a '),true); for(const x of ['', ' ', 2, null, 'a'.repeat(21)]) assert.equal(validName(x),false); for(const x of [undefined,null,{}, {name:3}]) assert.deepEqual(createItem(x),{status:400,body:{error:'invalid name'}}); assert.deepEqual(createItem({name:' hello '}),{status:201,body:{name:'hello'}});`,
30
+ },
31
+ 'dependency-format': {
32
+ category: 'dependency setup', paths: ['receipt.js'], dependency: true,
33
+ goal: 'Fix receipt(cents) in receipt.js to use the installed pilot-kit formatCents helper and return "Total: $12.34" for 1234, "Total: $0.00" for 0, and "Total: -$0.25" for -25. Dependency setup is already approved; do not change package or vendor files.',
34
+ files: { 'receipt.js': "export const receipt = cents => 'Total: $' + cents;\n" },
35
+ check: `import {receipt} from './receipt.js'; assert.equal(receipt(1234),'Total: $12.34'); assert.equal(receipt(0),'Total: $0.00'); assert.equal(receipt(-25),'Total: -$0.25');`,
36
+ },
37
+ 'dependency-sort': {
38
+ category: 'dependency setup', paths: ['sort.js'], dependency: true,
39
+ goal: 'Fix sortNames(names) in sort.js to use the installed pilot-kit naturalCompare helper, sorting names with numeric ordering into a new array without mutating input. Dependency setup is already approved; do not change package or vendor files.',
40
+ files: { 'sort.js': 'export const sortNames = names => names.sort();\n' },
41
+ check: `import {sortNames} from './sort.js'; const a=['file10','file2','file1']; assert.deepEqual(sortNames(a),['file1','file2','file10']); assert.deepEqual(a,['file10','file2','file1']); assert.deepEqual(sortNames([]),[]);`,
42
+ },
43
+ 'revision-csv': {
44
+ category: 'revision of existing behavior', paths: ['csv.js'],
45
+ goal: 'Revise csv(rows) to support comma, quote, and newline characters in fields. Quote such fields and double embedded quotes. Join records with CRLF and append a final CRLF for nonempty input. Preserve the csv export; empty rows input returns an empty string.',
46
+ files: { 'csv.js': "export const csv = rows => rows.map(r=>r.join(',')).join('\\n');\n" },
47
+ check: `import {csv} from './csv.js'; assert.equal(csv([]),''); assert.equal(csv([['a','b']]),'a,b\\r\\n'); assert.equal(csv([['a,b','say "hi"','x\\ny']]),'"a,b","say ""hi""","x\\ny"\\r\\n'); assert.equal(csv([[0,'']]),'0,\\r\\n');`,
48
+ },
49
+ 'revision-config': {
50
+ category: 'revision of existing behavior', paths: ['config.js'],
51
+ goal: 'Revise mergeConfig(base, overrides): shallow copy base, apply defined overrides including 0 and false, delete keys whose override is null, ignore undefined overrides, and never mutate either argument. Retain the mergeConfig export.',
52
+ files: { 'config.js': 'export const mergeConfig = (base, overrides) => Object.assign(base,overrides);\n' },
53
+ check: `import {mergeConfig} from './config.js'; const a={port:80,debug:true,host:'x',keep:1}; const b={port:0,debug:false,host:null,keep:undefined}; assert.deepEqual(mergeConfig(a,b),{port:0,debug:false,keep:1}); assert.deepEqual(a,{port:80,debug:true,host:'x',keep:1}); assert.equal(b.host,null); assert.deepEqual(mergeConfig({},{}),{});`,
54
+ },
55
+ 'ui-filter': {
56
+ category: 'UI flow', paths: ['index.html'], ui: true,
57
+ goal: 'Implement an accessible responsive catalog filter in index.html. Show a heading Catalog, an input named Filter, and a list with Apples, Bananas, Cherries. Filter case-insensitively as the user types, show "No matches" for an empty result, and restore all items when cleared. Fit a 390px mobile viewport without horizontal overflow. Use the provided check to capture desktop and mobile PNG evidence; run it before writing proof and cite output/playwright/desktop.png and mobile.png.',
58
+ files: { 'index.html': '<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1"><title>Catalog</title><h1>Catalog</h1><p>Filter not implemented.</p>\n' },
59
+ interaction: `const filter=page.getByRole('textbox',{name:'Filter',exact:true}); await filter.fill('AN'); await expectText(['Bananas']); await filter.fill('zzz'); await page.getByText('No matches',{exact:true}).waitFor(); await filter.fill(''); await expectText(['Apples','Bananas','Cherries']);`,
60
+ },
61
+ 'ui-form': {
62
+ category: 'UI flow', paths: ['index.html'], ui: true,
63
+ goal: 'Implement a responsive accessible task form in index.html. Heading Tasks, input named New task, button Add task, and a list of added tasks. Trim submitted text, reject blank submissions with "Enter a task", clear input after success, and give each added task a checkbox labelled by its text. Fit a 390px mobile viewport without overflow. Use the provided check to capture desktop and mobile PNG evidence; run it before writing proof and cite output/playwright/desktop.png and mobile.png.',
64
+ files: { 'index.html': '<!doctype html><meta name="viewport" content="width=device-width,initial-scale=1"><title>Tasks</title><h1>Tasks</h1><p>Task form not implemented.</p>\n' },
65
+ interaction: `const input=page.getByRole('textbox',{name:'New task',exact:true}); const add=page.getByRole('button',{name:'Add task',exact:true}); await input.fill(' '); await add.click(); await page.getByText('Enter a task',{exact:true}).waitFor(); await input.fill(' Buy milk '); await add.click(); assert.equal(await input.inputValue(),''); const done=page.getByRole('checkbox',{name:'Buy milk',exact:true}); await done.check(); assert.equal(await done.isChecked(),true); await done.uncheck(); assert.equal(await done.isChecked(),false);`,
66
+ },
67
+ };
68
+ const spec=cases[id]; if(!spec) throw new Error('unknown scenario '+id);
69
+ spec.id=id;
70
+ spec.files={ 'package.json': JSON.stringify({name:'standing-orders-pilot',private:true,type:'module',scripts:{test:'node verify.mjs'},...(spec.dependency?{dependencies:{'pilot-kit':'file:vendor/pilot-kit'}}:{})},null,2)+'\n', '.gitignore':'node_modules/\noutput/\n', 'README.md':'# Disposable unattended pilot\n\nRun npm test. Only the requested implementation files may change.\n', ...spec.files };
71
+ if(spec.dependency) {
72
+ spec.files['vendor/pilot-kit/package.json']=JSON.stringify({name:'pilot-kit',version:'1.0.0',type:'module',exports:'./index.js'})+'\n';
73
+ spec.files['vendor/pilot-kit/index.js']=`export const formatCents = n => (n < 0 ? '-$' : '$') + (Math.abs(n)/100).toFixed(2);\nexport const naturalCompare = (a,b) => a.localeCompare(b, 'en', {numeric:true});\n`;
74
+ }
75
+ if(spec.ui) {
76
+ if(!playwright) throw new Error('UI pilot needs --playwright pointing to an installed playwright/index.mjs');
77
+ spec.check=`import {chromium} from ${JSON.stringify(playwright)}; import {mkdir,readFile} from 'node:fs/promises'; import {createServer} from 'node:http';
78
+ const server=createServer(async (_req,res)=>{res.setHeader('Content-Type','text/html');res.end(await readFile('index.html'));}); await new Promise(r=>server.listen(0,'127.0.0.1',r));
79
+ const browser=await chromium.launch({headless:true}); try { const page=await browser.newPage(); const expectText=async expected=>assert.deepEqual((await page.getByRole('listitem').allTextContents()).map(x=>x.trim()),expected); await mkdir('output/playwright',{recursive:true});
80
+ for(const [name,width,height] of [['desktop',1280,800],['mobile',390,844]]) { await page.setViewportSize({width,height}); await page.goto('http://127.0.0.1:'+server.address().port); ${spec.interaction} assert.equal(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth),true); await page.screenshot({path:'output/playwright/'+name+'.png',fullPage:true}); console.log('PASS '+name+' interaction and '+width+'x'+height+' viewport; screenshot output/playwright/'+name+'.png'); }
81
+ } finally {await browser.close(); await new Promise(r=>server.close(r));}`;
82
+ }
83
+ spec.files['verify.mjs']=prelude+spec.check+success;
84
+ return spec;
85
+ }
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * The disposable service the launchd certificate supervises: beats a
4
+ * heartbeat file every 200 ms with its pid and a per-incarnation id, exits
5
+ * 0 when told to (the "unexpected clean exit" the supervisor must relaunch
6
+ * after), and otherwise lives until killed. No network, no database.
7
+ */
8
+ import { existsSync, mkdirSync, unlinkSync, writeFileSync } from "node:fs";
9
+ import { randomUUID } from "node:crypto";
10
+ import { join } from "node:path";
11
+
12
+ const dir = process.env["SO_CERT_STATE"];
13
+ if (dir === undefined) { console.error("SO_CERT_STATE is required"); process.exit(2); }
14
+ const incarnation = randomUUID();
15
+ mkdirSync(join(dir, "writers"), { recursive: true });
16
+ const beat = () => {
17
+ writeFileSync(join(dir, "heartbeat.json"), JSON.stringify({ pid: process.pid, incarnation, at: Date.now() }));
18
+ writeFileSync(join(dir, "writers", `${process.pid}-${incarnation}`), String(Date.now()));
19
+ };
20
+ beat();
21
+ setInterval(() => {
22
+ beat();
23
+ if (existsSync(join(dir, "exit-now"))) {
24
+ try { unlinkSync(join(dir, "exit-now")); } catch { /* raced */ }
25
+ process.exit(0);
26
+ }
27
+ }, 200);