elanous 0.2.16 → 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-b46d45b705e8c9ae.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/9247-269e4464cc4b491c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d92f7ee3994e84c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-3cb52e5ea8fc2fa5.js → page-4733c967b56a4277.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d22fd849cc22f104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-c1e20d7d338713f1.js → page-bb742f995c6769e7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-482c5dbde699947c.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-a0eb02638b1e6f31.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-a0e2a6d1bfb52fc5.js → page-308111abadf35670.js} +1 -1
- 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-c53598cfce6156f2.js → page-5d97b2901e0d1ea6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-13a63953f53c6a23.js → page-c39554c321576982.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-5d3c8429d04f3831.js → page-8c3d8e63ae2e1c98.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-3193bdc0bbab047a.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-6107ca8ddd5e3bdd.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-c130b63862437814.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-fa244bd146a00ae8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-44d56d6391965a6a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-4167348049166852.js → page-83a700d4c754d802.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{not-found-de52e8fdf9095f7a.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-ca3952cd78a83ca2.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-e8e1ecaea7cd71d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a1b7ffed710a93e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-1ebfe049c640e171.js → page-1467047104141297.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-2da6f9b8a38c6978.js +1 -0
- 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-abe8e3b74bea1ffc.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-d28573d6792b4275.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-d751de5f8062ff5e.js → page-b17cd46cf0e9b8cf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-fe1759f94003b32b.js → page-61553851aa606146.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-d64c984e855c8c68.js → page-a1f585e154e61c59.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-7bf54c9bb9274af8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-9afdee66b820c76e.js → page-5150ef17b5383a57.js} +1 -1
- 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-3544f154c87b43ee.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/graphs/intake/intake-daily.yaml +9 -2
- package/graphs/intake/news-sources.yaml +4 -0
- package/graphs/intake/recipes.yaml +6 -1
- 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/__fixtures__/intake-news.xml +7 -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/intake-news.ts +169 -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 +77 -11
- 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 +10 -3
- package/scripts/release-loop/release-docs.ts +134 -0
- 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 +149 -41
- package/scripts/release-story/draft.ts +42 -14
- 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/driver.ts +53 -10
- 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/autopilot/track-agent.ts +5 -3
- package/src/briefing/brief-items.ts +86 -17
- package/src/cli/autopilot-cli.ts +86 -1
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/decisions-cli.ts +4 -1
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/intake-cli.ts +74 -2
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pr-cli.ts +12 -1
- package/src/cli/provider-cli.ts +24 -0
- package/src/cli/release-cli.ts +101 -17
- package/src/cli/self-update-canary.ts +46 -0
- package/src/cli/self-update.ts +200 -1
- 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/context-card/index.ts +45 -0
- package/src/context-card/task.ts +76 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +2 -0
- package/src/decisions/decision-ledger.ts +10 -1
- 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 +151 -11
- package/src/harness/harness-pod-dispatch.ts +11 -0
- package/src/harness/harness-queue.ts +466 -32
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/nested-elanous-policy.ts +23 -4
- package/src/hq/fence-wrapper.ts +123 -0
- package/src/hq/hq.ts +8 -0
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +190 -23
- 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/digest.ts +39 -4
- 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/grid.ts +61 -0
- package/src/nexus/api/http-server.ts +19 -1
- package/src/nexus/api/loop-edges.ts +69 -13
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/oauth/codex.ts +120 -2
- 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 +24 -14
- 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 +107 -37
- package/src/self-dev/ask-launch-flow.ts +50 -10
- 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-store.ts +5 -1
- package/src/self-dev/run-supervisor.ts +21 -1
- package/src/self-dev/supervisor-verdict-edges.ts +46 -0
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/frozen-merges.ts +25 -20
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +81 -0
- package/src/self-implement/goal-author-cli.ts +33 -5
- package/src/self-implement/goal-author.ts +93 -12
- package/src/self-implement/goal-clarification-escalation.ts +37 -1
- package/src/self-implement/goal-document-write.ts +28 -0
- package/src/self-implement/heal-intake.ts +4 -0
- package/src/self-implement/host-regate.ts +3 -1
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +78 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +151 -0
- package/src/task-agent/judge.ts +122 -0
- 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-cards/card-store.ts +4 -1
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +43 -4
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-pool.ts +81 -13
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +225 -35
- package/src/telegram-commands.ts +1 -1
- package/src/telegram-loops-command.ts +22 -15
- package/src/telegram.ts +5 -20
- package/src/user-config.ts +86 -4
- 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-7c6208705c284124.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/8827-e2f5b09a8d37e83b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9247-29d889b809288e65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-8c2d9526164f5d09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-dce686e582c23937.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-1a61c7ca1ceb3ae7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-51f811fa119308b6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-5b042736aa113756.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-57924393e3e4e539.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-0f5cd57ca928f42b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-59f03fda76c03d22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-1ea64acadd704ace.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{rv9cierV0jSOvDBm1g0XB → 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');
|
|
@@ -183,7 +286,8 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
|
|
|
183
286
|
}
|
|
184
287
|
}
|
|
185
288
|
const title = kind === 'release' ? `release: ${target}` : `version: ${target}`;
|
|
186
|
-
const land = spawnSync('bun', ['bin/elanous.mjs', 'pr', 'land', '--commit-message', title, '--title', title, '--body', `Set package.json version to ${target}
|
|
289
|
+
const land = spawnSync('bun', ['bin/elanous.mjs', 'pr', 'land', '--commit-message', title, '--title', title, '--body', `Set package.json version to ${target}.`,
|
|
290
|
+
...(forceFreeze ? ['--force-freeze', `release run version bump ${target}`] : [])], {
|
|
187
291
|
cwd: worktree, encoding: 'utf8', maxBuffer: 16 * 1024 * 1024,
|
|
188
292
|
});
|
|
189
293
|
const transcript = `${land.stdout ?? ''}\n${land.stderr ?? ''}`;
|
|
@@ -206,11 +310,13 @@ function landing(repo: string, kind: Kind, releaseVersion: string, cut?: string,
|
|
|
206
310
|
}
|
|
207
311
|
}
|
|
208
312
|
|
|
209
|
-
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 {
|
|
210
314
|
let kind: Kind | null = null;
|
|
211
315
|
let version: string | null = null;
|
|
212
316
|
let cut: string | undefined;
|
|
213
317
|
let cutCommit: string | undefined;
|
|
318
|
+
let forceFreeze = false;
|
|
319
|
+
let branchCut = false;
|
|
214
320
|
try {
|
|
215
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');
|
|
216
322
|
kind = args[0];
|
|
@@ -224,8 +330,10 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
|
|
|
224
330
|
if (process.env.ELANOUS_GRAPH_CONTEXT) {
|
|
225
331
|
const location = process.env.ELANOUS_GRAPH_CONTEXT;
|
|
226
332
|
const context = JSON.parse(location.trimStart().startsWith('{') ? location : readFileSync(location, 'utf8')) as {
|
|
227
|
-
input?: { version?: unknown; cutCommit?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
|
|
333
|
+
input?: { version?: unknown; cutCommit?: unknown; forceFreeze?: unknown; branchCut?: unknown }; outputs?: Record<string, { commit?: unknown } | undefined> };
|
|
228
334
|
if (!version && typeof context.input?.version === 'string') version = context.input.version;
|
|
335
|
+
forceFreeze = context.input?.forceFreeze === true;
|
|
336
|
+
branchCut = context.input?.branchCut === true;
|
|
229
337
|
if (kind === 'release' && cutCommit === undefined && context.input?.cutCommit !== undefined) {
|
|
230
338
|
if (typeof context.input.cutCommit !== 'string') throw new Error('cutCommit must be a commit SHA');
|
|
231
339
|
cutCommit = context.input.cutCommit;
|
|
@@ -237,11 +345,11 @@ function main(args: string[] = process.argv.slice(2), repo = process.cwd()): Out
|
|
|
237
345
|
if (!version) throw new Error('version required (--version or ELANOUS_GRAPH_CONTEXT.input.version)');
|
|
238
346
|
nextDevVersion(version);
|
|
239
347
|
if (kind === 'dev-bump' && cutCommit !== undefined) throw new Error('--cut-commit is only valid for release');
|
|
240
|
-
return landing(resolve(repo), kind, version, cut, cutCommit);
|
|
348
|
+
return landing(resolve(repo), kind, version, cut, cutCommit, forceFreeze, branchCut);
|
|
241
349
|
} catch (error) {
|
|
242
350
|
const message = error instanceof Error ? error.message : String(error);
|
|
243
351
|
const worktree = /\[worktree: ([^\]]+)\]$/.exec(message)?.[1];
|
|
244
|
-
const outputVersion = kind === 'dev-bump' && version &&
|
|
352
|
+
const outputVersion = kind === 'dev-bump' && version && isStableVersion(version)
|
|
245
353
|
? nextDevVersion(version) : version;
|
|
246
354
|
return { outcome: 'error', kind, version: outputVersion,
|
|
247
355
|
commit: null, pr: null, ...(worktree ? { worktree } : {}), error: message };
|
|
@@ -86,18 +86,25 @@ function manualTexts(dir: string): string[] {
|
|
|
86
86
|
return texts;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
|
|
89
|
+
type StoryClaim = { id: string; sentence: string; command: string | null; firstPublicCandidate: boolean };
|
|
90
|
+
|
|
91
|
+
function linkedClaims(stateDir: string, version: string, ids: Set<string>): { claims: StoryClaim[]; error?: string } {
|
|
90
92
|
const ledger = new ClaimsLedger({ stateDir });
|
|
91
93
|
try {
|
|
92
|
-
if (!existsSync(stateDir)) return {
|
|
93
|
-
if (!statSync(stateDir).isDirectory()) return {
|
|
94
|
-
if (!existsSync(ledger.path)) return {
|
|
95
|
-
const
|
|
96
|
-
.
|
|
97
|
-
|
|
98
|
-
|
|
94
|
+
if (!existsSync(stateDir)) return { claims: [], error: '상태 경로 없음' };
|
|
95
|
+
if (!statSync(stateDir).isDirectory()) return { claims: [], error: '상태 경로가 폴더가 아님' };
|
|
96
|
+
if (!existsSync(ledger.path)) return { claims: [], error: '원장 없음' };
|
|
97
|
+
const claims = [...ledger.list({ status: 'verified' }), ...ledger.list({ status: 'public' })]
|
|
98
|
+
.map((row): StoryClaim | null => {
|
|
99
|
+
const detail = ledger.get(row.id);
|
|
100
|
+
if (!detail.links.some((link) => link.version === version && ids.has(link.cell))) return null;
|
|
101
|
+
return { id: row.id, sentence: row.claim, command: detail.evidence.at(-1)?.command ?? null,
|
|
102
|
+
firstPublicCandidate: !detail.history.some((entry) => entry.event === 'publish')
|
|
103
|
+
&& !detail.links.some((link) => link.version !== version && link.version !== null) };
|
|
104
|
+
}).filter((claim): claim is StoryClaim => claim !== null);
|
|
105
|
+
return { claims };
|
|
99
106
|
} catch (error) {
|
|
100
|
-
return {
|
|
107
|
+
return { claims: [], error: error instanceof Error ? error.message : String(error) };
|
|
101
108
|
}
|
|
102
109
|
}
|
|
103
110
|
|
|
@@ -117,15 +124,34 @@ export function draftReleaseStory(options: DraftOptions): DraftResult {
|
|
|
117
124
|
const candidates = green.filter((item) => !mentions.some((text) => text.includes(item.id)));
|
|
118
125
|
const evidence = linkedClaims(options.stateDir ?? effectiveInstanceRoot(), version, new Set(publicCells.map((item) => item.id)));
|
|
119
126
|
const changes = [...userLines, ...publicCells.map((item) => firstPhrase(item.title))];
|
|
127
|
+
const numeric = evidence.claims.find((claim) => claim.firstPublicCandidate && /\d/.test(claim.sentence) && claim.command);
|
|
128
|
+
const kindPattern = /\bnew\b|새로운|신규|새 종류/i;
|
|
129
|
+
const kindFromNext = userLines.find((line) => kindPattern.test(line));
|
|
130
|
+
const kindFromCell = publicCells.map((item) => firstPhrase(item.title)).find((line) => kindPattern.test(line));
|
|
131
|
+
const kindFromClaim = evidence.claims.find((claim) => kindPattern.test(claim.sentence));
|
|
132
|
+
const newKind = kindFromNext ? { text: kindFromNext, source: 'release/next.md', command: null }
|
|
133
|
+
: kindFromCell ? { text: kindFromCell, source: 'green 칸', command: null }
|
|
134
|
+
: kindFromClaim ? { text: kindFromClaim.sentence, source: `소구점 ${kindFromClaim.id}`, command: kindFromClaim.command } : null;
|
|
135
|
+
const card = numeric ? { kind: '첫 공개 숫자 후보', text: numeric.sentence, source: `소구점 ${numeric.id}`, command: numeric.command }
|
|
136
|
+
: newKind ? { kind: '새 종류 후보', ...newKind } : null;
|
|
120
137
|
const announcement = [
|
|
121
|
-
`# ${version} 판 공지 초안`, '', '##
|
|
138
|
+
`# ${version} 판 공지 초안`, '', '## 대표 카드 · CMO 확인 전',
|
|
139
|
+
...(card ? [`- ${card.kind}: ${card.text}`, `- 출처: ${card.source}`, `- 재측정: ${card.command ?? '확인 명령 없음 — 게시 전 확인'}`]
|
|
140
|
+
: ['- 후보 없음 — CMO가 이번 판의 대표 문장을 고른다']),
|
|
141
|
+
'- 첫 공개 여부·새 종류 여부는 앞 판의 공개 문장과 대조한 뒤 CMO가 확정한다.',
|
|
142
|
+
'', '## 무엇이 달라졌나',
|
|
122
143
|
...(changes.length ? changes.map((line) => `- ${line}`) : ['- 사용자 쪽 변경 없음']),
|
|
123
144
|
'', '## 근거',
|
|
124
|
-
...(evidence.error ? [`근거: 소구점 원장 못 읽음(${evidence.error})`] : evidence.
|
|
145
|
+
...(evidence.error ? [`근거: 소구점 원장 못 읽음(${evidence.error})`] : evidence.claims.length ? evidence.claims.map((claim) => `- ${claim.sentence}`) : ['근거: 연결된 소구점 없음']),
|
|
146
|
+
'', '## CMO 게시 판단 · 초안은 게시가 아니다',
|
|
147
|
+
'- 착지·출처: release/next.md와 green 칸의 공개 상태, 연결된 검증 소구점을 대조한다. 근거가 없거나 원장을 못 읽었으면 보류한다.',
|
|
148
|
+
'- 사실·톤: 숫자는 근거 명령으로 재측정하고 첫 공개·새 종류는 앞 판 공개 문장과 대조한다. 브랜드 규칙과 금지 표현을 확인한다.',
|
|
149
|
+
'- 채널·결정: 공지·사이트·매뉴얼 문장의 대상과 영어 원문을 대조하고 CMO가 게시/수정/보류와 이유를 기록한다. 실제 바깥 게시는 역할 파일의 사람 결정 관문을 따른다.',
|
|
125
150
|
'',
|
|
126
151
|
].join('\n');
|
|
127
152
|
const siteChanges = [...userLines, ...publicCells.map((item) => firstPhrase(item.title)).filter((title) => !/[가-힣]/.test(title))];
|
|
128
|
-
const
|
|
153
|
+
const siteCard = card && !/[가-힣]/.test(card.text) ? [`## Lead card candidate (CMO review required)`, card.text, ''] : [];
|
|
154
|
+
const siteNews = [`# What's new in ${version}`, '', ...siteCard, ...siteChanges.slice(0, 3).map((line) => `- ${line}`), '', `Release: ${version}`, ''].join('\n');
|
|
129
155
|
const manual = [`# ${version} 매뉴얼 갱신 후보`, '', ...candidates.map((item: ChecklistItem) => `- ${item.id} · ${firstPhrase(item.title)} · 매뉴얼 언급 없음`), ''].join('\n');
|
|
130
156
|
const outDir = options.outDir ?? join(effectiveInstanceRoot(), 'release', version, 'story');
|
|
131
157
|
mkdirSync(outDir, { recursive: true });
|
|
@@ -150,7 +176,7 @@ export function draftReleaseStory(options: DraftOptions): DraftResult {
|
|
|
150
176
|
files.push(file);
|
|
151
177
|
}
|
|
152
178
|
const result: DraftResult = { version, status: 'drafted', userLines: userLines.length, internalDropped, greenCells: green.length,
|
|
153
|
-
claims: evidence.
|
|
179
|
+
claims: evidence.claims.length, manualCandidates: candidates.length, brandFindings, ...(brandUnmeasured ? { brandUnmeasured } : {}), files };
|
|
154
180
|
debug.log('release.story', 'drafted', { version, userLines: result.userLines, internalDropped, greenCells: result.greenCells,
|
|
155
181
|
claims: result.claims, manualCandidates: result.manualCandidates, brandFindings, ...(brandUnmeasured ? { brandUnmeasured } : {}) });
|
|
156
182
|
return result;
|
|
@@ -161,7 +187,9 @@ export function runReleaseStory(context: GraphContext = readGraphContext(), draf
|
|
|
161
187
|
try {
|
|
162
188
|
if (context.outputs.publish?.outcome !== 'ok' || context.outputs.verify?.outcome !== 'ok')
|
|
163
189
|
throw new Error('published release and verification required');
|
|
164
|
-
|
|
190
|
+
// 판 노트 원천은 기본이 저장소의 release/next.md — 시험·재현은 env 로 다른 파일을 준다(실제 next.md 는 판마다 비워진다).
|
|
191
|
+
const nextPath = process.env.ELANOUS_RELEASE_NEXT_PATH?.trim();
|
|
192
|
+
const result = draft({ version, ...(nextPath ? { nextPath } : {}) });
|
|
165
193
|
if (result.status === 'drafted') {
|
|
166
194
|
const root = effectiveInstanceRoot();
|
|
167
195
|
const requestsPath = join(root, 'seat-requests', 'requests.jsonl');
|