elanous 0.2.11 → 0.2.13
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/{1394-556de6d71bbdb0fb.js → 1394-931c9827067b2276.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3992-2e522a8ac5f2afde.js → 3992-fe5c06d6b31f8b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5250-c78ca295585bbf95.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6511-ff8360be3d65aaa4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6588-ba380869b67c4fa7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-85c2b53f731da024.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8605-ea5bee9dfae512c7.js → 8605-9da4678f8a310d18.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9906-e7b8b5310a169dfe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-e5b4e1b256f41076.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-6a061bd97f0b9398.js → page-e49b9b76c565a0b1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-b4f032c893b21cc9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-f14987d5850a31e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b433308aad13ceb6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-7c97dcdfe2e37a60.js → page-4c1dbb21861beab2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-c85b816e3b269dbb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-5721537ca8a3598c.js → page-f6bc66b919b2ea7d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/{page-b654240a572e4c48.js → page-60603c1e056c9b25.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-b9750abc897508c3.js → page-a0ea8f5a2341ee6b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-45c5bf04b4a605e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-36c6c4183353ac9e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-cb2eb0fd809980da.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-c8b1ccda6ba7ae98.js → page-31012264da8a341f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-d6b1d156fb95e74b.js → layout-50309aeb0bdd554a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-bc00e964e855f4df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-9688f5b4f1ff950f.js → page-0696ee3fac9fe56c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-66c27608f651b866.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-35414e0a007b4082.js → page-6ddd2109b7fc3a80.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-4e50b7298c1f69a5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/{page-e001823c35c9dd67.js → page-7a2bf830038a2264.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-371423d1a573d64b.js → page-1f327f18694fabca.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-4b764b125339ac17.js → page-e5cef2fe6e4028df.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/{page-cdf8e4b01067bcf1.js → page-cf596128d2b420ba.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/{page-3a24072b35470157.js → page-f672536f14f0969f.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/page-b52365662232db89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-e7a6c80f288b1948.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-d37cb0d52f9b6bd5.js → page-29a709616df9b050.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/{page-686da0df9bf23e5c.js → page-fee842d7c2151129.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-4dc614f3ce043e63.js → page-78b40a344ed22f24.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-75d199f448171014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/{page-d02c782fc792e2af.js → page-a011dd5b0bff29c5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/{page-239c17b911b84e72.js → page-d92e32d51ab5157e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/share/{page-dc6a79fb003f746c.js → page-5f21aed6932ef375.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-0fffb84cafdc9da6.js → page-1e1a53946804debf.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-1218753c82d585f6.js → page-a1833cd43d832601.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-4b5935e67531efb5.js → page-63d14a4d8fe1e1c1.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/{page-3285bc87caf1eaa5.js → page-2a511d320f762e06.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/{page-ad553b0e06505ecd.js → page-1c3e03f88dac1e21.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-fca096e3e860306f.js → page-c4d5289c2ea97a89.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-9ebfa78f0d9885c3.js → page-b91f8952deaeaf27.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-3dc5f21f7e39622d.js → page-71b43a2588489987.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2b256feed331546f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-505bb96d69a8622c.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +14 -14
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +14 -14
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +14 -14
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +14 -14
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +14 -14
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +14 -14
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +14 -14
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +14 -14
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +14 -14
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +14 -14
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +14 -14
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +14 -14
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +14 -14
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +14 -14
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +14 -14
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +14 -14
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +14 -14
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +14 -14
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +14 -14
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +14 -14
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +14 -14
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +14 -14
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +14 -14
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +14 -14
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +15 -15
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +14 -14
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +14 -14
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +14 -14
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +14 -14
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +14 -14
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +14 -14
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +14 -14
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +14 -14
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +14 -14
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +14 -14
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +14 -14
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +14 -14
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +14 -14
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +14 -14
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +14 -14
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +14 -14
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +14 -14
- package/examples/teams/personas/backend.yaml +4 -0
- package/examples/teams/personas/pm.yaml +4 -0
- package/examples/teams/personas/qa.yaml +4 -0
- package/examples/teams/small-product.yaml +20 -0
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/graphs/lecture/lecture-note.yaml +28 -0
- package/graphs/lecture/recipes.yaml +17 -0
- package/package.json +2 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/scripts/context-hooks/emit.ts +36 -14
- package/scripts/coord-channel-watch.sh +1 -0
- package/scripts/discord-seat-probe.ts +144 -0
- package/scripts/hq/hq-standby.sh +65 -0
- package/scripts/hq/standby-promote.ts +173 -0
- package/scripts/hq/standby-role.ts +59 -0
- package/scripts/hq/standby-snapshot.ts +226 -0
- package/scripts/hq/standby-verify.ts +219 -0
- package/scripts/lecture-note/node.ts +256 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/release-loop/auto-approve-node.ts +1 -1
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/gate-node.ts +61 -17
- package/scripts/release-loop/ops-upgrade-node.ts +59 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/version-node.ts +13 -3
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-deterministic.ts +53 -3
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/ux/demo-rehearsal.ts +17 -13
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +12 -5
- package/skills/omni-crawl/SKILL.md +16 -16
- package/skills/omni-crawl/package-lock.json +1 -544
- package/skills/omni-crawl/package.json +3 -4
- package/skills/omni-crawl/scripts/main.ts +13 -13
- package/skills/omni-crawl/scripts/monitor.ts +8 -8
- package/skills/omni-crawl/src/apify.ts +13 -2
- package/skills/omni-crawl/src/capture.ts +6 -0
- package/skills/omni-crawl/src/registry.ts +2 -2
- package/skills/omni-crawl/src/stdout-json.ts +4 -0
- package/skills/omni-digest/SKILL.md +10 -11
- package/skills/omni-digest/package-lock.json +0 -545
- package/skills/omni-digest/package.json +2 -5
- package/skills/omni-digest/scripts/main.ts +3 -3
- package/skills/omni-digest/src/delegate.ts +5 -5
- package/skills/omni-digest/src/fetch.ts +2 -2
- package/skills/omni-digest/src/media.ts +25 -11
- package/skills/omni-digest/src/visual.ts +3 -2
- package/skills/omni-market/scripts/main.ts +1 -1
- package/skills/omni-market/scripts/stdout-json.ts +4 -0
- package/src/agent/identity-env.ts +11 -0
- package/src/agent-mission/driver.ts +150 -23
- package/src/agent-mission/duo.ts +94 -0
- package/src/agent-mission/pty-decision.ts +2 -2
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/autopilot/track-agent.ts +4 -1
- package/src/boot/daemon-prompt-request.ts +3 -0
- package/src/boot/daemon-prompt-turn.ts +6 -2
- package/src/boot/daemon-runtime.ts +48 -16
- package/src/chat/index.ts +1 -0
- package/src/cli/decisions-cli.ts +51 -19
- package/src/cli/outputs-cli.ts +92 -0
- package/src/cli/persona-cli.ts +45 -13
- package/src/cli/pod-cli.ts +27 -3
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/pr-cli.ts +15 -1
- package/src/cli/release-cli.ts +135 -15
- package/src/context-bus/condense.ts +194 -0
- package/src/context-bus/context-day.ts +82 -0
- package/src/context-bus/context-now-surfaces.ts +32 -2
- package/src/context-bus/context-now.ts +2 -2
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/long-term-memory.ts +118 -22
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +8 -0
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +24 -0
- package/src/decisions/decision-cards.ts +13 -7
- package/src/decisions/decision-ledger.ts +43 -17
- package/src/domains/outbound-alert.ts +99 -31
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/execution-loop/launch-gate.ts +17 -8
- package/src/field-note/telegram.ts +142 -0
- package/src/graph-kinds/registry.ts +2 -2
- package/src/graph-runner/graph-approval-card.ts +2 -2
- package/src/graph-runner/graph-grow-llm.ts +23 -13
- package/src/graph-runner/graph-grow.ts +69 -13
- package/src/graph-runner/graph-tick.ts +9 -2
- package/src/graph-runner/runner.ts +92 -24
- package/src/harness/harness-cli-command.ts +165 -17
- package/src/harness/harness-queue-child.ts +37 -0
- package/src/harness/harness-queue.ts +306 -0
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/github-shadow.ts +72 -2
- package/src/hooks/receiver.ts +82 -3
- package/src/hq/hq.ts +318 -0
- package/src/hq/lease.ts +210 -0
- package/src/hq/ledger-write-target.ts +41 -0
- package/src/index.ts +232 -16
- package/src/intake-plane/discord-seat-work.ts +36 -3
- package/src/intake-plane/linear-wish.ts +44 -0
- package/src/intake-plane/persona-answer.ts +5 -0
- package/src/intake-plane/telegram-seat-work.ts +13 -6
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/llm/difficulty.ts +1 -1
- package/src/loops/checker.ts +178 -0
- package/src/loops/loop-cli.ts +37 -3
- package/src/loops/observe.ts +49 -0
- package/src/loops/orchestrator/overlap.ts +141 -0
- package/src/loops/orchestrator/traffic.ts +150 -0
- package/src/loops/package/verify.ts +125 -0
- package/src/loops/verdict.ts +22 -0
- package/src/maturity/feature-maturity.ts +5 -4
- package/src/memory.ts +37 -4
- package/src/nexus/api/card-followup-route.ts +118 -0
- package/src/nexus/api/decisions-route.ts +68 -0
- package/src/nexus/api/http-server.ts +78 -8
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/personas.ts +97 -54
- package/src/nexus/api/skill-exec-route.ts +97 -0
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflows.ts +52 -0
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/persona/loader.ts +18 -0
- package/src/persona/persona-todo.ts +14 -9
- package/src/persona/presets.ts +10 -2
- package/src/persona/team.ts +105 -0
- package/src/persona/types.ts +4 -0
- package/src/plugins/maker/market-bundle.ts +2 -1
- package/src/pod-lease/host-lease.ts +130 -20
- package/src/release-loop/checklist.ts +15 -3
- package/src/release-loop/feature-store.ts +24 -10
- package/src/release-loop/landed-but-yellow.ts +60 -0
- package/src/seat-dispatch/seat-ask.ts +57 -12
- package/src/seat-loop/seat-loop.ts +253 -18
- package/src/seat-loop/seat-pack.ts +127 -0
- package/src/self-dev/ask-launch-flow.ts +3 -0
- package/src/self-dev/draft-sweep.ts +44 -10
- package/src/self-dev/draft-triage-rules.ts +54 -2
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-scope.ts +9 -8
- package/src/self-implement/headless-elanous-driver.ts +7 -2
- package/src/self-implement/l8-merge-queue.ts +157 -1
- package/src/self-implement/orchestrator.ts +15 -0
- package/src/self-implement/test-seams.ts +1 -0
- package/src/steward/launch.ts +5 -1
- package/src/steward/readiness.ts +125 -0
- package/src/steward/rescue.ts +75 -0
- package/src/steward/triage.ts +48 -9
- package/src/task-cards/card-cli.ts +78 -0
- package/src/task-orchestrator/surfaces/pod-command-job.ts +14 -10
- package/src/task-orchestrator/surfaces/pod-lease.ts +18 -6
- package/src/task-orchestrator/surfaces/pod-pool.ts +27 -10
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +46 -8
- package/src/telegram-commands.ts +21 -0
- package/src/telegram-dispatch-forward.ts +1 -1
- package/src/telegram-fabric-plan.ts +90 -0
- package/src/telegram-project-command.ts +84 -4
- package/src/telegram.ts +37 -5
- package/src/tool-runtime/elanous-control-runtimes.ts +2 -1
- package/src/user-config.ts +101 -13
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/executor.ts +4 -0
- package/src/workflow-runtime/node-catalog.ts +13 -0
- package/src/workflow-runtime/nodes/knowledge.ts +38 -0
- package/src/workflow-runtime/schema.ts +17 -1
- package/src/workflow-runtime/types.ts +13 -1
- package/apps/pwa/out/_next/static/chunks/2310-7a59f043f6db207e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2548-6e316138d347cefe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3869-a39d1d9921e10d43.js +0 -1
- package/apps/pwa/out/_next/static/chunks/507-5acd98bd6dab2d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5445-745eb795c5963d79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-1dcdc58fc2de2454.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-cb95a7432c9ee744.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-b12b6d2f8d2d1aea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-b9efe868bfd36255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-8fd36984f549270a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-4cd1de371f8ad048.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-d99f89657828421c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-50cb2a0eb15e6c83.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-2a9932ddb62d76bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6b15dd6456c5df09.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e7a529e0507ac86c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-94c82eb0ae5abb32.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0be8d4b2ced75ba0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-53f80edc6b8dc07c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-c6da2b468454532d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-93bc70e36f595414.js +0 -1
- package/apps/pwa/out/_next/static/css/543f56bcb1f79fca.css +0 -1
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_buildManifest.js +0 -0
- /package/apps/pwa/out/_next/static/{Y4zKzL62sakG5momz3SoQ → D51l8jUBplJrIpWvENsDK}/_ssgManifest.js +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { execFile } from 'node:child_process';
|
|
2
|
-
import { createHash } from 'node:crypto';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
3
|
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
4
4
|
import { Database } from 'bun:sqlite';
|
|
5
5
|
import { dirname, join, resolve } from 'node:path';
|
|
@@ -8,16 +8,18 @@ import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot
|
|
|
8
8
|
import { debug } from '../debug/log.js';
|
|
9
9
|
import { DecisionLedger, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
10
10
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
11
|
+
import { loopEvent } from '../loops/observe.js';
|
|
11
12
|
import { getUserConfig, type PersonaLoopConfig, type SeatLoopConfig, type UserConfig } from '../user-config.js';
|
|
12
13
|
import { checkSeatBudget, type RunningSeatPod } from '../org/seat-budget.js';
|
|
13
14
|
import { DEFAULT_STALL_PARENTS, findStalls } from '../org/stall-escalation.js';
|
|
14
15
|
import { openMsgStore, type MessageEnvelope } from '../msg/msg-store.js';
|
|
15
16
|
import { loadLayeredPersonaDirs, resolveRepositoryPersonaDir, resolveStatePersonaDir } from '../persona/global-registry.js';
|
|
16
|
-
import {
|
|
17
|
+
import { orderedPersonaTodos, personaTodoPath, readPersonaTodos, type PersonaTodo } from '../persona/persona-todo.js';
|
|
17
18
|
import { PersonaRegistry } from '../persona/registry.js';
|
|
19
|
+
import { recordSeatAskShadow } from '../seat-dispatch/seat-ask.js';
|
|
18
20
|
import { answerSeat, askSeat, deliveredSeatQuestion, hasSeatAnswer, isSeatId, seatQuestions, type SeatId } from '../seat-dispatch/seat-questions.js';
|
|
19
21
|
import { queryRunningRuns } from '../self-implement/running-runs.js';
|
|
20
|
-
import { listChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
22
|
+
import { checklistHistory, listChecklist, type Checklist } from '../release-loop/checklist.js';
|
|
21
23
|
import { readSchedules, releasedVersion } from '../release-loop/feature-store.js';
|
|
22
24
|
|
|
23
25
|
// `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
|
|
@@ -31,9 +33,14 @@ export type OpCandidate =
|
|
|
31
33
|
| { kind: 'release-readiness'; version: string | null; verdict: 'ready' | 'not-ready' | 'no-open-release'; cutAt?: string; landBy?: string | null; red: string[]; undecided: string[]; blocked: string[] }
|
|
32
34
|
| { kind: 'unassigned-cell'; version: string | null; id: string | null; title?: string; status?: string; verdict: 'assign' | 'none' }
|
|
33
35
|
| { kind: 'decision-delegation'; id: string | null; title?: string; category?: DecisionEntry['category']; dueAt?: string; verdict: 'review-delegation' | 'none' };
|
|
34
|
-
export type
|
|
36
|
+
export type TcCandidate =
|
|
37
|
+
| { kind: 'merged-pr-yellow-cell'; version: string; id: string; title: string; pr: number; mergedAt: string }
|
|
38
|
+
| { kind: 'publication-pr-approval-wait'; pr: number; title: string; readyAt: string; approvalWaitAt: string; paths: string[] }
|
|
39
|
+
| { kind: 'other-seat-cell-defect'; version: string; id: string; title: string; to: SeatId };
|
|
40
|
+
export type TcPullRequest = { number: number; title: string; body?: string; state: 'OPEN' | 'MERGED' | 'CLOSED'; isDraft: boolean; createdAt: string; updatedAt?: string; mergedAt?: string | null; readyAt?: string; approvalWaitAt?: string; paths?: string[]; reviewDecision?: string };
|
|
41
|
+
export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'wait' | 'asked' | 'answered' | 'awaiting-answer' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; candidate?: OpCandidate | TcCandidate; action?: SeatAction; reason?: string; runId?: string; inquiry?: SeatInquiry; answer?: string; escalated?: boolean };
|
|
35
42
|
export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
|
|
36
|
-
export type PersonaShadowEntry = { personaId: string; ts: string; status: 'shadow' | 'skipped-empty'; todo: PersonaTodo | null };
|
|
43
|
+
export type PersonaShadowEntry = { personaId: string; ts: string; status: 'shadow' | 'skipped-empty'; todo: PersonaTodo | null; action?: 'decision' | 'harness' | 'wait'; what?: string };
|
|
37
44
|
export type SeatDeps = {
|
|
38
45
|
personaConfig?: PersonaLoopConfig;
|
|
39
46
|
personaRegistry?: PersonaRegistry;
|
|
@@ -54,12 +61,16 @@ export type SeatDeps = {
|
|
|
54
61
|
schedules?: () => Array<{ version: string; cutAt: string; landBy?: string | null }>;
|
|
55
62
|
/** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
|
|
56
63
|
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null; disposition?: string; evidence?: string }>;
|
|
64
|
+
/** Chronological release-ledger events for a checklist id; injected for isolated release fixtures. */
|
|
65
|
+
checklistHistory?: (id: string) => ReturnType<typeof checklistHistory>;
|
|
57
66
|
/** Full checklist snapshot including history, for stall detection. */
|
|
58
67
|
stallChecklist?: (version: string) => Checklist;
|
|
59
68
|
/** Inject a mailbox write for isolated failure tests; production uses the seat message store. */
|
|
60
69
|
stallDelivery?: (message: MessageEnvelope, key: string, root: string) => boolean;
|
|
61
70
|
/** Open cards from the decision ledger; injected only for isolated ledger tests. */
|
|
62
71
|
pendingDecisions?: () => Array<Pick<DecisionEntry, 'id' | 'title' | 'category' | 'dueAt'>>;
|
|
72
|
+
/** Read-only PR snapshot for TC judgment; defaults to GitHub CLI reads. */
|
|
73
|
+
pullRequests?: () => Promise<TcPullRequest[]>;
|
|
63
74
|
ledgerFiles?: (directory: string) => string[];
|
|
64
75
|
lockContended?: () => void;
|
|
65
76
|
/** Supply a question only when this seat lacks evidence for the selected work item. */
|
|
@@ -126,8 +137,17 @@ export function personaShadowLedgerPath(personaId: string, root: string, now: Da
|
|
|
126
137
|
return join(root, 'persona-loop', personaId, `${seatDay(now)}.jsonl`);
|
|
127
138
|
}
|
|
128
139
|
|
|
140
|
+
export async function runPersonaLoopOnce(name: string, deps: SeatDeps = {}): Promise<PersonaShadowEntry> {
|
|
141
|
+
const entries = await pickPersonaShadows(deps, name);
|
|
142
|
+
return entries[0]!;
|
|
143
|
+
}
|
|
144
|
+
|
|
129
145
|
export async function runPersonaShadowOnce(deps: SeatDeps = {}): Promise<PersonaShadowEntry[]> {
|
|
130
146
|
if ((deps.personaConfig ?? getUserConfig().loops?.persona)?.enabled !== true) return [];
|
|
147
|
+
return pickPersonaShadows(deps);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function pickPersonaShadows(deps: SeatDeps, name?: string): Promise<PersonaShadowEntry[]> {
|
|
131
151
|
const root = deps.root ?? effectiveInstanceRoot();
|
|
132
152
|
const dir = deps.personaDir ?? (deps.root ? join(root, 'personas') : resolveStatePersonaDir());
|
|
133
153
|
const registry = deps.personaRegistry ?? new PersonaRegistry();
|
|
@@ -137,19 +157,31 @@ export async function runPersonaShadowOnce(deps: SeatDeps = {}): Promise<Persona
|
|
|
137
157
|
}
|
|
138
158
|
const now = (deps.now ?? (() => new Date()))();
|
|
139
159
|
const entries: PersonaShadowEntry[] = [];
|
|
140
|
-
|
|
160
|
+
const profiles = registry.list().sort((a, b) => a.personaId.localeCompare(b.personaId));
|
|
161
|
+
const selected = name === undefined ? profiles : profiles.filter((profile) => profile.personaId.toLowerCase() === name.trim().toLowerCase()
|
|
162
|
+
|| profile.displayName.toLowerCase() === name.trim().toLowerCase());
|
|
163
|
+
if (name !== undefined && selected.length !== 1) throw new Error(selected.length ? `ambiguous persona name: ${name}` : `persona not found: ${name}`);
|
|
164
|
+
for (const profile of selected) {
|
|
141
165
|
const personaId = profile.personaId;
|
|
142
|
-
const
|
|
166
|
+
const todoFile = profile.todo ?? `${personaId}.todo.jsonl`;
|
|
167
|
+
const { todos, errors } = readPersonaTodos(dir, todoFile.slice(0, -'.todo.jsonl'.length));
|
|
143
168
|
for (const error of errors) observe('persona-todo-error', { personaId, error });
|
|
144
|
-
const
|
|
145
|
-
const
|
|
169
|
+
const candidates = orderedPersonaTodos(todos);
|
|
170
|
+
const picked = pickNext({ requests: candidates.map((candidate) => ({
|
|
171
|
+
source: 'request', id: candidate.id, title: candidate.title, text: candidate.title, createdAt: candidate.createdAt,
|
|
172
|
+
})), checklist: [], role: '' }, [], { shadow: true });
|
|
173
|
+
const todo = picked ? candidates.find((candidate) => candidate.id === picked.id) ?? null : null;
|
|
174
|
+
const planned = name !== undefined && todo && picked ? planAction(picked, profile.seat) : undefined;
|
|
175
|
+
const entry: PersonaShadowEntry = { personaId, ts: now.toISOString(), status: todo ? 'shadow' : 'skipped-empty', todo,
|
|
176
|
+
...(planned ? { action: planned.kind, what: planned.text } : {}) };
|
|
146
177
|
const path = personaShadowLedgerPath(personaId, root, now);
|
|
147
178
|
if (deps.appendPersona) deps.appendPersona(path, entry);
|
|
148
179
|
else {
|
|
149
180
|
mkdirSync(dirname(path), { recursive: true });
|
|
150
181
|
appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
151
182
|
}
|
|
152
|
-
|
|
183
|
+
try { debug.log('persona.loop', 'picked', { personaId, status: entry.status, todoId: todo?.id ?? null, title: todo?.title ?? null, action: entry.action ?? null }); }
|
|
184
|
+
catch { /* observation cannot alter the shadow ledger */ }
|
|
153
185
|
entries.push(entry);
|
|
154
186
|
}
|
|
155
187
|
return entries;
|
|
@@ -159,6 +191,25 @@ export async function runSeatLoopTurn(seat: string, deps: SeatDeps = {}): Promis
|
|
|
159
191
|
const result = await runSeatLoopOnce(seat, deps);
|
|
160
192
|
try { await runPersonaShadowOnce(deps); }
|
|
161
193
|
catch (error) { observe('persona-shadow-error', { error: String(error).slice(0, 200) }); }
|
|
194
|
+
try {
|
|
195
|
+
const config = deps.config ?? getUserConfig().loops?.seat ?? { mode: 'off' };
|
|
196
|
+
const profile = result.status === 'skipped-off' ? 'off' : config.mode;
|
|
197
|
+
const context = { runId: 'runId' in result && result.runId ? result.runId : `seat-turn-${randomUUID()}`,
|
|
198
|
+
profile, sourceRef: `docs/roles/${seat}.md` };
|
|
199
|
+
const loopId = `${seat.toLowerCase()}-seat`;
|
|
200
|
+
loopEvent(loopId, 'tick', { ...context, outcome: result.status, reason: result.status });
|
|
201
|
+
if ('inquiry' in result && result.inquiry && (result.status === 'asked' || result.status === 'hitl')) {
|
|
202
|
+
loopEvent(loopId, 'exchange', { ...context, outcome: result.status, reason: 'delegation',
|
|
203
|
+
from: seat, to: result.inquiry.to, itemId: result.item?.id });
|
|
204
|
+
}
|
|
205
|
+
if ('escalated' in result && result.escalated && result.status === 'skipped-budget') {
|
|
206
|
+
loopEvent(loopId, 'exchange', { ...context, outcome: result.status, reason: 'delegation',
|
|
207
|
+
from: seat, to: (config.stall?.parents ?? {})[seat] ?? DEFAULT_STALL_PARENTS[seat], itemId: result.item?.id });
|
|
208
|
+
} else if (result.status === 'skipped-budget') {
|
|
209
|
+
loopEvent(loopId, 'exchange', { ...context, outcome: result.status, reason: 'requeue',
|
|
210
|
+
itemId: result.item?.id });
|
|
211
|
+
}
|
|
212
|
+
} catch { /* observation cannot change a completed seat turn */ }
|
|
162
213
|
return result;
|
|
163
214
|
}
|
|
164
215
|
|
|
@@ -190,6 +241,8 @@ function decisionReceipt(root: string, seat: string, item: SeatItem): DecisionEn
|
|
|
190
241
|
function handledKeys(ledger: readonly SeatEntry[], shadow: boolean, root?: string): Set<string> {
|
|
191
242
|
const inquiryRow = (entry: SeatEntry) => entry.action === 'seat-question' || entry.action === 'seat-answer' || !!entry.inquiry;
|
|
192
243
|
return new Set(ledger.filter((entry, index) => entry.status === 'asked' || entry.status === 'answered'
|
|
244
|
+
// RM3a: only TC's other-seat defect ask-shadows are deduplicated here — an ordinary inquiry recorded in shadow must still be delivered once questions turn on.
|
|
245
|
+
|| (entry.status === 'shadow' && entry.candidate?.kind === 'other-seat-cell-defect')
|
|
193
246
|
|| (entry.status === 'hitl' && (!entry.inquiry || (!!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))))
|
|
194
247
|
|| (entry.inquiry?.to === 'CEO' && !!root && !!entry.item && !!decisionReceipt(root, entry.seat, entry.item))
|
|
195
248
|
|| (!inquiryRow(entry) && entry.status === 'outcome-unknown')
|
|
@@ -211,17 +264,19 @@ function eligibleItem(item: SeatItem, ledger: readonly SeatEntry[], handled: Rea
|
|
|
211
264
|
return !lastWait?.item || itemSnapshot(lastWait.item) !== itemSnapshot(item);
|
|
212
265
|
}
|
|
213
266
|
|
|
214
|
-
|
|
267
|
+
const CHECKLIST_HANDLED_WINDOW_MS = 7 * 24 * 60 * 60 * 1000;
|
|
268
|
+
|
|
269
|
+
// Same-version snapshots are handled here; carried work requires an explicit release-ledger move.
|
|
215
270
|
export function alreadyHandled(item: SeatItem, ledger: readonly SeatEntry[]): boolean {
|
|
216
271
|
if (item.source !== 'checklist' || !item.seat || !item.asOf || item.evidenceHash === undefined) return false;
|
|
217
272
|
const now = Date.parse(item.asOf);
|
|
218
273
|
if (!Number.isFinite(now)) return false;
|
|
219
274
|
return ledger.some((entry) => {
|
|
220
275
|
const at = Date.parse(entry.at || entry.ts || '');
|
|
221
|
-
return entry.seat === item.seat && at >= now -
|
|
276
|
+
return entry.seat === item.seat && at >= now - CHECKLIST_HANDLED_WINDOW_MS && at <= now
|
|
222
277
|
// A shadowed seat question was never delivered — it must be picked again once delivery is on (RM3 · TC merge 10-03).
|
|
223
278
|
&& (entry.status === 'launched' || (entry.status === 'shadow' && !entry.inquiry))
|
|
224
|
-
&& entry.item?.source === 'checklist' && entry.item.id === item.id
|
|
279
|
+
&& entry.item?.source === 'checklist' && entry.item.id === item.id && entry.item.version === item.version
|
|
225
280
|
&& entry.item.title === item.title && entry.item.evidenceHash === item.evidenceHash;
|
|
226
281
|
});
|
|
227
282
|
}
|
|
@@ -293,9 +348,10 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
|
|
|
293
348
|
debug.log('seat.loop', 'schedule-unreadable', { seat, error: String(error).slice(0, 200) });
|
|
294
349
|
}
|
|
295
350
|
const checklist: SeatItem[] = [];
|
|
351
|
+
const closed: string[] = [];
|
|
296
352
|
for (const version of open === null ? [] : versions().sort(versionOrder)) {
|
|
297
353
|
if (!open!.has(version)) {
|
|
298
|
-
|
|
354
|
+
closed.push(version);
|
|
299
355
|
continue;
|
|
300
356
|
}
|
|
301
357
|
let items: ReturnType<typeof itemsOf>;
|
|
@@ -315,6 +371,9 @@ export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}, ledger
|
|
|
315
371
|
}
|
|
316
372
|
}
|
|
317
373
|
}
|
|
374
|
+
if (closed.length > 0) {
|
|
375
|
+
debug.log('seat.loop', 'skip-closed-versions', { seat, count: closed.length, newest: closed.at(-1)!, oldest: closed[0]! });
|
|
376
|
+
}
|
|
318
377
|
checklist.sort((a, b) => versionOrder(a.version!, b.version!) || a.id.localeCompare(b.id));
|
|
319
378
|
return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
|
|
320
379
|
}
|
|
@@ -357,6 +416,137 @@ function opCandidates(deps: SeatDeps, now: Date): OpCandidate[] {
|
|
|
357
416
|
];
|
|
358
417
|
}
|
|
359
418
|
|
|
419
|
+
const TC_APPROVAL_WAIT_MS = 2 * 60 * 60 * 1000;
|
|
420
|
+
|
|
421
|
+
function readTcChecklist(root: string): Array<{ version: string; id: string; title: string; status: string; owner: string | null }> {
|
|
422
|
+
const path = join(root, 'release', 'features.sqlite');
|
|
423
|
+
if (!existsSync(path)) return [];
|
|
424
|
+
const db = new Database(path, { readonly: true, strict: true });
|
|
425
|
+
try {
|
|
426
|
+
return db.query(`SELECT a.version, f.id, COALESCE(a.title_override, f.title) AS title, a.status, a.owner
|
|
427
|
+
FROM assignments a JOIN features f ON f.id = a.feature_id WHERE a.status IN ('yellow', 'red') AND a.owner IN ('OP', 'TC', 'MK', 'UX')
|
|
428
|
+
ORDER BY a.version, f.id`).all() as Array<{ version: string; id: string; title: string; status: string; owner: string | null }>;
|
|
429
|
+
} finally { db.close(); }
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
type TcTimelineEvent = { event: string; created_at: string };
|
|
433
|
+
|
|
434
|
+
export function tcApprovalWaitAt(pr: TcPullRequest, timeline: readonly TcTimelineEvent[]): { readyAt: string; approvalWaitAt: string } | undefined {
|
|
435
|
+
if (pr.state !== 'OPEN' || pr.isDraft || pr.reviewDecision !== 'REVIEW_REQUIRED') return undefined;
|
|
436
|
+
const events = timeline.filter((event) => typeof event.created_at === 'string' && Number.isFinite(Date.parse(event.created_at)))
|
|
437
|
+
.sort((a, b) => Date.parse(a.created_at) - Date.parse(b.created_at));
|
|
438
|
+
let readyAt: string | undefined = pr.createdAt;
|
|
439
|
+
let waitAt: string | undefined = pr.createdAt;
|
|
440
|
+
for (const event of events) {
|
|
441
|
+
if (event.event === 'converted_to_draft') { readyAt = undefined; waitAt = undefined; }
|
|
442
|
+
if (event.event === 'ready_for_review') { readyAt = event.created_at; waitAt = event.created_at; }
|
|
443
|
+
if (event.event === 'review_requested' && readyAt && Date.parse(event.created_at) >= Date.parse(readyAt)) waitAt = event.created_at;
|
|
444
|
+
}
|
|
445
|
+
return readyAt && waitAt && Number.isFinite(Date.parse(readyAt)) && Date.parse(waitAt) >= Date.parse(readyAt)
|
|
446
|
+
? { readyAt, approvalWaitAt: waitAt } : undefined;
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
export async function readTcPullRequests(query: (args: string[]) => Promise<string> = async (args) => {
|
|
450
|
+
const { stdout } = await exec('gh', args, { cwd: repoRoot, encoding: 'utf8', timeout: 120_000, maxBuffer: 64 * 1024 * 1024 });
|
|
451
|
+
return stdout;
|
|
452
|
+
}): Promise<TcPullRequest[]> {
|
|
453
|
+
const pages: unknown = JSON.parse(await query(['api', '--paginate', '--slurp', 'repos/{owner}/{repo}/pulls?state=all&per_page=100']));
|
|
454
|
+
if (!Array.isArray(pages) || !pages.every((page) => Array.isArray(page))) throw new Error('PR list is not an array of pages');
|
|
455
|
+
const prs = pages.flatMap((page: unknown[]) => page).filter((pr) => pr && typeof pr === 'object'
|
|
456
|
+
&& ((pr as Record<string, unknown>).state === 'open' || typeof (pr as Record<string, unknown>).merged_at === 'string'));
|
|
457
|
+
const results: TcPullRequest[] = [];
|
|
458
|
+
for (let offset = 0; offset < prs.length; offset += 10) {
|
|
459
|
+
results.push(...await Promise.all(prs.slice(offset, offset + 10).map(async (pr): Promise<TcPullRequest> => {
|
|
460
|
+
const listing = pr as Record<string, unknown>;
|
|
461
|
+
if (typeof listing.number !== 'number' || typeof listing.title !== 'string' || typeof listing.created_at !== 'string'
|
|
462
|
+
|| typeof listing.draft !== 'boolean' || (listing.state !== 'open' && listing.state !== 'closed')) {
|
|
463
|
+
throw new Error('incomplete PR record');
|
|
464
|
+
}
|
|
465
|
+
const details: unknown = listing.state === 'open'
|
|
466
|
+
? JSON.parse(await query(['pr', 'view', String(listing.number), '--json', 'number,title,body,state,isDraft,createdAt,mergedAt,files,reviewDecision']))
|
|
467
|
+
: { number: listing.number, title: listing.title, body: listing.body, state: 'MERGED', isDraft: listing.draft,
|
|
468
|
+
createdAt: listing.created_at, mergedAt: listing.merged_at, files: [] };
|
|
469
|
+
if (!details || typeof details !== 'object' || Array.isArray(details)) throw new Error('invalid PR record');
|
|
470
|
+
const row = details as Record<string, unknown>;
|
|
471
|
+
if (typeof row.number !== 'number' || typeof row.title !== 'string' || typeof row.createdAt !== 'string'
|
|
472
|
+
|| typeof row.isDraft !== 'boolean' || (row.state !== 'OPEN' && row.state !== 'MERGED' && row.state !== 'CLOSED')) {
|
|
473
|
+
throw new Error('incomplete PR record');
|
|
474
|
+
}
|
|
475
|
+
const paths = Array.isArray(row.files) ? row.files.flatMap((file: unknown) =>
|
|
476
|
+
file && typeof file === 'object' && typeof (file as { path?: unknown }).path === 'string' ? [(file as { path: string }).path] : []) : [];
|
|
477
|
+
const result: TcPullRequest = { number: row.number, title: row.title, body: typeof row.body === 'string' ? row.body : '',
|
|
478
|
+
state: row.state, isDraft: row.isDraft, createdAt: row.createdAt,
|
|
479
|
+
mergedAt: typeof row.mergedAt === 'string' ? row.mergedAt : null, paths,
|
|
480
|
+
reviewDecision: typeof row.reviewDecision === 'string' ? row.reviewDecision : undefined };
|
|
481
|
+
if (result.state === 'OPEN' && !result.isDraft && result.reviewDecision === 'REVIEW_REQUIRED'
|
|
482
|
+
&& paths.some((path) => path.startsWith('release/public/') || path.startsWith('scripts/public-export') || path.startsWith('src/market/'))) {
|
|
483
|
+
try {
|
|
484
|
+
const pages: unknown = JSON.parse(await query(['api', '--paginate', '--slurp', `repos/{owner}/{repo}/issues/${result.number}/timeline`]));
|
|
485
|
+
if (!Array.isArray(pages) || !pages.every((page) => Array.isArray(page))) throw new Error('PR timeline is not an array of pages');
|
|
486
|
+
const timeline: unknown[] = pages.flatMap((page: unknown[]) => page);
|
|
487
|
+
const transitions = timeline.filter((event): event is Record<string, unknown> => event !== null && typeof event === 'object'
|
|
488
|
+
&& ['converted_to_draft', 'ready_for_review', 'review_requested'].includes((event as Record<string, unknown>).event as string));
|
|
489
|
+
if (!transitions.every((event) => typeof event.created_at === 'string' && Number.isFinite(Date.parse(event.created_at)))) {
|
|
490
|
+
throw new Error('invalid PR timeline transition date');
|
|
491
|
+
}
|
|
492
|
+
Object.assign(result, tcApprovalWaitAt(result, transitions as TcTimelineEvent[]));
|
|
493
|
+
} catch (error) { observe('tc-pr-timeline-unreadable', { pr: result.number, error: String(error).slice(0, 200) }); }
|
|
494
|
+
}
|
|
495
|
+
return result;
|
|
496
|
+
})));
|
|
497
|
+
}
|
|
498
|
+
return results;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function tcCandidates(deps: SeatDeps, now: Date): Promise<TcCandidate[]> {
|
|
502
|
+
let prs: TcPullRequest[];
|
|
503
|
+
// An injected instance root must not fetch unrelated live GitHub state.
|
|
504
|
+
try { prs = await (deps.pullRequests ?? (deps.root ? async () => [] : readTcPullRequests))(); }
|
|
505
|
+
catch (error) {
|
|
506
|
+
observe('tc-pr-unreadable', { seat: 'TC', error: String(error).slice(0, 200) });
|
|
507
|
+
return [];
|
|
508
|
+
}
|
|
509
|
+
const merged = prs.filter((pr) => pr.state === 'MERGED' && pr.mergedAt && Number.isFinite(Date.parse(pr.mergedAt)) && Date.parse(pr.mergedAt) <= now.getTime());
|
|
510
|
+
const candidates: TcCandidate[] = [];
|
|
511
|
+
const releaseRoot = deps.root ?? releaseLedgerRoot();
|
|
512
|
+
try {
|
|
513
|
+
const cells = deps.checklistItems && deps.schedules
|
|
514
|
+
? deps.schedules().flatMap(({ version }) => deps.checklistItems!(version).map((cell) => ({ ...cell, version })))
|
|
515
|
+
: readTcChecklist(releaseRoot);
|
|
516
|
+
let shipped: string | undefined;
|
|
517
|
+
try { shipped = releasedVersion(releaseRoot); }
|
|
518
|
+
catch (error) { observe('tc-release-unreadable', { seat: 'TC', error: String(error).slice(0, 200) }); }
|
|
519
|
+
const openVersions = deps.schedules ? new Set(deps.schedules().filter(({ cutAt }) => Number.isFinite(Date.parse(cutAt))
|
|
520
|
+
&& Date.parse(cutAt) > now.getTime()).map(({ version }) => version)) : null;
|
|
521
|
+
for (const cell of cells) {
|
|
522
|
+
if (cell.owner !== 'TC' && ((shipped && versionOrder(cell.version, shipped) <= 0) || (openVersions && !openVersions.has(cell.version)))) continue;
|
|
523
|
+
if (cell.status !== 'yellow' && cell.status !== 'red') continue;
|
|
524
|
+
if (!cell.owner || !isSeatId(cell.owner)) continue;
|
|
525
|
+
if (cell.owner !== 'TC') {
|
|
526
|
+
if (cell.status === 'red') candidates.push({ kind: 'other-seat-cell-defect', version: cell.version, id: cell.id, title: cell.title, to: cell.owner });
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
for (const pr of merged) {
|
|
530
|
+
const id = cell.id.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
531
|
+
const reference = new RegExp(`(?<![A-Za-z0-9])${id}(?![A-Za-z0-9])`);
|
|
532
|
+
if (reference.test(pr.title) || reference.test(pr.body ?? '')) {
|
|
533
|
+
if (cell.owner === 'TC' && cell.status === 'yellow') candidates.push({ kind: 'merged-pr-yellow-cell', version: cell.version, id: cell.id, title: cell.title, pr: pr.number, mergedAt: pr.mergedAt! });
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
} catch (error) { observe('tc-checklist-unreadable', { seat: 'TC', error: String(error).slice(0, 200) }); }
|
|
538
|
+
for (const pr of prs) {
|
|
539
|
+
if (pr.state !== 'OPEN' || pr.isDraft || pr.reviewDecision !== 'REVIEW_REQUIRED'
|
|
540
|
+
|| !pr.paths?.some((path) => path.startsWith('release/public/') || path.startsWith('scripts/public-export') || path.startsWith('src/market/'))) continue;
|
|
541
|
+
const readyAt = pr.readyAt;
|
|
542
|
+
const age = now.getTime() - Date.parse(pr.approvalWaitAt ?? '');
|
|
543
|
+
if (!readyAt || !Number.isFinite(Date.parse(readyAt)) || Date.parse(readyAt) > now.getTime()
|
|
544
|
+
|| !Number.isFinite(age) || age < TC_APPROVAL_WAIT_MS) continue;
|
|
545
|
+
candidates.push({ kind: 'publication-pr-approval-wait', pr: pr.number, title: pr.title, readyAt, approvalWaitAt: pr.approvalWaitAt!, paths: pr.paths });
|
|
546
|
+
}
|
|
547
|
+
return candidates;
|
|
548
|
+
}
|
|
549
|
+
|
|
360
550
|
function deliverStall(message: MessageEnvelope, key: string, root: string): boolean {
|
|
361
551
|
const store = openMsgStore(join(root, 'msg', 'messages.db'));
|
|
362
552
|
try {
|
|
@@ -411,13 +601,34 @@ function escalateStalls(seat: string, deps: SeatDeps, config: SeatLoopConfig, no
|
|
|
411
601
|
}
|
|
412
602
|
|
|
413
603
|
// In shadow mode a shadowed item counts as handled, so a rehearsal day walks the queue like the seat would.
|
|
414
|
-
export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean; root?: string } = {}): SeatItem | null {
|
|
604
|
+
export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean; root?: string; history?: SeatDeps['checklistHistory'] } = {}): SeatItem | null {
|
|
415
605
|
const handled = handledKeys(ledger, opts.shadow === true, opts.root);
|
|
416
606
|
return [...inputs.requests, ...inputs.checklist].find((item) => {
|
|
417
607
|
if (alreadyHandled(item, ledger)) {
|
|
418
608
|
debug.log('seat.loop', 'skip-handled', { id: item.id });
|
|
419
609
|
return false;
|
|
420
610
|
}
|
|
611
|
+
if (item.source === 'checklist' && item.version && item.seat && item.asOf && item.evidenceHash !== undefined && opts.history
|
|
612
|
+
&& ledger.some((entry) => entry.seat === item.seat && entry.status === 'shadow'
|
|
613
|
+
&& entry.item?.source === 'checklist' && entry.item.id === item.id && entry.item.version !== item.version)) {
|
|
614
|
+
// Only an actual move into this version identifies a carried cell; unrelated releases may reuse an id.
|
|
615
|
+
const moves = opts.history(item.id).filter((event) => event.field === 'move'
|
|
616
|
+
&& typeof event.from === 'string' && typeof event.to === 'string' && Date.parse(event.at) <= Date.parse(item.asOf!));
|
|
617
|
+
const latest = moves.at(-1);
|
|
618
|
+
if (latest?.to === item.version) {
|
|
619
|
+
const processed = [...ledger].reverse().find((entry) => entry.seat === item.seat
|
|
620
|
+
&& entry.status === 'shadow' && !entry.inquiry && entry.action !== 'wait'
|
|
621
|
+
&& entry.item?.source === 'checklist' && entry.item.id === item.id);
|
|
622
|
+
if (processed && processed.item && processed.item.version === latest.from && processed.item.title === item.title
|
|
623
|
+
&& processed.item.evidenceHash === item.evidenceHash
|
|
624
|
+
&& Date.parse(processed.at || processed.ts || '') >= Date.parse(item.asOf) - CHECKLIST_HANDLED_WINDOW_MS
|
|
625
|
+
&& Date.parse(processed.at || processed.ts || '') <= Date.parse(item.asOf)
|
|
626
|
+
&& Date.parse(processed.at || processed.ts || '') <= Date.parse(latest.at)) {
|
|
627
|
+
debug.log('seat.loop', 'skip-carried', { id: item.id, from: latest.from, version: item.version });
|
|
628
|
+
return false;
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
421
632
|
return eligibleItem(item, ledger, handled, opts.shadow === true);
|
|
422
633
|
}) ?? null;
|
|
423
634
|
}
|
|
@@ -470,6 +681,29 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
470
681
|
const stateRoot = deps.root ?? effectiveInstanceRoot();
|
|
471
682
|
let turnError: unknown;
|
|
472
683
|
try {
|
|
684
|
+
if (seat === 'TC') {
|
|
685
|
+
const candidates = await tcCandidates(deps, now);
|
|
686
|
+
for (const candidate of candidates) {
|
|
687
|
+
if (candidate.kind === 'other-seat-cell-defect') {
|
|
688
|
+
const question = `${candidate.version} 체크리스트 ${candidate.id} · ${candidate.title}: ${candidate.to} 칸 결함을 확인해 주세요.`;
|
|
689
|
+
const recorded = recordSeatAskShadow(stateRoot, { from: 'TC', to: candidate.to, itemId: candidate.id, version: candidate.version, question }, now.getTime());
|
|
690
|
+
const inLedger = ledger.some((row) => row.status === 'shadow' && row.candidate?.kind === 'other-seat-cell-defect'
|
|
691
|
+
&& row.candidate.version === candidate.version && row.candidate.id === candidate.id && row.candidate.to === candidate.to);
|
|
692
|
+
if (!inLedger) {
|
|
693
|
+
const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: 'shadow', candidate,
|
|
694
|
+
item: { source: 'checklist', kind: 'cell', id: candidate.id, title: candidate.title, text: candidate.title, version: candidate.version, seat: candidate.to },
|
|
695
|
+
action: 'seat-question', inquiry: { to: candidate.to, question } };
|
|
696
|
+
(deps.append ?? defaultAppend)(path, entry);
|
|
697
|
+
}
|
|
698
|
+
if (!inLedger || recorded) observe('ask-shadow', { from: seat, to: candidate.to, itemId: candidate.id });
|
|
699
|
+
continue;
|
|
700
|
+
}
|
|
701
|
+
if (ledger.some((row) => row.status === 'shadow' && JSON.stringify(row.candidate) === JSON.stringify(candidate))) continue;
|
|
702
|
+
const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: 'shadow', candidate };
|
|
703
|
+
(deps.append ?? defaultAppend)(path, entry);
|
|
704
|
+
observe('tc-shadow-candidate', { seat, candidate });
|
|
705
|
+
}
|
|
706
|
+
}
|
|
473
707
|
if (seat === 'OP' && !seatQuestions(stateRoot, 'OP').some((question) =>
|
|
474
708
|
!hasSeatAnswer(stateRoot, question)
|
|
475
709
|
&& !handledKeys(ledger, config.mode !== 'on' || (config.questions ?? 'shadow') !== 'on', stateRoot).has(`seat-question::${question.id}`))) {
|
|
@@ -526,7 +760,8 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
526
760
|
const questionsMode = config.mode === 'on' ? config.questions ?? 'shadow' : 'shadow';
|
|
527
761
|
const questionHandled = handledKeys(ledger, questionsMode !== 'on', stateRoot);
|
|
528
762
|
const pendingQuestion = inputs.requests.find((candidate) => candidate.source === 'seat-question' && !questionHandled.has(itemKey(candidate)));
|
|
529
|
-
const ordinary = pickNext({ ...inputs, requests: inputs.requests.filter((candidate) => candidate.source !== 'seat-question') }, ledger,
|
|
763
|
+
const ordinary = pickNext({ ...inputs, requests: inputs.requests.filter((candidate) => candidate.source !== 'seat-question') }, ledger,
|
|
764
|
+
{ shadow: config.mode === 'shadow', root: stateRoot, history: deps.checklistHistory ?? checklistHistory });
|
|
530
765
|
const item = pendingQuestion ?? ordinary;
|
|
531
766
|
const append = deps.append ?? defaultAppend;
|
|
532
767
|
// RM3 review must-fix: `questions` now has a default, so it can't be the trigger — a shadow seat loop never calls the model on its own.
|
|
@@ -600,7 +835,7 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
600
835
|
await run(['decisions', 'raise', '--title', `${seat}: ${ask.question}`, '--category', 'other',
|
|
601
836
|
'--s', `${seat} 질문 · ${item.title}`, '--c', '판단 근거 부족 — 사람의 결정이 필요하다',
|
|
602
837
|
'--option', 'a=승인:사람 판단으로 진행', '--option', 'b=보류:진행하지 않음',
|
|
603
|
-
'--skip-recommend', '근거가 부족하다', '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
|
|
838
|
+
'--skip-recommend', '근거가 부족하다', '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②', '--agent', 'seat-loop', '--ref', decisionRef(seat, item), '--json']);
|
|
604
839
|
}
|
|
605
840
|
if (!decisionReceipt(root, seat, item)) throw new Error('decisions raise did not deliver a matching card');
|
|
606
841
|
entry.status = 'hitl';
|
|
@@ -705,7 +940,7 @@ export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promis
|
|
|
705
940
|
const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
|
|
706
941
|
'--s', `${seat} 배정 항목: ${action.text.replace(/[\r\n]+/g, ' ')}\n역할: ${inputs.role}`, '--c', '자동 실행 금지 문면에 해당하여 사람 결정이 필요하다',
|
|
707
942
|
'--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
|
|
708
|
-
'--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--agent', 'seat-loop', '--json']);
|
|
943
|
+
'--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--no-xcheck', '자리 루프 · 이웃 교환은 DEC-XCHECK ②', '--agent', 'seat-loop', '--json']);
|
|
709
944
|
const decision: unknown = JSON.parse(raised.trim());
|
|
710
945
|
if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
|
|
711
946
|
throw new Error('decisions raise returned no decision id');
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, lstatSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
|
+
import { userInfo } from 'node:os';
|
|
5
|
+
import { DEFAULT_CONCURRENT_PODS, DEFAULT_DAILY_GOALS } from '../org/seat-budget.js';
|
|
6
|
+
|
|
7
|
+
export interface SeatPackManifest {
|
|
8
|
+
name: string;
|
|
9
|
+
version: string;
|
|
10
|
+
files: Array<{ path: string; sha256: string }>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class SeatPackLeakError extends Error {
|
|
14
|
+
constructor(file: string, line: number, marker: string) {
|
|
15
|
+
super(`${file}:${line}: public leak (${marker})`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function regularFile(path: string): boolean {
|
|
20
|
+
return lstatSync(path).isFile();
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function walk(directory: string, root: string): string[] {
|
|
24
|
+
if (!existsSync(directory)) return [];
|
|
25
|
+
if (!lstatSync(directory).isDirectory()) throw new Error(`not a directory: ${relative(root, directory)}`);
|
|
26
|
+
return readdirSync(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))
|
|
27
|
+
.flatMap((entry) => {
|
|
28
|
+
const path = join(directory, entry.name);
|
|
29
|
+
if (entry.isSymbolicLink()) throw new Error(`symlink in seat pack: ${relative(root, path)}`);
|
|
30
|
+
return entry.isDirectory() ? walk(path, root) : entry.isFile() ? [relative(root, path)] : [];
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function scan(file: string, body: string): void {
|
|
35
|
+
const usernames = new Set([userInfo().username, process.env.ELANOUS_LEAK_HOME_USER].filter((name): name is string => Boolean(name)));
|
|
36
|
+
const accountPatterns = [...usernames].map((name) => new RegExp(`(?<![A-Za-z0-9._-])${name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}(?![A-Za-z0-9._-])`, 'iu'));
|
|
37
|
+
const patterns: Array<[string, RegExp]> = [
|
|
38
|
+
['royal marker', /\u{1F451}/u],
|
|
39
|
+
['track marker', /(?:🅞|🅣|🅢|🅕|\*\*\[(?:OP|TC|MK|UX|S|T|O|F)\]\*\*|\[(?:OP|TC|MK|UX|S|T|O|F)\])/u],
|
|
40
|
+
['absolute user path', /\/Users\/[A-Za-z0-9._-]+/u],
|
|
41
|
+
...accountPatterns.map((pattern): [string, RegExp] => ['account name', pattern]),
|
|
42
|
+
];
|
|
43
|
+
for (const [index, line] of body.split(/\r?\n/u).entries()) {
|
|
44
|
+
for (const [marker, pattern] of patterns) {
|
|
45
|
+
if (pattern.test(line)) throw new SeatPackLeakError(file, index + 1, marker);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** Build from charters, explicitly referenced seat graphs and the seat's own `graphs/<seat>/` folder; never copy an entire shared graph directory by default. */
|
|
51
|
+
export function exportSeatPack(seat: string, out: string, root = resolve(import.meta.dir, '../..')): SeatPackManifest {
|
|
52
|
+
if (!/^[A-Za-z][A-Za-z0-9_-]*$/u.test(seat)) throw new Error(`invalid seat: ${seat}`);
|
|
53
|
+
const repo = resolve(root);
|
|
54
|
+
const output = resolve(out);
|
|
55
|
+
const roles = join(repo, 'docs', 'roles');
|
|
56
|
+
const charter = join(roles, `${seat}.md`);
|
|
57
|
+
if (!existsSync(charter) || !regularFile(charter)) throw new Error(`seat charter not found: docs/roles/${seat}.md`);
|
|
58
|
+
const roleFiles = [`docs/roles/${seat}.md`, ...walk(join(roles, seat), repo).filter((file) => file.endsWith('.md'))];
|
|
59
|
+
const graphFiles = new Set<string>();
|
|
60
|
+
const contents = new Map<string, Buffer>();
|
|
61
|
+
for (const file of roleFiles) {
|
|
62
|
+
const body = readFileSync(join(repo, file));
|
|
63
|
+
contents.set(file, body);
|
|
64
|
+
for (const match of body.toString('utf8').matchAll(/graphs\/([A-Za-z0-9_./-]+(?:\.ya?ml|\/\*\*))/gu)) {
|
|
65
|
+
const ref = `graphs/${match[1]!}`;
|
|
66
|
+
if (ref.split('/').includes('..')) throw new Error(`invalid seat graph: ${ref}`);
|
|
67
|
+
if (ref.endsWith('/**')) {
|
|
68
|
+
if (!existsSync(join(repo, ref.slice(0, -3)))) throw new Error(`seat graph not found: ${ref}`);
|
|
69
|
+
for (const graph of walk(join(repo, ref.slice(0, -3)), repo)) {
|
|
70
|
+
if (/\.ya?ml$/u.test(graph)) graphFiles.add(graph);
|
|
71
|
+
}
|
|
72
|
+
} else graphFiles.add(ref);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// A seat's own graph folder (`graphs/<seat in lower case>/`) belongs to the seat even when the
|
|
76
|
+
// charter no longer mentions it — inclusion must not hinge on a text reference that can be edited away.
|
|
77
|
+
const ownGraphs = join(repo, 'graphs', seat.toLowerCase());
|
|
78
|
+
if (existsSync(ownGraphs)) {
|
|
79
|
+
for (const graph of walk(ownGraphs, repo)) if (/\.ya?ml$/u.test(graph)) graphFiles.add(graph);
|
|
80
|
+
}
|
|
81
|
+
for (const file of graphFiles) {
|
|
82
|
+
const path = join(repo, file);
|
|
83
|
+
if (!existsSync(path) || !regularFile(path)) throw new Error(`seat graph not found: ${file}`);
|
|
84
|
+
contents.set(file, readFileSync(path));
|
|
85
|
+
}
|
|
86
|
+
const verdicts = roleFiles.flatMap((file) => contents.get(file)!.toString('utf8').split(/\r?\n/u)
|
|
87
|
+
.filter((line) => /\|\s*판정선\s*\|/u.test(line)).map((line) => ({ source: file, text: line.split('|').slice(2, -1).join('|').trim() })));
|
|
88
|
+
for (const file of roleFiles) {
|
|
89
|
+
const charter = contents.get(file)!.toString('utf8');
|
|
90
|
+
for (const match of charter.matchAll(/(?:^|\n)\s*[-*]\s*\*\*(?:관문|판정선)\*\*\s*[—:-]\s*([^\n]+)/gu)) {
|
|
91
|
+
verdicts.push({ source: file, text: match[1]!.trim() });
|
|
92
|
+
}
|
|
93
|
+
let inGateSection = false;
|
|
94
|
+
for (const line of charter.split(/\r?\n/u)) {
|
|
95
|
+
if (/^##\s+\d+\.\s+관문(?:\s|\(|$)/u.test(line)) { inGateSection = true; continue; }
|
|
96
|
+
if (/^##\s/u.test(line)) inGateSection = false;
|
|
97
|
+
if (!inGateSection) continue;
|
|
98
|
+
const bullet = line.match(/^\s*[-*]\s+(.+)$/u);
|
|
99
|
+
if (bullet) verdicts.push({ source: file, text: bullet[1]!.trim() });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (verdicts.length === 0) throw new Error(`seat verdict not found for ${seat}`);
|
|
103
|
+
contents.set('verdicts.json', Buffer.from(`${JSON.stringify(verdicts, null, 2)}\n`));
|
|
104
|
+
contents.set('budget.json', Buffer.from(`${JSON.stringify({ dailyGoals: DEFAULT_DAILY_GOALS, concurrentPods: DEFAULT_CONCURRENT_PODS }, null, 2)}\n`));
|
|
105
|
+
const version = (JSON.parse(readFileSync(join(repo, 'package.json'), 'utf8')) as { version: string }).version;
|
|
106
|
+
const files = [...contents].sort(([a], [b]) => a.localeCompare(b)).map(([path, body]) => ({
|
|
107
|
+
path, sha256: createHash('sha256').update(body).digest('hex'),
|
|
108
|
+
}));
|
|
109
|
+
const manifest: SeatPackManifest = { name: seat, version, files };
|
|
110
|
+
const manifestBody = Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`);
|
|
111
|
+
for (const [file, body] of [...contents, ['manifest.json', manifestBody] as [string, Buffer]]) scan(file, body.toString('utf8'));
|
|
112
|
+
if (existsSync(output)) {
|
|
113
|
+
if (!lstatSync(output).isDirectory() || readdirSync(output).length > 0) throw new Error(`output must be an empty directory: ${output}`);
|
|
114
|
+
}
|
|
115
|
+
const sources = [roles, join(repo, 'graphs')];
|
|
116
|
+
if (output === repo || sources.some((source) => output === source || source.startsWith(`${output}${sep}`) || output.startsWith(`${source}${sep}`))) {
|
|
117
|
+
throw new Error('output overlaps seat pack sources');
|
|
118
|
+
}
|
|
119
|
+
mkdirSync(output, { recursive: true });
|
|
120
|
+
for (const [file, body] of contents) {
|
|
121
|
+
const dest = join(output, file);
|
|
122
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
123
|
+
writeFileSync(dest, body);
|
|
124
|
+
}
|
|
125
|
+
writeFileSync(join(output, 'manifest.json'), manifestBody);
|
|
126
|
+
return manifest;
|
|
127
|
+
}
|
|
@@ -158,6 +158,8 @@ export interface AskLaunchFlowDeps {
|
|
|
158
158
|
/** ⛔ 못 얻으면 `null` — 같은 이유. */
|
|
159
159
|
recentAuthoringSamples(): RecentAuthoringSample[] | null;
|
|
160
160
|
authorGoal(args: readonly string[], options: Record<string, unknown>): Promise<AskAuthoredGoal>;
|
|
161
|
+
/** Pod launches reserve a host slot before goal authoring begins. */
|
|
162
|
+
beforeAuthoring?(): Promise<void>;
|
|
161
163
|
/** 발사 전 권고용 분해 seam. 생략하면 전용 관측명을 준 기존 분해기를 쓴다. */
|
|
162
164
|
decomposeGoal?(goal: string): Promise<SelfDevDecomposition>;
|
|
163
165
|
/**
|
|
@@ -825,6 +827,7 @@ export async function runAskLaunchFlow(
|
|
|
825
827
|
const launchPreflight = await runPrePreflight(input, deps);
|
|
826
828
|
if (launchPreflight === false) return { kind: 'stopped-before-authoring' };
|
|
827
829
|
|
|
830
|
+
await deps.beforeAuthoring?.();
|
|
828
831
|
const authorAndPreflight = async (): Promise<{
|
|
829
832
|
readonly goalFile: string;
|
|
830
833
|
readonly decision: ReturnType<typeof decideAskPreflight>;
|