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,14 @@
|
|
|
1
|
+
/** U1a — how long an empty-input Ctrl+C stays armed (and its hint stays on screen). */
|
|
2
|
+
export const CTRL_C_ARM_MS = 2_000;
|
|
3
|
+
|
|
4
|
+
export function resolveCtrlCInInput({ text, now, armedAt }: {
|
|
5
|
+
text: string;
|
|
6
|
+
now: number;
|
|
7
|
+
armedAt: number | null;
|
|
8
|
+
}): { action: 'clear-input' | 'arm-exit' | 'exit'; armedAt: number | null; hint: string | null } {
|
|
9
|
+
if (text.length > 0) return { action: 'clear-input', armedAt: null, hint: null };
|
|
10
|
+
if (armedAt !== null && now >= armedAt && now - armedAt <= CTRL_C_ARM_MS) {
|
|
11
|
+
return { action: 'exit', armedAt: null, hint: null };
|
|
12
|
+
}
|
|
13
|
+
return { action: 'arm-exit', armedAt: now, hint: '한 번 더 누르면 나갑니다 · /quit' };
|
|
14
|
+
}
|
package/src/chat/index.ts
CHANGED
|
@@ -19,6 +19,7 @@ import type { ModalSurface } from '../display/modal-stack.js';
|
|
|
19
19
|
import type { ContentBlock } from '../llm.js';
|
|
20
20
|
import { debug } from '../debug/log.js';
|
|
21
21
|
import { matchTextInputGlobalAction } from './global-actions.js';
|
|
22
|
+
import { CTRL_C_ARM_MS, resolveCtrlCInInput } from './ctrl-c-in-input.js';
|
|
22
23
|
import { isTextInputQuitChord } from './input-quit-chord.js';
|
|
23
24
|
// P5 — leader-chord pass-through from input focus. chat.ts swallows
|
|
24
25
|
// plain letters into its textarea buffer, so the resolver never sees
|
|
@@ -896,6 +897,10 @@ export async function textInput(opts: {
|
|
|
896
897
|
let lastReportedLines = -1;
|
|
897
898
|
let lineIdx = 0; // current line
|
|
898
899
|
let colIdx = lines[0]!.length; // cursor at end of initial text
|
|
900
|
+
let ctrlCArmedAt: number | null = null;
|
|
901
|
+
let ctrlCHintTimer: ReturnType<typeof setTimeout> | null = null;
|
|
902
|
+
let ctrlCHint: string | null = null;
|
|
903
|
+
let ctrlCHintPainted = false;
|
|
899
904
|
|
|
900
905
|
// KX2 — picker selection / navigation / async fetch caches live in
|
|
901
906
|
// the shared state machine (chat-picker-state.ts). textInput keeps
|
|
@@ -1173,6 +1178,14 @@ export async function textInput(opts: {
|
|
|
1173
1178
|
}
|
|
1174
1179
|
prevDrawnLines = visibleLines;
|
|
1175
1180
|
|
|
1181
|
+
if (ctrlCHint) {
|
|
1182
|
+
writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K' + C.muted(truncate(ctrlCHint, width)));
|
|
1183
|
+
ctrlCHintPainted = true;
|
|
1184
|
+
} else if (ctrlCHintPainted) {
|
|
1185
|
+
writeTerminal(ansi.moveTo(row + 2, col) + '\x1b[2K');
|
|
1186
|
+
ctrlCHintPainted = false;
|
|
1187
|
+
}
|
|
1188
|
+
|
|
1176
1189
|
// Cursor — its buffer line is `lineIdx`; its screen row is the
|
|
1177
1190
|
// bottom-anchored offset within the visible window. Critically,
|
|
1178
1191
|
// `textBeforeCursor` reads from `lines[lineIdx]` (the buffer), not
|
|
@@ -1468,6 +1481,11 @@ export async function textInput(opts: {
|
|
|
1468
1481
|
process.exit(0);
|
|
1469
1482
|
}
|
|
1470
1483
|
|
|
1484
|
+
if (!(key.ctrl && (key.name === 'c' || key.name === 'ㅊ') && !key.shift && !key.alt)) {
|
|
1485
|
+
ctrlCArmedAt = null;
|
|
1486
|
+
ctrlCHint = null;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1471
1489
|
// A bracketed-paste envelope is one atomic event. Handle it before
|
|
1472
1490
|
// host routing so embedded newlines cannot reach a submit handler.
|
|
1473
1491
|
if (key.name === 'paste') {
|
|
@@ -1571,6 +1589,40 @@ export async function textInput(opts: {
|
|
|
1571
1589
|
continue;
|
|
1572
1590
|
}
|
|
1573
1591
|
|
|
1592
|
+
if (key.ctrl && (key.name === 'c' || key.name === 'ㅊ') && !key.shift && !key.alt) {
|
|
1593
|
+
const text = lines.join('\n');
|
|
1594
|
+
const result = resolveCtrlCInInput({ text, now: Date.now(), armedAt: ctrlCArmedAt });
|
|
1595
|
+
debug.log('chat.input', 'ctrl-c', { action: result.action, length: text.length });
|
|
1596
|
+
ctrlCArmedAt = result.armedAt;
|
|
1597
|
+
ctrlCHint = result.hint;
|
|
1598
|
+
if (ctrlCHintTimer) clearTimeout(ctrlCHintTimer);
|
|
1599
|
+
ctrlCHintTimer = null;
|
|
1600
|
+
if (result.action === 'arm-exit') {
|
|
1601
|
+
// U1a — the hint promises «one more press exits» only while that is true.
|
|
1602
|
+
const armedAt = result.armedAt;
|
|
1603
|
+
ctrlCHintTimer = setTimeout(() => {
|
|
1604
|
+
ctrlCHintTimer = null;
|
|
1605
|
+
if (ctrlCArmedAt !== armedAt) return;
|
|
1606
|
+
ctrlCArmedAt = null;
|
|
1607
|
+
ctrlCHint = null;
|
|
1608
|
+
drawAll();
|
|
1609
|
+
}, CTRL_C_ARM_MS + 50);
|
|
1610
|
+
ctrlCHintTimer.unref?.();
|
|
1611
|
+
}
|
|
1612
|
+
if (result.action === 'exit') return submitBufferResult('/quit');
|
|
1613
|
+
if (result.action === 'clear-input') {
|
|
1614
|
+
lines = [''];
|
|
1615
|
+
lineIdx = 0;
|
|
1616
|
+
colIdx = 0;
|
|
1617
|
+
historyIdx = -1;
|
|
1618
|
+
savedCurrentInput = '';
|
|
1619
|
+
onUserBufferEdit();
|
|
1620
|
+
await picker.refresh(bufView());
|
|
1621
|
+
}
|
|
1622
|
+
drawAll();
|
|
1623
|
+
continue;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1574
1626
|
const globalDispatcher = dispatchGlobalAction;
|
|
1575
1627
|
const globalAction = globalDispatcher
|
|
1576
1628
|
? matchTextInputGlobalAction(key, {
|
|
@@ -33,6 +33,7 @@ import type {
|
|
|
33
33
|
} from '../index.js';
|
|
34
34
|
import { filterSlashCommands } from '../index.js';
|
|
35
35
|
import { filterInputMatches } from '../../input/query-match.js';
|
|
36
|
+
import { readTuiSlashAudience, slashMaturity, slashVisibleFor } from '../../maturity/tui-slash-maturity.js';
|
|
36
37
|
|
|
37
38
|
/** Read-only snapshot of the textInput buffer handed to state on
|
|
38
39
|
* every public call. The state machine never mutates it; it only
|
|
@@ -234,8 +235,11 @@ export function createPickerState(deps: PickerDeps): PickerState {
|
|
|
234
235
|
let currentAtItems: AtCandidate[] = [];
|
|
235
236
|
let currentSkillItems: SkillCandidate[] = [];
|
|
236
237
|
|
|
237
|
-
const filteredSlash = (buf: PickerBufferView): SlashCommand[] =>
|
|
238
|
-
|
|
238
|
+
const filteredSlash = (buf: PickerBufferView): SlashCommand[] => {
|
|
239
|
+
const { role, showBeta } = readTuiSlashAudience();
|
|
240
|
+
return filterSlashCommands((buf.lines[0] ?? '').slice(1), deps.commands)
|
|
241
|
+
.filter((cmd) => (role === 'owner' && slashMaturity(cmd.name) === undefined) || slashVisibleFor(cmd.name, role, { showBeta }));
|
|
242
|
+
};
|
|
239
243
|
|
|
240
244
|
const refreshArgs = async (buf: PickerBufferView): Promise<ArgSuggestion[]> => {
|
|
241
245
|
const ctx = parseArgContext(buf, deps.commands);
|
package/src/cli/agent-cli.ts
CHANGED
|
@@ -3,6 +3,7 @@ import { resolve } from 'node:path';
|
|
|
3
3
|
import { getUserConfig, reloadUserConfig } from '../user-config.js';
|
|
4
4
|
import { handleOnboardingRefusal, needsOnboarding, runOnboarding } from '../onboarding.js';
|
|
5
5
|
import { runTurn, ensureCliSession, sessionBudget } from '../session/chat.js';
|
|
6
|
+
import { suggestProjectForFolder } from '../project/project-store.js';
|
|
6
7
|
import { NoLlmProviderAvailableError, noProviderAvailableMessage } from '../llm.js';
|
|
7
8
|
import { resolveSessionId, getActiveSessionId, setActiveSessionId } from '../session/index.js';
|
|
8
9
|
import { getHarnessSpace } from '../harness/harness-space.js';
|
|
@@ -433,6 +434,12 @@ export async function runChatTurnCli(opts: {
|
|
|
433
434
|
}
|
|
434
435
|
const session = ensureCliSession(opts.cfg, resolvedSessionIdHint);
|
|
435
436
|
setActiveSessionId(session.id);
|
|
437
|
+
if (!opts.json && (!resolvedSessionIdHint || session.id !== resolvedSessionIdHint)) {
|
|
438
|
+
try {
|
|
439
|
+
const project = suggestProjectForFolder(process.cwd());
|
|
440
|
+
if (project) console.log(`Project suggestion: ${project.name} (${project.id})`);
|
|
441
|
+
} catch { /* suggestions must not interrupt a CLI conversation */ }
|
|
442
|
+
}
|
|
436
443
|
const harnessSpace = getHarnessSpace();
|
|
437
444
|
// Tag the debug log with this session id so every event in this
|
|
438
445
|
// process attributes correctly. enrichDebugRecord picks up the
|
package/src/cli/decisions-cli.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
|
+
import { projectDecisionsToLinear } from '../decisions/decision-linear-projection.js';
|
|
2
3
|
import { DecisionLedger, importDecisionMarkdown, type DecisionCategory, type DecisionLedgerOptions, type DecisionOption, type DecisionTrack, type DecisionEntry } from '../decisions/decision-ledger.js';
|
|
3
4
|
|
|
4
5
|
const repeat = (value: string, values: string[]) => [...values, value];
|
|
@@ -49,22 +50,31 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
|
|
|
49
50
|
const root = program.command('decisions').description('대표 결정 원장 · 로컬 전용');
|
|
50
51
|
const emit = (value: unknown, json?: boolean, text?: string) => out.log(json ? JSON.stringify(value) : text ?? JSON.stringify(value));
|
|
51
52
|
const fail = (action: () => void) => { try { action(); } catch (e) { throw new Error(`decisions: ${e instanceof Error ? e.message : String(e)}`); } };
|
|
53
|
+
root.command('linear-sync').description('열린 결정을 COO Linear 프로젝트에 투영하고 닫힌 결정을 동기화한다')
|
|
54
|
+
.option('--dry-run').option('--json')
|
|
55
|
+
.action(async (o: { dryRun?: boolean; json?: boolean }) => {
|
|
56
|
+
const result = await projectDecisionsToLinear({ stateDir: config.stateDir, dryRun: o.dryRun });
|
|
57
|
+
emit(result, o.json, result.reason ?? `생성 ${result.created} · 닫음 ${result.closed} · 건너뜀 ${result.skipped} · 실패 ${result.failed}${result.plan?.length ? `\n${result.plan.map(p => `${p.action} ${p.decisionId}${p.issue ? ` ${p.issue}` : ''}`).join('\n')}` : ''}`);
|
|
58
|
+
});
|
|
52
59
|
root.command('raise').description('SCQA·선택지와 권고로 결정 항목을 올린다')
|
|
53
60
|
.requiredOption('--title <text>').requiredOption('--category <category>')
|
|
54
61
|
.requiredOption('--s <text>').requiredOption('--c <text>').option('--q <text>').option('--a <text>')
|
|
55
62
|
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
56
63
|
.option('--recommend <key>').option('--why <text>').option('--skip-recommend <reason>')
|
|
57
64
|
.option('--track <track>').option('--agent <agent>').option('--session <id>')
|
|
65
|
+
.option('--resume-question <qid>').option('--run <runId>')
|
|
58
66
|
.option('--ref <url>', '참조 (반복)', repeat, [] as string[])
|
|
59
67
|
.option('--due <when>', '기한 — UTC ISO(2026-10-04T09:00:00Z) 또는 +Nh(지금부터 N시간) · 기한 2시간 전 텔레그램·디스코드로 다시 알린다')
|
|
60
68
|
.option('--json')
|
|
61
|
-
.action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; ref: string[]; json?: boolean }) => fail(() => {
|
|
69
|
+
.action((o: { due?: string; title: string; category: DecisionCategory; s: string; c: string; q?: string; a?: string; option: string[]; recommend?: string; why?: string; skipRecommend?: string; track?: DecisionTrack; agent?: string; session?: string; resumeQuestion?: string; run?: string; ref: string[]; json?: boolean }) => fail(() => {
|
|
70
|
+
if (o.run !== undefined && o.resumeQuestion === undefined) throw new Error('--run requires --resume-question');
|
|
62
71
|
if (o.skipRecommend !== undefined && (o.recommend !== undefined || o.why !== undefined)) throw new Error('choose recommendation or skip, not both');
|
|
63
72
|
if (o.skipRecommend === undefined && (!o.recommend || !o.why)) throw new Error('--recommend and --why required, or --skip-recommend <reason>');
|
|
64
73
|
const options: DecisionOption[] = o.option.map(parseOption);
|
|
65
74
|
const entry = new DecisionLedger(config).raise({ title: o.title, category: o.category, scqa: { s: o.s, c: o.c, ...(o.q ? { q: o.q } : {}), ...(o.a ? { a: o.a } : {}) }, options,
|
|
66
75
|
recommendation: o.skipRecommend !== undefined ? { skipped: true, reason: o.skipRecommend } : { option: o.recommend!, why: o.why! },
|
|
67
76
|
raisedBy: { agent: agent(o.agent), ...(o.track ? { track: o.track } : {}), ...(o.session ? { session: o.session } : {}) }, ...(o.ref.length ? { refs: o.ref } : {}),
|
|
77
|
+
...(o.resumeQuestion !== undefined ? { resume: { questionId: o.resumeQuestion, ...(o.run !== undefined ? { runId: o.run } : {}) } } : {}),
|
|
68
78
|
...(o.due ? { dueAt: dueAt(o.due) } : {}) });
|
|
69
79
|
emit(entry, o.json, `올림: ${formatDecisionRow(entry)}`);
|
|
70
80
|
}));
|
|
@@ -83,8 +93,20 @@ export function registerDecisionsCommands(program: Command, config: DecisionLedg
|
|
|
83
93
|
if (o.auto && !o.delegation?.trim()) throw new Error('--auto requires --delegation');
|
|
84
94
|
if (!o.auto && (o.delegation || o.track || o.agent)) throw new Error('delegation/track/agent require --auto');
|
|
85
95
|
const by = o.auto ? { kind: 'auto' as const, agent: agent(o.agent), delegation: o.delegation!, ...(o.track ? { track: o.track } : {}) } : { kind: 'human' as const };
|
|
86
|
-
const entry = new DecisionLedger(config).
|
|
87
|
-
|
|
96
|
+
const { entry, delivery } = new DecisionLedger(config).decideWithDelivery(id, choice, by, o.note);
|
|
97
|
+
if (delivery && !delivery.ok) {
|
|
98
|
+
// The decision is recorded; only the answer to the waiting run failed — say so and how to retry, never «success».
|
|
99
|
+
const hint = `⚠ 답 전달 실패(${delivery.questionId} · ${delivery.reason}) — 결정은 기록됐다 · 다시: elanous decisions retry-answer ${entry.id}`;
|
|
100
|
+
emit({ ...entry, delivery }, o.json, `결정: ${formatDecisionDetail(entry)}\n${hint}`);
|
|
101
|
+
process.exitCode = 3;
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
emit(delivery ? { ...entry, delivery } : entry, o.json, `결정: ${formatDecisionDetail(entry)}`);
|
|
105
|
+
}));
|
|
106
|
+
root.command('retry-answer <id>').description('기록된 결정의 대기 질문 답 전달만 재시도한다').option('--json')
|
|
107
|
+
.action((id: string, o: { json?: boolean }) => fail(() => {
|
|
108
|
+
const entry = new DecisionLedger(config).retryAnswer(id);
|
|
109
|
+
emit(entry, o.json, `답 전달: ${entry.id} → ${entry.resume?.questionId}`);
|
|
88
110
|
}));
|
|
89
111
|
root.command('add-options <id>').description('선택지가 미기재된 과거 항목에 확인된 선택지를 추가')
|
|
90
112
|
.requiredOption('--option <key=label:consequence>', '선택지 (두 번 이상)', repeat, [] as string[])
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { debug } from '../debug/log.js';
|
|
3
|
+
|
|
4
|
+
export interface DoctorBundleUploadOptions {
|
|
5
|
+
path: string;
|
|
6
|
+
files: readonly string[];
|
|
7
|
+
uploadUrl?: string;
|
|
8
|
+
confirm: (message: string) => Promise<boolean>;
|
|
9
|
+
fetch: typeof globalThis.fetch;
|
|
10
|
+
yes?: boolean;
|
|
11
|
+
/** Present the inventory before either consent or transport; JSON callers can use stderr. */
|
|
12
|
+
announce?: (contents: string) => void;
|
|
13
|
+
/** Pass the size returned by buildDoctorBundle, if available. */
|
|
14
|
+
bytes?: number;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface DoctorBundleUploadResult {
|
|
18
|
+
path: string;
|
|
19
|
+
uploaded: boolean;
|
|
20
|
+
code?: string;
|
|
21
|
+
reason?: string;
|
|
22
|
+
exitCode: number;
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The bundle is local until both an HTTPS destination and affirmative consent exist. */
|
|
27
|
+
export async function uploadDoctorBundle({ path, files, uploadUrl, confirm, fetch, yes = false, bytes, announce = console.log }: DoctorBundleUploadOptions): Promise<DoctorBundleUploadResult> {
|
|
28
|
+
const size = bytes ?? (() => { try { return statSync(path).size; } catch { return undefined; } })();
|
|
29
|
+
const skipped = (reason: string, message: string): DoctorBundleUploadResult => {
|
|
30
|
+
debug.log('doctor.bundle', 'upload-skipped', { bytes: size, reason });
|
|
31
|
+
return { path, uploaded: false, reason, exitCode: 0, message };
|
|
32
|
+
};
|
|
33
|
+
if (!uploadUrl?.trim()) return skipped('no-recipient', `수신처가 아직 정해지지 않았습니다 — 파일을 지원에 직접 보내 주세요: ${path}`);
|
|
34
|
+
let destination: URL;
|
|
35
|
+
try {
|
|
36
|
+
destination = new URL(uploadUrl.trim());
|
|
37
|
+
if (destination.protocol !== 'https:' || !destination.hostname || destination.username || destination.password) throw new Error('invalid destination');
|
|
38
|
+
} catch {
|
|
39
|
+
return skipped('invalid-recipient', `수신처가 유효한 HTTPS 주소가 아닙니다 — 파일을 지원에 직접 보내 주세요: ${path}`);
|
|
40
|
+
}
|
|
41
|
+
const contents = `내용 목록 (${size === undefined ? '크기 확인 불가' : `${size} bytes`}):\n${files.map((file) => ` ${file}`).join('\n')}`;
|
|
42
|
+
announce(contents);
|
|
43
|
+
if (!yes) {
|
|
44
|
+
let agreed = false;
|
|
45
|
+
try { agreed = await confirm('올릴까요? (y/N) '); } catch { /* An unavailable prompt is a refusal. */ }
|
|
46
|
+
if (!agreed) {
|
|
47
|
+
debug.log('doctor.bundle', 'declined', { bytes: size, reason: 'consent-declined' });
|
|
48
|
+
return { path, uploaded: false, reason: 'consent-declined', exitCode: 0, message: `업로드하지 않았습니다: ${path}` };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const failed = (reason: string): DoctorBundleUploadResult => {
|
|
52
|
+
debug.log('doctor.bundle', 'upload-failed', { bytes: size, reason });
|
|
53
|
+
return { path, uploaded: false, reason, exitCode: 1, message: `업로드 실패 (${reason}) — 파일: ${path} — 다시 시도해 주세요.` };
|
|
54
|
+
};
|
|
55
|
+
try {
|
|
56
|
+
const response = await fetch(destination.href, {
|
|
57
|
+
method: 'POST', headers: { 'Content-Type': 'application/gzip' }, body: readFileSync(path), redirect: 'manual',
|
|
58
|
+
});
|
|
59
|
+
if (!response.ok) return failed(`HTTP ${response.status}`);
|
|
60
|
+
const data: unknown = await response.json();
|
|
61
|
+
const code = data && typeof data === 'object' && !Array.isArray(data) ? (data as { code?: unknown }).code : undefined;
|
|
62
|
+
if (typeof code !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9_-]{0,127}$/.test(code)) return failed('진단 코드 응답이 올바르지 않습니다');
|
|
63
|
+
debug.log('doctor.bundle', 'uploaded', { bytes: size, reason: 'success' });
|
|
64
|
+
return { path, uploaded: true, code, exitCode: 0, message: `진단 코드: ${code} — 이 코드만 지원에 알려 주세요` };
|
|
65
|
+
} catch {
|
|
66
|
+
return failed('네트워크 또는 파일 읽기 오류');
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/cli/doctor-cli.ts
CHANGED
|
@@ -38,6 +38,7 @@ import { describeModes, formatModeLine, type DoctorMode } from './doctor-modes.j
|
|
|
38
38
|
import { formatDoctorLocalLlm, probeDoctorLocalLlm, type DoctorLocalLlm } from './doctor-local-llm.js';
|
|
39
39
|
import type { LlmInventory } from '../llm/local-manager/types.js';
|
|
40
40
|
import { buildDoctorBundle } from './doctor-bundle.js';
|
|
41
|
+
import { uploadDoctorBundle } from './doctor-bundle-upload.js';
|
|
41
42
|
|
|
42
43
|
function defaultCheckPythonEnv(): { status: 'ok' | 'fixable' | 'manual'; evidence: string; remedy?: string } {
|
|
43
44
|
const c = checkPythonEnv(false);
|
|
@@ -193,6 +194,9 @@ export interface DoctorCliDeps extends DoctorOptions, Pick<DoctorFixDeps, 'home'
|
|
|
193
194
|
adviceInteractive?: boolean;
|
|
194
195
|
/** Local-manager inventory seam; default calls getInventory(). */
|
|
195
196
|
localLlmInventory?: () => Promise<LlmInventory>;
|
|
197
|
+
/** Inject the upload transport and consent for isolated CLI tests. */
|
|
198
|
+
uploadFetch?: typeof globalThis.fetch;
|
|
199
|
+
confirmBundleUpload?: (message: string) => Promise<boolean>;
|
|
196
200
|
}
|
|
197
201
|
|
|
198
202
|
const repositoryRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../..');
|
|
@@ -1217,15 +1221,21 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
|
|
|
1217
1221
|
.option('--json', 'structured output')
|
|
1218
1222
|
.option('--credentials', 'show per-credential details in human-readable output')
|
|
1219
1223
|
.option('--bundle', 'write a redacted diagnostics archive for support')
|
|
1224
|
+
.option('--upload', 'send the bundle to diagnostics.uploadUrl after consent (requires --bundle)')
|
|
1220
1225
|
.option('--out <dir>', 'output directory for --bundle')
|
|
1221
1226
|
.option('--fix', 'show repairs; git installation asks on a TTY (other repairs require --yes)')
|
|
1222
1227
|
.option('--advise', 'ask the configured LLM to rank catalog repairs (read-only advice)')
|
|
1223
|
-
.option('--yes', 'apply planned doctor repairs (
|
|
1228
|
+
.option('--yes', 'apply planned doctor repairs (--fix), or confirm bundle upload (--bundle --upload)')
|
|
1224
1229
|
.option('--sudo', 'also run the planned sudo install lines — only where `sudo -n true` works (requires --fix --yes)')
|
|
1225
1230
|
.option('--restart', 'restart the nexus service when it runs a different version than this installed copy, then verify it (requires --fix --yes · interrupts bots, terminals and running turns)')
|
|
1226
|
-
.action(async (opts: { json?: boolean; credentials?: boolean; bundle?: boolean; out?: string; fix?: boolean; advise?: boolean; yes?: boolean; sudo?: boolean; restart?: boolean }) => {
|
|
1231
|
+
.action(async (opts: { json?: boolean; credentials?: boolean; bundle?: boolean; upload?: boolean; out?: string; fix?: boolean; advise?: boolean; yes?: boolean; sudo?: boolean; restart?: boolean }) => {
|
|
1232
|
+
if (opts.upload && !opts.bundle) {
|
|
1233
|
+
err.error('--upload requires --bundle');
|
|
1234
|
+
setExitCode(1);
|
|
1235
|
+
return;
|
|
1236
|
+
}
|
|
1227
1237
|
if (opts.bundle) {
|
|
1228
|
-
if (opts.fix || opts.advise || opts.
|
|
1238
|
+
if (opts.fix || opts.advise || opts.sudo || opts.restart || (opts.yes && !opts.upload)) {
|
|
1229
1239
|
err.error('--bundle cannot be combined with repair or advice options');
|
|
1230
1240
|
setExitCode(1);
|
|
1231
1241
|
return;
|
|
@@ -1235,7 +1245,31 @@ export function registerDoctorCommand(program: Command, deps: DoctorCliDeps = {}
|
|
|
1235
1245
|
const doctorReport = runDoctor(deps);
|
|
1236
1246
|
if (doctorReport.ok) doctorReport.localLlm = await probeDoctorLocalLlm(deps.localLlmInventory);
|
|
1237
1247
|
const result = buildDoctorBundle({ outDir: opts.out ?? process.cwd(), doctorOptions: deps, doctorReport });
|
|
1238
|
-
|
|
1248
|
+
if (opts.upload) {
|
|
1249
|
+
let uploadUrl: string | undefined;
|
|
1250
|
+
try { uploadUrl = (deps.userConfig ?? (deps.getUserConfig ?? getUserConfig)()).diagnostics?.uploadUrl; }
|
|
1251
|
+
catch {
|
|
1252
|
+
const reason = '설정을 읽을 수 없습니다';
|
|
1253
|
+
debug.log('doctor.bundle', 'upload-failed', { bytes: result.bytes, reason: 'config-read-failed' });
|
|
1254
|
+
if (opts.json) out.log(JSON.stringify({ path: result.path, uploaded: false, reason }));
|
|
1255
|
+
else err.error(`업로드 실패 (${reason}) — 파일: ${result.path} — 다시 시도해 주세요.`);
|
|
1256
|
+
setExitCode(1);
|
|
1257
|
+
return;
|
|
1258
|
+
}
|
|
1259
|
+
const uploaded = await uploadDoctorBundle({
|
|
1260
|
+
...result,
|
|
1261
|
+
uploadUrl,
|
|
1262
|
+
yes: opts.yes,
|
|
1263
|
+
announce: (contents) => { if (opts.json) err.error(contents); else out.log(contents); },
|
|
1264
|
+
confirm: deps.confirmBundleUpload ?? ((message) => confirmDoctorAdvice(message, opts.json === true)),
|
|
1265
|
+
fetch: deps.uploadFetch ?? globalThis.fetch,
|
|
1266
|
+
});
|
|
1267
|
+
if (opts.json) out.log(JSON.stringify({ path: uploaded.path, uploaded: uploaded.uploaded, ...(uploaded.code ? { code: uploaded.code } : {}), ...(uploaded.reason ? { reason: uploaded.reason } : {}) }));
|
|
1268
|
+
else out.log(uploaded.message);
|
|
1269
|
+
if (uploaded.exitCode) setExitCode(uploaded.exitCode);
|
|
1270
|
+
} else {
|
|
1271
|
+
out.log(opts.json ? JSON.stringify(result) : [`Diagnostics: ${result.path} (${result.bytes} bytes)`, 'Contents:', ...result.files.map((file) => ` ${file}`)].join('\n'));
|
|
1272
|
+
}
|
|
1239
1273
|
} catch {
|
|
1240
1274
|
err.error('Could not create diagnostics archive.');
|
|
1241
1275
|
setExitCode(1);
|
package/src/cli/ops-cli.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import * as ui from '../ui.js';
|
|
3
3
|
import { writeStdoutJson } from './stdout-json.js';
|
|
4
|
+
import type { OpsNowResult } from '../ops-now/ops-now-refresh.js';
|
|
5
|
+
|
|
6
|
+
export function opsNowExitCode(outcome: OpsNowResult['outcome']): number {
|
|
7
|
+
return outcome === 'channel-unreadable' || outcome === 'summarize-failed' ? 2 : 0;
|
|
8
|
+
}
|
|
4
9
|
|
|
5
10
|
export function registerOpsCommands(program: Command): void {
|
|
6
11
|
const opsCmd = program.command('ops')
|
|
@@ -173,6 +178,21 @@ async function runOpsBuild(buildId: string | undefined, opts: OpsBuildOpts): Pro
|
|
|
173
178
|
process.exit(0);
|
|
174
179
|
}
|
|
175
180
|
|
|
181
|
+
opsCmd.command('now-refresh').description('채널 📌안내로 ops-now 고정 기억 갱신(1회)')
|
|
182
|
+
.option('--once', '한 번 실행(기본)')
|
|
183
|
+
.option('--dry-run', '기억을 쓰지 않고 새 본문 출력')
|
|
184
|
+
.option('--json', '결과를 JSON으로 출력')
|
|
185
|
+
.action(async (o: { dryRun?: boolean; json?: boolean }) => {
|
|
186
|
+
const { refreshOpsNow } = await import('../ops-now/ops-now-refresh.js');
|
|
187
|
+
const result = await refreshOpsNow({ dryRun: o.dryRun === true });
|
|
188
|
+
if (o.json) await writeStdoutJson(JSON.stringify(result, null, 2) + '\n');
|
|
189
|
+
else {
|
|
190
|
+
console.log(`ops-now: ${result.outcome} · 안내 ${result.notices}개`);
|
|
191
|
+
if (o.dryRun && result.body !== undefined) console.log(result.body);
|
|
192
|
+
}
|
|
193
|
+
process.exitCode = opsNowExitCode(result.outcome);
|
|
194
|
+
});
|
|
195
|
+
|
|
176
196
|
opsCmd.command('status').description('현재 상태 종합(미션·태스크·루프·오케스트레이션·스케줄) · --all-instances 로 fleet 전체 종합(미션/태스크/loops/스케줄/오케스트레이션) · -r/--remote 로 원격 GET 조립')
|
|
177
197
|
.option('--json').option('--all-instances', '등록 인스턴스 전체 종합(미션/태스크/loops/스케줄/오케스트레이션 · fleet · read-only · §10)')
|
|
178
198
|
.option('--include-test', '연합에 격리 test 인스턴스도 포함(기본 제외)')
|
package/src/cli/pod-cli.ts
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
// 산출 경로를 한 줄로 알리고 명령의 종료 코드로 끝난다. `--deadline` 생략 시 기존 Pod Job 상한.
|
|
3
3
|
|
|
4
4
|
import type { Command } from 'commander';
|
|
5
|
+
import { debug } from '../debug/log.js';
|
|
6
|
+
import { getUserConfig } from '../user-config.js';
|
|
7
|
+
import { listCodexAccountsInStore } from '../oauth/codex-account-store.js';
|
|
8
|
+
import { parsePodPool, resolvePodPoolSpec, type PoolKubectl } from '../task-orchestrator/surfaces/pod-pool.js';
|
|
9
|
+
import { leaseKubectl, measurePoolLease, recommendConcurrency, type PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
5
10
|
import { POD_COMMAND_DEADLINE_SECONDS, runPodCommand, type PodCommandResult, type RunPodCommandOptions } from '../task-orchestrator/surfaces/pod-command-job.js';
|
|
6
11
|
|
|
7
12
|
export interface PodCliIo {
|
|
@@ -13,6 +18,10 @@ export interface PodCliIo {
|
|
|
13
18
|
export interface PodCliDeps {
|
|
14
19
|
io?: PodCliIo;
|
|
15
20
|
run?: (options: RunPodCommandOptions) => Promise<PodCommandResult>;
|
|
21
|
+
kubectl?: PoolKubectl;
|
|
22
|
+
accounts?: () => number;
|
|
23
|
+
perAccount?: () => number;
|
|
24
|
+
poolSpec?: (explicit?: string) => string | null;
|
|
16
25
|
}
|
|
17
26
|
|
|
18
27
|
const HELP = `elanous pod run [--pool <스펙>] [--skill <이름,…>] [--llm grok] [--clone] [--deadline <초>] -- <명령…>
|
|
@@ -27,6 +36,46 @@ export function registerPodCommands(program: Command, deps: PodCliDeps = {}): vo
|
|
|
27
36
|
};
|
|
28
37
|
const run = deps.run ?? ((options: RunPodCommandOptions) => runPodCommand(options));
|
|
29
38
|
const pod = program.command('pod').description('Pod 명령 Job — 하니스 없이 명령 하나를 돌리고 산출을 돌려받는다');
|
|
39
|
+
pod.command('lease').description('Pod 풀의 읽기 전용 동시 실행 권장')
|
|
40
|
+
.command('status').description('지금 추가로 쏠 수 있는 Pod 골과 제한 근거')
|
|
41
|
+
.option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한] 쉼표')
|
|
42
|
+
.option('--json', '표 대신 동일 측정값을 JSON 으로')
|
|
43
|
+
.action((opts: { pool?: string; json?: boolean }) => {
|
|
44
|
+
try {
|
|
45
|
+
const kubectl = deps.kubectl ?? leaseKubectl;
|
|
46
|
+
const spec = (deps.poolSpec ?? ((explicit?: string) => resolvePodPoolSpec(explicit)))(opts.pool);
|
|
47
|
+
// No pool configured: mirror the Pod launch fallback to kubectl's current context.
|
|
48
|
+
const current = spec ? null : kubectl(['config', 'current-context']);
|
|
49
|
+
const context = spec ?? (current?.status === 0 ? current.stdout.trim() : '');
|
|
50
|
+
const members = context ? parsePodPool(context) : [];
|
|
51
|
+
const measure: PoolLeaseMeasure = members.length ? measurePoolLease(members, { kubectl }) : { members: [] };
|
|
52
|
+
let accounts: number | null = null;
|
|
53
|
+
let accountReason: string | null = null;
|
|
54
|
+
try { accounts = (deps.accounts ?? (() => listCodexAccountsInStore().length))(); }
|
|
55
|
+
catch { accountReason = '측정 불가: accounts'; }
|
|
56
|
+
const perAccount = (deps.perAccount ?? (() => getUserConfig().pod?.lease?.perAccount ?? 4))();
|
|
57
|
+
const decision = recommendConcurrency(measure, { capacity: members.reduce((sum, m) => sum + m.capacity, 0), accounts: accounts ?? -1, perAccount });
|
|
58
|
+
if (!context) { decision.recommended = null; decision.limitedBy = null; decision.reason = '측정 불가: cluster (current context)'; }
|
|
59
|
+
const result = { pool: spec, ...measure, accounts, perAccount, ...decision };
|
|
60
|
+
debug.log('pod.lease', 'status', { pool: spec ? members.map((m) => ({ capacity: m.capacity })) : null, running: decision.running, pending: decision.pending, recommended: decision.recommended, limitedBy: decision.limitedBy });
|
|
61
|
+
if (opts.json) io.log(JSON.stringify(result));
|
|
62
|
+
else {
|
|
63
|
+
io.log('컨텍스트 | 상한 | Running | Pending | 메모리(상한 합/할당 가능) | CPU(할당 가능)');
|
|
64
|
+
for (const m of measure.members) {
|
|
65
|
+
const mem = (v: number | null) => v === null ? '?' : `${(v / 1024 ** 3).toFixed(1)}Gi`;
|
|
66
|
+
io.log(`${m.context} | ${m.capacity} | ${m.running ?? '?'} | ${m.pending ?? '?'} | ${mem(m.memoryLimitBytes)}/${mem(m.allocatableMemoryBytes)} | ${m.allocatableCpuMillicores === null ? '?' : m.allocatableCpuMillicores / 1000}${m.reason ? ` (${m.reason})` : ''}`);
|
|
67
|
+
}
|
|
68
|
+
io.log(`권장 지금 ${decision.recommended ?? '?'} 개 더 (limitedBy=${decision.limitedBy ?? 'unknown'})${decision.reason ? ` · ${decision.reason}` : ''}`);
|
|
69
|
+
io.log(`capacity: ${decision.capacitySlots ?? '?'} 칸 (상한 ${members.reduce((sum, m) => sum + m.capacity, 0)} − Running ${decision.running ?? '?'} − Pending ${decision.pending ?? '?'})`);
|
|
70
|
+
io.log(`memory: ${decision.memorySlots ?? '?'} 칸 (할당 가능 − Running 하니스 상한 합; 골당 16Gi · 모든 네임스페이스의 노드별 기존 예약을 반영해 배치 가능 ${decision.placeableSlots ?? '?'} 칸)`);
|
|
71
|
+
io.log(`accounts (관측만 · 상한 아님): ${decision.accountSlots ?? '?'} 칸 (${accounts ?? '?'} × ${perAccount} − Running ${decision.running ?? '?'})${accountReason ? ` · ${accountReason}` : ''}`);
|
|
72
|
+
}
|
|
73
|
+
io.exit(0);
|
|
74
|
+
} catch (err) {
|
|
75
|
+
io.error(err instanceof Error ? err.message : String(err));
|
|
76
|
+
io.exit(1);
|
|
77
|
+
}
|
|
78
|
+
});
|
|
30
79
|
pod.command('run')
|
|
31
80
|
.description(HELP)
|
|
32
81
|
.option('--pool <spec>', '풀 스펙 — 컨텍스트[@ssh호스트][:상한] 쉼표')
|
package/src/cli/pr-cli.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { runGate as runChangedTypecheckGate } from '../../scripts/ci-typecheck-c
|
|
|
13
13
|
import { runIsolationHardcodeGate } from '../../scripts/ci-isolation-hardcode-gate.js';
|
|
14
14
|
import { androidFilesIn, runAndroidUnitTestGate } from '../../scripts/ci-android-unit-tests.js';
|
|
15
15
|
import { iosFilesIn, runIosUnitTestGate } from '../../scripts/ci-ios-unit-tests.js';
|
|
16
|
+
import { runPwaBuildGate } from '../../scripts/ci-pwa-build-gate.js';
|
|
16
17
|
import { runMockModuleRestoreGate } from '../../scripts/ci-mock-module-restore-gate.js';
|
|
17
18
|
import { runModelHardcodeGate } from '../../scripts/ci-model-hardcode-gate.js';
|
|
18
19
|
import { runDaemonPortGate } from '../../scripts/ci-daemon-port-gate.js';
|
|
@@ -51,6 +52,7 @@ import {
|
|
|
51
52
|
unionPaths,
|
|
52
53
|
} from './pr-granularity.js';
|
|
53
54
|
import { branchLineageSlug, findSiblingPrs } from './pr-lineage.js';
|
|
55
|
+
import { parseReleaseNoteSection } from '../release-loop/release-note.js';
|
|
54
56
|
|
|
55
57
|
export { branchLineageSlug, findSiblingPrs };
|
|
56
58
|
|
|
@@ -114,6 +116,7 @@ export interface PrLandDeps {
|
|
|
114
116
|
runAndroidGate?: (out: { log: (message: string) => void; error: (message: string) => void }, changedFiles: readonly string[]) => boolean;
|
|
115
117
|
/** iOS 시험 게이트 심(시험 주입용). */
|
|
116
118
|
runIosGate?: (out: { log: (message: string) => void; error: (message: string) => void }, changedFiles: readonly string[]) => boolean;
|
|
119
|
+
runPwaGate?: (out: { log: (message: string) => void; error: (message: string) => void }, changedFiles: readonly string[]) => boolean;
|
|
117
120
|
isInteractive?: () => boolean;
|
|
118
121
|
/** Clock for the recent-landings window (test seam · default Date.now). */
|
|
119
122
|
now?: () => number;
|
|
@@ -406,13 +409,14 @@ function gateVerdict(
|
|
|
406
409
|
label: string,
|
|
407
410
|
gate: (out: { log: (message: string) => void; error: (message: string) => void }) => boolean,
|
|
408
411
|
out: { log: (message: string) => void; error: (message: string) => void },
|
|
412
|
+
failClosed = false,
|
|
409
413
|
): { ok: boolean; measured: boolean } {
|
|
410
414
|
try {
|
|
411
415
|
return { ok: gate(out), measured: true };
|
|
412
416
|
} catch (error) {
|
|
413
417
|
out.error(`⚠ ${label}: 게이트가 «못 쟀다» — ${error instanceof Error ? error.message : String(error)}`);
|
|
414
|
-
out.error(`⚠ ${label}: 「위반 없음」이 아니라 「검사 못 함」이다. 이 착지는 통과시키되 게이트를
|
|
415
|
-
return { ok:
|
|
418
|
+
out.error(`⚠ ${label}: 「위반 없음」이 아니라 「검사 못 함」이다. ${failClosed ? '착지를 막는다.' : '이 착지는 통과시키되 게이트를 고쳐라.'}`);
|
|
419
|
+
return { ok: !failClosed, measured: false };
|
|
416
420
|
}
|
|
417
421
|
}
|
|
418
422
|
|
|
@@ -426,6 +430,21 @@ function record(step: string, ok: boolean, extra: Record<string, unknown> = {}):
|
|
|
426
430
|
debug.log('pr.land', 'step', { step, ok, ...extra });
|
|
427
431
|
}
|
|
428
432
|
|
|
433
|
+
export function nextMdWarning(body: string, changedFiles: readonly string[], out: { log: (message: string) => void }): void {
|
|
434
|
+
const section = body.split(/^## 릴리스 노트[ \t]*\r?$/m).at(-1)!;
|
|
435
|
+
const parsed = parseReleaseNoteSection(body);
|
|
436
|
+
const hasNote = parsed.fragment !== undefined || parsed.problems.length > 0;
|
|
437
|
+
const kind = parsed.fragment?.kind ?? (hasNote
|
|
438
|
+
? /^[ \t]*- 종류:[ \t]*(feat|fix|security|internal)[ \t]*\r?$/m.exec(section.split(/^#{1,6}[ \t]+/m)[0]!)?.[1]
|
|
439
|
+
: undefined);
|
|
440
|
+
const reason = hasNote
|
|
441
|
+
? (kind === 'feat' || kind === 'fix' || kind === 'security' ? `release-note:${kind}` : undefined)
|
|
442
|
+
: (changedFiles.some((file) => /^(?:apps\/pwa\/src\/|src\/cli\/|src\/dashboard\/)/.test(file)) ? 'user-facing-path' : undefined);
|
|
443
|
+
const warn = !!reason && !changedFiles.includes('release/next.md');
|
|
444
|
+
record('next-md', true, { warnOnly: true, reason: reason ?? (hasNote ? 'non-user-facing-note' : 'no-user-facing-path') });
|
|
445
|
+
if (warn) out.log('⚠ next-md: 사용자에게 보이는 변경인데 release/next.md 에 줄이 없습니다 — 공개 노트에서 빠집니다. 「- <kind> — <영어 한 문장>. Documentation: … Target: next.」 한 줄을 더하십시오.');
|
|
446
|
+
}
|
|
447
|
+
|
|
429
448
|
type PrRemoteCapability = 'github' | 'local-path' | 'no-remote' | 'other';
|
|
430
449
|
|
|
431
450
|
function isLocalRemote(raw: string): boolean {
|
|
@@ -1323,6 +1342,7 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1323
1342
|
}
|
|
1324
1343
|
|
|
1325
1344
|
const changedPaths = currentChangePaths(run, cwd, staged, base, out);
|
|
1345
|
+
nextMdWarning(body ?? '', changedPaths, out);
|
|
1326
1346
|
publicLeakWarning(changedPaths, deps.runPublicLeakGate ?? runPrLandPublicLeakGate, out);
|
|
1327
1347
|
if (!exportLeakBlock(changedPaths, () => (deps.runExportLeakCheck ?? runPrLandExportLeakCheck)(changedPaths ?? [], cwd), opts.allowPublicLeak === true, out)) return 1;
|
|
1328
1348
|
if (publicDocPaths(changedPaths, () => true).length > 0) {
|
|
@@ -1371,6 +1391,15 @@ export async function runPrLand(opts: PrLandOpts = {}, deps: PrLandDeps = {}): P
|
|
|
1371
1391
|
out.log('✓ ios-gate: scripts/ci-ios-unit-tests.ts PASS — iOS 순수-로직 시험이 «실제로 돌았고» 실패 0.');
|
|
1372
1392
|
}
|
|
1373
1393
|
|
|
1394
|
+
// PWA 는 bun test/tsc 밖의 정적 번들이다. 측정 불가도 착지 불가로 판정한다.
|
|
1395
|
+
const pwaGate = gateVerdict('pwa-gate', deps.runPwaGate
|
|
1396
|
+
? (o) => deps.runPwaGate!(o, changedPaths)
|
|
1397
|
+
: (o) => runPwaBuildGate({ args: ['--changed-files', ...changedPaths], cwd, log: o.log, error: o.error }) === 0, out, true);
|
|
1398
|
+
record('pwa-gate', pwaGate.ok && pwaGate.measured, { measured: pwaGate.measured });
|
|
1399
|
+
if (!pwaGate.ok || !pwaGate.measured) {
|
|
1400
|
+
out.error('✗ pwa-gate: scripts/ci-pwa-build-gate.ts blocked pr land — PWA 빌드 실패 또는 측정 불가.');
|
|
1401
|
+
return 1;
|
|
1402
|
+
}
|
|
1374
1403
|
|
|
1375
1404
|
const overlapFound = emitLandingOverlapAdvisory(run, cwd, base, changedPaths, out);
|
|
1376
1405
|
const siblingFound = emitSiblingPrAdvisory(
|