elanous 0.2.6 → 0.2.7
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/.env.example +10 -7
- package/README.md +4 -0
- package/apps/pwa/out/404/index.html +1 -1
- package/apps/pwa/out/404.html +1 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1201-09613bce624467c0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{3995-bee512a7063036ce.js → 2375-cb6debea56cce4fb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2415.ed6428ff334c7ce2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2929-617f1e66a55be184.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +25 -0
- package/apps/pwa/out/_next/static/chunks/{8d0d9faf-97887ff99713b84e.js → 2ea96958-e8c7e112de50b067.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{5329.8b7e4bca684b6eec.js → 3403.4bc97b4066945a94.js} +2 -2
- package/apps/pwa/out/_next/static/chunks/3441-a1dd4acfeda14488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{7633.0654fe6ea259c1ee.js → 4323.c5dfb6f503304775.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/4836-2eba6079f31b88d4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2984-977cfa339fcd65a5.js → 5080-1613752891fcadb4.js} +3 -3
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{763-7d0e1913b5074e98.js → 5791-bb82743fa6299562.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/{4240-7153881b0b377306.js → 6062-73e68343a9814f6a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6090-85a3f05f954e5697.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6284-69db415193d5f8bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6416-505ba707cacafbba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{91ff6dda-3b33d42d20657429.js → 8046cadf-211ca3c0e00f2b72.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/8067-f74bf14d67ad98f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8519-f6b5d8a5833a7603.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8894-e92491a164192cc1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9454-a3e0953307216c58.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9740-67051141115ce775.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9804-64c02c3c127d5014.js +1 -0
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d5ebde04844689e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-10ed24f413ed66e9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-211810b6c93ab66a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-624f7a2f89b60c8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/page-5a5fb385a4df0ffe.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-80497e93a878ddc5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-7457f52683562161.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-cee86394de407aa6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-4ff8edab44196b8c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-92e4f44428d5e290.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-b7dcf5d48a854b76.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-51480757e7d68b92.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2a05dfe80c8bdf16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-d9979633aa82f8e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-30533117beb53625.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-f7857826c2f97ad7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-07267f1391b96aba.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-55d50e9b0006a91d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-9699b1786be6171d.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-43dd772aee8af75f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-a7e37af7d0399cb8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-973ab7af59c718b7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a9f8d3865b91e436.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-15e5a15db84c56aa.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-a2e572e4511f2a16.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-dd107bf1c7d52a90.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-d42b99cdfb9f27c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-2764ff40dad5a995.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-c5f1152db116ff96.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-f933bca81ea38a6f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-c1c95739c0bff3b8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-a7aaffcf771662d8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-71d18eec33b9c667.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-56adbe1a1f1379bd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-2c4e59079ee357ac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-a81f113f3ef89d72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5d422f38c052967e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-fc27b00e20733146.js +1 -0
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-6870f30ea99f8346.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{webpack-37b8bf78a3c6eec5.js → webpack-e5bea36f183b25e6.js} +1 -1
- package/apps/pwa/out/_next/static/css/f537bdb19c69306a.css +1 -0
- package/apps/pwa/out/_next/static/keajW7DDyghEs1y8oJe6T/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +22 -22
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +22 -22
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +22 -22
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +21 -21
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +21 -21
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +21 -21
- package/apps/pwa/out/consult/index.html +1 -0
- package/apps/pwa/out/consult/index.txt +32 -0
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +22 -22
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +22 -22
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +22 -22
- package/apps/pwa/out/exec/index.html +1 -0
- package/apps/pwa/out/exec/index.txt +32 -0
- package/apps/pwa/out/field/index.html +1 -0
- package/apps/pwa/out/field/index.txt +32 -0
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +21 -21
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +22 -22
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +22 -22
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +21 -21
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +21 -21
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +22 -22
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +22 -22
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +22 -22
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +22 -22
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +21 -21
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +22 -22
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +22 -22
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +22 -22
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +22 -22
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +22 -22
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +21 -21
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +22 -22
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +22 -22
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +22 -22
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +22 -22
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +22 -22
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +22 -22
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +22 -22
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +22 -22
- package/bin/elanous.cjs +107 -0
- package/graphs/field/field-feed.yaml +24 -0
- package/graphs/field/recipes.yaml +12 -0
- package/graphs/landing/landing-heal.yaml +3 -1
- package/graphs/landing/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +10 -0
- package/graphs/release/release-loop.yaml +10 -3
- package/package.json +5 -5
- package/plugins/card-followup/README.md +13 -4
- package/plugins/card-followup/graphs/card-followup.yaml +3 -1
- package/plugins/card-followup/graphs/recipes.yaml +5 -1
- package/plugins/card-followup/graphs/run-step.ts +122 -18
- package/plugins/card-followup/graphs/sales-strategy.ts +173 -0
- package/plugins/card-followup/plugin.json +1 -1
- package/plugins/doc-draft/README.md +13 -0
- package/plugins/doc-draft/examples/brief-sample.json +8 -0
- package/plugins/doc-draft/graphs/doc-draft.yaml +14 -0
- package/plugins/doc-draft/graphs/recipes.yaml +9 -0
- package/plugins/doc-draft/graphs/run-step.ts +113 -0
- package/plugins/doc-draft/plugin.json +24 -0
- package/plugins/geo-check/README.md +3 -1
- package/plugins/geo-check/graphs/run-step.ts +24 -3
- package/plugins/geo-check/plugin.json +1 -1
- package/plugins/job-coach/.codex-plugin/plugin.json +1 -1
- package/plugins/job-coach/README.md +1 -1
- package/plugins/job-coach/graphs/run-step.ts +2 -2
- package/plugins/job-coach/plugin.json +1 -1
- package/plugins/job-coach/plugin.ts +1 -1
- package/scripts/affinity/endcard.sh +3 -2
- package/scripts/affinity/reframe.sh +3 -1
- package/scripts/ci-public-export-leak-gate.ts +48 -0
- package/scripts/codex-quota-alert.ts +122 -258
- package/scripts/coord-tracks.json +4 -4
- package/scripts/field-feed/deliver-node.ts +52 -0
- package/scripts/field-feed/feed-node.ts +67 -0
- package/scripts/field-feed/lib.ts +162 -0
- package/scripts/field-feed/reel-node.ts +24 -0
- package/scripts/field-feed/start.ts +62 -0
- package/scripts/fixtures/vflow-sample.ndjson +6 -0
- package/scripts/install.sh +92 -13
- package/scripts/landing-heal/clean-checkout.ts +114 -0
- package/scripts/landing-heal/run-mission.ts +38 -3
- package/scripts/lib/pwa-scenarios/scenarios.ts +2 -1
- package/scripts/public-export.ts +8 -4
- package/scripts/release-loop/baseline-from-logs.ts +92 -0
- package/scripts/release-loop/env-known-failures.ts +31 -0
- package/scripts/release-loop/gate-node.ts +81 -19
- package/scripts/release-loop/known-issues-node.ts +159 -0
- package/scripts/release-loop/npm-publish-node.ts +118 -0
- package/scripts/release-loop/ops-upgrade-node.ts +87 -0
- package/scripts/release-loop/release-readiness.ts +58 -0
- package/scripts/release-loop/unattended-release.ts +93 -0
- package/scripts/ux/measure-first-text.ts +102 -0
- package/scripts/vflow-collect.ts +20 -7
- package/scripts/vflow-search.ts +73 -0
- package/scripts/video-free-line-test-walker.ts +36 -0
- package/scripts/video-pipeline.ts +2 -2
- package/skills/explainer-video/SKILL.md +12 -9
- package/skills/explainer-video/engine/build.mjs +1 -1
- package/skills/explainer-video/engine/reel.mjs +138 -31
- package/skills/explainer-video/engine/reel.sh +2 -1
- package/skills/explainer-video/examples/elanous-0.2.5/script.json +1 -1
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/omni-digest/docs/diagrams/noncode-terminal-promotion-path-2026-08-13.mmd +1 -1
- package/src/a2a/server.ts +2 -0
- package/src/acp/elanous-aware-peers.ts +30 -0
- package/src/acp/server.ts +91 -15
- package/src/agent/monad-agent-turn.ts +25 -6
- package/src/agent/shared-app-tools.ts +5 -3
- package/src/agent-substrate/review-intent.ts +6 -2
- package/src/agent-substrate/shard-boundary-intent.ts +70 -0
- package/src/auth/github-app-token.ts +4 -1
- package/src/autopilot/track-agent-shadow.ts +151 -0
- package/src/autopilot/track-agent.ts +23 -3
- package/src/boot/acp-server-help.ts +16 -0
- package/src/boot/daemon-prompt-turn.ts +31 -10
- package/src/boot/daemon-tools/index.ts +7 -1
- package/src/budget/codex-credit-pace.ts +84 -0
- package/src/budget/codex-credit-plan.ts +154 -0
- package/src/budget/codex-reset-credit-state.ts +2 -0
- package/src/chat/chat-cli.ts +4 -1
- package/src/chat/short-question.ts +40 -0
- package/src/cli/agent-cli.ts +24 -15
- package/src/cli/ask-bare.ts +42 -0
- package/src/cli/cli-user-error.ts +25 -0
- package/src/cli/codex-credit-plan-view.ts +25 -0
- package/src/cli/dependence-gauge.ts +237 -0
- package/src/cli/doctor-cli.ts +5 -0
- package/src/cli/doctor-modes.ts +48 -0
- package/src/cli/intake-cli.ts +12 -2
- package/src/cli/intake-source-cli.ts +58 -0
- package/src/cli/machine-cli.ts +23 -0
- package/src/cli/msg-cli.ts +137 -0
- package/src/cli/plugin-cli.ts +25 -0
- package/src/cli/pr-cli.ts +36 -0
- package/src/cli/provider-cli.ts +5 -0
- package/src/cli/release-cli.ts +42 -1
- package/src/cli/release-run-if-ready.ts +48 -0
- package/src/cli/setup-cli.ts +27 -6
- package/src/cli/setup-recommend.ts +8 -0
- package/src/cli/usage-cli.ts +11 -1
- package/src/codex/setup.ts +27 -8
- package/src/dashboard/index.ts +1 -1
- package/src/discord.ts +56 -6
- package/src/domains/core-tools.ts +37 -14
- package/src/domains/outbound-alert.ts +50 -11
- package/src/exec-requests/planner.ts +140 -0
- package/src/exec-requests/runner.ts +338 -0
- package/src/exec-requests/store.ts +76 -0
- package/src/field/field-media.ts +8 -3
- package/src/field/field-reel.ts +196 -0
- package/src/grok/credential.ts +7 -5
- package/src/harness/aside-run-status.ts +33 -0
- package/src/harness/browser-verify.ts +48 -17
- package/src/harness/harness-cli-command.ts +25 -3
- package/src/harness/image-load-state.ts +21 -0
- package/src/harness/process-garbage.ts +45 -0
- package/src/index.ts +86 -14
- package/src/instance/leader.ts +13 -1
- package/src/intake-plane/collect-github.ts +5 -3
- package/src/intake-plane/collect-telegram-saved.ts +15 -3
- package/src/intake-plane/discord-seat-work.ts +56 -0
- package/src/intake-plane/intake-sources.ts +202 -0
- package/src/intake-plane/items.ts +28 -12
- package/src/intake-plane/seat-doc-route.ts +207 -0
- package/src/intake-plane/submit-intake-work.ts +9 -1
- package/src/intake-plane/telegram-seat-work.ts +72 -0
- package/src/llm/model-defaults.ts +24 -1
- package/src/llm.ts +5 -4
- package/src/machines/machine-ledger.ts +127 -0
- package/src/market/pack-internal-refs.ts +28 -0
- package/src/market/publish.ts +31 -4
- package/src/maturity/feature-maturity.ts +65 -0
- package/src/model-tier/turn-tier.ts +24 -0
- package/src/msg/msg-store.ts +149 -0
- package/src/native-tool-catalog.ts +2 -1
- package/src/nexus/api/chat-fast-path-config.ts +28 -0
- package/src/nexus/api/consult-requests.ts +117 -0
- package/src/nexus/api/discord-trigger-bot.ts +3 -0
- package/src/nexus/api/exec-requests.ts +51 -0
- package/src/nexus/api/field-uploads.ts +40 -3
- package/src/nexus/api/graph-approvals-feed.ts +140 -0
- package/src/nexus/api/graph-approvals.ts +71 -4
- package/src/nexus/api/http-server.ts +61 -7
- package/src/nexus/api/seat-requests.ts +254 -0
- package/src/nexus/api/trace-run-scan.ts +37 -0
- package/src/nexus/api/trace.ts +19 -3
- package/src/nexus/index.ts +3 -0
- package/src/nexus/setup-status.ts +4 -5
- package/src/notify/llm-budget-alert.ts +16 -10
- package/src/oauth/codex-account-rotation.ts +26 -4
- package/src/oauth/codex-account-store.ts +20 -2
- package/src/oauth/codex-browser-login.ts +173 -0
- package/src/oauth/codex.ts +4 -1
- package/src/oauth/fallback-chain.ts +5 -5
- package/src/oauth/store.ts +42 -5
- package/src/onboarding/continue-here.ts +29 -0
- package/src/onboarding/entry-hints.ts +8 -0
- package/src/onboarding/full-screen-io.ts +35 -3
- package/src/onboarding/help/skills.md +1 -1
- package/src/onboarding/non-interactive.ts +1 -0
- package/src/onboarding/subscription-hint.ts +27 -0
- package/src/onboarding/web-first.ts +83 -0
- package/src/onboarding.ts +230 -23
- package/src/plugins/maker/node-install.ts +92 -0
- package/src/plugins/maker/node-maker.ts +139 -0
- package/src/plugins/maker/plugin-maker.ts +19 -7
- package/src/policy/llm-policy.ts +268 -0
- package/src/prompt-library/session-guidance.ts +7 -1
- package/src/prompt-library/universal-preamble.ts +2 -2
- package/src/relay/relay-cli.ts +20 -0
- package/src/relay/relay-server.ts +220 -0
- package/src/release-loop/checklist.ts +9 -8
- package/src/self-dev/decompose.ts +1 -0
- package/src/self-dev/dev-pipeline.ts +6 -3
- package/src/self-dev/run-record-mirror.ts +44 -0
- package/src/self-dev/run-store.ts +12 -3
- package/src/self-implement/harness-policy-gates.ts +4 -1
- package/src/self-implement/report-origin.ts +12 -1
- package/src/session/chat.ts +17 -5
- package/src/skills/shared-agent-skill-roots.ts +25 -0
- package/src/steward/failure-streak.ts +78 -0
- package/src/steward/steward-cards.ts +24 -0
- package/src/steward/triage-budget.ts +127 -0
- package/src/steward/triage-plan.ts +75 -0
- package/src/steward/triage.ts +244 -42
- package/src/task-cards/card-store.ts +1 -1
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +11 -5
- package/src/task-orchestrator/surfaces/pod-ledger-events.ts +68 -0
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +78 -4
- package/src/telegram.ts +38 -2
- package/src/tool-runtime/elanous-skills-list-runtime.ts +3 -3
- package/src/tool-runtime/self-cognition-runtimes.ts +160 -2
- package/src/user-config.ts +80 -4
- package/src/version/packed-revision.json +1 -1
- package/src/video-pipeline/recipes/character.ts +42 -5
- package/src/video-pipeline/recipes/ffmpeg.ts +1 -1
- package/src/video-pipeline/recipes/free-line.ts +16 -3
- package/src/video-pipeline/recipes/remote.ts +7 -1
- package/src/video-pipeline/vflow-reference.ts +109 -0
- package/apps/pwa/out/_next/static/chunks/1169-696263ac7af8c4d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1883.c91a2648af4842fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1947-9d64872eeb9cfdec.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2808-dc06482994b40f70.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3165-95515ca5f299a120.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3190-e9282753e7ebde85.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4259-99614149577e77cb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4283-329eabeaf233e6cd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4328-cb5058b1acc2de5e.js +0 -25
- package/apps/pwa/out/_next/static/chunks/4801-ecee6ef62b349b18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5110-8058791dd25a2e14.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5330-c41e9670f594c38f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5423-e95fd125d1794349.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5478-b5eae07dc79ea619.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5787-96ee6642b9a661e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6448-79d72368589f98bc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6522-9866c002b5531836.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6729-b4a558b7aa891e0c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6969-cb03f9410b7d2e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7007-3b9861091bb8dd21.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7930-8dc7e20fc6fc19ca.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8071-187edbc22a86741f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8198-d4101ad70cb9a579.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8250.4a2c5553a07cdf47.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8293-123b91e7b0330dc0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8296-d6d5d87dfa911d41.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8305-603963c33ee8eec3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8612-4a0066f20951b8fd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9181-cf4e1b61c4783f92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9262-26c2ecbd5a67e4a2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9328-45abdebce4fbecb7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9355-dbc67205adf8745f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9763.255b90dd69875689.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9933-66d31ec25c0231af.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-4e586142da455a25.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-889ad5cddcf45b0d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-47ba401d56572c27.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-0b54f9c83458e038.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-4fcc296ef6c8ed90.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-fe9fda494a87f4e5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-f9642e25ce129590.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-e1063e8ba5f3e2e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-9d0df30027d8aa9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d97adf2c9455b8d0.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-236b80d202110362.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-21a30f35f1a276ad.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b1a5be357c4fa25d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-1705a60303f7f573.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-76b337c11ad0e237.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-7c0f85fbb56f64c6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-65ca16b20617b44d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-e7f9b8fc29411c1d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-92ea6213d1aeec04.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-a913a005aacb5476.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-2d78804a40dfdef6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-08a99148e780ac00.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-deb674e7ed36f815.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-49f8de0534d192d7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-bd06fb55f4da0381.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-ba162a28d2176e18.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-1ff1a37b550ab41a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-ccaa698f56c0fbdb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-53b944082de1d7db.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-9b70adf1552789bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-30d482aa63241fe8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-aecf786f54651d8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-6ab9b62c7b6e08f2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-08949735455e0c9a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-a1479005234a46c2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-c3890e90bd232732.js +0 -1
- package/apps/pwa/out/_next/static/chunks/beb5cf10-9adcc10b83930090.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-d68d41c88e05c801.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-11c63971b7a9593b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-d09452353bd0d9cc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-184d727cebeca767.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-21bbbbac99b720b1.js +0 -1
- package/apps/pwa/out/_next/static/css/eaa74e8bb2f62269.css +0 -1
- package/apps/pwa/out/_next/static/qMI0jnlZaBjjm4aIDYNGM/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{qMI0jnlZaBjjm4aIDYNGM → keajW7DDyghEs1y8oJe6T}/_ssgManifest.js +0 -0
|
@@ -7,6 +7,7 @@ import { debug } from '../../src/debug/log.js';
|
|
|
7
7
|
import { emitDecision } from '../../src/live/detail-switch.js';
|
|
8
8
|
import { parse } from '../../src/agent-substrate/pr-comment-meta.js';
|
|
9
9
|
import { classifyReviewRounds, matchMustFix, type MustFixVerdict } from './must-fix-match.js';
|
|
10
|
+
import { verifyCleanCheckout, type CheckoutResult } from './clean-checkout.js';
|
|
10
11
|
|
|
11
12
|
interface PullRequest {
|
|
12
13
|
number: number;
|
|
@@ -17,7 +18,7 @@ interface PullRequest {
|
|
|
17
18
|
files: Array<{ path: string }>;
|
|
18
19
|
}
|
|
19
20
|
interface Match { pr: number; text: string; verdict: MustFixVerdict | 'rereviewed' | 'open'; run?: string; round?: number; resolvedIn?: number[] }
|
|
20
|
-
interface GraphContext { runId?: string; input?: { windowMinutes?: number; mustFixByPr?: Record<string, string[]
|
|
21
|
+
interface GraphContext { runId?: string; input?: { windowMinutes?: number; mustFixByPr?: Record<string, string[]>; postVerified?: boolean }; outputs?: Record<string, unknown>; decision?: typeof emitDecision }
|
|
21
22
|
export type RunCommand = (args: string[]) => string;
|
|
22
23
|
// 상주 루프(30분마다)는 사람 계정 한도를 쓰지 않는다 — 부른 쪽이 GH_TOKEN 을 주지 않았으면 GitHub App 설치 토큰으로(09-29 한도 소진).
|
|
23
24
|
// 발급 함수마다 한 번(프로세스 전역 한 칸이면 앞선 호출의 진짜 토큰이 주입한 가짜를 이긴다 — 09-29 시험 실패 출력에 토큰이 찍혔다).
|
|
@@ -93,7 +94,7 @@ export function resolvedInPrs(body: string | undefined): number[] {
|
|
|
93
94
|
return match ? [...match[1]!.matchAll(/#(\d+)/g)].map((m) => Number(m[1])) : [];
|
|
94
95
|
}
|
|
95
96
|
|
|
96
|
-
export function runMission(mission: string, ctx: GraphContext, run: RunCommand = gh, now = new Date()): Record<string, unknown> {
|
|
97
|
+
export function runMission(mission: string, ctx: GraphContext, run: RunCommand = gh, now = new Date(), verify = verifyCleanCheckout): Record<string, unknown> {
|
|
97
98
|
switch (mission) {
|
|
98
99
|
case 'collect': {
|
|
99
100
|
const minutes = ctx.input?.windowMinutes ?? 60;
|
|
@@ -156,6 +157,37 @@ export function runMission(mission: string, ctx: GraphContext, run: RunCommand =
|
|
|
156
157
|
}).map((pr) => pr.number);
|
|
157
158
|
return { outcome: 'ok', verifyNeeded, verified };
|
|
158
159
|
}
|
|
160
|
+
case 'clean-checkout': {
|
|
161
|
+
const needed = output(ctx, 'verify-needed').verifyNeeded as number[];
|
|
162
|
+
const candidates = pullRequests(ctx).filter((pr) => needed.includes(pr.number))
|
|
163
|
+
.sort((a, b) => Date.parse(a.mergedAt ?? '') - Date.parse(b.mergedAt ?? '') || a.number - b.number).slice(0, 2);
|
|
164
|
+
const results: CheckoutResult[] = [];
|
|
165
|
+
for (const pr of candidates) {
|
|
166
|
+
let mergeSha = '';
|
|
167
|
+
try {
|
|
168
|
+
const details = JSON.parse(run(['pr', 'view', String(pr.number), '--json', 'mergeCommit'])) as { mergeCommit?: { oid?: string } };
|
|
169
|
+
mergeSha = details.mergeCommit?.oid ?? '';
|
|
170
|
+
if (!/^[a-f0-9]{40,64}$/i.test(mergeSha)) throw new Error('mergeCommit.oid unavailable or invalid');
|
|
171
|
+
} catch (error) {
|
|
172
|
+
results.push({ pr: pr.number, mergeSha: '', ok: false, steps: [{ name: 'merge commit', ok: false, seconds: 0, tail: String(error).split('\n').slice(-20).join('\n') }] });
|
|
173
|
+
debug.log('landing-heal', 'clean-checkout', { pr: pr.number, ok: false, seconds: 0 });
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
let result: CheckoutResult;
|
|
177
|
+
try {
|
|
178
|
+
result = verify({ pr: pr.number, mergeSha, files: pr.files.map((file) => file.path) });
|
|
179
|
+
} catch (error) {
|
|
180
|
+
result = { pr: pr.number, mergeSha, ok: false, steps: [{ name: 'clean checkout', ok: false, seconds: 0, tail: String(error).split('\n').slice(-20).join('\n') }] };
|
|
181
|
+
}
|
|
182
|
+
results.push(result);
|
|
183
|
+
debug.log('landing-heal', 'clean-checkout', { pr: pr.number, ok: result.ok, seconds: result.steps.reduce((sum, step) => sum + step.seconds, 0) });
|
|
184
|
+
if (result.ok && ctx.input?.postVerified === true) {
|
|
185
|
+
const summary = result.steps.filter((step) => step.name !== 'worktree remove').map((step) => step.name).join(' · ');
|
|
186
|
+
run(['pr', 'comment', String(pr.number), '--body', `landing-verified: ${mergeSha.slice(0, 8)} · clean checkout · ${summary}`]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return { outcome: 'ok', results };
|
|
190
|
+
}
|
|
159
191
|
case 'report': {
|
|
160
192
|
const collected = pullRequests(ctx);
|
|
161
193
|
const prs = collected.length;
|
|
@@ -165,6 +197,9 @@ export function runMission(mission: string, ctx: GraphContext, run: RunCommand =
|
|
|
165
197
|
const mustFixRereviewed = matches.filter((item) => item.verdict === 'rereviewed').length;
|
|
166
198
|
const mustFixUnresolved = mustFixOpen + mustFixUnknown;
|
|
167
199
|
const verifyNeeded = output(ctx, 'verify-needed').verifyNeeded as number[];
|
|
200
|
+
const checkoutResults = ctx.outputs?.['clean-checkout'] ? output(ctx, 'clean-checkout').results as CheckoutResult[] : undefined;
|
|
201
|
+
const checkoutPassed = checkoutResults?.filter((item) => item.ok).length ?? 0;
|
|
202
|
+
const checkoutFailed = checkoutResults?.filter((item) => !item.ok).map((item) => item.pr) ?? [];
|
|
168
203
|
const mergedPrs = new Set(collected.filter((pr) => pr.state === 'MERGED').map((pr) => pr.number));
|
|
169
204
|
const landedOpen = matches.filter((item) => item.verdict === 'open' && mergedPrs.has(item.pr));
|
|
170
205
|
const unresolved = matches.filter((item) => item.verdict === 'unresolved').length;
|
|
@@ -174,7 +209,7 @@ export function runMission(mission: string, ctx: GraphContext, run: RunCommand =
|
|
|
174
209
|
(ctx.decision ?? emitDecision)({
|
|
175
210
|
kind: 'VERIFY',
|
|
176
211
|
what: `착지·치유 관측: PR ${prs}건${open ? ` — 열린 must-fix ${open}건(PR ${openPrs.map((pr) => `#${pr}`).join(', ')})` : ''}`,
|
|
177
|
-
reason: `열린 채 착지 ${open}건 · 미해결 ${unresolved}건 · 미상 ${mustFixUnknown}건 · 다시 봄 ${mustFixRereviewed}건 · 착지 뒤 검증 필요 ${verifyNeeded.length}
|
|
212
|
+
reason: `열린 채 착지 ${open}건 · 미해결 ${unresolved}건 · 미상 ${mustFixUnknown}건 · 다시 봄 ${mustFixRereviewed}건 · 착지 뒤 검증 필요 ${verifyNeeded.length}건${checkoutResults ? ` · 검증 성공 ${checkoutPassed} · 실패 ${checkoutFailed.length}(PR ${checkoutFailed.map((pr) => `#${pr}`).join(', ') || '없음'})` : ''}`,
|
|
178
213
|
purpose: '착지 후속 작업 관측', target: 'landing-heal', ...(ctx.runId ? { runId: ctx.runId } : {}),
|
|
179
214
|
});
|
|
180
215
|
return { outcome: 'ok', prs, mustFixUnresolved, mustFixOpen, mustFixUnknown, mustFixRereviewed, verifyNeeded };
|
|
@@ -43,7 +43,8 @@ const tab = 'button[aria-label^="switch to "]';
|
|
|
43
43
|
const terminal = '.xterm';
|
|
44
44
|
const connected = '[aria-live="polite"]';
|
|
45
45
|
// ChatInput 의 입력창(`apps/pwa/src/components/chat/ChatInput.tsx` placeholder) — testid 가 없어 placeholder 로 고른다.
|
|
46
|
-
|
|
46
|
+
// Placeholder text is user copy (10-01 #22558 localized it and silently broke C1·C2a·C2b) — match both locales.
|
|
47
|
+
const chatInput = 'textarea[placeholder^="message"], textarea[placeholder^="메시지"]';
|
|
47
48
|
const intakeField = '[data-testid="intake-front-door-field"]';
|
|
48
49
|
const terminalIdentity = `(() => { const sessionId = localStorage.getItem('elanous.daemon.sessionId'); const terminalId = Array.from(document.querySelectorAll('button[aria-label^="switch to "]')).find(node => node.title?.includes('지금 보는 터미널'))?.getAttribute('aria-label')?.slice('switch to '.length); return sessionId && terminalId ? { sessionId, terminalId } : null; })()`;
|
|
49
50
|
const identity = (value: unknown): { sessionId: string; terminalId: string } | null => {
|
package/scripts/public-export.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* 표지 = LEAK_MARKERS(줄 단위 흔적 · 문서 링크는 private 에 «실재»하는 이름만) — rc 는 이것만 센다.
|
|
12
12
|
* 참고 = `runtime-path-outside-export`(공개 코드가 공개본에 없는 경로를 쥔다) — rc 에 안 센다 · 판정은 public-export-test-run 의 exportOnly=0.
|
|
13
13
|
* bun scripts/public-export.ts --out <dir> # 트리 복사 ⊕ replace 적용 ⊕ 유출 검사(있으면 rc=1 · 복사는 한다)
|
|
14
|
+
* bun scripts/public-export.ts --leak-check --files a b … # 그 파일들만 — prepare 와 같은 변환·치환 «뒤»에 잰다(pr land 가 부른다)
|
|
14
15
|
*
|
|
15
16
|
* ⛔ 목록 밖은 «전부» private 에 남는다 — 이 스크립트가 무엇을 «더» 넣는 일은 없다.
|
|
16
17
|
* ⛔ 원천은 `git ls-files`(추적 파일)다 — 작업 트리의 untracked·ignored 는 원리상 안 간다.
|
|
@@ -379,6 +380,9 @@ export function run(argv: readonly string[], root = resolve(import.meta.dir, '..
|
|
|
379
380
|
const pathIndex = argv.indexOf('--path');
|
|
380
381
|
const pathPrefix = pathIndex >= 0 ? argv[pathIndex + 1] : undefined;
|
|
381
382
|
if (pathIndex >= 0 && !pathPrefix) { console.error('⛔ --path needs a prefix'); return 2; }
|
|
383
|
+
const filesIndex = argv.indexOf('--files');
|
|
384
|
+
const onlyFiles = filesIndex >= 0 ? new Set(argv.slice(filesIndex + 1).filter((a) => !a.startsWith('--'))) : undefined;
|
|
385
|
+
const inScope = (f: string) => (!pathPrefix || f.startsWith(pathPrefix)) && (!onlyFiles || onlyFiles.has(f));
|
|
382
386
|
const outIndex = argv.indexOf('--out');
|
|
383
387
|
const out = outIndex >= 0 ? argv[outIndex + 1] : undefined;
|
|
384
388
|
if (outIndex >= 0 && !out) { console.error('⛔ --out needs a directory'); return 2; }
|
|
@@ -411,9 +415,9 @@ export function run(argv: readonly string[], root = resolve(import.meta.dir, '..
|
|
|
411
415
|
if (!existsSync(join(root, src))) { console.error(`⛔ replace source missing: ${src}`); return 2; }
|
|
412
416
|
contents.set(dest, readFileSync(join(root, src), 'utf8'));
|
|
413
417
|
}
|
|
414
|
-
const scanned =
|
|
418
|
+
const scanned = files.filter(inScope);
|
|
415
419
|
const realDocs = privateDocNames(tracked, files);
|
|
416
|
-
const before = [...scanLeaks(root, scanned, LEAK_MARKERS, undefined, realDocs), ...scanRuntimePaths(root, files, tracked).filter((h) =>
|
|
420
|
+
const before = [...scanLeaks(root, scanned, LEAK_MARKERS, undefined, realDocs), ...scanRuntimePaths(root, files, tracked).filter((h) => inScope(h.file))];
|
|
417
421
|
const transformedByRules = transformExportFiles(contents, config, files);
|
|
418
422
|
// ⭐ 개인 치환표(저장소 밖) — 규칙 변환 «뒤»에 건다. 없으면 «못 봤다»로 막는다.
|
|
419
423
|
const redactions = loadPrivateRedactions();
|
|
@@ -445,12 +449,12 @@ export function run(argv: readonly string[], root = resolve(import.meta.dir, '..
|
|
|
445
449
|
console.error(`exported ${files.length} files (+${Object.keys(config.replace).length} replaced) → ${target}`);
|
|
446
450
|
}
|
|
447
451
|
const hits = [
|
|
448
|
-
...gitIgnoredExportFiles(root, files).filter(
|
|
452
|
+
...gitIgnoredExportFiles(root, files).filter(inScope).map((file) => ({ marker: 'export-gitignored', file, line: 0, text: '공개본 .gitignore 에 걸린다 — 커밋하면 사라진다' })),
|
|
449
453
|
...scanLeaks(root, scanned, LEAK_MARKERS, transformed.contents, realDocs),
|
|
450
454
|
...(redactions ? scanLeaks(root, scanned, privateIdentifierMarkers(redactions), transformed.contents, realDocs) : []),
|
|
451
455
|
...(!redactions && !noPrivateList ? [{ marker: 'private-identifiers-unchecked', file: privateRedactionsPath(), line: 0, text: 'private redaction list missing' }] : []),
|
|
452
456
|
];
|
|
453
|
-
const advisory = scanRuntimePaths(root, files, tracked, transformed.contents).filter((h) =>
|
|
457
|
+
const advisory = scanRuntimePaths(root, files, tracked, transformed.contents).filter((h) => inScope(h.file));
|
|
454
458
|
const summary = summarizeLeaks(hits);
|
|
455
459
|
const advisorySummary = summarizeLeaks(advisory);
|
|
456
460
|
const beforeSummary = summarizeLeaks(before);
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { junitFailures, parseFailures } from './gate-diff';
|
|
4
|
+
|
|
5
|
+
export function baselineFromCutLogs(dir: string, expectedCommit?: string): { failures: string[]; errors: string[]; files: number; complete: boolean } | undefined {
|
|
6
|
+
if (!existsSync(dir)) return undefined;
|
|
7
|
+
let names: string[];
|
|
8
|
+
try { names = readdirSync(dir); }
|
|
9
|
+
catch { return undefined; }
|
|
10
|
+
const metadata = (name: string): { rc?: unknown; files?: unknown; shardCount?: unknown; commit?: unknown } | undefined => {
|
|
11
|
+
try {
|
|
12
|
+
const value: unknown = JSON.parse(readFileSync(join(dir, name), 'utf8'));
|
|
13
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
14
|
+
} catch { return undefined; }
|
|
15
|
+
};
|
|
16
|
+
const logs = names.filter((name) => /^pod-\d+(?:-[\w-]+)?\.log$/.test(name));
|
|
17
|
+
if (!logs.length) return undefined;
|
|
18
|
+
|
|
19
|
+
// A split parent's output is partial; only its measured leaves belong to the cut.
|
|
20
|
+
const leaves = logs.filter((name) => !names.some((other) => other.startsWith(name.slice(0, -4) + '-')
|
|
21
|
+
&& /^pod-\d+(?:-[\w-]+)?\.json$/.test(other)));
|
|
22
|
+
const failures = new Set<string>();
|
|
23
|
+
const errors = new Set<string>();
|
|
24
|
+
const files = new Set<string>();
|
|
25
|
+
let measured = true;
|
|
26
|
+
const measuredFiles = new Set<string>();
|
|
27
|
+
for (const name of leaves) {
|
|
28
|
+
const prefix = name.slice(0, -4);
|
|
29
|
+
const junitName = `${prefix}.junit.xml`;
|
|
30
|
+
let output: string;
|
|
31
|
+
let xml: string;
|
|
32
|
+
try {
|
|
33
|
+
output = readFileSync(join(dir, name), 'utf8');
|
|
34
|
+
xml = names.includes(junitName) ? readFileSync(join(dir, junitName), 'utf8') : '';
|
|
35
|
+
} catch {
|
|
36
|
+
measured = false;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
const named = new Set([...parseFailures(output), ...junitFailures(xml)]);
|
|
40
|
+
for (const id of named) failures.add(id);
|
|
41
|
+
for (const match of xml.matchAll(/<testsuite\b[^>]*\bfile="([^"]+)"/g)) files.add(match[1]!);
|
|
42
|
+
const shard = names.includes(`${prefix}.json`) ? metadata(`${prefix}.json`) : undefined;
|
|
43
|
+
const ran = [...output.matchAll(/Ran (\d+) tests? across (\d+) files?/g)].at(-1);
|
|
44
|
+
const reported = [...output.matchAll(/(?:^|\n)\s*(\d+) fail\s*(?=\n|$)/g)].at(-1);
|
|
45
|
+
const junitFiles = new Set([...xml.matchAll(/<testsuite\b[^>]*\bfile="([^"]+)"/g)].map((match) => match[1]!));
|
|
46
|
+
if (!shard || (shard.rc !== 0 && shard.rc !== 1) || (expectedCommit && shard.commit !== expectedCommit)
|
|
47
|
+
|| !ran || Number(ran[1]) < 1 || junitFiles.size !== Number(ran[2])
|
|
48
|
+
|| !reported || Number(reported[1]) !== named.size || !Array.isArray(shard.files)
|
|
49
|
+
|| !shard.files.every((file): file is string => typeof file === 'string')) measured = false;
|
|
50
|
+
else for (const file of shard.files) measuredFiles.add(file);
|
|
51
|
+
let file: string | undefined;
|
|
52
|
+
let attributedErrors = 0;
|
|
53
|
+
for (const raw of output.replace(/\x1b\[[0-9;]*[A-Za-z]/g, '').split(/\r?\n/)) {
|
|
54
|
+
const line = raw.trim();
|
|
55
|
+
const header = /^(?:\.\/)?((?:[\w.-]+\/)+[\w.-]+\.test\.tsx?):(?:\s|$)/.exec(line);
|
|
56
|
+
if (header) file = header[1];
|
|
57
|
+
if (/^# Unhandled error between tests/.test(line)) {
|
|
58
|
+
if (!file) measured = false;
|
|
59
|
+
else {
|
|
60
|
+
errors.add(`${file} > [error]`);
|
|
61
|
+
attributedErrors++;
|
|
62
|
+
file = undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const reportedErrors = [...output.matchAll(/(?:^|\n)\s*(\d+) errors?\s*(?=\n|$)/g)].at(-1);
|
|
67
|
+
if ((reportedErrors ? Number(reportedErrors[1]) : 0) !== attributedErrors
|
|
68
|
+
|| (shard?.rc === 1 && named.size + attributedErrors === 0)
|
|
69
|
+
|| (shard?.rc === 0 && named.size + attributedErrors > 0)) measured = false;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const plannedFiles = new Set<string>();
|
|
73
|
+
const roots = names.filter((name) => /^pod-\d+\.json$/.test(name));
|
|
74
|
+
const allMetadata = names.filter((name) => /^pod-\d+(?:-[\w-]+)?\.json$/.test(name)).map(metadata);
|
|
75
|
+
if (allMetadata.some((shard) => !shard || (expectedCommit && shard.commit !== expectedCommit))) measured = false;
|
|
76
|
+
const rootMetadata = roots.map(metadata);
|
|
77
|
+
for (const shard of rootMetadata) {
|
|
78
|
+
if (!shard || (expectedCommit && shard.commit !== expectedCommit)
|
|
79
|
+
|| !Array.isArray(shard.files) || !shard.files.every((file): file is string => typeof file === 'string')) measured = false;
|
|
80
|
+
else for (const file of shard.files) plannedFiles.add(file);
|
|
81
|
+
}
|
|
82
|
+
const count = rootMetadata[0]?.shardCount;
|
|
83
|
+
const complete = measured && rootMetadata.every((shard) => shard?.shardCount === count)
|
|
84
|
+
&& roots.length === count
|
|
85
|
+
&& plannedFiles.size > 0 && plannedFiles.size === measuredFiles.size
|
|
86
|
+
&& [...plannedFiles].every((file) => measuredFiles.has(file))
|
|
87
|
+
&& files.size === measuredFiles.size && [...measuredFiles].every((file) => files.has(file))
|
|
88
|
+
&& typeof count === 'number' && Number.isSafeInteger(count) && count > 0
|
|
89
|
+
&& Array.from({ length: count }, (_, index) => index).every((index) => names.includes(`pod-${index}.json`)
|
|
90
|
+
&& leaves.some((name) => name.startsWith(`pod-${index}.`) || name.startsWith(`pod-${index}-`)));
|
|
91
|
+
return { failures: [...failures].sort(), errors: [...errors].sort(), files: files.size, complete };
|
|
92
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
/** The identity is the complete Bun failure id, not its containing test file. */
|
|
5
|
+
export function loadEnvKnownFailures(repoRoot: string): Set<string> {
|
|
6
|
+
const path = join(repoRoot, 'test/env-known-failures.json');
|
|
7
|
+
if (!existsSync(path)) return new Set();
|
|
8
|
+
let data: unknown;
|
|
9
|
+
try { data = JSON.parse(readFileSync(path, 'utf8')); }
|
|
10
|
+
catch (error) { throw new Error(`invalid environment known failures: ${path}`, { cause: error }); }
|
|
11
|
+
if (!data || typeof data !== 'object' || Array.isArray(data)
|
|
12
|
+
|| !('env' in data) || data.env !== 'linux-pod'
|
|
13
|
+
|| !('measuredAt' in data) || typeof data.measuredAt !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(data.measuredAt)
|
|
14
|
+
|| !('tests' in data) || !Array.isArray(data.tests)
|
|
15
|
+
|| !data.tests.every((entry: unknown) => entry && typeof entry === 'object' && !Array.isArray(entry)
|
|
16
|
+
&& 'id' in entry && typeof entry.id === 'string'
|
|
17
|
+
&& /^(?:[\w.-]+\/)+[\w.-]+\.test\.tsx? > \S.*$/.test(entry.id)
|
|
18
|
+
&& 'reason' in entry && typeof entry.reason === 'string' && entry.reason.trim().length > 0)) {
|
|
19
|
+
throw new Error(`invalid environment known failures: ${path}`);
|
|
20
|
+
}
|
|
21
|
+
const ids = data.tests.map((entry: { id: string }) => entry.id);
|
|
22
|
+
if (new Set(ids).size !== ids.length) throw new Error(`invalid environment known failures: duplicate id in ${path}`);
|
|
23
|
+
return new Set(ids);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function splitKnownEnv(failures: string[], known: Set<string>): { counted: string[]; knownEnv: string[] } {
|
|
27
|
+
return {
|
|
28
|
+
counted: failures.filter((id) => !known.has(id)),
|
|
29
|
+
knownEnv: failures.filter((id) => known.has(id)),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
@@ -8,6 +8,8 @@ import { debug } from '../../src/debug/log.js';
|
|
|
8
8
|
import { effectiveInstanceRoot, releaseLedgerRoot } from '../../src/instance/resolve.js';
|
|
9
9
|
import { getElanousConfigDirOverride } from '../../src/elanous-config-dir.js';
|
|
10
10
|
import { diffFailures, junitFailures, parseFailures } from './gate-diff';
|
|
11
|
+
import { loadEnvKnownFailures, splitKnownEnv } from './env-known-failures';
|
|
12
|
+
import { baselineFromCutLogs } from './baseline-from-logs';
|
|
11
13
|
import { planShards, readFileDurations } from './shard-plan';
|
|
12
14
|
import { deriveCdpTestPatterns } from '../test-deterministic';
|
|
13
15
|
import { emitNodeResult, readGraphContext } from './node-verdict.js';
|
|
@@ -16,7 +18,7 @@ import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from
|
|
|
16
18
|
import { PodPoolScheduler, parsePodPool, checkPodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
17
19
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
18
20
|
|
|
19
|
-
interface CommandResult { rc: number; output: string }
|
|
21
|
+
interface CommandResult { rc: number; output: string; passedIds?: string[] }
|
|
20
22
|
export interface GateRunner {
|
|
21
23
|
command(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
22
24
|
localCommand(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
@@ -38,7 +40,7 @@ export interface GateOptions {
|
|
|
38
40
|
instanceRoot?: string;
|
|
39
41
|
ledgerRoot?: string;
|
|
40
42
|
repo?: string;
|
|
41
|
-
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string };
|
|
43
|
+
pod?: { pool: string; shards?: number; shardTimeoutSeconds?: number; durationSource?: string; bunCache?: string };
|
|
42
44
|
}
|
|
43
45
|
export interface GateResult {
|
|
44
46
|
outcome: 'ok' | 'regression' | 'error';
|
|
@@ -46,7 +48,9 @@ export interface GateResult {
|
|
|
46
48
|
introduced: string[];
|
|
47
49
|
preexisting: number;
|
|
48
50
|
fixed: number;
|
|
49
|
-
|
|
51
|
+
knownEnv: number;
|
|
52
|
+
knownEnvCleared: string[];
|
|
53
|
+
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
50
54
|
durationMs: number;
|
|
51
55
|
error?: string;
|
|
52
56
|
stalledShards?: Array<{ shard: number; files: string[]; reason: 'incomplete' | 'no-output' | 'job-failed' | 'unattributed'; lastFile?: string; summaryFailures?: number; namedFailures?: number; detail?: string }>;
|
|
@@ -64,6 +68,38 @@ function check(result: CommandResult, label: string): void {
|
|
|
64
68
|
}
|
|
65
69
|
|
|
66
70
|
type SweepFailures = { failures: string[]; errors: string[] };
|
|
71
|
+
|
|
72
|
+
/** Only named, completed JUnit cases can prove a listed failure passed in this cut. */
|
|
73
|
+
function junitPassedIds(xml: string): string[] {
|
|
74
|
+
const passed = new Set<string>();
|
|
75
|
+
const suites: string[] = [];
|
|
76
|
+
let file: string | undefined;
|
|
77
|
+
let open: { id: string; passed: boolean } | undefined;
|
|
78
|
+
const decode = (value: string) => value.replace(/&(?:quot|apos|lt|gt|amp|#\d+|#x[\da-fA-F]+);/g, (entity) => {
|
|
79
|
+
const named: Record<string, string> = { '"': '"', ''': "'", '<': '<', '>': '>', '&': '&' };
|
|
80
|
+
if (entity in named) return named[entity]!;
|
|
81
|
+
const code = entity.startsWith('&#x') ? Number.parseInt(entity.slice(3, -1), 16) : Number(entity.slice(2, -1));
|
|
82
|
+
return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : entity;
|
|
83
|
+
});
|
|
84
|
+
for (const match of xml.matchAll(/<(\/?)\s*(testsuite|testcase|failure|error|skipped)\b([^>]*?)(\/?)>/g)) {
|
|
85
|
+
const [, closing, tag, attrs, selfClosing] = match;
|
|
86
|
+
const attr = (name: string) => { const found = new RegExp(`\\b${name}="([^"]*)"`).exec(attrs!); return found ? decode(found[1]!) : undefined; };
|
|
87
|
+
if (tag === 'testsuite') {
|
|
88
|
+
if (closing) { suites.pop(); if (!suites.length) file = undefined; }
|
|
89
|
+
else if (!selfClosing) { suites.push(attr('name') ?? ''); if (suites.length === 1) file = attr('file') ?? attr('name'); }
|
|
90
|
+
} else if (tag === 'testcase') {
|
|
91
|
+
if (closing) { if (open?.passed) passed.add(open.id); open = undefined; }
|
|
92
|
+
else {
|
|
93
|
+
const name = [...suites.slice(1), attr('name') ?? ''].join(' > ');
|
|
94
|
+
const path = attr('file') ?? file;
|
|
95
|
+
const id = path && name ? `${path} > ${name}` : undefined;
|
|
96
|
+
if (selfClosing) { if (id) passed.add(id); }
|
|
97
|
+
else open = id ? { id, passed: true } : undefined;
|
|
98
|
+
}
|
|
99
|
+
} else if (!closing && open) open.passed = false;
|
|
100
|
+
}
|
|
101
|
+
return [...passed].sort();
|
|
102
|
+
}
|
|
67
103
|
// A test may spawn a nested `bun test` whose own summary lines land earlier in the same output
|
|
68
104
|
// (09-29 0.2.4 gate: «0 fail · Ran 2 tests across 1 file» at line 6765 of a 60064-test sweep) —
|
|
69
105
|
// the sweep's own summary is always the «last» one, so read the last match, never the first.
|
|
@@ -76,6 +112,11 @@ const summaryCount = (output: string, label: string) => Number(lastMatch(output,
|
|
|
76
112
|
/** bun reads a bare positional as a substring filter (`test` matches every *.test.ts); `./` makes it a path. */
|
|
77
113
|
const asPath = (path: string) => path.startsWith('./') || path.startsWith('/') ? path : `./${path}`;
|
|
78
114
|
export const POD_ISOLATION_MEMORY_LIMIT = '32Gi';
|
|
115
|
+
/** Integration tests that the Pod sweep does not run — each is judged by a real run elsewhere at the cut.
|
|
116
|
+
* 0.2.7 run 5 (10-01): `scripts/install.test.ts` (real `bun pm pack` → `bun add` installs, 301 s alone on node-b)
|
|
117
|
+
* sat in a shard of its own past the 1200 s deadline with no output and stopped the whole gate. Judged on the
|
|
118
|
+
* mbp at the cut instead (TD1 moves it to the integration line). Keep this list short and explained. */
|
|
119
|
+
export const POD_SWEEP_INTEGRATION_ONLY: readonly string[] = ['scripts/install.test.ts'];
|
|
79
120
|
const tail40 = (output: string) => output.trimEnd().split(/\r?\n/).slice(-40).join('\n');
|
|
80
121
|
const lastStartedTestFile = (output: string, paths: string[]): string | undefined => {
|
|
81
122
|
let last: string | undefined;
|
|
@@ -92,10 +133,15 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
92
133
|
const reportedErrors = summaryCount(output, 'error');
|
|
93
134
|
const errorCount = Number.isNaN(reportedErrors) ? 0 : reportedErrors;
|
|
94
135
|
const incomplete = (reason: string) => new Error(`${label} incomplete (rc=${run.rc}; Ran=${ran ? ran[0] : 'missing'}; errors=${Number.isNaN(reportedErrors) ? 'missing' : errorCount}; ${reason})\n${tail40(output)}`);
|
|
95
|
-
if (!ran || (Number(ran[1]) === 0 && errorCount === 0) || (run.rc !== 0 && run.rc !== 1)) throw incomplete('summary/exit');
|
|
96
136
|
const count = summaryCount(output, 'fail');
|
|
97
137
|
const failures = parseFailures(output);
|
|
98
138
|
const occurrences = [...output.matchAll(/\(fail\)\s+.+?(?:\s+\[[\d.]+(?:ms|s)\])?\s*$/gm)].length;
|
|
139
|
+
// A test that leaves process.exitCode set leaks a non-zero exit into a run whose own summary is complete and clean
|
|
140
|
+
// (0.2.7 gate: agent-cli at v0.2.6 · 12 pass · 0 fail · rc 2). The summary is the verdict; read that exit as clean.
|
|
141
|
+
const leakedExit = !!ran && run.rc !== 0 && run.rc !== 1 && count === 0 && errorCount === 0 && occurrences === 0
|
|
142
|
+
&& !/^# Unhandled error between tests/m.test(output);
|
|
143
|
+
const rc = leakedExit ? 0 : run.rc;
|
|
144
|
+
if (!ran || (Number(ran[1]) === 0 && errorCount === 0) || (rc !== 0 && rc !== 1)) throw incomplete('summary/exit');
|
|
99
145
|
// Bun reports file-level unhandled errors separately from test failures.
|
|
100
146
|
const errors: string[] = [];
|
|
101
147
|
let file: string | undefined;
|
|
@@ -110,7 +156,7 @@ function failuresOf(run: CommandResult, label: string): SweepFailures {
|
|
|
110
156
|
}
|
|
111
157
|
if (errors.length !== errorCount) throw incomplete(`error attribution identified=${errors.length}`);
|
|
112
158
|
if (!Number.isFinite(count) || count !== occurrences || (count > 0 && failures.length === 0)
|
|
113
|
-
|| (
|
|
159
|
+
|| (rc === 1 && count === 0 && errorCount === 0) || (rc === 0 && count + errorCount > 0)) {
|
|
114
160
|
throw incomplete(`failure attribution incomplete: summary=${count}, identified=${failures.length}`);
|
|
115
161
|
}
|
|
116
162
|
return { failures, errors: [...new Set(errors)].sort() };
|
|
@@ -156,7 +202,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
156
202
|
const cdpPatterns = cdp.output.split(/\r?\n/).filter((path) => path.endsWith('.test.ts'));
|
|
157
203
|
// CDP 시험은 `test:deterministic` 이 스스로 뺀다 — 조각에 그것만 남으면 bun «시험 없음» exit 1 을 «불완전»으로 읽었다(09-30 G1d 실측 둘).
|
|
158
204
|
// bun 의 탐색은 숨은 디렉터리(`.x/`)를 안 연다 — 사람의 `bun test` 가 한 번도 안 돌리는 빈 픽스처가 조각에 혼자 남아 «Ran 0 tests» 를 «불완전»으로 읽었다(09-30 G1e 5번 조각).
|
|
159
|
-
const assignable = files.filter((file) => !cdpPatterns.includes(file) && !
|
|
205
|
+
const assignable = files.filter((file) => !cdpPatterns.includes(file) && !POD_SWEEP_INTEGRATION_ONLY.includes(file)
|
|
206
|
+
&& !file.split('/').some((part) => part.startsWith('.')));
|
|
207
|
+
const integrationOnly = files.filter((file) => POD_SWEEP_INTEGRATION_ONLY.includes(file));
|
|
208
|
+
if (integrationOnly.length) debug.log('release-loop.gate', 'pod-sweep-integration-only', { files: integrationOnly });
|
|
160
209
|
if (!assignable.length) throw new Error('sweep incomplete: no tests ran');
|
|
161
210
|
const durations = pod.durationSource ? readFileDurations(pod.durationSource) : new Map<string, number>();
|
|
162
211
|
const shards = planShards(assignable, durations, shardCount);
|
|
@@ -169,7 +218,10 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
169
218
|
const ready = poolOverride ? undefined : checkPodPool(parsePodPool(pod.pool), defaultKubectl);
|
|
170
219
|
if (ready && !ready.ok) throw new Error('pod command: 풀의 노드가 하나도 준비되지 않았다');
|
|
171
220
|
const poolScheduler = poolOverride ?? new PodPoolScheduler(ready!.ready);
|
|
172
|
-
const
|
|
221
|
+
const configCache = typeof pod.bunCache === 'string' ? pod.bunCache.trim() : '';
|
|
222
|
+
const envCache = process.env[POD_BUN_CACHE_HOST_PATH]?.trim();
|
|
223
|
+
const bunCache = configCache || envCache || undefined;
|
|
224
|
+
debug.log('release-loop.gate', 'pod-bun-cache', { source: configCache ? 'config' : envCache ? 'env' : 'none' });
|
|
173
225
|
const cachePrefix = bunCache ? `${podBunCacheVolume(bunCache).shellPrefix} ` : '';
|
|
174
226
|
const jobLogTail = (job: PodCommandResult): string => {
|
|
175
227
|
if (podLogTail) return podLogTail(job);
|
|
@@ -179,7 +231,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
179
231
|
}
|
|
180
232
|
return '';
|
|
181
233
|
};
|
|
182
|
-
type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number };
|
|
234
|
+
type ShardRun = { output: string; rc: number; pass: number; fail: number; errors: number; ran: number; files: number; passedIds: string[] };
|
|
183
235
|
type ShardReason = NonNullable<GateResult['stalledShards']>[number]['reason'];
|
|
184
236
|
// 깊이 끝 OOM 조각은 «조각 전체»를 파일 하나씩 격리한다 — 상한 40 이면 나머지가 못 잰 채 남았다(09-30 ① 7번 조각 110 파일).
|
|
185
237
|
const isolationRemaining = shards.map((item) => item.files.length);
|
|
@@ -233,7 +285,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
233
285
|
const destination = join(logDir, `pod-${shard}${branch}.log`);
|
|
234
286
|
if (output !== undefined) writeFileSync(destination, output, { mode: 0o600 });
|
|
235
287
|
else rmSync(destination, { force: true });
|
|
236
|
-
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null, files: paths, plannedSeconds: estimated(paths) }) + '\n', { mode: 0o600 });
|
|
288
|
+
writeFileSync(join(logDir, `pod-${shard}${branch}.json`), JSON.stringify({ durationMs, rc: rc ?? null, files: paths, plannedSeconds: estimated(paths), shardCount: shards.length, commit }) + '\n', { mode: 0o600 });
|
|
237
289
|
const junitDestination = join(logDir, `pod-${shard}${branch}.junit.xml`);
|
|
238
290
|
if (junit !== undefined) writeFileSync(junitDestination, junit, { mode: 0o600 });
|
|
239
291
|
else rmSync(junitDestination, { force: true });
|
|
@@ -308,7 +360,7 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
308
360
|
}
|
|
309
361
|
return { runs: [{ output: clean!, rc: rc!, pass: passes, fail: fails,
|
|
310
362
|
errors: Number.isNaN(summaryCount(clean!, 'error')) ? 0 : summaryCount(clean!, 'error'),
|
|
311
|
-
ran: Number(ran![1]), files: Number(ran![2]) }], stalled: [] };
|
|
363
|
+
ran: Number(ran![1]), files: Number(ran![2]), passedIds: junit ? junitPassedIds(junit) : [] }], stalled: [] };
|
|
312
364
|
};
|
|
313
365
|
// 모든 조각이 끝난 뒤 판정한다 — 한 조각의 예외로 먼저 돌아가면 다른 Pod 가 도는 채로 정리가 시작된다(#22002 리뷰 R3).
|
|
314
366
|
const settled = await Promise.allSettled(shards.map((item, shard) => runShard(item.files, shard)));
|
|
@@ -323,7 +375,8 @@ export function createGateRunner(repo: string, remote?: string, commandOverride?
|
|
|
323
375
|
if (stalledShards.length) throw new StalledPodShards(stalledShards, total);
|
|
324
376
|
if (total.ran === 0 && total.errors === 0) throw new Error('sweep incomplete: no tests ran');
|
|
325
377
|
const outputs = measured.map((run) => run.output.replace(/(?:^|\n)\s*\d+ (?:pass|fail|errors?)\s*(?=\n|$)/g, '\n').replace(/Ran \d+ tests? across \d+ files?\.?/g, ''));
|
|
326
|
-
return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n
|
|
378
|
+
return { rc: total.fail + total.errors ? 1 : 0, output: outputs.join('\n') + `\n${total.pass} pass\n${total.fail} fail\n${total.errors} errors\nRan ${total.ran} tests across ${total.files} files.\n`,
|
|
379
|
+
passedIds: [...new Set(measured.flatMap((run) => run.passedIds))].sort() };
|
|
327
380
|
};
|
|
328
381
|
return {
|
|
329
382
|
command,
|
|
@@ -455,7 +508,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
455
508
|
return baselineCommit(root, version);
|
|
456
509
|
};
|
|
457
510
|
const repo = resolve(opts.repo ?? process.cwd());
|
|
458
|
-
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, durationMs: 0 };
|
|
511
|
+
const result: GateResult = { outcome: 'error', commit: opts.commit, introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], durationMs: 0 };
|
|
459
512
|
let work: string | undefined;
|
|
460
513
|
let baseSnapshot = false;
|
|
461
514
|
let cutFailures: SweepFailures | undefined;
|
|
@@ -510,8 +563,9 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
510
563
|
cutFailures = cut;
|
|
511
564
|
const baseSha = opts.baselineCommit ?? baselineReleaseCommit(opts.baselineVersion);
|
|
512
565
|
const cached = cachedBaseline(opts.baselineVersion, baseSha);
|
|
513
|
-
const
|
|
514
|
-
|
|
566
|
+
const recorded = cached ? undefined : baselineFromCutLogs(join(ledger, 'release', opts.baselineVersion, 'gate-logs', 'cut'), baseSha);
|
|
567
|
+
const trusted = cached?.saved ?? (recorded?.complete ? recorded : undefined);
|
|
568
|
+
result.baselineSource = cached ? cached.source : recorded?.complete ? 'cut-logs' : 'swept';
|
|
515
569
|
debug.log('release-loop.gate', 'baseline', { version: opts.baselineVersion, source: result.baselineSource, commit: baseSha });
|
|
516
570
|
let baseTree: string | undefined;
|
|
517
571
|
const getBaseTree = async () => {
|
|
@@ -534,7 +588,14 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
534
588
|
baseline = failuresOf(baseRun, 'baseline sweep');
|
|
535
589
|
}
|
|
536
590
|
for (const id of [...baseline.failures, ...baseline.errors, ...cut.failures, ...cut.errors]) fileOf(id);
|
|
537
|
-
const
|
|
591
|
+
const known = opts.pod ? loadEnvKnownFailures(repo) : new Set<string>();
|
|
592
|
+
const cutIds = [...cut.failures, ...cut.errors];
|
|
593
|
+
const baselineIds = [...baseline.failures, ...baseline.errors];
|
|
594
|
+
const countedCut = splitKnownEnv(cutIds, known);
|
|
595
|
+
const countedBaseline = splitKnownEnv(baselineIds, known);
|
|
596
|
+
result.knownEnv = countedCut.knownEnv.length;
|
|
597
|
+
result.knownEnvCleared = [...new Set(cutRun.passedIds ?? [])].filter((id) => known.has(id) && !cutIds.includes(id)).sort();
|
|
598
|
+
const diff = diffFailures(countedCut.counted, countedBaseline.counted);
|
|
538
599
|
result.fixed = diff.fixed.length;
|
|
539
600
|
result.preexisting = diff.common.length;
|
|
540
601
|
for (const file of new Set(diff.newFailures.map(fileOf))) {
|
|
@@ -584,7 +645,7 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
584
645
|
if (opts.remote) runner.remoteMirror = undefined;
|
|
585
646
|
result.durationMs = Date.now() - start;
|
|
586
647
|
}
|
|
587
|
-
if (
|
|
648
|
+
if (cutFailures && !result.stalledShards?.length) {
|
|
588
649
|
try {
|
|
589
650
|
for (const location of roots) {
|
|
590
651
|
const path = failureFile(location, opts.version);
|
|
@@ -608,10 +669,10 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
608
669
|
export function graphGateResult(result: GateResult, graph: boolean) {
|
|
609
670
|
return { ...result, ...(graph && result.outcome === 'regression' ? { outcome: 'fail' as const } : {}),
|
|
610
671
|
verdict: result.outcome === 'ok' ? 'pass' as const : 'fail' as const,
|
|
611
|
-
summary: result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}` };
|
|
672
|
+
summary: (result.outcome === 'ok' ? `새 회귀 ${result.introduced.length} · 기존 ${result.preexisting} · 고침 ${result.fixed}` : `게이트 ${result.outcome}: ${result.error ?? result.introduced.length + ' new regressions'}`) + ` · 환경 알려진 실패 ${result.knownEnv}` };
|
|
612
673
|
}
|
|
613
674
|
|
|
614
|
-
function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOptions | 'help' {
|
|
675
|
+
export function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOptions | 'help' {
|
|
615
676
|
const context = env.ELANOUS_GRAPH_CONTEXT && args.every((arg) => arg === '--json') ? readGraphContext(env) : undefined;
|
|
616
677
|
const fromGraph: Record<string, unknown> = context?.input ?? {};
|
|
617
678
|
const graphCommit = context?.outputs['version-release']?.commit;
|
|
@@ -629,6 +690,7 @@ function parseOptions(args: string[], env: NodeJS.ProcessEnv): GateOptions | 'he
|
|
|
629
690
|
pool: fromGraph.gatePodPool,
|
|
630
691
|
...(fromGraph.gatePodShards !== undefined ? { shards: Number(fromGraph.gatePodShards) } : {}),
|
|
631
692
|
...(fromGraph.gatePodShardTimeoutSeconds !== undefined ? { shardTimeoutSeconds: Number(fromGraph.gatePodShardTimeoutSeconds) } : {}),
|
|
693
|
+
...(typeof fromGraph.gatePodBunCache === 'string' && fromGraph.gatePodBunCache.trim() ? { bunCache: fromGraph.gatePodBunCache } : {}),
|
|
632
694
|
} : undefined,
|
|
633
695
|
};
|
|
634
696
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -665,7 +727,7 @@ if (import.meta.main) {
|
|
|
665
727
|
version = opts.version;
|
|
666
728
|
result = await judgeGate(opts);
|
|
667
729
|
} catch (error) {
|
|
668
|
-
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, durationMs: Date.now() - start, error: String(error) };
|
|
730
|
+
result = { outcome: 'error', commit: '', introduced: [], preexisting: 0, fixed: 0, knownEnv: 0, knownEnvCleared: [], durationMs: Date.now() - start, error: String(error) };
|
|
669
731
|
}
|
|
670
732
|
if (result.error) console.error(result.error);
|
|
671
733
|
debug.log('release-loop.gate', 'result', { version, outcome: result.outcome });
|