elanous 0.2.8 → 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-d4b6df905f185055.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-186fc6952f027acc.js → page-f8116559c7424da7.js} +1 -1
- 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-cbbe03c0da7b081a.js → page-54e1f966ab60a4dc.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +1 -0
- 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-c0c0d19d51342d3e.js → page-b31610deda76337b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/{page-2e002ad8faa43291.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-7464c1987dd76e6d.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-d8ab4d1489223459.js → page-4fddc90b23feb215.js} +1 -1
- 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-26102fd7350f171e.js +1 -0
- 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-139ba5c367bbf60a.js +1 -0
- 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 +30 -29
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +30 -29
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +30 -29
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +27 -26
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +27 -26
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +27 -26
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +30 -29
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +30 -29
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +30 -29
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +30 -29
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +30 -29
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +30 -29
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +27 -26
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +30 -29
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +30 -29
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +27 -26
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +27 -26
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +30 -29
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +30 -29
- package/apps/pwa/out/ops/checklist/index.html +1 -0
- package/apps/pwa/out/ops/checklist/index.txt +30 -0
- package/apps/pwa/out/ops/release/index.html +1 -0
- package/apps/pwa/out/ops/release/index.txt +30 -0
- 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 +28 -27
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +30 -29
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +27 -26
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +30 -29
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +30 -29
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +30 -29
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +30 -29
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +30 -29
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +27 -26
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +30 -29
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +30 -29
- 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 +30 -29
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +30 -29
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +30 -29
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +30 -29
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +30 -29
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +30 -29
- package/graphs/field/field-feed.yaml +2 -0
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +3 -1
- package/graphs/test-diet/nightly-audit.yaml +18 -0
- package/graphs/test-diet/recipes.yaml +3 -0
- 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 +14 -7
- 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 +31 -2
- package/scripts/release-loop/export-check-node.ts +85 -0
- package/scripts/release-loop/gate-node.ts +18 -4
- package/scripts/release-loop/node-verdict.ts +8 -1
- 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 +31 -1
- package/scripts/test-diet/node.ts +134 -17
- 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.fixture.ts +19 -0
- package/src/auth/setup-link-tokens.ts +164 -0
- 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/budget/unified-usage.ts +75 -0
- package/src/channel-bot-token.ts +37 -10
- package/src/chat/ctrl-c-in-input.ts +14 -0
- package/src/chat/index.ts +61 -9
- 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/op-proxy-sync.ts +83 -0
- package/src/cli/ops-cli.ts +20 -0
- package/src/cli/pod-cli.ts +49 -0
- package/src/cli/pr-cli.ts +37 -4
- package/src/cli/pwa-stop.ts +163 -13
- package/src/cli/pwa-test.ts +3 -0
- package/src/cli/release-cli.ts +98 -15
- 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/dashboard-status-lines.ts +45 -0
- package/src/dashboard/first-screen-band.ts +35 -0
- package/src/dashboard/index.ts +128 -34
- package/src/dashboard/provider-slash-runtime.ts +28 -11
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +17 -0
- package/src/dashboard/slash-runtime/help-from-registry.ts +43 -28
- package/src/dashboard/turn-stream-formatter.ts +1 -1
- package/src/decisions/decision-cards.ts +50 -2
- 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 +172 -41
- 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 +100 -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 +73 -1
- package/src/nexus/api/meta-api.ts +38 -0
- package/src/nexus/api/operator.ts +61 -0
- package/src/nexus/api/ops-api.ts +148 -0
- package/src/nexus/api/ops-seats-sources.ts +95 -0
- package/src/nexus/api/ops-seats.ts +124 -0
- package/src/nexus/api/public-routes.ts +14 -1
- package/src/nexus/api/seat-requests.ts +88 -8
- package/src/nexus/api/sessions-store.ts +63 -0
- package/src/nexus/api/setup-claim.ts +21 -0
- package/src/nexus/api/setup-codex-login.ts +160 -0
- package/src/nexus/api/usage-api.ts +102 -0
- package/src/nexus/config/secrets/file-backend.ts +56 -18
- package/src/nexus/index.ts +45 -4
- package/src/onboarding/first-run-doctor.ts +54 -0
- 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 +76 -61
- package/src/release-loop/feature-store.ts +460 -0
- package/src/release-loop/manifest.ts +59 -5
- package/src/release-loop/release-schedule.ts +53 -0
- package/src/repl/index.ts +7 -0
- package/src/seat-dispatch/ceo-commands.ts +98 -0
- 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/skills/tools/usage-slash.ts +8 -7
- 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-commands.ts +16 -0
- package/src/telegram-report.ts +15 -9
- package/src/telegram-run.ts +90 -13
- package/src/telegram.ts +29 -8
- package/src/thinking-line.ts +28 -20
- package/src/tool-runtime/types.ts +4 -0
- package/src/tui/chat/execution-badge.ts +1 -1
- package/src/tui-client/dashboard-session.ts +2 -0
- package/src/user-config.ts +118 -11
- package/src/ux-sim/turn-states.ts +1 -1
- package/src/version/packed-revision.json +1 -1
- package/src/web-push/notify-exec-request.ts +32 -0
- 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/4836-3084c58c1725a40b.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/8894-7317998f6cb97d8c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.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-9aa47b0bc6d570ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-f4d2ecbbae6088d6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-2053e49930c82748.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-a70ba27c33003dd0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-18166f885d831d3e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1c9d6cfe68b121c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-c108ff1fdb665c97.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-147a328753d983e2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-799395a9df7d95aa.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-6a8158b71d1fe9d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-9567560270857531.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-e7ed3d010153d818.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-b672e02e4bec8deb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-1ba4a5d857276334.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-9db2226f38c542fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-70ee65f5f7a151dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-0bb8e43203efb23b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-092e1bcf27d751cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-eb52580c4b75b905.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-e573437b0a1c728f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-294300625e49e4d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-085847b088675978.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-9fe9d38c36999eb4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-6141f67f531d8b6a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-295a02b1026b8255.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-1eb9a0e1e054209d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-82efa177a30f3741.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d0be51cec4f62606.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-2e367075ffdbc062.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-647ed2b66c1d49f1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-ba058c56e38f2792.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +0 -1
- package/apps/pwa/out/_next/static/css/5ef72a618a8994f3.css +0 -1
- package/apps/pwa/out/_next/static/lywc8m_HDzH9MKSDQhyEj/_buildManifest.js +0 -1
- package/src/dashboard/turn-typeahead-echo.ts +0 -23
- /package/apps/pwa/out/_next/static/{lywc8m_HDzH9MKSDQhyEj → 2DsiDfDCks44g1cPUMhiW}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// PWA build gate (GATE-PWA · 10-02 MAT1b near-miss: a PWA-bundled src module gained a TUI import and every gate passed).
|
|
3
|
+
// The PWA is a static export outside `bun test`/tsc, so a change that reaches its bundle graph is built here.
|
|
4
|
+
// Reach is decided by the TypeScript program of apps/pwa/tsconfig.json (aliases, extends, type imports included);
|
|
5
|
+
// anything the program cannot decide is treated as reaching the PWA — «unsure» builds, it never skips.
|
|
6
|
+
import { spawnSync } from 'node:child_process';
|
|
7
|
+
import { existsSync, readdirSync } from 'node:fs';
|
|
8
|
+
import { basename, dirname, extname, join, relative, resolve, sep } from 'node:path';
|
|
9
|
+
import ts from 'typescript';
|
|
10
|
+
import { debug } from '../src/debug/log.js';
|
|
11
|
+
import { checkPwaBuildDeps } from '../src/cli/pwa-build.js';
|
|
12
|
+
import { prepareDeterministicChildEnvironment } from './lib/deterministic-env.js';
|
|
13
|
+
import { parseChangedFiles } from './ci-ios-unit-tests.js';
|
|
14
|
+
|
|
15
|
+
const ROOT = join(import.meta.dir, '..');
|
|
16
|
+
const PWA_PREFIX = 'apps/pwa/';
|
|
17
|
+
const PWA_IGNORED = /^apps\/pwa\/(?:node_modules|\.next|out)\//;
|
|
18
|
+
const TS_SOURCE = /\.(?:tsx?|mts|cts)$/;
|
|
19
|
+
const CODE_OR_ASSET = /\.(?:[cm]?[jt]sx?|json|css|scss|svg|png|jpe?g|webp|woff2?)$/;
|
|
20
|
+
|
|
21
|
+
export type BuildResult = { status: number | null; output: string; error?: Error };
|
|
22
|
+
export type PwaGraph = { files: Set<string>; uncertain: string | null };
|
|
23
|
+
export type PwaGateIo = {
|
|
24
|
+
args?: readonly string[];
|
|
25
|
+
cwd?: string;
|
|
26
|
+
log?: (line: string) => void;
|
|
27
|
+
error?: (line: string) => void;
|
|
28
|
+
/** Repo-relative changed files when `--changed-files` is absent (test seam · default git diff vs origin/main). */
|
|
29
|
+
changedFiles?: (root: string) => string[] | null;
|
|
30
|
+
graph?: (root: string) => PwaGraph;
|
|
31
|
+
runBuild?: (root: string) => BuildResult;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
function defaultBuild(root: string): BuildResult {
|
|
35
|
+
const pwa = join(root, 'apps/pwa');
|
|
36
|
+
let deps = checkPwaBuildDeps(pwa);
|
|
37
|
+
if (!deps.ok) {
|
|
38
|
+
// Pod worktrees and fresh harvest trees often have no apps/pwa/node_modules — install once from the lockfile, then re-check.
|
|
39
|
+
const install = spawnSync('bun', ['install', '--frozen-lockfile'], { cwd: pwa, encoding: 'utf8', timeout: 300_000, maxBuffer: 16 * 1024 * 1024 });
|
|
40
|
+
try { debug.log('gate.pwa', 'deps-install', { status: install.status }); } catch { /* fail-soft */ }
|
|
41
|
+
deps = checkPwaBuildDeps(pwa);
|
|
42
|
+
if (!deps.ok) return { status: null, output: `apps/pwa/node_modules incomplete after install (rc=${install.status}): ${deps.missing.slice(0, 3).join(', ')}` };
|
|
43
|
+
}
|
|
44
|
+
const isolated = prepareDeterministicChildEnvironment('elanous-pwa-build-gate-');
|
|
45
|
+
try {
|
|
46
|
+
// Same build `nexus build` runs (runPwaBuild → bun run build), with HOME/state/config confined to a temp child env.
|
|
47
|
+
const result = spawnSync('bun', ['run', 'build'], { cwd: pwa, env: isolated.env, encoding: 'utf8', timeout: 300_000, maxBuffer: 32 * 1024 * 1024 });
|
|
48
|
+
return { status: result.status, output: `${result.stdout ?? ''}\n${result.stderr ?? ''}`, error: result.error };
|
|
49
|
+
} finally {
|
|
50
|
+
isolated.cleanup();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function defaultChangedFiles(root: string): string[] | null {
|
|
55
|
+
const r = spawnSync('git', ['diff', '--name-only', 'origin/main...HEAD'], { cwd: root, encoding: 'utf8' });
|
|
56
|
+
if (r.status !== 0) return null;
|
|
57
|
+
return r.stdout.split('\n').map((l) => l.trim()).filter(Boolean);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Dynamic import()/require() whose argument is not a string literal — the program cannot follow it. */
|
|
61
|
+
function opaqueLoad(sf: ts.SourceFile): boolean {
|
|
62
|
+
let found = false;
|
|
63
|
+
const visit = (node: ts.Node): void => {
|
|
64
|
+
if (found) return;
|
|
65
|
+
if (ts.isCallExpression(node) && (node.expression.kind === ts.SyntaxKind.ImportKeyword
|
|
66
|
+
|| (ts.isIdentifier(node.expression) && node.expression.text === 'require'))) {
|
|
67
|
+
const arg = node.arguments[0];
|
|
68
|
+
if (!arg || !(ts.isStringLiteral(arg) || ts.isNoSubstitutionTemplateLiteral(arg))) { found = true; return; }
|
|
69
|
+
}
|
|
70
|
+
ts.forEachChild(node, visit);
|
|
71
|
+
};
|
|
72
|
+
visit(sf);
|
|
73
|
+
return found;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Every repo file the PWA program loads, plus same-stem siblings (a `.d.ts` may stand in for the `.js` the bundler reads). */
|
|
77
|
+
export function pwaGraph(root: string): PwaGraph {
|
|
78
|
+
const configPath = join(root, 'apps/pwa/tsconfig.json');
|
|
79
|
+
const read = ts.readConfigFile(configPath, ts.sys.readFile);
|
|
80
|
+
if (read.error) return { files: new Set(), uncertain: `tsconfig unreadable: ${ts.flattenDiagnosticMessageText(read.error.messageText, ' ')}` };
|
|
81
|
+
const parsed = ts.parseJsonConfigFileContent(read.config, ts.sys, dirname(configPath));
|
|
82
|
+
if (parsed.errors.some((d) => d.category === ts.DiagnosticCategory.Error)) {
|
|
83
|
+
return { files: new Set(), uncertain: `tsconfig invalid: ${ts.flattenDiagnosticMessageText(parsed.errors[0]!.messageText, ' ')}` };
|
|
84
|
+
}
|
|
85
|
+
const program = ts.createProgram({ rootNames: parsed.fileNames, options: { ...parsed.options, noEmit: true } });
|
|
86
|
+
const files = new Set<string>();
|
|
87
|
+
const absRoot = resolve(root) + sep;
|
|
88
|
+
for (const sf of program.getSourceFiles()) {
|
|
89
|
+
const abs = resolve(sf.fileName);
|
|
90
|
+
if (!abs.startsWith(absRoot) || abs.includes(`${sep}node_modules${sep}`)) continue;
|
|
91
|
+
const rel = relative(root, abs).split(sep).join('/');
|
|
92
|
+
files.add(rel);
|
|
93
|
+
// A non-literal import()/require() is a bundler «context»: anything beside it may be loaded — take the whole directory.
|
|
94
|
+
if (opaqueLoad(sf)) {
|
|
95
|
+
const dir = dirname(abs);
|
|
96
|
+
try {
|
|
97
|
+
for (const name of readdirSync(dir)) if (/\.(?:[cm]?[jt]sx?|json)$/.test(name)) files.add(relative(root, join(dir, name)).split(sep).join('/'));
|
|
98
|
+
} catch { return { files, uncertain: `cannot list ${relative(root, dir)} beside a non-literal import` }; }
|
|
99
|
+
}
|
|
100
|
+
const stem = basename(rel).replace(/\.d\.ts$|\.[^.]+$/, '');
|
|
101
|
+
for (const ext of ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.d.ts', '.json', '.css']) {
|
|
102
|
+
const sibling = join(dirname(rel), stem + ext).split(sep).join('/');
|
|
103
|
+
if (existsSync(join(root, sibling))) files.add(sibling);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return { files, uncertain: null };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export type PwaReach = { reaches: boolean; reason: string; hits: string[] };
|
|
110
|
+
|
|
111
|
+
/** Decide whether a change set reaches the PWA build. Unknowns reach. */
|
|
112
|
+
export function pwaReach(changed: readonly string[], graph: () => PwaGraph): PwaReach {
|
|
113
|
+
const pwaHits = changed.filter((f) => f.startsWith(PWA_PREFIX) && !PWA_IGNORED.test(f));
|
|
114
|
+
if (pwaHits.length) return { reaches: true, reason: `apps/pwa 변경 ${pwaHits.length}개`, hits: pwaHits };
|
|
115
|
+
// Any repo file the PWA program loads counts (src/, scripts/, shared roots); non-code files under src/ build conservatively.
|
|
116
|
+
const candidates = changed.filter((f) => CODE_OR_ASSET.test(f));
|
|
117
|
+
if (!candidates.length) return { reaches: false, reason: `변경 ${changed.length}개 중 코드·자산 파일 0개`, hits: [] };
|
|
118
|
+
let g: PwaGraph;
|
|
119
|
+
try { g = graph(); } catch (err) { return { reaches: true, reason: `PWA 그래프를 못 읽었다(${err instanceof Error ? err.message : String(err)}) — 닿는 것으로 본다`, hits: candidates }; }
|
|
120
|
+
if (g.uncertain) return { reaches: true, reason: `PWA 그래프 판정 불가(${g.uncertain}) — 닿는 것으로 본다`, hits: candidates };
|
|
121
|
+
const hits = candidates.filter((f) => g.files.has(f) || (f.startsWith('src/') && !TS_SOURCE.test(f)));
|
|
122
|
+
if (hits.length) return { reaches: true, reason: `PWA 가 닿는 파일 ${hits.length}개`, hits };
|
|
123
|
+
return { reaches: false, reason: `코드 변경 ${candidates.length}개 — PWA 그래프(${g.files.size}개) 밖`, hits: [] };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export function runPwaBuildGate(io: PwaGateIo = {}): number {
|
|
127
|
+
const log = io.log ?? console.log;
|
|
128
|
+
const error = io.error ?? console.error;
|
|
129
|
+
const root = io.cwd ?? ROOT;
|
|
130
|
+
if (!io.graph && !existsSync(join(root, 'apps/pwa/package.json'))) {
|
|
131
|
+
log('[pwa-gate] 해당 없음 — 이 트리에 apps/pwa 가 없다.');
|
|
132
|
+
return 0;
|
|
133
|
+
}
|
|
134
|
+
const changed = parseChangedFiles(io.args ?? process.argv.slice(2)) ?? (io.changedFiles ?? defaultChangedFiles)(root);
|
|
135
|
+
if (changed === null) {
|
|
136
|
+
error('[pwa-gate] 측정 불가 — 변경 파일 목록을 못 얻었다(git diff 실패).');
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
const reach = pwaReach(changed, () => (io.graph ?? pwaGraph)(root));
|
|
140
|
+
try { debug.log('gate.pwa', reach.reaches ? 'reach' : 'skip', { changed: changed.length, hits: reach.hits.slice(0, 20), reason: reach.reason }); } catch { /* fail-soft */ }
|
|
141
|
+
if (!reach.reaches) {
|
|
142
|
+
log(`[pwa-gate] 해당 없음 — ${reach.reason}.`);
|
|
143
|
+
return 0;
|
|
144
|
+
}
|
|
145
|
+
log(`[pwa-gate] 대상 — ${reach.reason}${reach.hits.length ? ` (${reach.hits.slice(0, 5).join(', ')}${reach.hits.length > 5 ? ' …' : ''})` : ''} — PWA 빌드를 돌린다.`);
|
|
146
|
+
const started = Date.now();
|
|
147
|
+
const result = (io.runBuild ?? defaultBuild)(root);
|
|
148
|
+
const seconds = Math.round((Date.now() - started) / 1000);
|
|
149
|
+
try { debug.log('gate.pwa', 'build', { status: result.status, seconds }); } catch { /* fail-soft */ }
|
|
150
|
+
if (result.status === 0) {
|
|
151
|
+
log(`[pwa-gate] PASS — PWA 빌드 성공(${seconds}s).`);
|
|
152
|
+
return 0;
|
|
153
|
+
}
|
|
154
|
+
const tail = result.output.trim().split('\n').slice(-15).join('\n');
|
|
155
|
+
if (result.status === null) error(`[pwa-gate] 측정 불가 — 빌드를 못 돌렸다: ${result.error?.message ?? tail.split('\n').pop() ?? 'unknown'}`);
|
|
156
|
+
else error(`[pwa-gate] FAIL — PWA 빌드 실패(rc=${result.status} · ${seconds}s):\n${tail}`);
|
|
157
|
+
return 1;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (import.meta.main) process.exit(runPwaBuildGate());
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
|
|
49
49
|
set -u
|
|
50
50
|
|
|
51
|
-
PR_NUM="${CH_PR:-
|
|
52
|
-
# 📏 이력: #5730(07-28~08-12) → #8328(08-11~08-25) → #12577(08-25~09-10) → #16815(09-10~09-27) → #20798.
|
|
51
|
+
PR_NUM="${CH_PR:-23032}" # ⛔ 2026-10-03: #20798 가 2,500 상한 → 채널 «6»(OP) # ⛔ 2026-09-27: #16815 가 2,500 상한에 닿아 채널 «5» 로 옮김(🅞 · 벽에 «닿은 뒤» — 미리 못 옮겼다)
|
|
52
|
+
# 📏 이력: #5730(07-28~08-12) → #8328(08-11~08-25) → #12577(08-25~09-10) → #16815(09-10~09-27) → #20798(09-27~10-03) → #23032.
|
|
53
53
|
# ***수명이 «2주 안팎»이다 — 세 판이 전부 그랬다.*** 다음 벽은 09-24 언저리.
|
|
54
54
|
# 🩹 발신이 rc=1 이면 먼저 세라: gh api repos/ElanvitalAI/elanous/issues/<n> --jq .comments
|
|
55
55
|
REPO="${CH_REPO:-ElanvitalAI/elanous}"
|
|
@@ -418,6 +418,23 @@ FALLBACK_USES=0
|
|
|
418
418
|
# 「파이프를 통과하면 성패가 사라진다」와 같은 뿌리다.
|
|
419
419
|
FALLBACK_FLAG="${TMPDIR:-/tmp}/ch-watch-fallback-$PR_NUM-$TRACK.$$"
|
|
420
420
|
|
|
421
|
+
emit_comments() { # $1=JSONL (line, archive, comment)
|
|
422
|
+
local out="$1" deferred
|
|
423
|
+
[ -n "$out" ] || return 0
|
|
424
|
+
if [ "${COORD_WATCH_SKIP_OTHERS_REPORTS:-}" = "1" ]; then
|
|
425
|
+
deferred=$(printf '%s\n' "$out" | jq -c 'select(.archive) | .comment') || return 1
|
|
426
|
+
if [ -n "$deferred" ]; then
|
|
427
|
+
mkdir -p "$HOME/.elanous/coord" || return 1
|
|
428
|
+
printf '%s\n' "$deferred" >> "$HOME/.elanous/coord/$SELF_ID-reports.jsonl" || return 1
|
|
429
|
+
fi
|
|
430
|
+
fi
|
|
431
|
+
if [ "${COORD_WATCH_SKIP_OTHERS_REPORTS:-}" = "1" ]; then
|
|
432
|
+
printf '%s\n' "$out" | jq -r 'select(.archive | not) | .line' || return 1
|
|
433
|
+
else
|
|
434
|
+
printf '%s\n' "$out" | jq -r '.line' || return 1
|
|
435
|
+
fi
|
|
436
|
+
}
|
|
437
|
+
|
|
421
438
|
fetch_comments() { # $1=since(ISO8601) → stdout=포맷된 신규 행 · rc=0 성공 / rc=1 두 경로 다 실패
|
|
422
439
|
local since="$1" out fmt
|
|
423
440
|
# 두 경로가 «같은 형태»를 내도록 정규화된 객체 하나만 본다: {id, created_at, login, body}
|
|
@@ -426,13 +443,30 @@ fetch_comments() { # $1=since(ISO8601) → stdout=포맷된 신규 행 · rc=0
|
|
|
426
443
|
# 그 글은 필터를 «통과»했고 창에도 떴다 — 그런데 다른 글 사이에 섞여 «요청»으로 안 보였다.
|
|
427
444
|
# 🔑 그러므로 결손은 「고르기」가 아니라 ***「고른 뒤 «구분»하지 않은 것」***이다.
|
|
428
445
|
# ⛔ 표지를 «본문 앞»에 둔다 — 뒤에 두면 BODY_CHARS 절단에 잘려 사라진다.
|
|
429
|
-
fmt
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
446
|
+
fmt=$(cat <<JQ
|
|
447
|
+
select(((.body | sub("^[[:space:]#]+"; "")) | (startswith("**[$SELF_ID]") or startswith("[$SELF_ID]") or startswith("**[$SELF_ALIAS]") or startswith("[$SELF_ALIAS]"))) | not)
|
|
448
|
+
| . as \$comment
|
|
449
|
+
| ((.body | split("\n")[0] | capture("^[[:space:]#]*[*][*][[][^]]+[]][*][*][^\n]*→[[:space:]]*(?<to>[^\n]+?)[[:space:]]*·[[:space:]]*(?<kind>요청|결정|사고|보고|정정)[[:space:]]*·")) // null) as \$envelope
|
|
450
|
+
| (if \$envelope == null then false else
|
|
451
|
+
(\$envelope.to | split("·") | map(gsub("^[[:space:]]+|[[:space:]]+$"; "")))
|
|
452
|
+
| any(.[]; . == "$SELF_ID" or . == "$SELF_ALIAS" or . == "전원")
|
|
453
|
+
end) as \$mine
|
|
454
|
+
| ((.body | test("$REQ_ADDR")) or ((.body | test("$REQ_MARK")) and (.body | test("부탁드립니다|부탁합니다")))) as \$legacy_request
|
|
455
|
+
| (\$envelope != null and \$envelope.kind == "보고" and (\$mine | not)) as \$archive
|
|
456
|
+
| {comment: \$comment, archive: \$archive,
|
|
457
|
+
line: ("[#$PR_NUM 신규 id=" + (.id|tostring) + " " + .created_at + " @" + .login + "]"
|
|
458
|
+
+ (if \$mine and \$envelope.kind == "결정" then " 🙋 나에게 «결정»"
|
|
459
|
+
elif \$mine and \$envelope.kind == "요청" then " 🙋 나에게 «요청»"
|
|
460
|
+
else "" end)
|
|
461
|
+
+ (if \$legacy_request then " 🙋‼️ 나에게 온 «요청»" else "" end)
|
|
462
|
+
+ (if \$envelope != null and (\$envelope.kind == "사고" or \$envelope.kind == "정정") then " 🚨"
|
|
463
|
+
elif \$archive then " 📄 보고" else "" end)
|
|
464
|
+
+ " " + (.body[:$BODY_CHARS] | gsub("\n"; " ⏎ ")))}
|
|
465
|
+
JQ
|
|
466
|
+
)
|
|
433
467
|
if out=$(gh api --paginate "repos/$REPO/issues/$PR_NUM/comments?since=$since" \
|
|
434
468
|
--jq ".[] | {id, created_at, login: .user.login, body} | $fmt" 2>/dev/null); then
|
|
435
|
-
|
|
469
|
+
emit_comments "$out"; return $?
|
|
436
470
|
fi
|
|
437
471
|
local owner="${REPO%%/*}" name="${REPO##*/}"
|
|
438
472
|
if out=$(gh api graphql -f query="query{repository(owner:\"$owner\",name:\"$name\"){pullRequest(number:$PR_NUM){comments(last:$COMMENT_FALLBACK_LAST){nodes{databaseId createdAt author{login} body}}}}}" \
|
|
@@ -443,7 +477,7 @@ fetch_comments() { # $1=since(ISO8601) → stdout=포맷된 신규 행 · rc=0
|
|
|
443
477
|
# ⭐ 폴백이 «쓰였다»는 사실만 남긴다 — 세는 것도 알릴지 정하는 것도 «부모 셸»이 한다.
|
|
444
478
|
# ⛔ 여기서 세면 서브셸과 함께 사라진다(위 머리말).
|
|
445
479
|
: > "$FALLBACK_FLAG" 2>/dev/null || true
|
|
446
|
-
|
|
480
|
+
emit_comments "$out"; return $?
|
|
447
481
|
fi
|
|
448
482
|
return 1
|
|
449
483
|
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { debug } from '../src/debug/log.js';
|
|
7
|
+
import { devVersion, listChecklist, statusChangesSince } from '../src/release-loop/checklist.js';
|
|
8
|
+
|
|
9
|
+
interface Report { id: number | string; created_at: string; login: string; body: string }
|
|
10
|
+
interface Change { version: string; at: string; from: unknown; to: unknown }
|
|
11
|
+
interface Cell { id: string; reports: number; bySender: Record<string, number>; last: string | null; statusChanges: Change[] }
|
|
12
|
+
interface Digest { track: string; since: string; versions: string[]; cells: Cell[]; totals: { reports: number; cells: number; statusChanges: number } }
|
|
13
|
+
|
|
14
|
+
const NO_CELL = '칸 없음';
|
|
15
|
+
const registry = JSON.parse(readFileSync(join(import.meta.dir, 'coord-tracks.json'), 'utf8')) as { tracks: Array<{ id: string; alias?: string }> };
|
|
16
|
+
const recipients = new Set(['전원', ...registry.tracks.flatMap(({ id, alias }) => alias ? [id, alias] : [id])]);
|
|
17
|
+
const STATUS_ICON: Record<string, string> = { yellow: '🟡', green: '🟢', red: '🔴', done: '✅' };
|
|
18
|
+
|
|
19
|
+
function options(args: string[]): { track: string; hours: number; versions: string[]; json: boolean; explicitSince: boolean } {
|
|
20
|
+
let track = '';
|
|
21
|
+
let hours = 2;
|
|
22
|
+
let json = false;
|
|
23
|
+
let explicitSince = false;
|
|
24
|
+
const versions: string[] = [];
|
|
25
|
+
for (let i = 0; i < args.length; i++) {
|
|
26
|
+
const arg = args[i];
|
|
27
|
+
if (arg === '--track' && args[i + 1]) track = args[++i]!;
|
|
28
|
+
else if (arg === '--since' && args[i + 1]) {
|
|
29
|
+
const match = /^(\d+(?:\.\d+)?)h$/.exec(args[++i]!);
|
|
30
|
+
if (!match || Number(match[1]) <= 0) throw new Error('--since 는 양수 시간(예: 2h)이어야 한다');
|
|
31
|
+
hours = Number(match[1]);
|
|
32
|
+
explicitSince = true;
|
|
33
|
+
} else if (arg === '--version' && args[i + 1]) versions.push(args[++i]!);
|
|
34
|
+
else if (arg === '--json') json = true;
|
|
35
|
+
else throw new Error(`모르는 인자: ${arg}`);
|
|
36
|
+
}
|
|
37
|
+
if (!/^[A-Za-z][A-Za-z0-9]*$/.test(track)) throw new Error('--track <ID> 가 필요하다');
|
|
38
|
+
return { track, hours, versions: versions.length ? [...new Set(versions)] : [devVersion().replace(/-dev\.\d+$/, '')], json, explicitSince };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function reportCell(body: string): { id: string; sender: string } | null {
|
|
42
|
+
const line = body.split(/\r?\n/, 1)[0] ?? '';
|
|
43
|
+
const envelope = /^\s*(?:#+\s*)?\*\*\[([^\]]+)\]\*\*[^\n]*?→\s*(.+)$/.exec(line);
|
|
44
|
+
if (!envelope) return null;
|
|
45
|
+
const fields = envelope[2]!.split('·').map((part) => part.trim());
|
|
46
|
+
if (!recipients.has(fields[0] ?? '') || fields[1] !== '보고') return null;
|
|
47
|
+
const cell = fields[2];
|
|
48
|
+
return { id: !cell || cell === '-' ? NO_CELL : cell, sender: envelope[1]! };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function readCursor(path: string): Set<string> {
|
|
52
|
+
let raw: string;
|
|
53
|
+
try {
|
|
54
|
+
raw = readFileSync(path, 'utf8');
|
|
55
|
+
} catch (error) {
|
|
56
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return new Set();
|
|
57
|
+
throw error;
|
|
58
|
+
}
|
|
59
|
+
try {
|
|
60
|
+
const parsed: unknown = JSON.parse(raw);
|
|
61
|
+
if (!Array.isArray(parsed) || !parsed.every((id) => typeof id === 'string' || typeof id === 'number')) {
|
|
62
|
+
throw new Error('invalid cursor ids');
|
|
63
|
+
}
|
|
64
|
+
return new Set(parsed.map(String));
|
|
65
|
+
} catch (error) {
|
|
66
|
+
const backup = `${path}.corrupt-${randomUUID()}`;
|
|
67
|
+
writeFileSync(backup, raw, { mode: 0o600, flag: 'wx' });
|
|
68
|
+
console.error(`${path}: 손상된 커서를 ${backup} 에 보존하고 빈 커서로 복구: ${error instanceof Error ? error.message : String(error)}`);
|
|
69
|
+
return new Set();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function saveCursor(path: string, seen: Set<string>): void {
|
|
74
|
+
const temporary = `${path}.tmp-${randomUUID()}`;
|
|
75
|
+
try {
|
|
76
|
+
writeFileSync(temporary, `${JSON.stringify([...seen])}\n`, { mode: 0o600, flag: 'wx' });
|
|
77
|
+
renameSync(temporary, path);
|
|
78
|
+
} finally {
|
|
79
|
+
if (existsSync(temporary)) unlinkSync(temporary);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Serialize digest runs per track: cursor read → count → cursor save happens under one lock (review round 2).
|
|
84
|
+
* A lock whose owner pid is gone, or older than 60 s, is taken over; otherwise wait up to 10 s, then fail. */
|
|
85
|
+
function withDigestLock<T>(root: string, track: string, fn: () => T): T {
|
|
86
|
+
mkdirSync(root, { recursive: true });
|
|
87
|
+
const path = join(root, `${track}-digest.lock`);
|
|
88
|
+
const deadline = Date.now() + 10_000;
|
|
89
|
+
for (;;) {
|
|
90
|
+
try {
|
|
91
|
+
const fd = openSync(path, 'wx', 0o600);
|
|
92
|
+
writeFileSync(fd, String(process.pid));
|
|
93
|
+
closeSync(fd);
|
|
94
|
+
break;
|
|
95
|
+
} catch (error) {
|
|
96
|
+
if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error;
|
|
97
|
+
let stale = false;
|
|
98
|
+
try {
|
|
99
|
+
const owner = Number(readFileSync(path, 'utf8').trim());
|
|
100
|
+
const age = Date.now() - statSync(path).mtimeMs;
|
|
101
|
+
if (age > 60_000) stale = true;
|
|
102
|
+
else if (Number.isInteger(owner) && owner > 0) {
|
|
103
|
+
try { process.kill(owner, 0); } catch (e) { if ((e as NodeJS.ErrnoException).code === 'ESRCH') stale = true; }
|
|
104
|
+
}
|
|
105
|
+
} catch { stale = false; }
|
|
106
|
+
if (stale) { try { unlinkSync(path); } catch { /* another run took it */ } continue; }
|
|
107
|
+
if (Date.now() > deadline) throw new Error('다른 요약이 같은 자리에서 도는 중이다 — 잠시 뒤 다시');
|
|
108
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 100);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
try { return fn(); } finally { try { unlinkSync(path); } catch { /* already gone */ } }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function build(track: string, since: string, versions: string[], root: string, explicitSince: boolean): Digest {
|
|
115
|
+
const cells = new Map<string, Cell>();
|
|
116
|
+
const latest = new Map<string, number>();
|
|
117
|
+
const cellFor = (id: string): Cell => {
|
|
118
|
+
let cell = cells.get(id);
|
|
119
|
+
if (!cell) {
|
|
120
|
+
cell = { id, reports: 0, bySender: Object.create(null) as Record<string, number>, last: null, statusChanges: [] };
|
|
121
|
+
cells.set(id, cell);
|
|
122
|
+
}
|
|
123
|
+
return cell;
|
|
124
|
+
};
|
|
125
|
+
const reportsPath = join(root, `${track}-reports.jsonl`);
|
|
126
|
+
const cursorPath = join(root, `${track}-digest-cursor`);
|
|
127
|
+
const seen = readCursor(cursorPath);
|
|
128
|
+
const cutoff = Date.parse(since);
|
|
129
|
+
const now = Date.now();
|
|
130
|
+
let count = 0;
|
|
131
|
+
const counted = new Set<string>();
|
|
132
|
+
if (existsSync(reportsPath)) for (const [index, line] of readFileSync(reportsPath, 'utf8').split('\n').entries()) {
|
|
133
|
+
if (!line.trim()) continue;
|
|
134
|
+
let report: Report;
|
|
135
|
+
try {
|
|
136
|
+
const parsed: unknown = JSON.parse(line);
|
|
137
|
+
if (typeof parsed !== 'object' || parsed === null ||
|
|
138
|
+
!('id' in parsed) || (typeof parsed.id !== 'string' && typeof parsed.id !== 'number') ||
|
|
139
|
+
!('created_at' in parsed) || typeof parsed.created_at !== 'string' ||
|
|
140
|
+
!('login' in parsed) || typeof parsed.login !== 'string' ||
|
|
141
|
+
!('body' in parsed) || typeof parsed.body !== 'string') throw new Error('invalid report fields');
|
|
142
|
+
report = parsed as Report;
|
|
143
|
+
} catch (error) {
|
|
144
|
+
console.error(`${reportsPath}:${index + 1}: 잘못된 보고 줄 건너뜀: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
const id = String(report.id);
|
|
148
|
+
const at = Date.parse(report.created_at);
|
|
149
|
+
if (!id || !Number.isFinite(at) || at < cutoff || at > now || counted.has(id) || (!explicitSince && seen.has(id))) continue;
|
|
150
|
+
const envelope = reportCell(report.body);
|
|
151
|
+
if (envelope === null) continue;
|
|
152
|
+
const { id: cellId, sender } = envelope;
|
|
153
|
+
seen.add(id);
|
|
154
|
+
counted.add(id);
|
|
155
|
+
const cell = cellFor(cellId);
|
|
156
|
+
cell.reports++;
|
|
157
|
+
cell.bySender[sender] = (cell.bySender[sender] ?? 0) + 1;
|
|
158
|
+
const first = report.body.split(/\r?\n/, 1)[0] ?? '';
|
|
159
|
+
if (at >= (latest.get(cellId) ?? -Infinity)) {
|
|
160
|
+
cell.last = Array.from(first).slice(0, 80).join('');
|
|
161
|
+
latest.set(cellId, at);
|
|
162
|
+
}
|
|
163
|
+
count++;
|
|
164
|
+
}
|
|
165
|
+
for (const version of versions) for (const entry of statusChangesSince(listChecklist(version).history, since)) {
|
|
166
|
+
if (Date.parse(entry.at) > now) continue;
|
|
167
|
+
cellFor(entry.id).statusChanges.push({ version, at: entry.at, from: entry.from, to: entry.to });
|
|
168
|
+
}
|
|
169
|
+
const output = [...cells.values()].map(({ id, reports, bySender, last, statusChanges }) => ({
|
|
170
|
+
id, reports, bySender, last, statusChanges: statusChanges.sort((a, b) => a.at.localeCompare(b.at)),
|
|
171
|
+
})).sort((a, b) => a.id.localeCompare(b.id, 'ko'));
|
|
172
|
+
const totals = { reports: count, cells: output.length, statusChanges: output.filter((cell) => cell.statusChanges.length > 0).length };
|
|
173
|
+
debug.log('coord.digest', 'built', { track, reports: count, cells: totals.cells, statusChanges: totals.statusChanges });
|
|
174
|
+
mkdirSync(root, { recursive: true });
|
|
175
|
+
saveCursor(cursorPath, seen);
|
|
176
|
+
return { track, since, versions, cells: output, totals };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function text(digest: Digest): string {
|
|
180
|
+
const lines = digest.cells.map((cell) => {
|
|
181
|
+
const status = cell.statusChanges.map(({ from, to }) => `${STATUS_ICON[String(from)] ?? String(from)}→${STATUS_ICON[String(to)] ?? String(to)}`).join(', ') || '-';
|
|
182
|
+
const senders = Object.entries(cell.bySender).sort(([a], [b]) => a.localeCompare(b)).map(([name, count]) => `${name} ${count}`).join(' · ');
|
|
183
|
+
return `${cell.id} · ${status} · 보고 ${cell.reports}${senders ? `(${senders})` : ''} · 마지막: ${cell.last ?? '-'}`;
|
|
184
|
+
});
|
|
185
|
+
lines.push(`합계 · 글 ${digest.totals.reports} · 칸 ${digest.totals.cells} · 상태 바뀐 칸 ${digest.totals.statusChanges}`);
|
|
186
|
+
return lines.join('\n');
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (import.meta.main) {
|
|
190
|
+
try {
|
|
191
|
+
const { track, hours, versions, json, explicitSince } = options(process.argv.slice(2));
|
|
192
|
+
const root = join(process.env.HOME ?? homedir(), '.elanous', 'coord');
|
|
193
|
+
const digest = withDigestLock(root, track, () => build(track, new Date(Date.now() - hours * 3_600_000).toISOString(), versions, root, explicitSince));
|
|
194
|
+
console.log(json ? JSON.stringify(digest) : text(digest));
|
|
195
|
+
} catch (error) {
|
|
196
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
}
|
|
199
|
+
}
|
package/scripts/coord-post.sh
CHANGED
|
@@ -73,11 +73,62 @@ case "$FIRST" in
|
|
|
73
73
|
exit 5 ;;
|
|
74
74
|
esac
|
|
75
75
|
|
|
76
|
-
# 봉투 v2(RFC-coordination-cost §2 · C1 ·
|
|
77
|
-
|
|
78
|
-
|
|
76
|
+
# 봉투 v2(RFC-coordination-cost §2 · C1): 신원 · (📌안내) · 시각 → 받는 이 · 종류 · 칸 · (기한).
|
|
77
|
+
# 필드를 «순서대로» 읽는다 — 받는 이(정본 id 또는 전원)가 끝난 «바로 다음» 필드만 종류다(칸의 «결정 D1» 같은 글로 종류를 고르지 않는다).
|
|
78
|
+
# 전부 경고만 — 발신은 막지 않는다(옛 형식 하위 호환).
|
|
79
|
+
trim() { local v="$1"; v="${v#"${v%%[![:space:]]*}"}"; printf '%s' "${v%"${v##*[![:space:]]}"}"; }
|
|
80
|
+
HEADER=${FIRST#"**[$ID]**"}
|
|
81
|
+
ENVELOPE=""
|
|
82
|
+
case "$HEADER" in *'→'*) case "${HEADER%%→*}" in *' · '*) ;; *) ENVELOPE=$(trim "${HEADER#*→}") ;; esac ;; esac # → 는 시각 바로 뒤일 때만 봉투
|
|
83
|
+
SEP=$'\x1f'
|
|
84
|
+
FIELDS=()
|
|
85
|
+
[ -n "$ENVELOPE" ] && IFS="$SEP" read -r -a FIELDS <<< "${ENVELOPE// · /$SEP}"
|
|
86
|
+
N=${#FIELDS[@]}
|
|
87
|
+
i=0; RECIPIENT_COUNT=0; RECIPIENT_ALL=0
|
|
88
|
+
while [ "$i" -lt "$N" ]; do
|
|
89
|
+
F=$(trim "${FIELDS[$i]}")
|
|
90
|
+
F=${F%% — *} # 옛 머리 «→ OP — 무엇» 의 받는 이 부분만
|
|
91
|
+
TOKS=${F//,/ }
|
|
92
|
+
OK=1; C=0
|
|
93
|
+
for T in $TOKS; do
|
|
94
|
+
if [ "$T" = "전원" ]; then C=$((C + 1)); RECIPIENT_ALL=1
|
|
95
|
+
else case " $IDS " in *" $T "*) C=$((C + 1)) ;; *) OK=0; break ;; esac
|
|
96
|
+
fi
|
|
97
|
+
done
|
|
98
|
+
[ "$OK" -eq 1 ] && [ "$C" -gt 0 ] || break
|
|
99
|
+
RECIPIENT_COUNT=$((RECIPIENT_COUNT + C)); i=$((i + 1))
|
|
100
|
+
case "${FIELDS[$((i - 1))]}" in *' — '*) break ;; esac
|
|
101
|
+
done
|
|
102
|
+
KIND_FIELD=""; SLOT=""; DEADLINE_FIELD=""
|
|
103
|
+
if [ "$i" -lt "$N" ]; then KIND_FIELD=$(trim "${FIELDS[$i]}"); fi
|
|
104
|
+
if [ $((i + 1)) -lt "$N" ]; then SLOT=$(trim "${FIELDS[$((i + 1))]}"); fi
|
|
105
|
+
j=$((i + 1))
|
|
106
|
+
while [ "$j" -lt "$N" ]; do
|
|
107
|
+
F=$(trim "${FIELDS[$j]}")
|
|
108
|
+
case "$F" in '기한 '[![:space:]]*) DEADLINE_FIELD=$F ;; esac
|
|
109
|
+
j=$((j + 1))
|
|
110
|
+
done
|
|
111
|
+
case "$KIND_FIELD" in
|
|
112
|
+
요청|결정|사고|보고|정정) ;;
|
|
79
113
|
*) echo "⚠️ 첫 줄에 글 종류(요청·결정·사고·보고·정정)가 없다 — 받는 쪽 감시자가 즉시/모아 읽기를 못 가른다(발신은 한다)" >&2 ;;
|
|
80
114
|
esac
|
|
115
|
+
if [ "$RECIPIENT_COUNT" -eq 0 ]; then
|
|
116
|
+
echo "⚠️ 받는 이가 없다(→ TC · UX 꼴)" >&2
|
|
117
|
+
fi
|
|
118
|
+
case "$KIND_FIELD" in
|
|
119
|
+
요청|결정|사고|보고|정정)
|
|
120
|
+
case "$SLOT" in ''|기한|'기한 '*) echo "⚠️ 첫 줄에 칸(확인표·결정 id 또는 -)이 없다" >&2 ;; esac ;;
|
|
121
|
+
esac
|
|
122
|
+
case "$KIND_FIELD" in
|
|
123
|
+
요청|결정) [ -n "$DEADLINE_FIELD" ] || echo "⚠️ 요청·결정에는 기한을 적는다" >&2 ;;
|
|
124
|
+
esac
|
|
125
|
+
if [ "$RECIPIENT_ALL" -eq 1 ]; then
|
|
126
|
+
case "$KIND_FIELD" in
|
|
127
|
+
사고|정정) ;;
|
|
128
|
+
*) CEO_MARK=$(printf '\360\237\221\221') # the CEO mark, built from bytes so the public export carries no literal
|
|
129
|
+
case "$FIRST" in *"$CEO_MARK"*) ;; *) echo "⚠️ «전원»은 사고·정정·대표 지시에만" >&2 ;; esac ;;
|
|
130
|
+
esac
|
|
131
|
+
fi
|
|
81
132
|
|
|
82
133
|
TS=$(TZ=Asia/Seoul date '+%Y-%m-%d %H:%M KST')
|
|
83
134
|
TMP=$(mktemp "${TMPDIR:-/tmp}/coord-post.XXXXXX") || { echo "⛔ mktemp 실패 — 발신하지 않는다" >&2; exit 7; }
|
|
@@ -167,3 +218,9 @@ if [ "$RC" -ne 0 ]; then
|
|
|
167
218
|
exit "$RC"
|
|
168
219
|
fi
|
|
169
220
|
echo "[coord-post] 발신 성공$VIA · 채널 #$PR · 신원 [$ID] · 시각 $TS" >&2
|
|
221
|
+
# Only a successfully sent post is recorded; never pass the body or later lines to the ledger.
|
|
222
|
+
POST_URL=$(printf '%s\n' "$POST_OUTPUT" | grep -oE 'https://github\.com/[^[:space:]]+/issues/[0-9]+#issuecomment-[0-9]+|https://github\.com/[^[:space:]]+/pull/[0-9]+#issuecomment-[0-9]+' | head -1 || true)
|
|
223
|
+
if ! bun bin/elanous.mjs coord event record --seat "$ID" --header "$(head -1 "$TMP")" --url "$POST_URL" >/dev/null 2>&1; then
|
|
224
|
+
echo '⚠️ 맥락 원장 기록 실패(발신은 됐다)' >&2
|
|
225
|
+
fi
|
|
226
|
+
exit 0
|
package/scripts/install.ps1
CHANGED
|
@@ -3,6 +3,7 @@ param(
|
|
|
3
3
|
[string] $Prefix,
|
|
4
4
|
[string] $Source,
|
|
5
5
|
[switch] $NoModifyPath,
|
|
6
|
+
[switch] $NoBootstrapBun,
|
|
6
7
|
[switch] $Help,
|
|
7
8
|
[Parameter(ValueFromRemainingArguments = $true)]
|
|
8
9
|
[string[]] $RemainingArgs
|
|
@@ -20,8 +21,8 @@ $ErrorActionPreference = 'Stop'
|
|
|
20
21
|
|
|
21
22
|
function Show-Usage {
|
|
22
23
|
@'
|
|
23
|
-
Usage: powershell -File scripts/install.ps1 [-Prefix PATH] [-Source PATH.tgz|URL] [-NoModifyPath] [-Help]
|
|
24
|
-
powershell -File scripts/install.ps1 [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--help]
|
|
24
|
+
Usage: powershell -File scripts/install.ps1 [-Prefix PATH] [-Source PATH.tgz|URL] [-NoModifyPath] [-NoBootstrapBun] [-Help]
|
|
25
|
+
powershell -File scripts/install.ps1 [--prefix PATH] [--source PATH.tgz|URL] [--no-modify-path] [--no-bootstrap-bun] [--help]
|
|
25
26
|
|
|
26
27
|
Install elanous into a versioned layout.
|
|
27
28
|
Prefix / --prefix installation root (default: $ELANOUS_INSTALL_PREFIX or %LOCALAPPDATA%\elanous)
|
|
@@ -29,6 +30,7 @@ Install elanous into a versioned layout.
|
|
|
29
30
|
Source / --source install a package tarball (local path or http(s) URL; default: $ELANOUS_INSTALL_SOURCE,
|
|
30
31
|
else pack the checkout or fetch the verified release when standalone)
|
|
31
32
|
NoModifyPath / --no-modify-path do not append the elanous PATH block to the PowerShell profile
|
|
33
|
+
NoBootstrapBun / --no-bootstrap-bun fail instead of installing bun when missing
|
|
32
34
|
Help / --help show this help
|
|
33
35
|
'@ | Write-Output
|
|
34
36
|
}
|
|
@@ -38,11 +40,15 @@ function Fail([string] $Message, [int] $Code) {
|
|
|
38
40
|
exit $Code
|
|
39
41
|
}
|
|
40
42
|
|
|
43
|
+
# PowerShell 5.1 parses BOM-less scripts as ANSI. Keep translated messages ASCII in source and decode at runtime.
|
|
44
|
+
function Say([string] $English, [string] $Korean) {
|
|
45
|
+
if ($installLang -eq 'ko') { return [regex]::Unescape($Korean) }
|
|
46
|
+
return $English
|
|
47
|
+
}
|
|
48
|
+
|
|
41
49
|
function Get-RequiredCommands([string] $InstallerPath) {
|
|
42
|
-
# install.sh
|
|
43
|
-
|
|
44
|
-
$fallbackCommands = 'git', 'bun'
|
|
45
|
-
if (-not $InstallerPath -or -not (Test-Path -LiteralPath $InstallerPath)) { return $fallbackCommands }
|
|
50
|
+
# A checkout follows install.sh; standalone release installs need bun but not git.
|
|
51
|
+
if (-not $InstallerPath -or -not (Test-Path -LiteralPath $InstallerPath)) { return @('bun') }
|
|
46
52
|
$content = Get-Content -LiteralPath $InstallerPath -Raw
|
|
47
53
|
$match = [regex]::Match($content, 'REQUIRED_COMMANDS=\(([^)]*)\)')
|
|
48
54
|
if (-not $match.Success) { Fail "could not read REQUIRED_COMMANDS from $InstallerPath" 1 }
|
|
@@ -87,11 +93,14 @@ for ($index = 0; $index -lt $RemainingArgs.Count; $index++) {
|
|
|
87
93
|
$Source = $RemainingArgs[++$index]
|
|
88
94
|
}
|
|
89
95
|
'--no-modify-path' { $NoModifyPath = $true }
|
|
96
|
+
'--no-bootstrap-bun' { $NoBootstrapBun = $true }
|
|
90
97
|
default { Fail "unknown argument: $($RemainingArgs[$index])" 2 }
|
|
91
98
|
}
|
|
92
99
|
}
|
|
93
100
|
|
|
94
101
|
if ($Help) { Show-Usage; exit 0 }
|
|
102
|
+
$installLang = if ($env:ELANOUS_INSTALL_LANG) { $env:ELANOUS_INSTALL_LANG } else { [Globalization.CultureInfo]::CurrentUICulture.Name }
|
|
103
|
+
$installLang = if ($installLang -match '^ko(?:-|$)') { 'ko' } else { 'en' }
|
|
95
104
|
if (-not $Prefix) {
|
|
96
105
|
$dataHome = if ($env:LOCALAPPDATA) { $env:LOCALAPPDATA } else { Join-Path $HOME 'AppData\Local' }
|
|
97
106
|
$Prefix = if ($env:ELANOUS_INSTALL_PREFIX) { $env:ELANOUS_INSTALL_PREFIX } else { Join-Path $dataHome 'elanous' }
|
|
@@ -100,15 +109,46 @@ if (-not $Source -and $env:ELANOUS_INSTALL_SOURCE) { $Source = $env:ELANOUS_INST
|
|
|
100
109
|
|
|
101
110
|
$scriptDir = if ($PSCommandPath) { Split-Path -Parent $PSCommandPath } else { $null }
|
|
102
111
|
$repoRoot = if ($scriptDir) { [IO.Path]::GetFullPath((Join-Path $scriptDir '..')) } else { $null }
|
|
103
|
-
$
|
|
112
|
+
$installerPath = if ($scriptDir) { Join-Path $scriptDir 'install.sh' } else { $null }
|
|
113
|
+
$requiredCommands = Get-RequiredCommands $installerPath
|
|
114
|
+
$missingGit = -not (Get-Command git -ErrorAction SilentlyContinue)
|
|
115
|
+
$bootstrappedBun = $false
|
|
116
|
+
$bunBin = Join-Path $HOME '.bun\bin'
|
|
117
|
+
if (-not (Get-Command bun -ErrorAction SilentlyContinue) -and (Test-Path -LiteralPath (Join-Path $bunBin 'bun.exe') -PathType Leaf)) {
|
|
118
|
+
$env:PATH = $bunBin + [IO.Path]::PathSeparator + $env:PATH
|
|
119
|
+
}
|
|
120
|
+
# Preserve checkout required-command enforcement, including git, before changing any installation files.
|
|
104
121
|
foreach ($command in $requiredCommands) {
|
|
122
|
+
if ($command -eq 'bun') { continue }
|
|
105
123
|
if (-not (Get-Command $command -ErrorAction SilentlyContinue)) { Fail "required command missing: $command" 127 }
|
|
106
124
|
}
|
|
125
|
+
if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
|
|
126
|
+
$bunCommand = 'irm https://bun.sh/install.ps1 | iex'
|
|
127
|
+
if ($NoBootstrapBun) { Fail "$(Say 'bun is missing; run:' 'bun \uc774 \uc5c6\uc2b5\ub2c8\ub2e4. \uba3c\uc800 \uc2e4\ud589\ud558\uc138\uc694:') $bunCommand" 127 }
|
|
128
|
+
# Bun's official install.ps1 resets the session PATH to the user PATH only (System32 and tar vanish) - keep ours.
|
|
129
|
+
$pathBeforeBun = $env:PATH
|
|
130
|
+
try {
|
|
131
|
+
$bootstrapScript = if ($env:ELANOUS_INSTALL_BUN_SCRIPT) {
|
|
132
|
+
Read-Text $env:ELANOUS_INSTALL_BUN_SCRIPT
|
|
133
|
+
} else {
|
|
134
|
+
(Invoke-WebRequest -UseBasicParsing -Uri 'https://bun.sh/install.ps1').Content
|
|
135
|
+
}
|
|
136
|
+
if (-not $bootstrapScript) { throw 'empty installer response' }
|
|
137
|
+
& ([scriptblock]::Create([string]$bootstrapScript))
|
|
138
|
+
if (-not $?) { throw 'installer returned failure' }
|
|
139
|
+
if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "installer exited with code $LASTEXITCODE" }
|
|
140
|
+
} catch { Fail "$(Say 'bun bootstrap failed:' 'bun \uc124\uce58 \uc2e4\ud328:') $($_.Exception.Message)" 1 }
|
|
141
|
+
$env:PATH = $bunBin + [IO.Path]::PathSeparator + $pathBeforeBun
|
|
142
|
+
if (-not (Get-Command bun -ErrorAction SilentlyContinue)) {
|
|
143
|
+
Fail (Say 'bun bootstrap failed: bun not found after installation' 'bun \uc124\uce58 \uc2e4\ud328: \uc124\uce58 \ub4a4\uc5d0\ub3c4 bun \uc744 \ucc3e\uc744 \uc218 \uc5c6\uc2b5\ub2c8\ub2e4') 1
|
|
144
|
+
}
|
|
145
|
+
$bootstrappedBun = $true
|
|
146
|
+
}
|
|
107
147
|
$bunPath = (Get-Command bun).Source
|
|
108
148
|
|
|
109
149
|
# A standalone installer fetches a verified release; explicit sources and checkouts keep their existing paths.
|
|
110
150
|
$isCheckout = $false
|
|
111
|
-
if ($repoRoot -and (Test-Path -LiteralPath (Join-Path $repoRoot 'package.json'))) {
|
|
151
|
+
if ($installerPath -and (Test-Path -LiteralPath $installerPath) -and $repoRoot -and (Test-Path -LiteralPath (Join-Path $repoRoot 'package.json'))) {
|
|
112
152
|
$isCheckout = (Read-Text (Join-Path $repoRoot 'package.json')) -match '"name":\s*"elanous"'
|
|
113
153
|
}
|
|
114
154
|
$releaseDirectory = $null
|
|
@@ -247,7 +287,26 @@ try {
|
|
|
247
287
|
if (-not $NoModifyPath -and -not (Read-Text $profilePath).Contains($markerStart)) {
|
|
248
288
|
Add-Content -LiteralPath $profilePath -Value ("`r`n$markerStart`r`n$pathLine`r`n$markerEnd")
|
|
249
289
|
}
|
|
250
|
-
|
|
290
|
+
# The default execution policy (Restricted) skips $PROFILE in a new window - the user PATH is what a fresh shell sees.
|
|
291
|
+
if (-not $NoModifyPath -and -not $env:ELANOUS_INSTALL_NO_USER_PATH) {
|
|
292
|
+
$binDirectory = Join-Path $Prefix 'bin'
|
|
293
|
+
$userPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
|
294
|
+
$userEntries = @(($userPath -split ';') | Where-Object { $_ })
|
|
295
|
+
if ($userEntries -notcontains $binDirectory) {
|
|
296
|
+
[Environment]::SetEnvironmentVariable('Path', (@($binDirectory) + $userEntries) -join ';', 'User')
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
$installedMessage = Say 'Installed elanous {0} at {1}' 'elanous {0} \uc744(\ub97c) \uc124\uce58\ud588\uc2b5\ub2c8\ub2e4: {1}'
|
|
300
|
+
Write-Output ($installedMessage -f $version, $shimPath)
|
|
301
|
+
$bootstrapped = if ($bootstrappedBun) { 'bun' } else { 'none' }
|
|
302
|
+
$missing = if ($missingGit -and -not $isCheckout) { 'git(next step)' } else { 'none' }
|
|
303
|
+
Write-Output "$(Say 'Install summary' '\uc124\uce58 \uc694\uc57d'): bootstrapped: $bootstrapped; missing: $missing"
|
|
304
|
+
Write-Output (Say 'Next:' '\ub2e4\uc74c:')
|
|
305
|
+
if (-not $NoModifyPath) { Write-Output (Say ' Open a new PowerShell window to use elanous on PATH.' ' elanous \uc744 PATH \uc5d0\uc11c \uc4f0\ub824\uba74 \uc0c8 PowerShell \ucc3d\uc744 \uc5ec\uc138\uc694.') }
|
|
306
|
+
else { Write-Output "$(Say ' Run:' ' \uc2e4\ud589:') $shimPath" }
|
|
307
|
+
if ($missing -ne 'none') {
|
|
308
|
+
Write-Output " winget install --id Git.Git -e # $(Say 'the harness uses git' '\ud558\ub2c8\uc2a4\uac00 git \uc744 \uc0ac\uc6a9\ud569\ub2c8\ub2e4')"
|
|
309
|
+
}
|
|
251
310
|
} finally {
|
|
252
311
|
Remove-Item -LiteralPath $tempDirectory -Recurse -Force -ErrorAction SilentlyContinue
|
|
253
312
|
}
|