elanous 0.2.17 → 0.2.19
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/J5H7BvqgSI8DUjtRRcA_S/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1157-cba0e8553d35a980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{726.83532d69452ee24b.js → 1470.98a6ab61ccb4131c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/171-23a877ed11720de9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2336-e830c777eb6bce04.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9247-269e4464cc4b491c.js → 2375-e061bb24cd2b5cde.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{211-9c9090c59e2c5907.js → 2977-e38442e86289f1d3.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f6b7c9ba.64a895b8b2fedbdc.js → 2ea96958.e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3094.67339318c7f72a3a.js +2 -0
- package/apps/pwa/out/_next/static/chunks/338-ceb956612dcfaa86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3395-835e4ceaa6335ba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3441-be82e3595aef3a20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3689-89905039bf3e7257.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3822-6f268bc26c6a84ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1268.0840ff91e083fcbd.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4457-d7df81c90f9d0268.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4551-77dadae76f6cbace.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{6110.1297d4093fdfbb0d.js → 4847.56f5bc454fbcc827.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/4978.6fd93b974441e87f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-f746f6549b869f91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7327-2d69fb321ba73389.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/62-5bfed1e23790b745.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-9bc8561fde416e07.js +1 -0
- package/apps/pwa/out/_next/static/chunks/649-ca96d1c5d09e02b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6726-85a7db59b288c53f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6735-33267c4630d0d963.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-141682015873beb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{935.4ddaa99fc1d1126c.js → 7647.2fcd30d894d5ee30.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{f6231284-45726550aba08fca.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-143791231c99bd02.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8430-a10cfba8ef282d4e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8870-9614a213b990b10b.js +25 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-12c2f30dea62291c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/976-4a9b4ebdca7c739a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-15cdf568bafa9fc8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-e5b1ef00b2dbceca.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-deef5ee191830f14.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-b193da485f9a14b2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-b6e1a79df0e214f4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-e84df4d907f703c7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a5fb9a5db89ae4ea.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1499f481d7fc52d6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-c7cf753aeb648790.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4118469e514e10fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e968f7630654332a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/decisions/page-505915052f947022.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c2f962fb4a3b06c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7b5ed0bd8a589ec4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-cf8193250dd42dd4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-cf3f5dfa35dea8e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-5b29d9b06ceadade.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-a15e7fcc283e9ca7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d2915e7b70ff3d0e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-b70cd005d9169e25.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-2595f0eba29f57e1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-c776322454156e36.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-77bbd17f4435aa10.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-049689e611ecc936.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-85954a4fb09c44eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-e773e552e1068ee6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-5633c4bfa7684b4a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-d829b92bd01b4994.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-061c317fc771becf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-3a45ece8865ed4b6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-0be990618c0d56bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-bfea4865dc244b73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-0d6ba8e337741484.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-76a88b55d816482e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-bba1542df59f61ce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4185c95689bb372e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-b699e1a8d5d34b8b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-c225e338c31bf274.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-449353f70507ca1f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-90b889714102476d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-f1ee7fbeb7f96ddc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-d59a95e1434dfaa0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-71491a23414d1a47.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-de7112bdb65fa4fe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-feb4d0d88079660d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-f17d887b40dd6b1d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-8da77be2192bcd5d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-72c3b9a5e9582b29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3afe4c8d0c48c39.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-b9308f2d8a931531.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-6f35f9d892fa630b.js +1 -0
- package/apps/pwa/out/_next/static/css/{e99e3cff94ced78a.css → 599369d853c61df7.css} +1 -1
- package/apps/pwa/out/_next/static/css/85f53c6f35d11382.css +1 -0
- package/apps/pwa/out/_next/static/css/d3427996da11e480.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 -1
- package/apps/pwa/out/ceo/index.txt +23 -23
- 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/decisions/index.html +1 -0
- package/apps/pwa/out/decisions/index.txt +30 -0
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +25 -24
- 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 +24 -23
- 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 -1
- package/apps/pwa/out/loops/index.txt +28 -27
- 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/ops/guardian.yaml +26 -0
- package/graphs/ops/recipes.yaml +3 -0
- package/package.json +2 -1
- package/samples/workflows/templates/approval-gated-deploy.yaml +30 -0
- package/samples/workflows/templates/chat-driven-research.yaml +35 -0
- package/samples/workflows/templates/discord-listen-and-respond.yaml +29 -0
- package/samples/workflows/templates/http-poll-then-alert.yaml +30 -0
- package/samples/workflows/templates/manual-quickstart.yaml +21 -0
- package/samples/workflows/templates/scheduled-daily-digest.yaml +30 -0
- package/samples/workflows/templates/webhook-deploy-hook.yaml +27 -0
- package/scripts/ci-public-export-import-gate.ts +7 -2
- package/scripts/ci-typecheck-changed.ts +21 -6
- package/scripts/coord-channel-watch.sh +2 -2
- package/scripts/discord-seat-probe.ts +6 -3
- package/scripts/docs/generate-cli-anatomy.ts +162 -0
- package/scripts/goals/authoring-metrics.ts +73 -0
- package/scripts/landing-heal/must-fix-match.ts +7 -7
- package/scripts/landing-heal/run-mission.ts +3 -2
- package/scripts/lib/pch-regress-map.ts +23 -0
- package/scripts/morning-report.ts +18 -4
- package/scripts/pch-regress.ts +88 -0
- package/scripts/public-export-test-run.ts +37 -2
- package/scripts/release-loop/checklist-gate-node.ts +13 -11
- package/scripts/release-loop/cut-branch.ts +2 -1
- package/scripts/release-loop/cutoff.ts +4 -2
- package/scripts/release-loop/docs-land-node.ts +8 -1
- package/scripts/release-loop/docs-node.ts +1 -1
- package/scripts/release-loop/gate-diff.ts +18 -0
- package/scripts/release-loop/gate-node.ts +436 -59
- package/scripts/release-loop/gate-partial.ts +106 -0
- package/scripts/release-loop/gate-timing.ts +120 -0
- package/scripts/release-loop/node-verdict.ts +2 -1
- package/scripts/release-loop/npm-publish-node.ts +11 -7
- package/scripts/release-loop/ops-upgrade-node.ts +8 -1
- package/scripts/release-loop/preflight.ts +246 -0
- package/scripts/release-loop/publish-node.ts +8 -1
- package/scripts/release-loop/release-readiness.ts +11 -6
- package/scripts/release-loop/release-version.ts +48 -0
- package/scripts/release-loop/resume-release.ts +174 -0
- package/scripts/release-loop/shard-plan.ts +65 -0
- package/scripts/release-loop/unattended-release.ts +45 -7
- package/scripts/release-loop/version-node.ts +145 -40
- package/scripts/rhythm/daily.ts +25 -4
- package/scripts/skills/public-check.ts +62 -12
- package/scripts/test-deterministic.ts +51 -33
- package/scripts/test-diet/effectiveness.ts +62 -1
- package/scripts/test-diet/lib.ts +1 -1
- package/scripts/test-diet/node.ts +8 -1
- package/scripts/video/scene-table.ts +159 -0
- package/src/acp/core-turn-bridge.ts +39 -2
- package/src/agent-mission/review-loop.ts +4 -3
- package/src/agent-substrate/pr-comment-meta.ts +20 -0
- package/src/autopilot/build/llm-conflict-merge.ts +28 -8
- package/src/away/presence.ts +40 -0
- package/src/away/release-watch.ts +255 -0
- package/src/briefing/brief-items.ts +86 -17
- package/src/chat/index.ts +2 -1
- package/src/chat/short-question.ts +3 -40
- package/src/cli/autopilot-cli.ts +30 -11
- package/src/cli/away-cli.ts +68 -0
- package/src/cli/brief-cli.ts +36 -2
- package/src/cli/freeze-cli.ts +3 -2
- package/src/cli/guardian-cli.ts +49 -0
- package/src/cli/intake-cli.ts +85 -7
- package/src/cli/log-event-names.ts +1 -0
- package/src/cli/notify-cli.ts +75 -0
- package/src/cli/pod-cli.ts +5 -1
- package/src/cli/provider-cli.ts +12 -2
- package/src/cli/pty-drive-cli.ts +14 -3
- package/src/cli/release-cli.ts +128 -19
- package/src/cli/setup-cli.ts +31 -1
- package/src/cli/tasks-cli.ts +403 -4
- package/src/context-bus/condense.ts +19 -3
- package/src/context-bus/context-day.ts +77 -24
- package/src/context-bus/context-first.ts +33 -0
- package/src/context-bus/context-now-public.ts +1 -1
- package/src/context-bus/context-now-surfaces.ts +16 -6
- package/src/context-bus/context-now.ts +97 -3
- package/src/core-turn/short-question.ts +100 -0
- package/src/dashboard/dashboard-status-lines.ts +24 -4
- package/src/dashboard/index.ts +50 -5
- package/src/dashboard/input/slash-executor.ts +6 -2
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +63 -36
- package/src/dashboard/slash-runtime/decide-slash.ts +60 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +21 -8
- package/src/dashboard/slash-runtime/help-groups.ts +44 -0
- package/src/dashboard/slash-runtime/model-choices.ts +125 -0
- package/src/discord-self-message.ts +20 -2
- package/src/discord-slash-wire.ts +1 -0
- package/src/domains/guardian-tick.ts +138 -0
- package/src/domains/outbound-alert.ts +244 -40
- package/src/domains/outbound-log-probe.ts +16 -1
- package/src/domains/release-tool.ts +2 -2
- package/src/domains/schedule-registry.ts +23 -0
- package/src/domains/telegram-kind-route.ts +39 -0
- package/src/domains/watch-default-scheduler.ts +95 -0
- package/src/esc-abort-gate.ts +44 -0
- package/src/execution-loop/gates.ts +11 -2
- package/src/flow-loop/split.ts +8 -4
- package/src/git-fs/worktree.ts +23 -3
- package/src/graph-runner/runner.ts +25 -5
- package/src/harness/harness-cli-command.ts +12 -2
- package/src/harness/harness-queue.ts +388 -33
- package/src/harness/harness-salvage-cli.ts +78 -0
- package/src/harness/harness-worktree-add.ts +68 -0
- package/src/harness/helper-scan.ts +10 -3
- package/src/harness/ledger-write-guard.ts +6 -16
- package/src/hq/hq.ts +6 -1
- package/src/hq/seats.ts +369 -0
- package/src/index.ts +185 -18
- package/src/instance/test-write-guard-spy-first.fixture.ts +13 -0
- package/src/instance/test-write-guard.ts +200 -0
- package/src/intake-plane/adopt-gate.ts +49 -0
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/collect-github.ts +57 -25
- package/src/intake-plane/digest.ts +39 -15
- package/src/intake-plane/discord-seat-work.ts +8 -2
- package/src/intake-plane/harness-target.ts +4 -3
- package/src/intake-plane/intake-sources.ts +1 -0
- package/src/intake-plane/items.ts +1 -0
- package/src/intake-plane/requirement-funnel.ts +316 -0
- package/src/knowledge/know-find.ts +10 -1
- package/src/loops/budget.ts +36 -0
- package/src/loops/orchestrator/degradation.ts +57 -0
- package/src/loops/orchestrator/finish-rate.ts +106 -4
- package/src/loops/orchestrator/grid-place.ts +11 -0
- package/src/loops/orchestrator/tick.ts +450 -54
- package/src/maturity/feature-maturity.ts +8 -5
- package/src/maturity/tui-slash-maturity.ts +1 -0
- package/src/mss/logging/log-store.ts +3 -0
- package/src/nexus/api/decisions-route.ts +11 -1
- package/src/nexus/api/http-server.ts +16 -5
- package/src/nexus/api/loop-edges.ts +352 -23
- package/src/nexus/api/ops-api.ts +96 -28
- package/src/nexus/api/ops-seats-sources.ts +2 -2
- package/src/nexus/api/workflow-templates.ts +3 -0
- package/src/nexus/index.ts +7 -0
- package/src/oauth/codex.ts +38 -0
- package/src/onboarding/first-chat-gate.ts +29 -0
- package/src/release-loop/checklist.ts +106 -6
- package/src/release-loop/feature-store.ts +64 -16
- package/src/release-loop/landing-freeze.ts +6 -4
- package/src/release-loop/light-rc.ts +173 -0
- package/src/release-loop/placement.ts +64 -20
- package/src/release-loop/rubric.ts +136 -0
- package/src/seat-dispatch/seat-ask.ts +79 -16
- package/src/seat-loop/seat-loop.ts +12 -6
- package/src/self-dev/ask-launch-flow.ts +24 -3
- package/src/self-dev/ask-path-candidates.ts +68 -0
- package/src/self-dev/dev-cli.ts +36 -0
- package/src/self-dev/dev-pipeline.ts +4 -0
- package/src/self-dev/draft-triage-rules.ts +1 -1
- package/src/self-dev/orchestrate-cli.ts +22 -0
- package/src/self-dev/orchestrate.ts +1 -0
- package/src/self-dev/pr-preview.ts +179 -0
- package/src/self-dev/round-pr-supersede.ts +244 -0
- package/src/self-dev/run-supervisor.ts +14 -0
- package/src/self-dev/shard-run.ts +23 -7
- package/src/self-implement/follow-up-goals.ts +2 -2
- package/src/self-implement/gate-cli.ts +29 -8
- package/src/self-implement/gate-remote.ts +427 -0
- package/src/self-implement/goal-author-clarification.ts +88 -13
- package/src/self-implement/goal-author.ts +8 -3
- package/src/self-implement/goal-clarification-escalation.ts +2 -0
- package/src/self-implement/l8-merge-queue.ts +29 -5
- package/src/self-implement/orchestrator.ts +61 -13
- package/src/self-implement/prior-draft-findings.ts +4 -3
- package/src/self-implement/salvage-branches.ts +254 -0
- package/src/skills/skill-feedback.ts +76 -0
- package/src/task-agent/actions.ts +101 -7
- package/src/task-agent/board.ts +132 -0
- package/src/task-agent/card-evidence.ts +203 -0
- package/src/task-agent/completion-evidence.ts +126 -0
- package/src/task-agent/judge.ts +49 -5
- package/src/task-agent/live-moves.ts +176 -0
- package/src/task-agent/mission.ts +449 -0
- package/src/task-agent/shadow.ts +258 -0
- package/src/task-agent/task-hand.ts +337 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +64 -4
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +10 -0
- package/src/task-orchestrator/surfaces/pod-lease.ts +142 -13
- package/src/task-orchestrator/surfaces/pod-pool.ts +13 -5
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +88 -20
- package/src/telegram-away-command.ts +38 -0
- package/src/telegram-commands.ts +11 -0
- package/src/telegram-loops-command.ts +1 -1
- package/src/telegram-report-log-probe.ts +21 -0
- package/src/telegram-report.ts +27 -3
- package/src/telegram.ts +5 -20
- package/src/tui-client/dashboard-session.ts +1 -0
- package/src/user-config.ts +196 -4
- package/src/version/packed-revision.json +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-aac7332576c4d6fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1844-9ec4b1d12f6e80eb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1948.15f356b15a625597.js +0 -5
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +0 -1
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4614-21403aac730d4586.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4795-f372490b178583fc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +0 -2
- package/apps/pwa/out/_next/static/chunks/6877-fe94e76a5841ee9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8401-4ce1c42c8d89903f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +0 -25
- package/apps/pwa/out/_next/static/chunks/8949-ecb4128a0858b911.js +0 -1
- package/apps/pwa/out/_next/static/chunks/899-f41be85e8a680651.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9103-60fbef33e66f0a46.js +0 -1
- package/apps/pwa/out/_next/static/chunks/a9f2b54d-7865aba0f4d2ee49.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-3143b07a817d1da9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-17ba086ef82d339b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-7eb661ff23af7da2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-6b8444914da74ca6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-6e5c3a867557298b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-7110f732f45bf944.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-9a41d65355fc25b9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-5e77cd1802567c89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-41afec0b255ed2bb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4187666132fc98c4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-09328d499162bae4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-29ff3a8f84f03b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-80ec7401bc83f511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-ab93b240f687d69a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-fc7d86467291ae38.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-de43a186e5ba5683.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8b08cafad27759c9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/loops/page-0e2d6985fb42cd2f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-13e4108999f58d13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-e8296ee284aa1293.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-043a9111e74f6b24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-23a4601ceb7f452e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-1ed1324b1dc36754.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-10d958605a77b3bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-166745e51ac0c7b0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-a04260b8ccc968d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-7a1d85e121065cc8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1f50e3d4e13435f4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-12916918988ac97e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-d8a3347a9d0c3f4a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-a9117d2cd3093d36.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-5a64cb5208c6a8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1407ec83ea64c1f9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-1d824510df025d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-6e878ebbe1737d00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-7f49fe645d5132f7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-81a4e7f8dbfe74cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-27d71f131acd9320.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-3ab59d19913b200e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-bf85b2f5bf077925.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-424659bacfb8cdd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-ab32a8f522a60371.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-fc2df261f8a2c7df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-09461366af82ef13.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d4fef9ccd7d406b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ea90a8ac73ce2749.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +0 -1
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +0 -1
- package/apps/pwa/out/_next/static/yRA5ur6DcPl0YHSRheH2O/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{yRA5ur6DcPl0YHSRheH2O → J5H7BvqgSI8DUjtRRcA_S}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,316 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { elanousStateRoot } from '../autopilot/state-paths.js';
|
|
5
|
+
import { normalizeClaim, type AddBriefItem } from '../briefing/brief-items.js';
|
|
6
|
+
import { createRequire } from 'node:module';
|
|
7
|
+
import { debug } from '../debug/log.js';
|
|
8
|
+
import { listChecklist, setItem, type ChecklistItem } from '../release-loop/checklist.js';
|
|
9
|
+
import { placeCell, type PlacementDeps } from '../release-loop/placement.js';
|
|
10
|
+
import { listSchedules } from '../release-loop/release-schedule.js';
|
|
11
|
+
import { getUserConfig } from '../user-config.js';
|
|
12
|
+
import { dateKey } from '../time/format.js';
|
|
13
|
+
import { cardIndexPath, CardStore, type TaskCard } from '../task-cards/card-store.js';
|
|
14
|
+
import { loadIntakeLedger, type IntakeItem } from './items.js';
|
|
15
|
+
import { parseRubric, rubricGrade, rubricScore } from '../release-loop/rubric.js';
|
|
16
|
+
|
|
17
|
+
export type RequirementSource = 'directive' | 'intake' | 'grounding' | 'coordination' | 'linear';
|
|
18
|
+
export interface RequirementCandidate {
|
|
19
|
+
id: string;
|
|
20
|
+
source: RequirementSource;
|
|
21
|
+
text: string;
|
|
22
|
+
link: string;
|
|
23
|
+
receivedAt: string;
|
|
24
|
+
priority?: 'P0' | 'P1' | 'P2';
|
|
25
|
+
risk?: number;
|
|
26
|
+
cost?: number;
|
|
27
|
+
security?: number;
|
|
28
|
+
license?: string;
|
|
29
|
+
patentPrivatePath?: boolean;
|
|
30
|
+
owner?: string;
|
|
31
|
+
deadlineVersion?: string;
|
|
32
|
+
}
|
|
33
|
+
export type IncomingRequirement = Omit<RequirementCandidate, 'source'>;
|
|
34
|
+
export interface RequirementSources {
|
|
35
|
+
directives?: readonly DirectiveRow[];
|
|
36
|
+
intake?: readonly IntakeItem[];
|
|
37
|
+
grounding?: readonly IncomingRequirement[];
|
|
38
|
+
coordination?: readonly IncomingRequirement[];
|
|
39
|
+
linear?: readonly IncomingRequirement[];
|
|
40
|
+
}
|
|
41
|
+
export interface RequirementDecision {
|
|
42
|
+
candidate: RequirementCandidate;
|
|
43
|
+
sources: RequirementCandidate[];
|
|
44
|
+
status: 'adopted' | 'would-adopt' | 'proposal';
|
|
45
|
+
cellId?: string;
|
|
46
|
+
version?: string;
|
|
47
|
+
rubric: string;
|
|
48
|
+
reason?: string;
|
|
49
|
+
}
|
|
50
|
+
export interface RequirementFunnelResult {
|
|
51
|
+
counts: Record<RequirementSource, number>;
|
|
52
|
+
candidates: number;
|
|
53
|
+
decisions: RequirementDecision[];
|
|
54
|
+
unlinkedDirectives: RequirementCandidate[];
|
|
55
|
+
briefItems: AddBriefItem[];
|
|
56
|
+
}
|
|
57
|
+
export interface FunnelOptions {
|
|
58
|
+
sources: RequirementSources;
|
|
59
|
+
/** Older directives are only checked for unlinked P0 backlog, not counted as today's arrivals. */
|
|
60
|
+
historicalDirectives?: readonly DirectiveRow[];
|
|
61
|
+
cells: readonly { version: string; item: ChecklistItem }[];
|
|
62
|
+
now: Date;
|
|
63
|
+
mode?: 'shadow' | 'live';
|
|
64
|
+
/** Required for live external adoption; the unresolved daily limit has no implicit value. */
|
|
65
|
+
dailyCap?: number;
|
|
66
|
+
adoptedToday?: number;
|
|
67
|
+
allowedLicenses?: readonly string[];
|
|
68
|
+
placement?: PlacementDeps;
|
|
69
|
+
adopt?: (candidate: RequirementCandidate, version: string, cellId: string, evidence: string) => void;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** How many of the last 24 hours' unlinked directives the single P0 brief line names (the full backlog is one count). */
|
|
73
|
+
export const UNLINKED_DIRECTIVES_SHOWN = 5;
|
|
74
|
+
/** ABSORB-ADOPT daily limit when config sets none (OP 10-06 22:59 · the cell's own number). */
|
|
75
|
+
export const DEFAULT_DAILY_CAP = 5;
|
|
76
|
+
/** Directive row fields the funnel reads — kept local because `src/directives/directive-index` is excluded from the public export. */
|
|
77
|
+
export interface DirectiveRow { ts: string; track: string | null; text: string; source_file: string; line_no: number }
|
|
78
|
+
|
|
79
|
+
/** Loads the private directive index at run time only (same pattern as decisions/proact-meter), so public builds bundle cleanly. */
|
|
80
|
+
function openDirectiveIndex(stateDir: string): { dailyRequirements(since: string, until: string): DirectiveRow[]; close(): void } {
|
|
81
|
+
const spec = ['..', 'directives', 'directive-index.js'].join('/');
|
|
82
|
+
const mod = createRequire(import.meta.url)(spec) as { DirectiveIndex: new (o: { stateDir?: string }) => ReturnType<typeof openDirectiveIndex> };
|
|
83
|
+
return new mod.DirectiveIndex({ stateDir });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const oneLine = (value: string) => value.replace(/\s+/g, ' ').trim();
|
|
87
|
+
const idFor = (source: RequirementSource, key: string) => createHash('sha256').update(`${source}:${key}`).digest('hex').slice(0, 16);
|
|
88
|
+
|
|
89
|
+
export function directiveCandidates(rows: readonly DirectiveRow[]): RequirementCandidate[] {
|
|
90
|
+
return rows.map(row => ({ id: idFor('directive', `${row.source_file}:${row.line_no}`), source: 'directive',
|
|
91
|
+
text: oneLine(row.text), link: `${row.source_file}#L${row.line_no}`, receivedAt: row.ts,
|
|
92
|
+
priority: 'P2', owner: row.track && /^[A-Z]{2,8}$/.test(row.track) ? row.track : 'OP' }));
|
|
93
|
+
}
|
|
94
|
+
export function intakeCandidates(rows: readonly IntakeItem[]): RequirementCandidate[] {
|
|
95
|
+
return rows.map(row => ({ id: row.id, source: 'intake', text: oneLine(row.text || row.title || ''),
|
|
96
|
+
link: row.url || `intake:${row.id}`, receivedAt: row.observedAt }));
|
|
97
|
+
}
|
|
98
|
+
export function suppliedCandidates(source: 'grounding' | 'coordination' | 'linear', rows: readonly IncomingRequirement[]): RequirementCandidate[] {
|
|
99
|
+
return rows.map(row => ({ ...row, source, text: oneLine(row.text) }));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function linkFor(candidate: RequirementCandidate, cells: FunnelOptions['cells']): FunnelOptions['cells'][number] | undefined {
|
|
103
|
+
return cells.find(({ item }) => !!candidate.link && !!item.evidence && item.evidence.includes(candidate.link));
|
|
104
|
+
}
|
|
105
|
+
function gate(candidate: RequirementCandidate, options: FunnelOptions, used: number): string | null {
|
|
106
|
+
if (candidate.priority !== 'P1' && candidate.priority !== 'P2') return 'P1/P2 판정 없음';
|
|
107
|
+
if (candidate.priority === 'P1' && !candidate.deadlineVersion) return 'P1 마감 판 미확인';
|
|
108
|
+
if (candidate.risk === undefined || !Number.isFinite(candidate.risk) || candidate.risk > 1 || candidate.risk < 0) return '위험≤1 미확인';
|
|
109
|
+
if (candidate.cost !== 0) return '돈 0 미확인';
|
|
110
|
+
if (candidate.security !== 0) return '보안 0 미확인';
|
|
111
|
+
if (!candidate.license || !options.allowedLicenses?.includes(candidate.license)) return '허용 라이선스 미확인';
|
|
112
|
+
if (candidate.patentPrivatePath !== false) return '특허 비공개 경로 무접촉 미확인';
|
|
113
|
+
if (options.dailyCap === undefined || !Number.isSafeInteger(options.dailyCap) || options.dailyCap < 0) return '하루 상한 미설정';
|
|
114
|
+
if (used >= options.dailyCap) return '하루 상한 도달';
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** RUBRIC-PLACE: use the release placement engine's priority, deadline, capacity and predecessor checks, not an invented score. */
|
|
119
|
+
export function runRequirementFunnel(options: FunnelOptions): RequirementFunnelResult {
|
|
120
|
+
const { sources, cells, now } = options;
|
|
121
|
+
const arrivals = [
|
|
122
|
+
...directiveCandidates(sources.directives ?? []), ...intakeCandidates(sources.intake ?? []),
|
|
123
|
+
...suppliedCandidates('grounding', sources.grounding ?? []),
|
|
124
|
+
...suppliedCandidates('coordination', sources.coordination ?? []),
|
|
125
|
+
...suppliedCandidates('linear', sources.linear ?? []),
|
|
126
|
+
].filter(candidate => candidate.text && Number.isFinite(Date.parse(candidate.receivedAt)) && Date.parse(candidate.receivedAt) <= now.getTime());
|
|
127
|
+
const counts: RequirementFunnelResult['counts'] = { directive: 0, intake: 0, grounding: 0, coordination: 0, linear: 0 };
|
|
128
|
+
for (const candidate of arrivals) counts[candidate.source]++;
|
|
129
|
+
const groups = new Map<string, RequirementCandidate[]>();
|
|
130
|
+
for (const candidate of arrivals) {
|
|
131
|
+
const key = normalizeClaim(candidate.text);
|
|
132
|
+
groups.set(key, [...(groups.get(key) ?? []), candidate]);
|
|
133
|
+
}
|
|
134
|
+
const decisions: RequirementDecision[] = [];
|
|
135
|
+
let used = options.adoptedToday ?? 0;
|
|
136
|
+
for (const members of groups.values()) {
|
|
137
|
+
members.sort((a, b) => a.receivedAt.localeCompare(b.receivedAt));
|
|
138
|
+
const candidate = members.find(member => member.source === 'directive') ?? members[0]!;
|
|
139
|
+
const linked = members.map(member => linkFor(member, cells)).find(Boolean);
|
|
140
|
+
const evidence = members.map(member => `${member.link} (${member.receivedAt})`).join(' · ');
|
|
141
|
+
if (linked) {
|
|
142
|
+
decisions.push({ candidate, sources: members, status: 'adopted', cellId: linked.item.id, version: linked.version,
|
|
143
|
+
rubric: `RUBRIC-PLACE ${linked.item.priority ?? '미지정'} · ${linked.version}` });
|
|
144
|
+
continue;
|
|
145
|
+
}
|
|
146
|
+
const isDirective = members.some(member => member.source === 'directive');
|
|
147
|
+
const reason = isDirective ? null : gate(candidate, options, used);
|
|
148
|
+
if (reason) {
|
|
149
|
+
decisions.push({ candidate, sources: members, status: 'proposal', reason, rubric: `ABSORB-ADOPT · ${reason}` });
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const cellId = candidate.id;
|
|
153
|
+
const priority = candidate.priority ?? 'P1';
|
|
154
|
+
const rubricInput = { id: cellId, title: candidate.text, owner: candidate.owner ?? 'OP', priority,
|
|
155
|
+
predecessors: [], ...(priority === 'P1' && candidate.deadlineVersion ? { deadlineVersion: candidate.deadlineVersion } : {}) };
|
|
156
|
+
let placement: ReturnType<typeof placeCell>;
|
|
157
|
+
try { placement = placeCell(rubricInput, { ...options.placement, now, dryRun: true }); }
|
|
158
|
+
catch (error) {
|
|
159
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
160
|
+
decisions.push({ candidate, sources: members, status: isDirective ? 'would-adopt' : 'proposal', reason,
|
|
161
|
+
rubric: `RUBRIC-PLACE · 판 배치 보류: ${reason}` });
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const rubric = `RUBRIC-PLACE ${priority} · ${placement.version}: ${placement.reason}`;
|
|
165
|
+
if (options.mode === 'live') {
|
|
166
|
+
if (!options.adopt) throw new Error('편입 기록자가 설정되지 않았다');
|
|
167
|
+
options.adopt(candidate, placement.version, cellId, evidence);
|
|
168
|
+
}
|
|
169
|
+
decisions.push({ candidate, sources: members, status: options.mode === 'live' ? 'adopted' : 'would-adopt',
|
|
170
|
+
cellId, version: placement.version, rubric });
|
|
171
|
+
if (!isDirective) used++;
|
|
172
|
+
}
|
|
173
|
+
const unlinkedDirectives = [...directiveCandidates(options.historicalDirectives ?? []).filter(candidate =>
|
|
174
|
+
candidate.text && Number.isFinite(Date.parse(candidate.receivedAt)) && Date.parse(candidate.receivedAt) <= now.getTime()
|
|
175
|
+
&& !linkFor(candidate, cells) && !arrivals.some(arrival => arrival.id === candidate.id)),
|
|
176
|
+
...decisions.filter(decision => decision.status !== 'adopted')
|
|
177
|
+
.flatMap(decision => decision.sources.filter(source => source.source === 'directive'))]
|
|
178
|
+
.sort((a, b) => a.receivedAt.localeCompare(b.receivedAt));
|
|
179
|
+
const briefItems: AddBriefItem[] = [];
|
|
180
|
+
const summary = `들어온 요구 ${groups.size}(문별 지시 ${counts.directive} · 흡수 ${counts.intake} · 그라운딩 ${counts.grounding} · 조율 ${counts.coordination} · Linear ${counts.linear}) · 편입 ${decisions.filter(d => d.status === 'adopted').length} · 했을 것 ${decisions.filter(d => d.status === 'would-adopt').length} · 제안 ${decisions.filter(d => d.status === 'proposal').length}`;
|
|
181
|
+
if (arrivals.length || unlinkedDirectives.length) briefItems.push({ text: summary, domain: '흡수', priority: 'P1', source: 'requirement-funnel' });
|
|
182
|
+
for (const decision of decisions) {
|
|
183
|
+
const { candidate } = decision;
|
|
184
|
+
const status = decision.status === 'adopted' ? `편입 ${decision.cellId} · 판 ${decision.version}`
|
|
185
|
+
: decision.status === 'would-adopt' ? decision.version ? `했을 것 ${decision.cellId} · 판 ${decision.version}` : '편입 대상 · 판 배치 대기'
|
|
186
|
+
: `제안: ${decision.reason}`;
|
|
187
|
+
briefItems.push({ text: `${status} · ${candidate.text} · ${decision.rubric}`, domain: decision.status === 'proposal' ? '흡수' : '판',
|
|
188
|
+
priority: 'P1', source: 'requirement-funnel', evidence: decision.sources.map(member => `${member.link} (${member.receivedAt})`).join(' · ') });
|
|
189
|
+
}
|
|
190
|
+
// OP 10-06 22:59: the backlog can hold the whole directive history, so only the last 24 hours get named (one P0 line);
|
|
191
|
+
// the full backlog is a single count. Nothing recent → the count alone, at P1, so history cannot flood the brief.
|
|
192
|
+
if (unlinkedDirectives.length) {
|
|
193
|
+
const recent = unlinkedDirectives.filter(directive => now.getTime() - Date.parse(directive.receivedAt) <= 24 * 3_600_000);
|
|
194
|
+
const named = recent.slice(0, UNLINKED_DIRECTIVES_SHOWN);
|
|
195
|
+
const rest = recent.length - named.length;
|
|
196
|
+
const total = `전체 누계 ${unlinkedDirectives.length}`;
|
|
197
|
+
briefItems.push(recent.length
|
|
198
|
+
? { text: `대표 지시 중 아직 칸 없는 것 — 최근 24시간 ${recent.length}: ${named.map(directive => `${directive.text} (${directive.receivedAt})`).join(' · ')}${rest > 0 ? ` · 외 ${rest}` : ''} · ${total}`,
|
|
199
|
+
domain: '판', priority: 'P0', source: 'requirement-funnel', evidence: named.map(directive => directive.link).join(' · ') }
|
|
200
|
+
: { text: `대표 지시 중 아직 칸 없는 것 — 최근 24시간 0 · ${total}`, domain: '판', priority: 'P1', source: 'requirement-funnel' });
|
|
201
|
+
}
|
|
202
|
+
return { counts, candidates: groups.size, decisions, unlinkedDirectives, briefItems };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/** Existing ledgers only; no source collector or sender is started here. */
|
|
206
|
+
export function readRequirementFunnel(options: { stateDir?: string; now?: Date; mode?: 'shadow' | 'live'; dailyCap?: number;
|
|
207
|
+
allowedLicenses?: readonly string[]; grounding?: readonly IncomingRequirement[]; coordination?: readonly IncomingRequirement[];
|
|
208
|
+
linear?: readonly IncomingRequirement[]; placement?: PlacementDeps } = {}): RequirementFunnelResult {
|
|
209
|
+
const stateDir = options.stateDir ?? elanousStateRoot();
|
|
210
|
+
const now = options.now ?? new Date();
|
|
211
|
+
const config = (getUserConfig().raw.intake as { requirementFunnel?: { mode?: 'shadow' | 'live'; dailyCap?: number; allowedLicenses?: string[] } } | undefined)?.requirementFunnel;
|
|
212
|
+
const mode = options.mode ?? config?.mode ?? 'shadow';
|
|
213
|
+
if (mode !== 'shadow' && mode !== 'live') throw new Error(`알 수 없는 요구 깔때기 모드: ${mode}`);
|
|
214
|
+
const dailyCap = options.dailyCap ?? config?.dailyCap ?? DEFAULT_DAILY_CAP;
|
|
215
|
+
const allowedLicenses = options.allowedLicenses ?? config?.allowedLicenses;
|
|
216
|
+
let directives: DirectiveRow[] = [];
|
|
217
|
+
let historicalDirectives: DirectiveRow[] = [];
|
|
218
|
+
const since = new Date(Date.parse(`${dateKey(now, { timeZone: 'Asia/Seoul' })}T00:00:00+09:00`)).toISOString();
|
|
219
|
+
try {
|
|
220
|
+
const index = openDirectiveIndex(stateDir);
|
|
221
|
+
try {
|
|
222
|
+
directives = index.dailyRequirements(since, now.toISOString());
|
|
223
|
+
historicalDirectives = index.dailyRequirements('1970-01-01T00:00:00.000Z', since);
|
|
224
|
+
} finally { index.close(); }
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Public builds have no directive index; the other doors still report.
|
|
227
|
+
debug.log('intake.requirement-funnel', 'directives-unavailable', { error: error instanceof Error ? error.message : String(error) });
|
|
228
|
+
}
|
|
229
|
+
const schedules = listSchedules();
|
|
230
|
+
const cells = schedules.flatMap(schedule => listChecklist(schedule.version).items.map(item => ({ version: schedule.version, item })));
|
|
231
|
+
const ledger = loadIntakeLedger(stateDir);
|
|
232
|
+
const day = dateKey(now, { timeZone: 'Asia/Seoul' });
|
|
233
|
+
const todayCount = cells.filter(({ item }) => {
|
|
234
|
+
const adoptedAt = item.evidence?.match(/ABSORB-ADOPT@(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?Z)/)?.[1];
|
|
235
|
+
return adoptedAt && dateKey(adoptedAt, { timeZone: 'Asia/Seoul' }) === day;
|
|
236
|
+
}).length;
|
|
237
|
+
const cardRequests: Record<'coordination' | 'linear', IncomingRequirement[]> = { coordination: [], linear: [] };
|
|
238
|
+
if (existsSync(cardIndexPath(stateDir))) {
|
|
239
|
+
const cards = new CardStore(stateDir, true);
|
|
240
|
+
try {
|
|
241
|
+
for (const card of cards.listCards()) {
|
|
242
|
+
const section = card.sections.find(entry => entry.key === 'intake');
|
|
243
|
+
if (!section) continue;
|
|
244
|
+
let raw: unknown;
|
|
245
|
+
try { raw = JSON.parse(section.content); } catch { continue; }
|
|
246
|
+
if (!raw || typeof raw !== 'object') continue;
|
|
247
|
+
const input = raw as { source?: string; text?: string; ref?: string; replyTo?: { surface?: string } };
|
|
248
|
+
const source = input.source === 'linear' ? 'linear' : input.source === 'coordination' ? 'coordination' : null;
|
|
249
|
+
if (!source || !input.text) continue;
|
|
250
|
+
cardRequests[source].push({ id: card.goalId, text: oneLine(input.text.split('\n')[0] ?? ''),
|
|
251
|
+
link: input.ref || card.goalId, receivedAt: card.createdAt });
|
|
252
|
+
}
|
|
253
|
+
} finally { cards.close(); }
|
|
254
|
+
}
|
|
255
|
+
const fromQueue = (name: string): IncomingRequirement[] => {
|
|
256
|
+
const path = join(stateDir, 'intake', name);
|
|
257
|
+
if (!existsSync(path)) return [];
|
|
258
|
+
return readFileSync(path, 'utf8').split('\n').flatMap(line => {
|
|
259
|
+
if (!line.trim()) return [];
|
|
260
|
+
try {
|
|
261
|
+
const value: unknown = JSON.parse(line);
|
|
262
|
+
if (!value || typeof value !== 'object') return [];
|
|
263
|
+
const row = value as Partial<IncomingRequirement> & { path?: string };
|
|
264
|
+
const item = row.id ? ledger.items.get(row.id) : undefined;
|
|
265
|
+
const text = row.text ?? item?.text ?? item?.title;
|
|
266
|
+
const link = row.link ?? item?.url ?? row.path;
|
|
267
|
+
const receivedAt = row.receivedAt ?? (row as { at?: string }).at ?? item?.observedAt;
|
|
268
|
+
return typeof row.id === 'string' && typeof text === 'string' && typeof link === 'string' && typeof receivedAt === 'string'
|
|
269
|
+
? [{ ...row, id: row.id, text, link, receivedAt }] : [];
|
|
270
|
+
} catch { return []; }
|
|
271
|
+
});
|
|
272
|
+
};
|
|
273
|
+
// The grounding queue and the card store are cumulative; only today's rows are today's arrivals.
|
|
274
|
+
const today = (rows: readonly IncomingRequirement[]) => rows.filter(row => Date.parse(row.receivedAt) >= Date.parse(since));
|
|
275
|
+
return runRequirementFunnel({ sources: { directives, intake: [...ledger.items.values()].filter(item => Date.parse(item.observedAt) >= Date.parse(since)),
|
|
276
|
+
grounding: today(options.grounding ?? fromQueue('outbox/grounding.jsonl')),
|
|
277
|
+
coordination: today(options.coordination ?? cardRequests.coordination), linear: today(options.linear ?? cardRequests.linear) }, historicalDirectives, cells, now, mode,
|
|
278
|
+
dailyCap, adoptedToday: todayCount, allowedLicenses, placement: { ...options.placement, schedules },
|
|
279
|
+
adopt: (candidate, version, cellId, evidence) => {
|
|
280
|
+
placeCell({ id: cellId, title: candidate.text, owner: candidate.owner ?? 'OP', priority: candidate.priority ?? 'P1',
|
|
281
|
+
predecessors: [], ...(candidate.deadlineVersion ? { deadlineVersion: candidate.deadlineVersion } : {}) }, { ...options.placement, now, schedules });
|
|
282
|
+
setItem(version, cellId, { evidence: `${evidence} · ABSORB-ADOPT@${now.toISOString()}` }, 'OP');
|
|
283
|
+
} });
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// ── `intake candidates` — 읽기 전용 요구 후보 목록(지시·흡수·소원 · 명시 루브릭만 점수) ──
|
|
287
|
+
export interface FunnelCandidate {
|
|
288
|
+
source: 'directive' | 'intake' | 'wish';
|
|
289
|
+
id: string;
|
|
290
|
+
title: string;
|
|
291
|
+
score: number | null;
|
|
292
|
+
grade: 'P1' | 'P2' | 'P3' | 'P4' | null;
|
|
293
|
+
}
|
|
294
|
+
/** The candidate list keys directives by session — the funnel's local `DirectiveRow` plus the index's `session_id`. */
|
|
295
|
+
export type FunnelDirectiveRow = DirectiveRow & { session_id: string };
|
|
296
|
+
|
|
297
|
+
function funnelCandidate(source: FunnelCandidate['source'], id: string, title: string, original: string): FunnelCandidate {
|
|
298
|
+
const rubric = parseRubric(original);
|
|
299
|
+
const score = rubric === null ? null : rubricScore(rubric);
|
|
300
|
+
return { source, id, title: title.split(/\r?\n/, 1)[0]!.slice(0, 80), score, grade: score === null ? null : rubricGrade(score) };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
export function collectFunnelCandidates({ directives, intakeItems, cards }: {
|
|
304
|
+
directives: readonly FunnelDirectiveRow[];
|
|
305
|
+
intakeItems: readonly IntakeItem[];
|
|
306
|
+
cards: readonly TaskCard[];
|
|
307
|
+
}): FunnelCandidate[] {
|
|
308
|
+
return [
|
|
309
|
+
...directives.map((row) => funnelCandidate('directive', `${row.session_id}:${row.line_no}`, row.text, row.text)),
|
|
310
|
+
...intakeItems.filter((item) => item.status !== 'discarded')
|
|
311
|
+
.map((item) => funnelCandidate('intake', item.id, item.title ?? item.text ?? item.url ?? '', [item.title, item.text, item.url].filter(Boolean).join('\n'))),
|
|
312
|
+
...cards.filter((card) => card.status === 'open' && card.goalId.startsWith('wish:'))
|
|
313
|
+
.map((card) => funnelCandidate('wish', card.goalId, card.title, card.title)),
|
|
314
|
+
].sort((a, b) => a.score === null && b.score === null ? 0
|
|
315
|
+
: a.score === null ? 1 : b.score === null ? -1 : b.score - a.score);
|
|
316
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createRequire } from 'node:module';
|
|
2
2
|
import { LessonLedger, type LessonRow } from '../lessons/lesson-ledger.js';
|
|
3
|
-
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
3
|
+
import { DecisionLedger, type DecisionEntry, type SeatDecisionRecord } from '../decisions/decision-ledger.js';
|
|
4
4
|
import { devVersion, listChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
5
5
|
import { debug } from '../debug/log.js';
|
|
6
6
|
|
|
@@ -31,6 +31,7 @@ export interface KnowResult {
|
|
|
31
31
|
export interface KnowFindDeps {
|
|
32
32
|
directives: (query: string) => DirectiveRow[];
|
|
33
33
|
decisions: () => DecisionEntry[];
|
|
34
|
+
seatDecisions: () => SeatDecisionRecord[];
|
|
34
35
|
checklist: (version: string) => Checklist;
|
|
35
36
|
lessons: (query: string) => LessonRow[];
|
|
36
37
|
versions: () => string[];
|
|
@@ -49,6 +50,7 @@ const defaults: KnowFindDeps = {
|
|
|
49
50
|
try { return index.search(query); } finally { index.close(); }
|
|
50
51
|
},
|
|
51
52
|
decisions: () => new DecisionLedger().list({ status: 'all' }),
|
|
53
|
+
seatDecisions: () => new DecisionLedger().seatReport(),
|
|
52
54
|
checklist: (version) => listChecklist(version),
|
|
53
55
|
lessons: (query) => new LessonLedger().find(query),
|
|
54
56
|
versions,
|
|
@@ -84,6 +86,13 @@ export function knowFind(query: string, deps: Partial<KnowFindDeps> = {}): KnowR
|
|
|
84
86
|
ref: row.refs?.[0] ?? `decisions show ${row.id}` });
|
|
85
87
|
}
|
|
86
88
|
});
|
|
89
|
+
attempt('decision', () => {
|
|
90
|
+
for (const row of read.seatDecisions()) {
|
|
91
|
+
if (!matches([row.title, row.decision, row.delegation].join(' '), words)) continue;
|
|
92
|
+
rows.push({ source: 'decision', id: row.id, title: row.title, status: 'seat-posthoc',
|
|
93
|
+
at: row.decidedAt ?? row.recordedAt, current: false, ref: `decisions seat-report --seat ${row.seat}` });
|
|
94
|
+
}
|
|
95
|
+
});
|
|
87
96
|
attempt('checklist', () => {
|
|
88
97
|
for (const version of new Set(read.versions())) {
|
|
89
98
|
for (const row of read.checklist(version).items) {
|
package/src/loops/budget.ts
CHANGED
|
@@ -61,3 +61,39 @@ export function decideSpawn({ seat, running, caps, gate, injectedCap }: SpawnBud
|
|
|
61
61
|
? { allow: true, reason: 'within-cap', cap }
|
|
62
62
|
: { allow: false, reason: 'seat-cap', cap };
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/** SEAT-CAP-STALE: why a run or a queue row is left out of a seat's running count (counting only — nothing is stopped). */
|
|
66
|
+
export type SeatCapExclusionReason = 'stale' | 'soft-stopped' | 'unknown-seat' | 'dead-row';
|
|
67
|
+
export const SEAT_CAP_STALE_RUN_MINUTES = 30;
|
|
68
|
+
export const SEAT_CAP_UNKNOWN_SEAT_CAP = 2;
|
|
69
|
+
/** A just-launched row has no process yet; it keeps its seat for this long. */
|
|
70
|
+
export const SEAT_CAP_LAUNCH_GRACE_MINUTES = 5;
|
|
71
|
+
|
|
72
|
+
export type SeatCapSubject =
|
|
73
|
+
| { kind: 'run'; seat?: OrchestratorSeat; progressAt?: number; stopReason?: string }
|
|
74
|
+
| { kind: 'row'; status: string; receipt: 'started' | 'finished' | 'not-started' | null; live: boolean; launchedAt?: number };
|
|
75
|
+
|
|
76
|
+
export type SeatCapExclusion = { reason: SeatCapExclusionReason; idleMin: number | null } | null;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Pure judgement: null = the subject holds a seat. A run without a seat goes to the unknown-seat bucket; a run whose
|
|
80
|
+
* supervisor already stopped (awaiting harvest) or whose last progress is older than staleRunMinutes is excluded; a
|
|
81
|
+
* launched row with no live process past the launch grace is a dead row. A run with no progress signal still counts.
|
|
82
|
+
*/
|
|
83
|
+
export function seatCapExclusion(subject: SeatCapSubject, now: number,
|
|
84
|
+
{ staleRunMinutes = SEAT_CAP_STALE_RUN_MINUTES, launchGraceMinutes = SEAT_CAP_LAUNCH_GRACE_MINUTES }:
|
|
85
|
+
{ staleRunMinutes?: number; launchGraceMinutes?: number } = {}): SeatCapExclusion {
|
|
86
|
+
const minutes = (at: number | undefined): number | null =>
|
|
87
|
+
at !== undefined && Number.isFinite(at) ? Math.max(0, Math.floor((now - at) / 60_000)) : null;
|
|
88
|
+
if (subject.kind === 'row') {
|
|
89
|
+
if (subject.status !== 'launched' && subject.status !== 'launching') return null;
|
|
90
|
+
if (subject.live || subject.receipt === 'finished' || subject.receipt === 'not-started') return null;
|
|
91
|
+
const idle = minutes(subject.launchedAt);
|
|
92
|
+
return idle !== null && now - subject.launchedAt! >= launchGraceMinutes * 60_000 ? { reason: 'dead-row', idleMin: idle } : null;
|
|
93
|
+
}
|
|
94
|
+
const idle = minutes(subject.progressAt);
|
|
95
|
+
if (!subject.seat) return { reason: 'unknown-seat', idleMin: idle };
|
|
96
|
+
if (subject.stopReason) return { reason: 'soft-stopped', idleMin: idle };
|
|
97
|
+
if (idle !== null && now - subject.progressAt! >= staleRunMinutes * 60_000) return { reason: 'stale', idleMin: idle };
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// COORD-HA (0.2.19) — the signal the orchestrator tick reads for its degradation ladder (RFC loop map §A8b ④⑤):
|
|
2
|
+
// L1 = no usable LLM (rules only) · L2 = the task board cannot take a write (no new assignment). The tick already acts on
|
|
3
|
+
// `TickDeps.degradation`; until now nothing supplied it in production. Each probe answers «false» only on a measured
|
|
4
|
+
// failure — an unknown is not a degradation.
|
|
5
|
+
import { existsSync, mkdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { randomUUID } from 'node:crypto';
|
|
8
|
+
import { Database } from 'bun:sqlite';
|
|
9
|
+
import { cardIndexPath, taskCardsDir } from '../../task-cards/card-store.js';
|
|
10
|
+
import { resolveUsableLlm } from '../../llm/usable-llm.js';
|
|
11
|
+
|
|
12
|
+
export interface DegradationSignal { llm: boolean; board: boolean; why: string[] }
|
|
13
|
+
|
|
14
|
+
/** The task board takes a write: its folder is a directory a file can be created in (created and removed — a permission
|
|
15
|
+
* bit is not proof · review r1), and its index can take a write lock (busy 2 s · rolled back). */
|
|
16
|
+
export function probeBoardWritable(root: string): { ok: boolean; why?: string } {
|
|
17
|
+
const dir = taskCardsDir(root);
|
|
18
|
+
try {
|
|
19
|
+
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
20
|
+
if (!statSync(dir).isDirectory()) return { ok: false, why: 'board dir not writable: not a directory' };
|
|
21
|
+
// A fresh name each time — a leftover from a crashed probe can never make a writable board look full (review r2).
|
|
22
|
+
const probe = join(dir, `.degradation-probe-${randomUUID()}`);
|
|
23
|
+
try { writeFileSync(probe, '', { flag: 'wx', mode: 0o600 }); }
|
|
24
|
+
finally { rmSync(probe, { force: true }); }
|
|
25
|
+
} catch (error) { return { ok: false, why: `board dir not writable: ${(error as NodeJS.ErrnoException).code ?? String(error)}` }; }
|
|
26
|
+
const index = cardIndexPath(root);
|
|
27
|
+
// No index yet: the card store creates it on first open — a writable folder is enough.
|
|
28
|
+
if (!existsSync(index)) return { ok: true };
|
|
29
|
+
let db: Database | undefined;
|
|
30
|
+
try {
|
|
31
|
+
db = new Database(index, { readwrite: true, create: false });
|
|
32
|
+
db.exec('PRAGMA busy_timeout = 2000');
|
|
33
|
+
db.exec('BEGIN IMMEDIATE');
|
|
34
|
+
db.exec('ROLLBACK');
|
|
35
|
+
return { ok: true };
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return { ok: false, why: `board index write lock: ${String(error instanceof Error ? error.message : error).slice(0, 160)}` };
|
|
38
|
+
} finally { try { db?.close(); } catch { /* closing a failed handle is best effort */ } }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface DegradationDeps {
|
|
42
|
+
usableLlm?: () => { usable: boolean; why: string };
|
|
43
|
+
board?: (root: string) => { ok: boolean; why?: string };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function probeDegradation(root: string, deps: DegradationDeps = {}): DegradationSignal {
|
|
47
|
+
const why: string[] = [];
|
|
48
|
+
let llm = true;
|
|
49
|
+
try {
|
|
50
|
+
const usable = (deps.usableLlm ?? resolveUsableLlm)();
|
|
51
|
+
llm = usable.usable;
|
|
52
|
+
if (!llm) why.push(`llm: ${usable.why}`);
|
|
53
|
+
} catch (error) { why.push(`llm unknown: ${String(error).slice(0, 120)}`); }
|
|
54
|
+
const board = (deps.board ?? probeBoardWritable)(root);
|
|
55
|
+
if (!board.ok) why.push(board.why ?? 'board not writable');
|
|
56
|
+
return { llm, board: board.ok, why };
|
|
57
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { execFileSync } from 'node:child_process';
|
|
2
|
-
import { readdirSync, realpathSync, statSync } from 'node:fs';
|
|
2
|
+
import { appendFileSync, existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { debug } from '../../debug/log.js';
|
|
5
5
|
import { loadRunLedger, resolveFederatedRunLedgerTargets } from '../../self-implement/run-ledger.js';
|
|
6
|
+
import { withFileLockSync } from '../../storage/file-lock.js';
|
|
6
7
|
import type { LogTarget } from '../../cli/logs-cli.js';
|
|
7
8
|
|
|
8
9
|
const DAY_MS = 24 * 60 * 60 * 1_000;
|
|
@@ -14,6 +15,8 @@ export type FinishMetric = 'launched' | 'landed' | 'landingRate' | 'staleDrafts'
|
|
|
14
15
|
export type FinishMetrics = {
|
|
15
16
|
launched: number | null;
|
|
16
17
|
launchedUnreadable?: number;
|
|
18
|
+
/** Launched Pod runs whose child ledger never came back (`pod-ledger-incomplete`) — counted in `launched`, result unknown. */
|
|
19
|
+
launchedIncomplete?: number;
|
|
17
20
|
landed: number | null;
|
|
18
21
|
landingRate: number | null;
|
|
19
22
|
staleDrafts: number | null;
|
|
@@ -24,8 +27,81 @@ export type FinishMetrics = {
|
|
|
24
27
|
export type FinishAdvice = { finishSlots: number; launchSlots: number; state: 'healthy' | 'backlogged' | 'unknown'; reasons: string[] };
|
|
25
28
|
export type FinishThresholds = { landingRate?: number; staleDrafts?: number; conflictRatio?: number };
|
|
26
29
|
|
|
30
|
+
export type FinishTrend = 'worsening' | 'improving' | 'flat' | 'unknown';
|
|
31
|
+
export type FinishTrendInput = Pick<FinishMetrics, 'landingRate' | 'staleDrafts' | 'conflictRatio'>;
|
|
32
|
+
export type FinishHistoryRow = {
|
|
33
|
+
hour: string; at: string; launched: number | null; landed: number | null;
|
|
34
|
+
landingRate: number | null; staleDrafts: number | null; conflictRatio: number | null; trend: FinishTrend;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** FINISH-RATE 추세 — landingRate 는 내려가면 나쁨 · staleDrafts·conflictRatio 는 올라가면 나쁨.
|
|
38
|
+
* 양쪽 다 유한한 숫자인 지표만 비교한다(못 잰 값은 좋음도 나쁨도 아니다) · 임의 임계 없음.
|
|
39
|
+
* 나쁨 수 > 좋음 수 = worsening · 작으면 improving · 같으면 flat · 비교한 지표가 0 이면 unknown. */
|
|
40
|
+
export function finishTrend(previous: FinishTrendInput | null, current: FinishTrendInput): FinishTrend {
|
|
41
|
+
if (!previous) return 'unknown';
|
|
42
|
+
let compared = 0, worse = 0, better = 0;
|
|
43
|
+
for (const key of ['landingRate', 'staleDrafts', 'conflictRatio'] as const) {
|
|
44
|
+
const before = previous[key], after = current[key];
|
|
45
|
+
if (typeof before !== 'number' || !Number.isFinite(before) || typeof after !== 'number' || !Number.isFinite(after)) continue;
|
|
46
|
+
compared++;
|
|
47
|
+
const delta = key === 'landingRate' ? before - after : after - before;
|
|
48
|
+
if (delta > 0) worse++;
|
|
49
|
+
else if (delta < 0) better++;
|
|
50
|
+
}
|
|
51
|
+
if (compared === 0) return 'unknown';
|
|
52
|
+
return worse > better ? 'worsening' : worse < better ? 'improving' : 'flat';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function finishHistoryPath(root: string): string {
|
|
56
|
+
return join(root, 'harness', 'finish-history.jsonl');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** «한 시간 한 줄» 이력 — 같은 UTC 시간 줄이 이미 있으면 안 쓴다(null) · trend 는 지금보다 이른 가장 최근 시간 줄 대비.
|
|
60
|
+
* 읽을 수 없는 줄은 건너뛴다(이력 한 줄이 깨졌다고 측정을 멈추지 않는다). 쓰기 실패는 호출자에게 던진다. */
|
|
61
|
+
export function recordFinishHistory(root: string, metrics: FinishMetrics, now: Date): FinishHistoryRow | null {
|
|
62
|
+
const path = finishHistoryPath(root);
|
|
63
|
+
const hour = now.toISOString().slice(0, 13);
|
|
64
|
+
mkdirSync(join(root, 'harness'), { recursive: true });
|
|
65
|
+
// 확인과 덧붙이기를 한 잠금 안에서 — 같은 root 를 쓰는 두 틱이 같은 시간에 둘 다 «없음»을 보고 두 줄을 쓰지 않게.
|
|
66
|
+
const row = withFileLockSync(`${path}.lock`, (): FinishHistoryRow | null => {
|
|
67
|
+
const rows: FinishHistoryRow[] = [];
|
|
68
|
+
let text = '';
|
|
69
|
+
if (existsSync(path)) {
|
|
70
|
+
text = readFileSync(path, 'utf8');
|
|
71
|
+
for (const line of text.split('\n')) {
|
|
72
|
+
if (!line.trim()) continue;
|
|
73
|
+
try {
|
|
74
|
+
const parsed = JSON.parse(line) as FinishHistoryRow;
|
|
75
|
+
if (parsed && typeof parsed.hour === 'string') rows.push(parsed);
|
|
76
|
+
} catch { /* a broken line is skipped */ }
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (rows.some((existing) => existing.hour === hour)) return null;
|
|
80
|
+
// 앞 시간 = 지금보다 이른 줄 중 «가장 최근 UTC 시간»(파일 순서가 아니다 — 늦게 덧붙은 옛 시간 줄이 있을 수 있다) · 같은 시간이면 마지막 줄.
|
|
81
|
+
let previous: FinishHistoryRow | null = null;
|
|
82
|
+
for (const existing of rows) if (existing.hour < hour && (!previous || existing.hour >= previous.hour)) previous = existing;
|
|
83
|
+
const next: FinishHistoryRow = {
|
|
84
|
+
hour, at: now.toISOString(), launched: metrics.launched, landed: metrics.landed,
|
|
85
|
+
landingRate: metrics.landingRate, staleDrafts: metrics.staleDrafts, conflictRatio: metrics.conflictRatio,
|
|
86
|
+
trend: finishTrend(previous, metrics),
|
|
87
|
+
};
|
|
88
|
+
// 부분 쓰기로 끝 개행이 없으면 먼저 보정한다 — 새 줄이 깨진 꼬리에 붙어 둘 다 못 읽게 되지 않게.
|
|
89
|
+
appendFileSync(path, `${text && !text.endsWith('\n') ? '\n' : ''}${JSON.stringify(next)}\n`);
|
|
90
|
+
return next;
|
|
91
|
+
});
|
|
92
|
+
if (!row) return null;
|
|
93
|
+
try {
|
|
94
|
+
debug.log('loop.orchestrator', 'finish-history', {
|
|
95
|
+
hour, trend: row.trend, landingRate: row.landingRate, staleDrafts: row.staleDrafts, conflictRatio: row.conflictRatio,
|
|
96
|
+
});
|
|
97
|
+
} catch { /* the row is written; observation is fail-soft */ }
|
|
98
|
+
return row;
|
|
99
|
+
}
|
|
100
|
+
|
|
27
101
|
type Start = { runId: string; startedAt: string };
|
|
28
|
-
type StartObservation = { starts: readonly Start[]; unreadable: readonly { runId: string; reason: string }[]; unreadableDirectories?: number
|
|
102
|
+
type StartObservation = { starts: readonly Start[]; unreadable: readonly { runId: string; reason: string }[]; unreadableDirectories?: number;
|
|
103
|
+
/** Runs counted as launched although their child ledger never came back (reason = the host marker's data.reason). */
|
|
104
|
+
incomplete?: readonly { runId: string; reason: string }[] };
|
|
29
105
|
type Pr = { number: number; headRefName: string; mergedAt?: string; createdAt?: string; mergeable?: string; labels?: { name: string }[] };
|
|
30
106
|
export interface MeasureFinishDeps {
|
|
31
107
|
listStarts?: (cutoff: number) => StartObservation;
|
|
@@ -41,6 +117,7 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
|
|
|
41
117
|
let unreadableDirectories = 0;
|
|
42
118
|
let unresolvedDirectories = 0;
|
|
43
119
|
const launchOnlyIds = new Set<string>();
|
|
120
|
+
const incomplete = new Map<string, { runId: string; reason: string }>();
|
|
44
121
|
for (const { ledgerDirectory } of resolveFederatedRunLedgerTargets({ includeTest: true, ...(targets ? { targets } : {}) })) {
|
|
45
122
|
let dir: string;
|
|
46
123
|
try {
|
|
@@ -71,6 +148,20 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
|
|
|
71
148
|
continue;
|
|
72
149
|
}
|
|
73
150
|
const start = entries.find(entry => entry.event === 'start');
|
|
151
|
+
// ORCH-LIVE-1008 ①: the host writes `pod-ledger-incomplete` on a Pod child run's ledger when the child's own ledger
|
|
152
|
+
// never came back (OOM · logs unavailable · partial collection). The Job did run — it is a launch with an unknown
|
|
153
|
+
// result, not a ledger without a start. Its time is the marker's timestamp, else the ledger mtime.
|
|
154
|
+
const incompleteMarker = !start ? entries.find(entry => entry.event === 'pod-ledger-incomplete') : undefined;
|
|
155
|
+
if (incompleteMarker) {
|
|
156
|
+
// Only a real `start` (from any directory) outranks the marker; a host launch-only record does not — the same
|
|
157
|
+
// run read as launch-only first is still a launch whose result never came back.
|
|
158
|
+
if (starts.has(runId) && !launchOnlyIds.has(runId)) continue;
|
|
159
|
+
if (!starts.has(runId)) starts.set(runId, { runId, startedAt: incompleteMarker.timestamp ?? new Date(statSync(path).mtimeMs).toISOString() });
|
|
160
|
+
const why = (incompleteMarker.data as { reason?: unknown } | undefined)?.reason;
|
|
161
|
+
incomplete.set(runId, { runId, reason: typeof why === 'string' && why ? why : 'unknown' });
|
|
162
|
+
unreadable.delete(runId);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
74
165
|
// A Pod-dispatched launch leaves only a host launch record (launch-quota-policy · author-on-pod-receipt) — the run
|
|
75
166
|
// itself starts inside the Pod. That is still a launch: its time is the record's timestamp, else the ledger mtime.
|
|
76
167
|
const launchRecord = !start ? entries.find(entry => LAUNCH_ONLY_EVENTS.has(entry.event)) : undefined;
|
|
@@ -85,12 +176,14 @@ function defaultListStarts(cutoff: number, targets?: readonly LogTarget[]): Star
|
|
|
85
176
|
if (launchRecord) launchOnlyIds.add(runId); else launchOnlyIds.delete(runId);
|
|
86
177
|
starts.set(runId, { runId, startedAt });
|
|
87
178
|
unreadable.delete(runId);
|
|
179
|
+
incomplete.delete(runId);
|
|
88
180
|
}
|
|
89
181
|
}
|
|
90
182
|
debug.log('loop.orchestrator', 'finish-launch-population', {
|
|
91
183
|
directories: directories.size + unresolvedDirectories, launched: starts.size, launchOnly: launchOnlyIds.size, childLedgers: childLedgers.size, unreadable: unreadable.size,
|
|
184
|
+
...(incomplete.size > 0 ? { incomplete: incomplete.size } : {}),
|
|
92
185
|
});
|
|
93
|
-
return { starts: [...starts.values()], unreadable: [...unreadable.values()], unreadableDirectories };
|
|
186
|
+
return { starts: [...starts.values()], unreadable: [...unreadable.values()], unreadableDirectories, incomplete: [...incomplete.values()] };
|
|
94
187
|
}
|
|
95
188
|
|
|
96
189
|
function defaultRunGh(args: string[]): string {
|
|
@@ -112,6 +205,7 @@ export function measureFinish(deps: MeasureFinishDeps = {}, now: Date = new Date
|
|
|
112
205
|
const cutoff = now.getTime() - DAY_MS;
|
|
113
206
|
let launched: number | null = null;
|
|
114
207
|
let launchedUnreadable = 0;
|
|
208
|
+
let launchedIncomplete = 0;
|
|
115
209
|
let landed: number | null = null;
|
|
116
210
|
let staleDrafts: number | null = null;
|
|
117
211
|
let conflictRatio: number | null = null;
|
|
@@ -124,6 +218,14 @@ export function measureFinish(deps: MeasureFinishDeps = {}, now: Date = new Date
|
|
|
124
218
|
|| observation.unreadable.some(entry => !entry || typeof entry.runId !== 'string' || typeof entry.reason !== 'string')) throw new Error('invalid run start observation');
|
|
125
219
|
launched = new Set(observation.starts.filter(start => Date.parse(start.startedAt) >= cutoff && Date.parse(start.startedAt) <= now.getTime()).map(start => start.runId)).size;
|
|
126
220
|
launchedUnreadable = observation.unreadable.length;
|
|
221
|
+
const incompleteRuns = (observation.incomplete ?? []).filter(entry => observation.starts.some(start => start.runId === entry.runId
|
|
222
|
+
&& Date.parse(start.startedAt) >= cutoff && Date.parse(start.startedAt) <= now.getTime()));
|
|
223
|
+
launchedIncomplete = incompleteRuns.length;
|
|
224
|
+
if (launchedIncomplete > 0) {
|
|
225
|
+
const byReason: Record<string, number> = {};
|
|
226
|
+
for (const entry of incompleteRuns) byReason[entry.reason] = (byReason[entry.reason] ?? 0) + 1;
|
|
227
|
+
debug.log('loop.orchestrator', 'finish-ledger-incomplete', { count: launchedIncomplete, byReason, sample: incompleteRuns.slice(0, 5) });
|
|
228
|
+
}
|
|
127
229
|
if (launchedUnreadable > 0) {
|
|
128
230
|
reasons.launched = `${launchedUnreadable}개 원장 시작 줄 없음(하한값)`;
|
|
129
231
|
debug.log('loop.orchestrator', 'finish-ledger-unreadable', { count: launchedUnreadable, sample: observation.unreadable.slice(0, 5) });
|
|
@@ -162,7 +264,7 @@ export function measureFinish(deps: MeasureFinishDeps = {}, now: Date = new Date
|
|
|
162
264
|
: 'no harness runs launched in the last 24 hours';
|
|
163
265
|
else if (landed > launched) reasons.landingRate = `landed exceeds launched — population mismatch (launched=${launched}, landed=${landed})`;
|
|
164
266
|
else landingRate = landed / launched;
|
|
165
|
-
return { launched, ...(launchedUnreadable > 0 ? { launchedUnreadable } : {}), landed, landingRate, staleDrafts, conflictRatio, unknownMergeable, reasons };
|
|
267
|
+
return { launched, ...(launchedUnreadable > 0 ? { launchedUnreadable } : {}), ...(launchedIncomplete > 0 ? { launchedIncomplete } : {}), landed, landingRate, staleDrafts, conflictRatio, unknownMergeable, reasons };
|
|
166
268
|
}
|
|
167
269
|
|
|
168
270
|
/** Advice only: no launch cap, PR, or config is changed. */
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface GridCell { kind?: string }
|
|
2
|
+
export interface GridHost { name: string; capabilities: ReadonlySet<string>; available: boolean }
|
|
3
|
+
|
|
4
|
+
/** A proposal only: the caller owns the host inventory and the actual launch. */
|
|
5
|
+
export function placeOnGrid(cell: GridCell, hosts: readonly GridHost[]): { host: string; reason?: never } | { host: null; reason: string } {
|
|
6
|
+
const kind = cell.kind ?? 'unknown';
|
|
7
|
+
const host = hosts.find(candidate => candidate.available && candidate.capabilities.has(kind));
|
|
8
|
+
if (host) return { host: host.name };
|
|
9
|
+
return { host: null, reason: hosts.some(candidate => candidate.capabilities.has(kind))
|
|
10
|
+
? `no available host for ${kind}` : `no host with ${kind} capability` };
|
|
11
|
+
}
|