elanous 0.2.14 → 0.2.16
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/chunks/{1307-6e7e0377c1db0bdc.js → 1307-b46d45b705e8c9ae.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/5977-c952712c87a1f757.js +1 -0
- package/apps/pwa/out/_next/static/chunks/899-46946fe8363e1262.js +1 -0
- package/apps/pwa/out/_next/static/chunks/{9103-7a3ae7f60af3b99f.js → 9103-60fbef33e66f0a46.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-79c3ed84baaedd03.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/autopilot/{page-7b2b4f378354869a.js → page-3cb52e5ea8fc2fa5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-a424ef80885d1743.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-c1e20d7d338713f1.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/bots/{page-1512d91c7491ed62.js → page-482c5dbde699947c.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ceo/page-1e962b31d1f11081.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/chat/{page-a627db94ec6904a0.js → page-8c2d9526164f5d09.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-ffff6d819fbc6e0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/control/{page-e6b84ab07d3567d5.js → page-a0e2a6d1bfb52fc5.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-c53598cfce6156f2.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/editor/{page-fed69f9759d248d0.js → page-13a63953f53c6a23.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/exec/{page-15374909d8f1aebd.js → page-5d3c8429d04f3831.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/field/{page-012a742f81218b01.js → page-3193bdc0bbab047a.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-76270efaee638f17.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/intake/{page-99effd9d893a43dc.js → page-6107ca8ddd5e3bdd.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/{layout-938de57ae0315e46.js → layout-dce686e582c23937.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d3d53b146416e422.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/loops/{page-160089123a40dd91.js → page-1a61c7ca1ceb3ae7.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/market/{page-629e1ede482ec6eb.js → page-51f811fa119308b6.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-dacff46049e1557e.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/morning/{page-27e99b4e54a849d4.js → page-4167348049166852.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-de52e8fdf9095f7a.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-36a7d994f4ca0299.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/ops/checklist/{page-32cde713be47cddb.js → page-ca3952cd78a83ca2.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/release/{page-e7e10cd3386222e5.js → page-5b042736aa113756.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-acb749f1e432bcf3.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-cd78862707999911.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/page-54b9ef8afe6278fd.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/reflection/{page-e64caff084f7ce21.js → page-1ebfe049c640e171.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/scheduler/{page-db07eaffba0df7e3.js → page-57924393e3e4e539.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-9e0ce6c8129fa641.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-31fa1ce4c4aff9ae.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/settings/{page-86f91034bd5cabe5.js → page-0f5cd57ca928f42b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-abe8e3b74bea1ffc.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/setup/page-4364ef9f4e7c7f0f.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/share/{page-ce4d8a1215ce53a3.js → page-d28573d6792b4275.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/showroom/{page-58043db78ba519ae.js → page-59f03fda76c03d22.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/tasks/{page-357750dfb91bf960.js → page-d751de5f8062ff5e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/term/{page-274703bc41b71333.js → page-fe1759f94003b32b.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-d64c984e855c8c68.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/trace/page-86914b0dee8ab7f8.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/vault/{page-3b7e821dc492bdfd.js → page-9afdee66b820c76e.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-d4aef67abf264874.js +1 -0
- package/apps/pwa/out/_next/static/chunks/app/workflows/{page-f8da5dc6f3752276.js → page-1ea64acadd704ace.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/workspace/{page-2f810b18d89ecba6.js → page-3544f154c87b43ee.js} +1 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-30c2073bfc21c004.js +1 -0
- package/apps/pwa/out/_next/static/css/001d5fbaee388a25.css +1 -0
- package/apps/pwa/out/_next/static/css/{04dabcbcc4bfa6db.css → 085673d826b98b62.css} +1 -1
- package/apps/pwa/out/_next/static/rv9cierV0jSOvDBm1g0XB/_buildManifest.js +1 -0
- package/apps/pwa/out/approvals/index.html +1 -1
- package/apps/pwa/out/approvals/index.txt +15 -15
- package/apps/pwa/out/autopilot/index.html +1 -1
- package/apps/pwa/out/autopilot/index.txt +15 -15
- package/apps/pwa/out/board/index.html +1 -1
- package/apps/pwa/out/board/index.txt +15 -15
- package/apps/pwa/out/botlab/index.html +1 -1
- package/apps/pwa/out/botlab/index.txt +15 -15
- package/apps/pwa/out/bots/index.html +1 -1
- package/apps/pwa/out/bots/index.txt +15 -15
- package/apps/pwa/out/ceo/index.html +1 -0
- package/apps/pwa/out/ceo/index.txt +33 -0
- package/apps/pwa/out/chat/index.html +1 -1
- package/apps/pwa/out/chat/index.txt +15 -15
- package/apps/pwa/out/consult/index.html +1 -1
- package/apps/pwa/out/consult/index.txt +15 -15
- package/apps/pwa/out/control/index.html +1 -1
- package/apps/pwa/out/control/index.txt +15 -15
- package/apps/pwa/out/design-check/index.html +1 -1
- package/apps/pwa/out/design-check/index.txt +15 -15
- package/apps/pwa/out/editor/index.html +1 -1
- package/apps/pwa/out/editor/index.txt +15 -15
- package/apps/pwa/out/exec/index.html +1 -1
- package/apps/pwa/out/exec/index.txt +15 -15
- package/apps/pwa/out/field/index.html +1 -1
- package/apps/pwa/out/field/index.txt +15 -15
- package/apps/pwa/out/healthz +1 -1
- package/apps/pwa/out/index.html +1 -1
- package/apps/pwa/out/index.txt +15 -15
- package/apps/pwa/out/inside/index.html +1 -1
- package/apps/pwa/out/inside/index.txt +15 -15
- package/apps/pwa/out/intake/index.html +1 -1
- package/apps/pwa/out/intake/index.txt +15 -15
- package/apps/pwa/out/live/index.html +1 -1
- package/apps/pwa/out/live/index.txt +15 -15
- package/apps/pwa/out/loops/index.html +1 -1
- package/apps/pwa/out/loops/index.txt +15 -15
- package/apps/pwa/out/market/index.html +1 -1
- package/apps/pwa/out/market/index.txt +15 -15
- package/apps/pwa/out/missions/placeholder/index.html +1 -1
- package/apps/pwa/out/missions/placeholder/index.txt +15 -15
- package/apps/pwa/out/morning/index.html +1 -1
- package/apps/pwa/out/morning/index.txt +15 -15
- package/apps/pwa/out/observatory/index.html +1 -1
- package/apps/pwa/out/observatory/index.txt +15 -15
- package/apps/pwa/out/ops/checklist/index.html +1 -1
- package/apps/pwa/out/ops/checklist/index.txt +15 -15
- package/apps/pwa/out/ops/release/index.html +1 -1
- package/apps/pwa/out/ops/release/index.txt +15 -15
- package/apps/pwa/out/ops/seats/index.html +1 -1
- package/apps/pwa/out/ops/seats/index.txt +15 -15
- package/apps/pwa/out/outputs/index.html +1 -1
- package/apps/pwa/out/outputs/index.txt +15 -15
- package/apps/pwa/out/reflection/index.html +1 -1
- package/apps/pwa/out/reflection/index.txt +16 -16
- package/apps/pwa/out/scheduler/index.html +1 -1
- package/apps/pwa/out/scheduler/index.txt +15 -15
- package/apps/pwa/out/sessions/index.html +1 -1
- package/apps/pwa/out/sessions/index.txt +15 -15
- package/apps/pwa/out/settings/devices/index.html +1 -1
- package/apps/pwa/out/settings/devices/index.txt +15 -15
- package/apps/pwa/out/settings/index.html +1 -1
- package/apps/pwa/out/settings/index.txt +15 -15
- package/apps/pwa/out/setup/done/index.html +1 -1
- package/apps/pwa/out/setup/done/index.txt +15 -15
- package/apps/pwa/out/setup/index.html +1 -1
- package/apps/pwa/out/setup/index.txt +15 -15
- package/apps/pwa/out/share/index.html +1 -1
- package/apps/pwa/out/share/index.txt +15 -15
- package/apps/pwa/out/showroom/index.html +1 -1
- package/apps/pwa/out/showroom/index.txt +15 -15
- package/apps/pwa/out/tasks/index.html +1 -1
- package/apps/pwa/out/tasks/index.txt +15 -15
- package/apps/pwa/out/term/index.html +1 -1
- package/apps/pwa/out/term/index.txt +15 -15
- package/apps/pwa/out/today/index.html +1 -1
- package/apps/pwa/out/today/index.txt +15 -15
- package/apps/pwa/out/trace/index.html +1 -1
- package/apps/pwa/out/trace/index.txt +15 -15
- package/apps/pwa/out/vault/index.html +1 -1
- package/apps/pwa/out/vault/index.txt +15 -15
- package/apps/pwa/out/workflows/chat-ui/index.html +1 -1
- package/apps/pwa/out/workflows/chat-ui/index.txt +15 -15
- package/apps/pwa/out/workflows/index.html +1 -1
- package/apps/pwa/out/workflows/index.txt +15 -15
- package/apps/pwa/out/workspace/index.html +1 -1
- package/apps/pwa/out/workspace/index.txt +15 -15
- package/apps/pwa/out/worktrees/index.html +1 -1
- package/apps/pwa/out/worktrees/index.txt +15 -15
- package/graphs/doc-gen/doc-gen.yaml +24 -0
- package/graphs/doc-gen/recipes.yaml +3 -0
- package/graphs/heal/heal-loop.yaml +1 -1
- package/graphs/implement-loop.yaml +1 -1
- package/graphs/orchestrator/orchestrator.yaml +7 -0
- package/graphs/orchestrator/recipes.yaml +3 -0
- package/graphs/release/recipes.yaml +3 -0
- package/graphs/release/release-loop.yaml +4 -2
- package/graphs/steward/recipes.yaml +2 -1
- package/package.json +1 -1
- package/scripts/brand/check.ts +7 -5
- package/scripts/ci-public-export-import-gate.ts +154 -0
- package/scripts/ci-test-interference-gate.ts +5 -1
- package/scripts/ci-typecheck-changed.ts +82 -31
- package/scripts/docs/doc-rot.ts +164 -0
- package/scripts/docs/generate-graph-anatomy.ts +437 -0
- package/scripts/docs-cli-check.ts +4 -4
- package/scripts/expose-rubric-suggest.ts +18 -12
- package/scripts/goals/goal-census.ts +343 -0
- package/scripts/lecture-note/node.ts +31 -11
- package/scripts/lessons/handbook-page.ts +30 -3
- package/scripts/marketing/generate-ax-brief.ts +140 -0
- package/scripts/marketing/generate-feature-map.ts +118 -0
- package/scripts/measure/author-section-contribution.ts +84 -0
- package/scripts/release-loop/checklist-gate-node.ts +53 -14
- package/scripts/release-loop/cut-branch.ts +43 -13
- package/scripts/release-loop/gate-node.ts +82 -14
- package/scripts/release-loop/vault-note-node.ts +257 -8
- package/scripts/release-story/draft.ts +57 -9
- package/scripts/rhythm/daily.ts +263 -76
- package/scripts/rhythm/monthly.ts +97 -0
- package/scripts/rhythm/weekly.ts +30 -9
- package/scripts/smart-merge/resync-shadow.ts +147 -0
- package/src/acp/client.ts +17 -2
- package/src/agent-env/profile.ts +161 -0
- package/src/agent-mission/driver.ts +51 -1
- package/src/agent-mission/supervisor-metrics-cli.ts +40 -0
- package/src/agent-mission/supervisor-metrics.ts +218 -0
- package/src/agent-substrate/pr-reviewer.ts +2 -0
- package/src/ask-user-question/pending-questions.ts +5 -0
- package/src/auth/github-app-token.ts +112 -13
- package/src/autopilot/build/llm-conflict-merge.ts +32 -6
- package/src/autopilot/track-agent-shadow.ts +2 -1
- package/src/briefing/brief-items.ts +276 -0
- package/src/budget/usage-outcomes.ts +204 -0
- package/src/claims/claims-render.ts +14 -5
- package/src/cli/agent-env-cli.ts +21 -0
- package/src/cli/brief-cli.ts +73 -0
- package/src/cli/decisions-cli.ts +9 -0
- package/src/cli/doctor-cli.ts +23 -1
- package/src/cli/doctor-node-modules.ts +147 -0
- package/src/cli/intake-cli.ts +46 -17
- package/src/cli/know-cli.ts +20 -0
- package/src/cli/logs-cli.ts +43 -1
- package/src/cli/pod-cli.ts +2 -0
- package/src/cli/pr-cli.ts +80 -12
- package/src/cli/release-cli.ts +66 -29
- package/src/cli/seat-requests-cli.ts +52 -0
- package/src/cli/self-update.ts +200 -1
- package/src/cli/usage-cli.ts +26 -0
- package/src/context-bus/context-now-public.ts +16 -6
- package/src/context-bus/context-now.ts +3 -1
- package/src/context-bus/coord-events.ts +6 -4
- package/src/decisions/decision-cards.ts +33 -7
- package/src/decisions/proact-meter.ts +286 -0
- package/src/decisions/proact1-lite.ts +86 -0
- package/src/decisions/web-push-decision-cards.ts +1 -1
- package/src/discord-self-message.ts +9 -0
- package/src/domains/alert-audit.ts +271 -0
- package/src/domains/core-tools.ts +4 -3
- package/src/domains/ops-facts-tool.ts +31 -1
- package/src/flow-loop/split.ts +24 -0
- package/src/harness/harness-cli-command.ts +147 -17
- package/src/harness/harness-incidents.ts +2 -0
- package/src/harness/harness-pod-dispatch.ts +67 -8
- package/src/harness/harness-queue.ts +380 -50
- package/src/harness/harness-seams.ts +13 -3
- package/src/harness/helper-repair.ts +218 -0
- package/src/harness/helper-scan.ts +27 -4
- package/src/harness/launch-stamp.ts +236 -0
- package/src/harness/ledger-write-guard-child.fixture.ts +12 -0
- package/src/harness/ledger-write-guard.ts +35 -0
- package/src/harness/nested-elanous-policy.ts +114 -0
- package/src/harness/pod-memory-policy.ts +15 -0
- package/src/harness/repository-name.ts +55 -0
- package/src/hq/fence-audit.ts +90 -0
- package/src/hq/hq.ts +70 -6
- package/src/hq/lease.ts +9 -7
- package/src/hq/move-plan.ts +126 -0
- package/src/hq/promote.ts +31 -0
- package/src/hq/transfer.ts +185 -0
- package/src/index.ts +189 -13
- package/src/intake-plane/check.ts +63 -0
- package/src/intake-plane/digest.ts +65 -21
- package/src/intake-plane/front-classifier.ts +6 -0
- package/src/intake-plane/items.ts +3 -2
- package/src/intake-plane/lens.ts +177 -0
- package/src/intake-plane/route.ts +145 -1
- package/src/knowledge/know-find.ts +105 -0
- package/src/llm.ts +75 -19
- package/src/loops/budget.ts +36 -5
- package/src/loops/checker.ts +2 -2
- package/src/loops/orchestrator/author-depth.ts +65 -9
- package/src/loops/orchestrator/author-input-check.ts +8 -3
- package/src/loops/orchestrator/author-ledger.ts +3 -1
- package/src/loops/orchestrator/finish-rate.ts +14 -3
- package/src/loops/orchestrator/seat-share.ts +56 -0
- package/src/loops/orchestrator/tick.ts +107 -29
- package/src/loops/orchestrator/traffic.ts +117 -11
- package/src/loops/registry.ts +142 -61
- package/src/maturity/bot-slash-catalog.ts +11 -2
- package/src/maturity/feature-maturity.ts +4 -3
- package/src/maturity/tui-slash-availability.ts +40 -0
- package/src/mss/logging/log-store.ts +35 -4
- package/src/nexus/api/http-server.ts +28 -14
- package/src/nexus/api/loop-edges.ts +173 -0
- package/src/nexus/api/schedules-read.ts +13 -0
- package/src/nexus/api/seat-requests.ts +79 -56
- package/src/oauth/overload-failover-launch.ts +75 -0
- package/src/oauth/overload-failover.ts +137 -0
- package/src/prompt-enhance/enhance.ts +6 -1
- package/src/release-loop/auto-start.ts +6 -3
- package/src/release-loop/checklist.ts +19 -5
- package/src/release-loop/feature-store.ts +59 -31
- package/src/release-loop/merged-pr-checklist.ts +125 -0
- package/src/release-loop/placement.ts +98 -29
- package/src/seat-dispatch/seat-ask.ts +33 -6
- package/src/seat-dispatch/seat-request-eligibility.ts +19 -0
- package/src/seat-dispatch/seat-request-ledger.ts +133 -0
- package/src/seat-loop/seat-loop.ts +215 -12
- package/src/self-dev/ask-launch-flow.ts +22 -1
- package/src/self-dev/authored-goal-trace.ts +64 -0
- package/src/self-dev/dev-cli.ts +19 -1
- package/src/self-dev/dev-pipeline.ts +14 -9
- package/src/self-dev/draft-sweep.ts +90 -10
- package/src/self-dev/draft-triage-rules.ts +25 -8
- package/src/self-dev/entrance-registry.ts +10 -6
- package/src/self-dev/old-door.ts +73 -0
- package/src/self-dev/orchestrate.ts +15 -1
- package/src/self-dev/review-provider-fallback.ts +6 -1
- package/src/self-dev/run-supervisor.ts +49 -2
- package/src/self-implement/app-api-budget.ts +166 -0
- package/src/self-implement/budget-gate.ts +7 -5
- package/src/self-implement/follow-up-goals.ts +289 -0
- package/src/self-implement/frozen-merges.ts +3 -2
- package/src/self-implement/goal-author.ts +50 -6
- package/src/self-implement/goal-clarification-escalation.ts +5 -0
- package/src/self-implement/heal-intake.ts +120 -2
- package/src/self-implement/heal-triage.ts +44 -0
- package/src/self-implement/host-regate.ts +9 -2
- package/src/self-implement/l8-merge-queue.ts +132 -0
- package/src/self-implement/orchestrator.ts +325 -47
- package/src/self-implement/pipeline-shape.ts +1 -1
- package/src/self-implement/revert-guard.ts +42 -7
- package/src/self-implement/run-ledger.ts +6 -0
- package/src/self-implement/run-status-mapping.ts +2 -0
- package/src/self-implement/seams.ts +45 -16
- package/src/self-implement/self-implement-cli.ts +1 -0
- package/src/self-implement/speed-claims.ts +338 -0
- package/src/session-runtime/index.ts +2 -0
- package/src/session-runtime/retry-api.ts +87 -0
- package/src/session-runtime/retry-policy.ts +107 -6
- package/src/steward/triage.ts +74 -3
- package/src/steward/working-backwards.ts +20 -2
- package/src/task-cards/card-cli.ts +25 -0
- package/src/task-cards/card-store.ts +5 -3
- package/src/task-orchestrator/surfaces/pod-artifact-return.ts +63 -48
- package/src/task-orchestrator/surfaces/pod-command-job.ts +84 -9
- package/src/task-orchestrator/surfaces/pod-failure-reason.ts +48 -0
- package/src/task-orchestrator/surfaces/pod-ledger-collect.ts +4 -3
- package/src/task-orchestrator/surfaces/pod-ledger-prod-sink.ts +75 -0
- package/src/task-orchestrator/surfaces/pod-pool.ts +70 -9
- package/src/task-orchestrator/surfaces/pod-source-receive.ts +37 -1
- package/src/task-orchestrator/surfaces/self-implement-pod.ts +166 -46
- package/src/task-orchestrator/surfaces/self-implement.ts +7 -1
- package/src/telegram-commands.ts +5 -0
- package/src/telegram.ts +72 -4
- package/src/user-config.ts +64 -7
- package/src/version/packed-revision.json +1 -1
- package/src/workflow-runtime/nodes/bash.ts +9 -1
- package/apps/pwa/out/_next/static/chunks/4562-071d8bac44a01069.js +0 -1
- package/apps/pwa/out/_next/static/chunks/5977-2df489622a52a33a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/approvals/page-74383f890f075511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/board/page-c496bb16da85385f.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/botlab/page-1abfe3aec3be6bdc.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/consult/page-fb260b9283dbf646.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/design-check/page-61ec1d2e92bdd50a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/inside/page-fd7e200d4d83fbd7.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/live/page-d47266766766d227.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/missions/[id]/page-2ce5471926761fdd.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/not-found-e995d30f6800b6bf.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/observatory/page-39f89c53038a51d4.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/ops/seats/page-5a753bb4ec0cc511.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/outputs/page-04250f0e2c0d03df.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/page-341480ed8e3c1ea8.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/sessions/page-8ac8d71c2adf9c8a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/settings/devices/page-e1e47d8d940aba3d.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/done/page-94dc383d3d54db1a.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/setup/page-b3759cc14da80908.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/today/page-194dce4a83143fce.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/trace/page-35bdeae2e2c17147.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/workflows/chat-ui/page-16bef3da05a900c3.js +0 -1
- package/apps/pwa/out/_next/static/chunks/app/worktrees/page-134c1bc762aee62c.js +0 -1
- package/apps/pwa/out/_next/static/css/2e33713c444d5b39.css +0 -1
- package/apps/pwa/out/_next/static/x6mjI6vqOkVVu0jFuDkz1/_buildManifest.js +0 -1
- /package/apps/pwa/out/_next/static/{x6mjI6vqOkVVu0jFuDkz1 → rv9cierV0jSOvDBm1g0XB}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { resolve } from 'node:path';
|
|
3
|
+
import { FEATURE_MATURITY, type Maturity } from '../../src/maturity/feature-maturity.js';
|
|
4
|
+
import type { ChecklistItem } from '../../src/release-loop/checklist.js';
|
|
5
|
+
|
|
6
|
+
const DEFAULT_MAP = resolve(import.meta.dir, '../../docs/marketing/MAP-value-props-to-features.md');
|
|
7
|
+
const HELD = /특허\s*(?:보류|후보)|GOAL-CASCADE/i;
|
|
8
|
+
const PR = /(?:^|[^\w])#(\d+)\b|\/pull\/(\d+)\b/g;
|
|
9
|
+
type Grade = '된다' | '베타' | '로드맵';
|
|
10
|
+
type Row = { feature: string; grade: Grade; id: string; pr: string };
|
|
11
|
+
type Registry = typeof FEATURE_MATURITY;
|
|
12
|
+
|
|
13
|
+
function cells(line: string): string[] {
|
|
14
|
+
return line.slice(1, -1).split(/(?<!\\)\|/).map((cell) => cell.trim().replace(/\\\|/g, '|'));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function clean(text: string): string {
|
|
18
|
+
return text.replace(/\*\*/g, '').replace(/\s+/g, ' ').trim();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function gradeFor(feature: string, hint: string, registry: Registry): Maturity | undefined {
|
|
22
|
+
// A surface alone ("cli", "telegram") does not identify a feature in the registry.
|
|
23
|
+
const key = /\/(?:[\w-]+)(?:\/[\w-]+)*/.exec(hint)?.[0];
|
|
24
|
+
if (key && Object.prototype.hasOwnProperty.call(registry.pwaRoute, key))
|
|
25
|
+
return registry.pwaRoute[key as keyof Registry['pwaRoute']].pwa;
|
|
26
|
+
const named = /\b(cliRoot|tuiSlash|telegramCommand|discordCommand):([\w:-]+)/.exec(hint);
|
|
27
|
+
if (named) {
|
|
28
|
+
const group = registry[named[1] as Exclude<keyof Registry, 'pwaRoute'>] as Record<string, Maturity>;
|
|
29
|
+
return Object.prototype.hasOwnProperty.call(group, named[2]!) ? group[named[2]!] : undefined;
|
|
30
|
+
}
|
|
31
|
+
// Explicit command spelling in a feature name is unambiguous; a prose word is not.
|
|
32
|
+
const command = /`(?:elanous )?([\w:-]+)`/.exec(feature)?.[1];
|
|
33
|
+
if (command && Object.prototype.hasOwnProperty.call(registry.cliRoot, command))
|
|
34
|
+
return registry.cliRoot[command as keyof Registry['cliRoot']];
|
|
35
|
+
return undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function prs(text: string): string[] {
|
|
39
|
+
return [...new Set([...text.matchAll(PR)].map((m) => `#${m[1] ?? m[2]}`))];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function escapeCell(text: string): string {
|
|
43
|
+
return text.replace(/\|/g, '\\|').replace(/[\r\n]+/g, ' ');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Checklist cells require green status; rows without checklist cells use the map's maturity and public marker. */
|
|
47
|
+
export function generateFeatureMap(input: { map: string; items: readonly ChecklistItem[]; registry: Registry; version: string }): string {
|
|
48
|
+
const checklist = new Map(input.items.map((item) => [item.id, item]));
|
|
49
|
+
const green = new Map(input.items.filter((item) => item.status === 'green').map((item) => [item.id, item]));
|
|
50
|
+
const rows: Row[] = [];
|
|
51
|
+
let section = '';
|
|
52
|
+
for (const line of input.map.split(/\r?\n/)) {
|
|
53
|
+
if (/^#{2,3} /.test(line)) section = line;
|
|
54
|
+
if (!section.startsWith('## 2. 기능 배치표 ') || !line.startsWith('|')) continue;
|
|
55
|
+
const columns = cells(line);
|
|
56
|
+
if (columns.length !== 8 || !/^P\d+(?:[·⊕]P\d+)*$/.test(clean(columns[0]!))) continue;
|
|
57
|
+
const [_, rawFeature, rawId, , hint, publication, evidence] = columns;
|
|
58
|
+
const feature = clean(rawFeature!);
|
|
59
|
+
if (!feature || feature === '—') continue;
|
|
60
|
+
const id = clean(rawId!);
|
|
61
|
+
const ids = id.split(/[·⊕]/).map((part) => part.trim()).filter(Boolean);
|
|
62
|
+
const hasChecklistCell = ids.some((part) => checklist.has(part));
|
|
63
|
+
const matched = ids.map((part) => green.get(part)).filter((item): item is ChecklistItem => !!item);
|
|
64
|
+
const complete = ids.length > 0 && ids.every((part) => green.has(part));
|
|
65
|
+
const maturity = hasChecklistCell ? gradeFor(feature, hint!, input.registry) : undefined;
|
|
66
|
+
const mapMaturity = /^(?:\/\S+\s+)?(stable|beta)\b/i.exec(clean(hint!))?.[1]?.toLowerCase();
|
|
67
|
+
const grade: Grade = hasChecklistCell
|
|
68
|
+
? complete && maturity === 'stable' ? '된다' : complete && maturity === 'beta' ? '베타' : '로드맵'
|
|
69
|
+
: !clean(publication!).startsWith('✅') ? '로드맵'
|
|
70
|
+
: mapMaturity === 'stable' ? '된다' : mapMaturity === 'beta' ? '베타' : '로드맵';
|
|
71
|
+
const evidencePrs = hasChecklistCell
|
|
72
|
+
? [...new Set(matched.flatMap((item) => prs(item.evidence ?? '')))] : prs(evidence!);
|
|
73
|
+
const held = HELD.test([...columns, ...ids.map((part) => {
|
|
74
|
+
const item = checklist.get(part);
|
|
75
|
+
return item ? `${item.title} ${item.evidence ?? ''}` : '';
|
|
76
|
+
})].join(' '));
|
|
77
|
+
rows.push({ feature: held ? '개념' : feature, grade: held ? '로드맵' : grade,
|
|
78
|
+
id: held ? '—' : hasChecklistCell ? id : '지도 판정', pr: held || !evidencePrs.length ? '근거 없음'
|
|
79
|
+
: !hasChecklistCell || complete ? evidencePrs.join(', ') : `부분: ${evidencePrs.join(', ')} · 나머지 칸 미완` });
|
|
80
|
+
}
|
|
81
|
+
const lines = [
|
|
82
|
+
'# FEATURE-MAP — 엘라누스가 할 수 있는 것',
|
|
83
|
+
'',
|
|
84
|
+
`판: ${escapeCell(input.version)} · 원천: src/maturity/feature-maturity.ts + 릴리스 체크리스트 green + docs/marketing/MAP-value-props-to-features.md §2`,
|
|
85
|
+
`재생성: \`bun scripts/marketing/generate-feature-map.ts --version ${escapeCell(input.version)} --out docs/marketing/FEATURE-MAP.md\``,
|
|
86
|
+
'내부 배치용: 로드맵은 현재 기능이 아니며, 된다/베타도 공개 판정·컷의 조상 PR 확인을 대신하지 않는다.',
|
|
87
|
+
'',
|
|
88
|
+
'| 기능 | 성숙도 | 칸 id | 근거 PR |',
|
|
89
|
+
'|---|---|---|---|',
|
|
90
|
+
...rows.map((row) => `| ${escapeCell(row.feature)} | ${row.grade} | ${escapeCell(row.id)} | ${escapeCell(row.pr)} |`),
|
|
91
|
+
'',
|
|
92
|
+
];
|
|
93
|
+
return lines.join('\n');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (import.meta.main) {
|
|
97
|
+
const args = process.argv.slice(2);
|
|
98
|
+
const option = (name: string) => {
|
|
99
|
+
const index = args.indexOf(name);
|
|
100
|
+
if (index < 0) return undefined;
|
|
101
|
+
const value = args[index + 1];
|
|
102
|
+
if (!value || value.startsWith('--')) throw new Error(`${name} 값이 필요하다`);
|
|
103
|
+
args.splice(index, 2);
|
|
104
|
+
return value;
|
|
105
|
+
};
|
|
106
|
+
const version = option('--version');
|
|
107
|
+
const mapFile = option('--map') ?? DEFAULT_MAP;
|
|
108
|
+
const checklistFile = option('--checklist');
|
|
109
|
+
const out = option('--out');
|
|
110
|
+
if (!version || args.length) throw new Error('Usage: bun scripts/marketing/generate-feature-map.ts --version <v> [--map <file>] [--checklist <snapshot.json>] [--out <file>]');
|
|
111
|
+
const items = checklistFile
|
|
112
|
+
? (JSON.parse(readFileSync(checklistFile, 'utf8')) as { items: ChecklistItem[] }).items
|
|
113
|
+
: (await import('../../src/release-loop/checklist.js')).listChecklist(version).items;
|
|
114
|
+
if (!Array.isArray(items)) throw new Error('체크리스트 items 배열이 없다');
|
|
115
|
+
const body = generateFeatureMap({ map: readFileSync(mapFile, 'utf8'), items, registry: FEATURE_MATURITY, version });
|
|
116
|
+
if (out) writeFileSync(out, body);
|
|
117
|
+
else process.stdout.write(body);
|
|
118
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { readFileSync } from 'node:fs';
|
|
3
|
+
|
|
4
|
+
export interface AuthorSummary {
|
|
5
|
+
authorRunId: string;
|
|
6
|
+
sectionChars: Record<string, number>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface AssignedGoalEvent {
|
|
10
|
+
category: string;
|
|
11
|
+
event: string;
|
|
12
|
+
ts: string;
|
|
13
|
+
data: { authorRunId?: string; goalId?: string };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface ExecutionRun {
|
|
17
|
+
runId: string;
|
|
18
|
+
goalId: string;
|
|
19
|
+
startedAt: string;
|
|
20
|
+
outcome: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SectionContribution {
|
|
24
|
+
authorRunId: string;
|
|
25
|
+
goalId: string;
|
|
26
|
+
runId: string;
|
|
27
|
+
section: string;
|
|
28
|
+
chars: number;
|
|
29
|
+
outcome: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Join an authored summary to the first execution starting after that author's assignment. */
|
|
33
|
+
export function measureAuthorSectionContribution(
|
|
34
|
+
summaries: readonly AuthorSummary[],
|
|
35
|
+
events: readonly AssignedGoalEvent[],
|
|
36
|
+
runs: readonly ExecutionRun[],
|
|
37
|
+
): SectionContribution[] {
|
|
38
|
+
const rows: SectionContribution[] = [];
|
|
39
|
+
for (const summary of summaries) {
|
|
40
|
+
const assignment = events.filter((event) => event.category === 'goal-author'
|
|
41
|
+
&& event.event === 'goal-id-assigned'
|
|
42
|
+
&& event.data?.authorRunId === summary.authorRunId
|
|
43
|
+
&& typeof event.data.goalId === 'string'
|
|
44
|
+
&& Number.isFinite(Date.parse(event.ts)))
|
|
45
|
+
.sort((left, right) => Date.parse(left.ts) - Date.parse(right.ts)
|
|
46
|
+
|| left.data.goalId!.localeCompare(right.data.goalId!))[0];
|
|
47
|
+
if (!assignment) continue;
|
|
48
|
+
const assignedAt = Date.parse(assignment.ts);
|
|
49
|
+
const run = runs.filter((candidate) => candidate.goalId === assignment.data.goalId
|
|
50
|
+
&& Number.isFinite(Date.parse(candidate.startedAt))
|
|
51
|
+
&& Date.parse(candidate.startedAt) >= assignedAt)
|
|
52
|
+
.sort((left, right) => Date.parse(left.startedAt) - Date.parse(right.startedAt)
|
|
53
|
+
|| left.runId.localeCompare(right.runId))[0];
|
|
54
|
+
if (!run) continue;
|
|
55
|
+
for (const [section, chars] of Object.entries(summary.sectionChars)) {
|
|
56
|
+
rows.push({ authorRunId: summary.authorRunId, goalId: run.goalId, runId: run.runId, section, chars, outcome: run.outcome });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return rows;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function readRunLedger(path: string): ExecutionRun[] {
|
|
63
|
+
const db = new Database(path, { readonly: true });
|
|
64
|
+
try {
|
|
65
|
+
const records = db.query('SELECT run_id, goal_id, doc FROM goal_run').all() as Array<{ run_id: string; goal_id: string; doc: string }>;
|
|
66
|
+
return records.map(({ run_id, goal_id, doc }) => {
|
|
67
|
+
const record = JSON.parse(doc) as { startedAt: string; outcome: string };
|
|
68
|
+
return { runId: run_id, goalId: goal_id, startedAt: record.startedAt, outcome: record.outcome };
|
|
69
|
+
});
|
|
70
|
+
} finally {
|
|
71
|
+
db.close();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (import.meta.main) {
|
|
76
|
+
const [summaryFile, eventsFile, ledgerFile] = process.argv.slice(2);
|
|
77
|
+
if (!summaryFile || !eventsFile || !ledgerFile) throw new Error('usage: bun scripts/measure/author-section-contribution.ts <summaries.json> <events.jsonl> <goal-runs.db>');
|
|
78
|
+
const summaries = JSON.parse(readFileSync(summaryFile, 'utf8')) as AuthorSummary[];
|
|
79
|
+
const events = readFileSync(eventsFile, 'utf8').split('\n').filter(Boolean).map((line) => JSON.parse(line) as AssignedGoalEvent);
|
|
80
|
+
const rows = measureAuthorSectionContribution(summaries, events, readRunLedger(ledgerFile));
|
|
81
|
+
console.log('| authorRunId | goalId | runId | section | chars | outcome |');
|
|
82
|
+
console.log('| --- | --- | --- | --- | ---: | --- |');
|
|
83
|
+
for (const row of rows) console.log(`| ${row.authorRunId} | ${row.goalId} | ${row.runId} | ${row.section} | ${row.chars} | ${row.outcome} |`);
|
|
84
|
+
}
|
|
@@ -1,27 +1,66 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import {
|
|
2
|
+
import { checklistGate, listChecklist, summarizeChecklist } from '../../src/release-loop/checklist.js';
|
|
3
|
+
import * as store from '../../src/release-loop/feature-store.js';
|
|
4
|
+
import { getSchedule } from '../../src/release-loop/release-schedule.js';
|
|
5
|
+
import { debug } from '../../src/debug/log.js';
|
|
3
6
|
import { emitNodeResult, errorResult, readGraphContext, type GraphContext } from './node-verdict.js';
|
|
4
7
|
|
|
5
|
-
export function runChecklistGate(context: GraphContext = readGraphContext()) {
|
|
8
|
+
export function runChecklistGate(context: GraphContext = readGraphContext(), now: Date = new Date()) {
|
|
6
9
|
const version = context.input.version;
|
|
7
10
|
const data = listChecklist(version);
|
|
11
|
+
const schedule = getSchedule(version);
|
|
12
|
+
const deadline = schedule?.landBy;
|
|
13
|
+
const overdue = deadline !== null && deadline !== undefined && now.getTime() > Date.parse(deadline);
|
|
14
|
+
const autoMoved = overdue ? data.items.filter((item) => item.status === 'yellow' && item.priority !== 'P0' && item.disposition === undefined).map((item) => item.id) : [];
|
|
8
15
|
const gate = checklistGate(version);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
16
|
+
const p0 = data.items.filter((item) => item.status === 'yellow' && item.priority === 'P0').map((item) => item.id);
|
|
17
|
+
gate.undecided = gate.undecided.filter((id) => !autoMoved.includes(id) && !p0.includes(id));
|
|
18
|
+
gate.moved = gate.moved.filter((id) => !p0.includes(id)).concat(autoMoved);
|
|
19
|
+
gate.knownIssues = gate.knownIssues.filter((item) => !p0.includes(item.id));
|
|
20
|
+
gate.blocked.push(...p0.filter((id) => !gate.blocked.includes(id)));
|
|
21
|
+
gate.ok = gate.red.length === 0 && gate.undecided.length === 0 && gate.blocked.length === 0;
|
|
22
|
+
const [major, minor, patch] = version.split('.').map(Number);
|
|
23
|
+
const next = `${major}.${minor}.${patch! + 1}`;
|
|
24
|
+
const carried: string[] = [];
|
|
25
|
+
let carryFailure: string | undefined;
|
|
26
|
+
if (gate.moved.length) {
|
|
27
|
+
const nextChecklist = listChecklist(next);
|
|
28
|
+
const existing = new Map(nextChecklist.items.map((item) => [item.id, item]));
|
|
29
|
+
const movedFromHere = new Set(nextChecklist.history.filter((entry) => entry.field === 'move' && entry.from === version).map((entry) => entry.id));
|
|
30
|
+
const sameCarry = new Set<string>();
|
|
15
31
|
for (const id of gate.moved) {
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
32
|
+
const original = data.items.find((item) => item.id === id)!;
|
|
33
|
+
const destination = existing.get(id);
|
|
34
|
+
if (!destination) continue;
|
|
35
|
+
if (destination.title === original.title && destination.owner === original.owner && (movedFromHere.has(id) || destination.evidence?.split('\n').some((line) => line.startsWith(`${version}에서 이월`)))) {
|
|
36
|
+
sameCarry.add(id);
|
|
37
|
+
} else {
|
|
38
|
+
carryFailure = `이월 충돌: ${id} — 다음 판에 다른 칸이 같은 ID`;
|
|
39
|
+
debug.log('release-loop.checklist-gate', 'carry-collision', { id, from: version, to: next });
|
|
40
|
+
break;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!carryFailure) for (const id of gate.moved) {
|
|
44
|
+
try {
|
|
45
|
+
if (sameCarry.has(id)) {
|
|
46
|
+
store.remove(version, id, 'release-loop.checklist-gate');
|
|
47
|
+
debug.log('release-loop.checklist-gate', 'carry-closed', { id, from: version, to: next });
|
|
48
|
+
} else {
|
|
49
|
+
const reason = autoMoved.includes(id) ? `컷 자동 이월 · 마감 ${new Intl.DateTimeFormat('en-GB', { timeZone: 'Asia/Seoul', hour: '2-digit', minute: '2-digit', hourCycle: 'h23' }).format(new Date(deadline!))}` : '컷 자동 이월 · 처분 move';
|
|
50
|
+
store.move(id, version, next, 'release-loop.checklist-gate', undefined, undefined, reason, { clearDisposition: true });
|
|
51
|
+
carried.push(id);
|
|
52
|
+
debug.log('release-loop.checklist-gate', 'carried', { id, from: version, to: next });
|
|
53
|
+
}
|
|
54
|
+
} catch (error) {
|
|
55
|
+
carryFailure = `이월 실패: ${id} — ${error instanceof Error ? error.message : String(error)}`;
|
|
56
|
+
break;
|
|
21
57
|
}
|
|
22
58
|
}
|
|
23
59
|
}
|
|
24
|
-
|
|
60
|
+
if (carryFailure) gate.ok = false;
|
|
61
|
+
const counts = summarizeChecklist(data);
|
|
62
|
+
const summary = `${carryFailure ? `${carryFailure} · ` : ''}확인표 🟢${counts.green} 🟡${counts.yellow}(이동 ${gate.moved.length} · 알려진 문제 ${gate.knownIssues.length} · 막음 ${gate.blocked.length} · 판정 없음 ${gate.undecided.length}) 🔴${counts.red}${gate.red.length ? ` (${gate.red.join(', ')})` : ''}${gate.parity?.length ? ` · ⚠ 짝 경고 ${gate.parity.length}(${gate.parity.map((p) => p.id).join(', ')})` : ''} · 이월 ${carried.length}(${carried.join(', ') || '-'})`;
|
|
63
|
+
return { outcome: gate.ok ? 'ok' as const : 'fail' as const, verdict: gate.ok ? 'pass' as const : 'fail' as const, summary, ...gate, carried };
|
|
25
64
|
}
|
|
26
65
|
|
|
27
66
|
if (import.meta.main) {
|
|
@@ -4,7 +4,7 @@ import { tmpdir } from 'node:os';
|
|
|
4
4
|
import { join, resolve } from 'node:path';
|
|
5
5
|
import { debug } from '../../src/debug/log.js';
|
|
6
6
|
|
|
7
|
-
export interface CutBranchOptions { version: string; base: string; pick: string[]; dryRun?: boolean; repoRoot?: string; log?: (line: string) => void }
|
|
7
|
+
export interface CutBranchOptions { version: string; base: string; pick: string[]; append?: boolean; dryRun?: boolean; repoRoot?: string; log?: (line: string) => void }
|
|
8
8
|
|
|
9
9
|
// git-spawn-allow: release branch operations happen only in an isolated temporary worktree; main is never checked out or changed.
|
|
10
10
|
function git(repo: string, args: string[]): string {
|
|
@@ -23,8 +23,8 @@ export function cutReleaseBranch(opts: CutBranchOptions): { branch: string; comm
|
|
|
23
23
|
const repo = resolve(opts.repoRoot ?? process.cwd());
|
|
24
24
|
const branch = `release/${opts.version}`;
|
|
25
25
|
const log = opts.log ?? console.log;
|
|
26
|
-
const report = (event: 'created' | 'refused' | 'conflict', reason?: string, commit?: string) =>
|
|
27
|
-
debug.log('release-loop.cut-branch', event, { version: opts.version, branch, ...(commit ? { commit } : {}), ...(reason ? { reason } : {}) });
|
|
26
|
+
const report = (event: 'created' | 'refused' | 'conflict' | 'appended', reason?: string, commit?: string, extra?: Record<string, string | string[]>) =>
|
|
27
|
+
debug.log('release-loop.cut-branch', event, { version: opts.version, branch, ...(commit ? { commit } : {}), ...(reason ? { reason } : {}), ...extra });
|
|
28
28
|
try {
|
|
29
29
|
if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(opts.version)) throw new Error(`invalid release version: ${opts.version}`);
|
|
30
30
|
if (!opts.pick.length) throw new Error('at least one --pick commit is required');
|
|
@@ -37,18 +37,31 @@ export function cutReleaseBranch(opts: CutBranchOptions): { branch: string; comm
|
|
|
37
37
|
for (const sha of picks) if (ancestor(repo, sha, base)) throw new Error(`pick ${sha} is already in base ${base}`);
|
|
38
38
|
if (new Set(picks).size !== picks.length) throw new Error('duplicate --pick commit');
|
|
39
39
|
for (const sha of picks) if (git(repo, ['rev-list', '--parents', '-n', '1', sha]).split(' ').length !== 2) throw new Error(`pick ${sha} must have exactly one parent`);
|
|
40
|
-
|
|
40
|
+
const remoteLine = git(repo, ['ls-remote', '--heads', 'origin', `refs/heads/${branch}`]);
|
|
41
|
+
if (remoteLine && !opts.append) throw new Error(`${branch} already exists on origin — use --append to fast-forward more picks onto it`);
|
|
42
|
+
if (!remoteLine && opts.append) throw new Error(`${branch} does not exist on origin — omit --append to create it`);
|
|
41
43
|
if (spawnSync('git', ['show-ref', '--verify', '--quiet', `refs/heads/${branch}`], { cwd: repo }).status === 0) throw new Error(`${branch} already exists locally`);
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
|
|
44
|
+
let remoteTip: string | undefined;
|
|
45
|
+
if (opts.append) {
|
|
46
|
+
const advertised = remoteLine.split(/\s+/)[0] ?? '';
|
|
47
|
+
if (!/^[0-9a-f]{40}$/i.test(advertised)) throw new Error(`${branch} remote tip is not a commit SHA`);
|
|
48
|
+
git(repo, ['fetch', 'origin', `refs/heads/${branch}:refs/remotes/origin/${branch}`]);
|
|
49
|
+
remoteTip = git(repo, ['rev-parse', '--verify', `refs/remotes/origin/${branch}^{commit}`]);
|
|
50
|
+
if (advertised !== remoteTip) throw new Error(`${branch} remote tip moved — fast-forward refused (no force push)`);
|
|
51
|
+
for (const sha of picks) if (ancestor(repo, sha, remoteTip)) throw new Error(`pick ${sha} is already in ${branch}`);
|
|
52
|
+
}
|
|
53
|
+
const versionAt = remoteTip ?? base;
|
|
54
|
+
const pkg = JSON.parse(git(repo, ['show', `${versionAt}:package.json`])) as { version?: unknown };
|
|
55
|
+
if (pkg.version !== opts.version) throw new Error(`${opts.append ? 'release branch' : 'base'} package.json version ${pkg.version} is not ${opts.version}`);
|
|
56
|
+
const start = remoteTip ?? base;
|
|
57
|
+
log(`${branch}: ${start} + ${picks.join(' + ')}${opts.append ? ' (append)' : ''}${opts.dryRun ? ' (dry-run)' : ''}`);
|
|
45
58
|
if (opts.dryRun) return { branch, commit: null, dryRun: true };
|
|
46
59
|
const temp = mkdtempSync(join(tmpdir(), 'release-cut-branch-'));
|
|
47
60
|
const tree = join(temp, 'tree');
|
|
48
61
|
let added = false;
|
|
49
62
|
let commit: string | undefined;
|
|
50
63
|
try {
|
|
51
|
-
git(repo, ['worktree', 'add', '--detach', tree,
|
|
64
|
+
git(repo, ['worktree', 'add', '--detach', tree, start]);
|
|
52
65
|
added = true;
|
|
53
66
|
for (const sha of picks) {
|
|
54
67
|
try { git(tree, ['cherry-pick', sha]); }
|
|
@@ -68,13 +81,30 @@ export function cutReleaseBranch(opts: CutBranchOptions): { branch: string; comm
|
|
|
68
81
|
} finally { rmSync(temp, { recursive: true, force: true }); }
|
|
69
82
|
}
|
|
70
83
|
if (!commit) throw new Error('cherry-pick produced no commit');
|
|
71
|
-
const
|
|
72
|
-
|
|
84
|
+
const lease = opts.append ? remoteTip! : '';
|
|
85
|
+
let push: string;
|
|
86
|
+
try {
|
|
87
|
+
push = git(repo, ['push', '--porcelain', `--force-with-lease=refs/heads/${branch}:${lease}`, 'origin', `${commit}:refs/heads/${branch}`]);
|
|
88
|
+
} catch (error) {
|
|
89
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
90
|
+
if (opts.append && /stale info|rejected|non-fast-forward|failed to push/i.test(message)) {
|
|
91
|
+
throw new Error(`${branch} remote tip moved — fast-forward refused (no force push)`);
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
const landed = push.split('\n').some((line) => {
|
|
73
96
|
const [flag, ref, summary] = line.split('\t');
|
|
74
|
-
|
|
97
|
+
if (flag !== ' ' && flag !== '*') return false;
|
|
98
|
+
if (!ref?.endsWith(`:refs/heads/${branch}`)) return false;
|
|
99
|
+
if (!opts.append) return flag === '*' && summary === '[new branch]';
|
|
100
|
+
const range = summary?.match(/^([0-9a-f]+)\.\.([0-9a-f]+)$/i);
|
|
101
|
+
return flag === ' ' && !!range && remoteTip!.startsWith(range[1]!) && commit.startsWith(range[2]!);
|
|
75
102
|
});
|
|
76
|
-
if (!
|
|
77
|
-
|
|
103
|
+
if (!landed) throw new Error(opts.append
|
|
104
|
+
? `${branch} remote tip moved — fast-forward refused (no force push)`
|
|
105
|
+
: `${branch} already exists on origin (push did not create a new branch)`);
|
|
106
|
+
if (opts.append) report('appended', undefined, commit, { from: remoteTip!, to: commit, picks });
|
|
107
|
+
else report('created', undefined, commit);
|
|
78
108
|
log(`${branch} → ${commit}`);
|
|
79
109
|
return { branch, commit, dryRun: false };
|
|
80
110
|
} catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
3
|
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
4
|
import { tmpdir } from 'node:os';
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
@@ -19,10 +19,10 @@ import { POD_BUN_CACHE_HOST_PATH, parseInstallSeconds, podBunCacheVolume } from
|
|
|
19
19
|
import { PodPoolScheduler, parsePodPool, checkPodPool } from '../../src/task-orchestrator/surfaces/pod-pool.js';
|
|
20
20
|
import { defaultKubectl } from '../../src/task-orchestrator/surfaces/self-implement-pod.js';
|
|
21
21
|
|
|
22
|
-
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }
|
|
22
|
+
interface CommandResult { rc: number; output: string; passedIds?: string[]; stalledEnv?: Array<{ file: string; reason: 'no-output' }>; timedOut?: boolean }
|
|
23
23
|
export interface GateRunner {
|
|
24
|
-
command(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
25
|
-
localCommand(cmd: string, args: string[], cwd: string): Promise<CommandResult>;
|
|
24
|
+
command(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
25
|
+
localCommand(cmd: string, args: string[], cwd: string, limitMs?: number): Promise<CommandResult>;
|
|
26
26
|
sweep(tree: string, logDir?: string, pod?: GateOptions['pod']): Promise<CommandResult>;
|
|
27
27
|
add(tree: string, commit: string): Promise<void>;
|
|
28
28
|
remove(tree: string): Promise<void>;
|
|
@@ -52,7 +52,7 @@ export interface GateResult {
|
|
|
52
52
|
fixed: number;
|
|
53
53
|
knownEnv: number;
|
|
54
54
|
knownEnvCleared: string[];
|
|
55
|
-
stalledEnv: Array<{ file: string; reason: 'no-output'; local: 'passed' | 'failed' }>;
|
|
55
|
+
stalledEnv: Array<{ file: string; reason: 'no-output' | 'isolated-timeout'; local: 'passed' | 'failed' | 'timeout' }>;
|
|
56
56
|
baselineSource?: 'ledger' | 'instance' | 'cut-logs' | 'swept';
|
|
57
57
|
durationMs: number;
|
|
58
58
|
error?: string;
|
|
@@ -189,17 +189,65 @@ class StalledPodShards extends Error {
|
|
|
189
189
|
}
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
+
/** Isolated re-runs (host stall re-run · cut and baseline re-checks) are time-limited; sweeps and installs keep their full time. */
|
|
193
|
+
export const GATE_ISOLATED_TIMEOUT_MS = 300_000;
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Runs a limited local command in its own process group. On timeout: SIGTERM the group (test-deterministic forwards it to its own
|
|
197
|
+
* `bun test` group — Chrome included), SIGKILL after a grace, and return after a hard deadline even if a foreign-group process still holds the pipe.
|
|
198
|
+
*/
|
|
199
|
+
export function limitedLocalCommand(cmd: string, args: string[], cwd: string, limitMs: number, graceMs = 10_000): Promise<CommandResult> {
|
|
200
|
+
return new Promise((resolveRun) => {
|
|
201
|
+
const chunks: string[] = [];
|
|
202
|
+
let timedOut = false;
|
|
203
|
+
let done = false;
|
|
204
|
+
const timers: ReturnType<typeof setTimeout>[] = [];
|
|
205
|
+
const child = spawn(cmd, args, { cwd, detached: true, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
206
|
+
let kept = 0;
|
|
207
|
+
// Same ceiling as the unlimited spawnSync path (maxBuffer 128MB): drop the oldest output beyond it, keep the tail.
|
|
208
|
+
const keep = (d: string) => { chunks.push(d); kept += d.length; while (kept > 128 * 1024 * 1024 && chunks.length > 1) kept -= chunks.shift()!.length; };
|
|
209
|
+
child.stdout?.setEncoding('utf8').on('data', keep);
|
|
210
|
+
child.stderr?.setEncoding('utf8').on('data', keep);
|
|
211
|
+
const signalGroup = (signal: NodeJS.Signals) => { try { if (child.pid) process.kill(-child.pid, signal); } catch { /* group already gone */ } };
|
|
212
|
+
const finish = (code: number | null, error?: Error) => {
|
|
213
|
+
if (done) return;
|
|
214
|
+
done = true;
|
|
215
|
+
for (const timer of timers) clearTimeout(timer);
|
|
216
|
+
child.stdout?.destroy();
|
|
217
|
+
child.stderr?.destroy();
|
|
218
|
+
const tail = error ? `\n${error}` : timedOut ? `\nkilled after ${limitMs}ms (process group)` : '';
|
|
219
|
+
resolveRun({ rc: code ?? 2, output: `${chunks.join('')}${tail}`, ...(timedOut ? { timedOut } : {}) });
|
|
220
|
+
};
|
|
221
|
+
timers.push(setTimeout(() => {
|
|
222
|
+
timedOut = true;
|
|
223
|
+
signalGroup('SIGTERM');
|
|
224
|
+
timers.push(setTimeout(() => signalGroup('SIGKILL'), graceMs));
|
|
225
|
+
timers.push(setTimeout(() => finish(null), graceMs + 5_000));
|
|
226
|
+
}, limitMs));
|
|
227
|
+
child.on('error', (error) => finish(null, error));
|
|
228
|
+
child.on('close', (code) => finish(code));
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
|
|
192
232
|
export function createGateRunner(repo: string, remote?: string, commandOverride?: GateRunner['command'], podCommand: (options: RunPodCommandOptions) => Promise<PodCommandResult> = runPodCommand, poolOverride?: PodPoolScheduler, podLogTail?: (job: PodCommandResult) => string): GateRunner {
|
|
193
233
|
let remoteMirror: string | undefined;
|
|
194
|
-
const localCommand: GateRunner['
|
|
234
|
+
const localCommand: GateRunner['localCommand'] = async (cmd, args, cwd, limitMs) => {
|
|
235
|
+
if (limitMs) return limitedLocalCommand(cmd, args, cwd, limitMs);
|
|
195
236
|
const run = spawnSync(cmd, args, { cwd, encoding: 'utf8', maxBuffer: 128 * 1024 * 1024 });
|
|
196
237
|
return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}` };
|
|
197
238
|
};
|
|
198
239
|
const command: GateRunner['command'] = commandOverride ?? (remote
|
|
199
|
-
? async (cmd, args, cwd) => {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
240
|
+
? async (cmd, args, cwd, limitMs) => {
|
|
241
|
+
// A limited remote run is killed on the remote host; the local ssh deadline is a backstop.
|
|
242
|
+
// GNU timeout signals its own process group: -s KILL would kill timeout too (ssh 255), so TERM first then KILL after 10s (124 / 137).
|
|
243
|
+
// No coreutils timeout on the remote → run unlimited there and rely on the local ssh deadline.
|
|
244
|
+
const limitPrefix = limitMs ? `$(command -v timeout >/dev/null 2>&1 && echo 'timeout -k 10 ${Math.ceil(limitMs / 1000)}') ` : '';
|
|
245
|
+
// Start in /tmp, not the caller checkout. macOS resolves that symlink to /private/tmp before the child sees it;
|
|
246
|
+
// the remote command still `cd`s to the quoted path, so either spelling is the same directory.
|
|
247
|
+
const run = spawnSync('ssh', [remote, `PATH=$HOME/.bun/bin:/opt/homebrew/bin:$PATH; export PATH; cd ${quote(cwd)} && ${limitPrefix}${[cmd, ...args].map(quote).join(' ')}`],
|
|
248
|
+
{ cwd: '/tmp', encoding: 'utf8', maxBuffer: 128 * 1024 * 1024, ...(limitMs ? { timeout: limitMs + 60_000, killSignal: 'SIGKILL' as const } : {}) });
|
|
249
|
+
const timedOut = !!limitMs && (run.status === 124 || run.status === 137 || (run.error as NodeJS.ErrnoException | undefined)?.code === 'ETIMEDOUT');
|
|
250
|
+
return { rc: run.status ?? 2, output: `${run.stdout ?? ''}\n${run.stderr ?? ''}${run.error ? `\n${run.error}` : ''}`, ...(timedOut ? { timedOut } : {}) };
|
|
203
251
|
}
|
|
204
252
|
: localCommand);
|
|
205
253
|
const podSweep = async (tree: string, logDir: string | undefined, pod: NonNullable<GateOptions['pod']>): Promise<CommandResult> => {
|
|
@@ -630,7 +678,15 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
630
678
|
}
|
|
631
679
|
for (const { file, reason } of stalled) {
|
|
632
680
|
if (!/^(?:[\w.-]+\/)+[\w.-]+\.test\.tsx?$/.test(file) || file.split('/').includes('..')) throw new Error(`unsafe test path: ${file}`);
|
|
633
|
-
|
|
681
|
+
// 0.2.15: InsidePage.test.tsx gave no Pod output and then hung on the host with headless Chrome — the run could only fail.
|
|
682
|
+
const isolated = await runner.localCommand('bun', ['run', 'test:deterministic', asPath(file)], hostTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
683
|
+
// A file that stalls in its Pod shard and again on the host is an environment stall: record it, measure nothing from it.
|
|
684
|
+
if (isolated.timedOut) {
|
|
685
|
+
// Grandchildren (e.g. headless Chrome) can outlive the kill — the event lets the next release see a repeat.
|
|
686
|
+
debug.log('release-loop.gate', 'isolated-timeout', { file, label, limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
687
|
+
local.push({ file, reason, local: 'timeout' });
|
|
688
|
+
continue;
|
|
689
|
+
}
|
|
634
690
|
const parsed = failuresOf(isolated, `${label} host isolated ${file}`);
|
|
635
691
|
if ([...parsed.failures, ...parsed.errors].some((id) => fileOf(id) !== file)) throw new Error(`host isolated run attributed to another file: ${file}`);
|
|
636
692
|
measured.failures.push(...parsed.failures);
|
|
@@ -688,14 +744,26 @@ export async function judgeGate(opts: GateOptions, runner: GateRunner = createGa
|
|
|
688
744
|
result.fixed = diff.fixed.length;
|
|
689
745
|
result.preexisting = diff.common.length;
|
|
690
746
|
for (const file of new Set(diff.newFailures.map(fileOf))) {
|
|
691
|
-
|
|
747
|
+
// 0.2.15: the cut re-check of a new-failure file (InsidePage.test.tsx · headless Chrome) hung for good — limit it like the host re-run.
|
|
748
|
+
const cutCheck = cutHostFailed.has(file) ? undefined : await runner.command('bun', ['run', 'test:deterministic', asPath(file)], cutTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
749
|
+
if (cutCheck?.timedOut) {
|
|
750
|
+
debug.log('release-loop.gate', 'isolated-timeout', { file, label: 'cut isolated', limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
751
|
+
result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
|
|
752
|
+
continue;
|
|
753
|
+
}
|
|
754
|
+
const isolatedCut = cutCheck === undefined
|
|
692
755
|
? { failures: cut.failures.filter((id) => fileOf(id) === file), errors: cut.errors.filter((id) => fileOf(id) === file) }
|
|
693
|
-
: failuresOf(
|
|
756
|
+
: failuresOf(cutCheck, `cut isolated ${file}`);
|
|
694
757
|
const reproduced = new Set([...isolatedCut.failures, ...isolatedCut.errors]);
|
|
695
758
|
const candidates = diff.newFailures.filter((id) => fileOf(id) === file && reproduced.has(id));
|
|
696
759
|
if (candidates.length === 0) continue;
|
|
697
760
|
const baselineTree = await getBaseTree();
|
|
698
|
-
const previous = await runner.command('bun', ['run', 'test:deterministic', asPath(file)], baselineTree);
|
|
761
|
+
const previous = await runner.command('bun', ['run', 'test:deterministic', asPath(file)], baselineTree, GATE_ISOLATED_TIMEOUT_MS);
|
|
762
|
+
if (previous.timedOut) {
|
|
763
|
+
debug.log('release-loop.gate', 'isolated-timeout', { file, label: 'baseline isolated', limitMs: GATE_ISOLATED_TIMEOUT_MS });
|
|
764
|
+
result.stalledEnv.push({ file, reason: 'isolated-timeout', local: 'timeout' });
|
|
765
|
+
continue;
|
|
766
|
+
}
|
|
699
767
|
let oldFailures: Set<string>;
|
|
700
768
|
if (/No tests found|had no matches/i.test(previous.output) && previous.rc === 1) {
|
|
701
769
|
const lookup = await runner.command('git', ['ls-tree', '--name-only', baseSha, '--', file], opts.remote ? baselineTree : repo);
|