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
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
5
|
+
import { LogStore, type LogStoreRow } from '../mss/logging/log-store.js';
|
|
6
|
+
import { checkLoops } from './checker.js';
|
|
7
|
+
import { defaultLoopRoot, listAllLoops, type AllLoopEntry } from './registry.js';
|
|
8
|
+
import type { ScheduleRow } from '../domains/schedule-registry.js';
|
|
9
|
+
|
|
10
|
+
export interface ActivityNode {
|
|
11
|
+
id: string;
|
|
12
|
+
kind: AllLoopEntry['kind'] | 'seat' | 'card' | 'event';
|
|
13
|
+
title: string;
|
|
14
|
+
state: string | null;
|
|
15
|
+
reason: string | null;
|
|
16
|
+
lastAt: string | null;
|
|
17
|
+
events: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ActivityEdge {
|
|
20
|
+
from: string;
|
|
21
|
+
to: string;
|
|
22
|
+
kind: 'delegation' | 'request' | 'reply' | 'card';
|
|
23
|
+
count: number;
|
|
24
|
+
lastAt: string;
|
|
25
|
+
}
|
|
26
|
+
export interface LoopActivityResult {
|
|
27
|
+
since: string;
|
|
28
|
+
until: string;
|
|
29
|
+
nodes: ActivityNode[];
|
|
30
|
+
edges: ActivityEdge[];
|
|
31
|
+
}
|
|
32
|
+
export interface LoopActivityDeps {
|
|
33
|
+
listAllLoops?: (opts: { root: string; stateRoot: string; now: Date; schedules: ScheduleRow[] }) => AllLoopEntry[];
|
|
34
|
+
queryEvents?: (root: string, sinceMs: number, untilMs: number) => LogStoreRow[];
|
|
35
|
+
readJournal?: (root: string, name: string) => readonly Record<string, unknown>[];
|
|
36
|
+
readCards?: (root: string) => readonly { id: string; createdAt: string; sections: readonly { owner: string; createdAt: string }[] }[];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readSchedules(root: string): ScheduleRow[] {
|
|
40
|
+
const path = join(root, 'schedules.db');
|
|
41
|
+
if (!existsSync(path)) return [];
|
|
42
|
+
const db = new Database(path, { readonly: true });
|
|
43
|
+
try {
|
|
44
|
+
if (!db.query("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'schedule_registry'").get()) return [];
|
|
45
|
+
const columns = (db.query('PRAGMA table_info(schedule_registry)').all() as Array<{ name: string }>).map(row => row.name);
|
|
46
|
+
if (!columns.includes('run_via')) return [];
|
|
47
|
+
return db.query('SELECT * FROM schedule_registry').all() as ScheduleRow[];
|
|
48
|
+
} finally { db.close(); }
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readEvents(root: string, sinceMs: number, untilMs: number): LogStoreRow[] {
|
|
52
|
+
const path = join(root, 'logs', 'logs.db');
|
|
53
|
+
if (!existsSync(path)) return [];
|
|
54
|
+
const store = LogStore.openReadOnly(path);
|
|
55
|
+
try { return store.queryAll({ categories: ['loop.', 'seat.loop'], sinceMs, untilMs }); }
|
|
56
|
+
finally { store.close(); }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function parseObject(value: string): Record<string, unknown> {
|
|
60
|
+
try {
|
|
61
|
+
const parsed: unknown = JSON.parse(value);
|
|
62
|
+
return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed as Record<string, unknown> : {};
|
|
63
|
+
} catch { return {}; }
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readJournal(root: string, name: string): Record<string, unknown>[] {
|
|
67
|
+
const path = join(root, 'seat-requests', name);
|
|
68
|
+
return existsSync(path) ? readFileSync(path, 'utf8').split('\n').filter(Boolean).map(parseObject) : [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function readCards(root: string): { id: string; createdAt: string; sections: { owner: string; createdAt: string }[] }[] {
|
|
72
|
+
const dir = join(root, 'task-cards');
|
|
73
|
+
if (!existsSync(dir)) return [];
|
|
74
|
+
return readdirSync(dir).filter(file => file.endsWith('.jsonl')).flatMap(file => {
|
|
75
|
+
const entries = readFileSync(join(dir, file), 'utf8').split('\n').filter(Boolean).map(parseObject);
|
|
76
|
+
const created = entries.find(row => row.type === 'created');
|
|
77
|
+
if (!created || typeof created.id !== 'string' || typeof created.createdAt !== 'string') return [];
|
|
78
|
+
return [{ id: created.id, createdAt: created.createdAt,
|
|
79
|
+
sections: entries.filter(row => row.type === 'section' && typeof row.owner === 'string' && typeof row.createdAt === 'string')
|
|
80
|
+
.map(row => ({ owner: row.owner as string, createdAt: row.createdAt as string })) }];
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function text(value: unknown): string | null {
|
|
85
|
+
return typeof value === 'string' && value.trim() ? value.trim() : null;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function loopId(value: unknown): string | null {
|
|
89
|
+
const id = text(value);
|
|
90
|
+
if (!id) return null;
|
|
91
|
+
const seat = id.toUpperCase();
|
|
92
|
+
if (seat === 'OP' || seat === 'TC' || seat === 'UX') return `${seat.toLowerCase()}-seat`;
|
|
93
|
+
if (seat === 'MK' || seat === 'MK-SEAT') return 'cmo-seat';
|
|
94
|
+
return id;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function cardNodeId(value: unknown): string | null {
|
|
98
|
+
const id = text(value);
|
|
99
|
+
return id ? (id.startsWith('card:') ? id : `card:${id}`) : null;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Read-only registry/checker snapshot plus observed edges in the inclusive time window. */
|
|
103
|
+
export function loopActivity({ since, until, root = effectiveInstanceRoot(), deps = {} }: {
|
|
104
|
+
since: string | Date;
|
|
105
|
+
until?: string | Date;
|
|
106
|
+
root?: string;
|
|
107
|
+
deps?: LoopActivityDeps;
|
|
108
|
+
}): LoopActivityResult {
|
|
109
|
+
const end = until === undefined ? new Date() : new Date(until);
|
|
110
|
+
const duration = typeof since === 'string' ? /^(\d+)([mhd])$/.exec(since) : null;
|
|
111
|
+
const durationMs = duration ? Number(duration[1]) * ({ m: 60_000, h: 3_600_000, d: 86_400_000 }[duration[2]!] ?? NaN) : NaN;
|
|
112
|
+
const start = duration ? new Date(end.getTime() - durationMs) : new Date(since);
|
|
113
|
+
if (!Number.isFinite(start.getTime()) || !Number.isFinite(end.getTime()) || start > end) throw new Error('invalid loop activity window');
|
|
114
|
+
const inventory = (deps.listAllLoops ?? listAllLoops)({ root: deps.listAllLoops ? root : defaultLoopRoot(), stateRoot: root, now: end, schedules: deps.listAllLoops ? [] : readSchedules(root) });
|
|
115
|
+
const checked = checkLoops(inventory, end);
|
|
116
|
+
const byId = new Map(inventory.map(entry => [entry.id, entry]));
|
|
117
|
+
const nodes = new Map<string, ActivityNode>(checked.map(entry => [entry.id, {
|
|
118
|
+
id: entry.id, kind: byId.get(entry.id)?.kind ?? 'graph',
|
|
119
|
+
title: byId.get(entry.id)?.title ?? entry.id,
|
|
120
|
+
state: entry.state, reason: entry.reason, lastAt: entry.lastRunAt ?? null, events: 0,
|
|
121
|
+
}]));
|
|
122
|
+
const edges = new Map<string, ActivityEdge>();
|
|
123
|
+
const ensure = (id: string, kind: ActivityNode['kind'] = 'event'): ActivityNode => {
|
|
124
|
+
let node = nodes.get(id);
|
|
125
|
+
if (!node) {
|
|
126
|
+
node = { id, kind, title: id, state: null, reason: null, lastAt: null, events: 0 };
|
|
127
|
+
nodes.set(id, node);
|
|
128
|
+
}
|
|
129
|
+
return node;
|
|
130
|
+
};
|
|
131
|
+
const inWindow = (value: unknown): string | null => {
|
|
132
|
+
const ms = typeof value === 'string' ? Date.parse(value) : NaN;
|
|
133
|
+
return Number.isFinite(ms) && ms >= start.getTime() && ms <= end.getTime() ? new Date(ms).toISOString() : null;
|
|
134
|
+
};
|
|
135
|
+
const touch = (id: string, at: string, kind: ActivityNode['kind'] = 'event'): void => {
|
|
136
|
+
const node = ensure(id, kind);
|
|
137
|
+
node.events++;
|
|
138
|
+
if (!node.lastAt || at > node.lastAt) node.lastAt = at;
|
|
139
|
+
};
|
|
140
|
+
const connect = (from: string, to: string, kind: ActivityEdge['kind'], at: string): void => {
|
|
141
|
+
if (from === to) return;
|
|
142
|
+
ensure(from, from.startsWith('card:') ? 'card' : 'event');
|
|
143
|
+
ensure(to, to.startsWith('card:') ? 'card' : 'event');
|
|
144
|
+
const key = JSON.stringify([from, to, kind]);
|
|
145
|
+
const edge = edges.get(key);
|
|
146
|
+
if (edge) { edge.count++; if (at > edge.lastAt) edge.lastAt = at; }
|
|
147
|
+
else edges.set(key, { from, to, kind, count: 1, lastAt: at });
|
|
148
|
+
};
|
|
149
|
+
for (const row of (deps.queryEvents ?? readEvents)(root, start.getTime(), end.getTime())) {
|
|
150
|
+
const timestamp = Number.isFinite(row.ts_ms) ? row.ts_ms : Date.parse(row.ts);
|
|
151
|
+
if (!Number.isFinite(timestamp) || timestamp < start.getTime() || timestamp > end.getTime()) continue;
|
|
152
|
+
const data = parseObject(row.data ?? '');
|
|
153
|
+
const actor = row.category.startsWith('loop.') && !['loop.checker', 'loop.observe', 'loop.registry', 'loop.neighbors', 'loop.package'].includes(row.category)
|
|
154
|
+
? loopId(row.category.slice('loop.'.length)) : row.category === 'seat.loop' ? loopId(data.seat) : null;
|
|
155
|
+
if (!actor) continue;
|
|
156
|
+
const at = new Date(timestamp).toISOString();
|
|
157
|
+
touch(actor, at);
|
|
158
|
+
if (row.event === 'exchange' && text(data.reason) === 'delegation') {
|
|
159
|
+
const to = loopId(data.to ?? data.targetLoopId);
|
|
160
|
+
if (to) connect(loopId(data.from) ?? actor, to, 'delegation', at);
|
|
161
|
+
} else if (row.category === 'loop.orchestrator' && row.event === 'exchange' && data.reason === 'queued') {
|
|
162
|
+
const to = loopId(data.targetLoopId);
|
|
163
|
+
if (to) connect(actor, to, 'delegation', at);
|
|
164
|
+
} else if (row.event === 'exchange' && (data.reason === 'request' || data.reason === 'reply' || data.reason === 'card')) {
|
|
165
|
+
const to = data.reason === 'card' && data.cardId != null
|
|
166
|
+
? cardNodeId(data.cardId) : loopId(data.to ?? data.targetLoopId);
|
|
167
|
+
if (to) connect(loopId(data.from) ?? actor, to, data.reason, at);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
for (const item of (deps.readJournal ?? readJournal)(root, 'requests.jsonl')) {
|
|
171
|
+
const at = inWindow(item.queuedAt);
|
|
172
|
+
const to = loopId(item.loopId ?? item.seat);
|
|
173
|
+
if (!at || !to) continue;
|
|
174
|
+
touch(to, at, 'seat');
|
|
175
|
+
const from = loopId(item.from) ?? (item.source === 'orchestrator' || item.source === 'orchestrator-traffic' ? 'orchestrator'
|
|
176
|
+
: item.source === 'loop-checker' ? 'checker' : null);
|
|
177
|
+
if (from) connect(from, to, 'request', at);
|
|
178
|
+
}
|
|
179
|
+
for (const item of (deps.readJournal ?? readJournal)(root, 'replies.jsonl')) {
|
|
180
|
+
const at = inWindow(item.repliedAt ?? item.createdAt);
|
|
181
|
+
const from = loopId(item.from ?? item.seat);
|
|
182
|
+
if (!at || !from) continue;
|
|
183
|
+
touch(from, at, 'seat');
|
|
184
|
+
const to = loopId(item.to);
|
|
185
|
+
if (to) connect(from, to, 'reply', at);
|
|
186
|
+
}
|
|
187
|
+
for (const card of (deps.readCards ?? readCards)(root)) {
|
|
188
|
+
const id = cardNodeId(card.id);
|
|
189
|
+
if (!id) continue;
|
|
190
|
+
const created = inWindow(card.createdAt);
|
|
191
|
+
if (created) touch(id, created, 'card');
|
|
192
|
+
for (const section of card.sections) {
|
|
193
|
+
const at = inWindow(section.createdAt);
|
|
194
|
+
const owner = loopId(section.owner);
|
|
195
|
+
if (!at || !owner) continue;
|
|
196
|
+
touch(id, at, 'card');
|
|
197
|
+
connect(owner, id, 'card', at);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return { since: start.toISOString(), until: end.toISOString(),
|
|
201
|
+
nodes: [...nodes.values()].sort((a, b) => a.id.localeCompare(b.id)),
|
|
202
|
+
edges: [...edges.values()].sort((a, b) => a.from.localeCompare(b.from) || a.to.localeCompare(b.to) || a.kind.localeCompare(b.kind)) };
|
|
203
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { ORCHESTRATOR_DEFAULTS, type OrchestratorSeat } from '../user-config.js';
|
|
2
|
+
|
|
3
|
+
export interface SpawnBudgetInput {
|
|
4
|
+
seat: OrchestratorSeat;
|
|
5
|
+
/** Current running count for this seat; null/undefined means the ledger could not be measured. */
|
|
6
|
+
running: number | null | undefined;
|
|
7
|
+
/** Configured loops.orchestrator.seatCaps. Missing seats use the orchestrator defaults. */
|
|
8
|
+
caps?: Partial<Record<OrchestratorSeat, number>>;
|
|
9
|
+
/** Release-gate seat limits. Missing seats use the gate default of four. */
|
|
10
|
+
gate?: Partial<Record<OrchestratorSeat, number>>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface SpawnBudgetDecision {
|
|
14
|
+
allow: boolean;
|
|
15
|
+
reason: 'within-cap' | 'seat-cap' | 'unknown-running';
|
|
16
|
+
cap: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A release gate can lower, but never raise, the configured per-seat cap. */
|
|
20
|
+
export function decideSpawn({ seat, running, caps, gate }: SpawnBudgetInput): SpawnBudgetDecision {
|
|
21
|
+
const configured = caps?.[seat] ?? ORCHESTRATOR_DEFAULTS.seatCaps[seat];
|
|
22
|
+
const gated = gate?.[seat] ?? 4;
|
|
23
|
+
const cap = Number.isSafeInteger(configured) && configured >= 0 && Number.isSafeInteger(gated) && gated >= 0
|
|
24
|
+
? Math.min(configured, gated)
|
|
25
|
+
: 0;
|
|
26
|
+
if (!Number.isSafeInteger(running) || running === null || running === undefined || running < 0) {
|
|
27
|
+
return { allow: false, reason: 'unknown-running', cap };
|
|
28
|
+
}
|
|
29
|
+
return running < cap
|
|
30
|
+
? { allow: true, reason: 'within-cap', cap }
|
|
31
|
+
: { allow: false, reason: 'seat-cap', cap };
|
|
32
|
+
}
|
package/src/loops/checker.ts
CHANGED
|
@@ -14,6 +14,8 @@ export type LoopScope = 'graph-only' | 'registry';
|
|
|
14
14
|
export interface CheckEntry {
|
|
15
15
|
id: string;
|
|
16
16
|
owner?: string;
|
|
17
|
+
ownerSource?: 'header' | 'config' | 'seat' | 'default';
|
|
18
|
+
evidence?: 'log-mtime';
|
|
17
19
|
mode?: string;
|
|
18
20
|
expectEveryMinutes?: number;
|
|
19
21
|
/** The scheduled fire before the latest one (cron loops). A run older than this missed two scheduled fires. */
|
|
@@ -88,8 +90,8 @@ export interface RegistryAdapterDeps {
|
|
|
88
90
|
}
|
|
89
91
|
|
|
90
92
|
export function entriesFromRegistry(deps: RegistryAdapterDeps = {}): { entries: CheckEntry[]; scope: LoopScope } {
|
|
91
|
-
//
|
|
92
|
-
const all = deps.listAllLoops ?? (
|
|
93
|
+
// An explicitly injected graph inventory remains the checker test/adapter fallback.
|
|
94
|
+
const all = deps.listAllLoops ?? (deps.listLoops ? undefined : registry.listAllLoops);
|
|
93
95
|
if (typeof all === 'function') return { entries: all(), scope: 'registry' };
|
|
94
96
|
const now = deps.now ?? new Date();
|
|
95
97
|
return { scope: 'graph-only', entries: (deps.listLoops ?? registry.listLoops)().map(loop => {
|
|
@@ -102,7 +104,7 @@ export function entriesFromRegistry(deps: RegistryAdapterDeps = {}): { entries:
|
|
|
102
104
|
: registry.loopStatus(loop.id).recentRuns.map(run => run.status)
|
|
103
105
|
: [];
|
|
104
106
|
const dueAt = loop.trigger.cron ? dueBefore(loop.trigger.cron, now, deps.timeZone) : null;
|
|
105
|
-
return { id: loop.id, enabled: loop.enabled, registered: true,
|
|
107
|
+
return { id: loop.id, owner: loop.owner ?? undefined, ownerSource: loop.ownerSource, enabled: loop.enabled, registered: true,
|
|
106
108
|
...(interval !== undefined ? { expectEveryMinutes: interval } : {}),
|
|
107
109
|
...(dueAt ? { dueAt } : {}),
|
|
108
110
|
...(loop.lastRun ? { lastRunAt: loop.lastRun.at, lastStatus: loop.lastRun.status } : {}),
|
|
@@ -2,7 +2,7 @@ id: orchestrator
|
|
|
2
2
|
version: 1.0.0
|
|
3
3
|
kind: orchestrator
|
|
4
4
|
role: docs/roles/OP.md
|
|
5
|
-
graph: graphs/
|
|
5
|
+
graph: graphs/orchestrator/orchestrator.yaml
|
|
6
6
|
orchestration:
|
|
7
7
|
windows:
|
|
8
8
|
- {at: '08:00', delegateTo: [op-seat, cmo-seat, tc-seat, ux-seat], fromLedger: release/checklist-and-seat-requests}
|
package/src/loops/loop-cli.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import { writeStdoutJson } from '../cli/stdout-json.js';
|
|
3
|
-
import { listLoops, loopStatus, runLoop, setLoopEnabled } from './registry.js';
|
|
3
|
+
import { listAllLoops, listLoops, loopStatus, runLoop, setLoopEnabled, unregisteredCronLoops, type AllLoopEntry, type LoopRegistryOptions } from './registry.js';
|
|
4
4
|
import { checkLoops, countLoopStates, entriesFromRegistry, LOOP_STATES, notifyOwners, observeLoopCheck, type RegistryAdapterDeps } from './checker.js';
|
|
5
5
|
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
6
6
|
import { verifyLoopPackage } from './package/verify.js';
|
|
7
|
+
import { loopActivity } from './activity.js';
|
|
7
8
|
|
|
8
9
|
async function output(value: unknown, json: boolean): Promise<void> {
|
|
9
10
|
if (json) await writeStdoutJson(JSON.stringify(value) + '\n');
|
|
@@ -17,10 +18,41 @@ function handle(action: () => Promise<void> | void): Promise<void> {
|
|
|
17
18
|
});
|
|
18
19
|
}
|
|
19
20
|
|
|
20
|
-
|
|
21
|
+
function allLoopTable(entries: AllLoopEntry[]): void {
|
|
22
|
+
console.log('id\t층\t주인 자리\t모드\t기대 주기\t호스트\t울타리 역할\t관측 카테고리\t마지막 실행');
|
|
23
|
+
for (const entry of entries) {
|
|
24
|
+
const interval = entry.expectEveryMinutes === undefined ? '모름'
|
|
25
|
+
: entry.cronIrregular ? `불규칙 · 최대 ${entry.expectEveryMinutes}분` : `${entry.expectEveryMinutes}분`;
|
|
26
|
+
console.log([entry.id, entry.kind, entry.owner ?? '모름', entry.mode ?? '모름', interval,
|
|
27
|
+
entry.host ?? '모름', entry.fenceRole ?? '모름', entry.observationCategory ?? '모름', entry.lastRunAt ?? '모름'].join('\t'));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function registerLoopCommands(program: Command, checkerDeps: RegistryAdapterDeps & { root?: string; registryOptions?: LoopRegistryOptions } = {}): void {
|
|
21
32
|
const loop = program.command('loop').description('Inspect and control graph-backed loop agents');
|
|
22
|
-
loop.command('list').option('--json', 'Print JSON')
|
|
23
|
-
.action((opts: { json?: boolean }) => handle(() =>
|
|
33
|
+
loop.command('list').option('--all', 'Show the full loop registry').option('--json', 'Print JSON')
|
|
34
|
+
.action((opts: { all?: boolean; json?: boolean }) => handle(async () => {
|
|
35
|
+
if (!opts.all) { await output(listLoops(checkerDeps.registryOptions), !!opts.json); return; }
|
|
36
|
+
const registryOpts = checkerDeps.registryOptions ?? {};
|
|
37
|
+
const entries = listAllLoops(registryOpts);
|
|
38
|
+
if (opts.json) { await output(entries, true); return; }
|
|
39
|
+
allLoopTable(entries);
|
|
40
|
+
const missing = unregisteredCronLoops(entries, registryOpts);
|
|
41
|
+
if (missing.length) console.log(`미등록 ${missing.length}: ${missing.map(row => `${row.id} (${row.command})`).join(', ')}`);
|
|
42
|
+
}));
|
|
43
|
+
loop.command('activity').description('Read loop and seat activity in a time window (default: last 24h)')
|
|
44
|
+
.option('--since <duration-or-date>', 'Start of window: duration (e.g. 24h) or ISO date', '24h')
|
|
45
|
+
.option('--json', 'Print JSON')
|
|
46
|
+
.action((opts: { since: string; json?: boolean }) => handle(async () => {
|
|
47
|
+
const activity = loopActivity({ since: opts.since, root: checkerDeps.root ?? effectiveInstanceRoot() });
|
|
48
|
+
if (opts.json) { await output(activity, true); return; }
|
|
49
|
+
console.log(`Loop activity ${activity.since} → ${activity.until}`);
|
|
50
|
+
console.log(`${activity.nodes.length} nodes · ${activity.edges.length} edges`);
|
|
51
|
+
console.log('Nodes (id · state · events · last activity)');
|
|
52
|
+
for (const node of activity.nodes) console.log(`${node.id} · ${node.state ?? '—'} · ${node.events} · ${node.lastAt ?? '—'}`);
|
|
53
|
+
console.log('Edges (from → to · kind · count · last activity)');
|
|
54
|
+
for (const edge of activity.edges) console.log(`${edge.from} → ${edge.to} · ${edge.kind} · ${edge.count} · ${edge.lastAt}`);
|
|
55
|
+
}));
|
|
24
56
|
loop.command('status [id]').option('--all', 'Check all registered loops')
|
|
25
57
|
.option('--json', 'Print JSON').option('--notify', 'Queue late/failing loops for their owners')
|
|
26
58
|
.action((id: string | undefined, opts: { all?: boolean; json?: boolean; notify?: boolean }) => handle(async () => {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
|
|
3
|
+
/** The neighbor fields validated by src/loops/contract/validate.ts. */
|
|
4
|
+
export interface Neighbor {
|
|
5
|
+
id: string;
|
|
6
|
+
exchange: ('status' | 'request' | 'decision' | 'blocked')[];
|
|
7
|
+
heartbeat: { everyMinutes: number; missedTicks: number };
|
|
8
|
+
onAbsent: { action: 'delegate' | 'defer' | 'escalate'; delegateTo: string };
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface NeighborJudgment {
|
|
12
|
+
state: 'present' | 'absent' | 'unknown';
|
|
13
|
+
action?: Neighbor['onAbsent'];
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** lastSeenAt is the latest ACK of a heartbeat or envelope, not the last send attempt. */
|
|
18
|
+
export function judgeNeighbor({ neighbor, lastSeenAt, lastSendError, now }: {
|
|
19
|
+
neighbor: Neighbor;
|
|
20
|
+
lastSeenAt?: string | null;
|
|
21
|
+
lastSendError?: unknown;
|
|
22
|
+
now: Date;
|
|
23
|
+
}): NeighborJudgment {
|
|
24
|
+
const seen = lastSeenAt ? Date.parse(lastSeenAt) : NaN;
|
|
25
|
+
let result: NeighborJudgment;
|
|
26
|
+
if (lastSendError != null) {
|
|
27
|
+
result = { state: 'unknown', reason: 'send-failed' };
|
|
28
|
+
} else if (!Number.isFinite(seen) || !Number.isFinite(now.getTime()) || seen > now.getTime()) {
|
|
29
|
+
result = { state: 'unknown', reason: 'last-ack-unknown' };
|
|
30
|
+
} else if (now.getTime() - seen >= neighbor.heartbeat.everyMinutes * neighbor.heartbeat.missedTicks * 60_000) {
|
|
31
|
+
result = { state: 'absent', action: neighbor.onAbsent, reason: 'heartbeat-missed' };
|
|
32
|
+
} else {
|
|
33
|
+
result = { state: 'present', reason: 'within-heartbeat-window' };
|
|
34
|
+
}
|
|
35
|
+
// The validated neighbor entry does not carry its owning loop id; do not invent one.
|
|
36
|
+
debug.log('loop.neighbors', 'judged', {
|
|
37
|
+
loop: null, neighbor: neighbor.id, state: result.state, action: result.action, reason: result.reason,
|
|
38
|
+
});
|
|
39
|
+
return result;
|
|
40
|
+
}
|
package/src/loops/observe.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { debug } from '../debug/log.js';
|
|
2
|
+
import { recordFailureEvent } from '../self-implement/heal-intake.js';
|
|
2
3
|
|
|
3
4
|
/** Event vocabulary of the loop-agent manifest observability contract. */
|
|
4
5
|
export const LOOP_EVENTS = [
|
|
@@ -46,4 +47,13 @@ export function loopEvent(loopId: string, event: string, data: Record<string, un
|
|
|
46
47
|
const missingRequired = REQUIRED_CONTEXT.filter(field =>
|
|
47
48
|
typeof data[field] !== 'string' || !(data[field] as string).trim());
|
|
48
49
|
debug.log(`loop.${loopId}`, event, { ...data, loopId, missingRequired });
|
|
50
|
+
if (event === 'tick' && (data.outcome === 'failed' || data.status === 'failed')) {
|
|
51
|
+
try {
|
|
52
|
+
recordFailureEvent({ source: 'loop-tick', kind: event, ref: loopId,
|
|
53
|
+
summary: `Loop ${loopId} tick failed`, at: new Date().toISOString() });
|
|
54
|
+
} catch (error) {
|
|
55
|
+
try { debug.log('heal.intake', 'record-failed', { source: 'loop-tick', kind: event, ref: loopId, error: String(error) }, { level: 'error' }); }
|
|
56
|
+
catch { /* Heal reporting must not change the tick outcome. */ }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
49
59
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { debug } from '../../debug/log.js';
|
|
4
|
+
import { defaultListHarnessProcesses } from '../../harness/harness-cli-command.js';
|
|
5
|
+
import { listHarnessQueue, type QueueItem } from '../../harness/harness-queue.js';
|
|
6
|
+
import { releaseLedgerRoot } from '../../instance/resolve.js';
|
|
7
|
+
import { listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
8
|
+
import { listSchedules } from '../../release-loop/release-schedule.js';
|
|
9
|
+
import { getUserConfig, ORCHESTRATOR_DEFAULTS, type OrchestratorSeat } from '../../user-config.js';
|
|
10
|
+
import { collectWork, findOverlaps, type WorkItem } from './overlap.js';
|
|
11
|
+
import { seatOfTree, trafficTick, TRAFFIC_SEATS } from './traffic.js';
|
|
12
|
+
|
|
13
|
+
export type AuthorCell = Pick<ChecklistItem, 'id' | 'title' | 'owner' | 'status'> & { version: string };
|
|
14
|
+
export type AuthorQueueRow = Pick<QueueItem, 'seat' | 'status'> & Partial<Pick<QueueItem, 'input' | 'kind'>>;
|
|
15
|
+
export type AuthorDepthResult = {
|
|
16
|
+
seats: Array<{ seat: OrchestratorSeat; cap: number | null; running: number | null; queued: number | null;
|
|
17
|
+
target: number | null; short: number | null; wouldAuthor: Array<{ cellId: string; version: string; title: string }> }>;
|
|
18
|
+
unreadable: Array<{ source: string; reason: string }>;
|
|
19
|
+
};
|
|
20
|
+
export type AuthorDepthInput = {
|
|
21
|
+
seats: readonly OrchestratorSeat[];
|
|
22
|
+
caps: Partial<Record<OrchestratorSeat, number | null>>;
|
|
23
|
+
running: Partial<Record<OrchestratorSeat, number | null>> | null;
|
|
24
|
+
queued: readonly AuthorQueueRow[] | null;
|
|
25
|
+
cells: readonly AuthorCell[] | null;
|
|
26
|
+
overlaps: readonly WorkItem[] | null;
|
|
27
|
+
now: Date;
|
|
28
|
+
unreadable?: AuthorDepthResult['unreadable'];
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const cellRefs = (text: string): string[] => [...new Set([...text.matchAll(/\b\d+\.\d+\.\d+\s+칸\s+([A-Za-z][A-Za-z0-9-]*)/g)]
|
|
32
|
+
.map(match => match[1]!))];
|
|
33
|
+
|
|
34
|
+
/** A shadow decision: candidates are compared against existing work, never entered into the queue. */
|
|
35
|
+
export function authorDepth({ seats, caps, running, queued, cells, overlaps, now, unreadable = [] }: AuthorDepthInput): AuthorDepthResult {
|
|
36
|
+
const errors = [...unreadable];
|
|
37
|
+
// Version selection happens in the collector at `now`; the pure calculation receives those cells already selected.
|
|
38
|
+
// Reject an invalid snapshot clock rather than presenting its candidates as a valid shadow decision.
|
|
39
|
+
const validClock = Number.isFinite(now.getTime());
|
|
40
|
+
if (!validClock) errors.push({ source: 'clock', reason: 'invalid observation time' });
|
|
41
|
+
const busy = queued === null ? null : queued.filter(row => row.status === 'queued' || row.status === 'launching');
|
|
42
|
+
const queueWork: WorkItem[] = busy?.map((row, index) => ({
|
|
43
|
+
kind: 'goal', ref: `queue:${index}`, seat: row.seat, files: [], cells: row.input ? cellRefs(row.input) : [],
|
|
44
|
+
})) ?? [];
|
|
45
|
+
const base = overlaps === null ? null : [...overlaps, ...queueWork];
|
|
46
|
+
const unknownWorkCells = overlaps?.some(item => item.unreadable) ?? false;
|
|
47
|
+
const availableCells = cells ?? [];
|
|
48
|
+
// The collector supplies current release before next; numeric version order need not match release order.
|
|
49
|
+
const versionOrder = new Map([...new Set(availableCells.map(cell => cell.version))]
|
|
50
|
+
.map((version, index) => [version, index]));
|
|
51
|
+
const missing = (source: string, seat: OrchestratorSeat): void => {
|
|
52
|
+
if (!errors.some(item => item.source === source)) errors.push({ source, reason: `missing ${seat} observation` });
|
|
53
|
+
};
|
|
54
|
+
const rows = seats.map(seat => {
|
|
55
|
+
const cap = caps[seat] ?? null;
|
|
56
|
+
const active = running?.[seat] ?? null;
|
|
57
|
+
if (cap === null) missing('caps', seat);
|
|
58
|
+
if (active === null) missing('running', seat);
|
|
59
|
+
if (queued === null) missing('queue', seat);
|
|
60
|
+
if (cells === null && !errors.some(item => item.source === 'checklist' || item.source === 'versions')) missing('checklist', seat);
|
|
61
|
+
if (overlaps === null || unknownWorkCells) missing('overlap', seat);
|
|
62
|
+
const unknownQueueCells = busy?.some(row => row.seat === seat && (!row.input || cellRefs(row.input).length === 0)) ?? false;
|
|
63
|
+
if (unknownQueueCells) missing('queue-cells', seat);
|
|
64
|
+
const have = busy === null ? null : busy.filter(row => row.seat === seat).length;
|
|
65
|
+
const depthKnown = cap !== null && active !== null && have !== null;
|
|
66
|
+
const candidateKnown = validClock && cells !== null && base !== null && !unknownQueueCells && !unknownWorkCells;
|
|
67
|
+
const target = cap === null || active === null ? null : Math.max(0, cap - active) + 2;
|
|
68
|
+
const short = !depthKnown || !candidateKnown || target === null ? null : Math.max(0, target - have);
|
|
69
|
+
return { seat, cap, running: active, queued: have, target, short,
|
|
70
|
+
wouldAuthor: [] as Array<{ cellId: string; version: string; title: string }> };
|
|
71
|
+
});
|
|
72
|
+
const chosen = new Set<string>();
|
|
73
|
+
for (const cell of [...availableCells].sort((a, b) => versionOrder.get(a.version)! - versionOrder.get(b.version)! || a.id.localeCompare(b.id))) {
|
|
74
|
+
if (cell.status === 'green' || cell.status === 'done' || !cell.title.trim() || chosen.has(cell.id)) continue;
|
|
75
|
+
const row = rows.find(entry => entry.seat === cell.owner?.split('/')[0]);
|
|
76
|
+
if (!row || row.short === null || row.wouldAuthor.length >= row.short) continue;
|
|
77
|
+
const candidate: WorkItem = { kind: 'goal', ref: `candidate:${cell.version}:${cell.id}`, seat: row.seat, files: [], cells: [cell.id] };
|
|
78
|
+
if (findOverlaps([...base!, candidate]).some(overlap => overlap.type === 'cell' && overlap.cell === cell.id
|
|
79
|
+
&& overlap.refs.some(ref => ref.ref === candidate.ref))) continue;
|
|
80
|
+
chosen.add(cell.id);
|
|
81
|
+
row.wouldAuthor.push({ cellId: cell.id, version: cell.version, title: cell.title });
|
|
82
|
+
}
|
|
83
|
+
return { seats: rows, unreadable: errors };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface CollectAuthorDepthDeps {
|
|
87
|
+
seats?: readonly OrchestratorSeat[];
|
|
88
|
+
now?: Date;
|
|
89
|
+
caps?: () => Partial<Record<OrchestratorSeat, number>>;
|
|
90
|
+
running?: () => Partial<Record<OrchestratorSeat, number>>;
|
|
91
|
+
queued?: () => readonly AuthorQueueRow[];
|
|
92
|
+
cells?: (version: string) => readonly AuthorCell[];
|
|
93
|
+
overlaps?: () => readonly WorkItem[];
|
|
94
|
+
versions?: () => readonly [string, string];
|
|
95
|
+
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function defaultVersions(now: Date): readonly [string, string] {
|
|
99
|
+
if (!existsSync(join(releaseLedgerRoot(), 'release', 'features.sqlite'))) throw new Error('release schedule ledger unavailable');
|
|
100
|
+
const upcoming = listSchedules().filter(row => row.cutAt && Date.parse(row.cutAt) > now.getTime())
|
|
101
|
+
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
|
|
102
|
+
if (upcoming.length < 2) throw new Error('current and next release schedules unavailable');
|
|
103
|
+
return [upcoming[0]!.version, upcoming[1]!.version];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Independent collection failures are surfaced, never substituted with an apparently empty source. */
|
|
107
|
+
export function collectAuthorDepth(deps: CollectAuthorDepthDeps = {}): AuthorDepthResult {
|
|
108
|
+
const seats = deps.seats ?? TRAFFIC_SEATS;
|
|
109
|
+
const now = deps.now ?? new Date();
|
|
110
|
+
const unreadable: AuthorDepthResult['unreadable'] = [];
|
|
111
|
+
const observe = <T>(source: string, read: () => T): T | null => {
|
|
112
|
+
try { return read(); }
|
|
113
|
+
catch (error) { unreadable.push({ source, reason: error instanceof Error ? error.message : String(error) }); return null; }
|
|
114
|
+
};
|
|
115
|
+
const cfg = deps.caps && deps.running ? null : observe('config', () => getUserConfig().loops?.orchestrator ?? ORCHESTRATOR_DEFAULTS);
|
|
116
|
+
const caps = deps.caps ? observe('caps', deps.caps) : cfg === null ? null : observe('caps', () => cfg.seatCaps);
|
|
117
|
+
const running = deps.running ? observe('running', deps.running) : cfg === null ? null : observe('running', () => {
|
|
118
|
+
const observation = defaultListHarnessProcesses();
|
|
119
|
+
if (observation.status !== 'ok') throw new Error(`process observation ${observation.status}`);
|
|
120
|
+
const processes = observation.records.map(process => ({ ...process,
|
|
121
|
+
seat: seatOfTree(process.cwdStatus === 'unknown' ? undefined : process.cwd, cfg) }));
|
|
122
|
+
const measured = trafficTick({ processes, now, caps: cfg.seatCaps, openCells: [] });
|
|
123
|
+
if (measured.unassigned) throw new Error(`${measured.unassigned} unassigned harness launch process(es)`);
|
|
124
|
+
return Object.fromEntries(measured.seats.map(row => [row.seat, row.running]));
|
|
125
|
+
});
|
|
126
|
+
const queued = observe('queue', deps.queued ?? (() => listHarnessQueue()));
|
|
127
|
+
const versions = observe('versions', deps.versions ?? (() => defaultVersions(now)));
|
|
128
|
+
const cells = versions === null ? null : observe('checklist', () => versions.flatMap(version => {
|
|
129
|
+
if (!deps.cells && !existsSync(join(releaseLedgerRoot(), 'release', 'features.sqlite')))
|
|
130
|
+
throw new Error('checklist ledger unavailable');
|
|
131
|
+
return deps.cells?.(version) ?? listChecklist(version).items.map(item => ({ ...item, version }));
|
|
132
|
+
}));
|
|
133
|
+
const overlaps = observe('overlap', deps.overlaps ?? (() => {
|
|
134
|
+
const observed = collectWork();
|
|
135
|
+
if (observed.unreadableTrees || observed.unreadableGoals || observed.work.some(item => item.unreadable))
|
|
136
|
+
throw new Error(`incomplete work observation: trees=${observed.unreadableTrees} goals=${observed.unreadableGoals}`);
|
|
137
|
+
return observed.work;
|
|
138
|
+
}));
|
|
139
|
+
const result = authorDepth({ seats, now, caps: caps ?? {}, running, queued, cells, overlaps, unreadable });
|
|
140
|
+
const log = deps.log ?? ((category: string, event: string, data: Record<string, unknown>) => debug.log(category, event, data));
|
|
141
|
+
for (const row of result.seats) log('loop.orchestrator', 'would-author', {
|
|
142
|
+
seat: row.seat, target: row.target, have: row.queued, short: row.short, cells: row.wouldAuthor,
|
|
143
|
+
});
|
|
144
|
+
return result;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export function authorDepthLines(result: AuthorDepthResult): string[] {
|
|
148
|
+
return result.seats.map(row => `${row.seat} ${row.running ?? '?'}/${row.cap ?? '?'} · 대기 ${row.queued ?? '?'} · 목표 ${row.target ?? '?'} · 모자람 ${row.short ?? '?'} → ${row.wouldAuthor.map(cell => cell.cellId).join(', ') || '없음'}`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (import.meta.main) {
|
|
152
|
+
try {
|
|
153
|
+
if (process.argv.slice(2).some(arg => arg !== '--json')) throw new Error('usage: author-depth.ts [--json]');
|
|
154
|
+
const result = collectAuthorDepth();
|
|
155
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(result));
|
|
156
|
+
else {
|
|
157
|
+
for (const line of authorDepthLines(result)) console.log(line);
|
|
158
|
+
for (const source of result.unreadable) console.error(`author-depth ${source.source}: ${source.reason}`);
|
|
159
|
+
}
|
|
160
|
+
} catch (error) {
|
|
161
|
+
console.error(`author-depth: ${error instanceof Error ? error.message : String(error)}`);
|
|
162
|
+
process.exitCode = 1;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { debug } from '../../debug/log.js';
|
|
2
|
+
|
|
3
|
+
export interface AuthorInput {
|
|
4
|
+
title: string;
|
|
5
|
+
text: string;
|
|
6
|
+
cellId: string;
|
|
7
|
+
version: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export interface AuthorInputSignal {
|
|
11
|
+
kind: 'path' | 'symbol' | 'command' | 'marker';
|
|
12
|
+
match: string;
|
|
13
|
+
field: 'title' | 'text';
|
|
14
|
+
reason: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AuthorInputCheck {
|
|
18
|
+
verdict: 'approved' | 'resubmit' | 'confirm' | 'uncheckable';
|
|
19
|
+
signals: AuthorInputSignal[];
|
|
20
|
+
units: { checked: number; candidates: number };
|
|
21
|
+
implementationRatio: number | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const TOKEN_SHAPES: ReadonlyArray<{ kind: AuthorInputSignal['kind']; pattern: RegExp; reason: string }> = [
|
|
25
|
+
{ kind: 'path', pattern: /[\w.@~-]+\/[\w./@~-]+|[\w-]+\.[A-Za-z][A-Za-z0-9]{0,7}/g,
|
|
26
|
+
reason: '경로 또는 파일 이름 꼴' },
|
|
27
|
+
{ kind: 'symbol', pattern: /[A-Za-z_][\w.]*\(\)|`[A-Za-z_][\w.]*`/g,
|
|
28
|
+
reason: '함수 또는 심볼 이름 꼴' },
|
|
29
|
+
{ kind: 'command', pattern: /\b(?:bun|npm|pnpm|yarn|npx|bunx|node|deno|python3?|pip|uv|make|git|gh|cargo|go|docker|kubectl|elanous|eln|bash|sh|zsh|curl)\s+[a-z][\w-]*\b|--[\w-]+/g,
|
|
30
|
+
reason: '실행 명령 또는 플래그 꼴' },
|
|
31
|
+
{ kind: 'marker', pattern: /대상 경로:|경계:|불변식:|판정 신호:|관측\s*=|기대\s*=/g,
|
|
32
|
+
reason: '골 구조 마커 꼴' },
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const KOREAN_DIRECTIVE = /하라|해라|하세요|하십시오|해\s?주세요|해\s?줘|해야|바꿔|고쳐|넣어|빼|돌려|추가|수정|삭제|판정|실행|쓰라/;
|
|
36
|
+
const ENGLISH_DIRECTIVE = /\b(?:please|must|should)\b|^\s*(?:run|edit|change|fix|add|remove|delete|update|modify|execute|test|build|use|write|implement|create|set|check|verify)\b/i;
|
|
37
|
+
|
|
38
|
+
function sentences(value: string): string[] {
|
|
39
|
+
return value.split(/(?<=[.?])(?=\s|$)|\n+/).map(part => part.trim()).filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function checkAuthorInput({ title, text, cellId, version }: AuthorInput): AuthorInputCheck {
|
|
43
|
+
const valid = [title, text, cellId, version].every(value => typeof value === 'string' && !!value.trim())
|
|
44
|
+
&& /^\d+\.\d+\.\d+$/.test(version);
|
|
45
|
+
const signals: AuthorInputSignal[] = [];
|
|
46
|
+
let checked = 0;
|
|
47
|
+
let candidates = 0;
|
|
48
|
+
let directed = false;
|
|
49
|
+
|
|
50
|
+
if (valid) {
|
|
51
|
+
for (const field of ['title', 'text'] as const) {
|
|
52
|
+
for (const sentence of sentences(field === 'title' ? title : text)) {
|
|
53
|
+
checked++;
|
|
54
|
+
let found = false;
|
|
55
|
+
for (const { kind, pattern, reason } of TOKEN_SHAPES) {
|
|
56
|
+
for (const match of sentence.matchAll(pattern)) {
|
|
57
|
+
signals.push({ kind, match: match[0], field, reason });
|
|
58
|
+
found = true;
|
|
59
|
+
if (kind === 'marker' || kind === 'command') directed = true;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
if (found) {
|
|
63
|
+
candidates++;
|
|
64
|
+
if (KOREAN_DIRECTIVE.test(sentence) || ENGLISH_DIRECTIVE.test(sentence)) directed = true;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const verdict: AuthorInputCheck['verdict'] = !valid ? 'uncheckable'
|
|
71
|
+
: signals.length === 0 ? 'approved' : directed ? 'resubmit' : 'confirm';
|
|
72
|
+
const implementationRatio = valid ? candidates / checked : null;
|
|
73
|
+
debug.log('author.par', 'input-checked', {
|
|
74
|
+
cellId, version, verdict, signals: signals.length, ratio: implementationRatio,
|
|
75
|
+
});
|
|
76
|
+
return { verdict, signals, units: { checked, candidates }, implementationRatio };
|
|
77
|
+
}
|