elanous 0.2.9 → 0.2.10
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/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +1 -0
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8067-87ce253ec2f23697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{5080-1613752891fcadb4.js → 8195-6fde3d7a39859206.js} +5 -5
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-f972fc1c00a62e88.js → page-54b32bd27726588d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-973996d584f008c1.js → page-7ed95f296f5ee8e9.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/{page-8b0402a46a749a3d.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-037f83e2a1edc8e2.js → page-6cb0e1bbefe5fb3d.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/{page-0df30e0c7e50148a.js → page-e168fa1030af0548.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/{page-90670589b87abea8.js → page-26102fd7350f171e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/{page-35bedde6d885f08f.js → page-139ba5c367bbf60a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +1 -0
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.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/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 -0
- package/apps/pwa/out/ops/seats/index.txt +30 -0
- 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 -0
- package/apps/pwa/out/today/index.txt +33 -0
- 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/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/package.json +1 -1
- package/plugins/card-followup/graphs/run-step.ts +48 -13
- package/plugins/doc-draft/graphs/doc-draft.yaml +3 -3
- package/plugins/doc-draft/graphs/run-step.ts +13 -6
- package/scripts/ci-pwa-build-gate.ts +160 -0
- package/scripts/coord-channel-watch.sh +42 -8
- package/scripts/coord-digest.ts +199 -0
- package/scripts/coord-post.sh +60 -3
- package/scripts/install.ps1 +68 -9
- package/scripts/install.sh +39 -12
- package/scripts/lib/pwa-scenarios/scenarios.ts +20 -0
- package/scripts/lib/tui-regress-checks.ts +146 -0
- package/scripts/memory-lifecycle-cycle.ts +2 -2
- package/scripts/ops-now-probe.ts +327 -0
- package/scripts/publish-internal-dist.ts +11 -1
- package/scripts/release-loop/cutoff.ts +19 -8
- package/scripts/release-loop/ops-upgrade-node.ts +72 -16
- package/scripts/release-loop/release-readiness.ts +9 -0
- package/scripts/release-loop/version-node.ts +58 -10
- package/scripts/repo-watch-cycle.ts +1 -1
- package/scripts/retro-cycle.ts +2 -2
- package/scripts/schedule-health-report.ts +1 -1
- package/scripts/storyboard/storyboard.ts +5 -161
- package/scripts/test-diet/lib.ts +30 -1
- package/scripts/test-diet/node.ts +82 -7
- package/scripts/test-timeout-audit.ts +114 -0
- package/scripts/tree-sync-alert.ts +1 -1
- package/scripts/tui-regress.ts +71 -0
- package/scripts/waitlist-daily-digest.ts +1 -1
- package/skills/explainer-video/SKILL.md +4 -0
- package/skills/explainer-video/engine/reel.mjs +18 -13
- package/skills/explainer-video/engine/reel.sh +24 -5
- package/skills/explainer-video/engine/stitch.mjs +145 -0
- package/skills/pitch-deck/SKILL.md +36 -0
- package/skills/pitch-deck/template.html +80 -0
- package/src/acp/codex-approval-adapter.ts +9 -3
- package/src/acp/core-turn-bridge.ts +117 -18
- package/src/acp/fact-question.ts +11 -0
- package/src/acp/server.ts +48 -7
- package/src/acp/tool-approval.ts +126 -0
- package/src/acp/turn-runner.ts +36 -0
- package/src/agent/monad-agent-turn.ts +8 -0
- package/src/ask-user-question/pending-questions.ts +25 -10
- package/src/ask-user-question/types.ts +9 -0
- package/src/auth/setup-link-tokens.ts +16 -5
- package/src/autopilot/build/integrity-gate.ts +12 -0
- package/src/boot/daemon-prompt-turn.ts +3 -0
- package/src/boot/daemon-tools/index.ts +8 -0
- package/src/boot/daemon-tools/types.ts +2 -0
- package/src/boot/daemon-tools/web-search.ts +1 -0
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +52 -0
- package/src/chat/pickers/state.ts +6 -2
- package/src/cli/agent-cli.ts +7 -0
- package/src/cli/decisions-cli.ts +25 -3
- package/src/cli/doctor-bundle-upload.ts +68 -0
- package/src/cli/doctor-cli.ts +38 -4
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +31 -2
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +63 -20
- package/src/cli/self-update.ts +14 -0
- package/src/cli/storyboard-cli.ts +37 -0
- package/src/cli/test-state-dir-flag.ts +11 -4
- package/src/connectors/linear.ts +146 -0
- package/src/context-bus/coord-events.ts +115 -0
- package/src/dashboard/dashboard-boot-fixture.ts +23 -0
- package/src/dashboard/index.ts +58 -21
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +7 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/decisions/decision-cards.ts +49 -1
- package/src/decisions/decision-ledger.ts +75 -4
- package/src/decisions/decision-linear-projection.ts +157 -0
- package/src/decisions/web-push-decision-cards.ts +50 -0
- package/src/directive/interpretation-card.ts +36 -0
- package/src/discord.ts +2 -1
- package/src/domains/coo-admin-tool.ts +62 -0
- package/src/domains/core-tools.ts +11 -3
- package/src/domains/elanous-card.ts +29 -0
- package/src/domains/ops-facts-tool.ts +76 -0
- package/src/domains/outbound-alert.ts +41 -13
- package/src/domains/release-tool.ts +168 -0
- package/src/domains/telegram-kind-route.ts +19 -1
- package/src/exec-requests/planner.ts +98 -28
- package/src/exec-requests/runner.ts +107 -10
- package/src/exec-requests/store.ts +8 -2
- package/src/field/field-media.ts +10 -7
- package/src/field/field-reel.ts +14 -4
- package/src/git-fs/worktree.ts +45 -6
- package/src/graph-runner/graph-approval-card.ts +113 -0
- package/src/graph-runner/graph-cli.ts +129 -4
- package/src/graph-runner/graph-notify.ts +25 -3
- package/src/graph-runner/runner.ts +142 -16
- package/src/harness/browser-verify.ts +25 -2
- package/src/harness/harness-cli-command.ts +231 -35
- package/src/harness/harness-incidents-writer.fixture.ts +24 -0
- package/src/harness/harness-incidents.ts +129 -0
- package/src/harness/harness-stop.ts +53 -3
- package/src/hitl/clarification-policy.ts +3 -1
- package/src/index.ts +156 -37
- package/src/intake-plane/author-goals.ts +37 -4
- package/src/intake-plane/check.ts +3 -0
- package/src/intake-plane/discord-seat-work.ts +47 -2
- package/src/intake-plane/seat-doc-route.ts +170 -6
- package/src/intake-plane/telegram-seat-work.ts +44 -2
- package/src/intake-plane/telegram-work.ts +1 -0
- package/src/maturity/cli-maturity.ts +34 -0
- package/src/maturity/feature-maturity.ts +98 -1
- package/src/maturity/tui-slash-maturity.ts +31 -0
- package/src/memory.ts +32 -4
- package/src/nexus/api/exec-requests.ts +33 -3
- package/src/nexus/api/http-server.ts +53 -1
- package/src/nexus/api/meta-api.ts +11 -0
- package/src/nexus/api/ops-api.ts +10 -1
- package/src/nexus/api/ops-seats-sources.ts +37 -7
- package/src/nexus/api/ops-seats.ts +16 -6
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run.ts +89 -0
- package/src/onboarding/lan-address.ts +45 -0
- package/src/onboarding/web-first.ts +119 -24
- package/src/onboarding.ts +4 -0
- package/src/ops-now/ops-now-refresh.ts +100 -0
- package/src/project/project-store.ts +115 -0
- package/src/release-loop/checklist.ts +78 -4
- package/src/release-loop/feature-store.ts +154 -21
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +41 -8
- package/src/seat-dispatch/ceo-intent.ts +38 -0
- package/src/seat-loop/seat-loop.ts +231 -0
- package/src/seat-loop/seat-report.ts +64 -0
- package/src/self-dev/decompose.ts +66 -11
- package/src/self-dev/run-store.ts +10 -1
- package/src/self-implement/execution-clarification.ts +115 -0
- package/src/self-implement/gate-cli.ts +6 -3
- package/src/self-implement/orchestrator.ts +37 -3
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/run-status-mapping.ts +5 -3
- package/src/session/chat.ts +2 -0
- package/src/session/index.ts +9 -0
- package/src/shell-env-bootstrap.ts +17 -0
- package/src/steward/directive.ts +51 -2
- package/src/steward/launch.ts +250 -0
- package/src/steward/outcome-notify.ts +115 -0
- package/src/steward/steward-cards.ts +8 -2
- package/src/steward/triage-plan.ts +32 -4
- package/src/steward/triage.ts +93 -9
- package/src/steward/working-backwards.ts +64 -0
- package/src/storyboard/storyboard.ts +141 -0
- package/src/task-cards/card-store.ts +2 -2
- package/src/task-orchestrator/surfaces/pod-lease.ts +260 -0
- package/src/telegram-card-followup.ts +233 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +7 -0
- package/src/telegram.ts +29 -8
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- package/apps/pwa/out/_next/static/bFI5EEwg7T6aG88ibX4Zq/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7032-f4ef698518fba61c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8067-d976b441a104ab74.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8519-5632377cce994f86.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8630-cdfc61e82c653ffc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-e263b332208e3957.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-139040d37c79dbc9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9740-8f5ff96c522b5f55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-fb7c2d458f32fcad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-35e2713597dd24d1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-918b656f064577ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5f30e00693527ebd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-7fbae173b3c103a7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-3b4edcce364062ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-c5794f484c251f44.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-1130d8eefcd87e76.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-016e2bda04eecb1f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-b4d6d9a3b56f2940.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-ec49a466121233ba.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-f72db356744ceade.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-a6fd9b50d1b85378.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-adfbd2ba9598e59a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-46fedff57d4bec67.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-0cf318e9dcee2bbf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-899b327cb8b6845c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-6c1e6ba743a29dfe.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-1a8e4807292f6b17.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-28deba6bcb834916.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-de68e2205794037d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-c71c33a573230672.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-4fc56790fe0d2682.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-59df2aebb38a7c1e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4647be5dbcbd0795.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-e025892bda74b743.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-a0955de73b89f078.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-369846d2921af584.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-72cbc1c8cdd0f65d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-77b28bfb0996d5ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-3220c4934f49c6c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-7df9243ed021a4cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-4498c3853f4f688d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/f92f63a5df0ba229.css +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{bFI5EEwg7T6aG88ibX4Zq → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { appendFileSync, chmodSync, existsSync, mkdirSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { Database } from 'bun:sqlite';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { TRACK_AGENT_FORBIDDEN_ACTION_REGEX, universeLaunch } from '../autopilot/track-agent.js';
|
|
7
|
+
import { debug } from '../debug/log.js';
|
|
8
|
+
import { effectiveInstanceRoot, releaseLedgerRoot } from '../instance/resolve.js';
|
|
9
|
+
import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
|
|
10
|
+
import { listChecklist } from '../release-loop/checklist.js';
|
|
11
|
+
import { readSchedules } from '../release-loop/feature-store.js';
|
|
12
|
+
|
|
13
|
+
// `kind`/`createdAt` are the V3 shadow-compare keys (내부 문서 `METHOD-v3-shadow-compare-2026-10-02` · MK 10-02 18:54).
|
|
14
|
+
export type SeatItem = { source: 'request' | 'checklist'; kind?: 'request' | 'cell'; id: string; title: string; text: string; version?: string; queuedAt?: string; createdAt?: string };
|
|
15
|
+
export type SeatInputs = { requests: SeatItem[]; checklist: SeatItem[]; role: string };
|
|
16
|
+
// V3 ledger row: `ts` · `item.id` · `item.kind` · `item.createdAt` · `action` (door or skipped-*) · `reason` (forbidden word for a decision).
|
|
17
|
+
export type SeatAction = 'decision' | 'harness' | 'skipped-budget' | 'skipped-empty';
|
|
18
|
+
export type SeatEntry = { seat: string; ts?: string; at: string; status: 'shadow' | 'attempting' | 'outcome-unknown' | 'launched' | 'hitl' | 'skipped-budget' | 'skipped-empty'; item?: SeatItem; action?: SeatAction; reason?: string; runId?: string };
|
|
19
|
+
export type SeatLoopResult = SeatEntry | { seat: string; status: 'skipped-off' };
|
|
20
|
+
export type SeatDeps = {
|
|
21
|
+
root?: string;
|
|
22
|
+
repo?: string;
|
|
23
|
+
config?: SeatLoopConfig;
|
|
24
|
+
now?: () => Date;
|
|
25
|
+
read?: (path: string) => string;
|
|
26
|
+
run?: (args: string[]) => Promise<string>;
|
|
27
|
+
append?: (path: string, entry: SeatEntry) => void;
|
|
28
|
+
versions?: () => string[];
|
|
29
|
+
/** Checklist items of one version — default reads the release ledger DB (checklist.json is only a legacy import source since REL5b). */
|
|
30
|
+
checklistItems?: (version: string) => Array<{ id: string; title: string; status: string; owner?: string | null }>;
|
|
31
|
+
ledgerFiles?: (directory: string) => string[];
|
|
32
|
+
lockContended?: () => void;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const repoRoot = resolve(import.meta.dir, '../..');
|
|
36
|
+
const exec = promisify(execFile);
|
|
37
|
+
const defaultRead = (path: string): string => {
|
|
38
|
+
try { return readFileSync(path, 'utf8'); }
|
|
39
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code === 'ENOENT') return ''; throw error; }
|
|
40
|
+
};
|
|
41
|
+
const defaultRun = async (args: string[]): Promise<string> => {
|
|
42
|
+
const { argv, env } = universeLaunch(args);
|
|
43
|
+
const { stdout } = await exec('bun', argv, {
|
|
44
|
+
cwd: repoRoot, env, encoding: 'utf8',
|
|
45
|
+
timeout: args[0] === 'harness' && args[1] === 'say' ? 3_600_000 : 120_000, maxBuffer: 4 * 1024 * 1024,
|
|
46
|
+
});
|
|
47
|
+
return stdout;
|
|
48
|
+
};
|
|
49
|
+
const defaultAppend = (path: string, entry: SeatEntry): void => {
|
|
50
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
51
|
+
appendFileSync(path, `${JSON.stringify(entry)}\n`, { mode: 0o600 });
|
|
52
|
+
};
|
|
53
|
+
const observe = (event: string, data: Record<string, unknown>): void => {
|
|
54
|
+
try { debug.log('seat.loop', event, data); } catch { /* logging cannot change execution */ }
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// SQLite's OS lock survives the awaited external call and is released if the process exits.
|
|
58
|
+
async function withSeatLock<T>(directory: string, action: () => Promise<T>, contended?: () => void): Promise<T> {
|
|
59
|
+
mkdirSync(directory, { recursive: true });
|
|
60
|
+
const path = join(directory, '.once.mutex.sqlite');
|
|
61
|
+
const db = new Database(path, { create: true, strict: true });
|
|
62
|
+
try {
|
|
63
|
+
chmodSync(path, 0o600);
|
|
64
|
+
const deadline = Date.now() + 120_000;
|
|
65
|
+
let signalled = false;
|
|
66
|
+
while (true) {
|
|
67
|
+
try { db.exec('BEGIN IMMEDIATE'); break; }
|
|
68
|
+
catch (error) {
|
|
69
|
+
if ((error as { code?: string }).code !== 'SQLITE_BUSY' || Date.now() >= deadline) throw error;
|
|
70
|
+
if (!signalled) { contended?.(); signalled = true; }
|
|
71
|
+
await new Promise((done) => setTimeout(done, 25));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try { return await action(); }
|
|
75
|
+
finally { db.exec('ROLLBACK'); }
|
|
76
|
+
} finally { db.close(); }
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function seatDay(now: Date): string {
|
|
80
|
+
return new Intl.DateTimeFormat('en-CA', { timeZone: 'Asia/Seoul', year: 'numeric', month: '2-digit', day: '2-digit' }).format(now);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function seatLedgerPath(seat: string, root: string, now: Date): string {
|
|
84
|
+
return join(root, 'seat-loop', seat, `${seatDay(now)}.jsonl`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function rows(text: string): Record<string, unknown>[] {
|
|
88
|
+
return text.split('\n').filter(Boolean).map((line) => JSON.parse(line) as Record<string, unknown>);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function versionOrder(a: string, b: string): number {
|
|
92
|
+
const aa = a.split('.').map(Number), bb = b.split('.').map(Number);
|
|
93
|
+
return (aa[0]! - bb[0]!) || (aa[1]! - bb[1]!) || (aa[2]! - bb[2]!);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function gatherSeatInputs(seat: string, deps: SeatDeps = {}): Promise<SeatInputs> {
|
|
97
|
+
const read = deps.read ?? defaultRead;
|
|
98
|
+
const root = deps.root ?? effectiveInstanceRoot();
|
|
99
|
+
const requests = new Map<string, Record<string, unknown>>();
|
|
100
|
+
for (const row of rows(read(join(root, 'seat-requests', 'requests.jsonl')))) {
|
|
101
|
+
if (typeof row.key === 'string') requests.set(row.key, row);
|
|
102
|
+
}
|
|
103
|
+
const pending = [...requests.values()]
|
|
104
|
+
.filter((row) => row.seat === seat && (row.status === 'pending' || row.status === 'queued') && typeof row.text === 'string')
|
|
105
|
+
.map((row) => ({ source: 'request' as const, kind: 'request' as const, id: String(row.key), title: String(row.text), text: String(row.text), queuedAt: String(row.queuedAt ?? ''), createdAt: String(row.queuedAt ?? '') }))
|
|
106
|
+
.sort((a, b) => a.queuedAt.localeCompare(b.queuedAt) || a.id.localeCompare(b.id));
|
|
107
|
+
const releaseRoot = deps.root ?? releaseLedgerRoot();
|
|
108
|
+
const versions = deps.versions ?? (() => {
|
|
109
|
+
const dir = join(releaseRoot, 'release');
|
|
110
|
+
const fromDirs = existsSync(dir) ? readdirSync(dir).filter((v) => /^\d+\.\d+\.\d+$/.test(v)) : [];
|
|
111
|
+
let fromSchedule: string[] = [];
|
|
112
|
+
try { fromSchedule = readSchedules().map((row) => row.version); } catch { /* ledger unreadable — directories only */ }
|
|
113
|
+
return [...new Set([...fromDirs, ...fromSchedule])].filter((v) => /^\d+\.\d+\.\d+$/.test(v));
|
|
114
|
+
});
|
|
115
|
+
const itemsOf = deps.checklistItems ?? ((version: string) => listChecklist(version).items);
|
|
116
|
+
const checklist: SeatItem[] = [];
|
|
117
|
+
for (const version of versions().sort(versionOrder)) {
|
|
118
|
+
let items: ReturnType<typeof itemsOf>;
|
|
119
|
+
try { items = itemsOf(version); } catch (error) {
|
|
120
|
+
debug.log('seat.loop', 'checklist-unreadable', { seat, version, error: String(error).slice(0, 200) });
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
for (const item of items) {
|
|
124
|
+
if (item.owner === seat && (item.status === 'yellow' || item.status === 'red')) {
|
|
125
|
+
checklist.push({ source: 'checklist', kind: 'cell', version, id: item.id, title: item.title, text: item.title });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
checklist.sort((a, b) => versionOrder(a.version!, b.version!) || a.id.localeCompare(b.id));
|
|
130
|
+
return { requests: pending, checklist, role: read(join(deps.repo ?? repoRoot, 'docs', 'roles', `${seat}.md`)).slice(0, 4_000) };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function itemKey(item: SeatItem): string { return `${item.source}:${item.version ?? ''}:${item.id}`; }
|
|
134
|
+
|
|
135
|
+
// In shadow mode a shadowed item counts as handled, so a rehearsal day walks the queue like the seat would.
|
|
136
|
+
export function pickNext(inputs: SeatInputs, ledger: readonly SeatEntry[], opts: { shadow?: boolean } = {}): SeatItem | null {
|
|
137
|
+
const handled = new Set(ledger.filter((entry) => entry.status === 'launched' || entry.status === 'hitl' || entry.status === 'attempting' || entry.status === 'outcome-unknown' || (opts.shadow === true && entry.status === 'shadow'))
|
|
138
|
+
.filter((entry): entry is SeatEntry & { item: SeatItem } => !!entry.item && !!entry.item.source && typeof entry.item.id === 'string').map((entry) => itemKey(entry.item)));
|
|
139
|
+
return [...inputs.requests, ...inputs.checklist].find((item) => !handled.has(itemKey(item))) ?? null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function planAction(item: SeatItem, seat?: string): { kind: 'decision' | 'harness'; text: string; reason?: string } {
|
|
143
|
+
const hit = `${item.id}\n${item.title}\n${item.text}`.match(TRACK_AGENT_FORBIDDEN_ACTION_REGEX);
|
|
144
|
+
const kind = hit ? 'decision' : 'harness';
|
|
145
|
+
return { kind, text: seat && kind === 'harness' ? seatTaskText(seat, item) : `${item.id} ${item.title}`, ...(hit ? { reason: hit[0] } : {}) };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// The launched sentence names the seat, the source and the release so same-named items stay distinct.
|
|
149
|
+
export function seatTaskText(seat: string, item: SeatItem): string {
|
|
150
|
+
const where = item.source === 'checklist' ? `${item.version ?? '?'} 체크리스트 칸 ${item.id}` : `자리 요청 ${item.id}`;
|
|
151
|
+
return `[${seat} 자리 · ${where} · 역할 docs/roles/${seat}.md] ${item.title.replace(/[\r\n]+/g, ' ')}`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export async function runSeatLoopOnce(seat: string, deps: SeatDeps = {}): Promise<SeatLoopResult> {
|
|
155
|
+
if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
|
|
156
|
+
const config = deps.config ?? getUserConfig().loops?.seat ?? { mode: 'off' };
|
|
157
|
+
if (config.mode === 'off' || !(config.seats ?? ['MK']).includes(seat)) {
|
|
158
|
+
observe('skipped-off', { seat });
|
|
159
|
+
return { seat, status: 'skipped-off' };
|
|
160
|
+
}
|
|
161
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
162
|
+
const path = seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now);
|
|
163
|
+
const directory = dirname(path);
|
|
164
|
+
return withSeatLock(directory, async () => {
|
|
165
|
+
const files = deps.ledgerFiles?.(directory) ?? (existsSync(directory) ? readdirSync(directory) : []);
|
|
166
|
+
const ledger = files.filter((name) => /^\d{4}-\d{2}-\d{2}\.jsonl$/.test(name) && name <= `${seatDay(now)}.jsonl`)
|
|
167
|
+
.flatMap((name) => rows((deps.read ?? defaultRead)(join(directory, name))) as SeatEntry[]);
|
|
168
|
+
const inputs = await gatherSeatInputs(seat, deps);
|
|
169
|
+
const item = pickNext(inputs, ledger, { shadow: config.mode === 'shadow' });
|
|
170
|
+
if (item) observe('picked', { seat, item, date: seatDay(now) });
|
|
171
|
+
const planned = item ? planAction(item, seat) : undefined;
|
|
172
|
+
const entry: SeatEntry = { seat, ts: now.toISOString(), at: now.toISOString(), status: item ? 'shadow' : 'skipped-empty',
|
|
173
|
+
...(item ? { item, action: planned!.kind, ...(planned!.reason ? { reason: planned!.reason } : {}) } : { action: 'skipped-empty' as const }) };
|
|
174
|
+
const append = deps.append ?? defaultAppend;
|
|
175
|
+
if (config.mode === 'shadow' || !item) {
|
|
176
|
+
append(path, entry);
|
|
177
|
+
if (config.mode === 'shadow') observe('shadow', { seat, item, status: entry.status });
|
|
178
|
+
return entry;
|
|
179
|
+
}
|
|
180
|
+
const run = deps.run ?? defaultRun;
|
|
181
|
+
let outcome: string | undefined;
|
|
182
|
+
try { outcome = (JSON.parse(await run(['harness', 'budget', '--json'])) as { outcome?: string }).outcome; }
|
|
183
|
+
catch { /* failed budget observation is never permission to launch */ }
|
|
184
|
+
if (outcome !== 'proceed' && outcome !== 'next-provider') {
|
|
185
|
+
entry.status = 'skipped-budget';
|
|
186
|
+
entry.action = 'skipped-budget';
|
|
187
|
+
delete entry.reason;
|
|
188
|
+
append(path, entry);
|
|
189
|
+
observe('skipped-budget', { seat, item, outcome: outcome ?? 'unavailable' });
|
|
190
|
+
return entry;
|
|
191
|
+
}
|
|
192
|
+
const action = planned!;
|
|
193
|
+
// Persist the intent before crossing the process boundary: an unreturned call may already have acted.
|
|
194
|
+
append(path, { ...entry, status: 'attempting' });
|
|
195
|
+
try {
|
|
196
|
+
if (action.kind === 'decision') {
|
|
197
|
+
const title = `${seat}: ${action.text.replace(/[\r\n]+/g, ' ')}`;
|
|
198
|
+
const raised = await run(['decisions', 'raise', '--title', title, '--category', 'other',
|
|
199
|
+
'--s', `${seat} 배정 항목: ${action.text.replace(/[\r\n]+/g, ' ')}\n역할: ${inputs.role}`, '--c', '자동 실행 금지 문면에 해당하여 사람 결정이 필요하다',
|
|
200
|
+
'--option', 'a=사람 승인:승인 후 별도로 집행', '--option', 'b=보류:집행하지 않음',
|
|
201
|
+
'--skip-recommend', '금지 문면은 자동으로 권고하지 않는다', '--agent', 'seat-loop', '--json']);
|
|
202
|
+
const decision: unknown = JSON.parse(raised.trim());
|
|
203
|
+
if (!decision || typeof decision !== 'object' || typeof (decision as { id?: unknown }).id !== 'string') {
|
|
204
|
+
throw new Error('decisions raise returned no decision id');
|
|
205
|
+
}
|
|
206
|
+
entry.status = 'hitl';
|
|
207
|
+
append(path, entry);
|
|
208
|
+
observe('hitl', { seat, item });
|
|
209
|
+
return entry;
|
|
210
|
+
}
|
|
211
|
+
const output = await run(['harness', 'say', action.text, '--substrate', 'pod', '--pod-pool', config.podPool ?? 'pool-node-b@node-b:8', '--base', 'main', '--json']);
|
|
212
|
+
const results: unknown = JSON.parse(output.trim());
|
|
213
|
+
if (!Array.isArray(results) || results.length === 0 || results.some((result) =>
|
|
214
|
+
!result || typeof result !== 'object' || (result as { status?: unknown }).status !== 'done')) {
|
|
215
|
+
throw new Error('harness say did not complete successfully');
|
|
216
|
+
}
|
|
217
|
+
const runId = (results[0] as { runId?: unknown }).runId;
|
|
218
|
+
if (typeof runId !== 'string' || !/^run-[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(runId)) {
|
|
219
|
+
throw new Error('harness say output has no runId');
|
|
220
|
+
}
|
|
221
|
+
entry.status = 'launched';
|
|
222
|
+
entry.runId = runId;
|
|
223
|
+
append(path, entry);
|
|
224
|
+
observe('launched', { seat, item, runId });
|
|
225
|
+
return entry;
|
|
226
|
+
} catch (error) {
|
|
227
|
+
append(path, { ...entry, status: 'outcome-unknown' });
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
}, deps.lockContended);
|
|
231
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join, resolve } from 'node:path';
|
|
5
|
+
import { promisify } from 'node:util';
|
|
6
|
+
import { debug } from '../debug/log.js';
|
|
7
|
+
import { effectiveInstanceRoot } from '../instance/resolve.js';
|
|
8
|
+
import { getUserConfig, type SeatLoopConfig } from '../user-config.js';
|
|
9
|
+
import { seatDay, seatLedgerPath, type SeatEntry } from './seat-loop.js';
|
|
10
|
+
|
|
11
|
+
export type SeatReportDeps = {
|
|
12
|
+
root?: string;
|
|
13
|
+
repo?: string;
|
|
14
|
+
now?: () => Date;
|
|
15
|
+
read?: (path: string) => string;
|
|
16
|
+
config?: SeatLoopConfig;
|
|
17
|
+
post?: boolean;
|
|
18
|
+
send?: (body: string, seat: string, pr: number) => Promise<void> | void;
|
|
19
|
+
};
|
|
20
|
+
export type SeatReportResult = { seat: string; date: string; body: string; posted: boolean };
|
|
21
|
+
const repoRoot = resolve(import.meta.dir, '../..');
|
|
22
|
+
const exec = promisify(execFile);
|
|
23
|
+
|
|
24
|
+
async function defaultSend(body: string, seat: string, pr: number, repo: string): Promise<void> {
|
|
25
|
+
const dir = mkdtempSync(join(tmpdir(), 'seat-report-'));
|
|
26
|
+
try {
|
|
27
|
+
const file = join(dir, 'body.md');
|
|
28
|
+
writeFileSync(file, body);
|
|
29
|
+
await exec('bash', [join(repo, 'scripts', 'coord-post.sh'), file], {
|
|
30
|
+
cwd: repo, env: { ...process.env, COORD_ID: seat, CH_PR: String(pr) }, timeout: 30_000,
|
|
31
|
+
});
|
|
32
|
+
} finally { rmSync(dir, { recursive: true, force: true }); }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function seatReport(seat: string, deps: SeatReportDeps = {}): Promise<SeatReportResult> {
|
|
36
|
+
if (!/^(?:MK|OP|TC|UX)$/.test(seat)) throw new Error(`unknown seat: ${seat}`);
|
|
37
|
+
const now = (deps.now ?? (() => new Date()))();
|
|
38
|
+
const date = seatDay(now);
|
|
39
|
+
let raw = '';
|
|
40
|
+
try { raw = (deps.read ?? ((path) => readFileSync(path, 'utf8')))(seatLedgerPath(seat, deps.root ?? effectiveInstanceRoot(), now)); }
|
|
41
|
+
catch (error) { if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error; }
|
|
42
|
+
const entries = raw.split('\n').filter(Boolean).map((line) => JSON.parse(line) as SeatEntry);
|
|
43
|
+
// One line per item, its last state wins: an `attempting` row followed by `launched` is not «unconfirmed».
|
|
44
|
+
const latest = new Map<string, SeatEntry>();
|
|
45
|
+
for (const entry of entries) {
|
|
46
|
+
const key = entry.item ? `${entry.item.source}:${entry.item.version ?? ''}:${entry.item.id}` : `none:${entry.status}`;
|
|
47
|
+
latest.delete(key);
|
|
48
|
+
latest.set(key, entry);
|
|
49
|
+
}
|
|
50
|
+
const detail = [...latest.values()].map((entry) => {
|
|
51
|
+
const label = entry.item ? `${entry.item.version ? `${entry.item.version} ` : ''}${entry.item.id} ${entry.item.title}`.replace(/\s+/g, ' ').trim() : '배정 없음';
|
|
52
|
+
if (entry.status === 'launched') return `발사 ${label} (${entry.runId ?? 'runId 없음'})`;
|
|
53
|
+
if (entry.status === 'hitl') return `결정 상정 ${label}`;
|
|
54
|
+
if (entry.status === 'shadow') return `shadow ${label}`;
|
|
55
|
+
if (entry.status === 'attempting' || entry.status === 'outcome-unknown') return `결과 확인 필요 ${entry.status} ${label}`;
|
|
56
|
+
return `건너뜀 ${entry.status} ${label}`;
|
|
57
|
+
});
|
|
58
|
+
const body = `**[${seat}]** {{TS}} → 보고 ${date}: ${detail.join(' · ') || '원장 기록 없음'}`;
|
|
59
|
+
const pr = (deps.config ?? getUserConfig().loops?.seat)?.reportPr;
|
|
60
|
+
const posted = deps.post === true && pr !== undefined;
|
|
61
|
+
if (posted) await (deps.send ?? ((text, id, number) => defaultSend(text, id, number, deps.repo ?? repoRoot)))(body, seat, pr);
|
|
62
|
+
try { debug.log('seat.loop', 'reported', { seat, date, entries: entries.length, posted }); } catch { /* observation is fail-soft */ }
|
|
63
|
+
return { seat, date, body, posted };
|
|
64
|
+
}
|
|
@@ -156,6 +156,8 @@ interface SelfDevSubtaskGradeRationale {
|
|
|
156
156
|
}
|
|
157
157
|
|
|
158
158
|
export interface SelfDevDecompositionObservation extends SelfDevDecompositionMeta {
|
|
159
|
+
/** Number of registration-only goals folded into their dependency. */
|
|
160
|
+
foldedRegistration: number;
|
|
159
161
|
/** Number of retained subtasks whose original feature text grades `too_large`. */
|
|
160
162
|
tooLargeSubtaskCount: number;
|
|
161
163
|
/** Number of retained subtasks whose original feature text grades `too_small`. */
|
|
@@ -250,7 +252,7 @@ function observeDecomposerSelection(selection: SelfDevDecomposerSelection, obser
|
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
function applyGoalDefaultsAndHotPaths(goals: SelfDevGoal[], defaults: { base?: string; autoMerge?: boolean }): SelfDevGoal[] {
|
|
253
|
-
return
|
|
255
|
+
return goals.map((goal) => {
|
|
254
256
|
const hotPaths = [...new Set([...(goal.hotPaths ?? []), ...inferHotPaths(goal.feature ?? '')])];
|
|
255
257
|
return {
|
|
256
258
|
...goal,
|
|
@@ -258,7 +260,55 @@ function applyGoalDefaultsAndHotPaths(goals: SelfDevGoal[], defaults: { base?: s
|
|
|
258
260
|
...(defaults.base !== undefined ? { base: defaults.base } : {}),
|
|
259
261
|
...(defaults.autoMerge !== undefined ? { autoMerge: defaults.autoMerge } : {}),
|
|
260
262
|
};
|
|
261
|
-
})
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const REGISTRATION_FILES = new Set([
|
|
267
|
+
'src/nexus/api/http-server.ts',
|
|
268
|
+
'src/index.ts',
|
|
269
|
+
'src/telegram-commands.ts',
|
|
270
|
+
]);
|
|
271
|
+
|
|
272
|
+
function isRegistrationFile(path: string): boolean {
|
|
273
|
+
return REGISTRATION_FILES.has(path) || /^graphs\/(?:[^/]+\/)*[^/]+\.yaml$/.test(path);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/** Fold only registration-only goals into their one retained dependency. */
|
|
277
|
+
export function foldRegistrationOnlyGoals(goals: SelfDevGoal[]): SelfDevGoal[] {
|
|
278
|
+
let remaining = [...goals];
|
|
279
|
+
let changed = true;
|
|
280
|
+
while (changed) {
|
|
281
|
+
changed = false;
|
|
282
|
+
for (const registration of remaining) {
|
|
283
|
+
const files = registration.hotPaths ?? [];
|
|
284
|
+
if (files.length === 0 || !files.every(isRegistrationFile) || registration.dependsOn?.length !== 1) continue;
|
|
285
|
+
const dependencyId = registration.dependsOn[0];
|
|
286
|
+
const targetIndex = remaining.findIndex((goal) => goal.id === dependencyId && goal !== registration);
|
|
287
|
+
if (targetIndex < 0) continue;
|
|
288
|
+
const target = remaining[targetIndex]!;
|
|
289
|
+
remaining = remaining.filter((goal) => goal !== registration).map((goal) => {
|
|
290
|
+
if (goal === target) {
|
|
291
|
+
const dependsOn = goal.dependsOn?.filter((id) => id !== registration.id && id !== goal.id);
|
|
292
|
+
const { dependsOn: _old, ...rest } = goal;
|
|
293
|
+
return {
|
|
294
|
+
...rest,
|
|
295
|
+
...(dependsOn?.length ? { dependsOn } : {}),
|
|
296
|
+
feature: `${goal.feature}\nAlso register: ${registration.feature}`,
|
|
297
|
+
hotPaths: [...new Set([...(goal.hotPaths ?? []), ...files])],
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
if (!goal.dependsOn?.includes(registration.id!)) return goal;
|
|
301
|
+
const dependsOn = [...new Set(goal.dependsOn.map((id) => id === registration.id ? target.id! : id))]
|
|
302
|
+
.filter((id) => id !== goal.id);
|
|
303
|
+
const { dependsOn: _old, ...rest } = goal;
|
|
304
|
+
return dependsOn.length ? { ...rest, dependsOn } : rest;
|
|
305
|
+
});
|
|
306
|
+
debug.log('self-dev.decompose', 'fold-registration', { from: registration.id, into: target.id, files });
|
|
307
|
+
changed = true;
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
return remaining;
|
|
262
312
|
}
|
|
263
313
|
|
|
264
314
|
function observeDecomposition(
|
|
@@ -267,12 +317,14 @@ function observeDecomposition(
|
|
|
267
317
|
arcClassification?: { arcs: SelfDevArc[]; hintDeviation?: { requested: number; actual: number } },
|
|
268
318
|
observationContext: SelfDevDecompositionObservationContext = {},
|
|
269
319
|
pathGroundingOptions?: DecomposePathGroundingOptions,
|
|
320
|
+
foldedRegistration = 0,
|
|
270
321
|
): SelfDevDecomposition {
|
|
271
322
|
const retainedSubtasks = decomposition.outcome === 'decomposed' ? goals : [];
|
|
272
323
|
const gradedSubtasks = retainedSubtasks.map(gradeRetainedSubtask);
|
|
273
324
|
const tooLargeSubtaskCount = gradedSubtasks.filter(({ grade }) => grade.verdict === 'too_large').length;
|
|
274
325
|
const observation: SelfDevDecompositionObservation & { goalId: string | null; runId: string | null } = {
|
|
275
326
|
...decomposition,
|
|
327
|
+
foldedRegistration,
|
|
276
328
|
tooLargeSubtaskCount,
|
|
277
329
|
tooSmallSubtaskCount: gradedSubtasks.filter(({ grade }) => grade.verdict === 'too_small').length,
|
|
278
330
|
hasMultipleTooLargeSubtasks: tooLargeSubtaskCount >= 2,
|
|
@@ -467,14 +519,15 @@ async function finalizeDecomposition(
|
|
|
467
519
|
decomposition: SelfDevDecompositionMeta,
|
|
468
520
|
opts: SelfDevDecomposeOptions,
|
|
469
521
|
arcHint: number | undefined,
|
|
522
|
+
foldedRegistration = 0,
|
|
470
523
|
): Promise<SelfDevDecomposition> {
|
|
471
|
-
if (arcHint === undefined || goals.length === 0) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
|
|
524
|
+
if (arcHint === undefined || goals.length === 0) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
472
525
|
try {
|
|
473
526
|
const arcLlm = opts.arcLlm ?? opts.llm ?? (await defaultDecomposeLlm(opts.model));
|
|
474
527
|
const tasks = goals.map((goal) => ({ id: goal.id ?? '', title: goal.feature ?? '', description: goal.feature, dependsOn: goal.dependsOn }));
|
|
475
528
|
const grouping = parseSelfDevArcGrouping(await arcLlm(buildSelfDevArcClassifyPrompt(feature, tasks, arcHint)));
|
|
476
529
|
const classification = grouping ? deriveSelfDevArcsFromGrouping(tasks, grouping, arcHint) : null;
|
|
477
|
-
if (!classification) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
|
|
530
|
+
if (!classification) return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
478
531
|
const arcByTask = new Map(classification.arcs.flatMap((arc) => arc.taskIds.map((taskId) => [taskId, arc])));
|
|
479
532
|
const taskIdsForArc = new Map(classification.arcs.map((arc) => [arc.id, arc.taskIds]));
|
|
480
533
|
const orderedGoals = goals.map((goal) => {
|
|
@@ -482,9 +535,9 @@ async function finalizeDecomposition(
|
|
|
482
535
|
const dependsOn = [...new Set([...(goal.dependsOn ?? []), ...arcDependencies])].filter((id) => id !== goal.id);
|
|
483
536
|
return dependsOn.length ? { ...goal, dependsOn } : goal;
|
|
484
537
|
});
|
|
485
|
-
return observeDecomposition(pruneDanglingDependencies(orderedGoals), decomposition, classification, opts.observation, opts.pathGrounding);
|
|
538
|
+
return observeDecomposition(pruneDanglingDependencies(orderedGoals), decomposition, classification, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
486
539
|
} catch {
|
|
487
|
-
return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding);
|
|
540
|
+
return observeDecomposition(goals, decomposition, undefined, opts.observation, opts.pathGrounding, foldedRegistration);
|
|
488
541
|
}
|
|
489
542
|
}
|
|
490
543
|
|
|
@@ -514,17 +567,18 @@ export async function decomposeSelfDevGoal(
|
|
|
514
567
|
}
|
|
515
568
|
observeDecomposerSelection('fabric', opts.observation);
|
|
516
569
|
const actualTaskCount = result.goals.length;
|
|
517
|
-
const
|
|
570
|
+
const prepared = actualTaskCount > 0
|
|
518
571
|
? applyGoalDefaultsAndHotPaths(result.goals.slice(0, hardMaxTasks), defaults)
|
|
519
572
|
: [single];
|
|
520
|
-
|
|
573
|
+
const goals = foldRegistrationOnlyGoals(prepared);
|
|
574
|
+
return finalizeDecomposition(feature, pruneDanglingDependencies(goals), {
|
|
521
575
|
recommendedMaxTasks: maxTasks,
|
|
522
576
|
actualTaskCount,
|
|
523
577
|
truncatedAtHardMax: actualTaskCount > hardMaxTasks,
|
|
524
578
|
exceededRecommendedMax: actualTaskCount > maxTasks,
|
|
525
579
|
outcome: actualTaskCount === 0 ? 'single-no-subtasks' : 'decomposed',
|
|
526
580
|
omittedGoalCount: result.omittedGoalCount,
|
|
527
|
-
}, opts, actualTaskCount > 0 ? arcHint : undefined);
|
|
581
|
+
}, opts, actualTaskCount > 0 ? arcHint : undefined, prepared.length - goals.length);
|
|
528
582
|
}
|
|
529
583
|
|
|
530
584
|
observeDecomposerSelection('default', opts.observation);
|
|
@@ -539,8 +593,9 @@ export async function decomposeSelfDevGoal(
|
|
|
539
593
|
const actualTaskCount = rawSubtasks.length;
|
|
540
594
|
const decomposition: SelfDevDecompositionMeta = { recommendedMaxTasks: maxTasks, actualTaskCount, truncatedAtHardMax: actualTaskCount > hardMaxTasks, exceededRecommendedMax: actualTaskCount > maxTasks, outcome: actualTaskCount === 0 ? 'single-no-subtasks' : 'decomposed' };
|
|
541
595
|
const parsedGoals = parseSelfDevDecomposition(JSON.stringify({ subtasks: rawSubtasks.slice(0, hardMaxTasks) }), defaults);
|
|
542
|
-
const
|
|
543
|
-
|
|
596
|
+
const foldedGoals = foldRegistrationOnlyGoals(parsedGoals);
|
|
597
|
+
const goals = parsedGoals.length > 0 ? pruneDanglingDependencies(foldedGoals) : [single];
|
|
598
|
+
return finalizeDecomposition(feature, goals, decomposition, opts, parsedGoals.length > 0 ? arcHint : undefined, parsedGoals.length - foldedGoals.length);
|
|
544
599
|
}
|
|
545
600
|
|
|
546
601
|
/** Default LLM seam — lazy `streamLLM` wire (kept out of the test path). */
|
|
@@ -111,6 +111,8 @@ function runPath(runId: string, dir: string): string {
|
|
|
111
111
|
return path;
|
|
112
112
|
}
|
|
113
113
|
|
|
114
|
+
const loggedAbsentCheckpoints = new Set<string>();
|
|
115
|
+
|
|
114
116
|
function loadSelfDevRunFromPath(path: string): SelfDevRunState | null {
|
|
115
117
|
const runId = path.slice(path.lastIndexOf(sep) + 1, -'.json'.length);
|
|
116
118
|
try {
|
|
@@ -118,7 +120,14 @@ function loadSelfDevRunFromPath(path: string): SelfDevRunState | null {
|
|
|
118
120
|
if (o && typeof o.runId === 'string' && Array.isArray(o.results)) return o;
|
|
119
121
|
try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: 'invalid checkpoint shape' }); } catch { /* preserve fail-soft reads */ }
|
|
120
122
|
} catch (error) {
|
|
121
|
-
|
|
123
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
124
|
+
if (!loggedAbsentCheckpoints.has(runId)) {
|
|
125
|
+
loggedAbsentCheckpoints.add(runId);
|
|
126
|
+
try { debug.log('self-dev.run-store', 'checkpoint-absent', { runId }); } catch { /* preserve fail-soft reads */ }
|
|
127
|
+
}
|
|
128
|
+
} else {
|
|
129
|
+
try { debug.log('self-dev.run-store', 'read-failed', { runId, reason: String(error) }); } catch { /* preserve fail-soft reads */ }
|
|
130
|
+
}
|
|
122
131
|
}
|
|
123
132
|
return null;
|
|
124
133
|
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
import { createPendingQuestion, writePendingQuestion } from '../ask-user-question/pending-questions.js';
|
|
4
|
+
import { createFileAskUserQuestionResolver } from '../ask-user-question/tool.js';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import {
|
|
7
|
+
decideAndObserveClarification,
|
|
8
|
+
defaultClarificationBudget,
|
|
9
|
+
toAskUserQuestionRequest,
|
|
10
|
+
type ClarificationCandidate,
|
|
11
|
+
type ClarificationContext,
|
|
12
|
+
} from '../hitl/clarification-policy.js';
|
|
13
|
+
import { getUserConfig } from '../user-config.js';
|
|
14
|
+
import { appendRunLedgerEntry, type RunLedgerWriter } from './run-ledger.js';
|
|
15
|
+
|
|
16
|
+
export interface ExecutionClarificationContext {
|
|
17
|
+
runId: string;
|
|
18
|
+
budget?: ClarificationContext['budget'];
|
|
19
|
+
pendingQuestionCount?: number;
|
|
20
|
+
config?: { hitl?: { executionDeadlineMinutes?: number } };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ExecutionClarificationResult =
|
|
24
|
+
| { choice: string; by: 'assumed' | 'owner' | 'auto-recommended' }
|
|
25
|
+
| { deferred: true }
|
|
26
|
+
| { parked: true; reason: 'critical-unanswered'; questionId: string };
|
|
27
|
+
|
|
28
|
+
export interface ExecutionClarificationDeps {
|
|
29
|
+
now?: () => number;
|
|
30
|
+
createId?: () => string;
|
|
31
|
+
resolver?: typeof createFileAskUserQuestionResolver;
|
|
32
|
+
resolverDeps?: Parameters<typeof createFileAskUserQuestionResolver>[1];
|
|
33
|
+
writeRunLedger?: RunLedgerWriter;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** A single execution-phase decision; the file resolver owns the pending/answer lifecycle. */
|
|
37
|
+
export async function askAtExecution(
|
|
38
|
+
candidate: ClarificationCandidate,
|
|
39
|
+
ctx: ExecutionClarificationContext,
|
|
40
|
+
deps: ExecutionClarificationDeps = {},
|
|
41
|
+
): Promise<ExecutionClarificationResult> {
|
|
42
|
+
const recommended = candidate.options.filter(option => option.recommended);
|
|
43
|
+
if (candidate.options.length < 2 || candidate.options.length > 4 || recommended.length !== 1
|
|
44
|
+
|| new Set(candidate.options.map(option => option.label)).size !== candidate.options.length) {
|
|
45
|
+
throw new Error('Execution clarification requires 2–4 distinct options and exactly one recommendation.');
|
|
46
|
+
}
|
|
47
|
+
const observe = (event: 'assumed' | 'asked' | 'answered' | 'auto-recommended' | 'parked', questionId?: string): void => {
|
|
48
|
+
try {
|
|
49
|
+
debug.log('hitl.execution', event, { runId: ctx.runId, questionId, impact: candidate.impact });
|
|
50
|
+
} catch { /* Logging must not decide the user's choice. */ }
|
|
51
|
+
};
|
|
52
|
+
const record = (event: 'assumed' | 'auto-recommended' | 'parked', data: Record<string, unknown>): void => {
|
|
53
|
+
(deps.writeRunLedger ?? appendRunLedgerEntry)({
|
|
54
|
+
timestamp: new Date((deps.now ?? Date.now)()).toISOString(),
|
|
55
|
+
runId: ctx.runId,
|
|
56
|
+
event,
|
|
57
|
+
data: { impact: candidate.impact, ...data },
|
|
58
|
+
});
|
|
59
|
+
};
|
|
60
|
+
const decision = decideAndObserveClarification(candidate, {
|
|
61
|
+
phase: 'execution',
|
|
62
|
+
budget: ctx.budget ?? defaultClarificationBudget('execution'),
|
|
63
|
+
pendingQuestionCount: ctx.pendingQuestionCount,
|
|
64
|
+
}, { consumer: 'self-implement-execution' });
|
|
65
|
+
if (decision.action === 'defer') return { deferred: true };
|
|
66
|
+
if (decision.action === 'assume') {
|
|
67
|
+
record('assumed', { choice: recommended[0]!.label, assumption: decision.assumption });
|
|
68
|
+
observe('assumed');
|
|
69
|
+
return { choice: recommended[0]!.label, by: 'assumed' };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const request = toAskUserQuestionRequest(decision);
|
|
73
|
+
if (!request) throw new Error('Execution clarification policy approved a question without a valid wire request.');
|
|
74
|
+
const configured = ctx.config === undefined
|
|
75
|
+
? getUserConfig().hitl?.executionDeadlineMinutes
|
|
76
|
+
: ctx.config.hitl?.executionDeadlineMinutes;
|
|
77
|
+
const minutes = configured === undefined ? 30 : configured;
|
|
78
|
+
if (!Number.isSafeInteger(minutes) || minutes < 1 || minutes > 35_791) {
|
|
79
|
+
throw new Error('hitl.executionDeadlineMinutes must be a positive integer within the resolver timer limit.');
|
|
80
|
+
}
|
|
81
|
+
const questionId = (deps.createId ?? (() => `execution:${ctx.runId}:${randomUUID()}`))();
|
|
82
|
+
let expired = false;
|
|
83
|
+
let written = false;
|
|
84
|
+
const resolver = (deps.resolver ?? createFileAskUserQuestionResolver)(minutes * 60_000, {
|
|
85
|
+
...deps.resolverDeps,
|
|
86
|
+
observe: (state, data) => {
|
|
87
|
+
if (state === 'answer-missing') expired = true;
|
|
88
|
+
deps.resolverDeps?.observe?.(state, data);
|
|
89
|
+
},
|
|
90
|
+
createId: () => questionId,
|
|
91
|
+
write: (pending) => {
|
|
92
|
+
const question = createPendingQuestion(questionId, { ...request, runId: ctx.runId }, undefined,
|
|
93
|
+
{ now: () => new Date((deps.now ?? Date.now)()) },
|
|
94
|
+
{ surface: 'file', delivery: 'file', expiresAt: pending.expiresAt });
|
|
95
|
+
(deps.resolverDeps?.write ?? writePendingQuestion)(question);
|
|
96
|
+
written = true;
|
|
97
|
+
observe('asked', questionId);
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
const answer = await resolver({ ...request, runId: ctx.runId });
|
|
101
|
+
const chosen = answer.answers[candidate.id];
|
|
102
|
+
if (written && !expired && !answer.cancelled && typeof chosen === 'string' && candidate.options.some(option => option.label === chosen)) {
|
|
103
|
+
observe('answered', questionId);
|
|
104
|
+
return { choice: chosen, by: 'owner' };
|
|
105
|
+
}
|
|
106
|
+
if (!written || !expired) throw new Error('Execution clarification was cancelled or could not read its answer before expiry.');
|
|
107
|
+
if (candidate.impact === 'critical') {
|
|
108
|
+
record('parked', { questionId, reason: 'critical-unanswered' });
|
|
109
|
+
observe('parked', questionId);
|
|
110
|
+
return { parked: true, reason: 'critical-unanswered', questionId };
|
|
111
|
+
}
|
|
112
|
+
record('auto-recommended', { questionId, choice: recommended[0]!.label, by: 'auto-recommended' });
|
|
113
|
+
observe('auto-recommended', questionId);
|
|
114
|
+
return { choice: recommended[0]!.label, by: 'auto-recommended' };
|
|
115
|
+
}
|
|
@@ -7,6 +7,7 @@ import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
|
|
|
7
7
|
import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
|
|
8
8
|
import { runAndroidUnitTestGate } from '../../scripts/ci-android-unit-tests.js';
|
|
9
9
|
import { runIosUnitTestGate } from '../../scripts/ci-ios-unit-tests.js';
|
|
10
|
+
import { runPwaBuildGate } from '../../scripts/ci-pwa-build-gate.js';
|
|
10
11
|
import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-gate.js';
|
|
11
12
|
import { parseReviewDepthFromFilesJson } from '../agent-mission/review-depth.js';
|
|
12
13
|
import { buildGateBaselineReport, calculateGateTestCountChange, formatGateBaselineNote, formatGateTestCountNote, isGateTestFile, runGateBaseline, type BaselineProcessResult, type BaselineProcessStatus, type GateTestFailure } from './gate-baseline.js';
|
|
@@ -83,6 +84,7 @@ interface SelfGateCliDeps {
|
|
|
83
84
|
runAndroidGate?: (out: GateOutput) => number;
|
|
84
85
|
/** iOS 순수-로직 시험 게이트 심(시험 주입용). */
|
|
85
86
|
runIosGate?: (out: GateOutput) => number;
|
|
87
|
+
runPwaGate?: (out: GateOutput) => number;
|
|
86
88
|
listPrWorktrees?: (cwd: string) => PrWorktreeListEntry[] | { lookupFailed: string };
|
|
87
89
|
inspectWorktreeDirtiness?: (path: string) => { dirty: boolean } | { lookupFailed: string };
|
|
88
90
|
}
|
|
@@ -619,11 +621,12 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
619
621
|
const androidPassed = runAdditionalGate('android-gate', deps.runAndroidGate ?? runAndroidUnitTestGate, selection.files, cwd, lines);
|
|
620
622
|
// 🍎 iOS 축 — 안드로이드와 «같은 자리»에 둔다(skipTestStep 앞). Swift 만 바뀐 PR 도 여기서 조기 리턴한다.
|
|
621
623
|
const iosPassed = runAdditionalGate('ios-gate', deps.runIosGate ?? runIosUnitTestGate, selection.files, cwd, lines);
|
|
624
|
+
const pwaPassed = runAdditionalGate('pwa-gate', deps.runPwaGate ?? runPwaBuildGate, selection.files, cwd, lines);
|
|
622
625
|
|
|
623
626
|
if (scope.skipTestStep) {
|
|
624
627
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
625
628
|
logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
|
|
626
|
-
return { exitCode: androidPassed && iosPassed && policyPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
629
|
+
return { exitCode: androidPassed && iosPassed && pwaPassed && policyPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles: [], unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
627
630
|
}
|
|
628
631
|
|
|
629
632
|
const test = (deps.runTests ?? ((dir, files) => (deps.runCommand ?? defaultRunCommand)('bun', ['test', ...files], dir)))(cwd, testFiles);
|
|
@@ -632,7 +635,7 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
632
635
|
lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, `tests: pass (${testFiles.length} files)`));
|
|
633
636
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
634
637
|
logGateCliBaseline(cwd, { ...ZERO_GATE_BASELINE, unrunImporterTotal, lookupFailed: importerLookupFailed });
|
|
635
|
-
return { exitCode: policyPassed && androidPassed && iosPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
638
|
+
return { exitCode: policyPassed && androidPassed && iosPassed && pwaPassed ? 0 : 1, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
636
639
|
}
|
|
637
640
|
|
|
638
641
|
if (worktreeLog.includes('deterministic environment setup failed')) lines.push(worktreeLog);
|
|
@@ -653,6 +656,6 @@ export function runSelfGateCli(cwd: string, options: SelfGateCliOptions = {}, de
|
|
|
653
656
|
});
|
|
654
657
|
lines.push(formatPartialObservationNote(unrunImporterTotal ?? 0, formatGateBaselineNote(report, unrunImporterTotal ?? 0)));
|
|
655
658
|
const policyPassed = runPolicyGates(selection.files, cwd, lines, deps);
|
|
656
|
-
const exitCode = !androidPassed || !iosPassed || !policyPassed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
|
|
659
|
+
const exitCode = !androidPassed || !iosPassed || !pwaPassed || !policyPassed || report.introduced > 0 || report.unknown > 0 || report.preconditionUnmet > 0 ? 1 : 0;
|
|
657
660
|
return { exitCode, lines, changedFiles: selection.files, testFiles, unverified: scope.unverified, documentPaths: scope.documentPaths, documentsWithoutDerivedTests: scope.documentsWithoutDerivedTests };
|
|
658
661
|
}
|