elanous 0.2.13 → 0.2.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/{7334.a6679d9168f37aa8.js → 1268.0840ff91e083fcbd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1307-6e7e0377c1db0bdc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1826-5d279da64bbbc2de.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9656-c1540088f5859c80.js → 1844-9ec4b1d12f6e80eb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/1866-e7a6999f25548b30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{390.c373e31b3b9d0c41.js → 1948.15f356b15a625597.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/211-9c9090c59e2c5907.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2196-4700653d12a44b40.js +1 -0
- package/apps/pwa/out/_next/static/chunks/224-798346143007ff65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2699-63b73fa4d3bcf6ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2957-96ffab3d521fb08f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/311.e685738f92e6e2ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3194-807b212fb8eae065.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3324-d1d2979821dfa845.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3558-bbb5ab553a0471d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3916-3d02dd1fc257dd3c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4144-a0041c8dc3be57ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4554-3565eea1f1495ba7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4685-c3c1c625b71accd1.js → 4614-21403aac730d4586.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{8039-df92e08fede5de8f.js → 4795-f372490b178583fc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5135-44008fe8b1f2aa41.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5233-bf28bd6bd7a32099.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5360-be1f1d8f9fcc9755.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5994-ae92c0de046ab9ba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1452.93700483b9d4fc61.js → 6110.1297d4093fdfbb0d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6158.9c0449a9a8d0064b.js +2 -0
- package/apps/pwa/out/_next/static/chunks/6877-7c6208705c284124.js +1 -0
- package/apps/pwa/out/_next/static/chunks/726.83532d69452ee24b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7290-f09c928a06c52e2d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{1829-b453a1b22d3999bb.js → 7327-2d69fb321ba73389.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7395-164e238f471b5d7b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7787-02a1c0b04e25e811.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8060-cc008cc6fedfa818.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8085-d065d0393c1760f5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8682-7061dd4fa7bd62ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8827-e2f5b09a8d37e83b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8868-10a3a4440fb0ccd8.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8421-1c4acfb1395a0edf.js → 8949-ecb4128a0858b911.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9070.067c106528ec1ae6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9103-7a3ae7f60af3b99f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3869-3cd46526879f454c.js → 9247-29d889b809288e65.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/935.4ddaa99fc1d1126c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{79f8be9e-957d68913b590b08.js → a9f2b54d-7865aba0f4d2ee49.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-7b2b4f378354869a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f14987d5850a31e0.js → page-1abfe3aec3be6bdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-1512d91c7491ed62.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-a627db94ec6904a0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-e6b84ab07d3567d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-fed69f9759d248d0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-15374909d8f1aebd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-012a742f81218b01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-99effd9d893a43dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-938de57ae0315e46.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/page-160089123a40dd91.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-629e1ede482ec6eb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-66c27608f651b866.js → page-2ce5471926761fdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-27e99b4e54a849d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-32cde713be47cddb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e7e10cd3386222e5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e64caff084f7ce21.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-db07eaffba0df7e3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-78b40a344ed22f24.js → page-e1e47d8d940aba3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-86f91034bd5cabe5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-c1a96669158471ec.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-ce4d8a1215ce53a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-58043db78ba519ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-357750dfb91bf960.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/{page-63d14a4d8fe1e1c1.js → page-274703bc41b71333.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3b7e821dc492bdfd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-f8da5dc6f3752276.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2f810b18d89ecba6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5cd7e3a9-404fd6b8d0ecd44c.js → f6231284-45726550aba08fca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{f48170cc-d05d28bf87a43bf4.js → f6b7c9ba.64a895b8b2fedbdc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-5c169757551134ed.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-59fe95fed40c7a01.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-4ca74dbbee7057c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-13a0a886df4d97a1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-6af8591cd3a3f038.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-539cde99554be20f.js +1 -0
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +1 -0
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +22 -22
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/loops/index.html +1 -0
- package/apps/pwa/out/loops/index.txt +30 -0
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -23
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -23
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/orchestrator/orchestrator.yaml +59 -0
- package/graphs/orchestrator/recipes.yaml +18 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/rhythm/daily.yaml +38 -0
- package/graphs/rhythm/recipes.yaml +18 -0
- package/graphs/rhythm/weekly.yaml +39 -0
- package/graphs/video/recipes.yaml +3 -0
- package/graphs/video/youtube-summary.yaml +17 -0
- package/package.json +1 -1
- package/scripts/audit-test-state-writes.ts +72 -17
- package/scripts/brand/check.ts +186 -0
- package/scripts/coord-post.sh +11 -1
- package/scripts/docs-cli-check.ts +23 -6
- package/scripts/expose-rubric-check.ts +118 -0
- package/scripts/expose-rubric-suggest.ts +163 -0
- package/scripts/heal/run-role.ts +14 -0
- package/scripts/hq/standby-verify.ts +11 -7
- package/scripts/lessons/handbook-page.ts +85 -0
- package/scripts/release-loop/gate-node.ts +9 -0
- package/scripts/release-loop/publish-node.ts +14 -1
- package/scripts/release-loop/unattended-release.ts +23 -1
- package/scripts/release-loop/vault-note-node.ts +196 -0
- package/scripts/release-story/draft.ts +172 -0
- package/scripts/rhythm/daily.ts +383 -0
- package/scripts/rhythm/weekly.ts +294 -0
- package/scripts/skills/public-check.ts +169 -0
- package/scripts/test-deterministic.ts +4 -8
- package/scripts/test-diet/effectiveness.ts +195 -0
- package/scripts/test-diet/lib.ts +16 -8
- package/scripts/test-diet/node.ts +77 -9
- package/scripts/ux/demo-rehearsal.ts +2 -2
- package/scripts/ux/lib/rehearsal-verdict.ts +1 -0
- package/scripts/video/youtube-summary.ts +133 -0
- package/skills/diagram-master/SKILL.md +42 -49
- package/skills/diagram-master/references/pyproject.toml +4 -1
- package/skills/diagram-master/references/routing-guide.md +4 -2
- package/skills/grill-me/SKILL.md +1 -0
- package/skills/youtube-master/SKILL.md +13 -13
- package/skills/youtube-master/package-lock.json +0 -545
- package/skills/youtube-master/package.json +2 -5
- package/skills/youtube-master/scripts/main.ts +2 -2
- package/skills/youtube-master/scripts/transcribe-local.ts +1 -1
- package/skills/youtube-master/src/util.ts +30 -7
- package/src/agent-mission/driver.ts +3 -3
- package/src/autopilot/build/llm-conflict-merge.ts +45 -8
- package/src/autopilot/build/merge-intent.ts +53 -0
- package/src/chat/index.ts +2 -0
- package/src/claims/claims-ledger.ts +232 -0
- package/src/claims/claims-render.ts +157 -0
- package/src/cli/claims-cli.ts +94 -0
- package/src/cli/decisions-cli.ts +35 -3
- package/src/cli/docs-cli.ts +2 -0
- package/src/cli/flow-cli.ts +11 -0
- package/src/cli/freeze-cli.ts +45 -0
- package/src/cli/lesson-cli.ts +108 -0
- package/src/cli/logs-cli.ts +104 -2
- package/src/cli/logs-model-input.ts +131 -0
- package/src/cli/pr-cli.ts +29 -1
- package/src/cli/private-ledger-commands.ts +54 -4
- package/src/cli/pty-drive-verdict.ts +11 -2
- package/src/cli/release-cli.ts +144 -21
- package/src/cli/rfc-status.ts +91 -0
- package/src/connectors/linear.ts +5 -3
- package/src/context-bus/condense.ts +15 -20
- package/src/context-bus/context-now-public.ts +86 -0
- package/src/context-bus/context-now-surfaces.ts +55 -2
- package/src/context-bus/context-now.ts +4 -3
- package/src/context-bus/long-term-memory.ts +92 -11
- package/src/dashboard/index.ts +75 -22
- package/src/dashboard/slash-runtime/chat-persona.ts +66 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +99 -0
- package/src/dashboard/slash-runtime/loops-table.ts +77 -0
- package/src/decisions/decision-ledger.ts +40 -1
- package/src/discord/sprint21-runtime.ts +16 -0
- package/src/discord-self-message.ts +8 -0
- package/src/discord-slash-wire.ts +31 -2
- package/src/domains/outbound-alert.ts +1 -1
- package/src/elanous-config-dir.ts +29 -4
- package/src/execution-loop/launch-gate.ts +29 -14
- package/src/flow/cards.ts +239 -0
- package/src/graph-runner/graph-tick.ts +4 -0
- package/src/graph-runner/runner.ts +30 -5
- package/src/harness/harness-cli-command.ts +152 -27
- package/src/harness/harness-incidents.ts +43 -12
- package/src/harness/harness-pod-dispatch.ts +2 -0
- package/src/harness/harness-queue-child.ts +3 -3
- package/src/harness/harness-queue.ts +216 -28
- package/src/harness/helper-scan.ts +139 -0
- package/src/harness/model-input-observation.ts +37 -0
- package/src/hq/hq.ts +1 -1
- package/src/hq/promote.ts +301 -0
- package/src/index.ts +83 -21
- package/src/intake-plane/linear-wish.ts +39 -11
- package/src/intake-plane/wish-benchmark-ledger.ts +129 -0
- package/src/intake-plane/wish-card.ts +53 -2
- package/src/intake-plane/wish-folder.ts +5 -2
- package/src/intake-plane/wish-reply.ts +72 -0
- package/src/lessons/lesson-import.ts +106 -0
- package/src/lessons/lesson-ledger.ts +206 -0
- package/src/loops/activity.ts +203 -0
- package/src/loops/budget.ts +32 -0
- package/src/loops/checker.ts +5 -3
- package/src/loops/contract/examples/orchestrator.loop.yaml +1 -1
- package/src/loops/loop-cli.ts +36 -4
- package/src/loops/neighbors.ts +40 -0
- package/src/loops/observe.ts +10 -0
- package/src/loops/orchestrator/author-depth.ts +164 -0
- package/src/loops/orchestrator/author-input-check.ts +77 -0
- package/src/loops/orchestrator/author-ledger.ts +230 -0
- package/src/loops/orchestrator/finish-rate.ts +175 -0
- package/src/loops/orchestrator/overlap.ts +51 -12
- package/src/loops/orchestrator/resync-shadow.ts +227 -0
- package/src/loops/orchestrator/tick.ts +302 -0
- package/src/loops/orchestrator/traffic.ts +18 -3
- package/src/loops/registry.ts +454 -9
- package/src/loops/status-rows.ts +118 -0
- package/src/loops/verdict.ts +17 -5
- package/src/maturity/bot-slash-catalog.ts +66 -0
- package/src/maturity/feature-maturity.ts +7 -4
- package/src/nexus/api/dashboard.ts +6 -1
- package/src/nexus/api/http-server.ts +18 -2
- package/src/nexus/api/merge-approvals-gate.ts +1 -1
- package/src/nexus/api/schedules-read.ts +5 -1
- package/src/nexus/api/task-cards-api.ts +44 -5
- package/src/nexus/api/workflows.ts +2 -1
- package/src/release-loop/auto-start.ts +48 -0
- package/src/release-loop/checklist.ts +13 -4
- package/src/release-loop/feature-store.ts +28 -16
- package/src/release-loop/landing-freeze.ts +121 -0
- package/src/release-loop/placement.ts +242 -0
- package/src/release-loop/release-schedule.ts +3 -1
- package/src/seat-dispatch/ceo-commands.ts +2 -2
- package/src/seat-dispatch/seat-ask.ts +16 -3
- package/src/seat-dispatch/seat-questions.ts +58 -0
- package/src/seat-loop/seat-loop.ts +406 -31
- package/src/self-dev/dev-pipeline.ts +6 -2
- package/src/self-dev/draft-sweep.ts +76 -0
- package/src/self-dev/run-store.ts +73 -1
- package/src/self-dev/run-supervisor.ts +8 -0
- package/src/self-dev/self-orchestrate-runtime.ts +5 -1
- package/src/self-implement/budget-gate.ts +29 -0
- package/src/self-implement/expose-gate.ts +47 -0
- package/src/self-implement/frozen-merges.ts +294 -0
- package/src/self-implement/gate-cli.ts +10 -4
- package/src/self-implement/gate-scope.ts +46 -9
- package/src/self-implement/goal-archive.ts +222 -0
- package/src/self-implement/goal-lookup.ts +147 -0
- package/src/self-implement/heal-intake.ts +79 -0
- package/src/self-implement/host-regate.ts +25 -2
- package/src/self-implement/orchestrator.ts +114 -10
- package/src/self-implement/revert-guard.ts +62 -0
- package/src/self-implement/run-ledger.ts +1 -1
- package/src/self-implement/seams.ts +28 -8
- package/src/session/index.ts +2 -0
- package/src/task-cards/card-cli.ts +10 -2
- package/src/task-cards/card-store.ts +10 -6
- package/src/task-orchestrator/surfaces/pod-lease.ts +5 -2
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +46 -6
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +80 -16
- package/src/telegram-commands.ts +27 -3
- package/src/telegram-loops-command.ts +44 -0
- package/src/user-config.ts +120 -16
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/storage.ts +31 -13
- package/apps/pwa/out/_next/static/D51l8jUBplJrIpWvENsDK/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1394-931c9827067b2276.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3992-fe5c06d6b31f8b25.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +0 -2
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5845-e6c003ce8d5fd36a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8605-9da4678f8a310d18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9269.d2a4795bc6812717.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-e49b9b76c565a0b1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-4c1dbb21861beab2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f6bc66b919b2ea7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-60603c1e056c9b25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-a0ea8f5a2341ee6b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-31012264da8a341f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-50309aeb0bdd554a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-0696ee3fac9fe56c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6ddd2109b7fc3a80.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-7a2bf830038a2264.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-1f327f18694fabca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-e5cef2fe6e4028df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-cf596128d2b420ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-f672536f14f0969f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-29a709616df9b050.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-fee842d7c2151129.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-a011dd5b0bff29c5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-d92e32d51ab5157e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-5f21aed6932ef375.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-1e1a53946804debf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-a1833cd43d832601.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-2a511d320f762e06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1c3e03f88dac1e21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-c4d5289c2ea97a89.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-b91f8952deaeaf27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-71b43a2588489987.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +0 -1
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +0 -1
- /package/apps/pwa/out/_next/static/{D51l8jUBplJrIpWvENsDK → x6mjI6vqOkVVu0jFuDkz1}/_ssgManifest.js +0 -0
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readlinkSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { Database } from 'bun:sqlite';
|
|
6
6
|
import { debug } from '../debug/log.js';
|
|
7
7
|
import { effectiveInstanceRoot, prodInstanceRoot } from '../instance/resolve.js';
|
|
8
|
+
import { decideSpawn } from '../loops/budget.js';
|
|
9
|
+
import { listSelfDevRuns, processBirthId, selfDevRunsDir } from '../self-dev/run-store.js';
|
|
8
10
|
import { getUserConfig } from '../user-config.js';
|
|
9
11
|
import { hostLeaseCounts, leaseHasPendingPod } from '../pod-lease/host-lease.js';
|
|
10
12
|
import { leaseKubectl, measurePoolLease } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
@@ -14,14 +16,35 @@ import { writeHarnessQueueReceipt } from './harness-queue-child.js';
|
|
|
14
16
|
export type QueueSeat = 'OP' | 'TC' | 'MK' | 'UX';
|
|
15
17
|
export type QueueItem = {
|
|
16
18
|
id: string; seat: QueueSeat; kind: 'say' | 'ask'; input: string; hold: boolean; heavy: boolean;
|
|
17
|
-
at: string; status: 'queued' | 'launching' | 'launched' | 'finished'; pid?: number; launchId?: string;
|
|
19
|
+
at: string; status: 'queued' | 'launching' | 'launched' | 'finished'; pid?: number; launchId?: string; idempotencyKey?: string;
|
|
18
20
|
};
|
|
19
21
|
export type QueuePool = { running: number; pending: number; reserved: number; limit: number };
|
|
20
22
|
export type QueueTick = { outcome: 'launched' | 'waiting' | 'skipped'; item?: QueueItem; reason: string };
|
|
21
23
|
export type QueueProcess = { pid: number; seat?: QueueSeat; launchId?: string };
|
|
24
|
+
type ProcessProbe = {
|
|
25
|
+
run?: typeof spawnSync; platform?: NodeJS.Platform;
|
|
26
|
+
cwd?: (pid: number) => string;
|
|
27
|
+
seatTrees?: Partial<Record<QueueSeat, string[]>>;
|
|
28
|
+
runsDir?: string;
|
|
29
|
+
environ?: (pid: number) => string;
|
|
30
|
+
birthId?: (pid: number) => string | undefined;
|
|
31
|
+
};
|
|
32
|
+
const queueSeatNames = ['OP', 'TC', 'MK', 'UX'] as const;
|
|
33
|
+
|
|
34
|
+
export function queueSeatForCwd(cwd: string, trees: Partial<Record<QueueSeat, string[]>> = {}): QueueSeat | undefined {
|
|
35
|
+
if (!cwd.startsWith('/')) return undefined;
|
|
36
|
+
const current = resolve(cwd);
|
|
37
|
+
const matches = queueSeatNames.filter((s) => trees[s]?.some((tree) => {
|
|
38
|
+
if (!tree.startsWith('/')) return false;
|
|
39
|
+
const prefix = resolve(tree);
|
|
40
|
+
return current === prefix || current.startsWith(`${prefix}/`);
|
|
41
|
+
}));
|
|
42
|
+
return matches.length === 1 ? matches[0] : undefined;
|
|
43
|
+
}
|
|
22
44
|
|
|
23
45
|
export interface HarnessQueueDeps {
|
|
24
46
|
root?: string;
|
|
47
|
+
configPath?: string;
|
|
25
48
|
cap?: (seat: QueueSeat) => number;
|
|
26
49
|
pool?: () => QueuePool;
|
|
27
50
|
launch?: (item: QueueItem, args: string[], root: string) => Promise<number>;
|
|
@@ -49,7 +72,8 @@ function read(path: string): QueueItem[] {
|
|
|
49
72
|
&& typeof row.at === 'string' && typeof row.hold === 'boolean' && typeof row.heavy === 'boolean'
|
|
50
73
|
&& ['queued', 'launching', 'launched', 'finished'].includes(row.status)
|
|
51
74
|
&& (row.pid === undefined || Number.isSafeInteger(row.pid))
|
|
52
|
-
&& (row.launchId === undefined || typeof row.launchId === 'string')
|
|
75
|
+
&& (row.launchId === undefined || typeof row.launchId === 'string')
|
|
76
|
+
&& (row.idempotencyKey === undefined || typeof row.idempotencyKey === 'string'))) {
|
|
53
77
|
throw new Error('harness queue: invalid queue file (no launch)');
|
|
54
78
|
}
|
|
55
79
|
return value as QueueItem[];
|
|
@@ -90,7 +114,7 @@ function seat(value: string): QueueSeat {
|
|
|
90
114
|
return value as QueueSeat;
|
|
91
115
|
}
|
|
92
116
|
|
|
93
|
-
export async function addHarnessQueue(input: { seat: string; say?: string; ask?: string; hold?: boolean; heavy?: boolean }, deps: HarnessQueueDeps = {}): Promise<QueueItem> {
|
|
117
|
+
export async function addHarnessQueue(input: { seat: string; say?: string; ask?: string; hold?: boolean; heavy?: boolean; idempotencyKey?: string }, deps: HarnessQueueDeps = {}): Promise<QueueItem> {
|
|
94
118
|
const assigned = seat(input.seat);
|
|
95
119
|
if ((input.say === undefined) === (input.ask === undefined)) throw new Error('harness queue add: --say 또는 --ask 중 하나만 필요');
|
|
96
120
|
const kind = input.ask === undefined ? 'say' : 'ask';
|
|
@@ -100,9 +124,21 @@ export async function addHarnessQueue(input: { seat: string; say?: string; ask?:
|
|
|
100
124
|
if (kind === 'ask' && !existsSync(value)) throw new Error(`harness queue add: goal file not found: ${value}`);
|
|
101
125
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
102
126
|
return locked(root, async (path) => {
|
|
127
|
+
const rows = read(path);
|
|
128
|
+
if (input.idempotencyKey !== undefined) {
|
|
129
|
+
if (!input.idempotencyKey.trim()) throw new Error('harness queue add: empty idempotency key');
|
|
130
|
+
const existing = rows.find((row) => row.idempotencyKey === input.idempotencyKey
|
|
131
|
+
&& (row.status === 'queued' || row.status === 'launching' || row.status === 'launched')
|
|
132
|
+
&& queueRowOutcome(row, root) !== 'retryable');
|
|
133
|
+
if (existing) {
|
|
134
|
+
if (existing.seat !== assigned || existing.kind !== kind || existing.input !== value) throw new Error('harness queue: idempotency key collision');
|
|
135
|
+
return existing;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
103
138
|
const item: QueueItem = { id: `hq-${randomUUID()}`, seat: assigned, kind, input: value,
|
|
104
|
-
hold: input.hold === true, heavy: input.heavy === true, at: new Date().toISOString(), status: 'queued'
|
|
105
|
-
|
|
139
|
+
hold: input.hold === true, heavy: input.heavy === true, at: new Date().toISOString(), status: 'queued',
|
|
140
|
+
...(input.idempotencyKey === undefined ? {} : { idempotencyKey: input.idempotencyKey }) };
|
|
141
|
+
save(path, [...rows, item]);
|
|
106
142
|
observe('enqueued', { id: item.id, seat: item.seat, kind }, deps);
|
|
107
143
|
return item;
|
|
108
144
|
});
|
|
@@ -112,6 +148,60 @@ export function listHarnessQueue(deps: HarnessQueueDeps = {}): QueueItem[] {
|
|
|
112
148
|
return read(harnessQueuePath(deps.root ?? effectiveInstanceRoot()));
|
|
113
149
|
}
|
|
114
150
|
|
|
151
|
+
/** A missing row is not proof of failure unless the queue left a cancellation marker. */
|
|
152
|
+
export function harnessQueueOutcome(id: string, deps: HarnessQueueDeps = {}): 'pending' | 'succeeded' | 'retryable' | 'unknown' {
|
|
153
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
154
|
+
const row = listHarnessQueue(deps).find((item) => item.id === id);
|
|
155
|
+
if (!row) {
|
|
156
|
+
const marker = join(root, 'harness', `${id}.outcome`);
|
|
157
|
+
if (!existsSync(marker)) return 'unknown';
|
|
158
|
+
const value = readFileSync(marker, 'utf8');
|
|
159
|
+
return value === 'succeeded' || value === 'retryable' ? value : 'unknown';
|
|
160
|
+
}
|
|
161
|
+
return queueRowOutcome(row, root);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const keyMarkerPath = (root: string, key: string) => join(root, 'harness', 'by-key', `${createHash('sha256').update(key).digest('hex')}.id`);
|
|
165
|
+
|
|
166
|
+
function recordQueueOutcome(root: string, id: string, outcome: 'succeeded' | 'retryable', key?: string): void {
|
|
167
|
+
const path = join(root, 'harness', `${id}.outcome`);
|
|
168
|
+
if (!existsSync(path)) writeFileSync(path, outcome, { flag: 'wx', mode: 0o600 });
|
|
169
|
+
// The row is about to leave the queue; keep «which id last held this key» so a caller that crashed before
|
|
170
|
+
// recording the id can still find the outcome (LOOP-LIVE1 · seat loop recovery).
|
|
171
|
+
if (key) {
|
|
172
|
+
mkdirSync(join(root, 'harness', 'by-key'), { recursive: true });
|
|
173
|
+
writeFileSync(keyMarkerPath(root, key), id, { mode: 0o600 });
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Last queue id that settled under this idempotency key, or undefined when none was recorded. */
|
|
178
|
+
export function harnessQueueIdForKey(key: string, deps: HarnessQueueDeps = {}): string | undefined {
|
|
179
|
+
try {
|
|
180
|
+
const id = readFileSync(keyMarkerPath(deps.root ?? effectiveInstanceRoot(), key), 'utf8').trim();
|
|
181
|
+
return /^hq-[0-9a-f-]{36}$/i.test(id) ? id : undefined;
|
|
182
|
+
} catch { return undefined; }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function queueRowOutcome(row: QueueItem, root: string): 'pending' | 'succeeded' | 'retryable' | 'unknown' {
|
|
186
|
+
if (row.status === 'queued') return 'pending';
|
|
187
|
+
if (row.status === 'finished') {
|
|
188
|
+
const marker = join(root, 'harness', `${row.id}.outcome`);
|
|
189
|
+
if (existsSync(marker)) {
|
|
190
|
+
const value = readFileSync(marker, 'utf8');
|
|
191
|
+
if (value === 'succeeded' || value === 'retryable') return value;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
if (!row.launchId) return 'unknown';
|
|
195
|
+
const receiptPath = harnessQueueReceiptPath(root, row.launchId);
|
|
196
|
+
if (!existsSync(receiptPath)) return 'pending';
|
|
197
|
+
const record: unknown = JSON.parse(readFileSync(receiptPath, 'utf8'));
|
|
198
|
+
if (!record || typeof record !== 'object') return 'unknown';
|
|
199
|
+
const { state, exitCode } = record as { state?: unknown; exitCode?: unknown };
|
|
200
|
+
if (state === 'not-started') return 'retryable';
|
|
201
|
+
if (state === 'finished') return exitCode === 0 ? 'succeeded' : typeof exitCode === 'number' ? 'retryable' : 'unknown';
|
|
202
|
+
return state === 'started' ? 'pending' : 'unknown';
|
|
203
|
+
}
|
|
204
|
+
|
|
115
205
|
export async function removeHarnessQueue(id: string, deps: HarnessQueueDeps = {}): Promise<boolean> {
|
|
116
206
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
117
207
|
return locked(root, async (path) => {
|
|
@@ -124,6 +214,10 @@ export async function removeHarnessQueue(id: string, deps: HarnessQueueDeps = {}
|
|
|
124
214
|
if (state !== 'finished' && state !== 'not-started'
|
|
125
215
|
|| (deps.processes ?? readHarnessQueueProcesses)().some((row) => row.launchId === item.launchId)) return false;
|
|
126
216
|
}
|
|
217
|
+
const outcome = item.status === 'queued' ? 'retryable' : queueRowOutcome(item, root);
|
|
218
|
+
if (item.idempotencyKey && (outcome === 'retryable' || outcome === 'succeeded')) {
|
|
219
|
+
recordQueueOutcome(root, id, outcome, item.idempotencyKey);
|
|
220
|
+
}
|
|
127
221
|
save(path, rows.filter((row) => row.id !== id));
|
|
128
222
|
observe('skipped', { id, seat: item.seat, reason: 'removed' }, deps);
|
|
129
223
|
return true;
|
|
@@ -159,31 +253,106 @@ function alive(pid: number): boolean {
|
|
|
159
253
|
}
|
|
160
254
|
|
|
161
255
|
/** Host inventory includes harness authoring before it has a run ledger. Unattributed processes count for every seat. */
|
|
162
|
-
export function readHarnessQueueProcesses(): readonly QueueProcess[] {
|
|
163
|
-
const
|
|
256
|
+
export function readHarnessQueueProcesses(probe: ProcessProbe = {}): readonly QueueProcess[] {
|
|
257
|
+
const run = probe.run ?? spawnSync;
|
|
258
|
+
const platform = probe.platform ?? process.platform;
|
|
259
|
+
const result = run('ps', ['-eo', 'pid=,ppid=,args='], { encoding: 'utf8', timeout: 5_000, maxBuffer: 16 * 1024 * 1024 });
|
|
164
260
|
if (result.error || result.status !== 0 || typeof result.stdout !== 'string') throw new Error('harness process inventory unavailable');
|
|
165
|
-
const
|
|
261
|
+
const candidates: { pid: number; ppid: number; command: string }[] = [];
|
|
166
262
|
for (const line of result.stdout.split('\n')) {
|
|
167
|
-
const match = /^\s*(\d+)\s+(.+)$/.exec(line);
|
|
168
|
-
if (!match)
|
|
169
|
-
|
|
263
|
+
const match = /^\s*(\d+)\s+(\d+)\s+(.+)$/.exec(line);
|
|
264
|
+
if (!match) {
|
|
265
|
+
if (/(?:elanous\.mjs|harness-queue-child\.ts)/.test(line)) throw new Error('invalid harness process inventory row');
|
|
266
|
+
continue;
|
|
267
|
+
}
|
|
268
|
+
const command = match[3]!;
|
|
170
269
|
if (!/^(?:\S*\/)?(?:bun|node)(?:\s|$)/.test(command)
|
|
171
270
|
|| !/(?:elanous\.mjs|harness-queue-child\.ts)/.test(command)
|
|
172
271
|
|| !/(?:\bharness\s+(?:say|ask)\b|\bdev\s+.*(?:--say|--ask|--file|--implement)\b|\bself\s+(?:implement|orchestrate)\b|harness-queue-child\.ts)/.test(command)) continue;
|
|
173
|
-
const pid = Number(match[1]);
|
|
174
|
-
if (!Number.isSafeInteger(pid) || pid <= 0) throw new Error('invalid harness pid');
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
272
|
+
const pid = Number(match[1]), ppid = Number(match[2]);
|
|
273
|
+
if (!Number.isSafeInteger(pid) || pid <= 0 || !Number.isSafeInteger(ppid) || ppid < 0) throw new Error('invalid harness pid');
|
|
274
|
+
candidates.push({ pid, ppid, command });
|
|
275
|
+
}
|
|
276
|
+
const byPid = new Map(candidates.map((row) => [row.pid, row]));
|
|
277
|
+
const children = new Map<number, typeof candidates>();
|
|
278
|
+
for (const candidate of candidates) {
|
|
279
|
+
const siblings = children.get(candidate.ppid) ?? [];
|
|
280
|
+
siblings.push(candidate);
|
|
281
|
+
children.set(candidate.ppid, siblings);
|
|
282
|
+
}
|
|
283
|
+
const trees = probe.seatTrees ?? getUserConfig().loops?.orchestrator?.seatTrees;
|
|
284
|
+
const ledgerSeats = new Map<number, QueueSeat>();
|
|
285
|
+
const ambiguousPids = new Set<number>();
|
|
286
|
+
const candidatePids = new Set(candidates.map(({ pid }) => pid));
|
|
287
|
+
const births = new Map<number, string | undefined>();
|
|
288
|
+
for (const record of listSelfDevRuns(probe.runsDir ?? selfDevRunsDir())) {
|
|
289
|
+
const pid = record.pid;
|
|
290
|
+
if (!pid || !candidatePids.has(pid) || !record.pidStart || !queueSeatNames.some((name) => name === record.seat)) continue;
|
|
291
|
+
if (!births.has(pid)) {
|
|
292
|
+
try { births.set(pid, (probe.birthId ?? ((id: number) => processBirthId(id, platform)))(pid)); }
|
|
293
|
+
catch { births.set(pid, undefined); }
|
|
294
|
+
}
|
|
295
|
+
if (births.get(pid) !== record.pidStart || ambiguousPids.has(pid)) continue;
|
|
296
|
+
const prior = ledgerSeats.get(pid);
|
|
297
|
+
if (prior && prior !== record.seat) { ledgerSeats.delete(pid); ambiguousPids.add(pid); }
|
|
298
|
+
else ledgerSeats.set(pid, record.seat!);
|
|
299
|
+
}
|
|
300
|
+
const rows: QueueProcess[] = [];
|
|
301
|
+
// Attribute a run after grouping: the orchestrator may switch working trees while its launcher stays in HQ.
|
|
302
|
+
for (const root of candidates.filter((row) => !byPid.has(row.ppid))) {
|
|
303
|
+
const members: { process: typeof root; depth: number }[] = [];
|
|
304
|
+
const visit = (process: typeof root, depth: number): void => {
|
|
305
|
+
members.push({ process, depth });
|
|
306
|
+
for (const child of children.get(process.pid) ?? []) visit(child, depth + 1);
|
|
307
|
+
};
|
|
308
|
+
visit(root, 0);
|
|
309
|
+
const attributed: { seat: QueueSeat; depth: number; source: 'env' | 'flag' | 'ledger' | 'cwd'; priority: number }[] = [];
|
|
310
|
+
const worktrees: { seat: QueueSeat; depth: number }[] = [];
|
|
311
|
+
let launchId: string | undefined;
|
|
312
|
+
for (const { process: { pid, command }, depth } of members) {
|
|
313
|
+
let envSeat: string | undefined;
|
|
314
|
+
if (platform === 'linux') {
|
|
315
|
+
try {
|
|
316
|
+
const env = (probe.environ ?? ((id: number) => readFileSync(`/proc/${id}/environ`, 'utf8')))(pid).split('\0');
|
|
317
|
+
envSeat = env.find((entry) => entry.startsWith('ELANOUS_HARNESS_SEAT='))?.slice('ELANOUS_HARNESS_SEAT='.length);
|
|
318
|
+
launchId ??= env.find((entry) => entry.startsWith('ELANOUS_HARNESS_QUEUE_LAUNCH='))?.slice('ELANOUS_HARNESS_QUEUE_LAUNCH='.length);
|
|
319
|
+
} catch { /* Unknown attribution is charged to all seats. */ }
|
|
320
|
+
}
|
|
321
|
+
const flag = /--seat[=\s]+(OP|TC|MK|UX)(?:\s|$)/.exec(command)?.[1] as QueueSeat | undefined;
|
|
322
|
+
const envAssigned = envSeat && /^(OP|TC|MK|UX)$/.test(envSeat) ? envSeat as QueueSeat : undefined;
|
|
323
|
+
if (envAssigned) attributed.push({ seat: envAssigned, depth, source: 'env', priority: 0 });
|
|
324
|
+
if (flag) attributed.push({ seat: flag, depth, source: 'flag', priority: 1 });
|
|
325
|
+
const fromLedger = ledgerSeats.get(pid);
|
|
326
|
+
if (fromLedger) attributed.push({ seat: fromLedger, depth, source: 'ledger', priority: 2 });
|
|
327
|
+
if (!trees || !queueSeatNames.some((s) => trees[s]?.length)) continue;
|
|
178
328
|
try {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
329
|
+
let cwd: string;
|
|
330
|
+
if (probe.cwd) cwd = probe.cwd(pid);
|
|
331
|
+
else if (platform === 'linux') cwd = readlinkSync(`/proc/${pid}/cwd`);
|
|
332
|
+
else {
|
|
333
|
+
const lsof = run('lsof', ['-a', '-p', String(pid), '-d', 'cwd', '-Fn'], { encoding: 'utf8', timeout: 5_000 });
|
|
334
|
+
if (lsof.error || lsof.status !== 0 || typeof lsof.stdout !== 'string') throw new Error('cwd unavailable');
|
|
335
|
+
cwd = lsof.stdout.split('\n').find((line) => line.startsWith('n/'))?.slice(1) ?? '';
|
|
336
|
+
}
|
|
337
|
+
if (!cwd.startsWith('/')) continue;
|
|
338
|
+
const found = queueSeatForCwd(cwd, trees);
|
|
339
|
+
if (found) worktrees.push({ seat: found, depth });
|
|
182
340
|
} catch { /* Unknown attribution is charged to all seats. */ }
|
|
183
341
|
}
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
342
|
+
const deepest = Math.max(...members.map((entry) => entry.depth));
|
|
343
|
+
const inferred = new Set(worktrees.filter((entry) => entry.depth === deepest).map((entry) => entry.seat));
|
|
344
|
+
const complete = worktrees.filter((entry) => entry.depth === deepest).length
|
|
345
|
+
=== members.filter((entry) => entry.depth === deepest).length;
|
|
346
|
+
if (complete && inferred.size === 1) attributed.push({ seat: [...inferred][0]!, depth: deepest, source: 'cwd', priority: 3 });
|
|
347
|
+
const priority = Math.min(...attributed.map((entry) => entry.priority));
|
|
348
|
+
const eligible = attributed.filter((entry) => entry.priority === priority);
|
|
349
|
+
const depth = Math.max(...eligible.map((entry) => entry.depth));
|
|
350
|
+
const top = eligible.filter((entry) => entry.depth === depth);
|
|
351
|
+
const seats = new Set(top.map((entry) => entry.seat));
|
|
352
|
+
const assigned = seats.size === 1 ? top[0] : undefined;
|
|
353
|
+
try { debug.log('harness.queue', 'attributed', { pid: root.pid, seat: assigned?.seat ?? null, source: assigned?.source ?? 'all' }); }
|
|
354
|
+
catch { /* Observation does not block inventory. */ }
|
|
355
|
+
rows.push({ pid: root.pid, ...(assigned ? { seat: assigned.seat } : {}), ...(launchId ? { launchId } : {}) });
|
|
187
356
|
}
|
|
188
357
|
return rows;
|
|
189
358
|
}
|
|
@@ -242,6 +411,8 @@ export async function reconcileHarnessQueue(id: string, deps: HarnessQueueDeps =
|
|
|
242
411
|
if (state !== 'finished' && state !== 'not-started') return 'unknown';
|
|
243
412
|
// A finished receipt proves the child exited; a reused wrapper PID alone is not its identity.
|
|
244
413
|
// Keep completed launches in the ledger, but never charge their seat again.
|
|
414
|
+
const outcome = state === 'not-started' ? 'retryable' : queueRowOutcome(item, root);
|
|
415
|
+
if (item.idempotencyKey && (outcome === 'succeeded' || outcome === 'retryable')) recordQueueOutcome(root, id, outcome, item.idempotencyKey);
|
|
245
416
|
save(path, state === 'finished' && item.status === 'launched'
|
|
246
417
|
? rows.map((row) => row.id === id ? { ...row, status: 'finished' } : row)
|
|
247
418
|
: rows.filter((row) => row.id !== id));
|
|
@@ -256,21 +427,36 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}): Promise<Que
|
|
|
256
427
|
const items = read(path);
|
|
257
428
|
const item = items.find((row) => row.status === 'queued');
|
|
258
429
|
if (!item) { observe('skipped', { reason: 'empty' }, deps); return { outcome: 'skipped', reason: 'empty' }; }
|
|
259
|
-
const
|
|
430
|
+
const config = deps.configPath === undefined ? getUserConfig() : getUserConfig(deps.configPath);
|
|
431
|
+
const cap = (deps.cap ?? ((s: QueueSeat) => config.harness?.queue?.seatCap?.[s] ?? 8))(item.seat);
|
|
260
432
|
if (!Number.isSafeInteger(cap) || cap < 1) throw new Error(`harness queue: invalid seat cap for ${item.seat}`);
|
|
433
|
+
const caps = config.loops?.orchestrator?.seatCaps;
|
|
434
|
+
const gate = config.loops?.orchestrator?.releaseGate;
|
|
435
|
+
const attributed: Record<QueueSeat, number> = { OP: 0, TC: 0, MK: 0, UX: 0 };
|
|
436
|
+
let unattributed = 0;
|
|
261
437
|
const wait = (reason: string, active: number): QueueTick => {
|
|
262
|
-
observe('waiting', { id: item.id, seat: item.seat, reason, active, cap }, deps);
|
|
438
|
+
observe('waiting', { id: item.id, seat: item.seat, reason, active, cap, attributed, unattributed }, deps);
|
|
263
439
|
return { outcome: 'waiting', item, reason };
|
|
264
440
|
};
|
|
265
441
|
let processes: readonly QueueProcess[];
|
|
266
442
|
try { processes = (deps.processes ?? readHarnessQueueProcesses)(); }
|
|
267
|
-
catch (error) {
|
|
443
|
+
catch (error) {
|
|
444
|
+
const budget = decideSpawn({ seat: item.seat, running: null, caps, gate });
|
|
445
|
+
return wait(`${budget.reason}: harness process inventory unavailable: ${String(error)}`, 0);
|
|
446
|
+
}
|
|
447
|
+
for (const row of processes) {
|
|
448
|
+
if (row.seat) attributed[row.seat]++;
|
|
449
|
+
else unattributed++;
|
|
450
|
+
}
|
|
268
451
|
const current = items.flatMap((row) => {
|
|
269
452
|
if ((row.status !== 'launched' && row.status !== 'launching') || !row.launchId) return [row];
|
|
270
453
|
const state = (deps.receipt ?? receipt)(root, row.launchId);
|
|
271
454
|
if (state !== 'finished' && state !== 'not-started'
|
|
272
455
|
|| processes.some((process) => process.launchId === row.launchId)) return [row];
|
|
273
|
-
|
|
456
|
+
if (state === 'finished' && row.status === 'launched') return [{ ...row, status: 'finished' as const }];
|
|
457
|
+
const outcome = state === 'not-started' ? 'retryable' : queueRowOutcome(row, root);
|
|
458
|
+
if (row.idempotencyKey && (outcome === 'succeeded' || outcome === 'retryable')) recordQueueOutcome(root, row.id, outcome, row.idempotencyKey);
|
|
459
|
+
return [];
|
|
274
460
|
});
|
|
275
461
|
if (current.length !== items.length || current.some((row, index) => row !== items[index])) save(path, current);
|
|
276
462
|
const charged = processes.filter((row) => !row.seat || row.seat === item.seat);
|
|
@@ -289,6 +475,8 @@ export async function tickHarnessQueue(deps: HarnessQueueDeps = {}): Promise<Que
|
|
|
289
475
|
if (pool.running + pool.pending + pool.reserved + queueLaunching >= pool.limit) {
|
|
290
476
|
return wait(`pool: ${pool.running}+${pool.pending}+${pool.reserved}+${queueLaunching}/${pool.limit}`, active);
|
|
291
477
|
}
|
|
478
|
+
const budget = decideSpawn({ seat: item.seat, running: active, caps, gate });
|
|
479
|
+
if (!budget.allow) return wait(`${budget.reason}: seat ${item.seat}: ${active}/${budget.cap}`, active);
|
|
292
480
|
const launching: QueueItem = { ...item, status: 'launching', launchId: `hq-${randomUUID()}` };
|
|
293
481
|
save(path, current.map((row) => row.id === item.id ? launching : row));
|
|
294
482
|
try {
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
|
|
4
|
+
export type StoppedPrCategory = 'review-budget' | 'no-progress' | 'environment-gap' | 'memo-not-reflected' | 'review-oscillation' | 'goal-revision' | 'report-deficit' | 'unknown';
|
|
5
|
+
export type StoppedPrClassification = {
|
|
6
|
+
category: StoppedPrCategory;
|
|
7
|
+
signals: string[];
|
|
8
|
+
firstAction: string;
|
|
9
|
+
blockers: string[];
|
|
10
|
+
blockerCount: number;
|
|
11
|
+
hasPublicExportLeak: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
function section(body: string, title: string): string {
|
|
15
|
+
const heading = `## ${title}`;
|
|
16
|
+
const lines = body.split(/\r?\n/);
|
|
17
|
+
const start = lines.findIndex((line) => line.trim() === heading);
|
|
18
|
+
if (start < 0) return '';
|
|
19
|
+
const end = lines.findIndex((line, i) => i > start && /^##\s+/.test(line));
|
|
20
|
+
return lines.slice(start + 1, end < 0 ? undefined : end).join('\n');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function field(text: string, key: string): string {
|
|
24
|
+
const line = text.split(/\r?\n/).find((entry) => new RegExp(`^\\s*(?:[-*]\\s*)?(?:\\*\\*)?${key}(?:\\*\\*)?\\s*[::]\\s*`, 'i').test(entry));
|
|
25
|
+
return line?.replace(new RegExp(`^\\s*(?:[-*]\\s*)?(?:\\*\\*)?${key}(?:\\*\\*)?\\s*[::]\\s*`, 'i'), '').trim().replace(/^`|`$/g, '') ?? '';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function classifyStoppedPr({ body, comments }: { body: string; comments: readonly (string | { body: string })[] }): StoppedPrClassification {
|
|
29
|
+
const stop = section(body, '중단 사유');
|
|
30
|
+
const classification = section(body, '중단 원인 분류');
|
|
31
|
+
const gate = section(body, 'Gate');
|
|
32
|
+
const reason = field(stop, 'reason');
|
|
33
|
+
const verdict = field(stop, 'verdict');
|
|
34
|
+
const label = field(classification, 'classification');
|
|
35
|
+
const basis = field(classification, 'classificationBasis');
|
|
36
|
+
const commentText = comments.map((comment) => typeof comment === 'string' ? comment : comment.body);
|
|
37
|
+
const signals = [reason, verdict, label, basis].filter(Boolean);
|
|
38
|
+
const allBlockers = gate.split(/\r?\n/).filter((line) => /^\s*(?:✗\s+|\[test\]\s+FAIL\b)/i.test(line));
|
|
39
|
+
const blockers = allBlockers.slice(0, 5);
|
|
40
|
+
const publicExportLeak = allBlockers.find((line) => /^\s*✗\s+public-export-leak\b/i.test(line));
|
|
41
|
+
if (publicExportLeak && !blockers.includes(publicExportLeak)) signals.push(publicExportLeak);
|
|
42
|
+
const failedTests = allBlockers.filter((line) => /^\s*\[test\]\s+FAIL\b.*[—–-](?:\s*\d+\s+(?:pass|fail|skip|errors?)\b\s*[,|])*\s*[1-9]\d*\s+errors?\b/i.test(line));
|
|
43
|
+
const failedPwaTest = failedTests.find((line) => /\bapps\/pwa\/[^\s]*\.test\.tsx?\b/i.test(line));
|
|
44
|
+
if (failedPwaTest) signals.push(failedPwaTest);
|
|
45
|
+
const hostRetest = /(?:^|;\s*)host=[^;\n]+;\s*cause=(?:missing (?:node_modules|dependency|environment variable)|호스트 의존성 없음)[^;\n]*;\s*retest=PASS bun test (\S+\.test\.tsx?)(?:\s*(?:;|$))/i.exec(basis);
|
|
46
|
+
// A code error in the Gate outranks any written basis: a basis string alone never turns a code failure into an environment gap.
|
|
47
|
+
const codeErrorInGate = /\b(?:TypeError|ReferenceError|SyntaxError|RangeError)\b|expect\(received\)/.test(gate);
|
|
48
|
+
const hostFailureConfirmed = !codeErrorInGate && Boolean(hostRetest && failedTests.some((line) => {
|
|
49
|
+
const failedPath = /^\s*\[test\]\s+FAIL\s+bun test (\S+\.test\.tsx?)\s+[—–-]/i.exec(line)?.[1];
|
|
50
|
+
return failedPath === hostRetest?.[1];
|
|
51
|
+
}));
|
|
52
|
+
const reviews = commentText.flatMap((text) => {
|
|
53
|
+
const round = Number(/<!--\s*elanous-pr-comment v1 role=reviewer round=(\d+)\b/.exec(text)?.[1] ?? /\bRound\s+(\d+):\s*reviewer requested\b/i.exec(text)?.[1] ?? -1);
|
|
54
|
+
const mustFix = /\bRound\s+\d+:\s*reviewer requested\s+(\d+)\s+must-fix change\(s\)/i.exec(text);
|
|
55
|
+
return mustFix ? [{ round, count: Number(mustFix[1]), evidence: mustFix[0] }] : [];
|
|
56
|
+
}).sort((a, b) => b.round - a.round);
|
|
57
|
+
const latest = reviews[0];
|
|
58
|
+
if (latest) signals.push(latest.evidence);
|
|
59
|
+
|
|
60
|
+
const stopEvidence = `${reason}\n${verdict}\n${label}\n${basis}\n${body.match(/정지\((?:needs-human|no-progress)\)/g)?.join('\n') ?? ''}`;
|
|
61
|
+
let category: StoppedPrCategory = 'unknown';
|
|
62
|
+
let action = '하지 않음 — 정지 원인 근거 부족';
|
|
63
|
+
if (hostFailureConfirmed && failedTests.length > 0 && (/(?:자식\s*무관|environment(?:-gap|al)?|환경\s*결손)/i.test(`${label}\n${basis}`) || failedPwaTest)) {
|
|
64
|
+
category = 'environment-gap';
|
|
65
|
+
action = '그래프 인스턴스 성형 — 호스트에서 실패 시험만 재측; 원 Gate 보존';
|
|
66
|
+
} else if (/(?:메모\s*미반영|memo[- ]not[- ]reflected)/i.test(`${label}\n${basis}\n${reason}`)) {
|
|
67
|
+
category = 'memo-not-reflected';
|
|
68
|
+
action = '안내 메모 — 동일 칸의 살아 있는 런과 다음 산출 대조; 대상 불명이면 하지 않음';
|
|
69
|
+
} else if (/(?:리뷰\s*진동|설계\s*역행|review[- ]oscillation)/i.test(`${label}\n${basis}\n${reason}`)) {
|
|
70
|
+
category = 'review-oscillation';
|
|
71
|
+
action = latest?.count ? '헬퍼 자식 spawn — 기존 시험 설계 보존 후 한 라운드' : '하지 않음 — 구체적인 must-fix 없음';
|
|
72
|
+
} else if (label === 'goal-unconvergeable-candidate' || basis === 'supervisor-unconvergeable-goal-candidate') {
|
|
73
|
+
category = 'goal-revision';
|
|
74
|
+
signals.push(label === 'goal-unconvergeable-candidate'
|
|
75
|
+
? `classification=${label} → goal-revision`
|
|
76
|
+
: `classificationBasis=${basis} → goal-revision`);
|
|
77
|
+
action = '골 개정 — 골 문면(판정 신호·경계)을 고쳐 재발사; 구현 자식 붙이지 않음';
|
|
78
|
+
} else if (label === 'report-deficit') {
|
|
79
|
+
category = 'report-deficit';
|
|
80
|
+
signals.push(`classification=${label} → report-deficit`);
|
|
81
|
+
action = '증거 재측 — 인용된 판정 명령을 호스트에서 다시 돌려 결과를 PR 에 남김; 코드 수정 아님';
|
|
82
|
+
} else if (/review-budget/i.test(stopEvidence) || basis === 'must-fix-reported') {
|
|
83
|
+
// Real supervisor bodies (UNCONVERGEABLE · implementation-deficit) carry `classificationBasis: must-fix-reported` instead of the RFC word.
|
|
84
|
+
if (basis === 'must-fix-reported') signals.push('classificationBasis=must-fix-reported → review-budget');
|
|
85
|
+
category = 'review-budget';
|
|
86
|
+
action = latest?.count ? '헬퍼 자식 spawn — must-fix 원문과 기존 시험 설계로 한 라운드' : '하지 않음 — must-fix 0 또는 미관측; 수리 자식 대상 아님';
|
|
87
|
+
} else if (/no-progress/i.test(stopEvidence) || basis === 'no-must-fix-without-clean-worktree-or-completed-without-changes') {
|
|
88
|
+
if (basis === 'no-must-fix-without-clean-worktree-or-completed-without-changes') signals.push(`classificationBasis=${basis} → no-progress`);
|
|
89
|
+
category = 'no-progress';
|
|
90
|
+
const green = /\[test\]\s+PASS\b/.test(gate) && !/\[test\]\s+FAIL\b/.test(gate);
|
|
91
|
+
const documentGoal = /(?:문서\s*(?:골|목표)|docs?\/|RFC)/i.test(`${reason}\n${basis}\n${body.split(/\r?\n/)[0] ?? ''}`);
|
|
92
|
+
action = green && latest?.count === 0 && documentGoal
|
|
93
|
+
? '중간 착지 ⊕ 연결 골 — must-fix 0 · 시험 초록 · 문서 골이면 잔여 확인; 잔여 0이면 연결 골 하지 않음'
|
|
94
|
+
: '중간 착지 ⊕ 연결 골 — 녹색 조각만 확인; 불가분 골·실패 Gate 착지 하지 않음';
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
category, signals,
|
|
98
|
+
firstAction: allBlockers.length
|
|
99
|
+
? `Gate 차단 먼저${publicExportLeak ? ' (✗ public-export-leak 확인)' : category === 'environment-gap' ? ' (그래프 인스턴스 성형으로 호스트 실패 시험 재측)' : ''}`
|
|
100
|
+
: action,
|
|
101
|
+
blockers, blockerCount: allBlockers.length, hasPublicExportLeak: Boolean(publicExportLeak),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Stopped means the supervisor wrote a stop verdict or reason into the `## 중단 사유` section — a quoted heading or stop wording elsewhere in the body (goal text, templates) does not count. */
|
|
106
|
+
export function isStoppedPrBody(body: string): boolean {
|
|
107
|
+
const stop = section(body, '중단 사유');
|
|
108
|
+
return Boolean(field(stop, 'verdict') || field(stop, 'reason'));
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
export type ScannedStoppedPr = StoppedPrClassification & { pr: number; headRefName: string; isDraft: boolean };
|
|
112
|
+
export type RunGh = (args: readonly string[]) => string | Promise<string>;
|
|
113
|
+
|
|
114
|
+
export async function scanStoppedPrs({ runGh }: { runGh: RunGh }): Promise<ScannedStoppedPr[]> {
|
|
115
|
+
const prs = JSON.parse(await runGh(['pr', 'list', '--state', 'open', '--limit', '100000', '--json', 'number,headRefName,isDraft,body'])) as Array<{ number: number; headRefName: string; isDraft: boolean; body: string }>;
|
|
116
|
+
const results: ScannedStoppedPr[] = [];
|
|
117
|
+
for (const pr of prs) {
|
|
118
|
+
if (!pr.headRefName.startsWith('self-impl/') || !isStoppedPrBody(pr.body)) continue;
|
|
119
|
+
const data = JSON.parse(await runGh(['pr', 'view', String(pr.number), '--json', 'comments'])) as { comments: Array<{ body: string }> };
|
|
120
|
+
const result = classifyStoppedPr({ body: pr.body, comments: data.comments });
|
|
121
|
+
debug.log('harness.helper', 'classified', { pr: pr.number, category: result.category, firstAction: result.firstAction, blockers: result.blockers, blockerCount: result.blockerCount, hasPublicExportLeak: result.hasPublicExportLeak });
|
|
122
|
+
results.push({ pr: pr.number, headRefName: pr.headRefName, isDraft: pr.isDraft, ...result });
|
|
123
|
+
}
|
|
124
|
+
return results;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (import.meta.main) {
|
|
128
|
+
if (process.argv.slice(2).some((arg) => arg !== '--json')) {
|
|
129
|
+
console.error('사용법: bun src/harness/helper-scan.ts [--json]');
|
|
130
|
+
process.exitCode = 1;
|
|
131
|
+
} else {
|
|
132
|
+
const rows = await scanStoppedPrs({ runGh: (args) => execFileSync('gh', [...args], { encoding: 'utf8' }) });
|
|
133
|
+
if (process.argv.includes('--json')) console.log(JSON.stringify(rows, null, 2));
|
|
134
|
+
else for (const row of rows) {
|
|
135
|
+
const gate = row.blockerCount ? ` · Gate 차단 ${row.blockerCount}(${row.hasPublicExportLeak ? row.blockers.find((line) => /public-export-leak/i.test(line))?.trim() ?? '✗ public-export-leak (6번째 이후)' : row.blockers[0]!.trim()})` : '';
|
|
136
|
+
console.log(`#${row.pr} ${row.category}${gate} → ${row.firstAction}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { debug } from '../debug/log.js';
|
|
2
|
+
|
|
3
|
+
/** One observation per harness node execution or loop tick, not per LLM request. */
|
|
4
|
+
export type ModelInputObservationTarget = {
|
|
5
|
+
scope: 'harness-node' | 'loop-tick';
|
|
6
|
+
nodeKind: string;
|
|
7
|
+
graphId?: string;
|
|
8
|
+
runId?: string;
|
|
9
|
+
nodeId?: string;
|
|
10
|
+
tickId?: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export type ModelInputObservation = ModelInputObservationTarget & (
|
|
14
|
+
| { status: 'measured'; inputTokens: number }
|
|
15
|
+
| { status: 'unmeasured'; inputTokens: null; reason: 'usage-unavailable' | 'invalid-usage' }
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Publish a single structured record at the execution boundary. Only provider-reported,
|
|
20
|
+
* nonnegative integer counts are measurements; missing/invalid usage is never a zero.
|
|
21
|
+
* The caller supplies the total across model calls for this execution, if available.
|
|
22
|
+
*/
|
|
23
|
+
export function observeModelInputTokens(
|
|
24
|
+
target: ModelInputObservationTarget,
|
|
25
|
+
inputTokens?: number | null,
|
|
26
|
+
): ModelInputObservation {
|
|
27
|
+
const observation: ModelInputObservation = inputTokens === null || inputTokens === undefined
|
|
28
|
+
? { ...target, status: 'unmeasured', inputTokens: null, reason: 'usage-unavailable' }
|
|
29
|
+
: Number.isSafeInteger(inputTokens) && inputTokens >= 0
|
|
30
|
+
? { ...target, status: 'measured', inputTokens }
|
|
31
|
+
: { ...target, status: 'unmeasured', inputTokens: null, reason: 'invalid-usage' };
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
debug.log('harness.model-input', 'recorded', observation);
|
|
35
|
+
} catch { /* Logging must not interrupt the node or tick. */ }
|
|
36
|
+
return observation;
|
|
37
|
+
}
|
package/src/hq/hq.ts
CHANGED
|
@@ -302,7 +302,7 @@ export function hqCliWriteAllowed(command: string, override = false, deps: HqDep
|
|
|
302
302
|
}
|
|
303
303
|
if (allowed) return true;
|
|
304
304
|
observe(r, 'hq.fence', 'cli-refused', { command, reason: decision.reason, holder: decision.holder, generation: decision.generation });
|
|
305
|
-
console.error(`본부는 ${decision.holder ?? 'unknown'} gen ${decision.generation ?? 'unknown'} —
|
|
305
|
+
console.error(`본부는 ${decision.holder ?? 'unknown'} gen ${decision.generation ?? 'unknown'} — 원장 쓰기는 지금 본부로 보내라 (수동 우회: --hq-override, 관측)`);
|
|
306
306
|
process.exitCode = 4;
|
|
307
307
|
return false;
|
|
308
308
|
}
|