openlanes 3.0.0 → 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 +62 -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 +24 -3
- data/lib/openlanes/adapters/capabilities.rb +29 -0
- data/lib/openlanes/adapters/claude.rb +32 -10
- 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 +26 -2
- 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 -454
- data/lib/openlanes/cli/setup/setup.rb +51 -0
- data/lib/openlanes/cli/start/start.rb +7 -64
- 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 +94 -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 +1229 -116
- 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 +107 -0
- 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 +38 -105
- 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 +236 -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 +39 -11
- 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 +665 -0
- 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 +221 -31
- data/lib/openlanes/live/copilot/brief.rb +128 -0
- data/lib/openlanes/live/copilot/cards.rb +226 -0
- data/lib/openlanes/live/copilot/operator.rb +259 -0
- data/lib/openlanes/live/copilot/slash.rb +82 -0
- data/lib/openlanes/live/copilot.rb +290 -0
- 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 +114 -0
- data/lib/openlanes/live/mail.rb +143 -0
- data/lib/openlanes/live/memory_store.rb +36 -1
- data/lib/openlanes/live/memory_subscriber.rb +40 -0
- 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 +38 -4
- data/lib/openlanes/live/snapshot.rb +103 -42
- data/lib/openlanes/live/stations.rb +12 -8
- data/lib/openlanes/live/store.rb +36 -1
- data/lib/openlanes/live/subscriber.rb +93 -0
- data/lib/openlanes/live.rb +71 -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 +66 -7
- 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 +110 -21
- 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 +287 -7
- 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 +210 -14
- 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 +213 -15
- data/lib/openlanes/web/context.rb +33 -9
- data/lib/openlanes/web/demo.rb +39 -14
- data/lib/openlanes/web/demo_board.rb +63 -5
- data/lib/openlanes/web/events.rb +137 -9
- data/lib/openlanes/web/launcher.rb +70 -8
- 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 +31 -10
- data/lib/openlanes/web/stores.rb +11 -0
- data/lib/openlanes/web.rb +19 -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/favicon-BFJEKKAE.png +0 -0
- data/web/dist/assets/{index-Bp7W-ZTq.js → index-690ayLS4.js} +2 -2
- data/web/dist/assets/index-6ZmW4slI.js +199 -0
- data/web/dist/assets/{index-Byyxbh_z.js → index-B5KzlXp1.js} +2 -2
- data/web/dist/assets/{index-8dNhRmiO.js → index-BAo_v9bU.js} +2 -2
- data/web/dist/assets/{index-2xPC-eHt.js → index-BDm7g-xG.js} +2 -2
- data/web/dist/assets/{index-D0rmJN3h.js → index-BFH9bC7v.js} +2 -2
- data/web/dist/assets/{index-OR-odsl9.js → index-BVHPV7z4.js} +2 -2
- data/web/dist/assets/{index-C0VmkrO-.js → index-B_CPqjnA.js} +2 -2
- data/web/dist/assets/{index-DDI7zoAS.js → index-BhwBH5ct.js} +2 -2
- data/web/dist/assets/{index-BdR34cXC.js → index-Bq5AdwEt.js} +2 -2
- data/web/dist/assets/{index-uM88Njdj.js → index-CAW6ClM2.js} +2 -2
- data/web/dist/assets/index-Cp86u27u.css +1 -0
- data/web/dist/assets/{index-P3vSkiU-.js → index-Culm4pMO.js} +2 -2
- data/web/dist/assets/{index-DGLUuEXi.js → index-DElDJfp1.js} +2 -2
- data/web/dist/assets/{index-CYxcbpRM.js → index-Dja7osNW.js} +2 -2
- data/web/dist/assets/{index-popk9wbW.js → index-DjhU3FzU.js} +2 -2
- data/web/dist/assets/{index-mRV4XhJZ.js → index-Dutbxj4o.js} +2 -2
- data/web/dist/assets/{index-DB4QwuYn.js → index-RkDhPMDt.js} +2 -2
- data/web/dist/assets/{index-B7-O9ujQ.js → index-_mptZjlC.js} +2 -2
- data/web/dist/assets/logo-DXDCUiD8.png +0 -0
- data/web/dist/index.html +3 -3
- data/web/src/api/fixtures/board.json +203 -28
- metadata +1053 -881
- 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/favicon-C8pAuqqr.png +0 -0
- data/web/dist/assets/index-CI74IkWP.js +0 -198
- data/web/dist/assets/index-a4EHR-u4.css +0 -1
- data/web/dist/assets/logo-on-dark-DI9gCdN4.png +0 -0
- data/web/dist/assets/logo-on-light-BciwqxcJ.png +0 -0
- /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,108 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rack/body_proxy"
|
|
4
|
+
require "rack/utils"
|
|
5
|
+
|
|
6
|
+
module OpenLanes
|
|
7
|
+
module Web
|
|
8
|
+
# One log entry per request, through {Log}, wrapped around the whole app
|
|
9
|
+
# so a request Rack::Protection or host authorization turns away is
|
|
10
|
+
# logged too.
|
|
11
|
+
#
|
|
12
|
+
# Not Sinatra's `logging` or Rack::CommonLogger: both write Apache-style
|
|
13
|
+
# text to `rack.errors`, which is the launcher's raw output file rather
|
|
14
|
+
# than the log the engine writes, and neither knows a page from a request.
|
|
15
|
+
#
|
|
16
|
+
# The event stream is the exception to "one entry per request". It stays
|
|
17
|
+
# open as long as the page does, so it is logged twice: when it opens, and
|
|
18
|
+
# when Puma closes its body, with how long it was open. Logged once, at
|
|
19
|
+
# the end, a page left open all day would not appear until evening.
|
|
20
|
+
#
|
|
21
|
+
# Static assets and `/healthz` are logged at debug: the launcher polls
|
|
22
|
+
# `/healthz` ten times a second while it waits, and a page load fetches a
|
|
23
|
+
# dozen assets, so at info they would bury the API calls.
|
|
24
|
+
class RequestLog
|
|
25
|
+
# A response of this type is a stream that outlives its request.
|
|
26
|
+
STREAM = "text/event-stream"
|
|
27
|
+
|
|
28
|
+
# Paths logged at info. Everything else is the page's own files.
|
|
29
|
+
API = %r{\A/api(/|\z)}
|
|
30
|
+
|
|
31
|
+
# @param app [#call]
|
|
32
|
+
# @param logger [#info, #debug, #error, nil] {Log.logger} when nil, read
|
|
33
|
+
# per request so a {Log.setup} after this was built still applies
|
|
34
|
+
# @param clock [#call] monotonic seconds
|
|
35
|
+
def initialize(app, logger: nil, clock: -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) })
|
|
36
|
+
@app = app
|
|
37
|
+
@logger = logger
|
|
38
|
+
@clock = clock
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# @param env [Hash] the Rack environment
|
|
42
|
+
# @return [Array] status, headers and body, the body wrapped for a stream
|
|
43
|
+
def call(env)
|
|
44
|
+
started = @clock.call
|
|
45
|
+
status, headers, body = @app.call(env)
|
|
46
|
+
return [status, headers, opened(env, status, started, body)] if stream?(headers)
|
|
47
|
+
|
|
48
|
+
write(level(env), env, "#{line(env)} #{status}", status:, started:)
|
|
49
|
+
[status, headers, body]
|
|
50
|
+
rescue StandardError => e
|
|
51
|
+
write(:error, env, "#{line(env)} raised #{e.class}", status: 500, started:, exception: e)
|
|
52
|
+
raise
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
# Logs the stream opening, and hands back a body that logs its close.
|
|
58
|
+
#
|
|
59
|
+
# @return [Rack::BodyProxy]
|
|
60
|
+
def opened(env, status, started, body)
|
|
61
|
+
write(:info, env, "#{line(env)} stream opened", status:)
|
|
62
|
+
Rack::BodyProxy.new(body) { write(:info, env, "#{line(env)} stream closed", status:, started:) }
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @param headers [Hash]
|
|
66
|
+
# @return [Boolean]
|
|
67
|
+
def stream?(headers)
|
|
68
|
+
type = headers["content-type"] || headers["Content-Type"]
|
|
69
|
+
type.to_s.start_with?(STREAM)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# @param env [Hash]
|
|
73
|
+
# @return [Symbol]
|
|
74
|
+
def level(env) = env["PATH_INFO"].to_s.match?(API) ? :info : :debug
|
|
75
|
+
|
|
76
|
+
# @param env [Hash]
|
|
77
|
+
# @return [String] e.g. "GET /api/board"
|
|
78
|
+
def line(env) = "#{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
|
|
79
|
+
|
|
80
|
+
# The page's own id, from `?page=`, so the requests one browser tab
|
|
81
|
+
# made can be picked out of several tabs' worth.
|
|
82
|
+
#
|
|
83
|
+
# @param env [Hash]
|
|
84
|
+
# @return [String, nil]
|
|
85
|
+
def page(env)
|
|
86
|
+
id = Rack::Utils.parse_query(env["QUERY_STRING"].to_s)["page"]
|
|
87
|
+
id.is_a?(String) && !id.empty? ? id : nil
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# @param level [Symbol]
|
|
91
|
+
# @param env [Hash]
|
|
92
|
+
# @param message [String]
|
|
93
|
+
# @param status [Integer]
|
|
94
|
+
# @param started [Float, nil] when given, the entry carries the duration since
|
|
95
|
+
# @param exception [Exception, nil]
|
|
96
|
+
# @return [void]
|
|
97
|
+
def write(level, env, message, status:, started: nil, exception: nil)
|
|
98
|
+
payload = { method: env["REQUEST_METHOD"], path: env["PATH_INFO"], status: status.to_i, page: page(env) }.compact
|
|
99
|
+
entry = { message:, payload:, exception: }.compact
|
|
100
|
+
entry[:duration] = ((@clock.call - started) * 1000).round(1) if started
|
|
101
|
+
(@logger || Log.logger).public_send(level, **entry)
|
|
102
|
+
rescue StandardError
|
|
103
|
+
# A log that cannot be written is no reason to fail the request.
|
|
104
|
+
nil
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
data/lib/openlanes/web/server.rb
CHANGED
|
@@ -7,7 +7,8 @@ require "puma/server"
|
|
|
7
7
|
module OpenLanes
|
|
8
8
|
module Web
|
|
9
9
|
# The app on Puma, multi-threaded because every open page holds one
|
|
10
|
-
# thread for its event stream, and bound to {HOST}
|
|
10
|
+
# thread for its event stream, and bound to {HOST} unless a {Listen}
|
|
11
|
+
# names other addresses.
|
|
11
12
|
#
|
|
12
13
|
# server = Web.server(app:, context:, port: 11000).start
|
|
13
14
|
# server.url # => "http://127.0.0.1:11000"
|
|
@@ -18,29 +19,31 @@ module OpenLanes
|
|
|
18
19
|
MAX_THREADS = 64
|
|
19
20
|
|
|
20
21
|
# Seconds Puma waits for open streams after {#stop}. They notice the
|
|
21
|
-
# context's `stopping`
|
|
22
|
+
# context's `stopping` on the `stop` nudge {#stop} publishes; this is
|
|
23
|
+
# the backstop, for a Redis that went away and took the nudge with it.
|
|
22
24
|
SHUTDOWN_AFTER = 2
|
|
23
25
|
|
|
24
26
|
# @param app [#call]
|
|
25
27
|
# @param context [OpenLanes::Web::Context]
|
|
26
28
|
# @param port [Integer] 0 for any free port
|
|
27
|
-
# @param
|
|
29
|
+
# @param listen [OpenLanes::Web::Listen] the addresses to listen on
|
|
28
30
|
# @param log [IO] where Puma writes its errors
|
|
29
|
-
def initialize(app:, context:, port: DEFAULT_PORT,
|
|
31
|
+
def initialize(app:, context:, port: DEFAULT_PORT, listen: Listen.parse(nil), log: $stderr)
|
|
30
32
|
@app = app
|
|
31
33
|
@context = context
|
|
32
34
|
@port = Integer(port)
|
|
33
|
-
@
|
|
35
|
+
@listen = listen
|
|
34
36
|
@log = log
|
|
35
37
|
end
|
|
36
38
|
|
|
37
|
-
# @return [
|
|
38
|
-
attr_reader :
|
|
39
|
+
# @return [OpenLanes::Web::Listen]
|
|
40
|
+
attr_reader :listen
|
|
39
41
|
|
|
40
42
|
# Listen, and serve on Puma's own threads.
|
|
41
43
|
#
|
|
42
44
|
# @return [self]
|
|
43
45
|
# @raise [Errno::EADDRINUSE] when the port is taken
|
|
46
|
+
# @raise [Errno::EADDRNOTAVAIL] when an address is not this machine's
|
|
44
47
|
def start
|
|
45
48
|
@puma = Puma::Server.new(@app,
|
|
46
49
|
nil,
|
|
@@ -48,16 +51,29 @@ module OpenLanes
|
|
|
48
51
|
max_threads: MAX_THREADS,
|
|
49
52
|
force_shutdown_after: SHUTDOWN_AFTER,
|
|
50
53
|
log_writer: Puma::LogWriter.new(File.open(File::NULL, "w"), @log))
|
|
51
|
-
|
|
54
|
+
# Port 0 is resolved by the first listener, and the rest share it, so
|
|
55
|
+
# every address answers on the one port {#url} reports.
|
|
56
|
+
@listen.ips.each { @puma.add_tcp_listener(it, @puma.connected_ports.first || @port) }
|
|
52
57
|
@thread = @puma.run
|
|
58
|
+
@context.chat&.start
|
|
53
59
|
self
|
|
60
|
+
rescue SystemCallError
|
|
61
|
+
# A later address failing leaves the earlier ones bound; Puma is not
|
|
62
|
+
# running yet, so close its sockets here rather than leak them to
|
|
63
|
+
# whoever retries in this process.
|
|
64
|
+
@puma&.binder&.close
|
|
65
|
+
@puma = nil
|
|
66
|
+
raise
|
|
54
67
|
end
|
|
55
68
|
|
|
56
69
|
# @return [Integer] the port listened on, the real one when 0 was asked
|
|
57
70
|
def port = @puma&.connected_ports&.first || @port
|
|
58
71
|
|
|
59
|
-
# @return [String]
|
|
60
|
-
def url =
|
|
72
|
+
# @return [String] where this machine's browser opens the page
|
|
73
|
+
def url = @listen.browse_url(port)
|
|
74
|
+
|
|
75
|
+
# @return [Array<String>] every address the page answers on
|
|
76
|
+
def urls = @listen.urls(port)
|
|
61
77
|
|
|
62
78
|
# Block until the server stops.
|
|
63
79
|
#
|
|
@@ -69,6 +85,11 @@ module OpenLanes
|
|
|
69
85
|
# @return [void]
|
|
70
86
|
def stop
|
|
71
87
|
@context.stopping.make_true
|
|
88
|
+
# Wakes every stream blocked waiting for news, here and on any other
|
|
89
|
+
# server following this project, so ours see `stopping` now rather
|
|
90
|
+
# than at their next ping. The others look, find nothing, and sleep.
|
|
91
|
+
Live::News.publish(store: @context.store, keys: @context.keys, kind: "stop")
|
|
92
|
+
@context.chat&.stop
|
|
72
93
|
@puma&.stop(true)
|
|
73
94
|
@context.stores.close
|
|
74
95
|
end
|
data/lib/openlanes/web/stores.rb
CHANGED
|
@@ -26,6 +26,17 @@ module OpenLanes
|
|
|
26
26
|
# @return [OpenLanes::Live::Store, OpenLanes::Live::MemoryStore]
|
|
27
27
|
def current = @mutex.synchronize { @by_thread[Thread.current] ||= @factory.call }
|
|
28
28
|
|
|
29
|
+
# Drop and close this thread's store. A thread that is not one of
|
|
30
|
+
# Puma's pooled ones calls it when done, or the map would keep a
|
|
31
|
+
# connection for every such thread that ever ran. A shared store is
|
|
32
|
+
# dropped but not closed: the others still hold it.
|
|
33
|
+
#
|
|
34
|
+
# @return [void]
|
|
35
|
+
def release
|
|
36
|
+
store = @mutex.synchronize { @by_thread.delete(Thread.current) }
|
|
37
|
+
store&.close unless @mutex.synchronize { @by_thread.value?(store) }
|
|
38
|
+
end
|
|
39
|
+
|
|
29
40
|
# @return [void]
|
|
30
41
|
def close
|
|
31
42
|
stores = @mutex.synchronize { @by_thread.values.uniq.tap { @by_thread.clear } }
|
data/lib/openlanes/web.rb
CHANGED
|
@@ -25,8 +25,8 @@ module OpenLanes
|
|
|
25
25
|
# Where the server listens unless told otherwise (contract.md).
|
|
26
26
|
DEFAULT_PORT = 11_000
|
|
27
27
|
|
|
28
|
-
# Loopback
|
|
29
|
-
#
|
|
28
|
+
# Loopback by default: the page can stop agents and has no login, so
|
|
29
|
+
# nothing off this machine reaches it unless `--ips` or `-I` says so (W3).
|
|
30
30
|
HOST = "127.0.0.1"
|
|
31
31
|
|
|
32
32
|
module_function
|
|
@@ -38,12 +38,27 @@ module OpenLanes
|
|
|
38
38
|
# @param rest [Hash] {Context}'s other fields
|
|
39
39
|
# @return [OpenLanes::Web::Context]
|
|
40
40
|
def context(stores:, keys:, plans_dir:, url:, **rest)
|
|
41
|
+
rest[:chat] ||= chat(stores:, keys:, node: rest[:node])
|
|
41
42
|
Context.new(stores:, keys:, plans_dir:, url:, csrf: SecureRandom.hex(32), **rest)
|
|
42
43
|
end
|
|
43
44
|
|
|
45
|
+
# The chat between machines for one server (plan 006). Its machine is
|
|
46
|
+
# the node id in +keys+, so it answers for the same machine the board does.
|
|
47
|
+
#
|
|
48
|
+
# @param stores [OpenLanes::Web::Stores]
|
|
49
|
+
# @param keys [OpenLanes::Live::Keys]
|
|
50
|
+
# @param node [OpenLanes::Live::Node, nil]
|
|
51
|
+
# @return [OpenLanes::Live::Chat]
|
|
52
|
+
def chat(stores:, keys:, node: nil, **)
|
|
53
|
+
machine = keys.node.split(":").first
|
|
54
|
+
Live::Chat.new(stores:, keys:, machine:, label: node&.label || machine, **)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# The routes, with every request logged on the way out (see {RequestLog}).
|
|
58
|
+
#
|
|
44
59
|
# @param context [OpenLanes::Web::Context]
|
|
45
|
-
# @return [
|
|
46
|
-
def app(context) = App.serving(context)
|
|
60
|
+
# @return [#call] a Rack application
|
|
61
|
+
def app(context) = RequestLog.new(App.serving(context))
|
|
47
62
|
|
|
48
63
|
# @param app [#call]
|
|
49
64
|
# @param context [OpenLanes::Web::Context]
|
data/lib/openlanes.rb
CHANGED
|
@@ -6,7 +6,10 @@ require "dry/cli"
|
|
|
6
6
|
require "dry/cli/help"
|
|
7
7
|
|
|
8
8
|
require "dry/inflector"
|
|
9
|
+
# Every command workflow answers a Dry::Monads::Result (see OpenLanes::Refusal).
|
|
10
|
+
require "dry/monads"
|
|
9
11
|
require "fileutils"
|
|
12
|
+
require "open3"
|
|
10
13
|
require "shellwords"
|
|
11
14
|
require "zeitwerk"
|
|
12
15
|
loader = Zeitwerk::Loader.for_gem
|
|
@@ -21,13 +24,16 @@ loader.inflector.inflect(
|
|
|
21
24
|
"ui" => "UI"
|
|
22
25
|
)
|
|
23
26
|
loader.collapse("#{__dir__}/openlanes/support")
|
|
27
|
+
loader.collapse("#{__dir__}/openlanes/cli/coverage")
|
|
24
28
|
loader.collapse("#{__dir__}/openlanes/cli/create")
|
|
25
29
|
loader.collapse("#{__dir__}/openlanes/cli/docs")
|
|
26
30
|
loader.collapse("#{__dir__}/openlanes/cli/eval")
|
|
27
31
|
loader.collapse("#{__dir__}/openlanes/cli/index")
|
|
28
32
|
loader.collapse("#{__dir__}/openlanes/cli/init")
|
|
29
33
|
loader.collapse("#{__dir__}/openlanes/cli/list_plans")
|
|
34
|
+
loader.collapse("#{__dir__}/openlanes/cli/review")
|
|
30
35
|
loader.collapse("#{__dir__}/openlanes/cli/run")
|
|
36
|
+
loader.collapse("#{__dir__}/openlanes/cli/setup")
|
|
31
37
|
loader.collapse("#{__dir__}/openlanes/cli/start")
|
|
32
38
|
loader.collapse("#{__dir__}/openlanes/cli/states")
|
|
33
39
|
loader.collapse("#{__dir__}/openlanes/cli/unblock")
|
|
@@ -40,9 +46,13 @@ loader.collapse("#{__dir__}/openlanes/cli/mail/subcommands")
|
|
|
40
46
|
loader.collapse("#{__dir__}/openlanes/cli/state/subcommands")
|
|
41
47
|
loader.collapse("#{__dir__}/openlanes/cli/resync/subcommands")
|
|
42
48
|
loader.collapse("#{__dir__}/openlanes/cli/skills/subcommands")
|
|
49
|
+
loader.collapse("#{__dir__}/openlanes/cli/api/subcommands")
|
|
50
|
+
loader.ignore("#{__dir__}/openlanes/cli/api/api.rb")
|
|
51
|
+
loader.collapse("#{__dir__}/openlanes/cli/app/subcommands")
|
|
43
52
|
loader.ignore("#{__dir__}/openlanes/cli/linear/linear.rb")
|
|
44
53
|
loader.ignore("#{__dir__}/openlanes/cli/mail/mail.rb")
|
|
45
54
|
loader.ignore("#{__dir__}/openlanes/cli/state/state.rb")
|
|
55
|
+
loader.ignore("#{__dir__}/openlanes/cli/app/app.rb")
|
|
46
56
|
loader.ignore("#{__dir__}/openlanes/linear/mapping.rb")
|
|
47
57
|
loader.setup
|
|
48
58
|
|
|
@@ -51,8 +61,8 @@ loader.setup
|
|
|
51
61
|
# Three phases, each with a file that proves it happened, and a guarded state
|
|
52
62
|
# machine that refuses to let a plan folder claim a phase it has not reached.
|
|
53
63
|
#
|
|
54
|
-
# spec
|
|
55
|
-
# plan
|
|
64
|
+
# spec ⚪ New spec.md
|
|
65
|
+
# plan ⭐ Ready plan.md
|
|
56
66
|
# build 🟡 → ✅ pull-requests.md
|
|
57
67
|
#
|
|
58
68
|
# The conventions are not documented anywhere else by hand: the status
|
|
@@ -95,6 +105,8 @@ module OpenLanes
|
|
|
95
105
|
def self.move_directory(source, target)
|
|
96
106
|
return false if File.exist?(target)
|
|
97
107
|
|
|
108
|
+
# Into archive/ and friends, which the first move there creates.
|
|
109
|
+
FileUtils.mkdir_p(File.dirname(target))
|
|
98
110
|
parent = File.dirname(source)
|
|
99
111
|
tracked = system("git",
|
|
100
112
|
"-C",
|
|
@@ -116,7 +128,8 @@ module OpenLanes
|
|
|
116
128
|
true
|
|
117
129
|
end
|
|
118
130
|
|
|
119
|
-
# Plan numbers visible in `.plans` on a git ref, without checking it out
|
|
131
|
+
# Plan numbers visible in `.plans` on a git ref, without checking it out,
|
|
132
|
+
# the inactive subfolders included: an archived plan keeps its number.
|
|
120
133
|
#
|
|
121
134
|
# A pull request's branch is the only place that knows how far the sequence
|
|
122
135
|
# had got when the work started, and branches are not all rebased onto the
|
|
@@ -127,14 +140,39 @@ module OpenLanes
|
|
|
127
140
|
# @return [Array<OpenLanes::Plans::Ordinal>] possibly empty
|
|
128
141
|
def self.plans_on_ref(root, ref)
|
|
129
142
|
["origin/#{ref}", ref].each do |candidate|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
return out.lines.filter_map { |line| Plans::Ordinal.from_dirname(File.basename(line.strip)) }
|
|
143
|
+
found = plans_at(root, candidate)
|
|
144
|
+
return found unless found.empty?
|
|
134
145
|
end
|
|
135
146
|
[]
|
|
136
147
|
end
|
|
137
148
|
|
|
149
|
+
# Plan numbers in `.plans` on one tree-ish, the inactive subfolders
|
|
150
|
+
# included. {plans_on_ref} guesses at a branch name; this reads exactly
|
|
151
|
+
# what it is given, which is what allocating a number across every branch
|
|
152
|
+
# needs: a full ref name tried as `origin/refs/heads/x` would cost a
|
|
153
|
+
# second `ls-tree` per branch for nothing.
|
|
154
|
+
#
|
|
155
|
+
# @param root [String] any directory inside the repository
|
|
156
|
+
# @param treeish [String] a ref, or a commit
|
|
157
|
+
# @param plans [String] the `.plans` directory, relative to +root+
|
|
158
|
+
# @return [Array<OpenLanes::Plans::Ordinal>] empty when the ref is unknown
|
|
159
|
+
# or carries no plans
|
|
160
|
+
def self.plans_at(root, treeish, plans: PLANS_DIR)
|
|
161
|
+
dirs = [plans, *Plans::Tree::SUBFOLDERS.map { "#{plans}/#{it}" }].map { "#{it}/" }
|
|
162
|
+
out, status = Open3.capture2("git",
|
|
163
|
+
"-C",
|
|
164
|
+
root,
|
|
165
|
+
"ls-tree",
|
|
166
|
+
"-d",
|
|
167
|
+
"--name-only",
|
|
168
|
+
treeish,
|
|
169
|
+
*dirs,
|
|
170
|
+
err: File::NULL)
|
|
171
|
+
return [] unless status.success?
|
|
172
|
+
|
|
173
|
+
out.lines.filter_map { |line| Plans::Ordinal.from_dirname(File.basename(line.strip)) }
|
|
174
|
+
end
|
|
175
|
+
|
|
138
176
|
# The agent that writes a specification for work that already shipped.
|
|
139
177
|
# Named here rather than in the CLI so the definition file stays the single
|
|
140
178
|
# source of truth about who does what.
|
data/openlanes.gemspec
CHANGED
|
@@ -17,7 +17,7 @@ Gem::Specification.new do |spec|
|
|
|
17
17
|
TEXT
|
|
18
18
|
spec.homepage = "https://github.com/kigster/openlanes"
|
|
19
19
|
|
|
20
|
-
spec.required_ruby_version = ">=
|
|
20
|
+
spec.required_ruby_version = ">= 4" # rubocop:disable Gemspec/RequiredRubyVersion -- the floor users get, not the Ruby the cops lint for
|
|
21
21
|
|
|
22
22
|
spec.metadata = {
|
|
23
23
|
"homepage_uri" => spec.homepage,
|
|
@@ -33,7 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
33
33
|
# `openlanes web --demo` replays.
|
|
34
34
|
# Source maps stay out: they double the page's size for nobody who installs it.
|
|
35
35
|
globbed = Dir.glob(["*.gemspec", "lib/**/*.rb", "agents/*.md", "schemas/*.json", "evals/cases/**/*", "skills/**/*",
|
|
36
|
-
"exe/*", "
|
|
36
|
+
"exe/*", "web/dist/**/*", "web/src/api/fixtures/board.json", "Brewfile"])
|
|
37
37
|
globbed.select { |f| File.file?(f) && !f.end_with?(".map") }
|
|
38
38
|
end
|
|
39
39
|
|
|
@@ -49,10 +49,10 @@ Gem::Specification.new do |spec|
|
|
|
49
49
|
end
|
|
50
50
|
|
|
51
51
|
spec.bindir = "exe"
|
|
52
|
-
# `
|
|
52
|
+
# `ol` and `lanes` are symlinks to `openlanes` in the checkout; named here
|
|
53
53
|
# so that installing the gem writes all three, rather than only the one git
|
|
54
54
|
# recorded.
|
|
55
|
-
spec.executables = %w[openlanes
|
|
55
|
+
spec.executables = %w[openlanes ol lanes]
|
|
56
56
|
spec.require_paths = ["lib"]
|
|
57
57
|
|
|
58
58
|
spec.add_dependency "aasm"
|
|
@@ -66,6 +66,7 @@ Gem::Specification.new do |spec|
|
|
|
66
66
|
spec.add_dependency "dry-inflector"
|
|
67
67
|
spec.add_dependency "dry-monads"
|
|
68
68
|
spec.add_dependency "fuzzy-string-match"
|
|
69
|
+
spec.add_dependency "inquirex-llm", "~> 0.12"
|
|
69
70
|
# Opens the control room in the browser when `openlanes run` starts the API server.
|
|
70
71
|
spec.add_dependency "launchy", "~> 3.1"
|
|
71
72
|
spec.add_dependency "parallel"
|
|
@@ -76,7 +77,9 @@ Gem::Specification.new do |spec|
|
|
|
76
77
|
spec.add_dependency "rackup", "~> 2.1"
|
|
77
78
|
# The live run (plan 004) lives in Redis: board and command streams, plan claims.
|
|
78
79
|
spec.add_dependency "redis-client", "~> 0.31"
|
|
80
|
+
spec.add_dependency "semantic_logger", "~> 5.1"
|
|
79
81
|
spec.add_dependency "sinatra", "~> 4.2"
|
|
82
|
+
spec.add_dependency "agent-lock"
|
|
80
83
|
spec.add_dependency "tty-box"
|
|
81
84
|
spec.add_dependency "tty-command"
|
|
82
85
|
spec.add_dependency "tty-cursor"
|
|
@@ -84,4 +87,6 @@ Gem::Specification.new do |spec|
|
|
|
84
87
|
spec.add_dependency "tty-screen"
|
|
85
88
|
spec.add_dependency "unicode-display_width"
|
|
86
89
|
spec.add_dependency "zeitwerk"
|
|
90
|
+
spec.add_dependency "newrelic_rpm"
|
|
91
|
+
spec.add_dependency "newrelic-infinite_tracing"
|
|
87
92
|
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://github.com/kigster/openlanes/schemas/journal-event.schema.json",
|
|
4
|
+
"title": "openlanes engine journal event",
|
|
5
|
+
"description": "One line of a node's NDJSON progress journal, written by OpenLanes::Engine::EventJournal under Runtime.journal_dir. Append-only, fsync'd per event.",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["v", "seq", "at", "node", "run_id", "kind"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"v": { "const": 1 },
|
|
10
|
+
"seq": { "type": "integer", "minimum": 1 },
|
|
11
|
+
"at": { "type": "string", "format": "date-time" },
|
|
12
|
+
"node": { "type": "string", "minLength": 1 },
|
|
13
|
+
"run_id": { "type": "string", "minLength": 1 },
|
|
14
|
+
"kind": {
|
|
15
|
+
"enum": [
|
|
16
|
+
"run_started", "run_ended", "plan_assigned", "agent_started", "agent_session",
|
|
17
|
+
"agent_offset", "agent_ended", "transition", "extension", "question"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"plan": { "type": "string", "pattern": "^\\d{3}\\.\\d{2}$" },
|
|
21
|
+
"agent": { "type": "string", "minLength": 1 },
|
|
22
|
+
"round": { "type": "integer", "minimum": 0 },
|
|
23
|
+
"pid": { "type": "integer", "minimum": 1 },
|
|
24
|
+
"started_at": { "type": ["string", "null"], "format": "date-time" },
|
|
25
|
+
"session_id": { "type": ["string", "null"] },
|
|
26
|
+
"output_file": { "type": ["string", "null"] },
|
|
27
|
+
"offset": { "type": "integer", "minimum": 0 },
|
|
28
|
+
"exit": { "type": ["integer", "null"] },
|
|
29
|
+
"from": { "type": "string" },
|
|
30
|
+
"to": { "type": "string" },
|
|
31
|
+
"event": { "type": "string" },
|
|
32
|
+
"seconds": { "type": "number", "minimum": 0 },
|
|
33
|
+
"note": { "type": "string" },
|
|
34
|
+
"reason": { "type": "string" },
|
|
35
|
+
"jobs": { "type": "integer", "minimum": 1 },
|
|
36
|
+
"isolation": { "type": "string" }
|
|
37
|
+
},
|
|
38
|
+
"allOf": [
|
|
39
|
+
{
|
|
40
|
+
"if": { "properties": { "kind": { "const": "plan_assigned" } } },
|
|
41
|
+
"then": { "required": ["plan", "agent"] }
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"if": { "properties": { "kind": { "const": "agent_started" } } },
|
|
45
|
+
"then": { "required": ["plan", "agent", "pid", "started_at", "session_id", "output_file"] }
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"if": { "properties": { "kind": { "const": "agent_session" } } },
|
|
49
|
+
"then": { "required": ["plan", "agent", "session_id"], "properties": { "session_id": { "type": "string" } } }
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"if": { "properties": { "kind": { "const": "agent_offset" } } },
|
|
53
|
+
"then": { "required": ["plan", "agent", "offset"] }
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"if": { "properties": { "kind": { "const": "agent_ended" } } },
|
|
57
|
+
"then": { "required": ["plan", "agent", "exit"] }
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"if": { "properties": { "kind": { "const": "transition" } } },
|
|
61
|
+
"then": { "required": ["plan", "from", "to"] }
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"if": { "properties": { "kind": { "const": "extension" } } },
|
|
65
|
+
"then": { "required": ["plan", "agent", "seconds"] }
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"if": { "properties": { "kind": { "const": "question" } } },
|
|
69
|
+
"then": { "required": ["plan", "agent", "note"] }
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|