elanous 0.2.13 → 0.2.15
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/LZcstaO9_NXypPFmujolv/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-33ffcc49ab45123b.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/{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-c952712c87a1f757.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/899-46946fe8363e1262.js +1 -0
- 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-0e2c57fe6add4b2a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e05a77df7e005b04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-f544338193a9a762.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-5491c115b1f9a923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b8c49f6b4fa2df91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-96398e364fbd4548.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-976813c91d7394ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-28b9448091776c06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1d4901eb7c2d8822.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-72d3e7ff35a8c954.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-60c7a8e1a4fe62ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-2a52b316cb143d9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c3ce483c8d5db370.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-13c7b53e0c2519a9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-96f074f6538e8a2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e97be5334d83a9f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-65e484302921a932.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-34d08facaf08f556.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-955ec2da3be4c560.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-9f88fbc15d9058d1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-c1343f9499d86f3f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-8739a2e0ae793130.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-c9fc5706b8abaa11.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-d23b12bb92242558.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-8d47f45a0dc044ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c7fea475488201a8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-e2ee2896811e98d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-83b19dae8619de05.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-5efbf81124c474ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-85f1a1fc0a82738c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-da6c08aae0f92768.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-06ee266953d9c879.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-153c2b3bcc89e8de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-87a11e6a05f15199.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-7d4182964407c09c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-0d6d23b40263a7ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-7e8c39241f8eb6d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-008c1a96618a4887.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-f5597e894c02cd33.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-0337fdf7fb5e37b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-73735b64bfe1ae67.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-8fcec4c9ea2e7f70.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f7e631e9b4daf66e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-4881b8f9f63694b4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-9817c7f9007466cc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-97f9f43172a3a90e.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/1f7c958c41ffa3b9.css +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/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- 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/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +6 -0
- package/graphs/release/release-loop.yaml +6 -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 +188 -0
- package/scripts/ci-typecheck-changed.ts +50 -18
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs/generate-graph-anatomy.ts +315 -0
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +169 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +112 -0
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/gate-node.ts +89 -14
- 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 +428 -0
- package/scripts/release-story/draft.ts +220 -0
- package/scripts/rhythm/daily.ts +570 -0
- package/scripts/rhythm/weekly.ts +315 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/smart-merge/resync-shadow.ts +147 -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-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +3 -3
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +73 -10
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/briefing/brief-items.ts +121 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +166 -0
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +44 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/intake-cli.ts +46 -17
- 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/pod-cli.ts +2 -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 +190 -33
- package/src/cli/rfc-status.ts +91 -0
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +96 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +6 -3
- package/src/context-bus/coord-events.ts +6 -4
- 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-cards.ts +33 -7
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -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/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- 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 +259 -38
- package/src/harness/harness-incidents.ts +45 -12
- package/src/harness/harness-pod-dispatch.ts +102 -11
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +583 -65
- package/src/harness/helper-repair.ts +87 -0
- package/src/harness/helper-scan.ts +151 -0
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +63 -5
- package/src/hq/lease.ts +4 -4
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +332 -0
- package/src/index.ts +159 -27
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +37 -15
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/route.ts +145 -1
- 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/launch-head/seat-goal-queue-lock.fixture.ts +15 -0
- package/src/launch-head/seat-goal-queue.ts +198 -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 +63 -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 +220 -0
- package/src/loops/orchestrator/author-input-check.ts +82 -0
- package/src/loops/orchestrator/author-ledger.ts +232 -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/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +309 -0
- package/src/loops/orchestrator/traffic.ts +134 -13
- package/src/loops/registry.ts +535 -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 +9 -5
- package/src/maturity/tui-slash-availability.ts +31 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +33 -3
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +18 -1
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +51 -0
- package/src/release-loop/checklist.ts +22 -4
- package/src/release-loop/feature-store.ts +71 -32
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +302 -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-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +531 -40
- package/src/self-dev/dev-pipeline.ts +14 -6
- package/src/self-dev/draft-sweep.ts +162 -6
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +57 -2
- 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 +295 -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-author.ts +43 -5
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +197 -0
- package/src/self-implement/host-regate.ts +32 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +299 -48
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +97 -0
- package/src/self-implement/run-ledger.ts +3 -1
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +29 -9
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session/index.ts +2 -0
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +4 -1
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- 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/pod-pool.ts +60 -8
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +224 -49
- package/src/task-orchestrator/surfaces/self-implement.ts +5 -1
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/telegram.ts +25 -4
- package/src/user-config.ts +146 -18
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -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/botlab/page-f14987d5850a31e0.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/missions/[id]/page-66c27608f651b866.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/devices/page-78b40a344ed22f24.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 → LZcstaO9_NXypPFmujolv}/_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,63 @@
|
|
|
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
|
+
/** Optional queue-injected cap; it may lower, never replace, the configured limit. */
|
|
12
|
+
injectedCap?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SpawnBudgetDecision {
|
|
16
|
+
allow: boolean;
|
|
17
|
+
reason: 'within-cap' | 'seat-cap' | 'unknown-running';
|
|
18
|
+
cap: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type SeatCapDetails = {
|
|
22
|
+
cap: number;
|
|
23
|
+
seatCaps: number | '?';
|
|
24
|
+
releaseGate: number | '?';
|
|
25
|
+
injectedCap?: number | '?';
|
|
26
|
+
winners: ('releaseGate' | 'seatCaps' | 'injectedCap')[];
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/** Keep the cap calculation and the names shown to operators on the same source of truth. */
|
|
30
|
+
export function seatCapDetails({ seat, caps, gate, injectedCap }: Pick<SpawnBudgetInput, 'seat' | 'caps' | 'gate' | 'injectedCap'>): SeatCapDetails {
|
|
31
|
+
const configured = caps?.[seat] ?? ORCHESTRATOR_DEFAULTS.seatCaps[seat];
|
|
32
|
+
const gated = gate?.[seat] ?? 4;
|
|
33
|
+
const valid = (value: number): boolean => Number.isSafeInteger(value) && value >= 0;
|
|
34
|
+
const seatCaps = valid(configured) ? configured : '?';
|
|
35
|
+
const releaseGate = valid(gated) ? gated : '?';
|
|
36
|
+
const injected = injectedCap === undefined ? undefined : valid(injectedCap) ? injectedCap : '?';
|
|
37
|
+
const cap = [seatCaps, releaseGate, injected].some((value) => value === '?') ? 0
|
|
38
|
+
: Math.min(seatCaps as number, releaseGate as number, injected === undefined ? Infinity : injected as number);
|
|
39
|
+
const winners: SeatCapDetails['winners'] = [];
|
|
40
|
+
if (seatCaps !== '?' && releaseGate !== '?' && injected !== '?') {
|
|
41
|
+
if (releaseGate === cap) winners.push('releaseGate');
|
|
42
|
+
if (seatCaps === cap) winners.push('seatCaps');
|
|
43
|
+
if (injected === cap) winners.push('injectedCap');
|
|
44
|
+
}
|
|
45
|
+
return { cap, seatCaps, releaseGate, ...(injected === undefined ? {} : { injectedCap: injected }), winners };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function seatCapReason(seat: OrchestratorSeat, active: number, details: SeatCapDetails): string {
|
|
49
|
+
const keys = (['releaseGate', 'seatCaps', 'injectedCap'] as const).filter((name) => name !== 'injectedCap' || details.injectedCap !== undefined);
|
|
50
|
+
const candidates = [...details.winners, ...keys.filter((name) => !details.winners.includes(name))];
|
|
51
|
+
return `seat ${seat}: ${active}/${details.cap} (${candidates.map((name) => `${name}.${seat}=${details[name]}`).join(' · ')})`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A release gate or injected queue cap can lower, but never raise, the configured per-seat cap. */
|
|
55
|
+
export function decideSpawn({ seat, running, caps, gate, injectedCap }: SpawnBudgetInput): SpawnBudgetDecision {
|
|
56
|
+
const { cap } = seatCapDetails({ seat, caps, gate, injectedCap });
|
|
57
|
+
if (!Number.isSafeInteger(running) || running === null || running === undefined || running < 0) {
|
|
58
|
+
return { allow: false, reason: 'unknown-running', cap };
|
|
59
|
+
}
|
|
60
|
+
return running < cap
|
|
61
|
+
? { allow: true, reason: 'within-cap', cap }
|
|
62
|
+
: { allow: false, reason: 'seat-cap', cap };
|
|
63
|
+
}
|
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' | 'seat-arg' | 'default';
|
|
18
|
+
evidence?: 'log-mtime' | 'registry';
|
|
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,220 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { randomUUID } from 'node:crypto';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { effectiveInstanceRoot } from '../../instance/resolve.js';
|
|
5
|
+
import { debug } from '../../debug/log.js';
|
|
6
|
+
import { defaultListHarnessProcesses } from '../../harness/harness-cli-command.js';
|
|
7
|
+
import { listHarnessQueue, type QueueItem } from '../../harness/harness-queue.js';
|
|
8
|
+
import { releaseLedgerRoot } from '../../instance/resolve.js';
|
|
9
|
+
import { listChecklist, type ChecklistItem } from '../../release-loop/checklist.js';
|
|
10
|
+
import { listSchedules } from '../../release-loop/release-schedule.js';
|
|
11
|
+
import { getUserConfig, ORCHESTRATOR_DEFAULTS, type OrchestratorSeat } from '../../user-config.js';
|
|
12
|
+
import { checkAuthorInput } from './author-input-check.js';
|
|
13
|
+
import { AuthorLedger } from './author-ledger.js';
|
|
14
|
+
import { collectWork, findOverlaps, type WorkItem } from './overlap.js';
|
|
15
|
+
import { seatOfTree, trafficTick, TRAFFIC_SEATS } from './traffic.js';
|
|
16
|
+
|
|
17
|
+
export type AuthorCell = Pick<ChecklistItem, 'id' | 'title' | 'owner' | 'status' | 'evidence'> & { version: string };
|
|
18
|
+
export type AuthorQueueRow = Pick<QueueItem, 'seat' | 'status'> & Partial<Pick<QueueItem, 'input' | 'kind'>>;
|
|
19
|
+
export type AuthorDepthResult = {
|
|
20
|
+
seats: Array<{ seat: OrchestratorSeat; cap: number | null; running: number | null; queued: number | null;
|
|
21
|
+
target: number | null; short: number | null; wouldAuthor: Array<{ cellId: string; version: string; title: string }> }>;
|
|
22
|
+
unreadable: Array<{ source: string; reason: string }>;
|
|
23
|
+
receiptCounts?: { held: number; queuedForAuthor: number };
|
|
24
|
+
receiptFailures?: number;
|
|
25
|
+
};
|
|
26
|
+
export type AuthorDepthInput = {
|
|
27
|
+
seats: readonly OrchestratorSeat[];
|
|
28
|
+
caps: Partial<Record<OrchestratorSeat, number | null>>;
|
|
29
|
+
running: Partial<Record<OrchestratorSeat, number | null>> | null;
|
|
30
|
+
queued: readonly AuthorQueueRow[] | null;
|
|
31
|
+
cells: readonly AuthorCell[] | null;
|
|
32
|
+
overlaps: readonly WorkItem[] | null;
|
|
33
|
+
now: Date;
|
|
34
|
+
unreadable?: AuthorDepthResult['unreadable'];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const cellRefs = (text: string): string[] => [...new Set([...text.matchAll(/\b\d+\.\d+\.\d+\s+칸\s+([A-Za-z][A-Za-z0-9-]*)/g)]
|
|
38
|
+
.map(match => match[1]!))];
|
|
39
|
+
|
|
40
|
+
/** A shadow decision: candidates are compared against existing work, never entered into the queue. */
|
|
41
|
+
export function authorDepth({ seats, caps, running, queued, cells, overlaps, now, unreadable = [] }: AuthorDepthInput): AuthorDepthResult {
|
|
42
|
+
const errors = [...unreadable];
|
|
43
|
+
// Version selection happens in the collector at `now`; the pure calculation receives those cells already selected.
|
|
44
|
+
// Reject an invalid snapshot clock rather than presenting its candidates as a valid shadow decision.
|
|
45
|
+
const validClock = Number.isFinite(now.getTime());
|
|
46
|
+
if (!validClock) errors.push({ source: 'clock', reason: 'invalid observation time' });
|
|
47
|
+
const busy = queued === null ? null : queued.filter(row => row.status === 'queued' || row.status === 'launching');
|
|
48
|
+
const queueWork: WorkItem[] = busy?.map((row, index) => ({
|
|
49
|
+
kind: 'goal', ref: `queue:${index}`, seat: row.seat, files: [], cells: row.input ? cellRefs(row.input) : [],
|
|
50
|
+
})) ?? [];
|
|
51
|
+
const base = overlaps === null ? null : [...overlaps, ...queueWork];
|
|
52
|
+
const unknownWorkCells = overlaps?.some(item => item.unreadable) ?? false;
|
|
53
|
+
const availableCells = cells ?? [];
|
|
54
|
+
// The collector supplies current release before next; numeric version order need not match release order.
|
|
55
|
+
const versionOrder = new Map([...new Set(availableCells.map(cell => cell.version))]
|
|
56
|
+
.map((version, index) => [version, index]));
|
|
57
|
+
const missing = (source: string, seat: OrchestratorSeat): void => {
|
|
58
|
+
if (!errors.some(item => item.source === source)) errors.push({ source, reason: `missing ${seat} observation` });
|
|
59
|
+
};
|
|
60
|
+
const rows = seats.map(seat => {
|
|
61
|
+
const cap = caps[seat] ?? null;
|
|
62
|
+
const active = running?.[seat] ?? null;
|
|
63
|
+
if (cap === null) missing('caps', seat);
|
|
64
|
+
if (active === null) missing('running', seat);
|
|
65
|
+
if (queued === null) missing('queue', seat);
|
|
66
|
+
if (cells === null && !errors.some(item => item.source === 'checklist' || item.source === 'versions')) missing('checklist', seat);
|
|
67
|
+
if (overlaps === null || unknownWorkCells) missing('overlap', seat);
|
|
68
|
+
const unknownQueueCells = busy?.some(row => row.seat === seat && (!row.input || cellRefs(row.input).length === 0)) ?? false;
|
|
69
|
+
if (unknownQueueCells) missing('queue-cells', seat);
|
|
70
|
+
const have = busy === null ? null : busy.filter(row => row.seat === seat).length;
|
|
71
|
+
const depthKnown = cap !== null && active !== null && have !== null;
|
|
72
|
+
const candidateKnown = validClock && cells !== null && base !== null && !unknownQueueCells && !unknownWorkCells;
|
|
73
|
+
const target = cap === null || active === null ? null : Math.max(0, cap - active) + 2;
|
|
74
|
+
const short = !depthKnown || !candidateKnown || target === null ? null : Math.max(0, target - have);
|
|
75
|
+
return { seat, cap, running: active, queued: have, target, short,
|
|
76
|
+
wouldAuthor: [] as Array<{ cellId: string; version: string; title: string }> };
|
|
77
|
+
});
|
|
78
|
+
const chosen = new Set<string>();
|
|
79
|
+
for (const cell of [...availableCells].sort((a, b) => versionOrder.get(a.version)! - versionOrder.get(b.version)! || a.id.localeCompare(b.id))) {
|
|
80
|
+
if (cell.status === 'green' || cell.status === 'done' || !cell.title.trim() || chosen.has(cell.id)) continue;
|
|
81
|
+
const row = rows.find(entry => entry.seat === cell.owner?.split('/')[0]);
|
|
82
|
+
if (!row || row.short === null || row.wouldAuthor.length >= row.short) continue;
|
|
83
|
+
const candidate: WorkItem = { kind: 'goal', ref: `candidate:${cell.version}:${cell.id}`, seat: row.seat, files: [], cells: [cell.id] };
|
|
84
|
+
if (findOverlaps([...base!, candidate]).some(overlap => overlap.type === 'cell' && overlap.cell === cell.id
|
|
85
|
+
&& overlap.refs.some(ref => ref.ref === candidate.ref))) continue;
|
|
86
|
+
chosen.add(cell.id);
|
|
87
|
+
row.wouldAuthor.push({ cellId: cell.id, version: cell.version, title: cell.title });
|
|
88
|
+
}
|
|
89
|
+
return { seats: rows, unreadable: errors };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface CollectAuthorDepthDeps {
|
|
93
|
+
root?: string;
|
|
94
|
+
seats?: readonly OrchestratorSeat[];
|
|
95
|
+
now?: Date;
|
|
96
|
+
caps?: () => Partial<Record<OrchestratorSeat, number>>;
|
|
97
|
+
running?: () => Partial<Record<OrchestratorSeat, number>>;
|
|
98
|
+
queued?: () => readonly AuthorQueueRow[];
|
|
99
|
+
cells?: (version: string) => readonly AuthorCell[];
|
|
100
|
+
overlaps?: () => readonly WorkItem[];
|
|
101
|
+
versions?: () => readonly [string, string];
|
|
102
|
+
ledger?: Pick<AuthorLedger, 'request'>;
|
|
103
|
+
log?: (category: string, event: string, data: Record<string, unknown>) => void;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function defaultVersions(now: Date, root = releaseLedgerRoot()): readonly [string, string] {
|
|
107
|
+
if (!existsSync(join(root, 'release', 'features.sqlite'))) throw new Error('release schedule ledger unavailable');
|
|
108
|
+
const upcoming = listSchedules(root).filter(row => row.cutAt && Date.parse(row.cutAt) > now.getTime())
|
|
109
|
+
.sort((a, b) => Date.parse(a.cutAt) - Date.parse(b.cutAt));
|
|
110
|
+
if (upcoming.length < 2) throw new Error('current and next release schedules unavailable');
|
|
111
|
+
return [upcoming[0]!.version, upcoming[1]!.version];
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Independent collection failures are surfaced, never substituted with an apparently empty source. */
|
|
115
|
+
export function collectAuthorDepth(deps: CollectAuthorDepthDeps = {}): AuthorDepthResult {
|
|
116
|
+
const seats = deps.seats ?? TRAFFIC_SEATS;
|
|
117
|
+
const now = deps.now ?? new Date();
|
|
118
|
+
const unreadable: AuthorDepthResult['unreadable'] = [];
|
|
119
|
+
const observe = <T>(source: string, read: () => T): T | null => {
|
|
120
|
+
try { return read(); }
|
|
121
|
+
catch (error) { unreadable.push({ source, reason: error instanceof Error ? error.message : String(error) }); return null; }
|
|
122
|
+
};
|
|
123
|
+
const cfg = deps.caps && deps.running ? null : observe('config', () => getUserConfig().loops?.orchestrator ?? ORCHESTRATOR_DEFAULTS);
|
|
124
|
+
const caps = deps.caps ? observe('caps', deps.caps) : cfg === null ? null : observe('caps', () => cfg.seatCaps);
|
|
125
|
+
const running = deps.running ? observe('running', deps.running) : cfg === null ? null : observe('running', () => {
|
|
126
|
+
const observation = defaultListHarnessProcesses();
|
|
127
|
+
if (observation.status !== 'ok') throw new Error(`process observation ${observation.status}`);
|
|
128
|
+
const processes = observation.records.map(process => ({ ...process,
|
|
129
|
+
seat: seatOfTree(process.cwdStatus === 'unknown' ? undefined : process.cwd, cfg) }));
|
|
130
|
+
const measured = trafficTick({ processes, now, caps: cfg.seatCaps, openCells: [] });
|
|
131
|
+
if (measured.unassigned) throw new Error(`${measured.unassigned} unassigned harness launch process(es)`);
|
|
132
|
+
return Object.fromEntries(measured.seats.map(row => [row.seat, row.running]));
|
|
133
|
+
});
|
|
134
|
+
const queued = observe('queue', deps.queued ?? (() => listHarnessQueue({ root: deps.root })));
|
|
135
|
+
const releaseRoot = deps.root ?? releaseLedgerRoot();
|
|
136
|
+
const versions = observe('versions', deps.versions ?? (() => defaultVersions(now, releaseRoot)));
|
|
137
|
+
const cells = versions === null ? null : observe('checklist', () => versions.flatMap(version => {
|
|
138
|
+
if (!deps.cells && !existsSync(join(releaseRoot, 'release', 'features.sqlite')))
|
|
139
|
+
throw new Error('checklist ledger unavailable');
|
|
140
|
+
return deps.cells?.(version) ?? listChecklist(version, deps.root).items.map(item => ({ ...item, version }));
|
|
141
|
+
}));
|
|
142
|
+
const overlaps = observe('overlap', deps.overlaps ?? (() => {
|
|
143
|
+
const observed = collectWork();
|
|
144
|
+
if (observed.unreadableTrees || observed.unreadableGoals || observed.work.some(item => item.unreadable))
|
|
145
|
+
throw new Error(`incomplete work observation: trees=${observed.unreadableTrees} goals=${observed.unreadableGoals}`);
|
|
146
|
+
return observed.work;
|
|
147
|
+
}));
|
|
148
|
+
const result = authorDepth({ seats, now, caps: caps ?? {}, running, queued, cells, overlaps, unreadable });
|
|
149
|
+
const log = deps.log ?? ((category: string, event: string, data: Record<string, unknown>) => debug.log(category, event, data));
|
|
150
|
+
for (const row of result.seats) log('loop.orchestrator', 'would-author', {
|
|
151
|
+
seat: row.seat, target: row.target, have: row.queued, short: row.short, cells: row.wouldAuthor,
|
|
152
|
+
});
|
|
153
|
+
const ledger = deps.ledger ?? new AuthorLedger(deps.root === undefined ? {} : { path: join(deps.root, 'orchestrator', 'author-ledger.sqlite') });
|
|
154
|
+
const receiptCounts = { held: 0, queuedForAuthor: 0 };
|
|
155
|
+
let receiptFailures = 0;
|
|
156
|
+
for (const row of result.seats) for (const candidate of row.wouldAuthor) {
|
|
157
|
+
const cell = cells?.find(item => item.id === candidate.cellId && item.version === candidate.version);
|
|
158
|
+
try {
|
|
159
|
+
if (!cell) throw new Error('selected cell unavailable');
|
|
160
|
+
const text = cell.evidence ?? '';
|
|
161
|
+
const check = checkAuthorInput({ title: cell.title, text, cellId: cell.id, version: cell.version });
|
|
162
|
+
const receipt = ledger.request({ seat: row.seat, cellId: cell.id, version: cell.version, title: cell.title,
|
|
163
|
+
text, check: { verdict: check.verdict, signals: check.signals, ratio: check.implementationRatio } });
|
|
164
|
+
if (receipt.status === 'held') receiptCounts.held++;
|
|
165
|
+
if (receipt.status === 'queued-for-author') receiptCounts.queuedForAuthor++;
|
|
166
|
+
} catch (error) {
|
|
167
|
+
receiptFailures++;
|
|
168
|
+
result.unreadable.push({ source: 'author-ledger', reason: `${candidate.version} ${candidate.cellId}: ${error instanceof Error ? error.message : String(error)}` });
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
result.receiptCounts = receiptCounts;
|
|
172
|
+
result.receiptFailures = receiptFailures;
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const SHADOW_INTERVAL_MS = 30 * 60_000;
|
|
177
|
+
|
|
178
|
+
/** The queue tick serializes callers; stamp before collection so even a failing shadow cannot run every two minutes. */
|
|
179
|
+
export function runAuthorDepthShadow(root = effectiveInstanceRoot(), now = new Date(), collect = collectAuthorDepth): void {
|
|
180
|
+
const path = join(root, 'orchestrator', 'author-depth-shadow-at');
|
|
181
|
+
const time = now.getTime();
|
|
182
|
+
if (!Number.isFinite(time)) throw new Error('invalid author-depth shadow clock');
|
|
183
|
+
if (existsSync(path)) {
|
|
184
|
+
const last = Date.parse(readFileSync(path, 'utf8').trim());
|
|
185
|
+
if (!Number.isFinite(last)) throw new Error('invalid author-depth shadow marker');
|
|
186
|
+
if (time - last < SHADOW_INTERVAL_MS) return;
|
|
187
|
+
}
|
|
188
|
+
mkdirSync(join(root, 'orchestrator'), { recursive: true });
|
|
189
|
+
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
190
|
+
try {
|
|
191
|
+
writeFileSync(temporary, now.toISOString(), { flag: 'wx', mode: 0o600 });
|
|
192
|
+
renameSync(temporary, path);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
try { unlinkSync(temporary); } catch { /* No temporary file to remove. */ }
|
|
195
|
+
throw error;
|
|
196
|
+
}
|
|
197
|
+
const result = collect({ root, now });
|
|
198
|
+
if (result.unreadable.length || result.receiptFailures || !result.receiptCounts) {
|
|
199
|
+
throw new Error(`author-depth shadow incomplete: ${JSON.stringify({ unreadable: result.unreadable, receiptFailures: result.receiptFailures })}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function authorDepthLines(result: AuthorDepthResult): string[] {
|
|
204
|
+
return result.seats.map(row => `${row.seat} ${row.running ?? '?'}/${row.cap ?? '?'} · 대기 ${row.queued ?? '?'} · 목표 ${row.target ?? '?'} · 모자람 ${row.short ?? '?'} → ${row.wouldAuthor.map(cell => cell.cellId).join(', ') || '없음'}`);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (import.meta.main) {
|
|
208
|
+
try {
|
|
209
|
+
if (process.argv.slice(2).some(arg => arg !== '--json')) throw new Error('usage: author-depth.ts [--json]');
|
|
210
|
+
const result = collectAuthorDepth();
|
|
211
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(result));
|
|
212
|
+
else {
|
|
213
|
+
for (const line of authorDepthLines(result)) console.log(line);
|
|
214
|
+
for (const source of result.unreadable) console.error(`author-depth ${source.source}: ${source.reason}`);
|
|
215
|
+
}
|
|
216
|
+
} catch (error) {
|
|
217
|
+
console.error(`author-depth: ${error instanceof Error ? error.message : String(error)}`);
|
|
218
|
+
process.exitCode = 1;
|
|
219
|
+
}
|
|
220
|
+
}
|