elanous 0.2.10 → 0.2.12
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/-6XrAoD3eU7NyMHAE4xPS/_buildManifest.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1147-466ec15afacc1a65.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1159-0c5ef8bc21cb6afb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1394-ee3795b112907122.js +1 -0
- package/apps/pwa/out/_next/static/chunks/1452.93700483b9d4fc61.js +9 -0
- package/apps/pwa/out/_next/static/chunks/{6062-73e68343a9814f6a.js → 1829-b453a1b22d3999bb.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/2176-c379e3ebce874151.js +1 -0
- package/apps/pwa/out/_next/static/chunks/2426.8c6ee0c668d53def.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3170-7ec60a56fa0e4728.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3278-ae6ef084769f6195.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3454-988a53dac60a1763.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3638-aa80b4641ea439db.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3735-fe3402553ac1c9c6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/3869-3cd46526879f454c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/390.c373e31b3b9d0c41.js +5 -0
- package/apps/pwa/out/_next/static/chunks/3992-2e522a8ac5f2afde.js +25 -0
- package/apps/pwa/out/_next/static/chunks/4215-c9d91dd644ec1719.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4508-5d0161d1e724d735.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4685-c3c1c625b71accd1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4733-623a79345b171e1c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/4883.3bd969ea1d3aaaff.js +2 -0
- package/apps/pwa/out/_next/static/chunks/5532.6899a12958a89b72.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8067-87ce253ec2f23697.js → 5845-e6c003ce8d5fd36a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5978-b27004c677119115.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{8046cadf-211ca3c0e00f2b72.js → 5cd7e3a9-404fd6b8d0ecd44c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/6048-4c5c33579bc4ef78.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6303-422dbf0f67c72aac.js +1 -0
- package/apps/pwa/out/_next/static/chunks/6751-bb3595948b2e311b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7274-19b7279f18b47ab3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7277-0b2fc7c16ba2d64b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{4323.c5dfb6f503304775.js → 7334.a6679d9168f37aa8.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/7551-a0e0707b79164ddd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7758-0b018a9e4fc56200.js +1 -0
- package/apps/pwa/out/_next/static/chunks/7777-79f845045aa1ed86.js +1 -0
- package/apps/pwa/out/_next/static/chunks/79f8be9e-957d68913b590b08.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8025-cc24ee5d8f2fb6f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8039-df92e08fede5de8f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8206-c6684dcde12be721.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8421-1c4acfb1395a0edf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8605-0259994816604c49.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8725.bf46bb1395dd0fa2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8734-252d511cd276c0dc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/8834-ca1cb09c1cb1a6bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2415.ed6428ff334c7ce2.js → 9269.d2a4795bc6812717.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/9538-2816b90923606459.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9656-c1540088f5859c80.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9906-482c6619b3fdda20.js +1 -0
- package/apps/pwa/out/_next/static/chunks/9920-5cfdc5e8fd209825.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-d16993889b125c42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-cb169620945d1952.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-cefe8169e36cf62a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/board/page-d2e35228bc497119.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/{page-54b32bd27726588d.js → page-5c05b0da4bc1392c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e0ccdb576812b523.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/page-813c4a52c9d05ca8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/consult/page-4731f2c215358937.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/page-a022b257950f39e6.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-a2ffc717e04b8b0a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/page-b59ee4e70fc18fcf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/exec/page-a67e11ba962337b5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/field/page-c172dabd796eb7a3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-d16993889b125c42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/inside/page-1efd0cb9e0ed9d13.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/page-d5dc7ff69a37b4cf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/layout-e71c84189e8c683e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/live/page-1d6a533ce5a6efcb.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/market/page-dfca9b7d2de0b135.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-793fa536b3a6ef37.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/page-6dd3bfbbde2a5c2c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/not-found-490653e8d4cc8852.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-77447356baad75d2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-29e961191f6057df.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-c822f55b884d9104.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-fa4ccfc432424e52.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-6d604227e1664b71.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-9c7e8f744a765daf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-4048543b4b7ad5e0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-4c3a3126abed551a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-64b0aecb1ca75e75.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-4292d0149020e8a7.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/page-db14d0be7f1c460c.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-e5d8072cd7cfef73.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-d16993889b125c42.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-9547b5cec7cf76bf.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/page-e6dcb83265530636.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-45248f08d6aecae5.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-2cddeb9e2aeedc55.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/term/page-e297866051595d81.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/today/page-baba2e047872fb0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-e895da4ec9cac345.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/page-4344faf20216233b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-698bb29f11864b89.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-bd6a6dc1ddc273f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-2e9e2fb01c9da1c9.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-d62ceb13411ebba4.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{2ea96958-e8c7e112de50b067.js → f48170cc-d05d28bf87a43bf4.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/framework-172630e6aa5e2d7f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-app-f0fe2d7477845181.js +1 -0
- package/apps/pwa/out/_next/static/chunks/main-fc98ac70b44eccb0.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_app-5bd7f10271f72413.js +1 -0
- package/apps/pwa/out/_next/static/chunks/pages/_error-97d0d85ea05fef9b.js +1 -0
- package/apps/pwa/out/_next/static/chunks/webpack-9e4d02d567b98240.js +1 -0
- package/apps/pwa/out/_next/static/css/128fb7fb670b2fbf.css +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +23 -23
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +23 -23
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +23 -23
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +22 -22
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +22 -22
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +22 -22
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +23 -23
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +23 -23
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +23 -23
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +23 -23
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +23 -23
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +23 -23
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +22 -22
- package/apps/pwa/out/inside/index.html +1 -0
- package/apps/pwa/out/inside/index.txt +31 -0
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +23 -23
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +23 -23
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +22 -22
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +22 -22
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +23 -23
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +23 -23
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +22 -22
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +22 -22
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +22 -22
- package/apps/pwa/out/outputs/index.html +1 -0
- package/apps/pwa/out/outputs/index.txt +30 -0
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +23 -23
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +23 -23
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +22 -22
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +23 -23
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +23 -23
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +23 -23
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +23 -23
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +23 -23
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +22 -22
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +23 -23
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +23 -24
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +23 -23
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +23 -23
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +23 -23
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +23 -23
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +23 -23
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +23 -24
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +23 -23
- package/graphs/context/nightly-condense.yaml +21 -0
- package/graphs/context/recipes.yaml +12 -0
- package/graphs/demo/inside-seed.yaml +20 -0
- package/graphs/demo/recipes.yaml +12 -0
- package/graphs/drafts/draft-cleanup.yaml +19 -0
- package/graphs/drafts/recipes.yaml +23 -0
- package/graphs/intake/intake-daily.yaml +2 -1
- package/graphs/ops/recipes.yaml +3 -0
- package/graphs/ops/secret-exposure.yaml +24 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -1
- package/graphs/video/explainer-line.yaml +2 -2
- package/package.json +2 -1
- package/plugins/card-followup/examples/sample-card.png +0 -0
- package/plugins/elanous-hwp/README.md +25 -0
- package/plugins/elanous-hwp/cli.ts +22 -0
- package/plugins/elanous-hwp/converter.ts +237 -0
- package/plugins/elanous-hwp/fixtures/fixed-controls.hwpx +0 -0
- package/plugins/elanous-hwp/fixtures/fixed-table-controls.hwpx +0 -0
- package/plugins/elanous-hwp/fixtures/fixed.hwpx +0 -0
- package/plugins/elanous-hwp/plugin.json +16 -0
- package/plugins/elanous-hwp/plugin.ts +40 -0
- package/scripts/ci-public-export-leak-gate.ts +75 -14
- package/scripts/ci-public-leak-gate.ts +44 -9
- package/scripts/context-hooks/claude-stop.sh +4 -0
- package/scripts/context-hooks/emit.ts +24 -0
- package/scripts/context-hooks/post-tool-use.sh +4 -0
- package/scripts/coord-channel-watch.sh +12 -0
- package/scripts/coord-requests-bridge.ts +143 -0
- package/scripts/field-feed/lib.ts +4 -0
- package/scripts/fleet/node-setup.sh +20 -3
- package/scripts/homebrew-formula-bump.ts +69 -0
- package/scripts/lib/telegram-inject-args.ts +31 -0
- package/scripts/marketing/measure-site-numbers.ts +169 -0
- package/scripts/measure-gate-shards.ts +309 -0
- package/scripts/measure-pod-memory-by-goal.ts +130 -0
- package/scripts/mission-request-judge.ts +31 -1
- package/scripts/morning-report.ts +2 -2
- package/scripts/opportunity-scan.ts +1 -1
- package/scripts/release-loop/auto-approve-node.ts +21 -4
- package/scripts/release-loop/checklist-gate-node.ts +2 -1
- package/scripts/release-loop/cut-branch.ts +84 -0
- package/scripts/release-loop/gate-node.ts +22 -15
- package/scripts/release-loop/mac-smoke-node.ts +229 -0
- package/scripts/release-loop/node-verdict.ts +3 -3
- package/scripts/release-loop/ops-upgrade-node.ts +47 -6
- package/scripts/release-loop/publish-node.ts +4 -1
- package/scripts/release-loop/tui-node-checks.ts +42 -0
- package/scripts/release-loop/tui-sim-node.ts +32 -8
- package/scripts/release-loop/unattended-release.ts +5 -3
- package/scripts/release-loop/version-node.ts +74 -13
- package/scripts/signal-digest-cycle.ts +2 -7
- package/scripts/signal-digest-photo.ts +11 -0
- package/scripts/tech1-public-measure.ts +94 -0
- package/scripts/telegram-inject.ts +14 -6
- package/scripts/test-diet/node.ts +1 -1
- package/scripts/test-diet/remeasure-list.ts +112 -0
- package/scripts/tui-regress.ts +2 -2
- package/scripts/ux/demo-rehearsal.ts +247 -0
- package/scripts/ux/demo-seed.ts +108 -0
- package/scripts/ux/lib/rehearsal-verdict.ts +80 -0
- package/skills/diagram-master/SKILL.md +366 -0
- package/skills/diagram-master/references/engines/excalidraw/color-palette.md +67 -0
- package/skills/diagram-master/references/engines/excalidraw/element-templates.md +182 -0
- package/skills/diagram-master/references/engines/excalidraw/json-schema.md +71 -0
- package/skills/diagram-master/references/engines/excalidraw/render_excalidraw.py +189 -0
- package/skills/diagram-master/references/engines/excalidraw/render_template.html +57 -0
- package/skills/diagram-master/references/engines/matplotlib/matplotlib-patterns.md +235 -0
- package/skills/diagram-master/references/engines/matplotlib/render_matplotlib.py +184 -0
- package/skills/diagram-master/references/engines/mermaid/domain-templates.md +435 -0
- package/skills/diagram-master/references/engines/mermaid/render_mermaid.py +116 -0
- package/skills/diagram-master/references/engines/mermaid/syntax-reference.md +2247 -0
- package/skills/diagram-master/references/engines/nanobannana/render_nanobannana2.py +168 -0
- package/skills/diagram-master/references/engines/svg/color-palette.md +188 -0
- package/skills/diagram-master/references/engines/svg/html-base-template.html +332 -0
- package/skills/diagram-master/references/engines/svg/interactivity-patterns.md +435 -0
- package/skills/diagram-master/references/engines/svg/render_interactive_svg.py +212 -0
- package/skills/diagram-master/references/engines/svg/svg-element-templates.md +349 -0
- package/skills/diagram-master/references/examples/README.md +69 -0
- package/skills/diagram-master/references/examples/ref_grid_architecture.png +0 -0
- package/skills/diagram-master/references/examples/ref_layered_flow.png +0 -0
- package/skills/diagram-master/references/pyproject.toml +16 -0
- package/skills/diagram-master/references/quality-checklist.md +33 -0
- package/skills/diagram-master/references/routing-guide.md +164 -0
- package/skills/diagram-master/references/uv.lock +1356 -0
- package/skills/google-workspace/SKILL.md +33 -0
- package/skills/google-workspace/scripts/gws-safe.sh +177 -0
- package/skills/grill-me/SKILL.md +1 -1
- package/skills/lecture-note-digitizer/SKILL.md +926 -0
- package/skills/lecture-note-digitizer/references/convert_to_pdf.py +30 -0
- package/skills/lecture-note-digitizer/references/extract_slide_diagram.py +73 -0
- package/skills/lecture-note-digitizer/references/html-template.md +167 -0
- package/skills/lecture-note-digitizer/references/katex-style-guide.md +165 -0
- package/skills/omni-crawl/SKILL.md +21 -11
- package/skills/omni-crawl/scripts/main.ts +6 -6
- package/skills/omni-crawl/src/router.ts +3 -3
- package/skills/omni-digest/SKILL.md +17 -9
- package/skills/omni-digest/digest.md +2 -2
- package/skills/omni-digest/src/fetch.ts +30 -3
- package/skills/pitch-deck/SKILL.md +2 -2
- package/skills/pitch-deck/template.html +2 -2
- package/skills/youtube-master/SKILL.md +163 -0
- package/skills/youtube-master/package-lock.json +558 -0
- package/skills/youtube-master/package.json +13 -0
- package/skills/youtube-master/references/study-note-template.md +124 -0
- package/skills/youtube-master/scripts/main.ts +435 -0
- package/skills/youtube-master/scripts/transcribe-local.ts +24 -0
- package/skills/youtube-master/src/audio.ts +63 -0
- package/skills/youtube-master/src/domain-staging.ts +50 -0
- package/skills/youtube-master/src/env.ts +36 -0
- package/skills/youtube-master/src/obsidian.ts +155 -0
- package/skills/youtube-master/src/router.ts +110 -0
- package/skills/youtube-master/src/stt.ts +199 -0
- package/skills/youtube-master/src/study-note.ts +175 -0
- package/skills/youtube-master/src/summarize.ts +358 -0
- package/skills/youtube-master/src/transcript.ts +76 -0
- package/skills/youtube-master/src/types.ts +91 -0
- package/skills/youtube-master/src/util.ts +93 -0
- package/skills/youtube-master/src/youtube.ts +70 -0
- package/skills/youtube-master/tsconfig.json +13 -0
- package/src/acp/server.ts +5 -2
- package/src/agent-mission/driver.ts +215 -23
- package/src/agent-mission/pty-decision.ts +71 -0
- package/src/agent-mission/review-loop.ts +30 -0
- package/src/autopilot/build/llm-conflict-merge.ts +16 -3
- package/src/autopilot/pty-control-loop.ts +4 -1
- package/src/autopilot/tool-registry.ts +2 -0
- package/src/boot/daemon-prompt-turn.ts +5 -0
- package/src/boot/daemon-runtime.ts +9 -3
- package/src/boot/daemon-tools/web-search.ts +3 -0
- package/src/card-followup/core.ts +109 -0
- package/src/chat/index.ts +2 -0
- package/src/cli/drive-input-jail.ts +101 -0
- package/src/cli/hooks-cli.ts +17 -3
- package/src/cli/make-cli.ts +43 -0
- package/src/cli/persona-cli.ts +98 -9
- package/src/cli/plugin-cli.ts +31 -22
- package/src/cli/pod-cli.ts +36 -9
- package/src/cli/pod-memory-advice.ts +35 -0
- package/src/cli/pr-cli.ts +51 -15
- package/src/cli/pty-drive-cli.ts +41 -7
- package/src/cli/pty-drive-verdict.ts +40 -0
- package/src/cli/release-cli.ts +63 -10
- package/src/cli/setup-cli.ts +43 -1
- package/src/cli/skills-cli.ts +17 -1
- package/src/context-bus/claude-hooks-install.ts +10 -0
- package/src/context-bus/context-now-surfaces.ts +65 -0
- package/src/context-bus/context-now.ts +104 -0
- package/src/context-bus/coord-events.ts +2 -2
- package/src/context-bus/external-events.ts +52 -0
- package/src/context-bus/long-term-memory.ts +108 -0
- package/src/context-bus/nightly-condense.ts +153 -0
- package/src/context-bus/session-events.ts +35 -0
- package/src/dashboard/dashboard-status-lines.ts +2 -0
- package/src/dashboard/index.ts +17 -4
- package/src/dashboard/slash-runtime/dashboard-handlers.ts +31 -1
- package/src/decisions/decision-cards.ts +10 -3
- package/src/decisions/decision-ledger.ts +13 -5
- package/src/discord/sprint21-runtime.ts +5 -1
- package/src/discord-self-message.ts +32 -0
- package/src/discord-slash-wire.ts +5 -1
- package/src/discord.ts +17 -1
- package/src/domains/core-tools.ts +3 -2
- package/src/domains/outbound-alert.ts +105 -32
- package/src/domains/outbound-log-probe.ts +8 -0
- package/src/domains/telegram-kind-route.ts +51 -13
- package/src/exec-requests/default-outputs.ts +25 -0
- package/src/exec-requests/planner.ts +10 -2
- package/src/exec-requests/runner.ts +20 -6
- package/src/exec-requests/store.ts +3 -0
- package/src/execution-loop/launch-gate.ts +109 -0
- package/src/field/field-reel.ts +5 -1
- package/src/field-note/index.ts +201 -0
- package/src/field-note/telegram.ts +142 -0
- package/src/field-note/transcribe.ts +248 -0
- package/src/graph-kinds/registry.ts +3 -3
- package/src/graph-runner/graph-cli.ts +61 -1
- package/src/graph-runner/graph-grow-llm.ts +47 -0
- package/src/graph-runner/graph-grow.ts +50 -0
- package/src/graph-runner/runner.ts +329 -33
- package/src/grok/agent-search.ts +8 -2
- package/src/guardian/secret-exposure-scan.ts +101 -0
- package/src/harness/harness-cli-command.ts +375 -13
- package/src/harness/harness-pod-dispatch.ts +43 -23
- package/src/harness/harness-seams.ts +38 -4
- package/src/harness/harness-stop.ts +23 -4
- package/src/harness/launch-registry.ts +91 -0
- package/src/harness/process-garbage.ts +5 -3
- package/src/hooks/consult-intake.ts +153 -0
- package/src/hooks/dispatch.ts +82 -0
- package/src/hooks/expose.ts +254 -0
- package/src/hooks/github-shadow.ts +108 -0
- package/src/hooks/providers.ts +71 -5
- package/src/hooks/queue.ts +1 -1
- package/src/hooks/receiver.ts +94 -16
- package/src/index.ts +76 -14
- package/src/intake-plane/categorize.ts +18 -46
- package/src/intake-plane/discord-seat-work.ts +38 -2
- package/src/intake-plane/front-route-classifier.ts +33 -39
- package/src/intake-plane/persona-answer.ts +30 -0
- package/src/intake-plane/runtime-callables.ts +8 -1
- package/src/intake-plane/telegram-seat-work.ts +36 -2
- package/src/intake-plane/wish-card.ts +33 -0
- package/src/intake-plane/wish-folder.ts +67 -0
- package/src/intent-gate/gate.ts +30 -10
- package/src/live/detail-switch.ts +5 -0
- package/src/llm/difficulty.ts +57 -0
- package/src/llm/judge-layer.ts +101 -0
- package/src/llm/mission-router.ts +33 -19
- package/src/loops/contract/examples/cmo-seat.loop.yaml +51 -0
- package/src/loops/contract/examples/orchestrator.loop.yaml +57 -0
- package/src/loops/contract/examples/steward.loop.yaml +50 -0
- package/src/loops/contract/loop-agent.schema.json +138 -0
- package/src/loops/contract/validate.ts +64 -0
- package/src/loops/registry.ts +20 -6
- package/src/market/signed-index.ts +4 -0
- package/src/maturity/bot-command-maturity.ts +45 -0
- package/src/maturity/feature-maturity.ts +27 -4
- package/src/model-tier/index.ts +2 -2
- package/src/model-tier/tier-resolver.ts +20 -20
- package/src/model-tier/types.ts +6 -6
- package/src/mss/logging/log-store.ts +15 -0
- package/src/nexus/api/config-model-tier.ts +5 -4
- package/src/nexus/api/fabric-plans.ts +53 -0
- package/src/nexus/api/harness-api.ts +5 -1
- package/src/nexus/api/health.ts +2 -0
- package/src/nexus/api/http-server.ts +67 -8
- package/src/nexus/api/inside-events.ts +226 -0
- package/src/nexus/api/meta-api.ts +21 -8
- package/src/nexus/api/missions.ts +6 -1
- package/src/nexus/api/ops-seats.ts +33 -0
- package/src/nexus/api/outbound-report.ts +40 -7
- package/src/nexus/api/outputs-route.ts +44 -0
- package/src/nexus/api/personas.ts +95 -53
- package/src/nexus/api/seat-requests.ts +44 -2
- package/src/nexus/api/sessions-store.ts +17 -38
- package/src/nexus/api/sub-seat-charters.ts +34 -0
- package/src/nexus/api/tabs-mutations.ts +3 -1
- package/src/nexus/api/task-cards-api.ts +19 -0
- package/src/nexus/api/trace.ts +18 -6
- package/src/nexus/api/workflow-history.ts +88 -0
- package/src/nexus/api/workflow-run-event-bridge.ts +1 -0
- package/src/nexus/api/workflows.ts +132 -6
- package/src/nexus/index.ts +2 -0
- package/src/nexus/kinds/webterm.ts +2 -1
- package/src/nexus/outbound/delivery-ledger.ts +17 -8
- package/src/nexus/outbound/router.ts +8 -3
- package/src/nexus/webterm/pty.ts +6 -2
- package/src/onboarding/cli-auth-status.ts +80 -0
- package/src/onboarding/voice-ai.ts +3 -3
- package/src/org/seat-budget.ts +48 -0
- package/src/org/stall-escalation.ts +63 -0
- package/src/outputs/ledger.ts +120 -0
- package/src/persona/persona-todo.ts +79 -0
- package/src/persona/presets.ts +123 -2
- package/src/persona/team-set.ts +155 -0
- package/src/plugins/install/plugin-install.ts +14 -0
- package/src/plugins/maker/market-bundle.ts +110 -0
- package/src/plugins/maker/plugin-maker.ts +254 -11
- package/src/plugins/maker/wizard-generate.ts +103 -0
- package/src/pod-lease/admission.ts +150 -0
- package/src/pod-lease/dependency-state.ts +85 -0
- package/src/pod-lease/host-lease.ts +127 -0
- package/src/project/project-suggestion.ts +16 -0
- package/src/project/session-context.ts +24 -0
- package/src/release-loop/checklist.ts +17 -5
- package/src/release-loop/feature-store.ts +29 -13
- package/src/release-loop/landed-but-yellow.ts +48 -0
- package/src/release-loop/next-md-merge.ts +58 -0
- package/src/release-loop/release-note.ts +59 -4
- package/src/seat-dispatch/seat-ask.ts +130 -0
- package/src/seat-dispatch/seat-questions.ts +100 -0
- package/src/seat-loop/seat-loop.ts +709 -29
- package/src/seat-loop/seat-report.ts +37 -2
- package/src/self-dev/dev-cli.ts +4 -1
- package/src/self-dev/dev-pipeline.ts +37 -1
- package/src/self-dev/draft-sweep.ts +79 -40
- package/src/self-dev/draft-triage-rules.ts +62 -7
- package/src/self-dev/fabric-plan-core.ts +297 -0
- package/src/self-dev/orchestrate.ts +36 -4
- package/src/self-dev/release-path-guard.ts +62 -0
- package/src/self-dev/shard-aggregate.ts +230 -0
- package/src/self-dev/shard-placement.ts +60 -0
- package/src/self-dev/shard-plan.ts +48 -0
- package/src/self-dev/shard-pod-run.ts +77 -0
- package/src/self-dev/shard-run.ts +105 -0
- package/src/self-implement/budget-gate.ts +29 -12
- package/src/self-implement/gate-baseline.ts +1 -1
- package/src/self-implement/gate-candidate-ranking.ts +60 -0
- package/src/self-implement/gate-cli.ts +40 -1
- package/src/self-implement/gate-scope.ts +44 -13
- package/src/self-implement/graph-variant.ts +171 -0
- package/src/self-implement/harness-policy-gates.ts +1 -1
- package/src/self-implement/headless-elanous-driver.ts +27 -3
- package/src/self-implement/host-regate.ts +22 -1
- package/src/self-implement/importer-test-index.ts +2 -1
- package/src/self-implement/l8-merge-queue.ts +130 -0
- package/src/self-implement/orchestrator.ts +257 -9
- package/src/self-implement/route-consumer-test-index.ts +122 -0
- package/src/self-implement/seams.ts +72 -11
- package/src/self-implement/test-seams.ts +2 -0
- package/src/self-implement/uncommitted-work.ts +59 -0
- package/src/session/index.ts +36 -13
- package/src/skills/gift-install.ts +215 -0
- package/src/skills/tools/web-search.ts +4 -1
- package/src/steward/launch.ts +42 -26
- package/src/steward/steward-cards.ts +17 -9
- package/src/steward/triage.ts +46 -14
- package/src/task-cards/card-cli.ts +25 -1
- package/src/task-orchestrator/mission.ts +5 -0
- package/src/task-orchestrator/store.ts +12 -3
- package/src/task-orchestrator/surfaces/pod-command-job.ts +43 -17
- package/src/task-orchestrator/surfaces/pod-lease.ts +156 -19
- package/src/task-orchestrator/surfaces/pod-pool.ts +127 -26
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +222 -40
- package/src/task-orchestrator/surfaces/self-implement.ts +15 -2
- package/src/task-orchestrator/types.ts +2 -0
- package/src/telegram-card-followup.ts +16 -96
- package/src/telegram-commands.ts +31 -1
- package/src/telegram-project-command.ts +145 -0
- package/src/telegram-report.ts +21 -17
- package/src/telegram.ts +61 -8
- package/src/tool-runtime/self-cognition-runtimes.ts +13 -3
- package/src/user-config.ts +110 -24
- package/src/version/packed-revision.json +1 -1
- package/src/web-search/grok.ts +7 -1
- package/src/web-search/index.ts +28 -1
- package/src/web-search/provider.ts +4 -0
- package/src/web-search/x-intent.ts +5 -0
- package/src/workflow-runtime/executor.ts +277 -47
- package/src/workflow-runtime/index.ts +1 -0
- package/src/workflow-runtime/nodes/subworkflow.ts +60 -0
- package/src/workflow-runtime/plugin-kind-node.ts +1 -1
- package/src/workflow-runtime/schema.ts +79 -4
- package/src/workflow-runtime/types.ts +35 -4
- package/apps/pwa/out/_next/static/2DsiDfDCks44g1cPUMhiW/_buildManifest.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1105-c473ed46272fba79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1377-1efeffbbac0963ff.js +0 -1
- package/apps/pwa/out/_next/static/chunks/1470.98a6ab61ccb4131c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/216-5acd01cbde4acda7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2330-c2f07246c8788af1.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2375-cb6debea56cce4fb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/24-e6d28380efab7faf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2977-e38442e86289f1d3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/2982-2a2df543be3d2e5a.js +0 -25
- package/apps/pwa/out/_next/static/chunks/3403.4bc97b4066945a94.js +0 -5
- package/apps/pwa/out/_next/static/chunks/3615-740dd270764ffa24.js +0 -1
- package/apps/pwa/out/_next/static/chunks/371-4ad6af55dad02ced.js +0 -1
- package/apps/pwa/out/_next/static/chunks/3734-1015fe991eb9b7e7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/4114-3e63117452378807.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5012-a7fe6f3c8b6fe45a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5609-012bb3557211f07e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5783-ef40ebfc91d55230.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5791-bb82743fa6299562.js +0 -1
- package/apps/pwa/out/_next/static/chunks/62-ff227c3edb8b248b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6320-baac91d68843a9a6.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6725-68ab7cc0b1faf00d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/6998-a654fe5c58271600.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7633-1b3709b0854c9412.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7647.2fcd30d894d5ee30.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7673-2eda1507d38dadb8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7951-8b03dbb10b24cd06.js +0 -1
- package/apps/pwa/out/_next/static/chunks/7990-e12efba616f46e03.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8142-744998d65d7bb17e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8195-6fde3d7a39859206.js +0 -10
- package/apps/pwa/out/_next/static/chunks/8619-4365e03a2862830f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/8894-b5fb36bab837cd29.js +0 -1
- package/apps/pwa/out/_next/static/chunks/920-f7dceb78f1c585ef.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9454-84d0d5d77446bd92.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9651-3cd291a6fa1370d2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/988-5f92e7ab7ccea3dd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/9989-5582582f06429840.js +0 -1
- package/apps/pwa/out/_next/static/chunks/ae787e4c-f2998de7f1ba72a4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/_not-found/page-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-d2253d7e58ef1890.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/autopilot/page-5441d4a9ab35e518.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-d9b12d680922d980.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/bots/page-e268d6d145827a22.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/chat/page-f8116559c7424da7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-1085399fd0f32112.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/control/page-1df686a14f65895a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-54e1f966ab60a4dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/editor/page-7ed95f296f5ee8e9.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/page-11edac668d77e5c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/field/page-59e2b9a22cd5a6d5.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/healthz/route-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/intake/page-2976df21ad74ab48.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/layout-34d211a67f16d510.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-8ff652d5d950639b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/market/page-b31610deda76337b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-6cb0e1bbefe5fb3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/morning/page-a286a152cf584576.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-d9237581e7003bcb.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-83274e761643fdd2.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/page-ceee6cfb1d4e6d68.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/page-9c9f8bb3e4a64091.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-c17217603ea3426a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-efb6177cbf1e8a51.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/reflection/page-d307523703bc4df3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/page-462f4e4bab45ce7d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8990e96f33af3837.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e168fa1030af0548.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/page-1233e05a4068bc9c.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-13a1840b1fac337a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/layout-859cea1570b6227a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-64a29fc80b11ab55.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/share/page-68e787339a16b3dc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/page-4fddc90b23feb215.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/page-886ba8260dc79c79.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/term/page-0f26507a1722e40b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-109d4ccd42259c9d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-f5c2a4e510726774.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/vault/page-b2e0039416f42522.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-26102fd7350f171e.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/page-708948bc8a802794.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/page-5e5611620f78fb0b.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-139ba5c367bbf60a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/framework-ea806bb57f2b8923.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-8d2302399c2c8f87.js +0 -1
- package/apps/pwa/out/_next/static/chunks/main-app-202b4e81f33895ea.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_app-b10928c60f0b8488.js +0 -1
- package/apps/pwa/out/_next/static/chunks/pages/_error-689039227d443fab.js +0 -1
- package/apps/pwa/out/_next/static/chunks/webpack-e5bea36f183b25e6.js +0 -1
- package/apps/pwa/out/_next/static/css/43feca5fd387dcbd.css +0 -1
- /package/apps/pwa/out/_next/static/{2DsiDfDCks44g1cPUMhiW → -6XrAoD3eU7NyMHAE4xPS}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import {
|
|
2
|
+
allowsBaselineOnlyFailure,
|
|
3
|
+
buildGateBaselineReport,
|
|
4
|
+
type GateBaselineReport,
|
|
5
|
+
type GatePassedTestEvidence,
|
|
6
|
+
} from '../self-implement/gate-baseline.js';
|
|
7
|
+
import type { GateTestShard } from './shard-plan.js';
|
|
8
|
+
|
|
9
|
+
export interface GateShardAttempt {
|
|
10
|
+
shardId: string;
|
|
11
|
+
attempt: number;
|
|
12
|
+
/** A process death, missing report, or incomplete XML is not a test failure. */
|
|
13
|
+
currentJUnit?: string;
|
|
14
|
+
baselineJUnit?: string;
|
|
15
|
+
currentExitCode: number | null;
|
|
16
|
+
baselineExitCode: number | null;
|
|
17
|
+
currentSignal?: string | null;
|
|
18
|
+
baselineSignal?: string | null;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface GateShardAggregate {
|
|
22
|
+
status: 'passed' | 'failed' | 'unmeasured';
|
|
23
|
+
/** Only these shard identities need another assignment; completed siblings stay intact. */
|
|
24
|
+
retryShardIds: string[];
|
|
25
|
+
report?: GateBaselineReport;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
interface JUnitCase { file: string; name: string; failure?: string }
|
|
29
|
+
|
|
30
|
+
const decode = (text: string): string => text.replace(/&(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);/gi, (entity, key: string) => {
|
|
31
|
+
if (key[0] === '#') {
|
|
32
|
+
const code = key[1]?.toLowerCase() === 'x' ? parseInt(key.slice(2), 16) : parseInt(key.slice(1), 10);
|
|
33
|
+
return code > 0 && code <= 0x10ffff ? String.fromCodePoint(code) : entity;
|
|
34
|
+
}
|
|
35
|
+
return ({ amp: '&', lt: '<', gt: '>', quot: '"', apos: "'" } as Record<string, string>)[key.toLowerCase()] ?? entity;
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
interface XmlElement { name: string; attrs: Record<string, string>; children: XmlElement[]; text: string }
|
|
39
|
+
|
|
40
|
+
/** Strict, dependency-free XML reader for JUnit reports: the whole document must be well formed
|
|
41
|
+
* (one root, balanced tags, nothing but whitespace/comments after the root). CDATA and comments are
|
|
42
|
+
* text, never elements — so a `<testcase>` inside a failure message is not a test case, and a report
|
|
43
|
+
* followed by a truncated tail is rejected as unmeasured rather than read as complete. */
|
|
44
|
+
function parseXml(xml: string): XmlElement | undefined {
|
|
45
|
+
const stack: XmlElement[] = [];
|
|
46
|
+
let root: XmlElement | undefined;
|
|
47
|
+
let i = xml.charCodeAt(0) === 0xfeff ? 1 : 0;
|
|
48
|
+
const appendText = (text: string): boolean => {
|
|
49
|
+
const top = stack.at(-1);
|
|
50
|
+
if (!top) return text.trim() === '';
|
|
51
|
+
top.text += text;
|
|
52
|
+
return true;
|
|
53
|
+
};
|
|
54
|
+
while (i < xml.length) {
|
|
55
|
+
const lt = xml.indexOf('<', i);
|
|
56
|
+
if (lt === -1) return appendText(xml.slice(i)) && !stack.length && root ? root : undefined;
|
|
57
|
+
if (lt > i) {
|
|
58
|
+
const raw = xml.slice(i, lt);
|
|
59
|
+
if (!appendText(decode(raw))) return undefined;
|
|
60
|
+
if (/&(?!(#x[0-9a-f]+|#\d+|amp|lt|gt|quot|apos);)/i.test(raw)) return undefined;
|
|
61
|
+
}
|
|
62
|
+
if (xml.startsWith('<!--', lt)) {
|
|
63
|
+
const close = xml.indexOf('-->', lt + 4);
|
|
64
|
+
if (close === -1) return undefined;
|
|
65
|
+
i = close + 3;
|
|
66
|
+
} else if (xml.startsWith('<![CDATA[', lt)) {
|
|
67
|
+
const close = xml.indexOf(']]>', lt + 9);
|
|
68
|
+
if (close === -1 || !stack.length) return undefined;
|
|
69
|
+
appendText(xml.slice(lt + 9, close));
|
|
70
|
+
i = close + 3;
|
|
71
|
+
} else if (xml.startsWith('<?', lt)) {
|
|
72
|
+
const close = xml.indexOf('?>', lt + 2);
|
|
73
|
+
if (close === -1 || stack.length || root) return undefined;
|
|
74
|
+
i = close + 2;
|
|
75
|
+
} else if (xml.startsWith('<!', lt)) {
|
|
76
|
+
return undefined;
|
|
77
|
+
} else {
|
|
78
|
+
let j = lt + 1;
|
|
79
|
+
let quote: string | undefined;
|
|
80
|
+
for (; j < xml.length; j++) {
|
|
81
|
+
const ch = xml[j]!;
|
|
82
|
+
if (quote) { if (ch === quote) quote = undefined; else if (ch === '<') return undefined; }
|
|
83
|
+
else if (ch === '"' || ch === "'") quote = ch;
|
|
84
|
+
else if (ch === '>') break;
|
|
85
|
+
else if (ch === '<') return undefined;
|
|
86
|
+
}
|
|
87
|
+
if (j >= xml.length) return undefined;
|
|
88
|
+
const inner = xml.slice(lt + 1, j);
|
|
89
|
+
i = j + 1;
|
|
90
|
+
if (inner.startsWith('/')) {
|
|
91
|
+
const name = inner.slice(1).trim();
|
|
92
|
+
const open = stack.pop();
|
|
93
|
+
if (!open || open.name !== name) return undefined;
|
|
94
|
+
if (!stack.length) root = open;
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const selfClosing = inner.endsWith('/');
|
|
98
|
+
const body = selfClosing ? inner.slice(0, -1) : inner;
|
|
99
|
+
const head = /^([A-Za-z_][\w.:-]*)/.exec(body);
|
|
100
|
+
if (!head || (!stack.length && root)) return undefined;
|
|
101
|
+
const attrs: Record<string, string> = {};
|
|
102
|
+
let rest = body.slice(head[1]!.length);
|
|
103
|
+
const attrPattern = /^\s+([A-Za-z_][\w.:-]*)\s*=\s*(?:"([^"]*)"|'([^']*)')/;
|
|
104
|
+
for (let m = attrPattern.exec(rest); m; m = attrPattern.exec(rest)) {
|
|
105
|
+
if (m[1]! in attrs) return undefined;
|
|
106
|
+
attrs[m[1]!] = decode(m[2] ?? m[3] ?? '');
|
|
107
|
+
rest = rest.slice(m[0].length);
|
|
108
|
+
}
|
|
109
|
+
if (rest.trim() !== '') return undefined;
|
|
110
|
+
const element: XmlElement = { name: head[1]!, attrs, children: [], text: '' };
|
|
111
|
+
stack.at(-1)?.children.push(element);
|
|
112
|
+
if (selfClosing) { if (!stack.length) root = element; }
|
|
113
|
+
else stack.push(element);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return !stack.length && root ? root : undefined;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function textOf(element: XmlElement): string {
|
|
120
|
+
return element.text + element.children.map(textOf).join('');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function readJUnit(xml: string, files: readonly string[]): JUnitCase[] | undefined {
|
|
124
|
+
const root = parseXml(xml);
|
|
125
|
+
if (!root || (root.name !== 'testsuites' && root.name !== 'testsuite')) return undefined;
|
|
126
|
+
const expected = new Set(files);
|
|
127
|
+
if (expected.size === 0) return undefined;
|
|
128
|
+
const cases: JUnitCase[] = [];
|
|
129
|
+
const counts = new Map<string, number>();
|
|
130
|
+
const executed = new Set<string>();
|
|
131
|
+
let failures = 0;
|
|
132
|
+
let errors = 0;
|
|
133
|
+
const matchesCount = (value: string | undefined, actual: number) => value === undefined
|
|
134
|
+
|| (/^(0|[1-9]\d*)$/.test(value) && Number(value) === actual);
|
|
135
|
+
// Each suite's tests attribute covers its descendants too: compare subtree totals.
|
|
136
|
+
const walkSuite = (suite: XmlElement, parentFile: string | undefined): { tests: number; failures: number; errors: number } | undefined => {
|
|
137
|
+
const file = suite.attrs.file ?? parentFile;
|
|
138
|
+
const declared = Number(suite.attrs.tests);
|
|
139
|
+
if ((file && !expected.has(file)) || !Number.isSafeInteger(declared) || declared < 0) return undefined;
|
|
140
|
+
const total = { tests: 0, failures: 0, errors: 0 };
|
|
141
|
+
for (const child of suite.children) {
|
|
142
|
+
if (child.name === 'testsuite') {
|
|
143
|
+
const sub = walkSuite(child, file);
|
|
144
|
+
if (!sub) return undefined;
|
|
145
|
+
total.tests += sub.tests; total.failures += sub.failures; total.errors += sub.errors;
|
|
146
|
+
} else if (child.name === 'testcase') {
|
|
147
|
+
const caseFile = child.attrs.file ?? file;
|
|
148
|
+
const leaf = child.attrs.name;
|
|
149
|
+
if (!caseFile || !expected.has(caseFile) || !leaf || (file && file !== caseFile)) return undefined;
|
|
150
|
+
total.tests++;
|
|
151
|
+
counts.set(caseFile, (counts.get(caseFile) ?? 0) + 1);
|
|
152
|
+
if (child.children.some((c) => c.name === 'skipped')) return undefined;
|
|
153
|
+
const classname = child.attrs.classname;
|
|
154
|
+
const name = classname && classname !== caseFile ? `${classname} > ${leaf}` : leaf;
|
|
155
|
+
const identity = `${caseFile}\u0000${name}`;
|
|
156
|
+
if (executed.has(identity)) return undefined;
|
|
157
|
+
executed.add(identity);
|
|
158
|
+
const failure = child.children.find((c) => c.name === 'failure' || c.name === 'error');
|
|
159
|
+
if (failure?.name === 'failure') { failures++; total.failures++; }
|
|
160
|
+
else if (failure) { errors++; total.errors++; }
|
|
161
|
+
const diagnostic = failure ? [failure.attrs.message, textOf(failure).trim()].filter(Boolean).join('\n') : undefined;
|
|
162
|
+
cases.push({ file: caseFile, name, ...(failure ? { failure: diagnostic || 'JUnit test failed' } : {}) });
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
if (total.tests !== declared || !matchesCount(suite.attrs.failures, total.failures) || !matchesCount(suite.attrs.errors, total.errors)) return undefined;
|
|
166
|
+
return total;
|
|
167
|
+
};
|
|
168
|
+
const suites = root.name === 'testsuite' ? [root] : root.children.filter((c) => c.name === 'testsuite');
|
|
169
|
+
if (suites.length === 0 || (root.name === 'testsuites' && root.children.some((c) => c.name === 'testcase'))) return undefined;
|
|
170
|
+
let counted = 0;
|
|
171
|
+
for (const suite of suites) {
|
|
172
|
+
const total = walkSuite(suite, undefined);
|
|
173
|
+
if (!total) return undefined;
|
|
174
|
+
counted += total.tests;
|
|
175
|
+
}
|
|
176
|
+
if (root.name === 'testsuites' && (!matchesCount(root.attrs.tests, counted)
|
|
177
|
+
|| !matchesCount(root.attrs.failures, failures) || !matchesCount(root.attrs.errors, errors))) return undefined;
|
|
178
|
+
if (cases.length === 0 || [...expected].some((file) => !counts.has(file))) return undefined;
|
|
179
|
+
return cases;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function asGateLog(cases: readonly JUnitCase[]): string {
|
|
183
|
+
return cases.map(({ file, name, failure }) => `${file}:\n${failure === undefined ? `(pass) ${name}` : `${failure}\n(fail) ${name}`}`).join('\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Select the latest complete attempt per shard, then classify the union with the existing full-suite gate ruler. */
|
|
187
|
+
export function aggregateGateTestShards(shards: readonly GateTestShard[], attempts: readonly GateShardAttempt[]): GateShardAggregate {
|
|
188
|
+
const selected = new Map<string, GateShardAttempt>();
|
|
189
|
+
for (const attempt of attempts) {
|
|
190
|
+
if (!Number.isSafeInteger(attempt.attempt) || attempt.attempt < 1) throw new Error('invalid shard attempt');
|
|
191
|
+
if (!shards.some((shard) => shard.id === attempt.shardId)) throw new Error(`unknown shard: ${attempt.shardId}`);
|
|
192
|
+
const prior = selected.get(attempt.shardId);
|
|
193
|
+
if (prior?.attempt === attempt.attempt) throw new Error(`duplicate shard attempt: ${attempt.shardId}`);
|
|
194
|
+
if (!prior || prior.attempt < attempt.attempt) selected.set(attempt.shardId, attempt);
|
|
195
|
+
}
|
|
196
|
+
const retryShardIds: string[] = [];
|
|
197
|
+
const current: JUnitCase[] = [];
|
|
198
|
+
const baseline: JUnitCase[] = [];
|
|
199
|
+
for (const shard of shards) {
|
|
200
|
+
const attempt = selected.get(shard.id);
|
|
201
|
+
const head = attempt?.currentJUnit === undefined ? undefined : readJUnit(attempt.currentJUnit, shard.files);
|
|
202
|
+
const base = attempt?.baselineJUnit === undefined ? undefined : readJUnit(attempt.baselineJUnit, shard.files);
|
|
203
|
+
if (!attempt || attempt.currentExitCode === null || attempt.baselineExitCode === null
|
|
204
|
+
|| attempt.currentSignal || attempt.baselineSignal
|
|
205
|
+
|| ![0, 1].includes(attempt.currentExitCode) || ![0, 1].includes(attempt.baselineExitCode)
|
|
206
|
+
|| !head || !base
|
|
207
|
+
|| (attempt.currentExitCode === 0 && head.some((test) => test.failure !== undefined))
|
|
208
|
+
|| (attempt.baselineExitCode === 0 && base.some((test) => test.failure !== undefined))
|
|
209
|
+
|| (attempt.currentExitCode !== 0 && head.every((test) => test.failure === undefined))
|
|
210
|
+
|| (attempt.baselineExitCode !== 0 && base.every((test) => test.failure === undefined))) {
|
|
211
|
+
retryShardIds.push(shard.id);
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
current.push(...head);
|
|
215
|
+
baseline.push(...base);
|
|
216
|
+
}
|
|
217
|
+
if (retryShardIds.length > 0 || shards.length === 0) return { status: 'unmeasured', retryShardIds };
|
|
218
|
+
if (current.every((test) => test.failure === undefined)) return { status: 'passed', retryShardIds: [] };
|
|
219
|
+
const passedTestEvidence: GatePassedTestEvidence = {
|
|
220
|
+
status: 'available',
|
|
221
|
+
tests: baseline.filter((test) => test.failure === undefined).map(({ file, name }) => ({ file, name: `${file} > ${name}` })),
|
|
222
|
+
};
|
|
223
|
+
const report = buildGateBaselineReport(asGateLog(current), {
|
|
224
|
+
status: baseline.some((test) => test.failure !== undefined) ? 'test-fail' : 'pass',
|
|
225
|
+
output: asGateLog(baseline),
|
|
226
|
+
passedTestEvidence,
|
|
227
|
+
log: 'shard baseline JUnit complete',
|
|
228
|
+
});
|
|
229
|
+
return { status: allowsBaselineOnlyFailure(report) ? 'passed' : 'failed', retryShardIds: [], report };
|
|
230
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
2
|
+
import type { GateTestShard } from './shard-plan.js';
|
|
3
|
+
|
|
4
|
+
export interface ShardPlacement {
|
|
5
|
+
shardId: string;
|
|
6
|
+
context: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Assign only to measurable, ready Pod lease members. No remote work is performed here. */
|
|
10
|
+
export function assignGateShardsToPool(
|
|
11
|
+
shards: readonly GateTestShard[],
|
|
12
|
+
pool: PoolLeaseMeasure,
|
|
13
|
+
previous: readonly ShardPlacement[] = [],
|
|
14
|
+
failedContext?: string,
|
|
15
|
+
): ShardPlacement[] {
|
|
16
|
+
const ids = new Set(shards.map((shard) => shard.id));
|
|
17
|
+
if (ids.size !== shards.length) throw new Error('duplicate shard id');
|
|
18
|
+
if (shards.some((shard) => !Number.isFinite(shard.plannedRssMb) || shard.plannedRssMb <= 0)) throw new Error('invalid shard RSS');
|
|
19
|
+
const reservationBytes = (shard: GateTestShard) => Math.max(4, Math.ceil(shard.plannedRssMb / 1024)) * 1024 ** 3;
|
|
20
|
+
if (previous.some((placement) => !ids.has(placement.shardId))) throw new Error('unknown shard placement');
|
|
21
|
+
const retained = previous.filter((placement) => placement.context !== failedContext);
|
|
22
|
+
if (new Set(previous.map((placement) => placement.shardId)).size !== previous.length) throw new Error('duplicate shard placement');
|
|
23
|
+
const members = pool.members.filter((member) => member.context !== failedContext && member.reason === null
|
|
24
|
+
&& member.running !== null && member.pending !== null && member.availableMemoryByNodeBytes !== null
|
|
25
|
+
&& Number.isSafeInteger(member.capacity) && member.capacity > 0
|
|
26
|
+
&& member.availableMemoryByNodeBytes.every((bytes) => Number.isFinite(bytes) && bytes >= 0));
|
|
27
|
+
if (new Set(members.map((member) => member.context)).size !== members.length) throw new Error('duplicate pool context');
|
|
28
|
+
const state = members.map((member) => ({
|
|
29
|
+
context: member.context,
|
|
30
|
+
slots: Math.max(0, member.capacity - member.running! - member.pending!),
|
|
31
|
+
nodes: [...member.availableMemoryByNodeBytes!],
|
|
32
|
+
weight: member.availableMemoryByNodeBytes!.reduce((sum, bytes) => sum + Math.max(0, bytes), 0),
|
|
33
|
+
assigned: 0,
|
|
34
|
+
}));
|
|
35
|
+
for (const placement of retained) {
|
|
36
|
+
const member = state.find((entry) => entry.context === placement.context);
|
|
37
|
+
const shard = shards.find((entry) => entry.id === placement.shardId)!;
|
|
38
|
+
if (!member) throw new Error(`placed shard has no healthy pool member: ${placement.shardId}`);
|
|
39
|
+
const node = member.nodes.findIndex((bytes) => bytes >= reservationBytes(shard));
|
|
40
|
+
if (node < 0 || member.slots < 1) throw new Error(`placed shard exceeds pool lease: ${placement.shardId}`);
|
|
41
|
+
member.nodes[node]! -= reservationBytes(shard);
|
|
42
|
+
member.slots--;
|
|
43
|
+
member.assigned++;
|
|
44
|
+
}
|
|
45
|
+
const assigned = new Map(retained.map((placement) => [placement.shardId, placement.context]));
|
|
46
|
+
for (const shard of shards) {
|
|
47
|
+
if (assigned.has(shard.id)) continue;
|
|
48
|
+
const bytes = reservationBytes(shard);
|
|
49
|
+
const eligible = state.filter((member) => member.slots > 0 && member.nodes.some((free) => free >= bytes))
|
|
50
|
+
.sort((a, b) => a.assigned / a.weight - b.assigned / b.weight || b.weight - a.weight || a.context.localeCompare(b.context));
|
|
51
|
+
const member = eligible[0];
|
|
52
|
+
if (!member) throw new Error(`no pool lease memory for shard: ${shard.id}`);
|
|
53
|
+
const node = member.nodes.findIndex((free) => free >= bytes);
|
|
54
|
+
member.nodes[node]! -= bytes;
|
|
55
|
+
member.slots--;
|
|
56
|
+
member.assigned++;
|
|
57
|
+
assigned.set(shard.id, member.context);
|
|
58
|
+
}
|
|
59
|
+
return shards.map((shard) => ({ shardId: shard.id, context: assigned.get(shard.id)! }));
|
|
60
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export interface GateTestShard {
|
|
2
|
+
id: string;
|
|
3
|
+
files: string[];
|
|
4
|
+
plannedRssMb: number;
|
|
5
|
+
plannedSeconds: number;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/** RSS values are the per-file rss_mb observations; the budget is in GiB (1 GiB = 1024 MB here). */
|
|
9
|
+
export function planGateTestShards(
|
|
10
|
+
files: readonly string[],
|
|
11
|
+
rssMb: ReadonlyMap<string, number>,
|
|
12
|
+
seconds: ReadonlyMap<string, number>,
|
|
13
|
+
budgetGiB: number,
|
|
14
|
+
shardCount = Number.POSITIVE_INFINITY,
|
|
15
|
+
): GateTestShard[] {
|
|
16
|
+
if (!Number.isFinite(budgetGiB) || budgetGiB <= 0) throw new Error('invalid shard memory budget');
|
|
17
|
+
if (shardCount !== Number.POSITIVE_INFINITY && (!Number.isSafeInteger(shardCount) || shardCount < 1)) throw new Error('invalid shard count');
|
|
18
|
+
const capacity = budgetGiB * 1024;
|
|
19
|
+
const unique = [...new Set(files)];
|
|
20
|
+
for (const file of unique) {
|
|
21
|
+
const rss = rssMb.get(file);
|
|
22
|
+
const duration = seconds.get(file);
|
|
23
|
+
if (!file || rss === undefined || !Number.isFinite(rss) || rss <= 0
|
|
24
|
+
|| duration === undefined || !Number.isFinite(duration) || duration < 0) {
|
|
25
|
+
throw new Error(`missing or invalid RSS/seconds measurement: ${file}`);
|
|
26
|
+
}
|
|
27
|
+
if (rss > capacity) throw new Error(`test file exceeds shard memory budget: ${file}`);
|
|
28
|
+
}
|
|
29
|
+
const sorted = unique.sort((a, b) =>
|
|
30
|
+
rssMb.get(b)! - rssMb.get(a)! || seconds.get(b)! - seconds.get(a)! || (a < b ? -1 : a > b ? 1 : 0));
|
|
31
|
+
const shards: GateTestShard[] = [];
|
|
32
|
+
for (const file of sorted) {
|
|
33
|
+
const rss = rssMb.get(file)!;
|
|
34
|
+
const duration = seconds.get(file)!;
|
|
35
|
+
const eligible = shards.filter((shard) => shard.plannedRssMb + rss <= capacity)
|
|
36
|
+
.sort((a, b) => a.plannedSeconds - b.plannedSeconds || a.plannedRssMb - b.plannedRssMb || a.id.localeCompare(b.id));
|
|
37
|
+
let shard = eligible[0];
|
|
38
|
+
if ((!shard || (shardCount !== Number.POSITIVE_INFINITY && shards.length < Math.min(shardCount, sorted.length))) && shards.length < shardCount) {
|
|
39
|
+
shard = { id: `shard-${shards.length + 1}`, files: [], plannedRssMb: 0, plannedSeconds: 0 };
|
|
40
|
+
shards.push(shard);
|
|
41
|
+
}
|
|
42
|
+
if (!shard) throw new Error(`test files exceed ${shardCount} shard memory budgets`);
|
|
43
|
+
shard.files.push(file);
|
|
44
|
+
shard.plannedRssMb += rss;
|
|
45
|
+
shard.plannedSeconds += duration;
|
|
46
|
+
}
|
|
47
|
+
return shards;
|
|
48
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import type { runPodCommand } from '../task-orchestrator/surfaces/pod-command-job.js';
|
|
4
|
+
import type { PoolLeaseMeasure } from '../task-orchestrator/surfaces/pod-lease.js';
|
|
5
|
+
import { aggregateGateTestShards, type GateShardAttempt } from './shard-aggregate.js';
|
|
6
|
+
import { assignGateShardsToPool, type ShardPlacement } from './shard-placement.js';
|
|
7
|
+
import type { GateTestShard } from './shard-plan.js';
|
|
8
|
+
import type { ShardRunResult } from './shard-run.js';
|
|
9
|
+
|
|
10
|
+
type PodShardCommand = typeof runPodCommand;
|
|
11
|
+
|
|
12
|
+
/** Execute already-measured GT2 bundles through the existing Pod command Job, never through SSH. */
|
|
13
|
+
export async function runGateShardsOnPod(
|
|
14
|
+
shards: readonly GateTestShard[],
|
|
15
|
+
pool: PoolLeaseMeasure,
|
|
16
|
+
poolSpec: string,
|
|
17
|
+
currentCommit: string,
|
|
18
|
+
baselineCommit: string,
|
|
19
|
+
deps: { run?: PodShardCommand; read?: (path: string) => string; measure?: () => PoolLeaseMeasure } = {},
|
|
20
|
+
): Promise<ShardRunResult & { placements: ShardPlacement[] }> {
|
|
21
|
+
const run = deps.run ?? (await import('../task-orchestrator/surfaces/pod-command-job.js')).runPodCommand;
|
|
22
|
+
const read = deps.read ?? ((path: string) => readFileSync(path, 'utf8'));
|
|
23
|
+
const { parsePodPool } = await import('../task-orchestrator/surfaces/pod-pool.js');
|
|
24
|
+
const poolMembers = parsePodPool(poolSpec);
|
|
25
|
+
const specs = poolSpec.split(',').map((spec) => spec.trim()).filter(Boolean);
|
|
26
|
+
const members = new Map(poolMembers.map((member, index) => [member.context, specs[index]!]));
|
|
27
|
+
const measure = deps.measure ?? (async () => (await import('../task-orchestrator/surfaces/pod-lease.js')).measurePoolLease(poolMembers));
|
|
28
|
+
for (const commit of [currentCommit, baselineCommit]) {
|
|
29
|
+
if (!/^[0-9a-f]{40}$/.test(commit)) throw new Error('Pod shard source requires a 40-character commit SHA');
|
|
30
|
+
}
|
|
31
|
+
const attempts: GateShardAttempt[] = [];
|
|
32
|
+
const planned = [...shards];
|
|
33
|
+
let placements = assignGateShardsToPool(planned, pool);
|
|
34
|
+
if (placements.some((placement) => !members.has(placement.context))) throw new Error('leased pool member is not in Pod pool spec');
|
|
35
|
+
let pending = planned;
|
|
36
|
+
for (let round = 1; round <= 2 && pending.length; round++) {
|
|
37
|
+
const failed = new Set<string>();
|
|
38
|
+
for (const shard of pending) {
|
|
39
|
+
const context = placements.find((entry) => entry.shardId === shard.id)!.context;
|
|
40
|
+
let current: { exitCode: number | null; junit?: string } = { exitCode: null };
|
|
41
|
+
let base: { exitCode: number | null; junit?: string } = { exitCode: null };
|
|
42
|
+
try {
|
|
43
|
+
// Pin the context by narrowing the existing pool spec; do not send a command over SSH.
|
|
44
|
+
const member = members.get(context);
|
|
45
|
+
if (!member) throw new Error(`pool context not in spec: ${context}`);
|
|
46
|
+
const executeOnMember = async (commit: string) => {
|
|
47
|
+
const report = `shard-${shard.id}.xml`;
|
|
48
|
+
const result = await run({ command: ['bash', '-c', 'mkdir -p "$HOME/outbox"; bun install --frozen-lockfile >/dev/null && bun test --reporter=junit --reporter-outfile="$HOME/outbox/$1" "${@:2}"', 'shard', report, ...shard.files],
|
|
49
|
+
pool: member,
|
|
50
|
+
clone: true, memoryLimit: `${Math.max(4, Math.ceil(shard.plannedRssMb / 1024))}Gi`,
|
|
51
|
+
source: { kind: 'commit', sha: commit } });
|
|
52
|
+
return { exitCode: result.exitCode, junit: read(join(result.artifactsDir, report)) };
|
|
53
|
+
};
|
|
54
|
+
current = await executeOnMember(currentCommit);
|
|
55
|
+
base = await executeOnMember(baselineCommit);
|
|
56
|
+
} catch {
|
|
57
|
+
failed.add(context);
|
|
58
|
+
}
|
|
59
|
+
attempts.push({ shardId: shard.id, attempt: round, currentExitCode: current.exitCode, baselineExitCode: base.exitCode,
|
|
60
|
+
...(current.junit ? { currentJUnit: current.junit } : {}), ...(base.junit ? { baselineJUnit: base.junit } : {}) });
|
|
61
|
+
}
|
|
62
|
+
const retry = new Set(aggregateGateTestShards(planned, attempts).retryShardIds);
|
|
63
|
+
pending = planned.filter((shard) => retry.has(shard.id));
|
|
64
|
+
if (pending.length && round === 1 && failed.size) {
|
|
65
|
+
try {
|
|
66
|
+
const available = await measure();
|
|
67
|
+
const toMove = new Set(pending.filter((shard) => failed.has(placements.find((entry) => entry.shardId === shard.id)!.context)).map((shard) => shard.id));
|
|
68
|
+
const reassigned = assignGateShardsToPool(planned.filter((shard) => toMove.has(shard.id)),
|
|
69
|
+
{ members: available.members.filter((member) => !failed.has(member.context) && members.has(member.context)) });
|
|
70
|
+
placements = placements.map((placement) => reassigned.find((next) => next.shardId === placement.shardId) ?? placement);
|
|
71
|
+
} catch (error) {
|
|
72
|
+
return { aggregate: { status: 'unmeasured', retryShardIds: [...retry] }, shards: planned, attempts, placements, reason: String(error) };
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { aggregate: aggregateGateTestShards(planned, attempts), shards: planned, attempts, placements };
|
|
77
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { existsSync, mkdtempSync, readFileSync, rmSync } from 'node:fs';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { prepareDeterministicChildEnvironment } from '../../scripts/lib/deterministic-env.js';
|
|
5
|
+
import { withBaselineWorktree } from '../self-implement/gate-baseline.js';
|
|
6
|
+
import { aggregateGateTestShards, type GateShardAttempt, type GateShardAggregate } from './shard-aggregate.js';
|
|
7
|
+
import { planGateTestShards, type GateTestShard } from './shard-plan.js';
|
|
8
|
+
|
|
9
|
+
export interface ShardRunResult {
|
|
10
|
+
aggregate: GateShardAggregate;
|
|
11
|
+
shards: GateTestShard[];
|
|
12
|
+
attempts: GateShardAttempt[];
|
|
13
|
+
reason?: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type ShardProcess = (cwd: string, files: readonly string[]) => {
|
|
17
|
+
exitCode: number | null;
|
|
18
|
+
signal?: string | null;
|
|
19
|
+
junit?: string;
|
|
20
|
+
rssMb?: number;
|
|
21
|
+
seconds?: number;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** One process per bundle and per revision; a failed runner is retried only for its own bundle. */
|
|
25
|
+
export function runShardedGateTests(
|
|
26
|
+
cwd: string,
|
|
27
|
+
files: readonly string[],
|
|
28
|
+
baseRef: string,
|
|
29
|
+
count = 2,
|
|
30
|
+
run: ShardProcess = runBunShard,
|
|
31
|
+
baselineWorktree: typeof withBaselineWorktree = withBaselineWorktree,
|
|
32
|
+
budgetGiB = 8,
|
|
33
|
+
): ShardRunResult {
|
|
34
|
+
if (!Number.isSafeInteger(count) || count < 1) throw new Error('invalid --shards count');
|
|
35
|
+
const rss = new Map<string, number>();
|
|
36
|
+
const seconds = new Map<string, number>();
|
|
37
|
+
for (const file of [...new Set(files)]) {
|
|
38
|
+
let sample: ReturnType<ShardProcess>;
|
|
39
|
+
try {
|
|
40
|
+
sample = run(cwd, [file]);
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: `measurement failed: ${file}: ${String(error)}` };
|
|
43
|
+
}
|
|
44
|
+
if (![0, 1].includes(sample.exitCode ?? -1) || sample.signal || !sample.junit || !Number.isFinite(sample.rssMb) || !Number.isFinite(sample.seconds)) {
|
|
45
|
+
return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: `measurement unavailable: ${file}` };
|
|
46
|
+
}
|
|
47
|
+
rss.set(file, sample.rssMb!);
|
|
48
|
+
seconds.set(file, sample.seconds!);
|
|
49
|
+
}
|
|
50
|
+
let shards: GateTestShard[];
|
|
51
|
+
try {
|
|
52
|
+
shards = planGateTestShards(files, rss, seconds, budgetGiB, count);
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return { aggregate: { status: 'unmeasured', retryShardIds: [] }, shards: [], attempts: [], reason: String(error) };
|
|
55
|
+
}
|
|
56
|
+
const attempts: GateShardAttempt[] = [];
|
|
57
|
+
const baseline = baselineWorktree(cwd, baseRef, (dir) => {
|
|
58
|
+
let pending = shards;
|
|
59
|
+
for (let round = 1; round <= 2 && pending.length; round++) {
|
|
60
|
+
for (const shard of pending) {
|
|
61
|
+
const runSafely = (location: string): ReturnType<ShardProcess> => {
|
|
62
|
+
try { return run(location, shard.files); }
|
|
63
|
+
catch { return { exitCode: null }; }
|
|
64
|
+
};
|
|
65
|
+
const current = runSafely(cwd);
|
|
66
|
+
const base = runSafely(dir);
|
|
67
|
+
attempts.push({
|
|
68
|
+
shardId: shard.id, attempt: round,
|
|
69
|
+
currentJUnit: current.junit, baselineJUnit: base.junit,
|
|
70
|
+
currentExitCode: current.exitCode, baselineExitCode: base.exitCode,
|
|
71
|
+
currentSignal: current.signal, baselineSignal: base.signal,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const retry = new Set(aggregateGateTestShards(shards, attempts).retryShardIds);
|
|
75
|
+
pending = shards.filter((shard) => retry.has(shard.id));
|
|
76
|
+
}
|
|
77
|
+
return aggregateGateTestShards(shards, attempts);
|
|
78
|
+
});
|
|
79
|
+
if ('retryShardIds' in baseline) return { aggregate: baseline, shards, attempts };
|
|
80
|
+
return { aggregate: { status: 'unmeasured', retryShardIds: shards.map((shard) => shard.id) }, shards, attempts, reason: baseline.log };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function runBunShard(cwd: string, files: readonly string[]): ReturnType<ShardProcess> {
|
|
84
|
+
const dir = mkdtempSync(join(tmpdir(), 'elanous-shard-junit-'));
|
|
85
|
+
const report = join(dir, 'result.xml');
|
|
86
|
+
const isolated = prepareDeterministicChildEnvironment('elanous-shard-test-env-');
|
|
87
|
+
try {
|
|
88
|
+
const measured = files.length === 1;
|
|
89
|
+
const args = ['test', '--reporter=junit', `--reporter-outfile=${report}`, ...files];
|
|
90
|
+
const started = performance.now();
|
|
91
|
+
const result = Bun.spawnSync(['bun', ...args], {
|
|
92
|
+
cwd, env: isolated.env, stdout: 'pipe', stderr: 'pipe', timeout: 300_000,
|
|
93
|
+
});
|
|
94
|
+
return {
|
|
95
|
+
exitCode: result.exitCode,
|
|
96
|
+
signal: result.signalCode ? String(result.signalCode) : undefined,
|
|
97
|
+
junit: existsSync(report) ? readFileSync(report, 'utf8') : undefined,
|
|
98
|
+
rssMb: measured ? result.resourceUsage.maxRSS / (1024 * 1024) : undefined,
|
|
99
|
+
seconds: measured ? (performance.now() - started) / 1000 : undefined,
|
|
100
|
+
};
|
|
101
|
+
} finally {
|
|
102
|
+
isolated.cleanup();
|
|
103
|
+
rmSync(dir, { recursive: true, force: true });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -101,21 +101,27 @@ function codexCandidateUsable(candidate: BudgetCodexCandidate, cap: number | und
|
|
|
101
101
|
function slotUsable(
|
|
102
102
|
provider: string,
|
|
103
103
|
input: DecideBudgetInput,
|
|
104
|
-
|
|
104
|
+
allowUnmeasured = false,
|
|
105
|
+
): { usable: boolean; reason: string; unmeasured?: boolean } {
|
|
105
106
|
if (provider === CODEX_PROVIDER) {
|
|
106
107
|
const cap = capFor(input.maxUsedPercent, CODEX_PROVIDER);
|
|
107
|
-
const
|
|
108
|
-
|
|
108
|
+
const measuredUsable = input.codexCandidates.some((candidate) => codexCandidateUsable(candidate, cap));
|
|
109
|
+
const unmeasured = allowUnmeasured && !measuredUsable && (input.codexCandidates.length === 0
|
|
110
|
+
|| input.codexCandidates.some((candidate) => candidate.reached !== true
|
|
111
|
+
&& (typeof candidate.usedPercent !== 'number' || !Number.isFinite(candidate.usedPercent))));
|
|
112
|
+
return { usable: measuredUsable || unmeasured, reason: formatCodexReason(input.codexCandidates, cap), ...(unmeasured ? { unmeasured: true } : {}) };
|
|
109
113
|
}
|
|
110
114
|
if (provider === GROK_PROVIDER) {
|
|
111
115
|
const cap = capFor(input.maxUsedPercent, GROK_PROVIDER);
|
|
112
116
|
const known = typeof input.grokUsedPercent === 'number' && Number.isFinite(input.grokUsedPercent);
|
|
113
|
-
const
|
|
117
|
+
const unmeasured = allowUnmeasured && !known;
|
|
118
|
+
const usable = unmeasured || (cap === undefined
|
|
114
119
|
? known
|
|
115
|
-
: known && (input.grokUsedPercent as number) < cap;
|
|
120
|
+
: known && (input.grokUsedPercent as number) < cap);
|
|
116
121
|
return {
|
|
117
122
|
usable,
|
|
118
123
|
reason: formatGrokReason(input.grokUsedPercent, cap),
|
|
124
|
+
...(unmeasured ? { unmeasured: true } : {}),
|
|
119
125
|
};
|
|
120
126
|
}
|
|
121
127
|
return { usable: false, reason: `${provider}: 예산 판정 없음` };
|
|
@@ -127,6 +133,15 @@ function slotUsable(
|
|
|
127
133
|
* 아무것도 못 쓰면 onShortfall 이 wait-reset 일 때만 wait-reset, 아니면 stop.
|
|
128
134
|
*/
|
|
129
135
|
export function decideBudget(input: DecideBudgetInput): BudgetDecision {
|
|
136
|
+
return decideBudgetWithPolicy(input, false).decision;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Only the harness launch gate treats an unmeasured provider as launchable. */
|
|
140
|
+
export function decideLaunchBudget(input: DecideBudgetInput): { decision: BudgetDecision; unmeasuredProvider?: string } {
|
|
141
|
+
return decideBudgetWithPolicy(input, true);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function decideBudgetWithPolicy(input: DecideBudgetInput, allowUnmeasured: boolean): { decision: BudgetDecision; unmeasuredProvider?: string } {
|
|
130
145
|
const reasons: string[] = [];
|
|
131
146
|
if (input.preference.chain.length === 0) {
|
|
132
147
|
reasons.push('chain: 비었음 — tools.selfImplement.childLlm.chain 도 llm.fallbackChain 도 없다');
|
|
@@ -134,7 +149,7 @@ export function decideBudget(input: DecideBudgetInput): BudgetDecision {
|
|
|
134
149
|
let firstUnusable = false;
|
|
135
150
|
for (let index = 0; index < input.preference.chain.length; index++) {
|
|
136
151
|
const slot = input.preference.chain[index]!;
|
|
137
|
-
const judged = slotUsable(slot.provider, input);
|
|
152
|
+
const judged = slotUsable(slot.provider, input, allowUnmeasured);
|
|
138
153
|
reasons.push(judged.reason);
|
|
139
154
|
if (!judged.usable) {
|
|
140
155
|
if (index === 0) firstUnusable = true;
|
|
@@ -142,16 +157,18 @@ export function decideBudget(input: DecideBudgetInput): BudgetDecision {
|
|
|
142
157
|
}
|
|
143
158
|
const action: BudgetAction = index === 0 || !firstUnusable ? 'proceed' : 'next-provider';
|
|
144
159
|
return {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
160
|
+
decision: {
|
|
161
|
+
action,
|
|
162
|
+
provider: slot.provider,
|
|
163
|
+
...(slot.model !== undefined ? { model: slot.model } : {}),
|
|
164
|
+
reasons,
|
|
165
|
+
},
|
|
166
|
+
...(judged.unmeasured ? { unmeasuredProvider: slot.provider } : {}),
|
|
149
167
|
};
|
|
150
168
|
}
|
|
151
169
|
const onShortfall = input.preference.budgetGate.onShortfall;
|
|
152
170
|
return {
|
|
153
|
-
action: onShortfall === 'wait-reset' ? 'wait-reset' : 'stop',
|
|
154
|
-
reasons,
|
|
171
|
+
decision: { action: onShortfall === 'wait-reset' ? 'wait-reset' : 'stop', reasons },
|
|
155
172
|
};
|
|
156
173
|
}
|
|
157
174
|
|
|
@@ -764,7 +764,7 @@ export function classifyBaselineProcess(result: ProcessResult, budget?: { timeou
|
|
|
764
764
|
};
|
|
765
765
|
}
|
|
766
766
|
|
|
767
|
-
function withBaselineWorktree<T>(cwd: string, baseRef: string, run: (baselineDir: string) => T): T | BaselineProcessResult {
|
|
767
|
+
export function withBaselineWorktree<T>(cwd: string, baseRef: string, run: (baselineDir: string) => T): T | BaselineProcessResult {
|
|
768
768
|
const baselineDir = mkdtempSync(join(tmpdir(), 'elanous-gate-baseline-'));
|
|
769
769
|
let attached = false;
|
|
770
770
|
try {
|