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.
- checksums.yaml +7 -0
- data/agents/hansolo-reviewer.md +59 -0
- data/agents/lando-broker.md +70 -0
- data/agents/leah-researcher.md +60 -0
- data/agents/luke-backend.md +73 -0
- data/agents/palpatine-planner.md +61 -0
- data/agents/r2d2-mechanic.md +57 -0
- data/agents/rey-frontend.md +68 -0
- data/agents/yoda-writer.md +67 -0
- data/bin/factory-e2e +157 -0
- data/bin/plan-number +164 -0
- data/bin/setup +26 -0
- data/evals/cases/hansolo-reviewer/review-medium/fail/plan/plan.md +3 -0
- data/evals/cases/hansolo-reviewer/review-medium/fail/plan/pull-requests.md +12 -0
- data/evals/cases/hansolo-reviewer/review-medium/fail/plan/spec.md +5 -0
- data/evals/cases/hansolo-reviewer/review-medium/fail/repo/lib/cart.rb +9 -0
- data/evals/cases/hansolo-reviewer/review-medium/fail/repo/spec/cart_spec.rb +7 -0
- data/evals/cases/hansolo-reviewer/review-medium/fail/run.json +1 -0
- data/evals/cases/hansolo-reviewer/review-medium/pass/plan/plan.md +3 -0
- data/evals/cases/hansolo-reviewer/review-medium/pass/plan/pull-requests.md +15 -0
- data/evals/cases/hansolo-reviewer/review-medium/pass/plan/spec.md +5 -0
- data/evals/cases/hansolo-reviewer/review-medium/pass/repo/lib/cart.rb +9 -0
- data/evals/cases/hansolo-reviewer/review-medium/pass/repo/spec/cart_spec.rb +7 -0
- data/evals/cases/hansolo-reviewer/review-medium/pass/run.json +1 -0
- data/evals/cases/hansolo-reviewer/review-medium.yml +60 -0
- data/evals/cases/lando-broker/unblock-fast/fail/plan/blocked.md +9 -0
- data/evals/cases/lando-broker/unblock-fast/fail/plan/spec.md +16 -0
- data/evals/cases/lando-broker/unblock-fast/fail/run.json +1 -0
- data/evals/cases/lando-broker/unblock-fast/pass/plan/spec.md +18 -0
- data/evals/cases/lando-broker/unblock-fast/pass/run.json +1 -0
- data/evals/cases/lando-broker/unblock-fast.yml +44 -0
- data/evals/cases/leah-researcher/research-deep/fail/plan/spec.md +26 -0
- data/evals/cases/leah-researcher/research-deep/fail/run.json +1 -0
- data/evals/cases/leah-researcher/research-deep/pass/plan/spec.md +39 -0
- data/evals/cases/leah-researcher/research-deep/pass/run.json +1 -0
- data/evals/cases/leah-researcher/research-deep.yml +51 -0
- data/evals/cases/leah-researcher/research-fast/fail/plan/spec.md +27 -0
- data/evals/cases/leah-researcher/research-fast/fail/run.json +1 -0
- data/evals/cases/leah-researcher/research-fast/pass/plan/spec.md +23 -0
- data/evals/cases/leah-researcher/research-fast/pass/run.json +1 -0
- data/evals/cases/leah-researcher/research-fast.yml +55 -0
- data/evals/cases/luke-backend/build-medium/fail/plan/plan-backend.md +7 -0
- data/evals/cases/luke-backend/build-medium/fail/plan/plan-frontend.md +1 -0
- data/evals/cases/luke-backend/build-medium/fail/plan/plan.md +3 -0
- data/evals/cases/luke-backend/build-medium/fail/plan/pull-requests.md +8 -0
- data/evals/cases/luke-backend/build-medium/fail/plan/spec.md +8 -0
- data/evals/cases/luke-backend/build-medium/fail/repo/Gemfile +2 -0
- data/evals/cases/luke-backend/build-medium/fail/repo/lib/cart.rb +11 -0
- data/evals/cases/luke-backend/build-medium/fail/repo/spec/cart_spec.rb +7 -0
- data/evals/cases/luke-backend/build-medium/fail/run.json +1 -0
- data/evals/cases/luke-backend/build-medium/pass/plan/plan-backend.md +7 -0
- data/evals/cases/luke-backend/build-medium/pass/plan/plan-frontend.md +1 -0
- data/evals/cases/luke-backend/build-medium/pass/plan/plan.md +3 -0
- data/evals/cases/luke-backend/build-medium/pass/plan/pull-requests.md +8 -0
- data/evals/cases/luke-backend/build-medium/pass/plan/spec.md +8 -0
- data/evals/cases/luke-backend/build-medium/pass/repo/Gemfile +2 -0
- data/evals/cases/luke-backend/build-medium/pass/repo/lib/cart.rb +11 -0
- data/evals/cases/luke-backend/build-medium/pass/repo/spec/cart_spec.rb +11 -0
- data/evals/cases/luke-backend/build-medium/pass/run.json +1 -0
- data/evals/cases/luke-backend/build-medium.yml +68 -0
- data/evals/cases/palpatine-planner/plan-deep/fail/plan/plan-backend.md +6 -0
- data/evals/cases/palpatine-planner/plan-deep/fail/plan/plan.md +8 -0
- data/evals/cases/palpatine-planner/plan-deep/fail/plan/spec.md +14 -0
- data/evals/cases/palpatine-planner/plan-deep/fail/run.json +1 -0
- data/evals/cases/palpatine-planner/plan-deep/pass/plan/plan-backend.md +32 -0
- data/evals/cases/palpatine-planner/plan-deep/pass/plan/plan-frontend.md +1 -0
- data/evals/cases/palpatine-planner/plan-deep/pass/plan/plan.md +28 -0
- data/evals/cases/palpatine-planner/plan-deep/pass/plan/spec.md +14 -0
- data/evals/cases/palpatine-planner/plan-deep/pass/run.json +1 -0
- data/evals/cases/palpatine-planner/plan-deep.yml +50 -0
- data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan-backend.md +48 -0
- data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan-frontend.md +1 -0
- data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan.md +12 -0
- data/evals/cases/palpatine-planner/plan-fast/fail/plan/spec.md +12 -0
- data/evals/cases/palpatine-planner/plan-fast/fail/run.json +1 -0
- data/evals/cases/palpatine-planner/plan-fast/pass/plan/plan-backend.md +8 -0
- data/evals/cases/palpatine-planner/plan-fast/pass/plan/plan-frontend.md +1 -0
- data/evals/cases/palpatine-planner/plan-fast/pass/plan/plan.md +12 -0
- data/evals/cases/palpatine-planner/plan-fast/pass/plan/spec.md +12 -0
- data/evals/cases/palpatine-planner/plan-fast/pass/run.json +1 -0
- data/evals/cases/palpatine-planner/plan-fast.yml +47 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/plan/plan.md +7 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/plan/pull-requests.md +6 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/plan/spec.md +6 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/repo/README.md +3 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/repo/lib/greeter/formatter.rb +12 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/repo/lib/greeter.rb +15 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/repo/spec/greeter_spec.rb +7 -0
- data/evals/cases/r2d2-mechanic/task-fast/fail/run.json +1 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/plan/plan.md +7 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/plan/pull-requests.md +6 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/plan/spec.md +6 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/repo/README.md +3 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/repo/lib/greeter.rb +3 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/repo/spec/greeter_spec.rb +7 -0
- data/evals/cases/r2d2-mechanic/task-fast/pass/run.json +1 -0
- data/evals/cases/r2d2-mechanic/task-fast.yml +59 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/plan/plan.md +7 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/plan/pull-requests.md +6 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/plan/spec.md +6 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/repo/Gemfile +2 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/repo/lib/greeter.rb +6 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/repo/spec/greeter_spec.rb +11 -0
- data/evals/cases/r2d2-mechanic/task-medium/fail/run.json +1 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/plan/plan.md +7 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/plan/pull-requests.md +6 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/plan/spec.md +6 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/repo/Gemfile +2 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/repo/lib/greeter.rb +6 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/repo/spec/greeter_spec.rb +11 -0
- data/evals/cases/r2d2-mechanic/task-medium/pass/run.json +1 -0
- data/evals/cases/r2d2-mechanic/task-medium.yml +58 -0
- data/evals/cases/rey-frontend/ui-medium/fail/plan/plan-frontend.md +7 -0
- data/evals/cases/rey-frontend/ui-medium/fail/plan/plan.md +4 -0
- data/evals/cases/rey-frontend/ui-medium/fail/plan/pull-requests.md +8 -0
- data/evals/cases/rey-frontend/ui-medium/fail/plan/spec.md +5 -0
- data/evals/cases/rey-frontend/ui-medium/fail/repo/lib/cart.rb +5 -0
- data/evals/cases/rey-frontend/ui-medium/fail/repo/web/cart_badge.js +4 -0
- data/evals/cases/rey-frontend/ui-medium/fail/repo/web/cart_badge.test.js +3 -0
- data/evals/cases/rey-frontend/ui-medium/fail/run.json +1 -0
- data/evals/cases/rey-frontend/ui-medium/pass/plan/plan-frontend.md +7 -0
- data/evals/cases/rey-frontend/ui-medium/pass/plan/plan.md +4 -0
- data/evals/cases/rey-frontend/ui-medium/pass/plan/pull-requests.md +8 -0
- data/evals/cases/rey-frontend/ui-medium/pass/plan/spec.md +5 -0
- data/evals/cases/rey-frontend/ui-medium/pass/repo/lib/cart.rb +3 -0
- data/evals/cases/rey-frontend/ui-medium/pass/repo/web/cart_badge.js +4 -0
- data/evals/cases/rey-frontend/ui-medium/pass/repo/web/cart_badge.test.js +3 -0
- data/evals/cases/rey-frontend/ui-medium/pass/run.json +1 -0
- data/evals/cases/rey-frontend/ui-medium.yml +55 -0
- data/evals/cases/yoda-writer/spec-medium/fail/plan/plan.md +5 -0
- data/evals/cases/yoda-writer/spec-medium/fail/plan/spec.md +45 -0
- data/evals/cases/yoda-writer/spec-medium/fail/run.json +1 -0
- data/evals/cases/yoda-writer/spec-medium/pass/plan/plan.md +0 -0
- data/evals/cases/yoda-writer/spec-medium/pass/plan/spec.md +63 -0
- data/evals/cases/yoda-writer/spec-medium/pass/run.json +1 -0
- data/evals/cases/yoda-writer/spec-medium.yml +40 -0
- data/exe/depot +1 -0
- data/exe/lanes +1 -0
- data/exe/openlanes +32 -0
- data/lib/openlanes/adapters/base.rb +69 -0
- data/lib/openlanes/adapters/claude.rb +124 -0
- data/lib/openlanes/adapters/codex.rb +33 -0
- data/lib/openlanes/adapters/model.rb +37 -0
- data/lib/openlanes/adapters/pi.rb +22 -0
- data/lib/openlanes/adapters/stream.rb +196 -0
- data/lib/openlanes/adapters.rb +92 -0
- data/lib/openlanes/agents/agent.rb +103 -0
- data/lib/openlanes/agents/profile.rb +57 -0
- data/lib/openlanes/agents/registry.rb +151 -0
- data/lib/openlanes/agents/roster.rb +103 -0
- data/lib/openlanes/agents/routing.rb +88 -0
- data/lib/openlanes/agents.rb +20 -0
- data/lib/openlanes/cli/agents/subcommands/describe.rb +66 -0
- data/lib/openlanes/cli/agents/subcommands/list.rb +20 -0
- data/lib/openlanes/cli/base.rb +263 -0
- data/lib/openlanes/cli/create/create.rb +354 -0
- data/lib/openlanes/cli/docs/docs.rb +40 -0
- data/lib/openlanes/cli/eval/eval.rb +110 -0
- data/lib/openlanes/cli/index/index.rb +40 -0
- data/lib/openlanes/cli/init/init.rb +145 -0
- data/lib/openlanes/cli/linear/linear.rb +36 -0
- data/lib/openlanes/cli/linear/subcommands/import.rb +174 -0
- data/lib/openlanes/cli/linear/subcommands/projects.rb +55 -0
- data/lib/openlanes/cli/list_plans/list_plans.rb +21 -0
- data/lib/openlanes/cli/mail/mail.rb +31 -0
- data/lib/openlanes/cli/mail/subcommands/read.rb +43 -0
- data/lib/openlanes/cli/mail/subcommands/send.rb +43 -0
- data/lib/openlanes/cli/resync/subcommands/dirs.rb +48 -0
- data/lib/openlanes/cli/resync/subcommands/prs.rb +111 -0
- data/lib/openlanes/cli/run/run.rb +544 -0
- data/lib/openlanes/cli/skills/subcommands/install.rb +92 -0
- data/lib/openlanes/cli/start/start.rb +110 -0
- data/lib/openlanes/cli/state/state.rb +26 -0
- data/lib/openlanes/cli/state/subcommands/show.rb +30 -0
- data/lib/openlanes/cli/state/subcommands/sign.rb +48 -0
- data/lib/openlanes/cli/states/states.rb +15 -0
- data/lib/openlanes/cli/unblock/unblock.rb +235 -0
- data/lib/openlanes/cli/version/version.rb +13 -0
- data/lib/openlanes/cli/web/web.rb +100 -0
- data/lib/openlanes/cli/worktree/worktree.rb +85 -0
- data/lib/openlanes/cli.rb +162 -0
- data/lib/openlanes/engine/board.rb +104 -0
- data/lib/openlanes/engine/dispatcher.rb +1297 -0
- data/lib/openlanes/engine/progress_log.rb +125 -0
- data/lib/openlanes/engine/runner.rb +224 -0
- data/lib/openlanes/engine/state_file.rb +190 -0
- data/lib/openlanes/engine/tally.rb +177 -0
- data/lib/openlanes/engine.rb +19 -0
- data/lib/openlanes/evals/case.rb +191 -0
- data/lib/openlanes/evals/changes.rb +67 -0
- data/lib/openlanes/evals/check.rb +224 -0
- data/lib/openlanes/evals/evidence.rb +71 -0
- data/lib/openlanes/evals/live.rb +140 -0
- data/lib/openlanes/evals/recording.rb +38 -0
- data/lib/openlanes/evals/run.rb +34 -0
- data/lib/openlanes/evals/score.rb +24 -0
- data/lib/openlanes/evals/scorer.rb +42 -0
- data/lib/openlanes/evals/verdict.rb +18 -0
- data/lib/openlanes/evals.rb +56 -0
- data/lib/openlanes/execution/checks.rb +73 -0
- data/lib/openlanes/execution/child.rb +228 -0
- data/lib/openlanes/execution/clock.rb +121 -0
- data/lib/openlanes/execution/control.rb +194 -0
- data/lib/openlanes/execution/executor.rb +849 -0
- data/lib/openlanes/execution/prompt.rb +455 -0
- data/lib/openlanes/execution/transcript.rb +531 -0
- data/lib/openlanes/execution.rb +19 -0
- data/lib/openlanes/launcher.rb +69 -0
- data/lib/openlanes/lifecycle/adoption.rb +197 -0
- data/lib/openlanes/lifecycle/brief.rb +288 -0
- data/lib/openlanes/lifecycle/completion.rb +58 -0
- data/lib/openlanes/lifecycle/creator.rb +126 -0
- data/lib/openlanes/lifecycle/lanes.rb +74 -0
- data/lib/openlanes/lifecycle/resync.rb +428 -0
- data/lib/openlanes/lifecycle/unblocker.rb +236 -0
- data/lib/openlanes/lifecycle.rb +28 -0
- data/lib/openlanes/linear/api.rb +224 -0
- data/lib/openlanes/linear/attribution.rb +185 -0
- data/lib/openlanes/linear/fuzzy.rb +68 -0
- data/lib/openlanes/linear/import.rb +314 -0
- data/lib/openlanes/linear/issue.rb +187 -0
- data/lib/openlanes/linear/mapping.rb +116 -0
- data/lib/openlanes/linear/push.rb +197 -0
- data/lib/openlanes/linear/survey.rb +174 -0
- data/lib/openlanes/linear/unit.rb +274 -0
- data/lib/openlanes/linear.rb +41 -0
- data/lib/openlanes/live/claims.rb +71 -0
- data/lib/openlanes/live/command.rb +122 -0
- data/lib/openlanes/live/commands.rb +505 -0
- data/lib/openlanes/live/keys.rb +93 -0
- data/lib/openlanes/live/memory_store.rb +265 -0
- data/lib/openlanes/live/meter.rb +51 -0
- data/lib/openlanes/live/node.rb +88 -0
- data/lib/openlanes/live/publisher.rb +179 -0
- data/lib/openlanes/live/repo.rb +60 -0
- data/lib/openlanes/live/run_record.rb +133 -0
- data/lib/openlanes/live/run_state.rb +101 -0
- data/lib/openlanes/live/session.rb +107 -0
- data/lib/openlanes/live/snapshot.rb +322 -0
- data/lib/openlanes/live/stations.rb +67 -0
- data/lib/openlanes/live/store.rb +277 -0
- data/lib/openlanes/live.rb +109 -0
- data/lib/openlanes/plans/dev_work.rb +56 -0
- data/lib/openlanes/plans/feature.rb +131 -0
- data/lib/openlanes/plans/index.rb +208 -0
- data/lib/openlanes/plans/ledger.rb +255 -0
- data/lib/openlanes/plans/mailbox.rb +164 -0
- data/lib/openlanes/plans/ordinal.rb +92 -0
- data/lib/openlanes/plans/plan_state.rb +291 -0
- data/lib/openlanes/plans/pull_request.rb +29 -0
- data/lib/openlanes/plans/pull_requests.rb +229 -0
- data/lib/openlanes/plans/spec.rb +213 -0
- data/lib/openlanes/plans/state_machine.rb +433 -0
- data/lib/openlanes/plans/status.rb +365 -0
- data/lib/openlanes/plans/subject.rb +133 -0
- data/lib/openlanes/plans/tree.rb +79 -0
- data/lib/openlanes/plans.rb +39 -0
- data/lib/openlanes/presentation/diagram.rb +149 -0
- data/lib/openlanes/presentation/documentation.rb +438 -0
- data/lib/openlanes/presentation/reporter.rb +181 -0
- data/lib/openlanes/presentation/viewer.rb +62 -0
- data/lib/openlanes/presentation.rb +23 -0
- data/lib/openlanes/skills/installer.rb +68 -0
- data/lib/openlanes/skills/skill.rb +60 -0
- data/lib/openlanes/skills.rb +25 -0
- data/lib/openlanes/support/config.rb +217 -0
- data/lib/openlanes/support/frontmatter.rb +38 -0
- data/lib/openlanes/support/markdown.rb +56 -0
- data/lib/openlanes/support/project.rb +79 -0
- data/lib/openlanes/support/ui.rb +671 -0
- data/lib/openlanes/vcs/github.rb +171 -0
- data/lib/openlanes/vcs/publisher.rb +221 -0
- data/lib/openlanes/vcs/worktree.rb +248 -0
- data/lib/openlanes/vcs.rb +19 -0
- data/lib/openlanes/version.rb +10 -0
- data/lib/openlanes/web/app.rb +176 -0
- data/lib/openlanes/web/context.rb +66 -0
- data/lib/openlanes/web/demo.rb +169 -0
- data/lib/openlanes/web/demo_board.rb +286 -0
- data/lib/openlanes/web/events.rb +104 -0
- data/lib/openlanes/web/launcher.rb +143 -0
- data/lib/openlanes/web/server.rb +77 -0
- data/lib/openlanes/web/stores.rb +36 -0
- data/lib/openlanes/web.rb +92 -0
- data/lib/openlanes.rb +151 -0
- data/openlanes.gemspec +87 -0
- data/schemas/plan-state.schema.json +101 -0
- data/skills/openlanes/SKILL.md +48 -0
- data/skills/openlanes-documents/SKILL.md +61 -0
- data/skills/openlanes-documents/references/blocked-template.md +49 -0
- data/skills/openlanes-documents/references/closing-templates.md +59 -0
- data/skills/openlanes-documents/references/plan-template.md +61 -0
- data/skills/openlanes-documents/references/retroactive-spec.md +43 -0
- data/skills/openlanes-documents/references/spec-template.md +45 -0
- data/skills/openlanes-run/SKILL.md +90 -0
- data/skills/openlanes-setup/SKILL.md +64 -0
- data/skills/openlanes-spec/SKILL.md +90 -0
- data/skills/openlanes-status/SKILL.md +32 -0
- data/skills/openlanes-sync/SKILL.md +22 -0
- data/web/dist/assets/apl-B4CMkyY2.js +2 -0
- data/web/dist/assets/asciiarmor-Df11BRmG.js +2 -0
- data/web/dist/assets/asn1-EdZsLKOL.js +2 -0
- data/web/dist/assets/asterisk-B-8jnY81.js +2 -0
- data/web/dist/assets/barlow-condensed-latin-600-normal-BFJEwTuo.woff +0 -0
- data/web/dist/assets/barlow-condensed-latin-600-normal-DepVgxBB.woff2 +0 -0
- data/web/dist/assets/barlow-condensed-latin-ext-600-normal-18ESti3H.woff2 +0 -0
- data/web/dist/assets/barlow-condensed-latin-ext-600-normal-Clv9cIcR.woff +0 -0
- data/web/dist/assets/barlow-condensed-vietnamese-600-normal-A5AYRdjN.woff2 +0 -0
- data/web/dist/assets/barlow-condensed-vietnamese-600-normal-CNlPk46_.woff +0 -0
- data/web/dist/assets/brainfuck-C4LP7Hcl.js +2 -0
- data/web/dist/assets/clike-B9uivgTg.js +2 -0
- data/web/dist/assets/clojure-BMjYHr_A.js +2 -0
- data/web/dist/assets/cmake-BQqOBYOt.js +2 -0
- data/web/dist/assets/cobol-CWcv1MsR.js +2 -0
- data/web/dist/assets/coffeescript-S37ZYGWr.js +2 -0
- data/web/dist/assets/commonlisp-DBKNyK5s.js +2 -0
- data/web/dist/assets/crystal-SjHAIU92.js +2 -0
- data/web/dist/assets/css-BnMrqG3P.js +2 -0
- data/web/dist/assets/cypher-C_CwsFkJ.js +2 -0
- data/web/dist/assets/d-pRatUO7H.js +2 -0
- data/web/dist/assets/diff-DbItnlRl.js +2 -0
- data/web/dist/assets/dockerfile-BKs6k2Af.js +2 -0
- data/web/dist/assets/dtd-DF_7sFjM.js +2 -0
- data/web/dist/assets/dylan-DwRh75JA.js +2 -0
- data/web/dist/assets/ebnf-CDyGwa7X.js +2 -0
- data/web/dist/assets/ecl-Cabwm37j.js +2 -0
- data/web/dist/assets/eiffel-CnydiIhH.js +2 -0
- data/web/dist/assets/elm-vLlmbW-K.js +2 -0
- data/web/dist/assets/erlang-BNw1qcRV.js +2 -0
- data/web/dist/assets/factor-kuTfRLto.js +2 -0
- data/web/dist/assets/favicon-C8pAuqqr.png +0 -0
- data/web/dist/assets/fcl-Kvtd6kyn.js +2 -0
- data/web/dist/assets/forth-Ffai-XNe.js +2 -0
- data/web/dist/assets/fortran-DYz_wnZ1.js +2 -0
- data/web/dist/assets/gas-Bneqetm1.js +2 -0
- data/web/dist/assets/gherkin-heZmZLOM.js +2 -0
- data/web/dist/assets/groovy-D9Dt4D0W.js +2 -0
- data/web/dist/assets/haskell-Cw1EW3IL.js +2 -0
- data/web/dist/assets/haxe-H-WmDvRZ.js +2 -0
- data/web/dist/assets/http-DBlCnlav.js +2 -0
- data/web/dist/assets/idl-BEugSyMb.js +2 -0
- data/web/dist/assets/index-2xPC-eHt.js +2 -0
- data/web/dist/assets/index-8dNhRmiO.js +8 -0
- data/web/dist/assets/index-B7-O9ujQ.js +2 -0
- data/web/dist/assets/index-BdR34cXC.js +2 -0
- data/web/dist/assets/index-Bp7W-ZTq.js +2 -0
- data/web/dist/assets/index-Byyxbh_z.js +4 -0
- data/web/dist/assets/index-C0VmkrO-.js +2 -0
- data/web/dist/assets/index-CI74IkWP.js +198 -0
- data/web/dist/assets/index-CYxcbpRM.js +3 -0
- data/web/dist/assets/index-D0rmJN3h.js +2 -0
- data/web/dist/assets/index-DB4QwuYn.js +2 -0
- data/web/dist/assets/index-DDI7zoAS.js +2 -0
- data/web/dist/assets/index-DGLUuEXi.js +2 -0
- data/web/dist/assets/index-OR-odsl9.js +2 -0
- data/web/dist/assets/index-P3vSkiU-.js +2 -0
- data/web/dist/assets/index-a4EHR-u4.css +1 -0
- data/web/dist/assets/index-mRV4XhJZ.js +2 -0
- data/web/dist/assets/index-popk9wbW.js +2 -0
- data/web/dist/assets/index-uM88Njdj.js +2 -0
- data/web/dist/assets/javascript-iXu5QeM3.js +2 -0
- data/web/dist/assets/julia-DuME0IfC.js +2 -0
- data/web/dist/assets/livescript-BwQOo05w.js +2 -0
- data/web/dist/assets/logo-on-dark-DI9gCdN4.png +0 -0
- data/web/dist/assets/logo-on-light-BciwqxcJ.png +0 -0
- data/web/dist/assets/lua-VAEuO923.js +2 -0
- data/web/dist/assets/mathematica-DTrFuWx2.js +2 -0
- data/web/dist/assets/mbox-CNhZ1qSd.js +2 -0
- data/web/dist/assets/mirc-CjQqDB4T.js +2 -0
- data/web/dist/assets/mllike-CXdrOF99.js +2 -0
- data/web/dist/assets/modelica-Dc1JOy9r.js +2 -0
- data/web/dist/assets/mscgen-BA5vi2Kp.js +2 -0
- data/web/dist/assets/mumps-BT43cFF4.js +2 -0
- data/web/dist/assets/nginx-DdIZxoE0.js +2 -0
- data/web/dist/assets/nsis-LY0VT_u5.js +2 -0
- data/web/dist/assets/ntriples-BfvgReVJ.js +2 -0
- data/web/dist/assets/octave-Ck1zUtKM.js +2 -0
- data/web/dist/assets/overpass-cyrillic-400-normal-CZsFKZCk.woff +0 -0
- data/web/dist/assets/overpass-cyrillic-400-normal-tXDxWGRy.woff2 +0 -0
- data/web/dist/assets/overpass-cyrillic-600-normal-B-hc8bdn.woff2 +0 -0
- data/web/dist/assets/overpass-cyrillic-600-normal-CXB2Mrig.woff +0 -0
- data/web/dist/assets/overpass-cyrillic-800-normal-CP6RWG8X.woff2 +0 -0
- data/web/dist/assets/overpass-cyrillic-800-normal-PJW4A9xW.woff +0 -0
- data/web/dist/assets/overpass-cyrillic-ext-400-normal-AKUz2ZI5.woff2 +0 -0
- data/web/dist/assets/overpass-cyrillic-ext-400-normal-C2ZjKngx.woff +0 -0
- data/web/dist/assets/overpass-cyrillic-ext-600-normal-CW4Fknsm.woff2 +0 -0
- data/web/dist/assets/overpass-cyrillic-ext-600-normal-Zx-mtRSD.woff +0 -0
- data/web/dist/assets/overpass-cyrillic-ext-800-normal-CIth-cA7.woff2 +0 -0
- data/web/dist/assets/overpass-cyrillic-ext-800-normal-hx3cL3ZN.woff +0 -0
- data/web/dist/assets/overpass-latin-400-normal-BpeLJ0bs.woff2 +0 -0
- data/web/dist/assets/overpass-latin-400-normal-c_C8zulu.woff +0 -0
- data/web/dist/assets/overpass-latin-600-normal-25RhTNCi.woff2 +0 -0
- data/web/dist/assets/overpass-latin-600-normal-DhYQrzaY.woff +0 -0
- data/web/dist/assets/overpass-latin-800-normal-Bzx_XP5l.woff2 +0 -0
- data/web/dist/assets/overpass-latin-800-normal-D6-q4f0l.woff +0 -0
- data/web/dist/assets/overpass-latin-ext-400-normal-BIkXRwl6.woff2 +0 -0
- data/web/dist/assets/overpass-latin-ext-400-normal-DUq_h-_z.woff +0 -0
- data/web/dist/assets/overpass-latin-ext-600-normal-Cyj4Nio0.woff2 +0 -0
- data/web/dist/assets/overpass-latin-ext-600-normal-DQKKMGzp.woff +0 -0
- data/web/dist/assets/overpass-latin-ext-800-normal-D6WKWGNs.woff +0 -0
- data/web/dist/assets/overpass-latin-ext-800-normal-DZ60AB77.woff2 +0 -0
- data/web/dist/assets/overpass-mono-cyrillic-400-normal-B144qALP.woff2 +0 -0
- data/web/dist/assets/overpass-mono-cyrillic-400-normal-XK9_jwT8.woff +0 -0
- data/web/dist/assets/overpass-mono-cyrillic-ext-400-normal-Dgejvo4A.woff2 +0 -0
- data/web/dist/assets/overpass-mono-cyrillic-ext-400-normal-DgxwtUZy.woff +0 -0
- data/web/dist/assets/overpass-mono-latin-400-normal-DqAOLzCO.woff +0 -0
- data/web/dist/assets/overpass-mono-latin-400-normal-VINZG6Js.woff2 +0 -0
- data/web/dist/assets/overpass-mono-latin-ext-400-normal-5YIw-E8g.woff +0 -0
- data/web/dist/assets/overpass-mono-latin-ext-400-normal-DBdJzBs_.woff2 +0 -0
- data/web/dist/assets/overpass-mono-vietnamese-400-normal-DrLp_cHj.woff +0 -0
- data/web/dist/assets/overpass-mono-vietnamese-400-normal-jmOeL0rj.woff2 +0 -0
- data/web/dist/assets/overpass-vietnamese-400-normal-BKRCEiXy.woff2 +0 -0
- data/web/dist/assets/overpass-vietnamese-400-normal-CCWAWVG7.woff +0 -0
- data/web/dist/assets/overpass-vietnamese-600-normal-CjS-4vQf.woff2 +0 -0
- data/web/dist/assets/overpass-vietnamese-600-normal-DlMMlPfM.woff +0 -0
- data/web/dist/assets/overpass-vietnamese-800-normal-BB8K9-Vo.woff2 +0 -0
- data/web/dist/assets/overpass-vietnamese-800-normal-DZUygvj_.woff +0 -0
- data/web/dist/assets/oz-BzwKVEFT.js +2 -0
- data/web/dist/assets/pascal--L3eBynH.js +2 -0
- data/web/dist/assets/perl-CdXCOZ3F.js +2 -0
- data/web/dist/assets/pig-CevX1Tat.js +2 -0
- data/web/dist/assets/powershell-CFHJl5sT.js +2 -0
- data/web/dist/assets/properties-C78fOPTZ.js +2 -0
- data/web/dist/assets/protobuf-ChK-085T.js +2 -0
- data/web/dist/assets/pug-DeIclll2.js +2 -0
- data/web/dist/assets/puppet-DMA9R1ak.js +2 -0
- data/web/dist/assets/python-BuPzkPfP.js +2 -0
- data/web/dist/assets/q-pXgVlZs6.js +2 -0
- data/web/dist/assets/r-B6wPVr8A.js +2 -0
- data/web/dist/assets/rpm-CTu-6PCP.js +2 -0
- data/web/dist/assets/ruby-BK5aLJev.js +2 -0
- data/web/dist/assets/sas-B4kiWyti.js +2 -0
- data/web/dist/assets/scheme-C41bIUwD.js +2 -0
- data/web/dist/assets/shell-CjFT_Tl9.js +2 -0
- data/web/dist/assets/sieve-C3Gn_uJK.js +2 -0
- data/web/dist/assets/simple-mode-GW_nhZxv.js +2 -0
- data/web/dist/assets/smalltalk-CnHTOXQT.js +2 -0
- data/web/dist/assets/solr-DehyRSwq.js +2 -0
- data/web/dist/assets/sparql-DkYu6x3z.js +2 -0
- data/web/dist/assets/spreadsheet-BCZA_wO0.js +2 -0
- data/web/dist/assets/sql-D0XecflT.js +2 -0
- data/web/dist/assets/stex-C3f8Ysf7.js +2 -0
- data/web/dist/assets/stylus-B533Al4x.js +2 -0
- data/web/dist/assets/swift-BzpIVaGY.js +2 -0
- data/web/dist/assets/tcl-DVfN8rqt.js +2 -0
- data/web/dist/assets/textile-CnDTJFAw.js +2 -0
- data/web/dist/assets/tiddlywiki-DO-Gjzrf.js +2 -0
- data/web/dist/assets/tiki-DGYXhP31.js +2 -0
- data/web/dist/assets/toml-Bm5Em-hy.js +2 -0
- data/web/dist/assets/troff-wAsdV37c.js +2 -0
- data/web/dist/assets/ttcn-CfJYG6tj.js +2 -0
- data/web/dist/assets/ttcn-cfg-B9xdYoR4.js +2 -0
- data/web/dist/assets/turtle-B1tBg_DP.js +2 -0
- data/web/dist/assets/vb-CmGdzxic.js +2 -0
- data/web/dist/assets/vbscript-BuJXcnF6.js +2 -0
- data/web/dist/assets/velocity-D8B20fx6.js +2 -0
- data/web/dist/assets/verilog-C6RDOZhf.js +2 -0
- data/web/dist/assets/vhdl-lSbBsy5d.js +2 -0
- data/web/dist/assets/webidl-ZXfAyPTL.js +2 -0
- data/web/dist/assets/xquery-DzFWVndE.js +2 -0
- data/web/dist/assets/yacas-BJ4BC0dw.js +2 -0
- data/web/dist/assets/z80-Hz9HOZM7.js +2 -0
- data/web/dist/index.html +14 -0
- data/web/dist/vite.svg +1 -0
- data/web/src/api/fixtures/board.json +512 -0
- 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
|