elanous 0.2.17 → 0.2.18
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.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/P6YTVgzuZz5oTZhY4Din_/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1307-aac7332576c4d6fc.js → 1307-30dff22c9e1f3073.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/195-fb1d7690bf30bab4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1986-33e13d648a9df765.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2537-6c289376caa88ca1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-325cc923ba7b8631.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3857-4f936cdb30322aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-5761612531d0146b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4565-3d7e3fdeb43686a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-a11f37254196b659.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6158.9c0449a9a8d0064b.js → 6158.af2e93b7adff709b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6877-74399f69c7da1433.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7797-df5aa0381fdc078f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8401-8615a660c84cdb03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8695-846034ce44852c70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/{page-3143b07a817d1da9.js → page-d92f7ee3994e84c7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-17ba086ef82d339b.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/{page-7eb661ff23af7da2.js → page-d22fd849cc22f104.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-bb742f995c6769e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-6e5c3a867557298b.js → page-02b073a2ac793b83.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-60c3da708f9a0ccc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8a9b3594bc23289b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/{page-5e77cd1802567c89.js → page-a0eb02638b1e6f31.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-308111abadf35670.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-6eaf7bac37830c21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-4187666132fc98c4.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-09328d499162bae4.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-29ff3a8f84f03b6a.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-80ec7401bc83f511.js → page-9b72a07d424701c6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-bd5d586429167fea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-fc7d86467291ae38.js → page-a572f58cd3ca5864.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-59b7834d497c2255.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-be5ea965a07fbc41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-0e2d6985fb42cd2f.js → page-c130b63862437814.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-13e4108999f58d13.js → page-fa244bd146a00ae8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-e8296ee284aa1293.js → page-44d56d6391965a6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-043a9111e74f6b24.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-23a4601ceb7f452e.js → not-found-7b3170f374f77b9d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-5f23d0c66a3b4793.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-10d958605a77b3bd.js → page-a3c58f5c72a5f3f9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-a2c4625596f8d69c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-0e9099531d239f11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-7a1d85e121065cc8.js → page-e8e1ecaea7cd71d2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-12916918988ac97e.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d8a3347a9d0c3f4a.js → page-2da6f9b8a38c6978.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-102310930791197a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-f331e38fefc00393.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-6ed0b73bae8351d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-1d824510df025d18.js → page-44d5f767491692bf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-43336055451f6e37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-7f49fe645d5132f7.js → page-3cc47dca0922ab9a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a77ee355a2304ff0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-27d71f131acd9320.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-61553851aa606146.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-a1f585e154e61c59.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-424659bacfb8cdd7.js → page-7bf54c9bb9274af8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-5150ef17b5383a57.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b9d8ce70792c1672.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a5a3de231f2cc7d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-5d4fef9ccd7d406b.js → page-134da468538baf48.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-31ac4afa2dcd915c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-539cde99554be20f.js → webpack-c2782b330447ffb6.js} +1 -1
- package/apps/pwa/out/_next/static/css/7fa358fec7da7c47.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/ceo/index.html +1 -1
- package/apps/pwa/out/ceo/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/discord-seat-probe.ts +3 -2
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-node.ts +99 -14
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +115 -0
- package/scripts/release-loop/unattended-release.ts +43 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +73 -1
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/tasks-cli.ts +309 -3
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/outbound-alert.ts +200 -27
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/harness/harness-cli-command.ts +10 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/helper-scan.ts +10 -3
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +133 -15
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/finish-rate.ts +73 -1
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +295 -20
- package/src/maturity/feature-maturity.ts +3 -2
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +104 -6
- package/src/release-loop/feature-store.ts +10 -9
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/placement.ts +13 -3
- package/src/release-loop/rubric.ts +80 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +59 -12
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +39 -6
- package/src/task-agent/judge.ts +31 -5
- package/src/task-agent/mission.ts +423 -0
- package/src/task-agent/shadow.ts +90 -0
- package/src/task-agent/task-hand.ts +235 -0
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +86 -18
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +63 -3
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → P6YTVgzuZz5oTZhY4Din_}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// RELEASE-BRANCH (10-06): resume a failed release run after `release cut-branch --append --pick` moved release/<v>.
|
|
2
|
+
// The saved version-release output is re-pointed at the new branch tip (fast-forward only, state backed up first), so
|
|
3
|
+
// every later node reads the repaired commit without anyone hand-editing the run state.
|
|
4
|
+
import { spawnSync } from 'node:child_process';
|
|
5
|
+
import { constants, copyFileSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { effectiveInstanceRoot } from '../../src/instance/resolve.js';
|
|
9
|
+
import { lastJsonObject, runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
10
|
+
import { isReleaseVersion } from './release-version.js';
|
|
11
|
+
|
|
12
|
+
const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
|
|
13
|
+
|
|
14
|
+
export interface ResumeReleaseDeps {
|
|
15
|
+
root?: string;
|
|
16
|
+
repo?: string;
|
|
17
|
+
/** Runs git and returns stdout (throws on failure). */
|
|
18
|
+
git?: (args: string[]) => string;
|
|
19
|
+
/** `git merge-base --is-ancestor a b`. */
|
|
20
|
+
isAncestor?: (ancestor: string, descendant: string) => boolean;
|
|
21
|
+
graph?: typeof runGraph;
|
|
22
|
+
/** The release graph (tests point it at a probe graph with the same graph id). */
|
|
23
|
+
graphPath?: string;
|
|
24
|
+
/** Node command runner for the resumed graph (tests record each node's context instead of running it). */
|
|
25
|
+
runBash?: NonNullable<NonNullable<Parameters<typeof runGraph>[1]>['deps']>['runBash'];
|
|
26
|
+
now?: () => Date;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TipRefresh { version: string; branch: string; from: string; to: string; changed: boolean; statePath: string; backup?: string }
|
|
30
|
+
|
|
31
|
+
function gitRunner(repo: string) {
|
|
32
|
+
return (args: string[]): string => {
|
|
33
|
+
// git-spawn-allow: read-only fetch / ls-remote / rev-parse against release/<v>; nothing is pushed or checked out.
|
|
34
|
+
const result = spawnSync('git', args, { cwd: repo, encoding: 'utf8' });
|
|
35
|
+
if (result.status !== 0 || result.error) throw new Error(`git ${args[0]} failed: ${(result.stderr || result.error || 'no output').toString().trim()}`);
|
|
36
|
+
return result.stdout.trim();
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ancestorRunner(repo: string) {
|
|
41
|
+
return (ancestor: string, descendant: string): boolean => {
|
|
42
|
+
const result = spawnSync('git', ['merge-base', '--is-ancestor', ancestor, descendant], { cwd: repo });
|
|
43
|
+
if (result.status !== 0 && result.status !== 1) throw new Error(`git merge-base failed: ${result.error || result.stderr || 'unknown'}`);
|
|
44
|
+
return result.status === 0;
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function safeRunId(runId: string): string {
|
|
49
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(runId) || runId === '.' || runId === '..') throw new Error(`invalid run id: ${runId}`);
|
|
50
|
+
return runId;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Point the saved version-release output at the current release/<v> tip (fast-forward of the recorded commit only). */
|
|
54
|
+
export function refreshReleaseBranchTip(runId: string, deps: ResumeReleaseDeps = {}, from?: string): TipRefresh {
|
|
55
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
56
|
+
const statePath = join(root, 'graph-runs', 'release-loop', `${safeRunId(runId)}.json`);
|
|
57
|
+
const state = JSON.parse(readFileSync(statePath, 'utf8')) as GraphRunState;
|
|
58
|
+
if (state.graphId !== 'release-loop' || state.runId !== runId) throw new Error(`run identity mismatch: ${statePath}`);
|
|
59
|
+
if (state.status !== 'failed') throw new Error(`release run ${runId} is ${state.status} — only a failed run is resumed`);
|
|
60
|
+
const restartAt = from === undefined ? -1 : state.path.indexOf(from);
|
|
61
|
+
if (from !== undefined && (restartAt < 0 || state.path.lastIndexOf(from) !== restartAt)) throw new Error(`--from ${from} is not a node on the saved path of ${runId}`);
|
|
62
|
+
const index = state.nodes.findIndex((node) => node.nodeId === 'version-release');
|
|
63
|
+
const record = index >= 0 ? state.nodes[index]! : undefined;
|
|
64
|
+
const output = record ? lastJsonObject(record.output) : undefined;
|
|
65
|
+
if (!record?.ok || !output) throw new Error(`release run ${runId} has no successful version-release record`);
|
|
66
|
+
const version = typeof output.version === 'string' ? output.version : '';
|
|
67
|
+
const recorded = typeof output.commit === 'string' ? output.commit : '';
|
|
68
|
+
if (!isReleaseVersion(version) || !/^[0-9a-f]{40}$/i.test(recorded)) throw new Error(`release run ${runId} version-release output is not a release commit`);
|
|
69
|
+
const branch = `release/${version}`;
|
|
70
|
+
// Only a run that cut release/<v> itself is re-pointed — a main-cut or --cut-commit run has no branch of its own.
|
|
71
|
+
const input = (state.input ?? {}) as { branchCut?: unknown; version?: unknown };
|
|
72
|
+
if (input.branchCut !== true || input.version !== version || output.branch !== branch) {
|
|
73
|
+
throw new Error(`release run ${runId} was not cut as ${branch} (input.branchCut/version or version-release branch differ) — refusing to resume`);
|
|
74
|
+
}
|
|
75
|
+
const git = deps.git ?? gitRunner(deps.repo ?? process.cwd());
|
|
76
|
+
const advertised = git(['ls-remote', '--heads', 'origin', `refs/heads/${branch}`]).split(/\s+/)[0] ?? '';
|
|
77
|
+
if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} is not on origin — this run was not cut as a release branch`);
|
|
78
|
+
git(['fetch', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`]);
|
|
79
|
+
const to = git(['rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`]);
|
|
80
|
+
if (to !== advertised) throw new Error(`${branch} moved while it was read (${advertised} → ${to})`);
|
|
81
|
+
if (to === recorded) {
|
|
82
|
+
debug.log('release-loop.resume', 'tip-unchanged', { runId, version, commit: to });
|
|
83
|
+
return { version, branch, from: recorded, to, changed: false, statePath };
|
|
84
|
+
}
|
|
85
|
+
// A repair only appends (`cut-branch --append`): anything but a fast-forward is a different release.
|
|
86
|
+
if (!(deps.isAncestor ?? ancestorRunner(deps.repo ?? process.cwd()))(recorded, to)) {
|
|
87
|
+
throw new Error(`${branch} tip ${to} does not descend from the recorded release commit ${recorded} — refusing to resume`);
|
|
88
|
+
}
|
|
89
|
+
// A new tip invalidates everything built from the old one (gate verdict, prepared archive, …): restart no later
|
|
90
|
+
// than the first node that consumed the commit, so each of them runs again on the new tip.
|
|
91
|
+
const firstConsumer = state.path.indexOf('gate');
|
|
92
|
+
if (restartAt >= 0 && firstConsumer >= 0 && restartAt > firstConsumer) {
|
|
93
|
+
throw new Error(`${branch} moved (${recorded.slice(0, 12)} → ${to.slice(0, 12)}): resume at gate or earlier — later nodes were built from the old tip`);
|
|
94
|
+
}
|
|
95
|
+
const stamp = (deps.now?.() ?? new Date()).toISOString().replace(/[:.]/g, '-');
|
|
96
|
+
const backup = `${statePath}.before-tip-${stamp}-${process.pid}-${to.slice(0, 12)}.bak`;
|
|
97
|
+
// Exclusive: a retry never overwrites an earlier backup.
|
|
98
|
+
copyFileSync(statePath, backup, constants.COPYFILE_EXCL);
|
|
99
|
+
const refreshed = { ...output, commit: to, refreshedFrom: recorded };
|
|
100
|
+
const text = typeof record.output === 'string' ? record.output.replace(/\s*$/, '') : '';
|
|
101
|
+
state.nodes[index] = { ...record, output: `${text}\n${JSON.stringify(refreshed)}\n` };
|
|
102
|
+
const temporary = `${statePath}.${process.pid}.tip.tmp`;
|
|
103
|
+
writeFileSync(temporary, `${JSON.stringify(state, null, 2)}\n`);
|
|
104
|
+
renameSync(temporary, statePath);
|
|
105
|
+
debug.log('release-loop.resume', 'tip-refreshed', { runId, version, branch, from: recorded, to, backup });
|
|
106
|
+
return { version, branch, from: recorded, to, changed: true, statePath, backup };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Refresh the branch tip, then restart the failed run at `from` with the saved graph snapshot. */
|
|
110
|
+
export async function resumeReleaseRun(opts: { runId: string; from: string }, deps: ResumeReleaseDeps = {}): Promise<{ tip: TipRefresh; state: GraphRunState }> {
|
|
111
|
+
if (opts.from === 'version-release') throw new Error('--from version-release would cut again — resume at a later node');
|
|
112
|
+
const tip = refreshReleaseBranchTip(opts.runId, deps, opts.from);
|
|
113
|
+
const state = await (deps.graph ?? runGraph)(deps.graphPath ?? GRAPH, { resumeRunId: opts.runId, fromNodeId: opts.from, ...(deps.root || deps.runBash ? { deps: { ...(deps.root ? { root: deps.root } : {}), ...(deps.runBash ? { runBash: deps.runBash } : {}) } } : {}) });
|
|
114
|
+
return { tip, state };
|
|
115
|
+
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
1
2
|
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
3
|
import { join } from 'node:path';
|
|
3
4
|
import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
4
5
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
5
6
|
import { userConfigPath } from '../../src/user-config.js';
|
|
6
|
-
import {
|
|
7
|
+
import { cutChecklistGate } from '../../src/release-loop/checklist.js';
|
|
8
|
+
import { getSchedule } from '../../src/release-loop/release-schedule.js';
|
|
7
9
|
import { isLandingFreezeRefusal, readLandingFreeze, LandingFrozenError } from '../../src/release-loop/landing-freeze.js';
|
|
8
10
|
import { debug } from '../../src/debug/log.js';
|
|
9
11
|
import { runGraph, type GraphRunState } from '../../src/graph-runner/runner.js';
|
|
12
|
+
import { baseVersion, isStableVersion, nextPrereleaseVersion, type PrereleaseKind } from './release-version.js';
|
|
10
13
|
|
|
11
14
|
const VERSION = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
|
12
15
|
const GRAPH = join(import.meta.dir, '../../graphs/release/release-loop.yaml');
|
|
@@ -29,6 +32,8 @@ export interface ReleaseRunInput extends ReleaseLoopConfig {
|
|
|
29
32
|
cutCommit?: string;
|
|
30
33
|
gatePodPool: string;
|
|
31
34
|
forceFreeze?: boolean;
|
|
35
|
+
/** RELEASE-BRANCH: version-release cuts release/<v> from main HEAD and bumps only the branch. */
|
|
36
|
+
branchCut?: boolean;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
export interface UnattendedReleaseDeps {
|
|
@@ -36,8 +41,25 @@ export interface UnattendedReleaseDeps {
|
|
|
36
41
|
config?: ReleaseLoopConfig;
|
|
37
42
|
configPath?: string;
|
|
38
43
|
graph?: (path: string, options: { input: ReleaseRunInput }) => Promise<GraphRunState>;
|
|
39
|
-
checklist?: typeof
|
|
44
|
+
checklist?: (version: string) => Pick<ReturnType<typeof cutChecklistGate>, 'ok' | 'red' | 'undecided' | 'blocked'>;
|
|
40
45
|
freezeRoot?: string;
|
|
46
|
+
/** Names already taken for prerelease numbering (remote release branches and tags). */
|
|
47
|
+
releaseRefs?: (base: string, kind: PrereleaseKind) => string[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Remote `release/<base>-<kind>.*` branches and `v<base>-<kind>.*` tags — read-only. */
|
|
51
|
+
export function remoteReleaseRefs(base: string, kind: PrereleaseKind, repo = process.cwd()): string[] {
|
|
52
|
+
// git-spawn-allow: read-only ls-remote to number a prerelease.
|
|
53
|
+
const result = spawnSync('git', ['ls-remote', 'origin', `refs/heads/release/${base}-${kind}.*`, `refs/tags/v${base}-${kind}.*`], { cwd: repo, encoding: 'utf8' });
|
|
54
|
+
if (result.status !== 0 || result.error) throw new Error(`git ls-remote failed: ${(result.stderr || result.error || 'no output').toString().trim()}`);
|
|
55
|
+
return result.stdout.split('\n').map((line) => line.split(/\s+/)[1] ?? '').filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** `0.2.18` + `rc` → the next free `0.2.18-rc.<n>`; a stable run keeps its version. */
|
|
59
|
+
export function resolveRunVersion(version: string, prerelease: PrereleaseKind | undefined, deps: UnattendedReleaseDeps = {}): string {
|
|
60
|
+
if (!prerelease) return version;
|
|
61
|
+
if (!isStableVersion(version)) throw new Error(`--prerelease takes the base version x.y.z: ${version}`);
|
|
62
|
+
return nextPrereleaseVersion(version, prerelease, (deps.releaseRefs ?? remoteReleaseRefs)(version, prerelease));
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
function compareVersions(a: string, b: string): number {
|
|
@@ -76,7 +98,7 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
|
|
|
76
98
|
const config = deps.config ?? releaseLoopConfig(deps.configPath);
|
|
77
99
|
const pool = config.gatePodPool;
|
|
78
100
|
if (typeof pool !== 'string' || !pool.trim()) throw new Error('release.loop.gatePodPool is required before graph execution');
|
|
79
|
-
const previousVersion = latestPublishedPreviousVersion(version, deps.ledgerRoot);
|
|
101
|
+
const previousVersion = latestPublishedPreviousVersion(baseVersion(version), deps.ledgerRoot);
|
|
80
102
|
return {
|
|
81
103
|
...config,
|
|
82
104
|
version, previousVersion,
|
|
@@ -87,9 +109,14 @@ export function buildReleaseRunInput(version: string, deps: UnattendedReleaseDep
|
|
|
87
109
|
|
|
88
110
|
/** Fail closed at the entry boundary, before any graph node can change a release. */
|
|
89
111
|
export async function runUnattendedRelease(
|
|
90
|
-
opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean },
|
|
112
|
+
opts: { version: string; dryRun?: boolean; cutCommit?: string; forceFreeze?: boolean; mainCut?: boolean; prerelease?: PrereleaseKind },
|
|
91
113
|
deps: UnattendedReleaseDeps = {},
|
|
92
114
|
): Promise<{ input: ReleaseRunInput; dryRun: boolean; state?: GraphRunState }> {
|
|
115
|
+
// RELEASE-BRANCH (10-06): the default cut is a release branch, so main keeps landing and the run no longer needs a
|
|
116
|
+
// landing freeze. A freeze stays the emergency switch: when one is on, it still stops the run (`--force-freeze` overrides).
|
|
117
|
+
// `--cut-commit` (an already-cut branch) and `--main-cut` keep the old main-bump path.
|
|
118
|
+
const branchCut = opts.cutCommit === undefined && !opts.mainCut;
|
|
119
|
+
if (opts.prerelease && !branchCut) throw new Error('--prerelease needs the release-branch cut (no --cut-commit / --main-cut): a rehearsal must never bump main');
|
|
93
120
|
if (!opts.dryRun) {
|
|
94
121
|
const frozen = readLandingFreeze(deps.freezeRoot);
|
|
95
122
|
if (frozen) {
|
|
@@ -97,11 +124,20 @@ export async function runUnattendedRelease(
|
|
|
97
124
|
if (!opts.forceFreeze) throw new LandingFrozenError(frozen);
|
|
98
125
|
}
|
|
99
126
|
}
|
|
100
|
-
const
|
|
127
|
+
const version = resolveRunVersion(opts.version, opts.prerelease, deps);
|
|
128
|
+
const input = buildReleaseRunInput(version, deps, opts.cutCommit);
|
|
101
129
|
if (opts.forceFreeze) input.forceFreeze = true;
|
|
130
|
+
if (branchCut) input.branchCut = true;
|
|
131
|
+
debug.log('release.run', 'input', { version, requested: opts.version, branchCut, prerelease: opts.prerelease ?? null, dryRun: opts.dryRun === true });
|
|
102
132
|
if (opts.dryRun) return { input, dryRun: true };
|
|
103
|
-
|
|
104
|
-
|
|
133
|
+
if (opts.prerelease) {
|
|
134
|
+
// A rehearsal neither judges nor carries the stable version's checklist cells.
|
|
135
|
+
debug.log('release.run', 'checklist-skipped', { version, reason: 'prerelease' });
|
|
136
|
+
} else {
|
|
137
|
+
// Same judgement as the checklist-gate node: past the landing deadline non-P0 yellows carry; P0 yellows block.
|
|
138
|
+
const gate = (deps.checklist ?? ((v: string) => cutChecklistGate(v, getSchedule(v)?.landBy)))(version);
|
|
139
|
+
if (!gate.ok) throw new Error(`release checklist blocked: ${[...gate.red, ...gate.undecided, ...gate.blocked].join(', ')}`);
|
|
140
|
+
}
|
|
105
141
|
const beforeGraph = readLandingFreeze(deps.freezeRoot);
|
|
106
142
|
if (beforeGraph && !opts.forceFreeze) throw new LandingFrozenError(beforeGraph);
|
|
107
143
|
const state = await (deps.graph ?? runGraph)(GRAPH, { input });
|
|
@@ -5,12 +5,17 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { join, resolve } from 'node:path';
|
|
6
6
|
import { emitNodeResult } from './node-verdict.js';
|
|
7
7
|
import { debug } from '../../src/debug/log.js';
|
|
8
|
+
import { effectiveInstanceRoot, prodInstanceRoot } from '../../src/instance/resolve.js';
|
|
9
|
+
import { landingFreezeMessage, readLandingFreeze, type LandingFreeze } from '../../src/release-loop/landing-freeze.js';
|
|
10
|
+
import { baseVersion, isReleaseVersion, isStableVersion, prereleaseKind } from './release-version.js';
|
|
8
11
|
|
|
9
12
|
type Kind = 'release' | 'dev-bump';
|
|
10
|
-
type Output = { outcome: 'ok' | 'error'; kind: Kind | null; version: string | null; commit: string | null; pr: number | null; files?: string[]; worktree?: string; error?: string
|
|
13
|
+
type Output = { outcome: 'ok' | 'error'; kind: Kind | null; version: string | null; commit: string | null; pr: number | null; files?: string[]; worktree?: string; error?: string;
|
|
14
|
+
/** RELEASE-BRANCH: the release branch the cut lives on and the main commit it was cut from. */
|
|
15
|
+
branch?: string; base?: string; existing?: boolean; skipped?: string };
|
|
11
16
|
|
|
12
17
|
export function nextDevVersion(version: string): string {
|
|
13
|
-
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(version);
|
|
18
|
+
const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.exec(isReleaseVersion(version) ? baseVersion(version) : version);
|
|
14
19
|
if (!match) throw new Error(`invalid release version: ${version}`);
|
|
15
20
|
return `${match[1]}.${match[2]}.${BigInt(match[3]!) + 1n}-dev.0`;
|
|
16
21
|
}
|
|
@@ -64,9 +69,136 @@ function shippedNextMdLines(repo: string, cut: string | undefined): { lines: Set
|
|
|
64
69
|
return { lines: new Set(shown.stdout.split('\n').map((line) => line.trim()).filter((line) => line.startsWith('- '))) };
|
|
65
70
|
}
|
|
66
71
|
|
|
67
|
-
|
|
72
|
+
/** Rewrite the version in package.json, server.json, the ACP agent card and the bun.lock root; returns the files touched. */
|
|
73
|
+
function writeVersionBump(worktree: string, current: string, target: string): string[] {
|
|
74
|
+
const path = join(worktree, 'package.json');
|
|
75
|
+
const text = readFileSync(path, 'utf8');
|
|
76
|
+
const old = `"version": "${current}"`;
|
|
77
|
+
if (!text.includes(old)) throw new Error(`package.json version field not found: ${current}`);
|
|
78
|
+
writeFileSync(path, text.replace(old, `"version": "${target}"`));
|
|
79
|
+
const files = ['package.json'];
|
|
80
|
+
const serverPath = join(worktree, 'server.json');
|
|
81
|
+
if (existsSync(serverPath)) {
|
|
82
|
+
const server = JSON.parse(readFileSync(serverPath, 'utf8')) as { version: string; packages: Array<{ version: string }> };
|
|
83
|
+
server.version = target;
|
|
84
|
+
server.packages[0]!.version = target;
|
|
85
|
+
writeFileSync(serverPath, `${JSON.stringify(server, null, 2)}\n`);
|
|
86
|
+
files.push('server.json');
|
|
87
|
+
}
|
|
88
|
+
const agentPath = join(worktree, 'integrations', 'acp-registry', 'elanous', 'agent.json');
|
|
89
|
+
if (existsSync(agentPath)) {
|
|
90
|
+
const agent = JSON.parse(readFileSync(agentPath, 'utf8')) as { version: string; distribution: { npx: { package: string } } };
|
|
91
|
+
agent.version = target;
|
|
92
|
+
agent.distribution.npx.package = `elanous@${target}`;
|
|
93
|
+
writeFileSync(agentPath, `${JSON.stringify(agent, null, 2)}\n`);
|
|
94
|
+
files.push('agent.json');
|
|
95
|
+
}
|
|
96
|
+
const lockPath = join(worktree, 'bun.lock');
|
|
97
|
+
const lock = readFileSync(lockPath, 'utf8');
|
|
98
|
+
const root = /"workspaces"\s*:\s*\{\s*""\s*:\s*\{([\s\S]*?)(?=\s*"(?:dependencies|devDependencies|optionalDependencies)"\s*:)/.exec(lock);
|
|
99
|
+
if (!root) throw new Error('bun.lock root workspace not found');
|
|
100
|
+
const lockedVersion = /"version"\s*:\s*"([^"]+)"/.exec(root[1]!);
|
|
101
|
+
if (lockedVersion) {
|
|
102
|
+
if (lockedVersion[1] !== current) throw new Error(`bun.lock root version ${lockedVersion[1]} is not ${current}`);
|
|
103
|
+
writeFileSync(lockPath, lock.replace(root[0], root[0].replace(lockedVersion[0], `"version": "${target}"`)));
|
|
104
|
+
}
|
|
105
|
+
return files;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export interface BranchCutDeps {
|
|
109
|
+
/** Runs `bun install --frozen-lockfile` in the bump worktree (the lock must accept the new version). */
|
|
110
|
+
install?: (worktree: string) => void;
|
|
111
|
+
/** The landing freeze in force (production root first, like a merge). */
|
|
112
|
+
freeze?: () => LandingFreeze | null;
|
|
113
|
+
/** `--force-freeze`: cut while a freeze is on (recorded). */
|
|
114
|
+
forceFreeze?: boolean;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function currentFreeze(): LandingFreeze | null {
|
|
118
|
+
const prod = prodInstanceRoot();
|
|
119
|
+
const root = effectiveInstanceRoot();
|
|
120
|
+
return readLandingFreeze(prod) ?? (root === prod ? null : readLandingFreeze(root));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* RELEASE-BRANCH (대표 10-06 20:0x «0.2.18부터 브랜치 방식»): the release commit lives on `release/<v>`, never on main.
|
|
125
|
+
* An existing branch is reused as is (a resumed run, or a branch a repair appended to) — its tip is the release commit.
|
|
126
|
+
* Otherwise the branch is cut from origin/main and the version bump is committed on it; main keeps its -dev.N version
|
|
127
|
+
* until the post-publish dev bump.
|
|
128
|
+
*/
|
|
129
|
+
export function cutReleaseBranchForRun(repo: string, version: string, deps: BranchCutDeps = {}): Output {
|
|
130
|
+
if (!isReleaseVersion(version)) throw new Error(`invalid release version: ${version}`);
|
|
131
|
+
// The run does not need a freeze, but one that is on is the emergency stop: neither a new nor a reused cut passes it.
|
|
132
|
+
const frozen = (deps.freeze ?? currentFreeze)();
|
|
133
|
+
if (frozen) {
|
|
134
|
+
debug.log('release.run', deps.forceFreeze ? 'freeze-forced' : 'frozen', { version, reason: frozen.reason, until: frozen.until, node: 'version-release' });
|
|
135
|
+
if (!deps.forceFreeze) throw new Error(landingFreezeMessage(frozen));
|
|
136
|
+
}
|
|
137
|
+
fetchMain(repo);
|
|
138
|
+
const branch = `release/${version}`;
|
|
139
|
+
const advertised = git(repo, 'ls-remote', '--heads', 'origin', `refs/heads/${branch}`).split(/\s+/)[0] ?? '';
|
|
140
|
+
if (advertised) {
|
|
141
|
+
// A prerelease number is used once: an existing rc branch is another run's cut, never this run's.
|
|
142
|
+
if (prereleaseKind(version) !== null) throw new Error(`${branch} already exists — ${version} was cut by another run; run again for the next free number`);
|
|
143
|
+
if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} remote tip is not a commit SHA`);
|
|
144
|
+
git(repo, 'fetch', 'origin', `+refs/heads/${branch}:refs/remotes/origin/${branch}`);
|
|
145
|
+
const tip = git(repo, 'rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`);
|
|
146
|
+
if (tip !== advertised) throw new Error(`${branch} moved while it was read (${advertised} → ${tip})`);
|
|
147
|
+
const current = versionAt(repo, tip);
|
|
148
|
+
if (current !== version) throw new Error(`${branch} tip ${tip} package.json version ${current} is not ${version}`);
|
|
149
|
+
// The branch must be a cut of this release line: its fork point on main still carries <base>-dev.N (or <v>).
|
|
150
|
+
const fork = git(repo, 'merge-base', 'origin/main', tip);
|
|
151
|
+
const forkVersion = versionAt(repo, fork);
|
|
152
|
+
const stableLine = baseVersion(version);
|
|
153
|
+
if (forkVersion !== version && !new RegExp(`^${stableLine.replaceAll('.', '\\.')}-dev\\.(0|[1-9][0-9]*)$`).test(forkVersion)) {
|
|
154
|
+
throw new Error(`${branch} forks from main at ${fork} (version ${forkVersion}) — not a cut of ${stableLine}-dev.N`);
|
|
155
|
+
}
|
|
156
|
+
debug.log('release-loop.cut', 'chosen', { version, source: 'release-branch', commit: tip, branch, existing: true });
|
|
157
|
+
return { outcome: 'ok', kind: 'release', version, commit: tip, pr: null, branch, existing: true };
|
|
158
|
+
}
|
|
159
|
+
const base = git(repo, 'rev-parse', 'origin/main');
|
|
160
|
+
const current = versionAt(repo, base);
|
|
161
|
+
const stable = baseVersion(version);
|
|
162
|
+
const devOfStable = new RegExp(`^${stable.replaceAll('.', '\\.')}-dev\\.(0|[1-9][0-9]*)$`);
|
|
163
|
+
if (current !== version && !devOfStable.test(current)) throw new Error(`origin/main version ${current} is not ${stable}-dev.N (release ${version})`);
|
|
164
|
+
const temp = mkdtempSync(join(tmpdir(), 'release-branch-cut-'));
|
|
165
|
+
const worktree = join(temp, 'tree');
|
|
166
|
+
let added = false;
|
|
167
|
+
try {
|
|
168
|
+
git(repo, 'worktree', 'add', '--detach', worktree, base);
|
|
169
|
+
added = true;
|
|
170
|
+
const files = current === version ? [] : writeVersionBump(worktree, current, version);
|
|
171
|
+
if (files.length) {
|
|
172
|
+
(deps.install ?? ((tree: string) => { run('bun', ['install', '--frozen-lockfile'], tree); }))(worktree);
|
|
173
|
+
git(worktree, 'add', '-A');
|
|
174
|
+
git(worktree, 'commit', '-m', `release: ${version}`, '-m', `Cut from main ${base}.`);
|
|
175
|
+
}
|
|
176
|
+
const commit = git(worktree, 'rev-parse', 'HEAD');
|
|
177
|
+
// Create-only: an empty lease refuses to overwrite a branch someone else made meanwhile.
|
|
178
|
+
const push = git(repo, 'push', '--porcelain', `--force-with-lease=refs/heads/${branch}:`, 'origin', `${commit}:refs/heads/${branch}`);
|
|
179
|
+
const created = push.split('\n').some((line) => {
|
|
180
|
+
const [flag, ref, summary] = line.split('\t');
|
|
181
|
+
return flag === '*' && !!ref?.endsWith(`:refs/heads/${branch}`) && summary === '[new branch]';
|
|
182
|
+
});
|
|
183
|
+
if (!created) throw new Error(`${branch} was not created (push: ${push.replace(/\s+/g, ' ').trim() || 'no output'})`);
|
|
184
|
+
debug.log('release-loop.cut', 'chosen', { version, source: 'release-branch', commit, branch, base, existing: false });
|
|
185
|
+
return { outcome: 'ok', kind: 'release', version, commit, pr: null, branch, base, files };
|
|
186
|
+
} finally {
|
|
187
|
+
try { if (added) git(repo, 'worktree', 'remove', '--force', worktree); }
|
|
188
|
+
finally { rmSync(temp, { recursive: true, force: true }); }
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string, cutCommit?: string, forceFreeze = false, branchCut = false): Output {
|
|
193
|
+
if (kind === 'release' && branchCut && cutCommit === undefined) return cutReleaseBranchForRun(repo, releaseVersion, { forceFreeze });
|
|
194
|
+
if (kind === 'dev-bump' && prereleaseKind(releaseVersion) !== null) {
|
|
195
|
+
// A prerelease (rehearsal) never moves main: the next stable release still owns this -dev line.
|
|
196
|
+
debug.log('release-loop.version-dev-bump', 'skipped', { version: releaseVersion, reason: 'prerelease' });
|
|
197
|
+
return { outcome: 'ok', kind, version: releaseVersion, commit: null, pr: null, skipped: 'prerelease' };
|
|
198
|
+
}
|
|
68
199
|
const target = kind === 'release' ? releaseVersion : nextDevVersion(releaseVersion);
|
|
69
|
-
|
|
200
|
+
// With the release on its branch, main still carries <v>-dev.N when the dev bump runs.
|
|
201
|
+
const source = kind === 'release' ? `${releaseVersion}-dev.N` : branchCut ? `${releaseVersion}-dev.N` : releaseVersion;
|
|
70
202
|
fetchMain(repo);
|
|
71
203
|
if (kind === 'release' && cutCommit !== undefined) {
|
|
72
204
|
if (!/^[0-9a-f]{7,40}$/i.test(cutCommit)) throw new Error(`invalid cut commit SHA: ${cutCommit}`);
|
|
@@ -107,7 +239,8 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
|
|
|
107
239
|
return { outcome: 'ok', kind, version: current, commit: before, pr: null };
|
|
108
240
|
}
|
|
109
241
|
}
|
|
110
|
-
|
|
242
|
+
const devOfRelease = new RegExp(`^${releaseVersion.replaceAll('.', '\\.')}\\-dev\\.(0|[1-9][0-9]*)$`);
|
|
243
|
+
if (kind === 'release' || branchCut ? !devOfRelease.test(current) && !(kind === 'dev-bump' && current === releaseVersion) : current !== source) {
|
|
111
244
|
throw new Error(`origin/main version ${current} is not ${source} (target ${target})`);
|
|
112
245
|
}
|
|
113
246
|
|
|
@@ -115,37 +248,7 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
|
|
|
115
248
|
const worktree = join(temp, 'tree');
|
|
116
249
|
try {
|
|
117
250
|
git(repo, 'worktree', 'add', '-b', `release-version-${kind}-${Date.now()}-${process.pid}`, worktree, 'origin/main');
|
|
118
|
-
const
|
|
119
|
-
const text = readFileSync(path, 'utf8');
|
|
120
|
-
const old = `"version": "${current}"`;
|
|
121
|
-
if (!text.includes(old)) throw new Error(`package.json version field not found: ${current}`);
|
|
122
|
-
writeFileSync(path, text.replace(old, `"version": "${target}"`));
|
|
123
|
-
const files = ['package.json'];
|
|
124
|
-
const serverPath = join(worktree, 'server.json');
|
|
125
|
-
if (existsSync(serverPath)) {
|
|
126
|
-
const server = JSON.parse(readFileSync(serverPath, 'utf8')) as { version: string; packages: Array<{ version: string }> };
|
|
127
|
-
server.version = target;
|
|
128
|
-
server.packages[0]!.version = target;
|
|
129
|
-
writeFileSync(serverPath, `${JSON.stringify(server, null, 2)}\n`);
|
|
130
|
-
files.push('server.json');
|
|
131
|
-
}
|
|
132
|
-
const agentPath = join(worktree, 'integrations', 'acp-registry', 'elanous', 'agent.json');
|
|
133
|
-
if (existsSync(agentPath)) {
|
|
134
|
-
const agent = JSON.parse(readFileSync(agentPath, 'utf8')) as { version: string; distribution: { npx: { package: string } } };
|
|
135
|
-
agent.version = target;
|
|
136
|
-
agent.distribution.npx.package = `elanous@${target}`;
|
|
137
|
-
writeFileSync(agentPath, `${JSON.stringify(agent, null, 2)}\n`);
|
|
138
|
-
files.push('agent.json');
|
|
139
|
-
}
|
|
140
|
-
const lockPath = join(worktree, 'bun.lock');
|
|
141
|
-
const lock = readFileSync(lockPath, 'utf8');
|
|
142
|
-
const root = /"workspaces"\s*:\s*\{\s*""\s*:\s*\{([\s\S]*?)(?=\s*"(?:dependencies|devDependencies|optionalDependencies)"\s*:)/.exec(lock);
|
|
143
|
-
if (!root) throw new Error('bun.lock root workspace not found');
|
|
144
|
-
const lockedVersion = /"version"\s*:\s*"([^"]+)"/.exec(root[1]!);
|
|
145
|
-
if (lockedVersion) {
|
|
146
|
-
if (lockedVersion[1] !== current) throw new Error(`bun.lock root version ${lockedVersion[1]} is not ${current}`);
|
|
147
|
-
writeFileSync(lockPath, lock.replace(root[0], root[0].replace(lockedVersion[0], `"version": "${target}"`)));
|
|
148
|
-
}
|
|
251
|
+
const files = writeVersionBump(worktree, current, target);
|
|
149
252
|
run('bun', ['install', '--frozen-lockfile'], worktree);
|
|
150
253
|
if (kind === 'dev-bump') {
|
|
151
254
|
const nextPath = join(worktree, 'release', 'next.md');
|
|
@@ -207,12 +310,13 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
|
|
|
207
310
|
}
|
|
208
311
|
}
|
|
209
312
|
|
|
210
|
-
function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Output {
|
|
313
|
+
export function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Output {
|
|
211
314
|
let kind: Kind | null = null;
|
|
212
315
|
let version: string | null = null;
|
|
213
316
|
let cut: string | undefined;
|
|
214
317
|
let cutCommit: string | undefined;
|
|
215
318
|
let forceFreeze = false;
|
|
319
|
+
let branchCut = false;
|
|
216
320
|
try {
|
|
217
321
|
if (args[0] !== 'release' && args[0] !== 'dev-bump') throw new Error('usage: version-node.ts <release|dev-bump> [--version <v>] [--cut <sha>] [--cut-commit <sha>] --json');
|
|
218
322
|
kind = args[0];
|
|
@@ -226,9 +330,10 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
|
|
|
226
330
|
if (process.env.ELANOUS_GRAPH_CONTEXT) {
|
|
227
331
|
const location = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
228
332
|
const context = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as {
|
|
229
|
-
input?: { version?: unknown; cutCommit?: unknown; forceFreeze?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
|
|
333
|
+
input?: { version?: unknown; cutCommit?: unknown; forceFreeze?: unknown; branchCut?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
|
|
230
334
|
if (!version && typeof context.input?.version === 'string') version = context.input.version;
|
|
231
335
|
forceFreeze = context.input?.forceFreeze === true;
|
|
336
|
+
branchCut = context.input?.branchCut === true;
|
|
232
337
|
if (kind === 'release' && cutCommit === undefined && context.input?.cutCommit !== undefined) {
|
|
233
338
|
if (typeof context.input.cutCommit !== 'string') throw new Error('cutCommit must be a commit SHA');
|
|
234
339
|
cutCommit = context.input.cutCommit;
|
|
@@ -240,11 +345,11 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
|
|
|
240
345
|
if (!version) throw new Error('version required (--version or ELANOUS_GRAPH_CONTEXT.input.version)');
|
|
241
346
|
nextDevVersion(version);
|
|
242
347
|
if (kind === 'dev-bump' && cutCommit !== undefined) throw new Error('--cut-commit is only valid for release');
|
|
243
|
-
return landing(resolve(repo), kind, version, cut, cutCommit, forceFreeze);
|
|
348
|
+
return landing(resolve(repo), kind, version, cut, cutCommit, forceFreeze, branchCut);
|
|
244
349
|
} catch (error) {
|
|
245
350
|
const message = error instanceof Error ? error.message : String(error);
|
|
246
351
|
const worktree = /\[worktree: ([^\]]+)\]$/.exec(message)?.[1];
|
|
247
|
-
const outputVersion = kind === 'dev-bump' && version &&
|
|
352
|
+
const outputVersion = kind === 'dev-bump' && version && isStableVersion(version)
|
|
248
353
|
? nextDevVersion(version) : version;
|
|
249
354
|
return { outcome: 'error', kind, version: outputVersion,
|
|
250
355
|
commit: null, pr: null, ...(worktree ? { worktree } : {}), error: message };
|
package/scripts/rhythm/daily.ts
CHANGED
|
@@ -301,8 +301,9 @@ function recipient(deps: DailyDeps): { chatId: number; botToken: string } | null
|
|
|
301
301
|
function sendReview(file: string, text: string, target: { chatId: number; botToken: string },
|
|
302
302
|
send: (request: DailySendRequest) => DailySendConfirmation | null,
|
|
303
303
|
receipt: (key: string) => DeliveryReceipt,
|
|
304
|
-
recordDelivery?: (text: string, kind: 'ops-report') => void
|
|
305
|
-
|
|
304
|
+
recordDelivery?: (text: string, kind: 'ops-report') => void,
|
|
305
|
+
keySuffix = ''): DeliveryResult {
|
|
306
|
+
const key = file.slice(file.lastIndexOf('/') + 1).replace(/\.md$/, '') + keySuffix;
|
|
306
307
|
const label = `telegram:${target.chatId}`;
|
|
307
308
|
const db = new Database(join(dirname(file), 'delivery.sqlite'));
|
|
308
309
|
try {
|
|
@@ -346,6 +347,21 @@ function sendReview(file: string, text: string, target: { chatId: number; botTok
|
|
|
346
347
|
finally { db.close(); }
|
|
347
348
|
}
|
|
348
349
|
|
|
350
|
+
function newsMessage(markdown: string): string | null {
|
|
351
|
+
const section = markdown.split('## ⑦ 외부 동향\n')[1]?.split('\n\n## ')[0];
|
|
352
|
+
if (!section || section.startsWith('못 읽음 ·')) return null;
|
|
353
|
+
const articles = [...section.matchAll(/^- (.+) — (https?:\/\/\S+)\n 시사점: (.*)$/gm)].slice(0, 3);
|
|
354
|
+
return articles.length ? `## 외부 동향\n${articles.map(article => article[0]).join('\n')}` : null;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function sendNewsAfterReview(delivery: DeliveryResult, file: string, markdown: string,
|
|
358
|
+
target: { chatId: number; botToken: string }, deps: DailyDeps): string | null {
|
|
359
|
+
if (!['sent', 'already-sent', 'receipt-sent'].includes(delivery.deliveryState)) return null;
|
|
360
|
+
const text = newsMessage(markdown);
|
|
361
|
+
if (!text) return null;
|
|
362
|
+
return sendReview(file, text, target, deps.send ?? sendTelegram, deps.receipt ?? (() => 'unknown'), deps.recordDelivery, '-news').deliveryState;
|
|
363
|
+
}
|
|
364
|
+
|
|
349
365
|
export type DailyDeps = { now?: () => Date; root?: string; vaultRoot?: string | null; sendEnabled?: boolean; repoRoot?: string; repoName?: string;
|
|
350
366
|
landings?: (now: Date) => Promise<Landing[]>; release?: () => Promise<Release>; loops?: () => Promise<Loop[]>;
|
|
351
367
|
grid?: () => Promise<Seat[]>; decisions?: () => Promise<Pending[]>; news?: () => Promise<News[]>;
|
|
@@ -402,6 +418,7 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
|
|
|
402
418
|
let target = 'unknown';
|
|
403
419
|
let sendError: string | null = null;
|
|
404
420
|
let recordError: string | null = null;
|
|
421
|
+
let newsState: string | null = null;
|
|
405
422
|
let enabled = deps.sendEnabled === true;
|
|
406
423
|
if (deps.sendEnabled === undefined && options.stage === 'deliver' && !options.dryRun) {
|
|
407
424
|
try { enabled = record(record(record(getUserConfig().raw.loops).rhythm).daily).send === true; }
|
|
@@ -420,12 +437,13 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
|
|
|
420
437
|
sendError = sendError ?? delivery.sendError;
|
|
421
438
|
recordError = delivery.recordError ?? null;
|
|
422
439
|
deliveryState = delivery.deliveryState;
|
|
440
|
+
if (resolved) newsState = sendNewsAfterReview(delivery, file, markdown, resolved, deps);
|
|
423
441
|
}
|
|
424
442
|
tick.sent = sent;
|
|
425
443
|
const status = vault.vaultFatal || Object.values(sections).includes('unreadable') ? 'degraded'
|
|
426
444
|
: deliveryState === 'pending' ? 'pending'
|
|
427
445
|
: ['sent', 'already-sent', 'receipt-sent'].includes(deliveryState) ? 'ok' : 'degraded';
|
|
428
|
-
const deliveryLine = `${options.stage} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}`;
|
|
446
|
+
const deliveryLine = `${options.stage} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}${newsState ? ` · news=${newsState}` : ''}`;
|
|
429
447
|
if (options.stage === 'deliver') {
|
|
430
448
|
Object.assign(tick, { status, deliveryState, target, chars, receiptKey, deliveryLine, recordError });
|
|
431
449
|
if (!options.dryRun) {
|
|
@@ -526,6 +544,7 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
|
|
|
526
544
|
}
|
|
527
545
|
let sent = false;
|
|
528
546
|
let recordError: string | null = null;
|
|
547
|
+
let newsState: string | null = null;
|
|
529
548
|
let chars = 0;
|
|
530
549
|
let target = 'unknown';
|
|
531
550
|
if (!options.dryRun && options.stage !== 'collect' && enabled) {
|
|
@@ -541,11 +560,13 @@ export async function runDaily(options: { dryRun?: boolean; noNews?: boolean; st
|
|
|
541
560
|
sendError = sendError ?? delivery.sendError;
|
|
542
561
|
recordError = delivery.recordError ?? null;
|
|
543
562
|
deliveryState = delivery.deliveryState;
|
|
563
|
+
if (resolved && parts.news.status === 'ok' && parts.news.value.length > 0)
|
|
564
|
+
newsState = sendNewsAfterReview(delivery, file, review.markdown, resolved, deps);
|
|
544
565
|
}
|
|
545
566
|
const status = vaultFatal || Object.values(review.sections).includes('unreadable') ? 'degraded'
|
|
546
567
|
: deliveryState === 'pending' ? 'pending'
|
|
547
568
|
: ['sent', 'already-sent', 'receipt-sent'].includes(deliveryState) ? 'ok' : 'degraded';
|
|
548
|
-
const deliveryLine = `${options.stage === 'collect' ? 'collect' : 'deliver'} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}`;
|
|
569
|
+
const deliveryLine = `${options.stage === 'collect' ? 'collect' : 'deliver'} ${status} · target=${target} · ${deliveryState} · sent=${sent} · chars=${chars} · receipt=${receiptKey}${recordError ? ` · ${recordError}` : ''}${newsState ? ` · news=${newsState}` : ''}`;
|
|
549
570
|
if (options.stage !== 'collect') {
|
|
550
571
|
const tick = { sections: review.sections, risks: review.risks.length, sent, status, deliveryState, target, chars, receiptKey, deliveryLine, recordError };
|
|
551
572
|
if (!options.dryRun) {
|