openlanes 3.0.1 → 3.1.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 +4 -4
- data/Brewfile +42 -0
- data/agents/bb8-resolver.md +49 -0
- data/agents/c3po-operator.md +4 -0
- data/agents/chewie-verifier.md +85 -0
- data/agents/hansolo-reviewer.md +20 -15
- data/agents/lando-broker.md +81 -19
- data/agents/leah-researcher.md +6 -6
- data/agents/luke-backend.md +41 -20
- data/agents/{palpatine-planner.md → luthen-rael-planner.md} +15 -13
- data/agents/palpatine-risk-assessor.md +112 -0
- data/agents/r2d2-mechanic.md +5 -1
- data/agents/rey-frontend.md +38 -19
- data/agents/yoda-writer.md +7 -7
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/plan.md +7 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/pull-requests.md +10 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/spec.md +5 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/verification/1/final.png +0 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/plan/verification/1/report.md +7 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/repo/bin/greet +1 -0
- data/evals/cases/chewie-verifier/verify-medium/fail/run.json +1 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/plan.md +7 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/pull-requests.md +10 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/spec.md +5 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/verification/1/final.png +0 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/verification/1/report.md +14 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/plan/verification/1/step-01.png +0 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/repo/bin/greet +1 -0
- data/evals/cases/chewie-verifier/verify-medium/pass/run.json +1 -0
- data/evals/cases/chewie-verifier/verify-medium.yml +48 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/implementation-plan.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/plan.md +3 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/pull-requests.md +12 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/plan/spec.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/repo/lib/cart.rb +10 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/repo/spec/cart_spec.rb +9 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/fail/run.json +1 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/implementation-plan.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/plan.md +3 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/pull-requests.md +15 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/plan/spec.md +5 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/repo/lib/cart.rb +10 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/repo/spec/cart_spec.rb +9 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum/pass/run.json +1 -0
- data/evals/cases/hansolo-reviewer/coverage-minimum.yml +76 -0
- data/evals/cases/luthen-rael-planner/plan-deep/fail/plan/plan.md +8 -0
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/plan.md +2 -2
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep.yml +1 -1
- data/evals/cases/luthen-rael-planner/plan-fast/fail/plan/plan.md +12 -0
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/plan.md +2 -2
- data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast.yml +1 -1
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/plan/plan.md +20 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/plan/pull-requests.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/plan/spec.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/repo/db/migrate/20261020120000_drop_legacy_email.rb +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/fail/run.json +1 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/plan/plan.md +25 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/plan/pull-requests.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/plan/spec.md +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/repo/db/migrate/20261020120000_drop_legacy_email.rb +5 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium/pass/run.json +1 -0
- data/evals/cases/palpatine-risk-assessor/risk-medium.yml +47 -0
- data/evals/cases/yoda-writer/spec-medium/pass/plan/spec.md +1 -1
- data/lib/openlanes/adapters/base.rb +18 -4
- data/lib/openlanes/adapters/capabilities.rb +29 -0
- data/lib/openlanes/adapters/claude.rb +14 -11
- data/lib/openlanes/adapters/codex.rb +9 -0
- data/lib/openlanes/adapters/pi.rb +10 -0
- data/lib/openlanes/adapters/stream.rb +145 -1
- data/lib/openlanes/adapters.rb +7 -0
- data/lib/openlanes/agents/agent.rb +4 -3
- data/lib/openlanes/agents/roster.rb +14 -2
- data/lib/openlanes/agents/routing.rb +43 -3
- data/lib/openlanes/apps/definition.rb +152 -0
- data/lib/openlanes/apps/framing.rb +114 -0
- data/lib/openlanes/apps/listing.rb +60 -0
- data/lib/openlanes/apps/ports.rb +168 -0
- data/lib/openlanes/apps/probe.rb +63 -0
- data/lib/openlanes/apps/process_spec.rb +39 -0
- data/lib/openlanes/apps/runner.rb +174 -0
- data/lib/openlanes/apps/status.rb +62 -0
- data/lib/openlanes/apps/supervisor.rb +277 -0
- data/lib/openlanes/apps/watcher.rb +50 -0
- data/lib/openlanes/apps.rb +41 -0
- data/lib/openlanes/cli/api/api.rb +10 -0
- data/lib/openlanes/cli/api/subcommands/docs.rb +63 -0
- data/lib/openlanes/cli/app/app.rb +31 -0
- data/lib/openlanes/cli/app/subcommands/start.rb +114 -0
- data/lib/openlanes/cli/app/subcommands/status.rb +38 -0
- data/lib/openlanes/cli/app/subcommands/stop.rb +44 -0
- data/lib/openlanes/cli/base.rb +89 -16
- data/lib/openlanes/cli/coverage/coverage.rb +68 -0
- data/lib/openlanes/cli/create/create.rb +11 -265
- data/lib/openlanes/cli/eval/eval.rb +4 -1
- data/lib/openlanes/cli/init/init.rb +39 -2
- data/lib/openlanes/cli/linear/subcommands/import.rb +1 -1
- data/lib/openlanes/cli/linear/subcommands/projects.rb +1 -1
- data/lib/openlanes/cli/list_plans/list_plans.rb +8 -2
- data/lib/openlanes/cli/mail/subcommands/ack.rb +52 -0
- data/lib/openlanes/cli/mail/subcommands/render.rb +31 -0
- data/lib/openlanes/cli/mail/subcommands/send.rb +26 -0
- data/lib/openlanes/cli/resync/subcommands/dirs.rb +53 -12
- data/lib/openlanes/cli/resync/subcommands/prs.rb +8 -69
- data/lib/openlanes/cli/review/review.rb +72 -0
- data/lib/openlanes/cli/run/run.rb +22 -486
- data/lib/openlanes/cli/setup/setup.rb +51 -0
- data/lib/openlanes/cli/start/start.rb +7 -51
- data/lib/openlanes/cli/state/state.rb +1 -1
- data/lib/openlanes/cli/state/subcommands/resume.rb +61 -0
- data/lib/openlanes/cli/state/subcommands/sign.rb +1 -1
- data/lib/openlanes/cli/unblock/unblock.rb +11 -205
- data/lib/openlanes/cli/web/web.rb +84 -8
- data/lib/openlanes/cli/worktree/worktree.rb +1 -1
- data/lib/openlanes/cli/worktree/worktree_prune.rb +77 -0
- data/lib/openlanes/cli.rb +37 -2
- data/lib/openlanes/coverage/cobertura.rb +49 -0
- data/lib/openlanes/coverage/config.rb +194 -0
- data/lib/openlanes/coverage/istanbul.rb +31 -0
- data/lib/openlanes/coverage/lcov.rb +38 -0
- data/lib/openlanes/coverage/report.rb +71 -0
- data/lib/openlanes/coverage/simplecov.rb +70 -0
- data/lib/openlanes/coverage.rb +33 -0
- data/lib/openlanes/engine/agent_locks.rb +231 -0
- data/lib/openlanes/engine/arbitration.rb +25 -0
- data/lib/openlanes/engine/board.rb +36 -5
- data/lib/openlanes/engine/conversions.rb +160 -0
- data/lib/openlanes/engine/coverage_gate.rb +217 -0
- data/lib/openlanes/engine/dispatcher.rb +1175 -117
- data/lib/openlanes/engine/event_journal/replay.rb +96 -0
- data/lib/openlanes/engine/event_journal.rb +148 -0
- data/lib/openlanes/engine/extensions.rb +271 -0
- data/lib/openlanes/engine/merging.rb +459 -0
- data/lib/openlanes/engine/narrator.rb +6 -6
- data/lib/openlanes/engine/plan_locks.rb +114 -0
- data/lib/openlanes/engine/pr_outcomes.rb +161 -0
- data/lib/openlanes/engine/pr_watch.rb +83 -0
- data/lib/openlanes/engine/progress_log.rb +12 -120
- data/lib/openlanes/engine/quality_question.rb +125 -0
- data/lib/openlanes/engine/rebase.rb +200 -0
- data/lib/openlanes/engine/run_workflow.rb +776 -0
- data/lib/openlanes/engine/runner.rb +186 -15
- data/lib/openlanes/engine/start_workflow.rb +77 -0
- data/lib/openlanes/engine/state_file.rb +101 -34
- data/lib/openlanes/engine/tally.rb +1 -1
- data/lib/openlanes/engine/verification/app_session.rb +104 -0
- data/lib/openlanes/engine/verification/driver.rb +314 -0
- data/lib/openlanes/engine/verification/outcome.rb +516 -0
- data/lib/openlanes/engine/verification/report.rb +95 -0
- data/lib/openlanes/engine/verification/round.rb +100 -0
- data/lib/openlanes/engine/verification/screenshots.rb +120 -0
- data/lib/openlanes/engine/verification.rb +57 -0
- data/lib/openlanes/engine.rb +30 -1
- data/lib/openlanes/execution/boundary.rb +212 -0
- data/lib/openlanes/execution/child.rb +4 -2
- data/lib/openlanes/execution/clock.rb +36 -6
- data/lib/openlanes/execution/control.rb +32 -0
- data/lib/openlanes/execution/executor.rb +114 -35
- data/lib/openlanes/execution/process_start.rb +63 -0
- data/lib/openlanes/execution/prompt.rb +223 -34
- data/lib/openlanes/execution/transcript.rb +159 -8
- data/lib/openlanes/execution.rb +21 -0
- data/lib/openlanes/lifecycle/adoption.rb +25 -8
- data/lib/openlanes/lifecycle/aging.rb +166 -0
- data/lib/openlanes/lifecycle/allocator.rb +216 -0
- data/lib/openlanes/lifecycle/brief.rb +32 -2
- data/lib/openlanes/lifecycle/completion.rb +12 -6
- data/lib/openlanes/lifecycle/create_workflow.rb +272 -0
- data/lib/openlanes/lifecycle/creator.rb +17 -13
- data/lib/openlanes/lifecycle/lanes.rb +1 -1
- data/lib/openlanes/lifecycle/resync.rb +170 -42
- data/lib/openlanes/lifecycle/unblock_workflow.rb +234 -0
- data/lib/openlanes/lifecycle/unblocker.rb +76 -2
- data/lib/openlanes/lifecycle.rb +37 -1
- data/lib/openlanes/linear/mapping.rb +4 -1
- data/lib/openlanes/linear/unit.rb +1 -1
- data/lib/openlanes/live/chat.rb +60 -11
- data/lib/openlanes/live/choice_converter.rb +166 -0
- data/lib/openlanes/live/choice_judge.rb +109 -0
- data/lib/openlanes/live/claims.rb +9 -1
- data/lib/openlanes/live/command.rb +13 -10
- data/lib/openlanes/live/commands.rb +217 -30
- data/lib/openlanes/live/copilot/slash.rb +82 -0
- data/lib/openlanes/live/copilot.rb +38 -4
- data/lib/openlanes/live/event_bus/base.rb +52 -0
- data/lib/openlanes/live/event_bus/memory_bus.rb +14 -0
- data/lib/openlanes/live/event_bus/redis_bus.rb +10 -0
- data/lib/openlanes/live/event_bus/transaction.rb +27 -0
- data/lib/openlanes/live/event_bus.rb +29 -0
- data/lib/openlanes/live/interviewer.rb +146 -0
- data/lib/openlanes/live/keys.rb +56 -0
- data/lib/openlanes/live/mail.rb +143 -0
- data/lib/openlanes/live/memory_store.rb +12 -1
- data/lib/openlanes/live/news.rb +61 -0
- data/lib/openlanes/live/node.rb +20 -0
- data/lib/openlanes/live/outage.rb +225 -0
- data/lib/openlanes/live/outage_report.rb +89 -0
- data/lib/openlanes/live/publisher.rb +42 -2
- data/lib/openlanes/live/run_record.rb +39 -1
- data/lib/openlanes/live/run_state.rb +19 -2
- data/lib/openlanes/live/session.rb +31 -3
- data/lib/openlanes/live/snapshot.rb +103 -42
- data/lib/openlanes/live/stations.rb +4 -4
- data/lib/openlanes/live/store.rb +18 -1
- data/lib/openlanes/live/subscriber.rb +22 -2
- data/lib/openlanes/live.rb +66 -1
- data/lib/openlanes/plans/blocked_file.rb +117 -0
- data/lib/openlanes/plans/choices.rb +228 -0
- data/lib/openlanes/plans/dependencies.rb +304 -0
- data/lib/openlanes/plans/feature.rb +75 -16
- data/lib/openlanes/plans/journal.rb +169 -0
- data/lib/openlanes/plans/last_known_state.rb +82 -0
- data/lib/openlanes/plans/lock.rb +171 -0
- data/lib/openlanes/plans/mailbox.rb +145 -4
- data/lib/openlanes/plans/plan_state.rb +590 -9
- data/lib/openlanes/plans/pull_request_machine.rb +163 -0
- data/lib/openlanes/plans/review_label.rb +139 -0
- data/lib/openlanes/plans/spec.rb +239 -3
- data/lib/openlanes/plans/state_machine.rb +63 -30
- data/lib/openlanes/plans/status.rb +60 -11
- data/lib/openlanes/plans/subject.rb +4 -0
- data/lib/openlanes/plans/tree.rb +101 -10
- data/lib/openlanes/plans/verdict.rb +124 -0
- data/lib/openlanes/plans.rb +19 -0
- data/lib/openlanes/presentation/api_docs.rb +76 -0
- data/lib/openlanes/presentation/documentation.rb +105 -12
- data/lib/openlanes/presentation/pr_changes.rb +90 -0
- data/lib/openlanes/presentation/reporter.rb +64 -5
- data/lib/openlanes/presentation.rb +20 -1
- data/lib/openlanes/setup/check.rb +16 -0
- data/lib/openlanes/setup/machine.rb +179 -0
- data/lib/openlanes/setup.rb +34 -0
- data/lib/openlanes/support/clock.rb +57 -0
- data/lib/openlanes/support/config.rb +318 -6
- data/lib/openlanes/support/log.rb +206 -0
- data/lib/openlanes/support/refusal.rb +15 -0
- data/lib/openlanes/support/runtime.rb +61 -0
- data/lib/openlanes/support/spawner.rb +51 -0
- data/lib/openlanes/support/ui.rb +39 -24
- data/lib/openlanes/vcs/ci_gate.rb +31 -0
- data/lib/openlanes/vcs/github.rb +362 -12
- data/lib/openlanes/vcs/label_migration.rb +132 -0
- data/lib/openlanes/vcs/label_sync.rb +75 -0
- data/lib/openlanes/vcs/publisher.rb +226 -8
- data/lib/openlanes/vcs/refresh.rb +337 -0
- data/lib/openlanes/vcs/rename_date.rb +83 -0
- data/lib/openlanes/vcs/review.rb +66 -0
- data/lib/openlanes/vcs/worktree.rb +167 -6
- data/lib/openlanes/vcs.rb +18 -0
- data/lib/openlanes/version.rb +1 -1
- data/lib/openlanes/web/admin_auth.rb +104 -0
- data/lib/openlanes/web/app.rb +115 -16
- data/lib/openlanes/web/context.rb +15 -9
- data/lib/openlanes/web/demo.rb +29 -14
- data/lib/openlanes/web/demo_board.rb +63 -5
- data/lib/openlanes/web/events.rb +86 -7
- data/lib/openlanes/web/launcher.rb +69 -7
- data/lib/openlanes/web/listen.rb +133 -0
- data/lib/openlanes/web/plan_files.rb +136 -0
- data/lib/openlanes/web/request_log.rb +108 -0
- data/lib/openlanes/web/server.rb +29 -10
- data/lib/openlanes/web.rb +6 -4
- data/lib/openlanes.rb +45 -7
- data/openlanes.gemspec +9 -4
- data/schemas/journal-event.schema.json +72 -0
- data/schemas/plan-state.schema.json +163 -2
- data/skills/openlanes/SKILL.md +2 -2
- data/skills/openlanes-documents/SKILL.md +4 -4
- data/skills/openlanes-documents/references/blocked-template.md +2 -2
- data/skills/openlanes-documents/references/closing-templates.md +1 -1
- data/skills/openlanes-documents/references/plan-template.md +12 -4
- data/skills/openlanes-documents/references/retroactive-spec.md +1 -1
- data/skills/openlanes-run/SKILL.md +5 -5
- data/skills/openlanes-spec/SKILL.md +1 -1
- data/skills/openlanes-status/SKILL.md +1 -1
- data/web/dist/assets/{index-BFPI-zXI.js → index-690ayLS4.js} +2 -2
- data/web/dist/assets/index-6ZmW4slI.js +199 -0
- data/web/dist/assets/{index-bpsFIpXE.js → index-B5KzlXp1.js} +2 -2
- data/web/dist/assets/{index-DtA46i32.js → index-BAo_v9bU.js} +2 -2
- data/web/dist/assets/{index-CnJfB0l2.js → index-BDm7g-xG.js} +2 -2
- data/web/dist/assets/{index-DXgeajd6.js → index-BFH9bC7v.js} +2 -2
- data/web/dist/assets/{index-DH0gDlee.js → index-BVHPV7z4.js} +2 -2
- data/web/dist/assets/{index-DaSjQ-H4.js → index-B_CPqjnA.js} +2 -2
- data/web/dist/assets/{index-Dbxvk-EC.js → index-BhwBH5ct.js} +2 -2
- data/web/dist/assets/{index-6MZkmh3k.js → index-Bq5AdwEt.js} +2 -2
- data/web/dist/assets/{index-B5CKJPm-.js → index-CAW6ClM2.js} +2 -2
- data/web/dist/assets/index-Cp86u27u.css +1 -0
- data/web/dist/assets/{index-CnvkZu5Y.js → index-Culm4pMO.js} +2 -2
- data/web/dist/assets/{index-IqD8mBos.js → index-DElDJfp1.js} +2 -2
- data/web/dist/assets/{index-C3zc4gFe.js → index-Dja7osNW.js} +2 -2
- data/web/dist/assets/{index-C2jwoWVV.js → index-DjhU3FzU.js} +2 -2
- data/web/dist/assets/{index-pvJi-JJO.js → index-Dutbxj4o.js} +2 -2
- data/web/dist/assets/{index-BF6QYroq.js → index-RkDhPMDt.js} +2 -2
- data/web/dist/assets/{index-DbueqOK5.js → index-_mptZjlC.js} +2 -2
- data/web/dist/index.html +2 -2
- data/web/src/api/fixtures/board.json +203 -28
- metadata +1053 -889
- data/bin/factory-e2e +0 -157
- data/bin/plan-number +0 -164
- data/bin/setup +0 -26
- data/evals/cases/palpatine-planner/plan-deep/fail/plan/plan.md +0 -8
- data/evals/cases/palpatine-planner/plan-fast/fail/plan/plan.md +0 -12
- data/web/dist/assets/index-BYJCWlk0.js +0 -198
- data/web/dist/assets/index-CjInBi5X.css +0 -1
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/fail/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/fail/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/fail/run.json +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/plan-frontend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-deep/pass/run.json +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/plan/plan-frontend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/fail/run.json +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/plan-backend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/plan-frontend.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/plan/spec.md +0 -0
- /data/evals/cases/{palpatine-planner → luthen-rael-planner}/plan-fast/pass/run.json +0 -0
- /data/exe/{depot → ol} +0 -0
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
Six units, U6 is the integration check.
|
|
4
|
+
|
|
5
|
+
## Order
|
|
6
|
+
|
|
7
|
+
U1, then U2 to U5 in parallel, then U6.
|
|
8
|
+
|
|
9
|
+
> [!NOTE]
|
|
10
|
+
>
|
|
11
|
+
> [2026-09-04 01:10:00 PM PDT] [ agent: luthen-rael-planner status: Started, round 1 ]
|
|
12
|
+
> [2026-09-04 01:14:30 PM PDT] [ agent: luthen-rael-planner status: Completed, round 1 ]
|
|
@@ -8,5 +8,5 @@ One unit; it is also the integration check, since there is no front end.
|
|
|
8
8
|
|
|
9
9
|
> [!NOTE]
|
|
10
10
|
>
|
|
11
|
-
> [2026-09-04 01:10:00 PM PDT] [ agent:
|
|
12
|
-
> [2026-09-04 01:14:30 PM PDT] [ agent:
|
|
11
|
+
> [2026-09-04 01:10:00 PM PDT] [ agent: luthen-rael-planner status: Started, round 1 ]
|
|
12
|
+
> [2026-09-04 01:14:30 PM PDT] [ agent: luthen-rael-planner status: Completed, round 1 ]
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
1. U1 Remove the last reader of `legacy_email`, then drop the column.
|
|
4
|
+
|
|
5
|
+
## Merge risk, PR #21
|
|
6
|
+
|
|
7
|
+
**Verdict:** auto
|
|
8
|
+
|
|
9
|
+
**Triggers:** none.
|
|
10
|
+
|
|
11
|
+
**Signals:** none.
|
|
12
|
+
|
|
13
|
+
**Unplanned files:** none.
|
|
14
|
+
|
|
15
|
+
**Rollback:** `git revert` the merge.
|
|
16
|
+
|
|
17
|
+
> [!NOTE]
|
|
18
|
+
>
|
|
19
|
+
> [2026-10-20 10:00:00 AM PDT] [ agent: palpatine-risk-assessor status: Started, round 1 ]
|
|
20
|
+
> [2026-10-20 10:01:00 AM PDT] [ agent: palpatine-risk-assessor status: Completed, round 1 (auto) ]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"seconds":60,"tokens":15000,"state":"completed"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Plan
|
|
2
|
+
|
|
3
|
+
1. U1 Remove the last reader of `legacy_email`, then drop the column.
|
|
4
|
+
|
|
5
|
+
## Merge risk, PR #21
|
|
6
|
+
|
|
7
|
+
**Verdict:** manual
|
|
8
|
+
|
|
9
|
+
**Triggers:** schema and data: `db/migrate/20261020120000_drop_legacy_email.rb:3` drops `users.legacy_email`; the old code still running during the deploy overlap may read it.
|
|
10
|
+
|
|
11
|
+
**Signals:** none.
|
|
12
|
+
|
|
13
|
+
**Unplanned files:** none.
|
|
14
|
+
|
|
15
|
+
**Rollback:** a revert does not restore the dropped data; it needs a restore of the column from the last backup. Without a backup the drop is permanent.
|
|
16
|
+
|
|
17
|
+
**For the human:**
|
|
18
|
+
|
|
19
|
+
- Confirm a backup of `users.legacy_email` exists before merging.
|
|
20
|
+
- Confirm no deployed code reads the column.
|
|
21
|
+
|
|
22
|
+
> [!NOTE]
|
|
23
|
+
>
|
|
24
|
+
> [2026-10-20 10:00:00 AM PDT] [ agent: palpatine-risk-assessor status: Started, round 1 ]
|
|
25
|
+
> [2026-10-20 10:03:00 AM PDT] [ agent: palpatine-risk-assessor status: Completed, round 1 (manual) ]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"seconds":180,"tokens":40000,"state":"completed"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
id: risk-medium
|
|
2
|
+
agent: palpatine-risk-assessor
|
|
3
|
+
depth: medium
|
|
4
|
+
description: >
|
|
5
|
+
One ready-to-merge pull request whose branch adds a migration that drops
|
|
6
|
+
`users.legacy_email`, a hard trigger (schema and data). palpatine must
|
|
7
|
+
append a "Merge risk, PR #21" section naming the migration, write a
|
|
8
|
+
rollback, and sign manual. Signing auto means the trigger was missed.
|
|
9
|
+
palpatine judges; it changes no code.
|
|
10
|
+
fixture:
|
|
11
|
+
state: completed
|
|
12
|
+
slug: drop-legacy-email
|
|
13
|
+
files:
|
|
14
|
+
spec.md: |
|
|
15
|
+
# Drop Legacy Email
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
`users.legacy_email` is gone; nothing reads it any more.
|
|
20
|
+
plan.md: |
|
|
21
|
+
# Plan
|
|
22
|
+
|
|
23
|
+
1. U1 Remove the last reader of `legacy_email`, then drop the column.
|
|
24
|
+
pull-requests.md: |
|
|
25
|
+
# Pull Requests
|
|
26
|
+
|
|
27
|
+
| Pull Request Number | Pull Request Name | Status |
|
|
28
|
+
| ------------------: | :---------------- | -----: |
|
|
29
|
+
| 21 | [[001.00] Drop Legacy Email](https://github.com/example/accounts/pull/21) | Open 🟡 |
|
|
30
|
+
repo:
|
|
31
|
+
db/migrate/20261020120000_drop_legacy_email.rb: |
|
|
32
|
+
class DropLegacyEmail < ActiveRecord::Migration[8.0]
|
|
33
|
+
def change
|
|
34
|
+
remove_column :users, :legacy_email, :string
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
expect:
|
|
38
|
+
signed: manual
|
|
39
|
+
sections:
|
|
40
|
+
plan.md: ["Merge risk, PR #21"]
|
|
41
|
+
contains:
|
|
42
|
+
plan.md: ["/\\*\\*Verdict:\\*\\*\\s*manual/", "drop_legacy_email", "/\\*\\*Rollback:\\*\\*/"]
|
|
43
|
+
untouched: ["**/*"]
|
|
44
|
+
depth:
|
|
45
|
+
medium:
|
|
46
|
+
max_seconds: 600
|
|
47
|
+
max_tokens: 150000
|
|
@@ -60,4 +60,4 @@ When this ships, a cart cannot hold a quantity checkout would refuse, the limit
|
|
|
60
60
|
>
|
|
61
61
|
> [2026-09-04 12:02:11 PM PDT] [ agent: yoda-writer status: Started, round 1 ]
|
|
62
62
|
> [2026-09-04 12:09:40 PM PDT] [ agent: yoda-writer status: Completed, round 1 ]
|
|
63
|
-
> [2026-09-04 12:09:41 PM PDT] [ next:
|
|
63
|
+
> [2026-09-04 12:09:41 PM PDT] [ next: luthen-rael-planner ]
|
|
@@ -47,30 +47,44 @@ module OpenLanes
|
|
|
47
47
|
# @return [OpenLanes::Adapters::Model, nil]
|
|
48
48
|
def model_entry(name) = models.find { |m| m.id == name.to_s }
|
|
49
49
|
|
|
50
|
+
# What this CLI can do. Abstract on purpose: every adapter declares all
|
|
51
|
+
# of it, and the Executor and Dispatcher branch on these, never on
|
|
52
|
+
# {#name}.
|
|
53
|
+
#
|
|
54
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
55
|
+
def capabilities = raise NotImplementedError, "#{self.class} must declare its capabilities"
|
|
56
|
+
|
|
57
|
+
# The flags that start this CLI without the operator's personal
|
|
58
|
+
# plugins, skills, hooks and MCP servers; empty where {#argv} builds
|
|
59
|
+
# them itself.
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<String>]
|
|
62
|
+
def lean_flags = []
|
|
63
|
+
|
|
50
64
|
# What, sent into a resumed session, makes this CLI compact it (W28);
|
|
51
65
|
# nil for a CLI that cannot resume one, whose agents are therefore never
|
|
52
66
|
# compacted mid-round.
|
|
53
67
|
#
|
|
54
68
|
# @return [String, nil]
|
|
55
|
-
def compact_prompt =
|
|
69
|
+
def compact_prompt = capabilities.compact_prompt
|
|
56
70
|
|
|
57
71
|
# @return [Boolean] whether an agent on this CLI can be compacted and
|
|
58
72
|
# carried on in the same session
|
|
59
|
-
def compacts? =
|
|
73
|
+
def compacts? = capabilities.compacts
|
|
60
74
|
|
|
61
75
|
# Whether {#argv} honours {Invocation#resume}. The copilot (W17) keeps
|
|
62
76
|
# one conversation across messages only on a CLI that says yes; on any
|
|
63
77
|
# other each message starts afresh, and says so.
|
|
64
78
|
#
|
|
65
79
|
# @return [Boolean]
|
|
66
|
-
def resumes? =
|
|
80
|
+
def resumes? = capabilities.resumes
|
|
67
81
|
|
|
68
82
|
# Whether the CLI itself refuses the denied tools, rather than only being
|
|
69
83
|
# asked not to use them. The prompt says which, because telling an agent
|
|
70
84
|
# a guard exists when it does not is how it stops being careful.
|
|
71
85
|
#
|
|
72
86
|
# @return [Boolean]
|
|
73
|
-
def enforces_tool_denial? =
|
|
87
|
+
def enforces_tool_denial? = capabilities.enforces_tool_denial
|
|
74
88
|
end
|
|
75
89
|
end
|
|
76
90
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenLanes
|
|
4
|
+
module Adapters
|
|
5
|
+
# What one CLI can do, declared in one place. Every adapter states every
|
|
6
|
+
# field: a capability inherited as a silent default is how codex was once
|
|
7
|
+
# assumed to refuse denied tools it never refused.
|
|
8
|
+
#
|
|
9
|
+
# @!attribute [r] compacts
|
|
10
|
+
# @return [Boolean] an agent on this CLI can be compacted and carried on
|
|
11
|
+
# in the same session
|
|
12
|
+
# @!attribute [r] resumes
|
|
13
|
+
# @return [Boolean] {Base#argv} honours {Invocation#resume}
|
|
14
|
+
# @!attribute [r] enforces_tool_denial
|
|
15
|
+
# @return [Boolean] the CLI itself refuses the denied tools
|
|
16
|
+
# @!attribute [r] compact_prompt
|
|
17
|
+
# @return [String, nil] what, sent into a resumed session, compacts it;
|
|
18
|
+
# nil exactly when {#compacts} is false
|
|
19
|
+
Capabilities = Data.define(:compacts, :resumes, :enforces_tool_denial, :compact_prompt) do
|
|
20
|
+
def initialize(compacts:, resumes:, enforces_tool_denial:, compact_prompt:)
|
|
21
|
+
if compacts == compact_prompt.nil?
|
|
22
|
+
raise ArgumentError, "compact_prompt must be given exactly when compacts is true"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
super
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -105,18 +105,21 @@ module OpenLanes
|
|
|
105
105
|
rank && rank > LADDER.index(CEILING) ? CEILING : model.to_s
|
|
106
106
|
end
|
|
107
107
|
|
|
108
|
-
#
|
|
109
|
-
|
|
108
|
+
# `--disallowedTools` is refused by the CLI itself, and `--resume
|
|
109
|
+
# <session id>` carries a conversation on. `claude -p --resume <id>
|
|
110
|
+
# "/compact"` compacts the session and prints a `compact_boundary`
|
|
111
|
+
# event; the agent cannot run it on itself, because `/compact` is the
|
|
112
|
+
# caller's command, not a tool.
|
|
113
|
+
CAPABILITIES = Capabilities.new(compacts: true,
|
|
114
|
+
resumes: true,
|
|
115
|
+
enforces_tool_denial: true,
|
|
116
|
+
compact_prompt: "/compact")
|
|
117
|
+
|
|
118
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
119
|
+
def capabilities = CAPABILITIES
|
|
110
120
|
|
|
111
|
-
# @return [
|
|
112
|
-
def
|
|
113
|
-
|
|
114
|
-
# `claude -p --resume <id> "/compact"` compacts the session and prints a
|
|
115
|
-
# `compact_boundary` event; the agent cannot run it on itself, because
|
|
116
|
-
# `/compact` is the caller's command, not a tool.
|
|
117
|
-
#
|
|
118
|
-
# @return [String]
|
|
119
|
-
def compact_prompt = "/compact"
|
|
121
|
+
# @return [Array<String>]
|
|
122
|
+
def lean_flags = LEAN
|
|
120
123
|
|
|
121
124
|
# @return [Hash{String => String}]
|
|
122
125
|
def environment = { AUTOCOMPACT_VARIABLE => AUTOCOMPACT.to_s }
|
|
@@ -13,6 +13,15 @@ module OpenLanes
|
|
|
13
13
|
# Codex says `xhigh` for the top of its scale and has no `max`.
|
|
14
14
|
EFFORT = { "low" => "low", "medium" => "medium", "high" => "high", "xhigh" => "xhigh", "max" => "xhigh" }.freeze
|
|
15
15
|
|
|
16
|
+
# No session to resume, so nothing to compact, and no denylist of its own.
|
|
17
|
+
CAPABILITIES = Capabilities.new(compacts: false,
|
|
18
|
+
resumes: false,
|
|
19
|
+
enforces_tool_denial: false,
|
|
20
|
+
compact_prompt: nil)
|
|
21
|
+
|
|
22
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
23
|
+
def capabilities = CAPABILITIES
|
|
24
|
+
|
|
16
25
|
# @param invocation [OpenLanes::Adapters::Invocation]
|
|
17
26
|
# @return [Array<String>]
|
|
18
27
|
def argv(invocation)
|
|
@@ -8,6 +8,16 @@ module OpenLanes
|
|
|
8
8
|
# Pi's `--thinking` scale already uses these words.
|
|
9
9
|
THINKING = Adapters::EFFORTS
|
|
10
10
|
|
|
11
|
+
# `--no-session` every time: nothing resumes, nothing compacts, and
|
|
12
|
+
# nothing refuses a denied tool but the after-check.
|
|
13
|
+
CAPABILITIES = Capabilities.new(compacts: false,
|
|
14
|
+
resumes: false,
|
|
15
|
+
enforces_tool_denial: false,
|
|
16
|
+
compact_prompt: nil)
|
|
17
|
+
|
|
18
|
+
# @return [OpenLanes::Adapters::Capabilities]
|
|
19
|
+
def capabilities = CAPABILITIES
|
|
20
|
+
|
|
11
21
|
# @param invocation [OpenLanes::Adapters::Invocation]
|
|
12
22
|
# @return [Array<String>]
|
|
13
23
|
def argv(invocation)
|
|
@@ -15,6 +15,15 @@ module OpenLanes
|
|
|
15
15
|
# its events keeps working, and one that stops reporting usage shows a
|
|
16
16
|
# meter at zero rather than a crash.
|
|
17
17
|
class Stream
|
|
18
|
+
# Events that carry part of a message still being written. Only its
|
|
19
|
+
# finished lines are read out of one, and the closing message does not
|
|
20
|
+
# read them again (see {#take_partial}).
|
|
21
|
+
PARTIAL = /delta|update|partial|chunk/i
|
|
22
|
+
|
|
23
|
+
# Events that repeat messages already closed: pi's `turn_end` and
|
|
24
|
+
# `agent_end`, or anything carrying a `messages` list.
|
|
25
|
+
SUMMARY = /turn|agent|session|run/i
|
|
26
|
+
|
|
18
27
|
# @param trace [String, nil] where every line is kept verbatim
|
|
19
28
|
# @yieldparam progress [OpenLanes::Execution::Transcript::Progress]
|
|
20
29
|
def initialize(trace: nil, &on_progress)
|
|
@@ -30,6 +39,11 @@ module OpenLanes
|
|
|
30
39
|
@activity = nil
|
|
31
40
|
@result = nil
|
|
32
41
|
@error = nil
|
|
42
|
+
@journal = []
|
|
43
|
+
@heard = {}
|
|
44
|
+
@closed = Hash.new(0)
|
|
45
|
+
@open = { text: +"", taken: 0 }
|
|
46
|
+
@pending = Hash.new(0)
|
|
33
47
|
end
|
|
34
48
|
|
|
35
49
|
# @return [Integer] tokens sent, cached input included
|
|
@@ -59,6 +73,10 @@ module OpenLanes
|
|
|
59
73
|
# @return [String, nil]
|
|
60
74
|
attr_reader :trace_path
|
|
61
75
|
|
|
76
|
+
# @return [Array<OpenLanes::Plans::Journal::Entry, OpenLanes::Plans::Journal::Rejection>]
|
|
77
|
+
# the journal lines printed so far
|
|
78
|
+
def journal = @journal.dup
|
|
79
|
+
|
|
62
80
|
# @return [Integer, nil]
|
|
63
81
|
attr_accessor :pid
|
|
64
82
|
|
|
@@ -79,8 +97,19 @@ module OpenLanes
|
|
|
79
97
|
# @return [Boolean]
|
|
80
98
|
def failed? = !@error.nil?
|
|
81
99
|
|
|
100
|
+
# No sub-agents, running or spawned: codex and pi never say they delegated.
|
|
101
|
+
#
|
|
82
102
|
# @return [OpenLanes::Execution::Transcript::Progress]
|
|
83
|
-
def progress
|
|
103
|
+
def progress
|
|
104
|
+
Execution::Transcript::Progress.new(activity:,
|
|
105
|
+
up:,
|
|
106
|
+
down:,
|
|
107
|
+
subagents: 0,
|
|
108
|
+
pid:,
|
|
109
|
+
message: nil,
|
|
110
|
+
running: [],
|
|
111
|
+
journal: @journal.dup.freeze)
|
|
112
|
+
end
|
|
84
113
|
|
|
85
114
|
# @param chunk [String, nil]
|
|
86
115
|
# @return [void]
|
|
@@ -137,9 +166,124 @@ module OpenLanes
|
|
|
137
166
|
@activity = said.lines.first.to_s.strip[0, Execution::Transcript::LIMIT]
|
|
138
167
|
@result = said
|
|
139
168
|
end
|
|
169
|
+
unless type.match?(/tool|command|exec/i)
|
|
170
|
+
if type.match?(PARTIAL)
|
|
171
|
+
take_partial(event)
|
|
172
|
+
else
|
|
173
|
+
take_journal(event, summary: type.match?(SUMMARY) || event.key?("messages"))
|
|
174
|
+
end
|
|
175
|
+
end
|
|
140
176
|
@on_progress&.call(progress)
|
|
141
177
|
end
|
|
142
178
|
|
|
179
|
+
# Journal lines (M2) in what the agent itself said, in every text block
|
|
180
|
+
# of every message it finished. Not {#text_in}, which takes the first
|
|
181
|
+
# `text` anywhere in the event: that can be a tool's output, which would
|
|
182
|
+
# make a file the agent read into its memory, and it misses a line in a
|
|
183
|
+
# message's second block.
|
|
184
|
+
#
|
|
185
|
+
# A CLI read by shape delivers the same message more than once: as it
|
|
186
|
+
# closes, and again in a summary of the turn or the run. A message with
|
|
187
|
+
# an id is taken once by its id. One without is always taken as it
|
|
188
|
+
# closes, since an agent may mean `todo U1` again after it said `done
|
|
189
|
+
# U1`; a summary skips as many copies of its words as have closed, and
|
|
190
|
+
# takes the rest.
|
|
191
|
+
#
|
|
192
|
+
# @param event [Hash]
|
|
193
|
+
# @param summary [Boolean] whether the event repeats closed messages
|
|
194
|
+
# @return [void]
|
|
195
|
+
def take_journal(event, summary:)
|
|
196
|
+
seen = Hash.new(0)
|
|
197
|
+
messages_in(event).each do |message|
|
|
198
|
+
texts = texts_of(message)
|
|
199
|
+
next if texts.empty?
|
|
200
|
+
|
|
201
|
+
if (id = message["id"])
|
|
202
|
+
next if @heard.key?(id)
|
|
203
|
+
|
|
204
|
+
@heard[id] = true
|
|
205
|
+
else
|
|
206
|
+
next if summary && (seen[texts] += 1) <= @closed[texts]
|
|
207
|
+
|
|
208
|
+
@closed[texts] += 1
|
|
209
|
+
end
|
|
210
|
+
texts.flat_map { Plans::Journal.scan(it) }.each { keep(it) }
|
|
211
|
+
@open = { text: +"", taken: 0 }
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
# The finished lines of a message still being written, as they arrive,
|
|
216
|
+
# so an agent killed before the message closes keeps what it printed.
|
|
217
|
+
# An event carries either the message so far (pi's `message`, codex's
|
|
218
|
+
# `item`) or only the newest piece of it (a `delta`).
|
|
219
|
+
#
|
|
220
|
+
# @param event [Hash]
|
|
221
|
+
# @return [void]
|
|
222
|
+
def take_partial(event)
|
|
223
|
+
whole = messages_in(event).map { texts_of(it).join("\n") }.reject(&:empty?).last
|
|
224
|
+
whole ? @open[:text] = +whole : @open[:text] << delta_in(event).to_s
|
|
225
|
+
cut = @open[:text].rindex("\n") or return
|
|
226
|
+
|
|
227
|
+
entries = Plans::Journal.scan(@open[:text][0..cut])
|
|
228
|
+
entries.drop(@open[:taken]).each do |entry|
|
|
229
|
+
@journal << entry
|
|
230
|
+
@pending[entry] += 1
|
|
231
|
+
end
|
|
232
|
+
@open[:taken] = entries.size
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# An entry from a closing message, unless it was already taken from that
|
|
236
|
+
# message while it was being written.
|
|
237
|
+
#
|
|
238
|
+
# @param entry [OpenLanes::Plans::Journal::Entry, OpenLanes::Plans::Journal::Rejection]
|
|
239
|
+
# @return [void]
|
|
240
|
+
def keep(entry)
|
|
241
|
+
return @pending[entry] -= 1 if @pending[entry].positive?
|
|
242
|
+
|
|
243
|
+
@journal << entry
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
# @param event [Hash]
|
|
247
|
+
# @return [String, nil] the newest piece of text a partial event carries
|
|
248
|
+
def delta_in(event)
|
|
249
|
+
[event["delta"], event.dig("assistantMessageEvent", "delta")].each do |delta|
|
|
250
|
+
return delta if delta.is_a?(String)
|
|
251
|
+
return delta["text"] if delta.is_a?(Hash) && delta["text"].is_a?(String)
|
|
252
|
+
end
|
|
253
|
+
nil
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# The agent's own messages an event carries: one under `item` (codex)
|
|
257
|
+
# or `message` (pi), a list under `messages`, or the event itself when
|
|
258
|
+
# it is a message with its words at the top level. A message that names
|
|
259
|
+
# a role other than the assistant's is a prompt or a tool result, and
|
|
260
|
+
# an item typed as anything but a message is a command, a file change
|
|
261
|
+
# or reasoning.
|
|
262
|
+
#
|
|
263
|
+
# @param event [Hash]
|
|
264
|
+
# @return [Array<Hash>]
|
|
265
|
+
def messages_in(event)
|
|
266
|
+
itself = event.key?("content") || event["text"].is_a?(String) ? event : nil
|
|
267
|
+
found = [event["item"], event["message"], *Array(event["messages"]), itself]
|
|
268
|
+
found.select do |message|
|
|
269
|
+
message.is_a?(Hash) &&
|
|
270
|
+
[nil, "assistant"].include?(message["role"]) &&
|
|
271
|
+
(message["type"].nil? || message["type"].to_s.match?(/message|assistant/i))
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
# @param message [Hash]
|
|
276
|
+
# @return [Array<String>] its text, block by block, tool calls left out
|
|
277
|
+
def texts_of(message)
|
|
278
|
+
content = message["content"]
|
|
279
|
+
blocks = if content.is_a?(Array)
|
|
280
|
+
content.select { it.is_a?(Hash) && [nil, "text", "output_text"].include?(it["type"]) }.map { it["text"] }
|
|
281
|
+
else
|
|
282
|
+
[message["text"], content]
|
|
283
|
+
end
|
|
284
|
+
blocks.select { it.is_a?(String) && !it.strip.empty? }
|
|
285
|
+
end
|
|
286
|
+
|
|
143
287
|
# Usage counts arrive per turn, so they are added rather than replaced.
|
|
144
288
|
#
|
|
145
289
|
# @param event [Hash]
|
data/lib/openlanes/adapters.rb
CHANGED
|
@@ -98,6 +98,13 @@ module OpenLanes
|
|
|
98
98
|
klass.new
|
|
99
99
|
end
|
|
100
100
|
|
|
101
|
+
# The lean-start flags of one CLI, so a caller building its own argv
|
|
102
|
+
# never names an adapter class.
|
|
103
|
+
#
|
|
104
|
+
# @param name [String, Symbol, nil]
|
|
105
|
+
# @return [Array<String>]
|
|
106
|
+
def lean_flags(name = DEFAULT) = self.for(name).lean_flags
|
|
107
|
+
|
|
101
108
|
# @param name [String, Symbol, nil]
|
|
102
109
|
# @return [Boolean]
|
|
103
110
|
def known?(name) = registry.key?((name || DEFAULT).to_s)
|
|
@@ -89,11 +89,12 @@ module OpenLanes
|
|
|
89
89
|
# @return [Boolean]
|
|
90
90
|
def serves?(lane) = lanes.empty? || lanes.include?(lane.to_sym)
|
|
91
91
|
|
|
92
|
-
# The word after the hyphen: `researcher`, `backend
|
|
93
|
-
#
|
|
92
|
+
# The word after the last hyphen: `researcher`, `backend`, and `planner`
|
|
93
|
+
# for `luthen-rael-planner`, whose name has two words before its role.
|
|
94
|
+
# What the screen's agent column shows, the name being too long for it.
|
|
94
95
|
#
|
|
95
96
|
# @return [String]
|
|
96
|
-
def role = name.split("-"
|
|
97
|
+
def role = name.split("-").last.to_s
|
|
97
98
|
end
|
|
98
99
|
|
|
99
100
|
# More rounds than this costs tokens and buys nothing: an agent that has
|
|
@@ -27,6 +27,13 @@ module OpenLanes
|
|
|
27
27
|
# folder by what its files justify.
|
|
28
28
|
BY_CONTENTS = "by contents"
|
|
29
29
|
|
|
30
|
+
# What bb8-resolver is offered: no plan state, but the stopped rebases
|
|
31
|
+
# the harness hands it (D10). An empty cell read as "does nothing".
|
|
32
|
+
REBASES = "stopped rebases, from the harness"
|
|
33
|
+
|
|
34
|
+
# It writes, so "read-only" would be false; it moves no plan either.
|
|
35
|
+
STAGES_ONLY = "stages, never moves a plan"
|
|
36
|
+
|
|
30
37
|
# @param agents [OpenLanes::Agents::Registry]
|
|
31
38
|
def initialize(agents: Registry.new)
|
|
32
39
|
@agents = agents
|
|
@@ -50,7 +57,7 @@ module OpenLanes
|
|
|
50
57
|
# A state as its words alone, deliberately without the emoji that every
|
|
51
58
|
# other rendering of a state carries. The table widget pads a cell to a
|
|
52
59
|
# width it measures in characters, while the terminal draws a status emoji
|
|
53
|
-
# in two cells; the variation-selector ones (
|
|
60
|
+
# in two cells; the variation-selector ones (⭕ 🛑 ⚪ ⭐ ⏪) are two
|
|
54
61
|
# characters drawn in two cells, so no single count is right for all of
|
|
55
62
|
# them. A row carrying one lands short or long and the frame stops lining
|
|
56
63
|
# up. The label says the same thing unambiguously, and is worth more than
|
|
@@ -66,12 +73,17 @@ module OpenLanes
|
|
|
66
73
|
|
|
67
74
|
# @param agent [OpenLanes::Agents::Agent]
|
|
68
75
|
# @return [String]
|
|
69
|
-
def handles(agent)
|
|
76
|
+
def handles(agent)
|
|
77
|
+
return REBASES if agent.name == Engine::Rebase::RESOLVER
|
|
78
|
+
|
|
79
|
+
agent.handles.map { |key| state(key) }.join(", ")
|
|
80
|
+
end
|
|
70
81
|
|
|
71
82
|
# @param agent [OpenLanes::Agents::Agent]
|
|
72
83
|
# @return [String]
|
|
73
84
|
def advances(agent)
|
|
74
85
|
return state(agent.advances_to) if agent.advances_to
|
|
86
|
+
return STAGES_ONLY if agent.name == Engine::Rebase::RESOLVER
|
|
75
87
|
return BY_CONTENTS if agent.handles.any? && agent.handles.all? { |key| Plans::StateMachine::SETTLED.include?(key) }
|
|
76
88
|
|
|
77
89
|
READ_ONLY
|
|
@@ -16,6 +16,13 @@ module OpenLanes
|
|
|
16
16
|
# and "no front-end units" starts nobody.
|
|
17
17
|
UNIT_HEADING = /^[ \t]{0,3}\#{2,3}[ \t]+\S/
|
|
18
18
|
|
|
19
|
+
# A work unit inside a section of `plan.md`: the section is the `##`,
|
|
20
|
+
# so only a deeper heading is a unit.
|
|
21
|
+
SECTION_UNIT_HEADING = /^[ \t]{0,3}\#{3,4}[ \t]+\S/
|
|
22
|
+
|
|
23
|
+
# Any second-level heading, which ends the section before it.
|
|
24
|
+
SECTION_HEADING = /^[ \t]{0,3}\#\#[ \t]+(.+?)[ \t#]*$/
|
|
25
|
+
|
|
19
26
|
module_function
|
|
20
27
|
|
|
21
28
|
# @param agent [OpenLanes::Agents::Agent]
|
|
@@ -36,7 +43,7 @@ module OpenLanes
|
|
|
36
43
|
#
|
|
37
44
|
# {Agent#needs} only ever chooses between agents. When lanes and
|
|
38
45
|
# switches leave one candidate, that one runs: a plan parked at 🎨 is
|
|
39
|
-
# rey-frontend's to finish whatever `plan
|
|
46
|
+
# rey-frontend's to finish whatever `plan.md`'s `## Frontend` says, and an
|
|
40
47
|
# operator who typed `--agent rey-frontend` meant it.
|
|
41
48
|
#
|
|
42
49
|
# @param agents [Array<OpenLanes::Agents::Agent>]
|
|
@@ -79,10 +86,43 @@ module OpenLanes
|
|
|
79
86
|
# @return [Boolean]
|
|
80
87
|
def reviewable?(agent, subject) = agent.phase.to_s != "review" || subject.pull_requests.any?(&:open?)
|
|
81
88
|
|
|
89
|
+
# Whether +need+ holds a work unit. A need is a file (`plan-frontend.md`)
|
|
90
|
+
# or a section of one (`plan.md#frontend`: the `## Frontend` section,
|
|
91
|
+
# matched without regard to case). Units moved into `plan.md` in plan
|
|
92
|
+
# 003; a folder planned before that still holds `plan-<section>.md`, and
|
|
93
|
+
# is read from there when `plan.md` has no such section, so no folder
|
|
94
|
+
# needs migrating.
|
|
95
|
+
#
|
|
82
96
|
# @param subject [OpenLanes::Plans::Subject]
|
|
83
|
-
# @param
|
|
97
|
+
# @param need [String]
|
|
84
98
|
# @return [Boolean]
|
|
85
|
-
def units?(subject,
|
|
99
|
+
def units?(subject, need)
|
|
100
|
+
file, section = need.split("#", 2)
|
|
101
|
+
return Plans::Ledger.stripped(subject.read(file)).match?(UNIT_HEADING) unless section
|
|
102
|
+
|
|
103
|
+
body = section_of(Plans::Ledger.stripped(subject.read(file)), section)
|
|
104
|
+
return body.match?(SECTION_UNIT_HEADING) if body
|
|
105
|
+
|
|
106
|
+
units?(subject, "plan-#{section.downcase}.md")
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @param text [String]
|
|
110
|
+
# @param name [String]
|
|
111
|
+
# @return [String, nil] the lines under `## name`, up to the next `##`;
|
|
112
|
+
# nil when there is no such heading
|
|
113
|
+
def section_of(text, name)
|
|
114
|
+
body = nil
|
|
115
|
+
text.each_line do |line|
|
|
116
|
+
if (heading = SECTION_HEADING.match(line))
|
|
117
|
+
break if body
|
|
118
|
+
|
|
119
|
+
body = +"" if heading[1].strip.casecmp?(name)
|
|
120
|
+
elsif body
|
|
121
|
+
body << line
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
body
|
|
125
|
+
end
|
|
86
126
|
end
|
|
87
127
|
end
|
|
88
128
|
end
|