openlanes 3.0.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 (466) hide show
  1. checksums.yaml +7 -0
  2. data/agents/hansolo-reviewer.md +59 -0
  3. data/agents/lando-broker.md +70 -0
  4. data/agents/leah-researcher.md +60 -0
  5. data/agents/luke-backend.md +73 -0
  6. data/agents/palpatine-planner.md +61 -0
  7. data/agents/r2d2-mechanic.md +57 -0
  8. data/agents/rey-frontend.md +68 -0
  9. data/agents/yoda-writer.md +67 -0
  10. data/bin/factory-e2e +157 -0
  11. data/bin/plan-number +164 -0
  12. data/bin/setup +26 -0
  13. data/evals/cases/hansolo-reviewer/review-medium/fail/plan/plan.md +3 -0
  14. data/evals/cases/hansolo-reviewer/review-medium/fail/plan/pull-requests.md +12 -0
  15. data/evals/cases/hansolo-reviewer/review-medium/fail/plan/spec.md +5 -0
  16. data/evals/cases/hansolo-reviewer/review-medium/fail/repo/lib/cart.rb +9 -0
  17. data/evals/cases/hansolo-reviewer/review-medium/fail/repo/spec/cart_spec.rb +7 -0
  18. data/evals/cases/hansolo-reviewer/review-medium/fail/run.json +1 -0
  19. data/evals/cases/hansolo-reviewer/review-medium/pass/plan/plan.md +3 -0
  20. data/evals/cases/hansolo-reviewer/review-medium/pass/plan/pull-requests.md +15 -0
  21. data/evals/cases/hansolo-reviewer/review-medium/pass/plan/spec.md +5 -0
  22. data/evals/cases/hansolo-reviewer/review-medium/pass/repo/lib/cart.rb +9 -0
  23. data/evals/cases/hansolo-reviewer/review-medium/pass/repo/spec/cart_spec.rb +7 -0
  24. data/evals/cases/hansolo-reviewer/review-medium/pass/run.json +1 -0
  25. data/evals/cases/hansolo-reviewer/review-medium.yml +60 -0
  26. data/evals/cases/lando-broker/unblock-fast/fail/plan/blocked.md +9 -0
  27. data/evals/cases/lando-broker/unblock-fast/fail/plan/spec.md +16 -0
  28. data/evals/cases/lando-broker/unblock-fast/fail/run.json +1 -0
  29. data/evals/cases/lando-broker/unblock-fast/pass/plan/spec.md +18 -0
  30. data/evals/cases/lando-broker/unblock-fast/pass/run.json +1 -0
  31. data/evals/cases/lando-broker/unblock-fast.yml +44 -0
  32. data/evals/cases/leah-researcher/research-deep/fail/plan/spec.md +26 -0
  33. data/evals/cases/leah-researcher/research-deep/fail/run.json +1 -0
  34. data/evals/cases/leah-researcher/research-deep/pass/plan/spec.md +39 -0
  35. data/evals/cases/leah-researcher/research-deep/pass/run.json +1 -0
  36. data/evals/cases/leah-researcher/research-deep.yml +51 -0
  37. data/evals/cases/leah-researcher/research-fast/fail/plan/spec.md +27 -0
  38. data/evals/cases/leah-researcher/research-fast/fail/run.json +1 -0
  39. data/evals/cases/leah-researcher/research-fast/pass/plan/spec.md +23 -0
  40. data/evals/cases/leah-researcher/research-fast/pass/run.json +1 -0
  41. data/evals/cases/leah-researcher/research-fast.yml +55 -0
  42. data/evals/cases/luke-backend/build-medium/fail/plan/plan-backend.md +7 -0
  43. data/evals/cases/luke-backend/build-medium/fail/plan/plan-frontend.md +1 -0
  44. data/evals/cases/luke-backend/build-medium/fail/plan/plan.md +3 -0
  45. data/evals/cases/luke-backend/build-medium/fail/plan/pull-requests.md +8 -0
  46. data/evals/cases/luke-backend/build-medium/fail/plan/spec.md +8 -0
  47. data/evals/cases/luke-backend/build-medium/fail/repo/Gemfile +2 -0
  48. data/evals/cases/luke-backend/build-medium/fail/repo/lib/cart.rb +11 -0
  49. data/evals/cases/luke-backend/build-medium/fail/repo/spec/cart_spec.rb +7 -0
  50. data/evals/cases/luke-backend/build-medium/fail/run.json +1 -0
  51. data/evals/cases/luke-backend/build-medium/pass/plan/plan-backend.md +7 -0
  52. data/evals/cases/luke-backend/build-medium/pass/plan/plan-frontend.md +1 -0
  53. data/evals/cases/luke-backend/build-medium/pass/plan/plan.md +3 -0
  54. data/evals/cases/luke-backend/build-medium/pass/plan/pull-requests.md +8 -0
  55. data/evals/cases/luke-backend/build-medium/pass/plan/spec.md +8 -0
  56. data/evals/cases/luke-backend/build-medium/pass/repo/Gemfile +2 -0
  57. data/evals/cases/luke-backend/build-medium/pass/repo/lib/cart.rb +11 -0
  58. data/evals/cases/luke-backend/build-medium/pass/repo/spec/cart_spec.rb +11 -0
  59. data/evals/cases/luke-backend/build-medium/pass/run.json +1 -0
  60. data/evals/cases/luke-backend/build-medium.yml +68 -0
  61. data/evals/cases/palpatine-planner/plan-deep/fail/plan/plan-backend.md +6 -0
  62. data/evals/cases/palpatine-planner/plan-deep/fail/plan/plan.md +8 -0
  63. data/evals/cases/palpatine-planner/plan-deep/fail/plan/spec.md +14 -0
  64. data/evals/cases/palpatine-planner/plan-deep/fail/run.json +1 -0
  65. data/evals/cases/palpatine-planner/plan-deep/pass/plan/plan-backend.md +32 -0
  66. data/evals/cases/palpatine-planner/plan-deep/pass/plan/plan-frontend.md +1 -0
  67. data/evals/cases/palpatine-planner/plan-deep/pass/plan/plan.md +28 -0
  68. data/evals/cases/palpatine-planner/plan-deep/pass/plan/spec.md +14 -0
  69. data/evals/cases/palpatine-planner/plan-deep/pass/run.json +1 -0
  70. data/evals/cases/palpatine-planner/plan-deep.yml +50 -0
  71. data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan-backend.md +48 -0
  72. data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan-frontend.md +1 -0
  73. data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan.md +12 -0
  74. data/evals/cases/palpatine-planner/plan-fast/fail/plan/spec.md +12 -0
  75. data/evals/cases/palpatine-planner/plan-fast/fail/run.json +1 -0
  76. data/evals/cases/palpatine-planner/plan-fast/pass/plan/plan-backend.md +8 -0
  77. data/evals/cases/palpatine-planner/plan-fast/pass/plan/plan-frontend.md +1 -0
  78. data/evals/cases/palpatine-planner/plan-fast/pass/plan/plan.md +12 -0
  79. data/evals/cases/palpatine-planner/plan-fast/pass/plan/spec.md +12 -0
  80. data/evals/cases/palpatine-planner/plan-fast/pass/run.json +1 -0
  81. data/evals/cases/palpatine-planner/plan-fast.yml +47 -0
  82. data/evals/cases/r2d2-mechanic/task-fast/fail/plan/plan.md +7 -0
  83. data/evals/cases/r2d2-mechanic/task-fast/fail/plan/pull-requests.md +6 -0
  84. data/evals/cases/r2d2-mechanic/task-fast/fail/plan/spec.md +6 -0
  85. data/evals/cases/r2d2-mechanic/task-fast/fail/repo/README.md +3 -0
  86. data/evals/cases/r2d2-mechanic/task-fast/fail/repo/lib/greeter/formatter.rb +12 -0
  87. data/evals/cases/r2d2-mechanic/task-fast/fail/repo/lib/greeter.rb +15 -0
  88. data/evals/cases/r2d2-mechanic/task-fast/fail/repo/spec/greeter_spec.rb +7 -0
  89. data/evals/cases/r2d2-mechanic/task-fast/fail/run.json +1 -0
  90. data/evals/cases/r2d2-mechanic/task-fast/pass/plan/plan.md +7 -0
  91. data/evals/cases/r2d2-mechanic/task-fast/pass/plan/pull-requests.md +6 -0
  92. data/evals/cases/r2d2-mechanic/task-fast/pass/plan/spec.md +6 -0
  93. data/evals/cases/r2d2-mechanic/task-fast/pass/repo/README.md +3 -0
  94. data/evals/cases/r2d2-mechanic/task-fast/pass/repo/lib/greeter.rb +3 -0
  95. data/evals/cases/r2d2-mechanic/task-fast/pass/repo/spec/greeter_spec.rb +7 -0
  96. data/evals/cases/r2d2-mechanic/task-fast/pass/run.json +1 -0
  97. data/evals/cases/r2d2-mechanic/task-fast.yml +59 -0
  98. data/evals/cases/r2d2-mechanic/task-medium/fail/plan/plan.md +7 -0
  99. data/evals/cases/r2d2-mechanic/task-medium/fail/plan/pull-requests.md +6 -0
  100. data/evals/cases/r2d2-mechanic/task-medium/fail/plan/spec.md +6 -0
  101. data/evals/cases/r2d2-mechanic/task-medium/fail/repo/Gemfile +2 -0
  102. data/evals/cases/r2d2-mechanic/task-medium/fail/repo/lib/greeter.rb +6 -0
  103. data/evals/cases/r2d2-mechanic/task-medium/fail/repo/spec/greeter_spec.rb +11 -0
  104. data/evals/cases/r2d2-mechanic/task-medium/fail/run.json +1 -0
  105. data/evals/cases/r2d2-mechanic/task-medium/pass/plan/plan.md +7 -0
  106. data/evals/cases/r2d2-mechanic/task-medium/pass/plan/pull-requests.md +6 -0
  107. data/evals/cases/r2d2-mechanic/task-medium/pass/plan/spec.md +6 -0
  108. data/evals/cases/r2d2-mechanic/task-medium/pass/repo/Gemfile +2 -0
  109. data/evals/cases/r2d2-mechanic/task-medium/pass/repo/lib/greeter.rb +6 -0
  110. data/evals/cases/r2d2-mechanic/task-medium/pass/repo/spec/greeter_spec.rb +11 -0
  111. data/evals/cases/r2d2-mechanic/task-medium/pass/run.json +1 -0
  112. data/evals/cases/r2d2-mechanic/task-medium.yml +58 -0
  113. data/evals/cases/rey-frontend/ui-medium/fail/plan/plan-frontend.md +7 -0
  114. data/evals/cases/rey-frontend/ui-medium/fail/plan/plan.md +4 -0
  115. data/evals/cases/rey-frontend/ui-medium/fail/plan/pull-requests.md +8 -0
  116. data/evals/cases/rey-frontend/ui-medium/fail/plan/spec.md +5 -0
  117. data/evals/cases/rey-frontend/ui-medium/fail/repo/lib/cart.rb +5 -0
  118. data/evals/cases/rey-frontend/ui-medium/fail/repo/web/cart_badge.js +4 -0
  119. data/evals/cases/rey-frontend/ui-medium/fail/repo/web/cart_badge.test.js +3 -0
  120. data/evals/cases/rey-frontend/ui-medium/fail/run.json +1 -0
  121. data/evals/cases/rey-frontend/ui-medium/pass/plan/plan-frontend.md +7 -0
  122. data/evals/cases/rey-frontend/ui-medium/pass/plan/plan.md +4 -0
  123. data/evals/cases/rey-frontend/ui-medium/pass/plan/pull-requests.md +8 -0
  124. data/evals/cases/rey-frontend/ui-medium/pass/plan/spec.md +5 -0
  125. data/evals/cases/rey-frontend/ui-medium/pass/repo/lib/cart.rb +3 -0
  126. data/evals/cases/rey-frontend/ui-medium/pass/repo/web/cart_badge.js +4 -0
  127. data/evals/cases/rey-frontend/ui-medium/pass/repo/web/cart_badge.test.js +3 -0
  128. data/evals/cases/rey-frontend/ui-medium/pass/run.json +1 -0
  129. data/evals/cases/rey-frontend/ui-medium.yml +55 -0
  130. data/evals/cases/yoda-writer/spec-medium/fail/plan/plan.md +5 -0
  131. data/evals/cases/yoda-writer/spec-medium/fail/plan/spec.md +45 -0
  132. data/evals/cases/yoda-writer/spec-medium/fail/run.json +1 -0
  133. data/evals/cases/yoda-writer/spec-medium/pass/plan/plan.md +0 -0
  134. data/evals/cases/yoda-writer/spec-medium/pass/plan/spec.md +63 -0
  135. data/evals/cases/yoda-writer/spec-medium/pass/run.json +1 -0
  136. data/evals/cases/yoda-writer/spec-medium.yml +40 -0
  137. data/exe/depot +1 -0
  138. data/exe/lanes +1 -0
  139. data/exe/openlanes +32 -0
  140. data/lib/openlanes/adapters/base.rb +69 -0
  141. data/lib/openlanes/adapters/claude.rb +124 -0
  142. data/lib/openlanes/adapters/codex.rb +33 -0
  143. data/lib/openlanes/adapters/model.rb +37 -0
  144. data/lib/openlanes/adapters/pi.rb +22 -0
  145. data/lib/openlanes/adapters/stream.rb +196 -0
  146. data/lib/openlanes/adapters.rb +92 -0
  147. data/lib/openlanes/agents/agent.rb +103 -0
  148. data/lib/openlanes/agents/profile.rb +57 -0
  149. data/lib/openlanes/agents/registry.rb +151 -0
  150. data/lib/openlanes/agents/roster.rb +103 -0
  151. data/lib/openlanes/agents/routing.rb +88 -0
  152. data/lib/openlanes/agents.rb +20 -0
  153. data/lib/openlanes/cli/agents/subcommands/describe.rb +66 -0
  154. data/lib/openlanes/cli/agents/subcommands/list.rb +20 -0
  155. data/lib/openlanes/cli/base.rb +263 -0
  156. data/lib/openlanes/cli/create/create.rb +354 -0
  157. data/lib/openlanes/cli/docs/docs.rb +40 -0
  158. data/lib/openlanes/cli/eval/eval.rb +110 -0
  159. data/lib/openlanes/cli/index/index.rb +40 -0
  160. data/lib/openlanes/cli/init/init.rb +145 -0
  161. data/lib/openlanes/cli/linear/linear.rb +36 -0
  162. data/lib/openlanes/cli/linear/subcommands/import.rb +174 -0
  163. data/lib/openlanes/cli/linear/subcommands/projects.rb +55 -0
  164. data/lib/openlanes/cli/list_plans/list_plans.rb +21 -0
  165. data/lib/openlanes/cli/mail/mail.rb +31 -0
  166. data/lib/openlanes/cli/mail/subcommands/read.rb +43 -0
  167. data/lib/openlanes/cli/mail/subcommands/send.rb +43 -0
  168. data/lib/openlanes/cli/resync/subcommands/dirs.rb +48 -0
  169. data/lib/openlanes/cli/resync/subcommands/prs.rb +111 -0
  170. data/lib/openlanes/cli/run/run.rb +544 -0
  171. data/lib/openlanes/cli/skills/subcommands/install.rb +92 -0
  172. data/lib/openlanes/cli/start/start.rb +110 -0
  173. data/lib/openlanes/cli/state/state.rb +26 -0
  174. data/lib/openlanes/cli/state/subcommands/show.rb +30 -0
  175. data/lib/openlanes/cli/state/subcommands/sign.rb +48 -0
  176. data/lib/openlanes/cli/states/states.rb +15 -0
  177. data/lib/openlanes/cli/unblock/unblock.rb +235 -0
  178. data/lib/openlanes/cli/version/version.rb +13 -0
  179. data/lib/openlanes/cli/web/web.rb +100 -0
  180. data/lib/openlanes/cli/worktree/worktree.rb +85 -0
  181. data/lib/openlanes/cli.rb +162 -0
  182. data/lib/openlanes/engine/board.rb +104 -0
  183. data/lib/openlanes/engine/dispatcher.rb +1297 -0
  184. data/lib/openlanes/engine/progress_log.rb +125 -0
  185. data/lib/openlanes/engine/runner.rb +224 -0
  186. data/lib/openlanes/engine/state_file.rb +190 -0
  187. data/lib/openlanes/engine/tally.rb +177 -0
  188. data/lib/openlanes/engine.rb +19 -0
  189. data/lib/openlanes/evals/case.rb +191 -0
  190. data/lib/openlanes/evals/changes.rb +67 -0
  191. data/lib/openlanes/evals/check.rb +224 -0
  192. data/lib/openlanes/evals/evidence.rb +71 -0
  193. data/lib/openlanes/evals/live.rb +140 -0
  194. data/lib/openlanes/evals/recording.rb +38 -0
  195. data/lib/openlanes/evals/run.rb +34 -0
  196. data/lib/openlanes/evals/score.rb +24 -0
  197. data/lib/openlanes/evals/scorer.rb +42 -0
  198. data/lib/openlanes/evals/verdict.rb +18 -0
  199. data/lib/openlanes/evals.rb +56 -0
  200. data/lib/openlanes/execution/checks.rb +73 -0
  201. data/lib/openlanes/execution/child.rb +228 -0
  202. data/lib/openlanes/execution/clock.rb +121 -0
  203. data/lib/openlanes/execution/control.rb +194 -0
  204. data/lib/openlanes/execution/executor.rb +849 -0
  205. data/lib/openlanes/execution/prompt.rb +455 -0
  206. data/lib/openlanes/execution/transcript.rb +531 -0
  207. data/lib/openlanes/execution.rb +19 -0
  208. data/lib/openlanes/launcher.rb +69 -0
  209. data/lib/openlanes/lifecycle/adoption.rb +197 -0
  210. data/lib/openlanes/lifecycle/brief.rb +288 -0
  211. data/lib/openlanes/lifecycle/completion.rb +58 -0
  212. data/lib/openlanes/lifecycle/creator.rb +126 -0
  213. data/lib/openlanes/lifecycle/lanes.rb +74 -0
  214. data/lib/openlanes/lifecycle/resync.rb +428 -0
  215. data/lib/openlanes/lifecycle/unblocker.rb +236 -0
  216. data/lib/openlanes/lifecycle.rb +28 -0
  217. data/lib/openlanes/linear/api.rb +224 -0
  218. data/lib/openlanes/linear/attribution.rb +185 -0
  219. data/lib/openlanes/linear/fuzzy.rb +68 -0
  220. data/lib/openlanes/linear/import.rb +314 -0
  221. data/lib/openlanes/linear/issue.rb +187 -0
  222. data/lib/openlanes/linear/mapping.rb +116 -0
  223. data/lib/openlanes/linear/push.rb +197 -0
  224. data/lib/openlanes/linear/survey.rb +174 -0
  225. data/lib/openlanes/linear/unit.rb +274 -0
  226. data/lib/openlanes/linear.rb +41 -0
  227. data/lib/openlanes/live/claims.rb +71 -0
  228. data/lib/openlanes/live/command.rb +122 -0
  229. data/lib/openlanes/live/commands.rb +505 -0
  230. data/lib/openlanes/live/keys.rb +93 -0
  231. data/lib/openlanes/live/memory_store.rb +265 -0
  232. data/lib/openlanes/live/meter.rb +51 -0
  233. data/lib/openlanes/live/node.rb +88 -0
  234. data/lib/openlanes/live/publisher.rb +179 -0
  235. data/lib/openlanes/live/repo.rb +60 -0
  236. data/lib/openlanes/live/run_record.rb +133 -0
  237. data/lib/openlanes/live/run_state.rb +101 -0
  238. data/lib/openlanes/live/session.rb +107 -0
  239. data/lib/openlanes/live/snapshot.rb +322 -0
  240. data/lib/openlanes/live/stations.rb +67 -0
  241. data/lib/openlanes/live/store.rb +277 -0
  242. data/lib/openlanes/live.rb +109 -0
  243. data/lib/openlanes/plans/dev_work.rb +56 -0
  244. data/lib/openlanes/plans/feature.rb +131 -0
  245. data/lib/openlanes/plans/index.rb +208 -0
  246. data/lib/openlanes/plans/ledger.rb +255 -0
  247. data/lib/openlanes/plans/mailbox.rb +164 -0
  248. data/lib/openlanes/plans/ordinal.rb +92 -0
  249. data/lib/openlanes/plans/plan_state.rb +291 -0
  250. data/lib/openlanes/plans/pull_request.rb +29 -0
  251. data/lib/openlanes/plans/pull_requests.rb +229 -0
  252. data/lib/openlanes/plans/spec.rb +213 -0
  253. data/lib/openlanes/plans/state_machine.rb +433 -0
  254. data/lib/openlanes/plans/status.rb +365 -0
  255. data/lib/openlanes/plans/subject.rb +133 -0
  256. data/lib/openlanes/plans/tree.rb +79 -0
  257. data/lib/openlanes/plans.rb +39 -0
  258. data/lib/openlanes/presentation/diagram.rb +149 -0
  259. data/lib/openlanes/presentation/documentation.rb +438 -0
  260. data/lib/openlanes/presentation/reporter.rb +181 -0
  261. data/lib/openlanes/presentation/viewer.rb +62 -0
  262. data/lib/openlanes/presentation.rb +23 -0
  263. data/lib/openlanes/skills/installer.rb +68 -0
  264. data/lib/openlanes/skills/skill.rb +60 -0
  265. data/lib/openlanes/skills.rb +25 -0
  266. data/lib/openlanes/support/config.rb +217 -0
  267. data/lib/openlanes/support/frontmatter.rb +38 -0
  268. data/lib/openlanes/support/markdown.rb +56 -0
  269. data/lib/openlanes/support/project.rb +79 -0
  270. data/lib/openlanes/support/ui.rb +671 -0
  271. data/lib/openlanes/vcs/github.rb +171 -0
  272. data/lib/openlanes/vcs/publisher.rb +221 -0
  273. data/lib/openlanes/vcs/worktree.rb +248 -0
  274. data/lib/openlanes/vcs.rb +19 -0
  275. data/lib/openlanes/version.rb +10 -0
  276. data/lib/openlanes/web/app.rb +176 -0
  277. data/lib/openlanes/web/context.rb +66 -0
  278. data/lib/openlanes/web/demo.rb +169 -0
  279. data/lib/openlanes/web/demo_board.rb +286 -0
  280. data/lib/openlanes/web/events.rb +104 -0
  281. data/lib/openlanes/web/launcher.rb +143 -0
  282. data/lib/openlanes/web/server.rb +77 -0
  283. data/lib/openlanes/web/stores.rb +36 -0
  284. data/lib/openlanes/web.rb +92 -0
  285. data/lib/openlanes.rb +151 -0
  286. data/openlanes.gemspec +87 -0
  287. data/schemas/plan-state.schema.json +101 -0
  288. data/skills/openlanes/SKILL.md +48 -0
  289. data/skills/openlanes-documents/SKILL.md +61 -0
  290. data/skills/openlanes-documents/references/blocked-template.md +49 -0
  291. data/skills/openlanes-documents/references/closing-templates.md +59 -0
  292. data/skills/openlanes-documents/references/plan-template.md +61 -0
  293. data/skills/openlanes-documents/references/retroactive-spec.md +43 -0
  294. data/skills/openlanes-documents/references/spec-template.md +45 -0
  295. data/skills/openlanes-run/SKILL.md +90 -0
  296. data/skills/openlanes-setup/SKILL.md +64 -0
  297. data/skills/openlanes-spec/SKILL.md +90 -0
  298. data/skills/openlanes-status/SKILL.md +32 -0
  299. data/skills/openlanes-sync/SKILL.md +22 -0
  300. data/web/dist/assets/apl-B4CMkyY2.js +2 -0
  301. data/web/dist/assets/asciiarmor-Df11BRmG.js +2 -0
  302. data/web/dist/assets/asn1-EdZsLKOL.js +2 -0
  303. data/web/dist/assets/asterisk-B-8jnY81.js +2 -0
  304. data/web/dist/assets/barlow-condensed-latin-600-normal-BFJEwTuo.woff +0 -0
  305. data/web/dist/assets/barlow-condensed-latin-600-normal-DepVgxBB.woff2 +0 -0
  306. data/web/dist/assets/barlow-condensed-latin-ext-600-normal-18ESti3H.woff2 +0 -0
  307. data/web/dist/assets/barlow-condensed-latin-ext-600-normal-Clv9cIcR.woff +0 -0
  308. data/web/dist/assets/barlow-condensed-vietnamese-600-normal-A5AYRdjN.woff2 +0 -0
  309. data/web/dist/assets/barlow-condensed-vietnamese-600-normal-CNlPk46_.woff +0 -0
  310. data/web/dist/assets/brainfuck-C4LP7Hcl.js +2 -0
  311. data/web/dist/assets/clike-B9uivgTg.js +2 -0
  312. data/web/dist/assets/clojure-BMjYHr_A.js +2 -0
  313. data/web/dist/assets/cmake-BQqOBYOt.js +2 -0
  314. data/web/dist/assets/cobol-CWcv1MsR.js +2 -0
  315. data/web/dist/assets/coffeescript-S37ZYGWr.js +2 -0
  316. data/web/dist/assets/commonlisp-DBKNyK5s.js +2 -0
  317. data/web/dist/assets/crystal-SjHAIU92.js +2 -0
  318. data/web/dist/assets/css-BnMrqG3P.js +2 -0
  319. data/web/dist/assets/cypher-C_CwsFkJ.js +2 -0
  320. data/web/dist/assets/d-pRatUO7H.js +2 -0
  321. data/web/dist/assets/diff-DbItnlRl.js +2 -0
  322. data/web/dist/assets/dockerfile-BKs6k2Af.js +2 -0
  323. data/web/dist/assets/dtd-DF_7sFjM.js +2 -0
  324. data/web/dist/assets/dylan-DwRh75JA.js +2 -0
  325. data/web/dist/assets/ebnf-CDyGwa7X.js +2 -0
  326. data/web/dist/assets/ecl-Cabwm37j.js +2 -0
  327. data/web/dist/assets/eiffel-CnydiIhH.js +2 -0
  328. data/web/dist/assets/elm-vLlmbW-K.js +2 -0
  329. data/web/dist/assets/erlang-BNw1qcRV.js +2 -0
  330. data/web/dist/assets/factor-kuTfRLto.js +2 -0
  331. data/web/dist/assets/favicon-C8pAuqqr.png +0 -0
  332. data/web/dist/assets/fcl-Kvtd6kyn.js +2 -0
  333. data/web/dist/assets/forth-Ffai-XNe.js +2 -0
  334. data/web/dist/assets/fortran-DYz_wnZ1.js +2 -0
  335. data/web/dist/assets/gas-Bneqetm1.js +2 -0
  336. data/web/dist/assets/gherkin-heZmZLOM.js +2 -0
  337. data/web/dist/assets/groovy-D9Dt4D0W.js +2 -0
  338. data/web/dist/assets/haskell-Cw1EW3IL.js +2 -0
  339. data/web/dist/assets/haxe-H-WmDvRZ.js +2 -0
  340. data/web/dist/assets/http-DBlCnlav.js +2 -0
  341. data/web/dist/assets/idl-BEugSyMb.js +2 -0
  342. data/web/dist/assets/index-2xPC-eHt.js +2 -0
  343. data/web/dist/assets/index-8dNhRmiO.js +8 -0
  344. data/web/dist/assets/index-B7-O9ujQ.js +2 -0
  345. data/web/dist/assets/index-BdR34cXC.js +2 -0
  346. data/web/dist/assets/index-Bp7W-ZTq.js +2 -0
  347. data/web/dist/assets/index-Byyxbh_z.js +4 -0
  348. data/web/dist/assets/index-C0VmkrO-.js +2 -0
  349. data/web/dist/assets/index-CI74IkWP.js +198 -0
  350. data/web/dist/assets/index-CYxcbpRM.js +3 -0
  351. data/web/dist/assets/index-D0rmJN3h.js +2 -0
  352. data/web/dist/assets/index-DB4QwuYn.js +2 -0
  353. data/web/dist/assets/index-DDI7zoAS.js +2 -0
  354. data/web/dist/assets/index-DGLUuEXi.js +2 -0
  355. data/web/dist/assets/index-OR-odsl9.js +2 -0
  356. data/web/dist/assets/index-P3vSkiU-.js +2 -0
  357. data/web/dist/assets/index-a4EHR-u4.css +1 -0
  358. data/web/dist/assets/index-mRV4XhJZ.js +2 -0
  359. data/web/dist/assets/index-popk9wbW.js +2 -0
  360. data/web/dist/assets/index-uM88Njdj.js +2 -0
  361. data/web/dist/assets/javascript-iXu5QeM3.js +2 -0
  362. data/web/dist/assets/julia-DuME0IfC.js +2 -0
  363. data/web/dist/assets/livescript-BwQOo05w.js +2 -0
  364. data/web/dist/assets/logo-on-dark-DI9gCdN4.png +0 -0
  365. data/web/dist/assets/logo-on-light-BciwqxcJ.png +0 -0
  366. data/web/dist/assets/lua-VAEuO923.js +2 -0
  367. data/web/dist/assets/mathematica-DTrFuWx2.js +2 -0
  368. data/web/dist/assets/mbox-CNhZ1qSd.js +2 -0
  369. data/web/dist/assets/mirc-CjQqDB4T.js +2 -0
  370. data/web/dist/assets/mllike-CXdrOF99.js +2 -0
  371. data/web/dist/assets/modelica-Dc1JOy9r.js +2 -0
  372. data/web/dist/assets/mscgen-BA5vi2Kp.js +2 -0
  373. data/web/dist/assets/mumps-BT43cFF4.js +2 -0
  374. data/web/dist/assets/nginx-DdIZxoE0.js +2 -0
  375. data/web/dist/assets/nsis-LY0VT_u5.js +2 -0
  376. data/web/dist/assets/ntriples-BfvgReVJ.js +2 -0
  377. data/web/dist/assets/octave-Ck1zUtKM.js +2 -0
  378. data/web/dist/assets/overpass-cyrillic-400-normal-CZsFKZCk.woff +0 -0
  379. data/web/dist/assets/overpass-cyrillic-400-normal-tXDxWGRy.woff2 +0 -0
  380. data/web/dist/assets/overpass-cyrillic-600-normal-B-hc8bdn.woff2 +0 -0
  381. data/web/dist/assets/overpass-cyrillic-600-normal-CXB2Mrig.woff +0 -0
  382. data/web/dist/assets/overpass-cyrillic-800-normal-CP6RWG8X.woff2 +0 -0
  383. data/web/dist/assets/overpass-cyrillic-800-normal-PJW4A9xW.woff +0 -0
  384. data/web/dist/assets/overpass-cyrillic-ext-400-normal-AKUz2ZI5.woff2 +0 -0
  385. data/web/dist/assets/overpass-cyrillic-ext-400-normal-C2ZjKngx.woff +0 -0
  386. data/web/dist/assets/overpass-cyrillic-ext-600-normal-CW4Fknsm.woff2 +0 -0
  387. data/web/dist/assets/overpass-cyrillic-ext-600-normal-Zx-mtRSD.woff +0 -0
  388. data/web/dist/assets/overpass-cyrillic-ext-800-normal-CIth-cA7.woff2 +0 -0
  389. data/web/dist/assets/overpass-cyrillic-ext-800-normal-hx3cL3ZN.woff +0 -0
  390. data/web/dist/assets/overpass-latin-400-normal-BpeLJ0bs.woff2 +0 -0
  391. data/web/dist/assets/overpass-latin-400-normal-c_C8zulu.woff +0 -0
  392. data/web/dist/assets/overpass-latin-600-normal-25RhTNCi.woff2 +0 -0
  393. data/web/dist/assets/overpass-latin-600-normal-DhYQrzaY.woff +0 -0
  394. data/web/dist/assets/overpass-latin-800-normal-Bzx_XP5l.woff2 +0 -0
  395. data/web/dist/assets/overpass-latin-800-normal-D6-q4f0l.woff +0 -0
  396. data/web/dist/assets/overpass-latin-ext-400-normal-BIkXRwl6.woff2 +0 -0
  397. data/web/dist/assets/overpass-latin-ext-400-normal-DUq_h-_z.woff +0 -0
  398. data/web/dist/assets/overpass-latin-ext-600-normal-Cyj4Nio0.woff2 +0 -0
  399. data/web/dist/assets/overpass-latin-ext-600-normal-DQKKMGzp.woff +0 -0
  400. data/web/dist/assets/overpass-latin-ext-800-normal-D6WKWGNs.woff +0 -0
  401. data/web/dist/assets/overpass-latin-ext-800-normal-DZ60AB77.woff2 +0 -0
  402. data/web/dist/assets/overpass-mono-cyrillic-400-normal-B144qALP.woff2 +0 -0
  403. data/web/dist/assets/overpass-mono-cyrillic-400-normal-XK9_jwT8.woff +0 -0
  404. data/web/dist/assets/overpass-mono-cyrillic-ext-400-normal-Dgejvo4A.woff2 +0 -0
  405. data/web/dist/assets/overpass-mono-cyrillic-ext-400-normal-DgxwtUZy.woff +0 -0
  406. data/web/dist/assets/overpass-mono-latin-400-normal-DqAOLzCO.woff +0 -0
  407. data/web/dist/assets/overpass-mono-latin-400-normal-VINZG6Js.woff2 +0 -0
  408. data/web/dist/assets/overpass-mono-latin-ext-400-normal-5YIw-E8g.woff +0 -0
  409. data/web/dist/assets/overpass-mono-latin-ext-400-normal-DBdJzBs_.woff2 +0 -0
  410. data/web/dist/assets/overpass-mono-vietnamese-400-normal-DrLp_cHj.woff +0 -0
  411. data/web/dist/assets/overpass-mono-vietnamese-400-normal-jmOeL0rj.woff2 +0 -0
  412. data/web/dist/assets/overpass-vietnamese-400-normal-BKRCEiXy.woff2 +0 -0
  413. data/web/dist/assets/overpass-vietnamese-400-normal-CCWAWVG7.woff +0 -0
  414. data/web/dist/assets/overpass-vietnamese-600-normal-CjS-4vQf.woff2 +0 -0
  415. data/web/dist/assets/overpass-vietnamese-600-normal-DlMMlPfM.woff +0 -0
  416. data/web/dist/assets/overpass-vietnamese-800-normal-BB8K9-Vo.woff2 +0 -0
  417. data/web/dist/assets/overpass-vietnamese-800-normal-DZUygvj_.woff +0 -0
  418. data/web/dist/assets/oz-BzwKVEFT.js +2 -0
  419. data/web/dist/assets/pascal--L3eBynH.js +2 -0
  420. data/web/dist/assets/perl-CdXCOZ3F.js +2 -0
  421. data/web/dist/assets/pig-CevX1Tat.js +2 -0
  422. data/web/dist/assets/powershell-CFHJl5sT.js +2 -0
  423. data/web/dist/assets/properties-C78fOPTZ.js +2 -0
  424. data/web/dist/assets/protobuf-ChK-085T.js +2 -0
  425. data/web/dist/assets/pug-DeIclll2.js +2 -0
  426. data/web/dist/assets/puppet-DMA9R1ak.js +2 -0
  427. data/web/dist/assets/python-BuPzkPfP.js +2 -0
  428. data/web/dist/assets/q-pXgVlZs6.js +2 -0
  429. data/web/dist/assets/r-B6wPVr8A.js +2 -0
  430. data/web/dist/assets/rpm-CTu-6PCP.js +2 -0
  431. data/web/dist/assets/ruby-BK5aLJev.js +2 -0
  432. data/web/dist/assets/sas-B4kiWyti.js +2 -0
  433. data/web/dist/assets/scheme-C41bIUwD.js +2 -0
  434. data/web/dist/assets/shell-CjFT_Tl9.js +2 -0
  435. data/web/dist/assets/sieve-C3Gn_uJK.js +2 -0
  436. data/web/dist/assets/simple-mode-GW_nhZxv.js +2 -0
  437. data/web/dist/assets/smalltalk-CnHTOXQT.js +2 -0
  438. data/web/dist/assets/solr-DehyRSwq.js +2 -0
  439. data/web/dist/assets/sparql-DkYu6x3z.js +2 -0
  440. data/web/dist/assets/spreadsheet-BCZA_wO0.js +2 -0
  441. data/web/dist/assets/sql-D0XecflT.js +2 -0
  442. data/web/dist/assets/stex-C3f8Ysf7.js +2 -0
  443. data/web/dist/assets/stylus-B533Al4x.js +2 -0
  444. data/web/dist/assets/swift-BzpIVaGY.js +2 -0
  445. data/web/dist/assets/tcl-DVfN8rqt.js +2 -0
  446. data/web/dist/assets/textile-CnDTJFAw.js +2 -0
  447. data/web/dist/assets/tiddlywiki-DO-Gjzrf.js +2 -0
  448. data/web/dist/assets/tiki-DGYXhP31.js +2 -0
  449. data/web/dist/assets/toml-Bm5Em-hy.js +2 -0
  450. data/web/dist/assets/troff-wAsdV37c.js +2 -0
  451. data/web/dist/assets/ttcn-CfJYG6tj.js +2 -0
  452. data/web/dist/assets/ttcn-cfg-B9xdYoR4.js +2 -0
  453. data/web/dist/assets/turtle-B1tBg_DP.js +2 -0
  454. data/web/dist/assets/vb-CmGdzxic.js +2 -0
  455. data/web/dist/assets/vbscript-BuJXcnF6.js +2 -0
  456. data/web/dist/assets/velocity-D8B20fx6.js +2 -0
  457. data/web/dist/assets/verilog-C6RDOZhf.js +2 -0
  458. data/web/dist/assets/vhdl-lSbBsy5d.js +2 -0
  459. data/web/dist/assets/webidl-ZXfAyPTL.js +2 -0
  460. data/web/dist/assets/xquery-DzFWVndE.js +2 -0
  461. data/web/dist/assets/yacas-BJ4BC0dw.js +2 -0
  462. data/web/dist/assets/z80-Hz9HOZM7.js +2 -0
  463. data/web/dist/index.html +14 -0
  464. data/web/dist/vite.svg +1 -0
  465. data/web/src/api/fixtures/board.json +512 -0
  466. metadata +907 -0
