elanous 0.2.13 → 0.2.14
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/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +9 -0
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-mission/driver.ts +3 -3
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +35 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +144 -21
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +152 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +216 -28
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hq/hq.ts +1 -1
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +83 -21
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +32 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +18 -3
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +7 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +18 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +406 -31
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +294 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/user-config.ts +120 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -1,11 +1,32 @@
|
|
|
1
1
|
import { existsSync } from 'node:fs';
|
|
2
2
|
import { resolve } from 'node:path';
|
|
3
3
|
import { Command } from 'commander';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
|
|
7
|
+
import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
|
|
4
8
|
|
|
5
|
-
// Private local ledgers (decisions · directives) are absent from the public export;
|
|
9
|
+
// Private local ledgers (decisions · directives · claims · lesson) are absent from the public export;
|
|
6
10
|
// JS-only distributions still register them. Kept out of src/index.ts so the `self`
|
|
7
11
|
// block stays free of direct sink registration (see self-cli-sink-surface.test.ts).
|
|
8
|
-
|
|
12
|
+
const privateLedgerWrites: Record<string, readonly string[]> = {
|
|
13
|
+
claims: ['add', 'verify', 'publish', 'link', 'retract', 'list', 'show', 'render', 'recheck'], // list/show can mark expired evidence stale
|
|
14
|
+
lesson: ['add', 'recur', 'enforce', 'promote'],
|
|
15
|
+
directives: ['sync', 'search', 'list'], // search/list refresh the persistent index too
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
function mayWritePrivateLedger(name: string, args: readonly string[], hqDeps: HqDeps, ledgerRoot: string): boolean {
|
|
19
|
+
const action = args[0];
|
|
20
|
+
if (!action || !privateLedgerWrites[name]?.includes(action)) return true;
|
|
21
|
+
const command = `${name} ${action}`;
|
|
22
|
+
if (isIsolatedLedgerWriteRoot(ledgerRoot)) {
|
|
23
|
+
try { debug.log('hq.fence', 'skipped-isolated', { root: ledgerRoot, command }); } catch { /* observation is fail-soft */ }
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
return hqCliWriteAllowed(command, false, hqDeps);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function registerPrivateLedgerCommands(program: Command, hqDeps: HqDeps = {}, ledgerRoot = elanousStateRoot()): void {
|
|
9
30
|
if (existsSync(resolve(import.meta.dir, 'decisions-cli.ts')) || existsSync(resolve(import.meta.dir, 'decisions-cli.js'))) {
|
|
10
31
|
program.command('decisions').description('대표 결정 로컬 원장').allowUnknownOption().allowExcessArguments(true)
|
|
11
32
|
.action(async (_opts: unknown, command: Command) => {
|
|
@@ -18,13 +39,42 @@ export function registerPrivateLedgerCommands(program: Command): void {
|
|
|
18
39
|
await privateProgram.parseAsync(['decisions', ...command.args], { from: 'user' });
|
|
19
40
|
});
|
|
20
41
|
}
|
|
42
|
+
if (existsSync(resolve(import.meta.dir, 'claims-cli.ts')) || existsSync(resolve(import.meta.dir, 'claims-cli.js'))) {
|
|
43
|
+
program.command('claims').description('소구점 실측 근거 로컬 원장').allowUnknownOption().allowExcessArguments(true)
|
|
44
|
+
.addHelpText('after', '\n add <id> 주장 등록\n verify <id> 실측 근거 기록\n publish <id> 유효 근거로 공개\n link <id> 체크리스트 연결\n retract <id> 소구점 철회\n list 소구점 목록\n show <id> 근거·연결·이력 보기\n render deck·site·notice 문면 생성\n recheck 낡은 근거 재측 요청\n\n각 명령의 옵션: elanous claims <명령> --help')
|
|
45
|
+
.action(async (_opts: unknown, command: Command) => {
|
|
46
|
+
if (!mayWritePrivateLedger('claims', command.args, hqDeps, ledgerRoot)) return;
|
|
47
|
+
try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('claims'); }
|
|
48
|
+
catch { /* Logging must not prevent recording a claim. */ }
|
|
49
|
+
const privateClaimsModule: string = './claims-cli.js';
|
|
50
|
+
const { registerClaimsCommands } = await import(privateClaimsModule) as { registerClaimsCommands: (program: Command, options: { stateDir: string; instanceRoot: string }) => void };
|
|
51
|
+
const privateProgram = new Command();
|
|
52
|
+
registerClaimsCommands(privateProgram, { stateDir: ledgerRoot, instanceRoot: ledgerRoot });
|
|
53
|
+
await privateProgram.parseAsync(['claims', ...command.args], { from: 'user' });
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
if (existsSync(resolve(import.meta.dir, 'lesson-cli.ts')) || existsSync(resolve(import.meta.dir, 'lesson-cli.js'))) {
|
|
57
|
+
program.command('lesson').description('교훈 · 재발 로컬 원장').allowUnknownOption().allowExcessArguments(true)
|
|
58
|
+
.addHelpText('after', '\n import 문서 교훈 미리 보기 (--apply 로 적재)\n add <id> 사고·원인·처방 등록\n recur <id> 재발 기록\n enforce <id> 강제 자리 기록\n promote <id> 규칙 경로 기록\n find <query> 교훈 검색\n show <id> 발생·이력 보기\n candidates 반복 교훈 목록\n\n각 명령의 옵션: elanous lesson <명령> --help')
|
|
59
|
+
.action(async (_opts: unknown, command: Command) => {
|
|
60
|
+
if (!mayWritePrivateLedger('lesson', command.args, hqDeps, ledgerRoot)) return;
|
|
61
|
+
try { await (await import('../domains/standalone-log-sink.js')).registerStandaloneLogSink('lesson'); }
|
|
62
|
+
catch { /* Logging must not prevent recording a lesson. */ }
|
|
63
|
+
const privateLessonModule: string = './lesson-cli.js';
|
|
64
|
+
const { registerLessonCommands } = await import(privateLessonModule) as { registerLessonCommands: (program: Command, options: { stateDir: string }) => void };
|
|
65
|
+
const privateProgram = new Command();
|
|
66
|
+
registerLessonCommands(privateProgram, { stateDir: ledgerRoot });
|
|
67
|
+
await privateProgram.parseAsync(['lesson', ...command.args], { from: 'user' });
|
|
68
|
+
});
|
|
69
|
+
}
|
|
21
70
|
if (existsSync(resolve(import.meta.dir, 'directives-cli.ts')) || existsSync(resolve(import.meta.dir, 'directives-cli.js'))) {
|
|
22
71
|
program.command('directives').description('대표 지시 로컬 색인·검색').allowUnknownOption().allowExcessArguments(true)
|
|
23
72
|
.action(async (_opts: unknown, command: Command) => {
|
|
73
|
+
if (!mayWritePrivateLedger('directives', command.args, hqDeps, ledgerRoot)) return;
|
|
24
74
|
const privateDirectivesModule: string = './directives-cli.js';
|
|
25
|
-
const { registerDirectivesCommands } = await import(privateDirectivesModule) as { registerDirectivesCommands: (program: Command) => void };
|
|
75
|
+
const { registerDirectivesCommands } = await import(privateDirectivesModule) as { registerDirectivesCommands: (program: Command, options: { stateDir: string }) => void };
|
|
26
76
|
const privateProgram = new Command();
|
|
27
|
-
registerDirectivesCommands(privateProgram);
|
|
77
|
+
registerDirectivesCommands(privateProgram, { stateDir: ledgerRoot });
|
|
28
78
|
await privateProgram.parseAsync(['directives', ...command.args], { from: 'user' });
|
|
29
79
|
});
|
|
30
80
|
}
|
|
@@ -8,6 +8,8 @@ export interface FinalPtyEvidence {
|
|
|
8
8
|
readonly screen: string;
|
|
9
9
|
readonly inputHistory: readonly string[];
|
|
10
10
|
readonly exitCode: number | null;
|
|
11
|
+
/** A separately verified mission artifact; tool-history failures are not final-answer failures. */
|
|
12
|
+
readonly artifactEvidence?: boolean;
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
// eslint-disable-next-line no-control-regex
|
|
@@ -16,7 +18,7 @@ const FAILURE = /(?:\bcommand not found\b|\bnot recognized as (?:an internal or
|
|
|
16
18
|
const SUCCESS = /(?:\b[1-9]\d*\s+(?:pass|passed)\b|\btests?:\s*[1-9]\d*\s+passed\b|\bbuild successful\b|\b(?:successfully|succeeded)\b|\bGOAL-COMPLETE\b)/i;
|
|
17
19
|
|
|
18
20
|
/** Failure wins over success; an exit code of zero is not itself proof of the goal. */
|
|
19
|
-
export function verdictForFinalPtyScreen({ screen, inputHistory, exitCode }: FinalPtyEvidence): PtyDriveVerdict {
|
|
21
|
+
export function verdictForFinalPtyScreen({ screen, inputHistory, exitCode, artifactEvidence = false }: FinalPtyEvidence): PtyDriveVerdict {
|
|
20
22
|
if (exitCode !== null && exitCode !== 0) {
|
|
21
23
|
return { kind: 'done-but-failed', reason: `child exit code ${exitCode}`, evidence: null };
|
|
22
24
|
}
|
|
@@ -32,7 +34,14 @@ export function verdictForFinalPtyScreen({ screen, inputHistory, exitCode }: Fin
|
|
|
32
34
|
}));
|
|
33
35
|
const lines = screen.replace(ANSI, '').replace(/\r/g, '\n').split('\n')
|
|
34
36
|
.map((line) => line.trim()).filter((line) => line && !inputs.has(line) && !echoedLiterals.has(line) && ![...inputs].some((input) => line.endsWith(`$ ${input}`) || line.endsWith(`> ${input}`)));
|
|
35
|
-
const
|
|
37
|
+
const toolNoMatch = (line: string) =>
|
|
38
|
+
/\b(?:rg|grep|diff)\b.*\bexit(?:ed)?\s*(?:with\s*)?(?:code\s*)?1\b|\bexit(?:ed)?\s*(?:with\s*)?(?:code\s*)?1\b.*\b(?:rg|grep|diff)\b/i.test(line)
|
|
39
|
+
&& !/(?:\b(?:error|fatal|exception)\s*:|\b(?:build failed|tests? failed|failed to|permission denied|command not found)\b)/i.test(line);
|
|
40
|
+
// Codex renders tool status as «• Failed (exit 1) …». It is not the agent's final answer.
|
|
41
|
+
// For artifact-backed missions, consider only answer text following the last tool block.
|
|
42
|
+
const lastTool = artifactEvidence ? lines.reduce((index, line, i) => /^•\s+(?:Ran|Failed|Running|Worked)\b/i.test(line) ? i : index, -1) : -1;
|
|
43
|
+
const answer = lastTool < 0 ? lines : lines.slice(lastTool + 1).filter((line) => !/^[└│]/.test(line));
|
|
44
|
+
const failure = (artifactEvidence ? answer : lines).find((line) => FAILURE.test(line) && !toolNoMatch(line));
|
|
36
45
|
if (failure) return { kind: 'done-but-failed', reason: failure, evidence: null };
|
|
37
46
|
const evidence = lines.find((line) => SUCCESS.test(line) && !/\b0\s+(?:pass|passed)\b/i.test(line));
|
|
38
47
|
if (evidence) return { kind: 'success', reason: 'final PTY screen has success evidence', evidence };
|
package/src/cli/release-cli.ts
CHANGED
|
@@ -28,14 +28,17 @@ import { addItem, claimItem, decodeClaimHistoryEntry, devVersion, listChecklist,
|
|
|
28
28
|
import * as features from '../release-loop/feature-store.js';
|
|
29
29
|
import { evidencePlan, landedButYellow, type MergedChecklistPr } from '../release-loop/landed-but-yellow.js';
|
|
30
30
|
import { formatSchedule, getSchedule, listSchedules, setSchedule } from '../release-loop/release-schedule.js';
|
|
31
|
+
import { placeCell, rebalance, seatMove, type PlacementPriority } from '../release-loop/placement.js';
|
|
31
32
|
import { writeStdoutJson } from './stdout-json.js';
|
|
32
33
|
import { CliUserError } from './cli-user-error.js';
|
|
33
34
|
import { hqCliWriteAllowed, type HqDeps } from '../hq/hq.js';
|
|
34
35
|
import { fileLeaseStore } from '../hq/lease.js';
|
|
36
|
+
import { isIsolatedLedgerWriteRoot } from '../hq/ledger-write-target.js';
|
|
35
37
|
import { runUnattendedRelease, type UnattendedReleaseDeps } from '../../scripts/release-loop/unattended-release.js';
|
|
36
38
|
import { cutReleaseBranch } from '../../scripts/release-loop/cut-branch.js';
|
|
37
39
|
import { releaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
38
40
|
import { runReleaseIfReady } from './release-run-if-ready.js';
|
|
41
|
+
import { landingFreezeMessage, LandingFrozenError, readLandingFreeze } from '../release-loop/landing-freeze.js';
|
|
39
42
|
|
|
40
43
|
export const DEFAULT_PUBLIC_REPO = 'ElanvitalAI/elanous';
|
|
41
44
|
const SEMVER = /^\d+\.\d+\.\d+(?:-(?:rc|alpha|beta)\.\d+)?$/;
|
|
@@ -459,7 +462,8 @@ async function jsonAction<T>(json: boolean | undefined, body: (log: (l: string)
|
|
|
459
462
|
if (json) process.stdout.write(`${JSON.stringify({ ok: ok(r), ...(r as object) })}\n`);
|
|
460
463
|
if (!ok(r) && !process.exitCode) process.exitCode = 1;
|
|
461
464
|
} catch (e) {
|
|
462
|
-
|
|
465
|
+
const frozen = e instanceof LandingFrozenError ? { reason: 'frozen' as const } : {};
|
|
466
|
+
if (json) process.stdout.write(`${JSON.stringify({ ok: false, ...frozen, error: (e as Error).message })}\n`);
|
|
463
467
|
else console.error(`⛔ ${(e as Error).message}`);
|
|
464
468
|
process.exitCode = 1;
|
|
465
469
|
}
|
|
@@ -511,6 +515,46 @@ export interface LandedButYellowDeps {
|
|
|
511
515
|
released?: typeof features.releasedVersion;
|
|
512
516
|
}
|
|
513
517
|
|
|
518
|
+
const MERGED_PR_LIMIT = 400;
|
|
519
|
+
|
|
520
|
+
function mergedChecklistPrs(since: string, now: Date, run: Runner): { prs: MergedChecklistPr[]; truncated: boolean } {
|
|
521
|
+
const cwd = process.cwd();
|
|
522
|
+
const fetch = (lower: string, upper?: string, limit = MERGED_PR_LIMIT): MergedChecklistPr[] => {
|
|
523
|
+
const search = `merged:>=${lower}${upper ? ` merged:<${upper}` : ''}`;
|
|
524
|
+
const raw = must(run('gh', ['pr', 'list', '--state', 'merged', '--search', search, '--limit', String(limit), '--json', 'number,title,body,mergedAt'], cwd), '병합 PR 조회(gh)');
|
|
525
|
+
const page = JSON.parse(raw) as MergedChecklistPr[];
|
|
526
|
+
if (!Array.isArray(page)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
|
|
527
|
+
return page;
|
|
528
|
+
};
|
|
529
|
+
const unique = new Map<number, MergedChecklistPr>();
|
|
530
|
+
let truncated = false;
|
|
531
|
+
const collect = (page: MergedChecklistPr[]) => {
|
|
532
|
+
for (const pr of page) if (!unique.has(pr.number)) unique.set(pr.number, pr);
|
|
533
|
+
};
|
|
534
|
+
const first = fetch(since);
|
|
535
|
+
if (first.length < MERGED_PR_LIMIT) return { prs: first, truncated: false };
|
|
536
|
+
const lower = Date.parse(`${since}T00:00:00Z`);
|
|
537
|
+
const upper = Math.max(lower + 1000, Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1));
|
|
538
|
+
const visit = (start: number, end: number): void => {
|
|
539
|
+
const from = new Date(start).toISOString();
|
|
540
|
+
const to = new Date(end).toISOString();
|
|
541
|
+
const page = fetch(from, to);
|
|
542
|
+
if (page.length < MERGED_PR_LIMIT) { collect(page); return; }
|
|
543
|
+
if (end - start <= 1000) {
|
|
544
|
+
// The second-precision search cannot be subdivided; probe one extra row to distinguish exactly 400 from a real truncation.
|
|
545
|
+
const probe = fetch(from, to, MERGED_PR_LIMIT + 1);
|
|
546
|
+
collect(probe.slice(0, MERGED_PR_LIMIT));
|
|
547
|
+
if (probe.length > MERGED_PR_LIMIT) truncated = true;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
const middle = start + Math.floor((end - start) / 2000) * 1000;
|
|
551
|
+
visit(start, middle);
|
|
552
|
+
visit(middle, end);
|
|
553
|
+
};
|
|
554
|
+
visit(lower, upper);
|
|
555
|
+
return { prs: [...unique.values()], truncated };
|
|
556
|
+
}
|
|
557
|
+
|
|
514
558
|
function printLandedRows(rows: ReturnType<typeof landedButYellow>, items: ReturnType<typeof listChecklist>['items']): void {
|
|
515
559
|
const updatedAtById = new Map(items.map((item) => [item.id, item.updatedAt]));
|
|
516
560
|
const date = (iso: string) => {
|
|
@@ -544,6 +588,13 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
544
588
|
const ledgerFenceDeps = (): HqDeps => getElanousConfigDirOverride() && !hqDeps.store && !getUserConfig().hq?.arbiter
|
|
545
589
|
? { ...hqDeps, store: fileLeaseStore(join(effectiveInstanceRoot(), 'hq', 'lease.json')), seenPath: join(effectiveInstanceRoot(), 'hq', 'seen-generation'), localPath: join(effectiveInstanceRoot(), 'hq', 'local.json') }
|
|
546
590
|
: hqDeps;
|
|
591
|
+
const mayWriteLedger = (command: string, override: boolean, ledgerRoot: string) => {
|
|
592
|
+
if (isIsolatedLedgerWriteRoot(ledgerRoot)) {
|
|
593
|
+
try { debug.log('hq.fence', 'skipped-isolated', { root: ledgerRoot, command }); } catch { /* observation is fail-soft */ }
|
|
594
|
+
return true;
|
|
595
|
+
}
|
|
596
|
+
return hqCliWriteAllowed(command, override, ledgerFenceDeps());
|
|
597
|
+
};
|
|
547
598
|
schedule.command('list').option('--json', '결과 JSON').action((o: { json?: boolean }) => {
|
|
548
599
|
const rows = listSchedules(scheduleLedgerRoot());
|
|
549
600
|
if (o.json) console.log(JSON.stringify(rows));
|
|
@@ -556,18 +607,45 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
556
607
|
if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
|
|
557
608
|
});
|
|
558
609
|
schedule.command('set').requiredOption('--version <v>', '갱신할 판')
|
|
559
|
-
.option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감')
|
|
560
|
-
.
|
|
561
|
-
|
|
562
|
-
if (
|
|
563
|
-
|
|
610
|
+
.option('--cut-at <iso>', '오프셋 포함 컷 시각').option('--land-by <iso>', '오프셋 포함 착지 마감')
|
|
611
|
+
.option('--freeze-from <iso>', '동결 시작').option('--freeze-until <iso>', '동결 종료').option('--json', '결과 JSON').option('--hq-override')
|
|
612
|
+
.action((o: { version: string; cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string; json?: boolean; hqOverride?: boolean }) => {
|
|
613
|
+
if (o.cutAt === undefined && o.landBy === undefined && o.freezeFrom === undefined && o.freezeUntil === undefined) throw new CliUserError('갱신할 시각을 지정하라', '--cut-at <iso> 또는 --land-by <iso>');
|
|
614
|
+
if (!mayWriteLedger('release schedule set', Boolean(o.hqOverride), scheduleLedgerRoot())) return;
|
|
615
|
+
const row = setSchedule(o.version, { cutAt: o.cutAt, landBy: o.landBy, freezeFrom: o.freezeFrom, freezeUntil: o.freezeUntil }, process.env.ELANOUS_TRACK || 'cli', scheduleLedgerRoot());
|
|
564
616
|
if (o.json) console.log(JSON.stringify(row)); else console.log(formatSchedule(row));
|
|
565
617
|
});
|
|
618
|
+
release.command('place <id>').description('칸의 우선순위·마감·용량으로 판 배치')
|
|
619
|
+
.option('--priority <priority>', 'P0|P1|P2').option('--dry-run', '이동 없이 배치 보기')
|
|
620
|
+
.action((id: string, opts: { priority?: string; dryRun?: boolean }) => {
|
|
621
|
+
const rows = listSchedules(scheduleLedgerRoot());
|
|
622
|
+
const found = rows.flatMap(({ version }) => listChecklist(version).items.filter((item) => item.id === id));
|
|
623
|
+
if (found.length !== 1) throw new CliUserError(found.length ? `여러 판의 같은 칸: ${id}` : `없는 칸: ${id}`, 'release checklist add 로 칸을 먼저 만든다');
|
|
624
|
+
const item = found[0]!;
|
|
625
|
+
if (!item.owner) throw new CliUserError(`담당 없는 칸: ${id}`);
|
|
626
|
+
const who = process.env.ELANOUS_TRACK;
|
|
627
|
+
if (who && who !== 'OP' && !ownerMatches(item.owner, parseOwner(who).seat)) throw new CliUserError('남의 칸 당기기 거부 — COO 에 요청');
|
|
628
|
+
if (!opts.dryRun && who !== 'OP') throw new CliUserError('판 배치는 COO 에 요청 — 자리는 이유와 함께 release checklist move 로 다음 판에만 이월');
|
|
629
|
+
if (!opts.dryRun && !mayWriteLedger('release place', false, scheduleLedgerRoot())) return;
|
|
630
|
+
const priority = opts.priority ?? item.priority;
|
|
631
|
+
if (!priority) throw new CliUserError(`우선순위가 없는 칸: ${id} — --priority P0|P1|P2 를 지정하라`);
|
|
632
|
+
const result = placeCell({ id, title: item.title, owner: item.owner, priority: priority as PlacementPriority, predecessors: item.predecessors ?? [], deadlineVersion: item.deadlineVersion }, { schedules: rows, dryRun: opts.dryRun, by: who ?? 'cli' });
|
|
633
|
+
console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${id} ${result.from ?? '-'} → ${result.version} · ${result.reason}${result.displaced.length ? ` · P2 이월 ${result.displaced.map((row) => row.id).join(', ')}` : ''}`);
|
|
634
|
+
});
|
|
635
|
+
release.command('rebalance').description('마감 2시간 전 미시작 칸을 다음 판으로')
|
|
636
|
+
.requiredOption('--version <v>', '대상 판').option('--dry-run', '이동 없이 보기')
|
|
637
|
+
.action((opts: { version: string; dryRun?: boolean }) => {
|
|
638
|
+
if (process.env.ELANOUS_TRACK !== 'OP' && !opts.dryRun) throw new CliUserError('판 이월은 COO 에 요청');
|
|
639
|
+
if (!opts.dryRun && !mayWriteLedger('release rebalance', false, scheduleLedgerRoot())) return;
|
|
640
|
+
const decisions = rebalance(opts.version, { schedules: listSchedules(scheduleLedgerRoot()), dryRun: opts.dryRun });
|
|
641
|
+
for (const row of decisions) console.log(`${opts.dryRun ? '· 드라이런' : '✅'} ${row.id} ${row.from} → ${row.version} · ${row.reason}`);
|
|
642
|
+
if (!decisions.length) console.log('이월할 미시작 칸 없음');
|
|
643
|
+
});
|
|
566
644
|
const checklist = release.command('checklist').description('판별 확인표 조회·갱신')
|
|
567
645
|
.option('--version <v>', '판 또는 별칭(기본: package.json 의 개발판에서 -dev.N 제거)')
|
|
568
646
|
.option('--json', '결과 JSON')
|
|
569
647
|
.option('--hq-override', '본부 임대 거부를 관측하며 수동 우회');
|
|
570
|
-
const mayWrite = (cmd: Command) =>
|
|
648
|
+
const mayWrite = (cmd: Command) => mayWriteLedger(`release checklist ${cmd.name()}`, Boolean(cmd.optsWithGlobals().hqOverride), releaseLedgerRoot());
|
|
571
649
|
const context = (cmd: Command) => {
|
|
572
650
|
const opts = { ...(cmd.parent?.parent?.opts() as { version?: string; json?: boolean }), ...(cmd.parent?.opts() as { version?: string; json?: boolean }), ...(cmd.opts() as { version?: string; json?: boolean }) };
|
|
573
651
|
const codenames = checklistCodenames();
|
|
@@ -607,11 +685,14 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
607
685
|
return;
|
|
608
686
|
}
|
|
609
687
|
if (opts.applyEvidence && !opts.dryRun && !mayWrite(cmd)) return;
|
|
610
|
-
const since = new Date((landedDeps.now ?? (() => new Date()))().getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
|
|
611
|
-
const raw = must((landedDeps.run ?? defaultRunner)('gh', ['pr', 'list', '--state', 'merged', '--search', `merged:>=${since}`, '--limit', '400', '--json', 'number,title,body,mergedAt'], process.cwd()), '병합 PR 조회(gh)');
|
|
612
|
-
const prs = JSON.parse(raw) as MergedChecklistPr[];
|
|
613
|
-
if (!Array.isArray(prs)) throw new Error('병합 PR 조회(gh): 배열이 아니다');
|
|
614
688
|
const snapshots = opts.open ? openSnapshots : [(landedDeps.checklist ?? listChecklist)(context(cmd).version)];
|
|
689
|
+
const now = (landedDeps.now ?? (() => new Date()))();
|
|
690
|
+
const updated = snapshots.flatMap(({ items }) => items
|
|
691
|
+
.filter((item) => (item.status === 'yellow' || item.status === 'red') && (opts.owner === undefined || ownerMatches(item.owner, opts.owner)))
|
|
692
|
+
.map((item) => Date.parse(item.updatedAt)).filter((at) => Number.isFinite(at)));
|
|
693
|
+
const since = updated.length ? new Date(updated.reduce((earliest, at) => Math.min(earliest, at))).toISOString().slice(0, 10)
|
|
694
|
+
: new Date(now.getTime() - 7 * 86_400_000).toISOString().slice(0, 10);
|
|
695
|
+
const { prs, truncated } = mergedChecklistPrs(since, now, landedDeps.run ?? defaultRunner);
|
|
615
696
|
const results: Array<{ version: string; rows?: ReturnType<typeof landedButYellow>; plan?: ReturnType<typeof evidencePlan> }> = [];
|
|
616
697
|
let totalAdded = 0, totalSkipped = 0;
|
|
617
698
|
for (const snapshot of snapshots) {
|
|
@@ -643,30 +724,32 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
643
724
|
if (!json) console.log(`판 ${snapshots.length} · 붙임 합 ${totalAdded} · 건너뜀(언급만) 합 ${totalSkipped}`);
|
|
644
725
|
else await writeStdoutJson(`${JSON.stringify(results)}\n`);
|
|
645
726
|
} else if (json) await writeStdoutJson(`${JSON.stringify(opts.applyEvidence || opts.dryRun ? results[0]!.plan : results[0]!.rows)}\n`);
|
|
646
|
-
if (
|
|
727
|
+
if (truncated) console.error('⚠ 병합 PR 400개 상한: 목록이 잘렸을 수 있다');
|
|
647
728
|
} catch (error) {
|
|
648
729
|
console.error(`⛔ ${error instanceof Error ? error.message : String(error)}`);
|
|
649
730
|
process.exitCode = 1;
|
|
650
731
|
}
|
|
651
732
|
});
|
|
652
733
|
withContext(checklist.command('add <id> <title>').description('칸 추가')).option('--owner <owner>', '담당').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
734
|
+
.option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
|
|
653
735
|
.option('--allow-duplicate-id', '다른 판에 같은 id 가 있어도 경고 후 추가')
|
|
654
|
-
.action((id: string, title: string, opts: { owner?: string; kind?: string; allowDuplicateId?: boolean }, cmd: Command) => {
|
|
736
|
+
.action((id: string, title: string, opts: { owner?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[]; allowDuplicateId?: boolean }, cmd: Command) => {
|
|
655
737
|
const { version, json } = context(cmd);
|
|
656
738
|
if (!mayWrite(cmd)) return;
|
|
657
|
-
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
739
|
+
const data = addItem(version, { id, title, ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}) }, { allowDuplicateId: opts.allowDuplicateId });
|
|
658
740
|
if (json) console.log(JSON.stringify(data)); else console.log(`✅ ${id} 추가`);
|
|
659
741
|
});
|
|
660
742
|
withContext(checklist.command('set <id>').description('칸 상태·근거·담당·처분 갱신'))
|
|
661
743
|
.option('--status <status>', 'green|yellow|red|done').option('--evidence <evidence>', '근거').option('--owner <owner>', '담당')
|
|
662
744
|
.option('--disposition <disposition>', 'move|known-issue|block').option('--kind <kind>', 'screen = 다섯 화면 짝 칸 · 근거에 짝: PWA … · 데스크톱 … · 폴드 … · 아이폰 … · 아이패드 … 한 줄')
|
|
663
|
-
.
|
|
745
|
+
.option('--priority <priority>', 'P0|P1|P2').option('--deadline-version <v>', '마감 판').option('--predecessor <id...>', '선행 칸 id')
|
|
746
|
+
.action((id: string, opts: { status?: string; evidence?: string; owner?: string; disposition?: string; kind?: string; priority?: string; deadlineVersion?: string; predecessor?: string[] }, cmd: Command) => {
|
|
664
747
|
const { version, json } = context(cmd);
|
|
665
748
|
if (opts.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(opts.status)) throw new CliUserError(`잘못된 상태: ${opts.status}`, 'green|yellow|red|done');
|
|
666
749
|
if (opts.disposition !== undefined && !['move', 'known-issue', 'block'].includes(opts.disposition)) throw new CliUserError(`잘못된 처분: ${opts.disposition}`, 'move|known-issue|block');
|
|
667
|
-
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind 중 하나');
|
|
750
|
+
if (opts.status === undefined && opts.evidence === undefined && opts.owner === undefined && opts.disposition === undefined && opts.kind === undefined && opts.priority === undefined && opts.deadlineVersion === undefined && opts.predecessor === undefined) throw new CliUserError('갱신할 칸을 지정하라', '--status · --evidence · --owner · --disposition · --kind · --priority · --deadline-version · --predecessor 중 하나');
|
|
668
751
|
if (!mayWrite(cmd)) return;
|
|
669
|
-
const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
752
|
+
const data = setItem(version, id, { ...(opts.status !== undefined ? { status: opts.status as ChecklistStatus } : {}), ...(opts.evidence !== undefined ? { evidence: opts.evidence } : {}), ...(opts.owner !== undefined ? { owner: opts.owner } : {}), ...(opts.disposition !== undefined ? { disposition: opts.disposition as ChecklistDisposition } : {}), ...(opts.kind !== undefined ? { kind: opts.kind as ChecklistKind } : {}), ...(opts.priority !== undefined ? { priority: opts.priority as PlacementPriority } : {}), ...(opts.deadlineVersion !== undefined ? { deadlineVersion: opts.deadlineVersion } : {}), ...(opts.predecessor !== undefined ? { predecessors: opts.predecessor } : {}) }, process.env.ELANOUS_TRACK || 'cli');
|
|
670
753
|
const item = data.items.find((entry) => entry.id === id);
|
|
671
754
|
const why = item?.kind === 'screen' && item.status === 'green' ? parityGap(item.evidence) : null;
|
|
672
755
|
if (why) {
|
|
@@ -701,8 +784,15 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
701
784
|
.action((id: string, opts: { from: string; to: string; reason?: string }, cmd: Command) => {
|
|
702
785
|
const { codenames, json } = context(cmd);
|
|
703
786
|
const from = checklistVersion(opts.from, codenames), to = checklistVersion(opts.to, codenames);
|
|
787
|
+
if (!opts.reason?.trim()) throw new CliUserError('이동 이유가 필요하다');
|
|
704
788
|
if (!mayWrite(cmd)) return;
|
|
705
|
-
|
|
789
|
+
if (actor() === 'cli') {
|
|
790
|
+
// No seat identity (ELANOUS_TRACK unset): keep the plain move so seat sessions without the variable still work, but say so.
|
|
791
|
+
console.error('⚠ 자리 신원 없음(ELANOUS_TRACK 미설정) — 자리 규칙(자기 칸 · 다음 판 · 용량) 검사 없이 옮긴다');
|
|
792
|
+
debug.log('release.placement', 'anonymous-move', { id, from, to });
|
|
793
|
+
features.move(id, from, to, actor(), undefined, undefined, opts.reason);
|
|
794
|
+
} else if (actor() !== 'OP') seatMove(id, from, to, actor(), opts.reason, { schedules: listSchedules(scheduleLedgerRoot()) });
|
|
795
|
+
else features.move(id, from, to, actor(), undefined, undefined, opts.reason);
|
|
706
796
|
if (json) console.log(JSON.stringify(listChecklist(to))); else console.log(`✅ ${id} ${from} → ${to}`);
|
|
707
797
|
});
|
|
708
798
|
withContext(checklist.command('retitle <id> <title>').description('칸 제목 수정')).action((id: string, title: string, _opts: unknown, cmd: Command) => {
|
|
@@ -813,14 +903,40 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
813
903
|
.option('--cut-commit <sha>', 'origin/main HEAD 대신 컷으로 쓸 커밋')
|
|
814
904
|
.option('--dry-run', '체크리스트·그래프 실행 없이 전체 입력 보기')
|
|
815
905
|
.option('--if-ready', '준비되지 않았으면 사유를 출력하고 성공으로 건너뛴다')
|
|
906
|
+
.option('--force-freeze', '동결 중 게이트·발행 강행(관측 기록)')
|
|
816
907
|
.option('--json', '결과 한 줄 JSON(stdout) · 사람 줄은 stderr')
|
|
817
|
-
.action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; json?: boolean }) => {
|
|
908
|
+
.action(async (o: { version: string; cutCommit?: string; dryRun?: boolean; ifReady?: boolean; forceFreeze?: boolean; json?: boolean }) => {
|
|
909
|
+
if (!o.dryRun) {
|
|
910
|
+
try {
|
|
911
|
+
const frozen = readLandingFreeze(releaseRunDeps.freezeRoot);
|
|
912
|
+
if (frozen) {
|
|
913
|
+
debug.log('release.run', o.forceFreeze ? 'freeze-forced' : 'frozen', { version: o.version, reason: frozen.reason, until: frozen.until });
|
|
914
|
+
if (!o.forceFreeze) {
|
|
915
|
+
// `--if-ready` (the scheduled path) defers quietly; a direct run fails — the release run is run again after `freeze off`.
|
|
916
|
+
if (o.ifReady) {
|
|
917
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: landingFreezeMessage(frozen) })}\n`);
|
|
918
|
+
else console.log(`· ${landingFreezeMessage(frozen)} · 릴리스 루프 연기`);
|
|
919
|
+
} else {
|
|
920
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, reason: 'frozen', error: landingFreezeMessage(frozen) })}\n`);
|
|
921
|
+
else console.error(`⛔ ${landingFreezeMessage(frozen)} · 릴리스 루프 거부(--force-freeze 로만 강행)`);
|
|
922
|
+
process.exitCode = 1;
|
|
923
|
+
}
|
|
924
|
+
return;
|
|
925
|
+
}
|
|
926
|
+
}
|
|
927
|
+
} catch (e) {
|
|
928
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
|
|
929
|
+
else console.error(`⛔ ${(e as Error).message}`);
|
|
930
|
+
process.exitCode = 1;
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
}
|
|
818
934
|
if (o.ifReady && !o.dryRun) {
|
|
819
935
|
try {
|
|
820
936
|
const outcome = await runReleaseIfReady(o.version, {
|
|
821
937
|
ledgerRoot: releaseRunDeps.ledgerRoot,
|
|
822
938
|
readiness: (version, options) => releaseReadiness(version, { ledgerRoot: releaseRunDeps.ledgerRoot, checklist: releaseRunDeps.checklist, ...options }),
|
|
823
|
-
run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit }, releaseRunDeps),
|
|
939
|
+
run: () => runUnattendedRelease({ version: o.version, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps),
|
|
824
940
|
});
|
|
825
941
|
if (outcome.skipped) {
|
|
826
942
|
if (o.json) await writeStdoutJson(`${JSON.stringify(outcome)}\n`);
|
|
@@ -833,6 +949,13 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
833
949
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok, ...result })}\n`);
|
|
834
950
|
if (!ok && !process.exitCode) process.exitCode = 1;
|
|
835
951
|
} catch (e) {
|
|
952
|
+
if (e instanceof LandingFrozenError) {
|
|
953
|
+
// A freeze switched on after the entry check still defers the scheduled run rather than failing it.
|
|
954
|
+
debug.log('release.run', 'frozen', { version: o.version, reason: e.freeze.reason, until: e.freeze.until, at: 'run-boundary' });
|
|
955
|
+
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: true, skipped: true, reason: 'frozen', detail: e.message })}\n`);
|
|
956
|
+
else console.log(`· ${e.message} · 릴리스 루프 연기`);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
836
959
|
if (o.json) await writeStdoutJson(`${JSON.stringify({ ok: false, error: (e as Error).message })}\n`);
|
|
837
960
|
else console.error(`⛔ ${(e as Error).message}`);
|
|
838
961
|
process.exitCode = 1;
|
|
@@ -840,7 +963,7 @@ export function registerReleaseCommands(program: Command, releaseRunDeps: Unatte
|
|
|
840
963
|
return;
|
|
841
964
|
}
|
|
842
965
|
await jsonAction(o.json, async (log) => {
|
|
843
|
-
const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit }, releaseRunDeps);
|
|
966
|
+
const result = await runUnattendedRelease({ version: o.version, dryRun: o.dryRun, cutCommit: o.cutCommit, forceFreeze: o.forceFreeze }, releaseRunDeps);
|
|
844
967
|
log(`${result.dryRun ? '· 드라이런' : '▶ 릴리스 루프'} ${result.input.version} · 입력 ${JSON.stringify(result.input)}`);
|
|
845
968
|
return result;
|
|
846
969
|
}, (r) => r.dryRun || r.state?.status === 'done' || r.state?.status === 'awaiting-approval');
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join, relative, resolve } from 'node:path';
|
|
4
|
+
import YAML from 'yaml';
|
|
5
|
+
|
|
6
|
+
export const RFC_STATUSES = ['unknown', 'proposed', 'adopted', 'completed', 'superseded', 'rejected'] as const;
|
|
7
|
+
export type RfcStatus = typeof RFC_STATUSES[number];
|
|
8
|
+
export interface RfcRow {
|
|
9
|
+
path: string;
|
|
10
|
+
status: RfcStatus;
|
|
11
|
+
owner: string | null;
|
|
12
|
+
card: string | null;
|
|
13
|
+
supersededBy: string | null;
|
|
14
|
+
handbook: string | null;
|
|
15
|
+
issue: string | null;
|
|
16
|
+
/** Legacy top-level `status:` / `owner:` values, verbatim: evidence for a human migration, never the lifecycle status. */
|
|
17
|
+
legacyStatus: string | null;
|
|
18
|
+
legacyOwner: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function rfcPaths(dir: string): string[] {
|
|
22
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
|
|
23
|
+
const path = join(dir, entry.name);
|
|
24
|
+
if (entry.isDirectory()) return rfcPaths(path);
|
|
25
|
+
return entry.isFile() && /^RFC-.*\.md$/.test(entry.name) ? [path] : [];
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function field(value: unknown): string | null {
|
|
30
|
+
return typeof value === 'string' && value.trim() && value.trim().toLowerCase() !== 'unknown' ? value.trim() : null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Explicit lifecycle fields only: prose, old `status: rfc`, and file location do not establish adoption. */
|
|
34
|
+
export function readRfcRow(text: string, path: string): RfcRow {
|
|
35
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(text);
|
|
36
|
+
let meta: Record<string, unknown> = {};
|
|
37
|
+
let issue: string | null = null;
|
|
38
|
+
let legacyStatus: string | null = null;
|
|
39
|
+
let legacyOwner: string | null = null;
|
|
40
|
+
if (!match) issue = 'lifecycle frontmatter missing';
|
|
41
|
+
else {
|
|
42
|
+
// Legacy RFC frontmatter can contain non-YAML prose. Parse only the new, top-level rfc_* declarations.
|
|
43
|
+
legacyStatus = /^status:\s*(.+)$/m.exec(match[1]!)?.[1]?.trim().replace(/^["']|["']$/g, '') || null;
|
|
44
|
+
legacyOwner = /^owner:\s*(.+)$/m.exec(match[1]!)?.[1]?.trim().replace(/^["']|["']$/g, '') || null;
|
|
45
|
+
const declarations = [...match[1]!.matchAll(/^(rfc_status|rfc_owner|rfc_card|rfc_superseded_by|rfc_handbook):\s*(.*)$/gm)];
|
|
46
|
+
for (const [, key, raw] of declarations) {
|
|
47
|
+
try { meta[key!] = YAML.parse(raw!); }
|
|
48
|
+
catch { issue = 'invalid lifecycle frontmatter'; }
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const rawStatus = meta.rfc_status;
|
|
52
|
+
const status: RfcStatus = typeof rawStatus === 'string' && RFC_STATUSES.includes(rawStatus as RfcStatus)
|
|
53
|
+
? rawStatus as RfcStatus : 'unknown';
|
|
54
|
+
if (rawStatus !== undefined && status === 'unknown' && rawStatus !== 'unknown') issue = 'invalid rfc_status';
|
|
55
|
+
return {
|
|
56
|
+
path, status, owner: field(meta.rfc_owner), card: field(meta.rfc_card),
|
|
57
|
+
supersededBy: field(meta.rfc_superseded_by), handbook: field(meta.rfc_handbook), issue, legacyStatus, legacyOwner,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function scanRfcs(repoRoot: string): RfcRow[] {
|
|
62
|
+
const root = resolve(repoRoot);
|
|
63
|
+
return rfcPaths(join(root, 'docs')).map((file) => readRfcRow(readFileSync(file, 'utf8'), relative(root, file).replaceAll('\\', '/')))
|
|
64
|
+
.sort((a, b) => a.path.localeCompare(b.path, 'en'));
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** A missing declaration is not proof that a card does not exist; an adopted RFC with an explicit null is. */
|
|
68
|
+
export function adoptedWithoutCard(rows: readonly RfcRow[]): RfcRow[] {
|
|
69
|
+
return rows.filter((row) => row.status === 'adopted' && row.card === null);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function runRfcStatus(options: { json?: boolean; missingCards?: boolean }, root = process.cwd(),
|
|
73
|
+
write: (text: string) => void = (text) => process.stdout.write(text)): void {
|
|
74
|
+
const all = scanRfcs(root);
|
|
75
|
+
const rows = options.missingCards ? adoptedWithoutCard(all) : all;
|
|
76
|
+
if (options.json) {
|
|
77
|
+
write(`${JSON.stringify({ total: all.length, adoptedWithoutCard: adoptedWithoutCard(all).length, rows }, null, 2)}\n`);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
write(`RFC ${all.length} · 채택·칸 없음 ${adoptedWithoutCard(all).length} · 표시 ${rows.length}\n`);
|
|
81
|
+
write('상태\t담당\t칸\t대체 문서\t핸드북\t옛 상태\t옛 담당\t경로\t메타 오류\n');
|
|
82
|
+
for (const row of rows) write(`${row.status}\t${row.owner ?? '모름'}\t${row.card ?? '모름'}\t${row.supersededBy ?? '모름'}\t${row.handbook ?? '모름'}\t${row.legacyStatus ?? '-'}\t${row.legacyOwner ?? '-'}\t${row.path}\t${row.issue ?? '-'}\n`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function registerRfcStatusCommand(docs: Command): void {
|
|
86
|
+
docs.command('rfc-status')
|
|
87
|
+
.description('전체 RFC 생애주기 표 (RFC frontmatter만 읽음)')
|
|
88
|
+
.option('--json', '기계가 읽을 JSON')
|
|
89
|
+
.option('--missing-cards', '채택됐으나 칸이 기록되지 않은 RFC만')
|
|
90
|
+
.action((options: { json?: boolean; missingCards?: boolean }) => runRfcStatus(options));
|
|
91
|
+
}
|
package/src/connectors/linear.ts
CHANGED
|
@@ -68,8 +68,8 @@ export function parseLinearWebhook(body: unknown, deliveryId: string, ledger?: E
|
|
|
68
68
|
return isLinearProjectionEcho(event, ledger ?? new EventLedger()) ? null : event;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, since, fetch: fetchFn = fetch, ledger }: {
|
|
72
|
-
apiKey: string; teamKey: string; labelOrPrefix?: string; since?: string; fetch?: typeof fetch; ledger?: EventLedger;
|
|
71
|
+
export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, excludeLabel, since, fetch: fetchFn = fetch, ledger }: {
|
|
72
|
+
apiKey: string; teamKey: string; labelOrPrefix?: string; excludeLabel?: string; since?: string; fetch?: typeof fetch; ledger?: EventLedger;
|
|
73
73
|
}): Promise<ExternalTaskEvent[]> {
|
|
74
74
|
const query = `query ConnectorIssues($teamKey: String!, $after: String) {
|
|
75
75
|
issues(filter: { team: { key: { eq: $teamKey } } }, first: 100, after: $after) {
|
|
@@ -106,8 +106,10 @@ export async function fetchLinearIssues({ apiKey, teamKey, labelOrPrefix, since,
|
|
|
106
106
|
const issue = asIssue(node);
|
|
107
107
|
if (!issue || (since && issue.updatedAt < since) ||
|
|
108
108
|
['completed', 'canceled', 'duplicate'].includes((node.state as { type?: string } | undefined)?.type ?? '')) continue;
|
|
109
|
+
const labels = (node.labels as { nodes?: Array<{ name: string }> } | undefined)?.nodes;
|
|
110
|
+
if (excludeLabel && labels?.some(label => label.name.toLowerCase() === excludeLabel.toLowerCase())) continue;
|
|
109
111
|
if (labelOrPrefix && !issue.title.startsWith(labelOrPrefix) &&
|
|
110
|
-
!
|
|
112
|
+
!labels?.some(label => label.name === labelOrPrefix)) continue;
|
|
111
113
|
const event: ExternalTaskEvent = { provider: 'linear', eventId: `${issue.id}:${issue.updatedAt}`, kind: 'updated',
|
|
112
114
|
ref: issue.id, identifier: issue.identifier, title: issue.title, body: issue.description ?? '',
|
|
113
115
|
url: issue.url ?? '', priority: priorityOf(issue.priority), occurredAt: issue.updatedAt };
|
|
@@ -1,32 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { closeSync, constants, existsSync, fstatSync, lstatSync, mkdirSync, openSync } from 'node:fs';
|
|
1
|
+
import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync } from 'node:fs';
|
|
3
2
|
import { dlopen, FFIType } from 'bun:ffi';
|
|
4
3
|
import { join } from 'node:path';
|
|
5
4
|
import { listMemories, memoryRoot, saveMemory, type MemoryEntry } from '../memory.js';
|
|
6
|
-
import { surfaceEventsDbPath } from '../domains/surface-events.js';
|
|
7
5
|
import { DecisionLedger } from '../decisions/decision-ledger.js';
|
|
8
|
-
import {
|
|
9
|
-
import { condenseContextWindowWithReport, type CondenseSkipped, type MemoryDeps, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
|
|
6
|
+
import { condenseContextWindowWithReport, readCondenseEvents, type CondenseFunnel, type CondenseSkipped, type MemoryDeps, type MemoryItem, type MemorySource, type MemorySummary } from './long-term-memory.js';
|
|
10
7
|
|
|
11
8
|
const summarize = async (source: MemorySource): Promise<MemorySummary> =>
|
|
12
9
|
(await import('./nightly-condense.js')).summarize(source);
|
|
13
10
|
|
|
14
|
-
/** Reuse the context-bus reader against a strictly read-only SQLite handle. */
|
|
15
|
-
function readContextEvents(since: string) {
|
|
16
|
-
const path = surfaceEventsDbPath();
|
|
17
|
-
if (!existsSync(path)) return [];
|
|
18
|
-
const db = new Database(path, { readonly: true, strict: true });
|
|
19
|
-
try { return listCoordEvents({ since }, { db }); }
|
|
20
|
-
finally { db.close(); }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
11
|
export interface CondenseReport {
|
|
24
12
|
since: string;
|
|
25
13
|
until: string;
|
|
26
14
|
cards: MemoryItem[];
|
|
27
15
|
conflicts: number;
|
|
28
16
|
skipped: CondenseSkipped;
|
|
17
|
+
funnel: CondenseFunnel;
|
|
29
18
|
folded: number;
|
|
19
|
+
droppedHarnessChild: number;
|
|
30
20
|
markdown: string;
|
|
31
21
|
applied?: { written: number; retired: number; decisions: string[] };
|
|
32
22
|
}
|
|
@@ -35,12 +25,17 @@ function mdCell(value: string): string {
|
|
|
35
25
|
return value.replaceAll('|', '\\|').replaceAll('\n', ' ');
|
|
36
26
|
}
|
|
37
27
|
|
|
38
|
-
export function renderCondenseMarkdown(report: Pick<CondenseReport, 'since' | 'until' | 'cards' | 'conflicts' | 'skipped'>): string {
|
|
28
|
+
export function renderCondenseMarkdown(report: Pick<CondenseReport, 'since' | 'until' | 'cards' | 'conflicts' | 'skipped' | 'droppedHarnessChild' | 'funnel'>): string {
|
|
29
|
+
const seats = (counts: Record<string, number>) => ['OP', 'TC', 'MK', 'UX', ...Object.keys(counts).filter(seat => !['OP', 'TC', 'MK', 'UX'].includes(seat)).sort()]
|
|
30
|
+
.map(seat => `${seat} ${counts[seat] ?? 0}`).join(' · ');
|
|
31
|
+
const stage = (name: string, value: { total: number; bySeat: Record<string, number> }) =>
|
|
32
|
+
`${name} ${value.total} (${seats(value.bySeat)})`;
|
|
39
33
|
const lines = [
|
|
40
34
|
'# Long-term context memory candidates',
|
|
41
35
|
'',
|
|
42
36
|
`Window: ${report.since} → ${report.until} · Cards: ${report.cards.length} · Conflicts: ${report.conflicts}`,
|
|
43
|
-
`
|
|
37
|
+
`Funnel: ${stage('read', report.funnel.read)} → ${stage('sources', report.funnel.sources)} → ${stage('summarized', report.funnel.summarized)} → ${stage('cards', report.funnel.cards)}`,
|
|
38
|
+
`Skipped: ${JSON.stringify(report.skipped)} · Dropped harness-child: ${report.droppedHarnessChild}`,
|
|
44
39
|
'',
|
|
45
40
|
];
|
|
46
41
|
for (const card of report.cards) {
|
|
@@ -180,15 +175,15 @@ export async function runContextCondense(options: {
|
|
|
180
175
|
const now = options.now ?? new Date();
|
|
181
176
|
const until = now.toISOString();
|
|
182
177
|
const since = new Date(now.getTime() - options.hours * 3_600_000).toISOString();
|
|
183
|
-
const { cards, skipped, folded } = await condenseContextWindowWithReport(since, until, [], {
|
|
178
|
+
const { cards, skipped, folded, droppedHarnessChild, funnel } = await condenseContextWindowWithReport(since, until, [], {
|
|
184
179
|
summarize: deps.summarize,
|
|
185
|
-
events: deps.events ??
|
|
180
|
+
events: deps.events ?? readCondenseEvents,
|
|
186
181
|
decisions: deps.decisions ?? (() => new DecisionLedger(options.root ? { stateDir: options.root } : {}).list({ status: 'all' })
|
|
187
182
|
.filter(decision => decision.raisedBy.agent !== 'context-condense')),
|
|
188
183
|
}, now);
|
|
189
184
|
const conflicts = cards.filter(card => card.conflict !== undefined).length;
|
|
190
|
-
const report: CondenseReport = { since, until, cards, conflicts, skipped, folded,
|
|
191
|
-
markdown: renderCondenseMarkdown({ since, until, cards, conflicts, skipped }) };
|
|
185
|
+
const report: CondenseReport = { since, until, cards, conflicts, skipped, folded, droppedHarnessChild, funnel,
|
|
186
|
+
markdown: renderCondenseMarkdown({ since, until, cards, conflicts, skipped, droppedHarnessChild, funnel }) };
|
|
192
187
|
if (options.apply === true) report.applied = applyCondensedCards(cards, store ?? defaultStore(options.root));
|
|
193
188
|
return report;
|
|
194
189
|
}
|