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
package/src/loops/verdict.ts
CHANGED
|
@@ -1,20 +1,32 @@
|
|
|
1
1
|
export type LoopCronVerdict = 'alive' | 'late' | 'failed' | 'off';
|
|
2
2
|
|
|
3
|
-
/** Shared display inputs for a graph loop or
|
|
4
|
-
*
|
|
3
|
+
/** Shared display inputs for a graph loop or cron job. Fixed intervals use
|
|
4
|
+
* milliseconds; calendar cron schedules supply the second actual due instant. */
|
|
5
5
|
export interface LoopCronVerdictInput {
|
|
6
6
|
enabled: boolean;
|
|
7
7
|
lastRunAt: string | null;
|
|
8
8
|
lastStatus: string | null;
|
|
9
9
|
intervalMs: number | null;
|
|
10
|
+
/** Second scheduled execution after the last run, for calendar-based cron schedules. */
|
|
11
|
+
secondDueAt?: string | null;
|
|
12
|
+
/** Cron-aware daemon state (src/domains/schedule-state.ts). Callers pass it for cron schedules only —
|
|
13
|
+
* a fixed interval keeps its own «2× interval» rule (review must-fix: stale must not override an interval). */
|
|
14
|
+
scheduleState?: 'live' | 'firing' | 'stale' | 'off';
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
/** Pure health classification; a failed last execution takes precedence over age.
|
|
13
|
-
*
|
|
18
|
+
* Without a valid last run and a cadence/due instant, lateness needs the daemon stale signal. */
|
|
14
19
|
export function loopCronVerdict(entry: LoopCronVerdictInput, nowMs: number): LoopCronVerdict {
|
|
15
|
-
if (!entry.enabled) return 'off';
|
|
16
|
-
if (entry.lastStatus === 'failed' || entry.lastStatus === 'error' || entry.lastStatus === 'failure'
|
|
20
|
+
if (!entry.enabled || entry.scheduleState === 'off') return 'off';
|
|
21
|
+
if (entry.lastStatus === 'failed' || entry.lastStatus === 'error' || entry.lastStatus === 'failure' ||
|
|
22
|
+
entry.lastStatus === 'abandoned' || entry.lastStatus === 'expired') return 'failed';
|
|
23
|
+
if (entry.scheduleState === 'stale') return 'late';
|
|
17
24
|
const lastRunMs = entry.lastRunAt === null ? NaN : Date.parse(entry.lastRunAt);
|
|
25
|
+
if (entry.secondDueAt !== undefined) {
|
|
26
|
+
const secondDueMs = entry.secondDueAt === null ? NaN : Date.parse(entry.secondDueAt);
|
|
27
|
+
return Number.isFinite(lastRunMs) && Number.isFinite(nowMs) &&
|
|
28
|
+
Number.isFinite(secondDueMs) && secondDueMs > lastRunMs && nowMs > secondDueMs ? 'late' : 'alive';
|
|
29
|
+
}
|
|
18
30
|
if (Number.isFinite(nowMs) && Number.isFinite(lastRunMs) &&
|
|
19
31
|
entry.intervalMs !== null && Number.isFinite(entry.intervalMs) && entry.intervalMs > 0 &&
|
|
20
32
|
nowMs - lastRunMs > 2 * entry.intervalMs) return 'late';
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type { SlashCommand } from '../chat/index.js';
|
|
2
|
+
import type { Maturity } from './feature-maturity.js';
|
|
3
|
+
import type { BotSurface } from './bot-command-maturity.js';
|
|
4
|
+
|
|
5
|
+
export interface BotSlashCatalogEntry {
|
|
6
|
+
name: string;
|
|
7
|
+
description: string;
|
|
8
|
+
supported: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface BotSlashCatalog {
|
|
12
|
+
commands: BotSlashCatalogEntry[];
|
|
13
|
+
/** Null means this name is already handled, or is not in the catalog. */
|
|
14
|
+
unsupportedReply(name: string): string | null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** The menu is a projection of registered core commands, not a second list of invented names.
|
|
18
|
+
* A bot's actual handlers take precedence over maturity metadata: a grade describes
|
|
19
|
+
* visibility, never whether an existing command can be dispatched. */
|
|
20
|
+
export function buildBotSlashCatalog(options: {
|
|
21
|
+
surface: BotSurface;
|
|
22
|
+
coreCommands: readonly Pick<SlashCommand, 'name' | 'description'>[];
|
|
23
|
+
maturity: {
|
|
24
|
+
tuiSlash: Readonly<Record<string, Maturity>>;
|
|
25
|
+
telegramCommand: Readonly<Record<string, Maturity>>;
|
|
26
|
+
discordCommand: Readonly<Record<string, Maturity>>;
|
|
27
|
+
};
|
|
28
|
+
handledCommands: readonly { name: string; description: string }[];
|
|
29
|
+
}): BotSlashCatalog {
|
|
30
|
+
const { surface, coreCommands, maturity, handledCommands } = options;
|
|
31
|
+
const grades = surface === 'telegram' ? maturity.telegramCommand : maturity.discordCommand;
|
|
32
|
+
const validName = surface === 'telegram' ? /^[a-z0-9_]{1,32}$/ : /^[a-z0-9_-]{1,32}$/;
|
|
33
|
+
const commands: BotSlashCatalogEntry[] = [];
|
|
34
|
+
const seen = new Set<string>();
|
|
35
|
+
for (const command of handledCommands) {
|
|
36
|
+
if (seen.has(command.name)) continue;
|
|
37
|
+
commands.push({ name: command.name, description: command.description, supported: true });
|
|
38
|
+
seen.add(command.name);
|
|
39
|
+
}
|
|
40
|
+
const limit = surface === 'telegram' ? 100 : 25;
|
|
41
|
+
const candidateNames = new Set<string>();
|
|
42
|
+
const candidates = coreCommands.filter((command) => {
|
|
43
|
+
const name = command.name;
|
|
44
|
+
const grade = Object.hasOwn(grades, name) ? grades[name] : undefined;
|
|
45
|
+
if (seen.has(name) || candidateNames.has(name) || !validName.test(name) || grade === undefined || grade === 'broken' || grade === 'system') return false;
|
|
46
|
+
candidateNames.add(name);
|
|
47
|
+
return true;
|
|
48
|
+
});
|
|
49
|
+
if (commands.length + candidates.length > limit) {
|
|
50
|
+
throw new Error(`${surface} command catalog exceeds ${limit} slots`);
|
|
51
|
+
}
|
|
52
|
+
for (const command of candidates) {
|
|
53
|
+
const name = command.name;
|
|
54
|
+
const description = command.description.replace(/\s+/g, ' ').trim();
|
|
55
|
+
const suffix = ` (${surface === 'telegram' ? '텔레그램' : '디스코드'} 미지원)`;
|
|
56
|
+
commands.push({ name, description: `${description.slice(0, 100 - suffix.length)}${suffix}`, supported: false });
|
|
57
|
+
seen.add(name);
|
|
58
|
+
}
|
|
59
|
+
const unsupported = new Set(commands.filter((command) => !command.supported).map((command) => command.name));
|
|
60
|
+
return {
|
|
61
|
+
commands,
|
|
62
|
+
unsupportedReply(name) {
|
|
63
|
+
return unsupported.has(name) ? `/${name}은(는) ${surface === 'telegram' ? '텔레그램' : '디스코드'}에서 아직 지원되지 않습니다. TUI에서 /${name}을(를) 사용하세요.` : null;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|
|
@@ -41,6 +41,7 @@ export const FEATURE_MATURITY = {
|
|
|
41
41
|
'/design-check': { pwa: 'tool' },
|
|
42
42
|
'/control': { pwa: 'tool' },
|
|
43
43
|
'/scheduler': { pwa: 'ops' },
|
|
44
|
+
'/loops': { pwa: 'beta' },
|
|
44
45
|
'/ops/release': { pwa: 'ops' },
|
|
45
46
|
'/ops/seats': { pwa: 'ops' },
|
|
46
47
|
'/ops/checklist': { pwa: 'ops' },
|
|
@@ -57,7 +58,7 @@ export const FEATURE_MATURITY = {
|
|
|
57
58
|
pty: 'beta', pr: 'beta', repo: 'beta', role: 'beta', machine: 'beta',
|
|
58
59
|
where: 'stable', shadow: 'beta', llm: 'beta', storage: 'beta', leader: 'beta',
|
|
59
60
|
pod: 'beta', browser: 'beta', questions: 'beta', usage: 'stable', live: 'beta',
|
|
60
|
-
research: 'beta', release: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
|
|
61
|
+
research: 'beta', release: 'beta', freeze: 'beta', flow: 'beta', seat: 'beta', hq: 'beta', steward: 'beta', coord: 'beta', context: 'beta', 'model-watch': 'beta', doctor: 'stable',
|
|
61
62
|
control: 'beta', resources: 'beta', hooks: 'beta', setup: 'stable', start: 'stable',
|
|
62
63
|
grounding: 'beta', graph: 'beta', loop: 'stable', card: 'stable', 'launch-head': 'beta',
|
|
63
64
|
a2a: 'beta', plugin: 'stable', make: 'beta', skills: 'beta', connect: 'beta', import: 'beta',
|
|
@@ -71,7 +72,7 @@ export const FEATURE_MATURITY = {
|
|
|
71
72
|
provider: 'stable', 'provider:set': 'beta', 'provider:restore': 'beta',
|
|
72
73
|
'provider:rotate': 'beta', 'provider:use': 'beta', 'status-bar': 'beta',
|
|
73
74
|
schedule: 'beta', intake: 'beta', logs: 'stable', ad: 'beta', docs: 'beta',
|
|
74
|
-
decisions: 'beta', directives: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
|
|
75
|
+
decisions: 'beta', directives: 'beta', claims: 'beta', lesson: 'beta', fleet: 'beta', ops: 'stable', outputs: 'beta', publish: 'beta',
|
|
75
76
|
env: 'beta', 'agent-mission': 'beta', dev: 'beta', keys: 'beta', finance: 'beta',
|
|
76
77
|
config: 'stable', tasks: 'stable', labels: 'beta', msg: 'beta', ask: 'stable',
|
|
77
78
|
onboarding: 'beta', login: 'stable', session: 'beta', chat: 'stable', repl: 'stable',
|
|
@@ -110,6 +111,8 @@ export const FEATURE_MATURITY = {
|
|
|
110
111
|
plugin: 'tool',
|
|
111
112
|
widget: 'tool',
|
|
112
113
|
log: 'tool',
|
|
114
|
+
loops: 'beta',
|
|
115
|
+
persona: 'beta', // G3 b — 대화별 페르소나(GOAL-BENCH B7 #23836)
|
|
113
116
|
memory: 'beta',
|
|
114
117
|
export: 'stable',
|
|
115
118
|
delta: 'tool',
|
|
@@ -153,7 +156,7 @@ export const FEATURE_MATURITY = {
|
|
|
153
156
|
ping: 'stable', provider: 'stable', sessions: 'stable', fork: 'stable', resume: 'stable',
|
|
154
157
|
decisions: 'stable', work: 'stable', cancel: 'stable',
|
|
155
158
|
skills: 'beta', skill: 'beta', digest: 'beta', intake: 'beta', ad: 'beta',
|
|
156
|
-
taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', project: 'beta', wish: 'beta',
|
|
159
|
+
taste: 'beta', missions: 'beta', attach: 'beta', detach: 'beta', now: 'beta', loops: 'beta', project: 'beta', wish: 'beta',
|
|
157
160
|
brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool', local: 'tool',
|
|
158
161
|
harness: 'ops',
|
|
159
162
|
// Existing commands outside the CXO four-grade menu list are owner-only.
|
|
@@ -162,7 +165,7 @@ export const FEATURE_MATURITY = {
|
|
|
162
165
|
},
|
|
163
166
|
discordCommand: {
|
|
164
167
|
status: 'stable', sessions: 'stable', new: 'stable', fork: 'stable',
|
|
165
|
-
persona: 'beta', poll: 'beta', attach: 'beta',
|
|
168
|
+
persona: 'beta', poll: 'beta', attach: 'beta', now: 'beta',
|
|
166
169
|
brain: 'tool', cc: 'tool', cdx: 'tool', gem: 'tool',
|
|
167
170
|
relay: 'ops', showroom: 'ops', bots: 'ops', bot: 'ops', screen: 'ops', chart: 'ops',
|
|
168
171
|
routines: 'ops', botsay: 'ops', 'voice-join': 'ops', 'voice-leave': 'ops', 'voice-status': 'ops',
|
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
// GET /v1/dashboard/timeline — 신호 타임라인 (?hours=48&floor=6)
|
|
5
5
|
// GET /v1/dashboard/heatmap — 자산×국가×섹터 매력도 + US 섹터 + 뉴스 분포
|
|
6
6
|
// GET /v1/dashboard/digs — 디깅 리포트 피드 (?limit=20)
|
|
7
|
+
// GET /v1/dashboard/loop-activity — 루프 활동 그래프 (?since=24h)
|
|
7
8
|
//
|
|
8
9
|
// 전부 read-only · 캐시/로컬 SQLite 만 (dashboard-data.ts) — 라이브 fetch 없음.
|
|
9
10
|
// PWA 폴링(60s) 대상. reflection.ts 패턴(CORS·checkAuth seam) 상속.
|
|
@@ -16,12 +17,15 @@ import {
|
|
|
16
17
|
} from '../../domains/dashboard-data.js';
|
|
17
18
|
import { readLiveSnapshot } from '../../domains/market-live.js';
|
|
18
19
|
import { marketSessions } from '../../domains/finance.js';
|
|
20
|
+
import { loopActivity } from '../../loops/activity.js';
|
|
19
21
|
|
|
20
22
|
export interface DashboardRouteOpts {
|
|
21
23
|
/** Auth check seam. */
|
|
22
24
|
checkAuth?: (req: Request) => boolean;
|
|
23
25
|
/** Test seam — 실제 detached 수집 spawn 대체. */
|
|
24
26
|
spawnCollector?: () => void;
|
|
27
|
+
/** Read-only activity provider for isolated route tests. */
|
|
28
|
+
loopActivity?: typeof loopActivity;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
const CORS_HEADERS: Record<string, string> = {
|
|
@@ -43,7 +47,7 @@ export function parseDashboardPath(pathname: string): string | null {
|
|
|
43
47
|
const m = /^\/v1\/dashboard\/([^/]+)$/.exec(pathname);
|
|
44
48
|
if (!m) return null;
|
|
45
49
|
const seg = decodeURIComponent(m[1]!);
|
|
46
|
-
return ['summary', 'timeline', 'heatmap', 'digs', 'schedules', 'ontology', 'backtest', 'loops', 'ops'].includes(seg) ? seg : null;
|
|
50
|
+
return ['summary', 'timeline', 'heatmap', 'digs', 'schedules', 'ontology', 'backtest', 'loops', 'loop-activity', 'ops'].includes(seg) ? seg : null;
|
|
47
51
|
}
|
|
48
52
|
|
|
49
53
|
/** POST /v1/dashboard/refresh-live — 온디맨드 라이브 재수집 트리거 (refresh 버튼 설계 ·
|
|
@@ -99,6 +103,7 @@ export function handleDashboard(req: Request, section: string, opts: DashboardRo
|
|
|
99
103
|
case 'ontology': return jsonResponse({ ok: true, ontology: dashboardOntology() }, 200);
|
|
100
104
|
case 'backtest': return jsonResponse({ ok: true, backtest: dashboardBacktest() }, 200);
|
|
101
105
|
case 'loops': return jsonResponse({ ok: true, loops: dashboardLoops() }, 200);
|
|
106
|
+
case 'loop-activity': return jsonResponse({ ok: true, activity: (opts.loopActivity ?? loopActivity)({ since: q.get('since') ?? '24h' }) }, 200);
|
|
102
107
|
case 'ops': {
|
|
103
108
|
const mid = q.get('mission');
|
|
104
109
|
if (mid) return jsonResponse({ ok: true, missionDetail: dashboardOpsMission(mid) }, 200);
|
|
@@ -184,7 +184,9 @@ import { handleChannelBotsGet, handleChannelBotSet } from './setup-channel-bot.j
|
|
|
184
184
|
import { handleLlmRoutePredict } from './llm-route-predict.js';
|
|
185
185
|
import type { MissionRouter } from '../../llm/mission-router.js';
|
|
186
186
|
import { handleContextFetchUrl } from './context-url.js';
|
|
187
|
-
import { contextNow } from '../../context-bus/context-now.js';
|
|
187
|
+
import { contextNow, type ContextNowDeps } from '../../context-bus/context-now.js';
|
|
188
|
+
import type { ContextNowAudience } from '../../context-bus/context-now-surfaces.js';
|
|
189
|
+
import { renderVoiceNow } from '../../context-bus/context-now-surfaces.js';
|
|
188
190
|
import type { IntentPredictionService } from '../../intent-prediction/index.js';
|
|
189
191
|
import type { NotificationActionLoopback } from '../../web-push/notification-action-loopback.js';
|
|
190
192
|
import { handlePlatforms } from './platforms.js';
|
|
@@ -486,6 +488,8 @@ export interface NexusHttpServerOpts {
|
|
|
486
488
|
eventBus: NexusEventBus;
|
|
487
489
|
/** Test seam for the read-only usage collector; production uses the cached handler. */
|
|
488
490
|
usageHandler?: () => Promise<Response>;
|
|
491
|
+
/** Test seam for the context-now ledgers; production reads the live ledgers. */
|
|
492
|
+
contextNowDeps?: ContextNowDeps;
|
|
489
493
|
/** Supervisor handle — required for the write API (PR ι). When omitted
|
|
490
494
|
* (skipSupervisor mode), mutation routes return 503. */
|
|
491
495
|
supervisor?: Supervisor;
|
|
@@ -2879,7 +2883,19 @@ export async function routeRequest(
|
|
|
2879
2883
|
}
|
|
2880
2884
|
if (pathname === '/v1/context/now') {
|
|
2881
2885
|
if (!opts.metaApi || !checkAuth(req, opts.metaApi)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
2882
|
-
|
|
2886
|
+
const audiences = url.searchParams.getAll('audience');
|
|
2887
|
+
if (audiences.length > 1) return jsonResponse({ error: 'invalid_audience' }, 400);
|
|
2888
|
+
const requested = audiences[0];
|
|
2889
|
+
if (requested !== undefined && requested !== 'operator' && requested !== 'user' && requested !== 'public-demo') {
|
|
2890
|
+
return jsonResponse({ error: 'invalid_audience' }, 400);
|
|
2891
|
+
}
|
|
2892
|
+
const forced = getUserConfig().nexus?.demoMode === true;
|
|
2893
|
+
const audience: ContextNowAudience = forced ? 'public-demo' : requested ?? 'operator';
|
|
2894
|
+
const answer = contextNow({ audience, ...(url.searchParams.has('topic') ? { topic: url.searchParams.get('topic') ?? '' } : {}) }, opts.contextNowDeps);
|
|
2895
|
+
debug.log('context.now', 'served', { audience, hidden: answer.hiddenCount ?? 0, forced });
|
|
2896
|
+
return jsonResponse(url.searchParams.get('format') === 'voice'
|
|
2897
|
+
? { text: renderVoiceNow(answer, audience), audience, hiddenCount: answer.hiddenCount ?? 0 }
|
|
2898
|
+
: { ...answer, audience, hiddenCount: answer.hiddenCount ?? 0 });
|
|
2883
2899
|
}
|
|
2884
2900
|
// §6.3 (2026-05-09) — context URL fetch (showroom).
|
|
2885
2901
|
// POST /v1/context/fetch-url
|
|
@@ -53,7 +53,7 @@ export function startApprovalGate(input: ApprovalGateInput, deps: ApprovalGateDe
|
|
|
53
53
|
void Promise.resolve().then(() => (deps.runHostRegate ?? runHostRegate)({
|
|
54
54
|
prNumber: input.prNumber, headCommit: input.headSha, repoRoot: input.repoRoot, verifyOnly: true,
|
|
55
55
|
})).then(async (result) => {
|
|
56
|
-
let status = result.status ?? (result.passed ? 'passed' : 'failed');
|
|
56
|
+
let status: ApprovalGateStatus = result.status === 'frozen' ? 'unmeasured' : result.status ?? (result.passed ? 'passed' : 'failed');
|
|
57
57
|
let failures = result.failures.map(({ step, detail }) => `${step} — ${detail.replace(/\s+/g, ' ')}`.slice(0, 800));
|
|
58
58
|
if (status === 'passed') {
|
|
59
59
|
const current = await (deps.readPr ?? readPr)(input);
|
|
@@ -193,9 +193,13 @@ function inventory(deps: SchedulesReadDeps): { cards: ScheduleCard[]; names: Map
|
|
|
193
193
|
return { cards, names };
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
export function readSchedulesInventory(deps: SchedulesReadDeps = {}): ScheduleCard[] {
|
|
197
|
+
return inventory(deps).cards;
|
|
198
|
+
}
|
|
199
|
+
|
|
196
200
|
export function handleSchedulesList(req: Request, opts: MetaApiOpts, deps: SchedulesReadDeps = {}): Response {
|
|
197
201
|
if (!checkAuth(req, opts)) return jsonResponse({ error: 'unauthorized' }, 401);
|
|
198
|
-
const
|
|
202
|
+
const cards = readSchedulesInventory(deps);
|
|
199
203
|
const schedules = new URL(req.url).searchParams.get('includeOff') === '1' ? cards : cards.filter(c => c.state !== 'off');
|
|
200
204
|
return jsonResponse({ schedules, count: schedules.length }, 200);
|
|
201
205
|
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { CardStore, type TaskCard } from '../../task-cards/card-store.js';
|
|
5
|
+
import { releaseLedgerRoot } from '../../instance/resolve.js';
|
|
2
6
|
import { createWishCard } from '../../intake-plane/wish-card.js';
|
|
3
7
|
import { jsonResponse } from './json-response.js';
|
|
4
8
|
|
|
@@ -7,19 +11,53 @@ export async function handleTaskCardsWishPost(req: Request, root?: string): Prom
|
|
|
7
11
|
try { body = await req.json(); }
|
|
8
12
|
catch { return jsonResponse({ error: 'bad_request' }, 400); }
|
|
9
13
|
if (!body || typeof body !== 'object' || Array.isArray(body)) return jsonResponse({ error: 'bad_request' }, 400);
|
|
10
|
-
const { text, ref } = body as Record<string, unknown>;
|
|
11
|
-
if (typeof text !== 'string' || !text.trim() || typeof ref !== 'string' || !ref.trim()
|
|
14
|
+
const { text, ref, sessionId } = body as Record<string, unknown>;
|
|
15
|
+
if (typeof text !== 'string' || !text.trim() || typeof ref !== 'string' || !ref.trim()
|
|
16
|
+
|| typeof sessionId !== 'string' || !sessionId.trim()) {
|
|
12
17
|
return jsonResponse({ error: 'bad_request' }, 400);
|
|
13
18
|
}
|
|
14
19
|
const store = new CardStore(root);
|
|
15
20
|
try {
|
|
16
|
-
const card = createWishCard({ text, source: 'pwa', ref }, store);
|
|
21
|
+
const card = createWishCard({ text, source: 'pwa', ref, replyTo: { surface: 'pwa', sessionId } }, store);
|
|
17
22
|
return jsonResponse(card, card.created ? 201 : 200);
|
|
18
23
|
} finally {
|
|
19
24
|
store.close();
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
|
|
28
|
+
export interface WishPlacement { cellId: string; cellTitle: string; version: string; status: 'green' | 'yellow' | 'red' | 'done' }
|
|
29
|
+
|
|
30
|
+
export function isSplitCell(cell: unknown): cell is { id: string } {
|
|
31
|
+
return cell !== null && typeof cell === 'object' && 'id' in cell
|
|
32
|
+
&& typeof cell.id === 'string' && cell.id.trim().length > 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** Join the live checklist to the wish card by the split cell ID, never by title or card status. */
|
|
36
|
+
export function wishPlacements(card: TaskCard, root?: string): WishPlacement[] {
|
|
37
|
+
if (!card.goalId.startsWith('wish:')) return [];
|
|
38
|
+
const split = [...card.sections].reverse().find(section => section.key === 'orch:split');
|
|
39
|
+
if (!split) return [];
|
|
40
|
+
let cells: unknown;
|
|
41
|
+
try { cells = JSON.parse(split.content); } catch { return []; }
|
|
42
|
+
if (!Array.isArray(cells)) return [];
|
|
43
|
+
const ids = [...new Set(cells.filter(isSplitCell).map(cell => cell.id.trim()))];
|
|
44
|
+
if (!ids.length) return [];
|
|
45
|
+
const ledgerRoot = root ?? releaseLedgerRoot();
|
|
46
|
+
const path = join(ledgerRoot, 'release', 'features.sqlite');
|
|
47
|
+
if (!existsSync(path)) return [];
|
|
48
|
+
const db = new Database(path, { readonly: true, strict: true });
|
|
49
|
+
try {
|
|
50
|
+
const placements: WishPlacement[] = [];
|
|
51
|
+
for (const cellId of ids) {
|
|
52
|
+
const rows = db.query('SELECT a.version, a.status, COALESCE(a.title_override, f.title) AS title FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.feature_id = ? ORDER BY a.version').all(cellId) as Array<{ version: string; status: string; title: string }>;
|
|
53
|
+
for (const row of rows) if (['green', 'yellow', 'red', 'done'].includes(row.status)) {
|
|
54
|
+
placements.push({ cellId, cellTitle: row.title, version: row.version, status: row.status as WishPlacement['status'] });
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return placements;
|
|
58
|
+
} finally { db.close(); }
|
|
59
|
+
}
|
|
60
|
+
|
|
23
61
|
/** Read-only card API; route registration and authorization belong to the HTTP server. */
|
|
24
62
|
export function handleTaskCardsGet(pathname: string, root?: string): Response {
|
|
25
63
|
if (pathname !== '/v1/task-cards' && !/^\/v1\/task-cards\/[^/]+$/.test(pathname)) {
|
|
@@ -46,7 +84,8 @@ export function handleTaskCardsGet(pathname: string, root?: string): Response {
|
|
|
46
84
|
const store = new CardStore(root);
|
|
47
85
|
try {
|
|
48
86
|
const card = store.getCard(id);
|
|
49
|
-
return card ? jsonResponse({ card
|
|
87
|
+
return card ? jsonResponse({ card, ...(card.goalId.startsWith('wish:') ? { placements: wishPlacements(card, root) } : {}) })
|
|
88
|
+
: jsonResponse({ error: 'not_found' }, 404);
|
|
50
89
|
} finally {
|
|
51
90
|
store.close();
|
|
52
91
|
}
|
|
@@ -302,6 +302,7 @@ export async function handleWorkflowPut(
|
|
|
302
302
|
}
|
|
303
303
|
const r = saveWorkflow(name, yamlText, { scope });
|
|
304
304
|
if (!r.ok) {
|
|
305
|
+
if (r.readonly) return jsonResponse({ error: 'readonly_workflow', reason: r.error }, 409);
|
|
305
306
|
return jsonResponse(
|
|
306
307
|
{
|
|
307
308
|
error: 'invalid_workflow',
|
|
@@ -323,7 +324,7 @@ export function handleWorkflowDelete(
|
|
|
323
324
|
const url = new URL(req.url);
|
|
324
325
|
const scope = url.searchParams.get('scope') === 'global' ? 'global' : 'project';
|
|
325
326
|
const r = deleteWorkflow(name, { scope });
|
|
326
|
-
if (!r.ok) return jsonResponse({ error: 'delete_failed', reason: r.error }, 404);
|
|
327
|
+
if (!r.ok) return jsonResponse({ error: r.readonly ? 'readonly_workflow' : 'delete_failed', reason: r.error }, r.readonly ? 409 : 404);
|
|
327
328
|
return jsonResponse({ ok: true, path: r.path, scope }, 200);
|
|
328
329
|
}
|
|
329
330
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { releaseLedgerRoot } from '../instance/resolve.js';
|
|
2
|
+
import { releaseReadiness, type ReleaseReadiness } from '../../scripts/release-loop/release-readiness.js';
|
|
3
|
+
import { listSchedules, type ReleaseSchedule } from './release-schedule.js';
|
|
4
|
+
|
|
5
|
+
/** Cuts become eligible at cutAt and remain eligible through the end of the window. */
|
|
6
|
+
export function selectScheduledCut(schedules: readonly ReleaseSchedule[], now: Date, windowMinutes: number): ReleaseSchedule | null {
|
|
7
|
+
if (!Number.isFinite(windowMinutes) || windowMinutes <= 0) throw new Error('windowMinutes must be positive and finite');
|
|
8
|
+
const current = now.getTime();
|
|
9
|
+
if (!Number.isFinite(current)) throw new Error('now must be a valid date');
|
|
10
|
+
return schedules
|
|
11
|
+
.filter((schedule) => {
|
|
12
|
+
const cut = Date.parse(schedule.cutAt);
|
|
13
|
+
return Number.isFinite(cut) && cut <= current && current - cut <= windowMinutes * 60_000;
|
|
14
|
+
})
|
|
15
|
+
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt) || a.version.localeCompare(b.version))[0] ?? null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type AutoStartResult =
|
|
19
|
+
| { status: 'no-cut' }
|
|
20
|
+
| { status: 'planned'; version: string; cutAt: string }
|
|
21
|
+
| { status: 'skipped'; version: string; reason: Exclude<ReleaseReadiness, { ready: true }>['reason'] }
|
|
22
|
+
| { status: 'started'; version: string; cutAt: string };
|
|
23
|
+
|
|
24
|
+
export interface AutoStartOptions {
|
|
25
|
+
windowMinutes: number;
|
|
26
|
+
apply?: boolean;
|
|
27
|
+
now?: Date;
|
|
28
|
+
ledgerRoot?: string;
|
|
29
|
+
schedules?: (root: string) => ReleaseSchedule[];
|
|
30
|
+
readiness?: (version: string, ledgerRoot: string, now: Date) => ReleaseReadiness;
|
|
31
|
+
launch?: (version: string) => void | Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Select from the release schedule; the existing `release run --if-ready` owns the launch-time lock and guards. */
|
|
35
|
+
export async function autoStartScheduledRelease(options: AutoStartOptions): Promise<AutoStartResult> {
|
|
36
|
+
const root = options.ledgerRoot ?? releaseLedgerRoot();
|
|
37
|
+
const now = options.now ?? new Date();
|
|
38
|
+
// Validate the window even when the ledger has no rows.
|
|
39
|
+
const schedule = selectScheduledCut((options.schedules ?? listSchedules)(root), now, options.windowMinutes);
|
|
40
|
+
if (!schedule) return { status: 'no-cut' };
|
|
41
|
+
const { version, cutAt } = schedule;
|
|
42
|
+
if (!options.apply) return { status: 'planned', version, cutAt };
|
|
43
|
+
const readiness = (options.readiness ?? ((v, ledgerRoot, at) => releaseReadiness(v, { ledgerRoot, now: () => at })))(version, root, now);
|
|
44
|
+
if (!readiness.ready) return { status: 'skipped', version, reason: readiness.reason };
|
|
45
|
+
if (!options.launch) throw new Error('auto-start apply requires a launch callback');
|
|
46
|
+
await options.launch(version);
|
|
47
|
+
return { status: 'started', version, cutAt };
|
|
48
|
+
}
|
|
@@ -13,6 +13,9 @@ export interface ChecklistItem {
|
|
|
13
13
|
title: string;
|
|
14
14
|
status: ChecklistStatus;
|
|
15
15
|
owner?: string;
|
|
16
|
+
priority?: 'P0' | 'P1' | 'P2';
|
|
17
|
+
predecessors?: string[];
|
|
18
|
+
deadlineVersion?: string;
|
|
16
19
|
evidence?: string;
|
|
17
20
|
disposition?: ChecklistDisposition;
|
|
18
21
|
kind?: ChecklistKind;
|
|
@@ -93,11 +96,14 @@ function change(data: Checklist, id: string, field: string, from: unknown, to: u
|
|
|
93
96
|
debug.log('release-loop.checklist', 'change', { version: data.version, id, field, from: from ?? null, to: to ?? null, by });
|
|
94
97
|
}
|
|
95
98
|
|
|
96
|
-
export function addItem(v: string, input: { id: string; title: string; owner?: string; kind?: ChecklistKind }, options: { allowDuplicateId?: boolean } = {}): Checklist {
|
|
99
|
+
export function addItem(v: string, input: { id: string; title: string; owner?: string; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string }, options: { allowDuplicateId?: boolean } = {}): Checklist {
|
|
97
100
|
if (!input.id.trim()) throw new CliUserError('칸 id 가 비었다');
|
|
98
101
|
if (!input.title.trim()) throw new CliUserError('칸 제목이 비었다');
|
|
99
102
|
if (input.kind !== undefined && input.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${input.kind}`, 'screen');
|
|
100
103
|
if (input.owner !== undefined) parseOwner(input.owner);
|
|
104
|
+
if (input.priority !== undefined && !['P0', 'P1', 'P2'].includes(input.priority)) throw new CliUserError(`잘못된 우선순위: ${input.priority}`);
|
|
105
|
+
if (input.predecessors !== undefined && (!Array.isArray(input.predecessors) || input.predecessors.some((p) => typeof p !== 'string' || !p.trim() || p === input.id))) throw new CliUserError('잘못된 선행 칸');
|
|
106
|
+
if (input.deadlineVersion !== undefined) store.validateVersion(input.deadlineVersion);
|
|
101
107
|
return mutate(v, (data, otherItems) => {
|
|
102
108
|
if (data.items.some((item) => item.id === input.id)) throw new CliUserError(`이미 있는 칸: ${input.id}`, 'set <id> 로 고친다');
|
|
103
109
|
const collisions = otherItems(input.id);
|
|
@@ -108,27 +114,30 @@ export function addItem(v: string, input: { id: string; title: string; owner?: s
|
|
|
108
114
|
}
|
|
109
115
|
const by = process.env.ELANOUS_TRACK || 'cli';
|
|
110
116
|
const at = new Date().toISOString();
|
|
111
|
-
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), updatedAt: at, updatedBy: by };
|
|
117
|
+
const item: ChecklistItem = { id: input.id, title: input.title, status: 'yellow', ...(input.owner !== undefined ? { owner: input.owner } : {}), ...(input.kind !== undefined ? { kind: input.kind } : {}), ...(input.priority !== undefined ? { priority: input.priority } : {}), ...(input.predecessors !== undefined ? { predecessors: input.predecessors } : {}), ...(input.deadlineVersion !== undefined ? { deadlineVersion: input.deadlineVersion } : {}), updatedAt: at, updatedBy: by };
|
|
112
118
|
data.items.push(item);
|
|
113
119
|
change(data, item.id, 'add', null, item, by, at);
|
|
114
120
|
return true;
|
|
115
121
|
});
|
|
116
122
|
}
|
|
117
123
|
|
|
118
|
-
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind }, by: string): Checklist {
|
|
124
|
+
export function setItem(v: string, id: string, patch: { status?: ChecklistStatus; evidence?: string; owner?: string; disposition?: ChecklistDisposition; kind?: ChecklistKind; priority?: ChecklistItem['priority']; predecessors?: string[]; deadlineVersion?: string }, by: string): Checklist {
|
|
119
125
|
return mutate(v, (data) => {
|
|
120
126
|
const item = data.items.find((i) => i.id === id);
|
|
121
127
|
if (!item) throw new CliUserError(`없는 칸: ${id}`, 'list 로 칸 목록을 본다');
|
|
122
128
|
if (patch.status !== undefined && !['green', 'yellow', 'red', 'done'].includes(patch.status)) throw new CliUserError(`잘못된 상태: ${patch.status}`);
|
|
123
129
|
if (patch.disposition !== undefined && !['move', 'known-issue', 'block'].includes(patch.disposition)) throw new CliUserError(`잘못된 처분: ${patch.disposition}`);
|
|
124
130
|
if (patch.kind !== undefined && patch.kind !== 'screen') throw new CliUserError(`잘못된 종류: ${patch.kind}`, 'screen');
|
|
131
|
+
if (patch.priority !== undefined && !['P0', 'P1', 'P2'].includes(patch.priority)) throw new CliUserError(`잘못된 우선순위: ${patch.priority}`);
|
|
132
|
+
if (patch.predecessors !== undefined && (!Array.isArray(patch.predecessors) || patch.predecessors.some((p) => typeof p !== 'string' || !p.trim() || p === id))) throw new CliUserError('잘못된 선행 칸');
|
|
133
|
+
if (patch.deadlineVersion !== undefined) store.validateVersion(patch.deadlineVersion);
|
|
125
134
|
if (patch.owner !== undefined) {
|
|
126
135
|
parseOwner(patch.owner);
|
|
127
136
|
if (item.owner && patch.owner !== item.owner) {
|
|
128
137
|
throw new CliUserError(`지금 주인: ${item.owner} — --force 로만 바꾼다`, 'release checklist claim <id> --by <자리> --force');
|
|
129
138
|
}
|
|
130
139
|
}
|
|
131
|
-
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind'] as const).filter((field) => patch[field] !== undefined && patch[field] !== item[field]);
|
|
140
|
+
const fields = (['evidence', 'owner', 'status', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion'] as const).filter((field) => patch[field] !== undefined && JSON.stringify(patch[field]) !== JSON.stringify(item[field]));
|
|
132
141
|
if (fields.length === 0) return false;
|
|
133
142
|
const at = new Date().toISOString();
|
|
134
143
|
for (const field of fields) {
|
|
@@ -58,14 +58,20 @@ function open(root = releaseLedgerRoot()): Database {
|
|
|
58
58
|
if (!columns.some((column) => column.name === 'imported_at')) db.exec('ALTER TABLE imported_versions ADD COLUMN imported_at TEXT');
|
|
59
59
|
if (!(db.query('PRAGMA table_info(events)').all() as Array<{ name: string }>).some((column) => column.name === 'reason')) db.exec('ALTER TABLE events ADD COLUMN reason TEXT');
|
|
60
60
|
db.exec('CREATE TABLE IF NOT EXISTS release_schedules (version TEXT PRIMARY KEY, cut_at TEXT NOT NULL, land_by TEXT, updated_at TEXT NOT NULL, updated_by TEXT NOT NULL)');
|
|
61
|
+
const assignmentColumns = db.query('PRAGMA table_info(assignments)').all() as Array<{ name: string }>;
|
|
62
|
+
for (const [name, sql] of [['priority', 'TEXT'], ['predecessors', 'TEXT'], ['deadline_version', 'TEXT']] as const) {
|
|
63
|
+
if (!assignmentColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE assignments ADD COLUMN ${name} ${sql}`);
|
|
64
|
+
}
|
|
65
|
+
const scheduleColumns = db.query('PRAGMA table_info(release_schedules)').all() as Array<{ name: string }>;
|
|
66
|
+
for (const name of ['freeze_from', 'freeze_until']) if (!scheduleColumns.some((column) => column.name === name)) db.exec(`ALTER TABLE release_schedules ADD COLUMN ${name} TEXT`);
|
|
61
67
|
return db;
|
|
62
68
|
} catch (error) { db.close(); throw error; }
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; updatedAt: string; updatedBy: string }
|
|
66
|
-
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; updated_at: string; updated_by: string };
|
|
71
|
+
export interface ScheduleRow { version: string; cutAt: string; landBy: string | null; freezeFrom?: string | null; freezeUntil?: string | null; updatedAt: string; updatedBy: string }
|
|
72
|
+
type StoredSchedule = { version: string; cut_at: string; land_by: string | null; freeze_from: string | null; freeze_until: string | null; updated_at: string; updated_by: string };
|
|
67
73
|
function scheduleRow(row: StoredSchedule): ScheduleRow {
|
|
68
|
-
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
74
|
+
return { version: row.version, cutAt: row.cut_at, landBy: row.land_by, ...(row.freeze_from ? { freezeFrom: row.freeze_from } : {}), ...(row.freeze_until ? { freezeUntil: row.freeze_until } : {}), updatedAt: row.updated_at, updatedBy: row.updated_by };
|
|
69
75
|
}
|
|
70
76
|
|
|
71
77
|
export function readSchedule(version: string, root?: string): ScheduleRow | null {
|
|
@@ -84,7 +90,7 @@ export function readSchedules(root?: string): ScheduleRow[] {
|
|
|
84
90
|
finally { db.close(); }
|
|
85
91
|
}
|
|
86
92
|
|
|
87
|
-
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string }, by: string, root?: string): ScheduleRow {
|
|
93
|
+
export function writeSchedule(version: string, patch: { cutAt?: string; landBy?: string; freezeFrom?: string; freezeUntil?: string }, by: string, root?: string): ScheduleRow {
|
|
88
94
|
validateVersion(version);
|
|
89
95
|
const db = open(root);
|
|
90
96
|
try {
|
|
@@ -93,13 +99,17 @@ export function writeSchedule(version: string, patch: { cutAt?: string; landBy?:
|
|
|
93
99
|
const cutAt = patch.cutAt ?? previous?.cut_at;
|
|
94
100
|
if (!cutAt) throw new CliUserError('새 판에는 --cut-at 이 필요하다');
|
|
95
101
|
const landBy = patch.landBy ?? previous?.land_by ?? null;
|
|
96
|
-
|
|
102
|
+
const freezeFrom = patch.freezeFrom ?? previous?.freeze_from ?? null;
|
|
103
|
+
const freezeUntil = patch.freezeUntil ?? previous?.freeze_until ?? null;
|
|
104
|
+
if (Boolean(freezeFrom) !== Boolean(freezeUntil) || (freezeFrom && freezeUntil && Date.parse(freezeFrom) >= Date.parse(freezeUntil))) throw new CliUserError('동결 시작·끝은 함께 주고 시작이 끝보다 앞서야 한다');
|
|
105
|
+
if (previous && previous.cut_at === cutAt && previous.land_by === landBy && previous.freeze_from === freezeFrom && previous.freeze_until === freezeUntil) return scheduleRow(previous);
|
|
97
106
|
const at = new Date().toISOString();
|
|
98
|
-
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, updated_at, updated_by) VALUES (?, ?, ?, ?, ?)
|
|
99
|
-
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
100
|
-
.run(version, cutAt, landBy, at, by);
|
|
107
|
+
db.query(`INSERT INTO release_schedules (version, cut_at, land_by, freeze_from, freeze_until, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?)
|
|
108
|
+
ON CONFLICT(version) DO UPDATE SET cut_at=excluded.cut_at, land_by=excluded.land_by, freeze_from=excluded.freeze_from, freeze_until=excluded.freeze_until, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
109
|
+
.run(version, cutAt, landBy, freezeFrom, freezeUntil, at, by);
|
|
101
110
|
for (const [field, from, to] of [
|
|
102
111
|
['cut_at', previous?.cut_at ?? null, cutAt], ['land_by', previous?.land_by ?? null, landBy],
|
|
112
|
+
['freeze_from', previous?.freeze_from ?? null, freezeFrom], ['freeze_until', previous?.freeze_until ?? null, freezeUntil],
|
|
103
113
|
] as const) {
|
|
104
114
|
if (from !== to) record(db, version, { at, by, id: '@version', field, from, to, released: releasedVersion(root), dev: devVersion() });
|
|
105
115
|
}
|
|
@@ -134,12 +144,14 @@ function evidenceRefs(db: Database, version: string, id: string): string[] {
|
|
|
134
144
|
return (db.query('SELECT ref FROM evidence WHERE feature_id = ? AND version = ? ORDER BY rowid').all(id, version) as Array<{ ref: string }>).map((row) => row.ref);
|
|
135
145
|
}
|
|
136
146
|
function items(db: Database, version: string): ChecklistItem[] {
|
|
137
|
-
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
147
|
+
const rows = db.query(`SELECT f.id, COALESCE(a.title_override, f.title) AS title, a.owner, a.kind, a.priority, a.predecessors, a.deadline_version, a.status, a.disposition, a.evidence, a.updated_at, a.updated_by
|
|
138
148
|
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.version = ? ORDER BY a.rowid`).all(version) as Array<Record<string, string | null>>;
|
|
139
149
|
return rows.map((r) => {
|
|
140
150
|
const evidence = [r.evidence, ...evidenceRefs(db, version, r.id!)].filter((value) => value !== null).join('\n');
|
|
141
151
|
return { id: r.id!, title: r.title!, status: r.status as ChecklistItem['status'],
|
|
142
152
|
...(r.owner !== null ? { owner: r.owner! } : {}), ...(r.kind !== null ? { kind: r.kind as ChecklistItem['kind'] } : {}),
|
|
153
|
+
...(r.priority !== null ? { priority: r.priority as ChecklistItem['priority'] } : {}), ...(r.predecessors !== null ? { predecessors: JSON.parse(r.predecessors!) as string[] } : {}),
|
|
154
|
+
...(r.deadline_version !== null ? { deadlineVersion: r.deadline_version! } : {}),
|
|
143
155
|
...(r.evidence !== null || evidence ? { evidence } : {}), ...(r.disposition !== null ? { disposition: r.disposition as ChecklistItem['disposition'] } : {}),
|
|
144
156
|
updatedAt: r.updated_at!, updatedBy: r.updated_by! };
|
|
145
157
|
});
|
|
@@ -153,9 +165,9 @@ function putItem(db: Database, version: string, item: ChecklistItem): void {
|
|
|
153
165
|
if (!feature) db.query('INSERT INTO features (id, title, owner, kind, created_at) VALUES (?, ?, ?, ?, ?)').run(item.id, item.title, item.owner ?? null, item.kind ?? null, item.updatedAt);
|
|
154
166
|
// features.owner/kind follow the latest assignment so details() and the checklist never disagree after a set.
|
|
155
167
|
else if (item.owner !== undefined || item.kind !== undefined) db.query('UPDATE features SET owner = COALESCE(?, owner), kind = COALESCE(?, kind) WHERE id = ?').run(item.owner ?? null, item.kind ?? null, item.id);
|
|
156
|
-
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
157
|
-
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
158
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.updatedAt, item.updatedBy);
|
|
168
|
+
db.query(`INSERT INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
169
|
+
ON CONFLICT(feature_id, version) DO UPDATE SET status=excluded.status, disposition=excluded.disposition, evidence=excluded.evidence, owner=excluded.owner, kind=excluded.kind, priority=excluded.priority, predecessors=excluded.predecessors, deadline_version=excluded.deadline_version, updated_at=excluded.updated_at, updated_by=excluded.updated_by`)
|
|
170
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, feature && feature.title !== item.title ? item.title : null, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.updatedAt, item.updatedBy);
|
|
159
171
|
}
|
|
160
172
|
function jsonHash(contents: string | Buffer): string { return createHash('sha256').update(contents).digest('hex'); }
|
|
161
173
|
|
|
@@ -196,8 +208,8 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
196
208
|
// Keep a legacy per-version title until an explicit retitle unifies the feature identity.
|
|
197
209
|
if (!existing) putItem(db, version, item);
|
|
198
210
|
else {
|
|
199
|
-
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
200
|
-
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.updatedAt, item.updatedBy);
|
|
211
|
+
db.query('INSERT OR IGNORE INTO assignments (feature_id, version, status, disposition, evidence, title_override, owner, kind, priority, predecessors, deadline_version, updated_at, updated_by) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)')
|
|
212
|
+
.run(item.id, version, item.status, item.disposition ?? null, item.evidence ?? null, item.title === existing.title ? null : item.title, item.owner ?? null, item.kind ?? null, item.priority ?? null, item.predecessors ? JSON.stringify(item.predecessors) : null, item.deadlineVersion ?? null, item.updatedAt, item.updatedBy);
|
|
201
213
|
}
|
|
202
214
|
}
|
|
203
215
|
for (const entry of data.history) record(db, version, entry);
|
|
@@ -224,12 +236,12 @@ function importOnDb(db: Database, version: string): boolean {
|
|
|
224
236
|
record(db, version, { at: new Date().toISOString(), by: item.updatedBy, id: item.id, field: 'reimport', from: null, to: item, released: data.released, dev: data.dev });
|
|
225
237
|
continue;
|
|
226
238
|
}
|
|
227
|
-
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind'] as const;
|
|
239
|
+
const fields = ['title', 'status', 'evidence', 'owner', 'disposition', 'kind', 'priority', 'predecessors', 'deadlineVersion'] as const;
|
|
228
240
|
const baseEvidence = (db.query('SELECT evidence FROM assignments WHERE feature_id = ? AND version = ?').get(item.id, version) as { evidence: string | null }).evidence ?? undefined;
|
|
229
241
|
const preserveRefs = item.evidence === baseEvidence || item.evidence === current.evidence;
|
|
230
242
|
const differs = fields.some((field) => field === 'evidence'
|
|
231
243
|
? (!preserveRefs && current.evidence !== item.evidence)
|
|
232
|
-
: current[field] !== item[field]);
|
|
244
|
+
: JSON.stringify(current[field]) !== JSON.stringify(item[field]));
|
|
233
245
|
if (!differs) continue;
|
|
234
246
|
if (!(Date.parse(item.updatedAt) > Date.parse(current.updatedAt))) {
|
|
235
247
|
ledgerWonIds.push(item.id);
|