@@ -0,0 +1,455 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+
5
+ module OpenLanes
6
+ module Execution
7
+ # What one agent is told on one plan: its own definition, then the facts
8
+ # of this invocation, the ledger it signs, its budgets, its control file,
9
+ # its partner's mailbox and the boundary it works inside.
10
+ #
11
+ # Built here rather than in {Executor} so that starting a process and
12
+ # deciding what to say to it are two jobs in two places. Every number in
13
+ # it (seconds, tokens, the commands withheld) is handed in by the
14
+ # executor that enforces it, so the prompt and the enforcement cannot
15
+ # disagree.
16
+ class Prompt
17
+ # This checkout's own executable, named in every command the agent is
18
+ # told to run. A bare `openlanes` resolves through the agent's PATH to
19
+ # whatever release is installed, which can predate `state sign`.
20
+ EXECUTABLE = File.expand_path("../../../exe/openlanes", __dir__)
21
+
22
+ # @param agent [OpenLanes::Agents::Agent]
23
+ # @param subject [OpenLanes::Plans::Subject]
24
+ # @param root [String] the checkout the agent works in
25
+ # @param profile [OpenLanes::Agents::Profile]
26
+ # @param round [Integer]
27
+ # @param successor [String, nil] who the agent names on its `next:`
28
+ # @param partners [Array<OpenLanes::Agents::Agent>]
29
+ # @param control [String, nil] the control file, when there is one
30
+ # @param instructions [String] what `run --prompt` typed
31
+ # @param max_tokens [Integer, nil] the enforced token budget
32
+ # @param seconds [Integer, nil] the enforced clock
33
+ # @param denied [Array<String>] commands withheld
34
+ # @param granted [Array<String>] commands lifted for this agent
35
+ def initialize(agent:, subject:, root:, profile:, round: 1, successor: nil, partners: [], control: nil,
36
+ instructions: "", max_tokens: nil, fresh_budget: false, seconds: nil, denied: [], granted: [])
37
+ @agent = agent
38
+ @subject = subject
39
+ @root = root
40
+ @profile = profile
41
+ @round = round
42
+ @successor = successor
43
+ @partners = partners
44
+ @control = control
45
+ @instructions = instructions.to_s.strip
46
+ @max_tokens = max_tokens
47
+ @fresh_budget = fresh_budget
48
+ @seconds = seconds
49
+ @denied = denied
50
+ @granted = granted
51
+ end
52
+
53
+ # @return [String]
54
+ def to_s
55
+ <<~PROMPT
56
+ #{agent.prompt}
57
+
58
+ ---
59
+
60
+ ## This invocation
61
+
62
+ Plan folder : #{subject.feature.path}
63
+ Plan number : #{subject.feature.ordinal}
64
+ Current state : #{subject.status.emoji} #{subject.status.label}
65
+ Repository root: #{root}
66
+
67
+ #{"The folder's name is not currently justified: #{subject.violation}" if subject.violation}
68
+ #{operator_instructions}#{requirements_section}#{completion_section}#{suggestions_section}#{ledger_section(agent, subject, round:, successor:)}#{budget_section}#{time_budget_section(agent)}#{control_section(control, agent, subject)}#{questions_section}#{mailbox_section(agent, subject, partners)}
69
+ ## Boundary — enforced, not requested
70
+
71
+ You may read anything, and write source, tests and the plan's own
72
+ markdown.
73
+
74
+ #{withheld_preamble(profile)}
75
+
76
+ #{@denied.map { |c| " #{c}" }.join("\n")}
77
+ #{granted_section}
78
+ The harness checks afterwards that HEAD has not moved, and a round that
79
+ moved it is reported as a failure and rolled into the report. A prompt
80
+ is a request; a check is a guarantee.
81
+
82
+ Claim each directory or file before you write it, and release it when
83
+ that write is done. Name yourself on every call, since `alock` would
84
+ otherwise sign with a fingerprint your sub-agents share; give each
85
+ sub-agent its own suffix, e.g. `AGENT_ID=#{agent.name}-schema`:
86
+
87
+ AGENT_ID=#{agent.name} alock acquire <path> "<why>"
88
+ AGENT_ID=#{agent.name} alock release <path>
89
+
90
+ A refused `acquire` means another agent holds it: work on something
91
+ else, never write it anyway. Before your closing signature, run
92
+ `AGENT_ID=#{agent.name} alock release-all`.
93
+ PROMPT
94
+ end
95
+
96
+ # What a compacted agent is told when its session is resumed (W28). Its
97
+ # own instructions survive in the compacted summary; what it needs is
98
+ # to know that the INTERRUPT it just obeyed is over, and that the
99
+ # session it is in is the same one.
100
+ #
101
+ # @return [String]
102
+ def continuation
103
+ plans_dir = File.dirname(subject.feature.path)
104
+ <<~PROMPT
105
+ Your context was just compacted at a person's request. This is the same
106
+ session and the same round on plan #{subject.feature.ordinal}.
107
+
108
+ The INTERRUPT you were sent was only to pause you for the compaction.
109
+ It is over: do not end your turn because of it. Read your RESUME note,
110
+
111
+ #{cli} mail read --dir "#{plans_dir}" --plan #{subject.feature.ordinal} --for #{agent.name}
112
+
113
+ then carry on from its exact next step, as your original instructions
114
+ say, and sign your ledger when the work is done, as before. If you had
115
+ already finished and signed Completed, end your turn now.
116
+
117
+ #{"Your control file is still #{control}, now empty. Keep polling it between steps." if control}
118
+ PROMPT
119
+ end
120
+
121
+ private
122
+
123
+ attr_reader :agent, :subject, :root, :profile, :round, :successor, :partners, :control
124
+
125
+ # Only a CLI that actually refuses the commands may be described as
126
+ # refusing them.
127
+ #
128
+ # @param profile [OpenLanes::Agents::Profile]
129
+ # @return [String]
130
+ def withheld_preamble(profile)
131
+ if profile.adapter.enforces_tool_denial?
132
+ "These are withheld from you, not merely discouraged — `#{profile.adapter.executable}` is\n" \
133
+ "invoked with them disallowed:"
134
+ else
135
+ "You must not run these. `#{profile.adapter.executable}` cannot withhold them, so the\n" \
136
+ "check below is what catches a run that does:"
137
+ end
138
+ end
139
+
140
+ # The one paragraph every agent gets, identically, about the ledger. It
141
+ # lives here rather than in seven definition files so the wording cannot
142
+ # drift between agents, and so the names, the round and the successor are
143
+ # the harness's facts rather than the agent's guesses.
144
+ #
145
+ # @param agent [OpenLanes::Agents::Agent]
146
+ # @param subject [OpenLanes::Plans::Subject]
147
+ # @param round [Integer]
148
+ # @param successor [String, nil]
149
+ # @return [String]
150
+ def ledger_section(agent, subject, round:, successor:)
151
+ sign = "#{cli} state sign --dir \"#{File.dirname(subject.feature.path)}\" --plan #{subject.feature.ordinal} " \
152
+ "--agent #{agent.name} --round #{round}"
153
+ handoff = successor ? " --next #{successor}" : ""
154
+ <<~SECTION
155
+
156
+ ## The ledger - sign at the start and at the end
157
+
158
+ You sign this plan's `state.json`, through the command below. Never
159
+ edit `state.json` by hand: the command takes a lock, so you and an
160
+ agent working beside you cannot overwrite each other. Run it by the
161
+ path given, not as a bare `openlanes`: the one on your PATH may be an
162
+ older release without these commands, and wherever your instructions
163
+ say `openlanes`, they mean `#{cli}`. Before you do any work:
164
+
165
+ #{sign} --status Started
166
+
167
+ When you finish:
168
+
169
+ #{sign} --status Completed#{handoff}
170
+
171
+ Sign `Completed` only if your assignment is genuinely done. Otherwise
172
+ use `--status "Almost completed"`, `Interrupted` or `Blocked`, with no
173
+ `--next`. Where your instructions say to sign `Blocked, round N
174
+ (technical)` or `Completed, round N (approved)`, pass the words in
175
+ parentheses as `--note technical` or `--note approved`. The harness
176
+ renames the plan folder and starts the next agent from these
177
+ signatures; you never rename the folder yourself.
178
+ SECTION
179
+ end
180
+
181
+ # The section `run --max-tokens` adds. Stating the number is what lets
182
+ # the agent finish before it, rather than discovering the cap by dying
183
+ # on it with half a file written.
184
+ #
185
+ # @return [String]
186
+ def budget_section
187
+ return "" unless @max_tokens&.positive?
188
+
189
+ "\n## Token budget — #{@max_tokens} tokens, enforced\n\n" \
190
+ "This invocation is aborted once its total spend (#{spend}, " \
191
+ "sub-agents included) crosses #{@max_tokens} tokens. Budget the work: " \
192
+ "plan what fits, write results to disk as you go, and finish — or " \
193
+ "write a handoff note into the plan folder — before the meter runs " \
194
+ "out. Anything unwritten at the cap is lost.\n"
195
+ end
196
+
197
+ # @return [String] the executable, quoted for a shell
198
+ def cli = Shellwords.escape(EXECUTABLE)
199
+
200
+ # @return [String] what the budget counts
201
+ def spend = @fresh_budget ? "new input plus output; cache reads are free" : "input plus output"
202
+
203
+ # The section describing the advisory clock this invocation runs against,
204
+ # stated in the prompt so an agent can pace itself. The number is
205
+ # whatever {#timeout_for} will actually enforce, so the prompt and the
206
+ # clock can never disagree — an agent whose prose names its own figure
207
+ # goes stale the first time someone passes `--timeout`, and stale is
208
+ # worse than silent.
209
+ #
210
+ # @param agent [OpenLanes::Agents::Agent]
211
+ # @return [String]
212
+ def time_budget_section(agent)
213
+ seconds = @seconds
214
+ return "" unless seconds&.positive?
215
+
216
+ minutes = (seconds / 60.0).round
217
+ "\n## Time budget - #{seconds} seconds\n\n" \
218
+ "You have about #{minutes} minute#{"s" unless minutes == 1} of wall clock. The control " \
219
+ "file below tells you how it is going: `WARN: 10 minutes left`, `WARN: 5 minutes left`, " \
220
+ "`WRAP_UP: 1 minute left, write to disk now`, then `STOP`. Sixty seconds after STOP " \
221
+ "the process is killed, and anything unwritten is lost. Write each result to disk as " \
222
+ "you reach it, and sign your closing ledger entry before anything else once you see " \
223
+ "WRAP_UP.#{concurrency_advice(agent)}\n"
224
+ end
225
+
226
+ # Only worth saying to an agent that can actually do it. Telling an agent
227
+ # without `Task` to parallelise is telling it to feel bad about a tool it
228
+ # was not given.
229
+ #
230
+ # @param agent [OpenLanes::Agents::Agent]
231
+ # @return [String]
232
+ def concurrency_advice(agent)
233
+ return "" unless agent.allowed_tools.include?("Task")
234
+
235
+ " Where the work divides into parts that do not read each other's output, run them " \
236
+ "as one wave of concurrent sub-agents rather than in series: the wave costs one " \
237
+ "part's wall clock, and the series costs the sum of all of them."
238
+ end
239
+
240
+ # Two agents on one plan are two processes. Naming the partner and the
241
+ # exact commands is what replaced guessing the partner's session from
242
+ # every Claude session on the machine.
243
+ #
244
+ # @param agent [OpenLanes::Agents::Agent]
245
+ # @param subject [OpenLanes::Plans::Subject]
246
+ # @param partners [Array<OpenLanes::Agents::Agent>]
247
+ # @return [String]
248
+ def mailbox_section(agent, subject, partners)
249
+ return "" if partners.empty?
250
+
251
+ plans_dir = File.dirname(subject.feature.path)
252
+ plan = subject.feature.ordinal
253
+ names = partners.map { |p| "`#{p.name}`" }
254
+ who = names.size == 1 ? "Your partner on this plan is #{names.first}" : "Your partners on this plan are #{names.join(" and ")}"
255
+
256
+ <<~SECTION
257
+
258
+ ## Mailbox - poll it between steps
259
+
260
+ #{who}. You share one worktree and one branch, but not a process, so
261
+ the only way to reach each other is the plan's mailbox, kept in the
262
+ plan's `state.json`. Use the commands; never edit the file by hand.
263
+
264
+ Read it before each significant step and whenever you finish a unit:
265
+
266
+ #{cli} mail read --dir "#{plans_dir}" --plan #{plan} --for #{agent.name}
267
+
268
+ Pass `--after N`, with the number of the last message you have read,
269
+ to see only what is new. Write to it when you land an interface your
270
+ partner is waiting on, when you amend the contract, when you need
271
+ something from their half, and when you finish:
272
+
273
+ #{cli} mail send --dir "#{plans_dir}" --plan #{plan} --from #{agent.name} --to #{partners.first.name} "what you need them to know"
274
+
275
+ Every message is appended with a number and a timestamp and never
276
+ edited, so a person can read the exchange after the round. A question
277
+ your partner has not answered within a few steps is not a reason to
278
+ stop: write your assumption into implementation-plan.md and carry on.
279
+ SECTION
280
+ end
281
+
282
+ # The section a control file adds. Present on every invocation, because
283
+ # the advisory clock writes into it whether or not anyone is watching.
284
+ #
285
+ # INTERRUPT is what makes a restart after Ctrl-C safe: the agent that
286
+ # was cut off leaves a mailbox note to whoever picks the plan up, and
287
+ # every agent is told to read its mail first, so the next run resumes
288
+ # from the note rather than redoing work already on disk.
289
+ #
290
+ # @param control [String, nil]
291
+ # @param agent [OpenLanes::Agents::Agent]
292
+ # @param subject [OpenLanes::Plans::Subject]
293
+ # @return [String]
294
+ def control_section(control, agent, subject)
295
+ return "" if control.nil?
296
+
297
+ plans_dir = File.dirname(subject.feature.path)
298
+ plan = subject.feature.ordinal
299
+ <<~SECTION
300
+
301
+ ## Control file — poll it between steps
302
+
303
+ #{control}
304
+
305
+ Read this file before each significant step. Empty means carry on.
306
+ A line starting WARN: tells you how much time is left. WRAP_UP: means
307
+ finish the essential remainder now. STOP means write what you have,
308
+ sign your ledger entry, and end your turn.
309
+
310
+ HOLD means a person wants to say something to you. Finish the write
311
+ you are in, then read your mail and wait for their next message
312
+ before you do anything else; check every thirty seconds:
313
+
314
+ #{cli} mail read --dir "#{plans_dir}" --plan #{plan} --for #{agent.name}
315
+
316
+ INTERRUPT means the operator pressed Ctrl-C and the run will be started
317
+ again later. Start nothing new. Finish the write you are in the middle
318
+ of, so no file is left half-written, then leave a resume note for
319
+ whoever runs next — most likely you — saying what is done, what is
320
+ not, and the exact next step:
321
+
322
+ #{cli} mail send --dir "#{plans_dir}" --plan #{plan} --from #{agent.name} --to #{agent.name} "RESUME: ..."
323
+
324
+ Then sign `--status Interrupted`, with no `--next`, and
325
+ end your turn.
326
+
327
+ Before you start, check for such a note from an earlier run:
328
+
329
+ #{cli} mail read --dir "#{plans_dir}" --plan #{plan} --for #{agent.name}
330
+
331
+ If there is a RESUME note, continue from it and do not redo what it
332
+ says is done; check the files it names rather than taking it on trust.
333
+ SECTION
334
+ end
335
+
336
+ # How to mark a yes/no question in `blocked.md` (W30). The page offers
337
+ # Yes and No buttons for exactly the questions marked this way and never
338
+ # guesses from the wording, so an agent that does not mark one gets a
339
+ # text box, and one that marks a two-way choice gets a meaningless yes.
340
+ # Said once here, for every agent that writes `blocked.md`, rather than
341
+ # in each definition, so the marker cannot drift between them.
342
+ #
343
+ # @return [String] empty for an agent whose definition never mentions
344
+ # `blocked.md`, and for the agent that drains it
345
+ def questions_section
346
+ return "" unless agent.prompt.include?("blocked.md") && !agent.handles.include?(:blocked)
347
+
348
+ <<~SECTION
349
+
350
+ ## Questions in blocked.md: yes/no questions
351
+
352
+ When a question can be answered with a plain yes or no, ask it that
353
+ way and mark it, on its own line straight under its heading:
354
+
355
+ ## B2 Rebase onto main
356
+ #{Live::Snapshot::YES_NO_MARK}
357
+
358
+ The pull request conflicts in lib/openlanes/cli.rb. Should I rebase onto main?
359
+
360
+ The person answers with a button, and the answer reaches you as the
361
+ bare word `yes` or `no`, so branch on the word. Mark only a real yes/no
362
+ question: "Should I start it, or skip the integration specs?" is a
363
+ choice between two things, and needs words.
364
+ SECTION
365
+ end
366
+
367
+ # How the spec's author says the task is finished, and how to tell. The
368
+ # harness copies both into plan.md after the planner signs; saying so
369
+ # here keeps a planner from burying them in a paraphrase, and gives the
370
+ # builder and the reviewer the same two lists to hold the work to.
371
+ #
372
+ # @return [String] empty when the spec says neither
373
+ def completion_section
374
+ spec = Plans::Spec.for(subject)
375
+ return "" if spec.completed_when.empty? && spec.how_to_verify.empty?
376
+
377
+ lists = [["The task is finished when", spec.completed_when], ["How to verify it", spec.how_to_verify]]
378
+ body = lists.reject { |_, items| items.empty? }.map do |title, items|
379
+ "#{title}:\n\n#{items.map { |item| "- #{item}" }.join("\n")}\n"
380
+ end
381
+
382
+ "\n## Completion criteria — from the spec's author\n\n" \
383
+ "Do not sign Completed until the first list holds, and run each check in the second. " \
384
+ "If you write plan.md, copy both lists into it verbatim, under `## Task completed when` and " \
385
+ "`## How to verify`; the harness adds them if you do not. A reviewer rejects work that fails a check.\n\n" \
386
+ "#{body.join("\n")}"
387
+ end
388
+
389
+ # What the spec's author requires, if they said. The opposite of
390
+ # {#suggestions_section}: an agent that weighs a requirement against its
391
+ # own taste will sometimes decide against it, so this states that there
392
+ # is nothing to weigh, and gives it the one honest way out, which is to
393
+ # stop and say so. A reviewer is told to hold the work to them.
394
+ #
395
+ # {Checks} appends the one requirement no spec states: the suite and the
396
+ # linters pass. It goes last, after the author's own.
397
+ #
398
+ # @return [String] empty when the spec requires nothing and the agent
399
+ # changes no code
400
+ def requirements_section
401
+ requirements = Plans::Spec.for(subject).requirements
402
+ requirements += [Checks.requirement(root)] if Checks.apply_to?(agent)
403
+ return "" if requirements.empty?
404
+
405
+ "\n## Implementation requirements\n\n" \
406
+ "These are requirements, not suggestions. Do exactly what each one says; do not substitute, " \
407
+ "weaken or reinterpret one, and do not decide you know better. If you cannot meet one, stop " \
408
+ "and sign Blocked with `--note technical`, and write which requirement and why in the " \
409
+ "document you own. A reviewer rejects work that does not meet them.\n\n" \
410
+ "#{requirements.map { |item| "- #{item}" }.join("\n")}\n"
411
+ end
412
+
413
+ # What the spec's author would build it with, if they said. Labelled as
414
+ # a suggestion in so many words, because an agent handed a language and
415
+ # a library list as an order will follow it into a dead end rather than
416
+ # say the route is wrong; handed as advice, it can take a better one and
417
+ # write the reason in the document it owns. The signature note is
418
+ # read by the harness for keywords, so it is no place for prose.
419
+ #
420
+ # @return [String] empty when the spec suggests nothing
421
+ def suggestions_section
422
+ suggestions = Plans::Spec.for(subject).suggestions
423
+ return "" if suggestions.empty?
424
+
425
+ "\n## Implementation suggestions — from the spec's author\n\n" \
426
+ "These are suggestions, not requirements: a language, libraries, an approach the author " \
427
+ "had in mind. Prefer them when they fit. If you find a better route, take it and write why " \
428
+ "in the document you own, not in the signature note.\n\n#{suggestions.map { |item| "- #{item}" }.join("\n")}\n"
429
+ end
430
+
431
+ # The section `run --prompt` adds, labelled as coming from the person who
432
+ # started the run so the agent can tell a one-off steer from its own
433
+ # standing definition.
434
+ #
435
+ # @return [String]
436
+ def operator_instructions
437
+ return "" if @instructions.empty?
438
+
439
+ "\n## Operator instructions — this invocation only\n\n#{@instructions}\n"
440
+ end
441
+
442
+ # Spelled out in the prompt as well as withheld at the tool layer, because
443
+ # an agent that does not know it has been granted something does not use
444
+ # it — and `hansolo-reviewer` silently never approving anything looks
445
+ # exactly like `hansolo-reviewer` approving nothing worth approving.
446
+ #
447
+ # @return [String]
448
+ def granted_section
449
+ return "" if @granted.empty?
450
+
451
+ "\nYou may run these, which most agents may not:\n\n#{@granted.map { |c| " #{c}" }.join("\n")}\n"
452
+ end
453
+ end
454
+ end
455
+ end