@overdeck/desktop 0.45.24 → 0.47.0
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/drizzle/overdeck/0000_overdeck_init.sql +36 -1
- package/package.json +1 -1
- package/server/{acceptance-criteria-DDCnXF58.js → acceptance-criteria-10Ur5Dne.js} +2 -2
- package/server/activity-logger-CMthG6Vd.js +3 -0
- package/server/{activity-logger-DDlOIAML.js → activity-logger-CyteR4JO.js} +24 -13
- package/server/{advancing-selfheal-BVwW4qRn.js → advancing-selfheal-BNwqxseD.js} +6 -6
- package/server/{agent-gc-CHcnvonI.js → agent-gc-C3h9Or3a.js} +5 -5
- package/server/agent-state-CYXJVG72.js +3 -0
- package/server/agent-state-service-C63p_ftq.js +2 -0
- package/server/{agent-state-service-BsgWxZuB.js → agent-state-service-Ds6OeFvo.js} +15 -9
- package/server/{agents-EbI0zCJl.js → agents-J5mEnekq.js} +23 -16
- package/server/agents-ePFVoZbu.js +3 -0
- package/server/archive-planning-B2XplFzT.js +2 -0
- package/server/{archive-planning-Du25Jtst.js → archive-planning-BwhxeP7C.js} +22 -26
- package/server/{artifact-index-jobs-CbEDfCDu.js → artifact-index-jobs-D1q_vR3V.js} +2 -2
- package/server/{assemble-J2ZZ2C15.js → assemble-Bgwqv-Ro.js} +2 -2
- package/server/auto-spawn-consent-CQ9Bi9q6.js +1141 -0
- package/server/{backlog-auto-trigger-2R2jog7n.js → backlog-auto-trigger-n1lzPgN4.js} +3 -3
- package/server/{behavior-B3exJ42O.js → behavior-td6-xhQk.js} +2 -2
- package/server/boot-reconciled-stop-events-C7gGe3oB.js +44 -0
- package/server/bridge-pool-patrol-CYH2OLb5.js +136 -0
- package/server/{briefing-freshness-D1wzUIWv.js → briefing-freshness-DRqZ8lAz.js} +2 -2
- package/server/{build-info-Ie_D0_BT.js → build-info-7yiEpl6u.js} +5 -3
- package/server/{build-info-CZk_TITh.js → build-info-CpG__c63.js} +1 -1
- package/server/{checkpoint-manager-DwbQoqrC.js → checkpoint-manager-CkKmB0wZ.js} +1 -1
- package/server/{checkpoint-manager-BI1Wxoku.js → checkpoint-manager-DBcyNR8w.js} +2 -2
- package/server/checkpoint-worker.js +1 -1
- package/server/{checkpoints-CQB1h765.js → checkpoints-D95apurL.js} +2 -2
- package/server/{checkpoints-BJIs5QIA.js → checkpoints-Wm963lm8.js} +1 -1
- package/server/{claude-hooks-provision-8Ngo8TpT.js → claude-hooks-provision-4RPxNkE8.js} +1 -1
- package/server/{claude-hooks-provision-D8GXF3_s.js → claude-hooks-provision-Bjitl9aI.js} +13 -2
- package/server/{claude-permissions-DNWEVV6z.js → claude-permissions-CpbpvUQ2.js} +2 -2
- package/server/{claude-settings-overlay-DWy-Bb4Y.js → claude-settings-overlay-BzkHYDwR.js} +1 -1
- package/server/{claude-settings-overlay-CEdaqpWI.js → claude-settings-overlay-_H9Yrzab.js} +2 -2
- package/server/cli-CjOCSnfj.js +3 -0
- package/server/{cli-RoOx5DUf.js → cli-Cte9ZFGn.js} +9 -9
- package/server/{cliproxy-C8a1vtTU.js → cliproxy-C5fMJ7rY.js} +2 -2
- package/server/{cliproxy-fHfIeiGd.js → cliproxy-DaAbWMjC.js} +1 -1
- package/server/{close-issue-DoOifFIK.js → close-issue-BPh1ARCF.js} +5 -5
- package/server/{closed-issue-reaper-CktPXo8m.js → closed-issue-reaper-DHRfRMrp.js} +1 -1
- package/server/{closed-issue-reaper-Bk3SU4RC.js → closed-issue-reaper-DoOZqGFK.js} +88 -17
- package/server/{codex-BZXGhUVB.js → codex-BzGDW_d3.js} +36 -7
- package/server/{codex-GcHuUF-k.js → codex-o8FJwzmy.js} +1 -1
- package/server/{codex-parser-CFZ1_1Jl.js → codex-parser-Dt-ycU8L.js} +2 -2
- package/server/compaction-continuation-CvXpbZ4w.js +200 -0
- package/server/{config-CKxYiCmX.js → config-Bi3u1VK7.js} +4 -3
- package/server/{config-BZ0sHTju.js → config-BvLe652W.js} +2 -2
- package/server/{config-DzV30TOm.js → config-CL3Gyvja.js} +1 -1
- package/server/{config-yaml-DySPNmKN.js → config-yaml-CxdwhAIA.js} +276 -14
- package/server/conflict-gate-CX80JYYS.js +3 -0
- package/server/{conflict-gate-BaCVrH-N.js → conflict-gate-E4nt7ZeS.js} +60 -14
- package/server/{context-layers-igZ4FI4_.js → context-layers-C_IIysnm.js} +3 -3
- package/server/{context-layers-BrPmE__h.js → context-layers-DAG83UZt.js} +2 -2
- package/server/{control-settings-BQp7_4uD.js → control-settings-CiqGxXnc.js} +56 -19
- package/server/{conversation-compaction-eQB5CkOe.js → conversation-compaction-BE3CNtHF.js} +1 -1
- package/server/{conversation-compaction-CgrR8YYp.js → conversation-compaction-D6-yLtqt.js} +8 -8
- package/server/{conversation-service-DPJ71F33.js → conversation-service-DBxoYMDl.js} +10 -8
- package/server/conversation-service-D_TS2b1r.js +3 -0
- package/server/{conversations-CAsJF9kh.js → conversations-BKDVpB-3.js} +1 -1
- package/server/{conversations-6AfUfbwc.js → conversations-D0UEdLtk.js} +4 -4
- package/server/{cost-R-m6Yi-W.js → cost-DS1s0DbG.js} +3 -3
- package/server/{cost-B2ddP6wq.js → cost-DpgYmYqR.js} +12 -2
- package/server/{cost-sync-BsN6ER0n.js → cost-sync-tOCCGnEt.js} +3 -3
- package/server/{dag-B1ucnjDU.js → dag-BtkgLeqY.js} +2 -2
- package/server/{dangerous-git-ops-BXqeWVSf.js → dangerous-git-ops-CFLykHWt.js} +1 -1
- package/server/dashboard-db-worker.js +7 -7
- package/server/deacon-BIblC-WX.js +4 -0
- package/server/{deacon-nudge-log-6wVqAaqz.js → deacon-nudge-log-B08gF0mh.js} +3 -3
- package/server/{deacon-CU_cAYSR.js → deacon-pLRLbPvR.js} +3308 -2707
- package/server/deacon-pending-lifecycle-B-Io84AP.js +33 -0
- package/server/deacon-swarm-B1XkIvfr.js +2 -0
- package/server/{deacon-swarm-BXZZ1Z2-.js → deacon-swarm-v5PMun-m.js} +15 -14
- package/server/deacon.js +14 -19
- package/server/dead-end-trip-D8A3msX9.js +13 -0
- package/server/delivery-BX9HR4wQ.js +3 -0
- package/server/{deploy-patrol-AiFoP8dw.js → deploy-patrol-sYgOxLeb.js} +69 -18
- package/server/deploy-queue-VfohaPN_.js +169 -0
- package/server/deploy-queue-gHJBycaF.js +3 -0
- package/server/deploy-window-CFRZ0-kt.js +3 -0
- package/server/{deploy-window-C3F-tihF.js → deploy-window-D4DQb8VP.js} +40 -16
- package/server/{dev-supervisor-bcOWqdQE.js → dev-supervisor-DiOBDM1c.js} +2 -2
- package/server/{devcontainer-renderer-_d6XhyND.js → devcontainer-renderer-BUOIlfgD.js} +3 -3
- package/server/dist-B3YPQa5A.js +3 -0
- package/server/{dist-Cb_0uvpS.js → dist-C3A8B8wM.js} +1 -1
- package/server/{dist-src-BSoS7mkd.js → dist-src-C0adpegX.js} +1 -1
- package/server/{docker-DcMTFTUC.js → docker-BJzlkqYB.js} +2 -2
- package/server/docker-Y3PElJb0.js +3 -0
- package/server/dod-DlNtcH6n.js +91 -0
- package/server/durable-review-pipeline-Cibps-Rg.js +20 -0
- package/server/durable-review-pipeline-DP6bIcOY.js +3 -0
- package/server/{ensure-devcontainer-DNi-ono3.js → ensure-devcontainer-BAofzBPv.js} +4 -4
- package/server/{ensure-devcontainer-BZgOhRo7.js → ensure-devcontainer-DMAbWqtQ.js} +1 -1
- package/server/{esm-shims-OSHAYaS1.js → esm-shims-BeObUkaw.js} +1 -1
- package/server/{event-store-EV2cMiXj.js → event-store-5UKzh9Jf.js} +1 -1
- package/server/{event-store-CQL6NeqB.js → event-store-CrHcVC5_.js} +23 -4
- package/server/{events-DTfAe4My.js → events-BNrvchBg.js} +3 -3
- package/server/{events-BTdrdEq8.js → events-CF8QDbuV.js} +1 -1
- package/server/{factory-CFibJuaw.js → factory-Cmm9Hsho.js} +5 -5
- package/server/{feature-registry-population-DKZFMbFt.js → feature-registry-population-B6HpNWRy.js} +4 -4
- package/server/{feature-registry-storage-BdPuLcBX.js → feature-registry-storage-DJEFx1Pf.js} +2 -2
- package/server/{features-ez2271oH.js → features-D5km80UI.js} +2 -2
- package/server/{feedback-target-CoFDHiM8.js → feedback-target-BJ5ZADsu.js} +1 -1
- package/server/{feedback-target-7mqqwrX6.js → feedback-target-DSxEL6lP.js} +8 -8
- package/server/{feedback-writer-CY5L1lB7.js → feedback-writer-C27Hak8a.js} +5 -5
- package/server/{feedback-writer-D3a22RCd.js → feedback-writer-CoB9Z6aU.js} +1 -1
- package/server/{flywheel-DHrgCphM.js → flywheel-BHx8XuMr.js} +47 -20
- package/server/{flywheel-SVF02Jpq.js → flywheel-DZZdtiOL.js} +1 -1
- package/server/{flywheel-DzGtWnJ1.js → flywheel-f662Q-y2.js} +16 -15
- package/server/flywheel-merge-order-D_xLMbJU.js +462 -0
- package/server/flywheel-merge-order-Dil0LmdN.js +2 -0
- package/server/forge-B5o319qI.js +3 -0
- package/server/forge-BaKGCya6.js +383 -0
- package/server/{from-kNxT0bZC.js → from-B3tZbB55.js} +1 -1
- package/server/fs-lock-Bre3sGoz.js +130 -0
- package/server/{fts-db-CE46vDkZ.js → fts-db-IVc4_M3f.js} +2 -2
- package/server/{fts-operations-CqY6zl-i.js → fts-operations-B9jG8wa3.js} +2 -2
- package/server/{git-utils-CRw2zLPC.js → git-utils-CsvYfJU3.js} +30 -5
- package/server/git-utils-Xg4PFN45.js +3 -0
- package/server/{github-app-ClZ2lk8D.js → github-app-Bmg4R9mv.js} +1 -1
- package/server/{github-app-C3QrAUd0.js → github-app-CmNk0zLU.js} +3 -3
- package/server/{github-client-DfQYVUaw.js → github-client-B6KM9_wA.js} +1 -1
- package/server/{github-client-BnIsSrEN.js → github-client-qazkPrg0.js} +3 -3
- package/server/github-pr-lookup-DAcVeRLK.js +58 -0
- package/server/{harness-binary-DPUi-Pv8.js → harness-binary-BSpiXaLw.js} +2 -2
- package/server/{harness-resolve-3lNZkyT4.js → harness-resolve-BAJu5MX8.js} +4 -4
- package/server/{health-events-BDOHby5Q.js → health-events-2Z6Wa9SM.js} +1 -1
- package/server/{health-events-BBcTYQKU.js → health-events-BwX32k8d.js} +2 -2
- package/server/{hooks-DPVX1rwQ.js → hooks-D1g1jKLI.js} +1 -1
- package/server/{hooks-CKZgXlJb.js → hooks-DbtbimEf.js} +2 -2
- package/server/{hume-Cz8igP9E.js → hume-DXNiu6nQ.js} +2 -2
- package/server/hume-VUCNbTgn.js +3 -0
- package/server/{idle-stack-reaper-CuLF0j2H.js → idle-stack-reaper-CRA1e0jr.js} +1 -1
- package/server/{idle-stack-reaper-DDIRwxUY.js → idle-stack-reaper-Cy-dQzW4.js} +99 -6
- package/server/in-flight-guard-D-sSkADM.js +45 -0
- package/server/{infra-BEqFxlJc.js → infra-BDQDRopn.js} +81 -5
- package/server/{injection-C8aiVJrF.js → injection-6AGnXxlj.js} +7 -7
- package/server/{injection-CPUIqNhI.js → injection-Bl8d_jHZ.js} +1 -1
- package/server/inspect-agent-CUwVmr2h.js +2 -0
- package/server/{inspect-agent-BLuJ1AkT.js → inspect-agent-nXdIptyh.js} +26 -25
- package/server/{internal-token-s0-oKybJ.js → internal-token-DzK385vD.js} +2 -2
- package/server/{internal-token-BrcU6qfe.js → internal-token-Ias7N8Kg.js} +1 -1
- package/server/{io-C9kF1wtJ.js → io-B00PBNku.js} +44 -27
- package/server/{issue-closed-CDJk4pIp.js → issue-closed-BehTG1be.js} +8 -8
- package/server/{issue-lifecycle-ojZAzDBP.js → issue-lifecycle-Birk-ifu.js} +42 -30
- package/server/{issue-lifecycle-B8sZHVHh.js → issue-lifecycle-BqSuQvbn.js} +1 -1
- package/server/{issue-service-singleton-BvJ09SaR.js → issue-service-singleton-B2wXiIlw.js} +1 -1
- package/server/{issue-service-singleton-Yx5DdmVJ.js → issue-service-singleton-C6ZduvuY.js} +10 -10
- package/server/{issues-BMmP_77e.js → issues-DQ5-LatB.js} +2 -2
- package/server/{jsonl-parser-BXSlHwEh.js → jsonl-parser-BDpnGFCl.js} +5 -4
- package/server/{label-cleanup-B0UJwnv5.js → label-cleanup-Dwnky8FG.js} +3 -3
- package/server/{lifecycle-xmBfK2GJ.js → lifecycle-B3s4sM1G.js} +6 -7
- package/server/{lifecycle-yPd___5E.js → lifecycle-B4Xdb4cY.js} +2 -2
- package/server/{lifecycle-io-DPo7x11I.js → lifecycle-io-CzTHEooc.js} +1 -1
- package/server/{lifecycle-io-BREGpnLk.js → lifecycle-io-DCo6s6ks.js} +5 -5
- package/server/{linear-client-BsuUvkdA.js → linear-client-9uq7fhiV.js} +3 -3
- package/server/{linear-client-CTskEeT_.js → linear-client-CnbtHiic.js} +1 -1
- package/server/linear-mcp-auth-BxUOzswy.js +280 -0
- package/server/linear-mcp-auth-D46fBCLf.js +2 -0
- package/server/memory-fts-worker.js +1 -1
- package/server/{merge-CefQNAnq.js → merge-DiW9RdSB.js} +13 -11
- package/server/{merge-agent-4WOWlSYA.js → merge-agent-BLUgRsz3.js} +234 -84
- package/server/{merge-agent-CKkgIBPB.js → merge-agent-DQTXZk8z.js} +1 -1
- package/server/{merge-batch-ClFn2T-y.js → merge-batch-Cp_j9BGk.js} +1 -1
- package/server/merge-completeness-B46KqhtY.js +277 -0
- package/server/merge-completeness-BLbB5-6n.js +3 -0
- package/server/merge-ops-1abz6O_P.js +2 -0
- package/server/merge-rebase-CJJVAJuz.js +2 -0
- package/server/{merge-rebase-BfaTyXsw.js → merge-rebase-DIkBjgwq.js} +3 -3
- package/server/{merge-set-DJ8-UErs.js → merge-set-CBgHfwM0.js} +11 -5
- package/server/merge-set-DeUxFsC3.js +3 -0
- package/server/{merge-B1DiziGU.js → merge-sr8Nrnmi.js} +1 -1
- package/server/merge-sync-CpGmk6Ch.js +3 -0
- package/server/{merge-sync-VPV28g0g.js → merge-sync-kr4vqyjM.js} +409 -153
- package/server/{merge-train-CHopycP0.js → merge-train-Dmrcus12.js} +18 -8
- package/server/{merge-train-deps-D1cOZzy2.js → merge-train-deps-D_uGX91G.js} +21 -15
- package/server/merge-verification-NvaAd8CZ.js +220 -0
- package/server/merged-docker-cleanup-worker-DH_hrGPI.js +3 -0
- package/server/merged-docker-cleanup-worker-Y1R06TEl.js +107 -0
- package/server/{multipart-parser-duPAfSBw.js → multipart-parser-R-oC8dQg.js} +2 -2
- package/server/{no-resume-mode-Nw29vaFb.js → no-resume-mode-BgD0JUmM.js} +1 -1
- package/server/node_modules/.package-lock.json +30 -30
- package/server/node_modules/@effect/platform-node/node_modules/@effect/platform-node-shared/package.json +3 -3
- package/server/node_modules/@google-cloud/common/package.json +2 -2
- package/server/node_modules/@types/node/README.md +1 -1
- package/server/node_modules/@types/node/crypto.d.ts +1 -1
- package/server/node_modules/@types/node/package.json +2 -2
- package/server/node_modules/gaxios/build/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/server/node_modules/gaxios/build/esm/tsconfig.tsbuildinfo +1 -1
- package/server/node_modules/gaxios/package.json +1 -1
- package/server/node_modules/google-auth-library/build/src/auth/googleauth.js +24 -19
- package/server/node_modules/google-auth-library/package.json +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/auth.proto +2 -1
- package/server/node_modules/google-gax/build/protos/google/api/backend.proto +12 -1
- package/server/node_modules/google-gax/build/protos/google/api/billing.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/client.proto +165 -53
- package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1/resources.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/quota_adjuster_settings.proto +7 -3
- package/server/node_modules/google-gax/build/protos/google/api/cloudquotas/v1beta/resources.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/config_change.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/context.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/control.proto +3 -3
- package/server/node_modules/google-gax/build/protos/google/api/distribution.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/documentation.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/endpoint.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/error_reason.proto +186 -1
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/checked.proto +31 -3
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/eval.proto +8 -9
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/explain.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/syntax.proto +20 -22
- package/server/node_modules/google-gax/build/protos/google/api/expr/v1alpha1/value.proto +2 -3
- package/server/node_modules/google-gax/build/protos/google/api/field_behavior.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/field_info.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/http.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/httpbody.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/label.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/launch_stage.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/log.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/logging.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/metric.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/monitored_resource.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/monitoring.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/policy.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/quota.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/resource.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/routing.proto +8 -4
- package/server/node_modules/google-gax/build/protos/google/api/service.proto +2 -3
- package/server/node_modules/google-gax/build/protos/google/api/source_info.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/system_parameter.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/api/usage.proto +7 -27
- package/server/node_modules/google-gax/build/protos/google/api/visibility.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/longrunning/operations.proto +20 -1
- package/server/node_modules/google-gax/build/protos/google/protobuf/any.proto +52 -108
- package/server/node_modules/google-gax/build/protos/google/protobuf/compiler/ruby/ruby_generated_code.proto +2 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_features.proto +21 -1
- package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_file_options.proto +24 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/cpp_options.proto +15 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/descriptor.proto +118 -30
- package/server/node_modules/google-gax/build/protos/google/protobuf/field_mask.proto +16 -18
- package/server/node_modules/google-gax/build/protos/google/protobuf/internal_options.proto +8 -1
- package/server/node_modules/google-gax/build/protos/google/protobuf/json/json_enumval_custom_string.proto +26 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/json_enumvalue_options.proto +29 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/json_options.proto +14 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/struct.proto +38 -22
- package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/forced_micro_string.proto +3 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/test_protos/repeated_field_proxy_import_message.proto +14 -0
- package/server/node_modules/google-gax/build/protos/google/protobuf/timestamp.proto +9 -8
- package/server/node_modules/google-gax/build/protos/google/protobuf/util/json_format_proto3.proto +18 -0
- package/server/node_modules/google-gax/build/protos/google/rpc/code.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/rpc/context/attribute_context.proto +7 -2
- package/server/node_modules/google-gax/build/protos/google/rpc/context/audit_context.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/rpc/error_details.proto +5 -5
- package/server/node_modules/google-gax/build/protos/google/rpc/http.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/rpc/status.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/type/calendar_period.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/color.proto +9 -10
- package/server/node_modules/google-gax/build/protos/google/type/date.proto +11 -9
- package/server/node_modules/google-gax/build/protos/google/type/datetime.proto +17 -17
- package/server/node_modules/google-gax/build/protos/google/type/dayofweek.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/decimal.proto +11 -13
- package/server/node_modules/google-gax/build/protos/google/type/expr.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/fraction.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/interval.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/type/latlng.proto +4 -5
- package/server/node_modules/google-gax/build/protos/google/type/localized_text.proto +3 -3
- package/server/node_modules/google-gax/build/protos/google/type/money.proto +1 -2
- package/server/node_modules/google-gax/build/protos/google/type/month.proto +1 -1
- package/server/node_modules/google-gax/build/protos/google/type/phone_number.proto +20 -15
- package/server/node_modules/google-gax/build/protos/google/type/postal_address.proto +39 -39
- package/server/node_modules/google-gax/build/protos/google/type/quaternion.proto +4 -4
- package/server/node_modules/google-gax/build/protos/google/type/timeofday.proto +11 -8
- package/server/node_modules/google-gax/package.json +5 -5
- package/server/node_modules/retry-request/package.json +4 -4
- package/server/node_modules/teeny-request/package.json +5 -5
- package/server/node_modules/undici/lib/cache/memory-cache-store.js +54 -9
- package/server/node_modules/undici/lib/cache/sqlite-cache-store.js +7 -1
- package/server/node_modules/undici/lib/core/request.js +12 -1
- package/server/node_modules/undici/lib/dispatcher/client-h1.js +11 -2
- package/server/node_modules/undici/lib/global.js +36 -16
- package/server/node_modules/undici/lib/handler/cache-handler.js +236 -116
- package/server/node_modules/undici/lib/handler/cache-revalidation-handler.js +3 -3
- package/server/node_modules/undici/lib/handler/retry-handler.js +24 -0
- package/server/node_modules/undici/lib/interceptor/cache.js +152 -62
- package/server/node_modules/undici/lib/interceptor/decompress.js +5 -2
- package/server/node_modules/undici/lib/util/cache.js +328 -66
- package/server/node_modules/undici/lib/util/date.js +23 -6
- package/server/node_modules/undici/lib/web/cookies/util.js +78 -8
- package/server/node_modules/undici/package.json +1 -1
- package/server/{orphan-proposed-reconciler-BvKkWMJQ.js → orphan-proposed-reconciler-B6vJHxtX.js} +242 -34
- package/server/{orphan-proposed-reconciler-CTSGcK8p.js → orphan-proposed-reconciler-DcgTPPFY.js} +1 -1
- package/server/package-lock.json +31 -31
- package/server/package.json +1 -1
- package/server/{pan-dir-B0c3w0Ya.js → pan-dir-DMzF5wne.js} +25 -11
- package/server/{paths-D-xWGHdh.js → paths-sJBSb7Ul.js} +2 -2
- package/server/pending-lifecycle-claim-DilzGfgk.js +110 -0
- package/server/persistent-logger-H_VjuOu7.js +57 -0
- package/server/{pickup-DUCLWrJY.js → pickup-DWhqzliO.js} +4 -3
- package/server/pipeline-membership-C88VBnhJ.js +3 -0
- package/server/pipeline-membership-D9SBvItj.js +57 -0
- package/server/pipeline-membership-gather-4eR-7Tqy.js +3 -0
- package/server/pipeline-membership-gather-CeVGnNJ2.js +593 -0
- package/server/{pipeline-notifier-BXkJzDC4.js → pipeline-notifier-D0ALV6Nw.js} +2 -2
- package/server/{pipeline-notifier-BJr8gQX6.js → pipeline-notifier-DT6xLFao.js} +1 -1
- package/server/post-merge-lifecycle-worker-8HIeS_J6.js +50 -0
- package/server/{project-pipelines-C0MWUeCU.js → project-pipelines-9SKt2bEc.js} +2 -2
- package/server/{project-repos-B6GycDh9.js → project-repos-B5pIphKg.js} +7 -3
- package/server/project-repos-DPjOdfPa.js +3 -0
- package/server/projects-_n3Zui6K.js +3 -0
- package/server/{prompts-DxzFXuLl.js → prompts-CgzMBvQF.js} +2 -2
- package/server/{providers-u4Ga0jOr.js → providers-BI1mOBQ4.js} +1 -1
- package/server/{providers-qQAwGQDW.js → providers-BJQSnAAp.js} +7 -7
- package/server/public/assets/{chunk-BO2N2NFS-BamJdinX.js → chunk-BO2N2NFS-D_pGACYN.js} +4 -4
- package/server/public/assets/{diagram-OG6HWLK6-DfOB8-w4.js → diagram-OG6HWLK6-BO6fe3hH.js} +1 -1
- package/server/public/assets/{dist-ByIuzMQW.js → dist-5zingMiA.js} +3 -3
- package/server/public/assets/{dist-D7zYSEA6.js → dist-CVxhbI2E.js} +1 -1
- package/server/public/assets/{ganttDiagram-6RSMTGT7-EdmRyXL3.js → ganttDiagram-6RSMTGT7-Bgj4A7kp.js} +1 -1
- package/server/public/assets/{highlighted-body-OFNGDK62-MerOfTX8.js → highlighted-body-OFNGDK62-DNPK7_cm.js} +1 -1
- package/server/public/assets/index-DEpY-D1O.js +807 -0
- package/server/public/assets/index-Ddkcz3WT.css +1 -0
- package/server/public/assets/{linear-BBbArIcK.js → linear-BB0jQT16.js} +1 -1
- package/server/public/assets/mermaid-GHXKKRXX-BzPfaQJ1.js +1 -0
- package/server/public/assets/{pieDiagram-4H26LBE5-BKIddiJN.js → pieDiagram-4H26LBE5-DeGIzzpZ.js} +1 -1
- package/server/public/assets/{prod-B8vYiu52.js → prod-XBLvikex.js} +4 -4
- package/server/public/assets/{quadrantDiagram-W4KKPZXB-D6RtoULk.js → quadrantDiagram-W4KKPZXB-BV9SLeWz.js} +1 -1
- package/server/public/assets/{sankeyDiagram-5OEKKPKP-CLrCedZX.js → sankeyDiagram-5OEKKPKP-Dwja0gj8.js} +1 -1
- package/server/public/assets/{xychartDiagram-2RQKCTM6-hznR8DYl.js → xychartDiagram-2RQKCTM6-DCB4qvfw.js} +1 -1
- package/server/public/index.html +2 -2
- package/server/{pull-requests-_lHueOHW.js → pull-requests-7BgLDsIo.js} +19 -10
- package/server/{pull-requests-7N8WKseP.js → pull-requests-Dq0y6IxE.js} +1 -1
- package/server/{queries-CjNIf-Ub.js → queries-C-MUZaIy.js} +824 -192
- package/server/{rally-CyWT_lNO.js → rally-CPysR4T_.js} +3 -3
- package/server/rally-D_VW4bJv.js +3 -0
- package/server/{rally-api-C8e_1JRi.js → rally-api-8Ptlp2os.js} +2 -2
- package/server/{rally-api-ClOSd-Tt.js → rally-api-CgosKdKL.js} +1 -1
- package/server/{rally-client-sgtVqzNU.js → rally-client-CCX0hADj.js} +1 -1
- package/server/{rally-client-lc-nokqr.js → rally-client-iYMHWQD7.js} +5 -5
- package/server/{reap-terminal-sessions-7KeHv1R1.js → reap-terminal-sessions-4Xg9NGA8.js} +2 -2
- package/server/{reap-terminal-sessions-BtHPo6Xc.js → reap-terminal-sessions-Qf-9LVQc.js} +2 -2
- package/server/{rebuild-stack-lwtsW0GO.js → rebuild-stack-CMdgaylx.js} +16 -7
- package/server/{rebuild-stack-CFvcBHsS.js → rebuild-stack-DqcOVmJO.js} +2 -2
- package/server/{reconstruct-cache-C3Ui7ojV.js → reconstruct-cache-CMZJ_Vjq.js} +9 -7
- package/server/record-BTwWhtBb.js +3 -0
- package/server/{record-update-dE1Aw83j.js → record-update-DaOKS7Wb.js} +124 -157
- package/server/record-update-DhI7eySG.js +3 -0
- package/server/{recovery-trip-BvRWfUXM.js → recovery-trip-DDH-02ng.js} +2 -2
- package/server/{release-engine-Cx48W7z8.js → release-engine-B2X9AfFC.js} +5 -5
- package/server/{release-set-CQfuN9-v.js → release-set-G4XKyQ8K.js} +3 -3
- package/server/{remote-C_2hasl7.js → remote-BXarMvqf.js} +3 -3
- package/server/{remote-ChgimyYa.js → remote-CUhyqjXk.js} +3 -3
- package/server/{remote-agents-By1ige5O.js → remote-agents-Bba-MgSA.js} +311 -48
- package/server/{remote-agents-CA3Ihlxj.js → remote-agents-CLZB3Coc.js} +2 -2
- package/server/{remote-completion-fVe9e_3u.js → remote-completion-CwAp79EC.js} +13 -13
- package/server/resume-mOREjlYu.js +3 -0
- package/server/{review-agent-CIGOQYBD.js → review-agent--pOptAEV.js} +65 -50
- package/server/{review-agent-DNboMyW4.js → review-agent-BTCNkq2h.js} +1 -1
- package/server/{review-artifacts-nRDPuMxd.js → review-artifacts-BNmw8dWE.js} +7 -6
- package/server/{review-context-2uj-YQ8J.js → review-context-DFxYOOJW.js} +9 -9
- package/server/{in-flight-guard-CAAkqPLu.js → review-convoy-liveness-BgU_1jJf.js} +3 -44
- package/server/review-status-CGvlA4o_.js +7737 -0
- package/server/review-status-Dma9SFaN.js +3 -0
- package/server/review-status-emit-DURrmGYX.js +50 -0
- package/server/{review-status-enrichment-Dci-ZwEt.js → review-status-enrichment-8NiNXi5W.js} +4 -4
- package/server/review-status-source-BLx_1siH.js +3 -0
- package/server/{review-status-source-CxuEjLGg.js → review-status-source-DIk1JSow.js} +24 -3
- package/server/{review-status-sync-BTOpxtTC.js → review-status-sync-0GxOqeqF.js} +11 -6
- package/server/{review-status-sync-DSDK3OMy.js → review-status-sync-muT-EBp2.js} +1 -1
- package/server/{review-verdict-feedback-CTd4QMBu.js → review-verdict-feedback-HwF6ODFy.js} +1 -1
- package/server/{review-verdict-feedback-CCjVA7Uk.js → review-verdict-feedback-xJNdqBnX.js} +65 -11
- package/server/{rollup-FGsKki9R.js → rollup-BQaol5TX.js} +8 -8
- package/server/{rollup-BKQC7jp4.js → rollup-BR4t_wck.js} +1 -1
- package/server/{scanner-CnqEZeDy.js → scanner-C-D2cFdX.js} +8 -7
- package/server/{sequence-io-D909zgeH.js → sequence-io-Bmf1EZM5.js} +4 -4
- package/server/{sequencer-agent-CzkcK6P5.js → sequencer-agent-CigMyKh2.js} +7 -6
- package/server/server.js +2005 -614
- package/server/{service-CLeAoU6G.js → service-CD0DvFvD.js} +114 -68
- package/server/{session-fork-CHI1XvL9.js → session-fork-B6cQ5eya.js} +2 -2
- package/server/{session-history-BT3IvqhC.js → session-history-3ON_IJjm.js} +7 -55
- package/server/settings-CVmWiJ-r.js +2 -0
- package/server/{settings-C0zgxOGf.js → settings-PilbFZAM.js} +3 -2
- package/server/{settings-BTMvpNFU.js → settings-VN0qOh4U.js} +3 -3
- package/server/{shadow-engineering-BJfgm-hX.js → shadow-engineering-DXvx77oE.js} +3 -3
- package/server/{shadow-state-7a-524T8.js → shadow-state-BBk_zacw.js} +2 -2
- package/server/{shadow-state-DnDyjujH.js → shadow-state-CZBp7pwq.js} +1 -1
- package/server/{shadow-utils-Db3oroFG.js → shadow-utils-DdZ05AJS.js} +2 -2
- package/server/{ship-log-D9MlVzYU.js → ship-log-7kJfjUX2.js} +1 -1
- package/server/{smart-compaction-CV2ukKND.js → smart-compaction-8fA5k5XN.js} +1 -1
- package/server/{smart-compaction-KCYScAU1.js → smart-compaction-ByhL9cRS.js} +8 -7
- package/server/{spawn-planning-session-DKpdFyOC.js → spawn-planning-session-pcF-YeFE.js} +38 -50
- package/server/spawn-planning-session-vLUJoFjy.js +3 -0
- package/server/{specialist-completion-Ky9wCDpb.js → specialist-completion-B07XjWD2.js} +1 -1
- package/server/{specialist-context-Bn3dCGS2.js → specialist-context-DJ-zFyL3.js} +5 -5
- package/server/{specialist-handoff-logger-CMEwYxa2.js → specialist-handoff-logger-aKSxbIgD.js} +2 -2
- package/server/{specialist-logs-C5p8G7tx.js → specialist-logs-Dae-n52_.js} +3 -3
- package/server/{specialists-CC4r-c2s.js → specialists-6PWdtWcg.js} +1177 -311
- package/server/{specialists-DM6bWjsi.js → specialists-Bz-wFT7R.js} +1 -1
- package/server/{specialists-jiuuyVkp.js → specialists-DUiExaxb.js} +18 -18
- package/server/{specialists-CShuVGWT.js → specialists-KR3pKulN.js} +1 -1
- package/server/specs--enInima.js +3 -0
- package/server/src-B948bGVR.js +11452 -0
- package/server/{src-CXmtj0im.js → src-rac0Mepv.js} +6 -6
- package/server/{src-CdmnTM2m.js → src-vHy2-oCv.js} +3 -3
- package/server/{stack-health-Bn3o8FBX.js → stack-health-BikqHSjn.js} +1 -1
- package/server/{stack-health-DxSdG9cW.js → stack-health-DqLTiEu4.js} +7 -5
- package/server/{stale-task-claims-KJKxxiiQ.js → stale-task-claims-Cab_Fo_Z.js} +5 -4
- package/server/{staleness-DPm7ClwA.js → staleness-C-a5rBxF.js} +1 -1
- package/server/{staleness-CT34nwr9.js → staleness-CGTZyp6V.js} +1 -1
- package/server/{state-home-Cn_Qu8IX.js → state-home-722Dkx92.js} +2 -2
- package/server/{state-mapping-BL_-3Yt8.js → state-mapping-CybR81Uf.js} +1 -1
- package/server/{state-migrate-BGg2ZaOt.js → state-migrate-2u8wIpoL.js} +4 -4
- package/server/{stuck-remediation-DYuPRYYw.js → stuck-remediation-CC6q3eqQ.js} +84 -26
- package/server/{stuck-remediation-s9_2qfi6.js → stuck-remediation-r1TJu68G.js} +1 -1
- package/server/{substrate-bug-weights-service-B-FwCs5V.js → substrate-bug-weights-service-CzIyyWFT.js} +3 -3
- package/server/{summary-fork-DpcxN9tf.js → summary-fork-BqgmgPDt.js} +2 -2
- package/server/{summary-fork-D1RAk9rJ.js → summary-fork-Df2WFijM.js} +8 -8
- package/server/supervisor/{injection-budget-Csw5tqfJ.js → injection-budget-Cg_KIb3o.js} +1 -1
- package/server/supervisor/{paths-DehGY2Fj.js → paths-BBOnCYlO.js} +10 -2
- package/server/supervisor/pty-supervisor.js +50 -4
- package/server/supervisor/{pty-token-x-ea28Us.js → pty-token-BbjwEUUM.js} +3 -3
- package/server/supervisor-B4anCCSm.js +2 -0
- package/server/{supervisor-wM4ooB5O.js → supervisor-BxqSpjcN.js} +3 -3
- package/server/{swarm-readiness-DlP_UjHq.js → swarm-readiness-DfFxa9-n.js} +3 -3
- package/server/{system-prerequisites-DUDj1YHf.js → system-prerequisites-ICLSkhhZ.js} +2 -2
- package/server/{teardown-workspace-DeTTImn-.js → teardown-workspace-DRdi_CPa.js} +15 -16
- package/server/{test-agent-queue-MbxX5vqq.js → test-agent-queue-A_-p-i0I.js} +8 -7
- package/server/{test-verdict-gscuksPv.js → test-verdict-BHgTAwIG.js} +1 -1
- package/server/{test-verdict-bWYBtqBg.js → test-verdict-C4i9aqEP.js} +1 -1
- package/server/{tldr-daemon-BYFkts95.js → tldr-daemon-CvWXBhX1.js} +1 -1
- package/server/{tldr-daemon-C-69c3OI.js → tldr-daemon-wvsU6bKv.js} +2 -2
- package/server/{tmux-R7N7y-Mh.js → tmux-CfDHob9M.js} +174 -4
- package/server/{tmux-B6z7h_vZ.js → tmux-Ctk6RV5l.js} +1 -1
- package/server/{token-BK6t8BIT.js → token-DrxwytsZ.js} +2 -2
- package/server/{token-util-DxXu-vd1.js → token-util-B48BlCba.js} +1 -1
- package/server/token-util-D411CAq9.js +3 -0
- package/server/{tracker-config-G6latr8C.js → tracker-config-BcPMElRB.js} +2 -2
- package/server/{tracker-utils-DHv5pSDT.js → tracker-utils-CJ6BHjcD.js} +3 -3
- package/server/{traefik-connect-Df2uXPsQ.js → traefik-connect-DZ6nMYMj.js} +1 -1
- package/server/{tunnel-DGdUkGoZ.js → tunnel-Bp-dRHZz.js} +1 -1
- package/server/{tunnel-CDVKJpRv.js → tunnel-DUbUPElU.js} +3 -3
- package/server/{typed-errors-BHhE85Ul.js → typed-errors-CKeoDdU3.js} +1 -1
- package/server/{typed-errors-2BTMxkZq.js → typed-errors-DyjseiB6.js} +1 -1
- package/server/{types-hraoaQIp.js → types-Cm5ASJbq.js} +1 -1
- package/server/{types-5M0lupud.js → types-Di8wdakq.js} +1 -1
- package/server/uat-promote-verification-CoJuh7XO.js +92 -0
- package/server/uat-train-4Q0k06CQ.js +0 -0
- package/server/uat-train-Dewoatps.js +2 -0
- package/server/{validate-IfDGkaVR.js → validate-B0C8p66t.js} +8 -6
- package/server/{validation-Dw1pzDzr.js → validation-B_kIRfNe.js} +4 -4
- package/server/{validation-CC9OPqJZ.js → validation-hVOwQk8T.js} +1 -1
- package/server/{verification-artifact-B3itKTxu.js → verification-artifact-BxlxTiYK.js} +1 -1
- package/server/{verification-artifact-TVFSz_q1.js → verification-artifact-D70AtckY.js} +1 -1
- package/server/{verification-runner-BW4HOKAz.js → verification-runner-Bc3cJvpC.js} +1 -1
- package/server/{verification-runner-CkgX6EtI.js → verification-runner-KZTz6u72.js} +40 -18
- package/server/{vtt-parser-DYPbKcIJ.js → vtt-parser-CE_i3Bcw.js} +1 -1
- package/server/{work-agent-prompt-Du8Vh2aJ.js → work-agent-prompt-ByrPAzOy.js} +20 -9
- package/server/{workflows-mbQzh6mQ.js → workflows-D0vP1WrK.js} +252 -99
- package/server/workflows-DtDX08ed.js +2 -0
- package/server/{workspace--8tnhDI0.js → workspace-COWvdibH.js} +1 -1
- package/server/{workspace-BPUws1-_.js → workspace-DQkRPLlQ.js} +3 -3
- package/server/workspace-anchor-drift-DXYIAHzr.js +224 -0
- package/server/workspace-anchor-drift-pwz23TI1.js +3 -0
- package/server/{workspace-config-CIVWZOQr.js → workspace-config-BdZM4PvZ.js} +1 -1
- package/server/{workspace-manager-CBhNdzE4.js → workspace-manager-BPgTrG66.js} +157 -118
- package/server/workspace-manager-C1F3XsnS.js +3 -0
- package/server/{workspace-metadata-BzHnwwX4.js → workspace-metadata-CdjzAP8x.js} +1 -1
- package/server/{workspace-metadata-_WygnyoX.js → workspace-metadata-DkO_iTLo.js} +2 -2
- package/server/{workspaces-Do_9J0LQ.js → workspaces-CRBJikLp.js} +749 -299
- package/sync-sources/dev-skills/pan-dashboard-restart/SKILL.md +34 -10
- package/sync-sources/hooks/linear-mcp-auth-hook +97 -0
- package/sync-sources/rules/ask-user-question-self-contained.md +9 -0
- package/sync-sources/skills/pan-close/SKILL.md +5 -3
- package/sync-sources/skills/pan-merge/SKILL.md +7 -5
- package/sync-sources/skills/pan-peer-review/SKILL.md +195 -0
- package/sync-sources/skills/pan-release/SKILL.md +57 -0
- package/sync-sources/skills/pan-reload/SKILL.md +3 -3
- package/sync-sources/skills/pan-restart/SKILL.md +5 -4
- package/sync-sources/skills/pan-review/SKILL.md +7 -1
- package/sync-sources/skills/pan-sync-main/SKILL.md +9 -8
- package/sync-sources/skills/pipeline-status/SKILL.md +10 -1
- package/sync-sources/skills/workspace-add-repo/SKILL.md +16 -1
- package/server/activity-logger-OEUBdfZ8.js +0 -3
- package/server/agent-state-Bc3tzUC8.js +0 -3
- package/server/agent-state-service-D5UGKIJL.js +0 -2
- package/server/agents-wi9NWzs2.js +0 -3
- package/server/archive-planning-BZDgzoEj.js +0 -3
- package/server/cli-B86rejVV.js +0 -3
- package/server/conflict-gate-BhAY7TUd.js +0 -3
- package/server/conversation-service-BLd3MYsG.js +0 -3
- package/server/deacon-kiQrb4ZT.js +0 -4
- package/server/deacon-swarm-C-GwfjJy.js +0 -2
- package/server/deploy-window-DkZraDyj.js +0 -3
- package/server/dist-Bzy0y69j.js +0 -3
- package/server/dod-C6n9RV3G.js +0 -66
- package/server/flywheel-merge-order-UU0tiUld.js +0 -245
- package/server/flywheel-merge-order-UUcsCDb6.js +0 -2
- package/server/forge-CNFnyqps.js +0 -270
- package/server/forge-DXnS_YFH.js +0 -2
- package/server/git-utils-D0_yhCgF.js +0 -3
- package/server/github-pr-lookup-D16NGsSO.js +0 -44
- package/server/hume-ccTf4wTX.js +0 -3
- package/server/inspect-agent-Brtz0URj.js +0 -2
- package/server/label-reconciler-B4iOAjlC.js +0 -113
- package/server/launcher-generator-BBWG_SiS.js +0 -473
- package/server/merge-ops-CR44oU9-.js +0 -2
- package/server/merge-set-bUqbm3yc.js +0 -3
- package/server/merge-sync-BJYeOODr.js +0 -3
- package/server/merge-verification-BkaOqPsw.js +0 -124
- package/server/pipeline-membership-mY3GprlK.js +0 -420
- package/server/project-repos-DXKQsc_m.js +0 -3
- package/server/projects-vjjcmiN8.js +0 -3
- package/server/public/assets/index-CWeMerGO.js +0 -804
- package/server/public/assets/index-CenuJ3Cw.css +0 -1
- package/server/public/assets/mermaid-GHXKKRXX-CpPWAFwE.js +0 -1
- package/server/rally-zhhtXuDP.js +0 -3
- package/server/record-CN0pj4_5.js +0 -3
- package/server/record-update-bLJxAXH4.js +0 -3
- package/server/resume-BFv1PGpA.js +0 -3
- package/server/review-status-btEYlaxA.js +0 -3
- package/server/review-status-kZj_KyCk.js +0 -1470
- package/server/settings-66A6uvIA.js +0 -2
- package/server/spawn-planning-session-qUC2VWu3.js +0 -2
- package/server/specs-C8Wsn2XO.js +0 -3
- package/server/src-DtcUOZCq.js +0 -4826
- package/server/supervisor-FTJAIsAK.js +0 -2
- package/server/token-util-DuPuNriz.js +0 -3
- package/server/tracker-utils-B4ylXZQK.js +0 -3
- package/server/uat-train-B05BWkp-.js +0 -2
- package/server/uat-train-DDSrSoV1.js +0 -2152
- package/server/workflows-BQu4RFQ-.js +0 -2
- package/server/workspace-manager-CWT55qeI.js +0 -3
|
@@ -1,2152 +0,0 @@
|
|
|
1
|
-
import { Tn as init_projects, d as loadConfigNoMigration, fn as findProjectByPathSync, t as init_config_yaml, tt as resolveModel } from "./config-yaml-DySPNmKN.js";
|
|
2
|
-
import { n as getDashboardIdentity, t as recordDeaconNudge } from "./deacon-nudge-log-6wVqAaqz.js";
|
|
3
|
-
import { D as killSession, E as isPaneDead, H as sessionExists, w as init_tmux } from "./tmux-R7N7y-Mh.js";
|
|
4
|
-
import { X as messageAgent, b as init_agents } from "./queries-CjNIf-Ub.js";
|
|
5
|
-
import { S as setUatGenerationStackStartedAtSync, a as getUatGenerationSync, h as listUatGenerationsWithStacksSync, l as isMergeTrainEnabled, m as listUatGenerationsSync, o as init_merge_sync, p as listUatGenerationNamesSync, s as insertUatGenerationSync, w as updateUatGenerationSync } from "./merge-sync-VPV28g0g.js";
|
|
6
|
-
import { A as init_xbrief_index, M as readXBriefDocument, k as findXBriefByIssue } from "./io-C9kF1wtJ.js";
|
|
7
|
-
import { S as getConversationByName, _t as getDiscoveredStats, at as aggregateDiscoveredSessionCost, ct as countDiscoveredSessions, ft as findDiscoveredSessions, k as init_conversations, mt as getDiscoveredSessionById, ot as aggregateDiscoveredSessionCostBy, yt as init_discovered_sessions } from "./conversations-6AfUfbwc.js";
|
|
8
|
-
import { f as isFlywheelGloballyPaused, i as getFlywheelActiveRunId, n as FLYWHEEL_GLOBAL_PAUSE_KEY, o as getSetting, s as init_control_settings, t as FLYWHEEL_ACTIVE_RUN_ID_KEY, w as setSetting } from "./control-settings-BQp7_4uD.js";
|
|
9
|
-
import { n as resolveHarness, t as init_harness_resolve } from "./harness-resolve-3lNZkyT4.js";
|
|
10
|
-
import { a as enrichSessions, c as searchSessions, i as CostThresholdError, n as getSessionsFeedFacets, o as embedSessions, r as listSessionsFeed, t as scan } from "./scanner-CnqEZeDy.js";
|
|
11
|
-
import { t as listSubstrateBugWeights } from "./substrate-bug-weights-service-B-FwCs5V.js";
|
|
12
|
-
import { D as writeFlywheelLaunchMetadata, O as writeLatestFlywheelStatus, S as nextFlywheelRunId, T as resolveLiveFlywheelRunId, a as isFlywheelDevcontainerRuntime, f as spawnFlywheelAgent, g as abortFlywheelRun, s as loadResumeSessionId, t as FLYWHEEL_ORCHESTRATOR_AGENT_ID, u as saveResumeSessionId, v as getFlywheelRunDetail, w as readFlywheelLaunchMetadata, x as listFlywheelRuns } from "./flywheel-DHrgCphM.js";
|
|
13
|
-
import { t as extractACFromDocument } from "./acceptance-criteria-DDCnXF58.js";
|
|
14
|
-
import { n as ensureDevcontainerSync, r as init_ensure_devcontainer } from "./ensure-devcontainer-DNi-ono3.js";
|
|
15
|
-
import { isAbsolute, relative, resolve, sep } from "node:path";
|
|
16
|
-
import { Effect } from "effect";
|
|
17
|
-
import { existsSync } from "fs";
|
|
18
|
-
import { basename as basename$1, dirname as dirname$1, isAbsolute as isAbsolute$1, join as join$1, relative as relative$1, resolve as resolve$1 } from "path";
|
|
19
|
-
import { tmpdir } from "os";
|
|
20
|
-
import { freemem as freemem$1, totalmem as totalmem$1 } from "node:os";
|
|
21
|
-
import { readFile, realpath, stat } from "node:fs/promises";
|
|
22
|
-
import { exec, execFile, spawn } from "node:child_process";
|
|
23
|
-
import { promisify } from "node:util";
|
|
24
|
-
import { execFile as execFile$1 } from "child_process";
|
|
25
|
-
import { promisify as promisify$1 } from "util";
|
|
26
|
-
import { randomUUID } from "node:crypto";
|
|
27
|
-
import { layer } from "@effect/platform-node/NodeServices";
|
|
28
|
-
import { Worker } from "node:worker_threads";
|
|
29
|
-
//#region services/dashboard-db-task.ts
|
|
30
|
-
init_projects();
|
|
31
|
-
init_config_yaml();
|
|
32
|
-
init_tmux();
|
|
33
|
-
init_merge_sync();
|
|
34
|
-
init_xbrief_index();
|
|
35
|
-
init_harness_resolve();
|
|
36
|
-
init_agents();
|
|
37
|
-
init_discovered_sessions();
|
|
38
|
-
init_conversations();
|
|
39
|
-
init_control_settings();
|
|
40
|
-
const MAX_PENDING_JOBS = 32;
|
|
41
|
-
const SEMANTIC_SEARCH_TIMEOUT_MS = Number.parseInt(process.env["OVERDECK_SEMANTIC_SEARCH_TIMEOUT_MS"] ?? "15000", 10);
|
|
42
|
-
const COALESCED_OPERATIONS = new Set([
|
|
43
|
-
"scanConversations",
|
|
44
|
-
"enrichSessions",
|
|
45
|
-
"embedSessions",
|
|
46
|
-
"searchSessionsSemantic",
|
|
47
|
-
"listSubstrateBugWeights"
|
|
48
|
-
]);
|
|
49
|
-
const workers = {
|
|
50
|
-
read: null,
|
|
51
|
-
long: null,
|
|
52
|
-
semantic: null
|
|
53
|
-
};
|
|
54
|
-
const pending = /* @__PURE__ */ new Map();
|
|
55
|
-
const sharedJobs = /* @__PURE__ */ new Map();
|
|
56
|
-
let latestSemanticJobId = null;
|
|
57
|
-
function workerScriptUrl() {
|
|
58
|
-
return import.meta.url.endsWith(".ts") ? new URL("./dashboard-db-worker.ts", import.meta.url) : new URL("./dashboard-db-worker.js", import.meta.url);
|
|
59
|
-
}
|
|
60
|
-
function failPendingForLane(lane, err) {
|
|
61
|
-
for (const [id, job] of pending.entries()) {
|
|
62
|
-
if (job.lane !== lane) continue;
|
|
63
|
-
if (job.timeout) clearTimeout(job.timeout);
|
|
64
|
-
job.reject(err);
|
|
65
|
-
pending.delete(id);
|
|
66
|
-
}
|
|
67
|
-
for (const [key, job] of sharedJobs.entries()) if (job.lane === lane) sharedJobs.delete(key);
|
|
68
|
-
if (lane === "semantic") latestSemanticJobId = null;
|
|
69
|
-
}
|
|
70
|
-
function stableStringify(value) {
|
|
71
|
-
if (value == null || typeof value !== "object") return JSON.stringify(value);
|
|
72
|
-
if (Array.isArray(value)) return `[${value.map(stableStringify).join(",")}]`;
|
|
73
|
-
return `{${Object.entries(value).filter(([, v]) => typeof v !== "function" && v !== void 0).sort(([a], [b]) => a.localeCompare(b)).map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`).join(",")}}`;
|
|
74
|
-
}
|
|
75
|
-
function coalescingKey(operation, payload) {
|
|
76
|
-
if (!COALESCED_OPERATIONS.has(operation)) return null;
|
|
77
|
-
return `${operation}:${stableStringify(payload)}`;
|
|
78
|
-
}
|
|
79
|
-
function workerLane(operation) {
|
|
80
|
-
if (operation === "searchSessionsSemantic") return "semantic";
|
|
81
|
-
return COALESCED_OPERATIONS.has(operation) ? "long" : "read";
|
|
82
|
-
}
|
|
83
|
-
function cancelOlderSemanticSearches() {
|
|
84
|
-
if (!latestSemanticJobId && !workers.semantic) return;
|
|
85
|
-
failPendingForLane("semantic", /* @__PURE__ */ new Error("Superseded by a newer semantic search"));
|
|
86
|
-
workers.semantic?.terminate();
|
|
87
|
-
workers.semantic = null;
|
|
88
|
-
}
|
|
89
|
-
function aggregateDiscoveredSessionCostByPayload(payload) {
|
|
90
|
-
if (typeof payload === "string") return aggregateDiscoveredSessionCostBy(payload);
|
|
91
|
-
const input = payload;
|
|
92
|
-
return aggregateDiscoveredSessionCostBy(input?.groupBy ?? "workspace", input?.filter ?? {});
|
|
93
|
-
}
|
|
94
|
-
function getWorker(lane) {
|
|
95
|
-
const existing = workers[lane];
|
|
96
|
-
if (existing) return existing;
|
|
97
|
-
const worker = new Worker(workerScriptUrl(), { execArgv: process.execArgv.filter((arg) => !arg.startsWith("--inspect")) });
|
|
98
|
-
workers[lane] = worker;
|
|
99
|
-
worker.on("message", (message) => {
|
|
100
|
-
const job = pending.get(message.id);
|
|
101
|
-
if (!job) return;
|
|
102
|
-
if (message.progress !== void 0) {
|
|
103
|
-
job.progressChain = job.progressChain.then(async () => {
|
|
104
|
-
for (const listener of job.progressListeners) await listener(message.progress);
|
|
105
|
-
});
|
|
106
|
-
return;
|
|
107
|
-
}
|
|
108
|
-
pending.delete(message.id);
|
|
109
|
-
if (job.timeout) clearTimeout(job.timeout);
|
|
110
|
-
if (job.lane === "semantic" && latestSemanticJobId === message.id) latestSemanticJobId = null;
|
|
111
|
-
if (message.ok) {
|
|
112
|
-
job.progressChain.then(() => job.resolve(message.result), job.reject);
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const err = message.error?.name === "CostThresholdError" ? new CostThresholdError(message.error.estimatedCost ?? 0, message.error.threshold ?? 0, message.error.sessionCount ?? 0) : new Error(message.error?.message ?? "Dashboard database worker failed");
|
|
116
|
-
err.name = message.error?.name ?? "DashboardDatabaseWorkerError";
|
|
117
|
-
err.stack = message.error?.stack;
|
|
118
|
-
job.progressChain.then(() => job.reject(err), job.reject);
|
|
119
|
-
});
|
|
120
|
-
worker.on("error", (err) => {
|
|
121
|
-
failPendingForLane(lane, err);
|
|
122
|
-
if (workers[lane] === worker) workers[lane] = null;
|
|
123
|
-
});
|
|
124
|
-
worker.on("exit", (code) => {
|
|
125
|
-
if (code !== 0) failPendingForLane(lane, /* @__PURE__ */ new Error(`Dashboard database ${lane} worker exited with code ${code}`));
|
|
126
|
-
if (workers[lane] === worker) workers[lane] = null;
|
|
127
|
-
});
|
|
128
|
-
return worker;
|
|
129
|
-
}
|
|
130
|
-
async function runInline(operation, payload, onProgress) {
|
|
131
|
-
switch (operation) {
|
|
132
|
-
case "getDiscoveredStats": return getDiscoveredStats();
|
|
133
|
-
case "listDiscoveredSessions": {
|
|
134
|
-
const filter = payload;
|
|
135
|
-
return {
|
|
136
|
-
sessions: findDiscoveredSessions(filter),
|
|
137
|
-
total: countDiscoveredSessions({
|
|
138
|
-
...filter,
|
|
139
|
-
limit: void 0,
|
|
140
|
-
offset: void 0
|
|
141
|
-
})
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
case "listSessionsFeed": return listSessionsFeed(payload);
|
|
145
|
-
case "getSessionsFeedFacets": return getSessionsFeedFacets(payload);
|
|
146
|
-
case "getDiscoveredSessionById": return getDiscoveredSessionById(payload);
|
|
147
|
-
case "aggregateDiscoveredSessionCost": return aggregateDiscoveredSessionCost(payload);
|
|
148
|
-
case "aggregateDiscoveredSessionCostBy": return aggregateDiscoveredSessionCostByPayload(payload);
|
|
149
|
-
case "searchSessions":
|
|
150
|
-
case "searchSessionsSemantic": return searchSessions(payload);
|
|
151
|
-
case "scanConversations": return scan({
|
|
152
|
-
...payload,
|
|
153
|
-
onProgress
|
|
154
|
-
});
|
|
155
|
-
case "enrichSessions": return enrichSessions({
|
|
156
|
-
...payload,
|
|
157
|
-
onProgress
|
|
158
|
-
});
|
|
159
|
-
case "embedSessions": return embedSessions({
|
|
160
|
-
...payload,
|
|
161
|
-
autoInstall: true,
|
|
162
|
-
onProgress
|
|
163
|
-
});
|
|
164
|
-
case "getConversationByName": return getConversationByName(payload);
|
|
165
|
-
case "getSetting": return getSetting(payload);
|
|
166
|
-
case "setSetting": {
|
|
167
|
-
const input = payload;
|
|
168
|
-
setSetting(input.key, input.value);
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
case "listSubstrateBugWeights": {
|
|
172
|
-
const input = payload;
|
|
173
|
-
return listSubstrateBugWeights(input.window, {
|
|
174
|
-
limit: input.limit,
|
|
175
|
-
offset: input.offset
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
case "getArtifactBySlug": {
|
|
179
|
-
const { getArtifactBySlugJob } = await import("./artifact-index-jobs-CbEDfCDu.js");
|
|
180
|
-
return getArtifactBySlugJob(payload);
|
|
181
|
-
}
|
|
182
|
-
case "listArtifactsForWorkspaceOrIssue": {
|
|
183
|
-
const { listArtifactsForWorkspaceOrIssueJob } = await import("./artifact-index-jobs-CbEDfCDu.js");
|
|
184
|
-
return listArtifactsForWorkspaceOrIssueJob(payload);
|
|
185
|
-
}
|
|
186
|
-
case "unshareArtifactBySlug": {
|
|
187
|
-
const { unshareArtifactBySlugJob } = await import("./artifact-index-jobs-CbEDfCDu.js");
|
|
188
|
-
return unshareArtifactBySlugJob(payload);
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
function runDashboardDbJob(operation, payload, onProgress) {
|
|
193
|
-
if (import.meta.url.endsWith(".ts") && process.env["VITEST"]) return runInline(operation, payload, onProgress);
|
|
194
|
-
const key = coalescingKey(operation, payload);
|
|
195
|
-
const existing = key ? sharedJobs.get(key) : void 0;
|
|
196
|
-
if (existing) {
|
|
197
|
-
if (onProgress) existing.progressListeners.add(onProgress);
|
|
198
|
-
return existing.promise;
|
|
199
|
-
}
|
|
200
|
-
if (pending.size >= MAX_PENDING_JOBS) return Promise.reject(/* @__PURE__ */ new Error("Dashboard database worker queue is full"));
|
|
201
|
-
const id = randomUUID();
|
|
202
|
-
const lane = workerLane(operation);
|
|
203
|
-
if (operation === "searchSessionsSemantic") {
|
|
204
|
-
cancelOlderSemanticSearches();
|
|
205
|
-
latestSemanticJobId = id;
|
|
206
|
-
}
|
|
207
|
-
const progressListeners = /* @__PURE__ */ new Set();
|
|
208
|
-
if (onProgress) progressListeners.add(onProgress);
|
|
209
|
-
const promise = new Promise((resolve, reject) => {
|
|
210
|
-
const timeout = operation === "searchSessionsSemantic" ? setTimeout(() => {
|
|
211
|
-
if (!pending.get(id)) return;
|
|
212
|
-
pending.delete(id);
|
|
213
|
-
for (const [sharedKey, sharedJob] of sharedJobs.entries()) if (sharedJob.promise === promise) sharedJobs.delete(sharedKey);
|
|
214
|
-
if (latestSemanticJobId === id) latestSemanticJobId = null;
|
|
215
|
-
reject(/* @__PURE__ */ new Error("Semantic search timed out"));
|
|
216
|
-
workers.semantic?.terminate();
|
|
217
|
-
workers.semantic = null;
|
|
218
|
-
}, Number.isFinite(SEMANTIC_SEARCH_TIMEOUT_MS) ? SEMANTIC_SEARCH_TIMEOUT_MS : 15e3) : null;
|
|
219
|
-
pending.set(id, {
|
|
220
|
-
lane,
|
|
221
|
-
operation,
|
|
222
|
-
resolve,
|
|
223
|
-
reject,
|
|
224
|
-
progressListeners,
|
|
225
|
-
progressChain: Promise.resolve(),
|
|
226
|
-
timeout
|
|
227
|
-
});
|
|
228
|
-
getWorker(lane).postMessage({
|
|
229
|
-
id,
|
|
230
|
-
operation,
|
|
231
|
-
payload
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
if (key) {
|
|
235
|
-
sharedJobs.set(key, {
|
|
236
|
-
lane,
|
|
237
|
-
promise,
|
|
238
|
-
progressListeners
|
|
239
|
-
});
|
|
240
|
-
promise.then(() => sharedJobs.delete(key), () => sharedJobs.delete(key));
|
|
241
|
-
}
|
|
242
|
-
return promise;
|
|
243
|
-
}
|
|
244
|
-
//#endregion
|
|
245
|
-
//#region services/flywheel-actions.ts
|
|
246
|
-
init_ensure_devcontainer();
|
|
247
|
-
init_control_settings();
|
|
248
|
-
const DEFAULT_BRIEF_PATH = "docs/flywheel-brief.md";
|
|
249
|
-
const execAsync = promisify(exec);
|
|
250
|
-
function mb(bytes) {
|
|
251
|
-
return Math.round(bytes / 1024 / 1024);
|
|
252
|
-
}
|
|
253
|
-
function isInsideRoot(projectRoot, candidate) {
|
|
254
|
-
const relativePath = relative(projectRoot, candidate);
|
|
255
|
-
return relativePath === "" || !relativePath.startsWith("..") && !isAbsolute(relativePath);
|
|
256
|
-
}
|
|
257
|
-
function resolveFlywheelStartBriefPath(cwd, requestedPath) {
|
|
258
|
-
const rawPath = requestedPath?.trim() || DEFAULT_BRIEF_PATH;
|
|
259
|
-
if (rawPath.includes("\0")) throw new Error("Brief path is invalid");
|
|
260
|
-
const root = resolve(cwd);
|
|
261
|
-
const absolutePath = isAbsolute(rawPath) ? resolve(rawPath) : resolve(root, rawPath);
|
|
262
|
-
if (!isInsideRoot(root, absolutePath)) throw new Error("Brief path must stay inside the project root");
|
|
263
|
-
const normalizedRoot = root.endsWith(sep) ? root : `${root}${sep}`;
|
|
264
|
-
const displayPath = absolutePath === root ? "." : relative(root, absolutePath);
|
|
265
|
-
return {
|
|
266
|
-
absolutePath,
|
|
267
|
-
displayPath: absolutePath.startsWith(normalizedRoot) ? displayPath : absolutePath
|
|
268
|
-
};
|
|
269
|
-
}
|
|
270
|
-
async function assertExistingPathInsideRoot(projectRoot, candidate) {
|
|
271
|
-
const [realRoot, realCandidate] = await Promise.all([realpath(projectRoot), realpath(candidate)]);
|
|
272
|
-
if (!isInsideRoot(realRoot, realCandidate)) throw new Error("Brief path must stay inside the project root");
|
|
273
|
-
}
|
|
274
|
-
async function requireFlywheelBrief(cwd, requestedPath) {
|
|
275
|
-
const resolved = resolveFlywheelStartBriefPath(cwd, requestedPath);
|
|
276
|
-
try {
|
|
277
|
-
await assertExistingPathInsideRoot(cwd, resolved.absolutePath);
|
|
278
|
-
await readFile(resolved.absolutePath, "utf8");
|
|
279
|
-
return resolved;
|
|
280
|
-
} catch (error) {
|
|
281
|
-
if ((typeof error === "object" && error !== null && "code" in error ? error.code : void 0) === "ENOENT") throw new Error(`Flywheel brief not found: ${resolved.displayPath}`);
|
|
282
|
-
throw error;
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
async function dashboardSetting(key) {
|
|
286
|
-
return runDashboardDbJob("getSetting", key);
|
|
287
|
-
}
|
|
288
|
-
async function setDashboardSetting(key, value) {
|
|
289
|
-
await runDashboardDbJob("setSetting", {
|
|
290
|
-
key,
|
|
291
|
-
value
|
|
292
|
-
});
|
|
293
|
-
}
|
|
294
|
-
async function getActiveRunId() {
|
|
295
|
-
const value = await dashboardSetting(FLYWHEEL_ACTIVE_RUN_ID_KEY);
|
|
296
|
-
return value && value.trim() ? value : null;
|
|
297
|
-
}
|
|
298
|
-
async function setActiveRunId(runId) {
|
|
299
|
-
await setDashboardSetting(FLYWHEEL_ACTIVE_RUN_ID_KEY, runId ?? "");
|
|
300
|
-
}
|
|
301
|
-
async function isPaused() {
|
|
302
|
-
return await dashboardSetting(FLYWHEEL_GLOBAL_PAUSE_KEY) === "true";
|
|
303
|
-
}
|
|
304
|
-
async function setPaused(paused) {
|
|
305
|
-
await setDashboardSetting(FLYWHEEL_GLOBAL_PAUSE_KEY, paused ? "true" : "false");
|
|
306
|
-
}
|
|
307
|
-
async function readGateSnapshot() {
|
|
308
|
-
const [paused, activeRunId] = await Promise.all([isPaused(), getActiveRunId()]);
|
|
309
|
-
return {
|
|
310
|
-
paused,
|
|
311
|
-
activeRunId
|
|
312
|
-
};
|
|
313
|
-
}
|
|
314
|
-
async function resolveFlywheelRoleConfig() {
|
|
315
|
-
const { config } = await Effect.runPromise(loadConfigNoMigration());
|
|
316
|
-
const flywheel = config.roles?.flywheel;
|
|
317
|
-
const model = resolveModel("flywheel", void 0, config);
|
|
318
|
-
return {
|
|
319
|
-
harness: await resolveHarness({ model }),
|
|
320
|
-
model,
|
|
321
|
-
effort: flywheel?.effort ?? "high",
|
|
322
|
-
minAgents: flywheel?.minAgents ?? 20,
|
|
323
|
-
maxAgents: flywheel?.maxAgents ?? 30,
|
|
324
|
-
scope: flywheel?.scope ?? "pan-only"
|
|
325
|
-
};
|
|
326
|
-
}
|
|
327
|
-
async function gitOutput(command, cwd) {
|
|
328
|
-
const { stdout } = await execAsync(command, {
|
|
329
|
-
cwd,
|
|
330
|
-
encoding: "utf8"
|
|
331
|
-
});
|
|
332
|
-
return stdout.trim();
|
|
333
|
-
}
|
|
334
|
-
async function createInitialFlywheelStatus(runId, startedAt, cwd, agentModel, agentHarness, roleConfig) {
|
|
335
|
-
const ramTotalMb = mb(totalmem$1());
|
|
336
|
-
return {
|
|
337
|
-
runId,
|
|
338
|
-
startedAt,
|
|
339
|
-
elapsedMs: 0,
|
|
340
|
-
orchestrator: {
|
|
341
|
-
harness: agentHarness ?? roleConfig.harness,
|
|
342
|
-
model: agentModel ?? roleConfig.model,
|
|
343
|
-
effort: roleConfig.effort,
|
|
344
|
-
ctxPercent: 0
|
|
345
|
-
},
|
|
346
|
-
headline: {
|
|
347
|
-
bugsFixed: 0,
|
|
348
|
-
swarmItemsMerged: 0,
|
|
349
|
-
swarmItemsTotal: 0,
|
|
350
|
-
prsMerged: 0,
|
|
351
|
-
awaitingUat: 0
|
|
352
|
-
},
|
|
353
|
-
activePipeline: [],
|
|
354
|
-
substrateBugs: [],
|
|
355
|
-
agents: [{
|
|
356
|
-
id: FLYWHEEL_ORCHESTRATOR_AGENT_ID,
|
|
357
|
-
label: "flywheel-orchestrator",
|
|
358
|
-
status: "running",
|
|
359
|
-
role: "flywheel",
|
|
360
|
-
model: agentModel
|
|
361
|
-
}],
|
|
362
|
-
parked: [],
|
|
363
|
-
suggestions: [],
|
|
364
|
-
system: {
|
|
365
|
-
mainHead: await gitOutput("git rev-parse --short HEAD", cwd).catch(() => "unknown"),
|
|
366
|
-
ramUsedMb: Math.max(0, ramTotalMb - mb(freemem$1())),
|
|
367
|
-
ramTotalMb,
|
|
368
|
-
swapUsedMb: 0,
|
|
369
|
-
swapTotalMb: 0,
|
|
370
|
-
agentsActive: 1,
|
|
371
|
-
agentsCap: roleConfig.maxAgents
|
|
372
|
-
},
|
|
373
|
-
openQuestions: [],
|
|
374
|
-
ticks: 0,
|
|
375
|
-
lastTickAt: startedAt
|
|
376
|
-
};
|
|
377
|
-
}
|
|
378
|
-
async function startFlywheelRunForDashboard(options = {}) {
|
|
379
|
-
const cwd = options.cwd ?? process.cwd();
|
|
380
|
-
if (isFlywheelDevcontainerRuntime()) throw new Error("Refusing to spawn flywheel-orchestrator inside a workspace devcontainer");
|
|
381
|
-
const activeRunId = await resolveLiveFlywheelRunId();
|
|
382
|
-
if (activeRunId) if (!await Effect.runPromise(sessionExists("flywheel-orchestrator")) || await Effect.runPromise(isPaneDead("flywheel-orchestrator"))) {
|
|
383
|
-
console.log(`[flywheel] ${activeRunId} gate stuck — orchestrator pane is dead; auto-aborting`);
|
|
384
|
-
await Effect.runPromise(killSession(FLYWHEEL_ORCHESTRATOR_AGENT_ID)).catch(() => {});
|
|
385
|
-
await abortFlywheelRun(activeRunId);
|
|
386
|
-
} else throw new Error(`Flywheel run ${activeRunId} is already active; pause, resume, or report it before starting another run`);
|
|
387
|
-
const brief = await requireFlywheelBrief(cwd, options.brief ?? DEFAULT_BRIEF_PATH);
|
|
388
|
-
const runId = await nextFlywheelRunId();
|
|
389
|
-
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
390
|
-
await writeFlywheelLaunchMetadata({
|
|
391
|
-
version: 1,
|
|
392
|
-
runId,
|
|
393
|
-
workspace: cwd,
|
|
394
|
-
briefPath: brief.absolutePath,
|
|
395
|
-
briefDisplayPath: brief.displayPath
|
|
396
|
-
});
|
|
397
|
-
const roleConfig = await resolveFlywheelRoleConfig();
|
|
398
|
-
const agent = await spawnFlywheelAgent(runId, {
|
|
399
|
-
briefPath: brief.absolutePath,
|
|
400
|
-
workspace: cwd,
|
|
401
|
-
model: roleConfig.model,
|
|
402
|
-
harness: roleConfig.harness,
|
|
403
|
-
effort: roleConfig.effort,
|
|
404
|
-
minAgents: roleConfig.minAgents,
|
|
405
|
-
maxAgents: roleConfig.maxAgents,
|
|
406
|
-
scope: roleConfig.scope
|
|
407
|
-
});
|
|
408
|
-
await setActiveRunId(runId);
|
|
409
|
-
await setPaused(false);
|
|
410
|
-
await writeLatestFlywheelStatus(await createInitialFlywheelStatus(runId, startedAt, cwd, agent.model, agent.harness, roleConfig));
|
|
411
|
-
return {
|
|
412
|
-
runId,
|
|
413
|
-
briefDisplayPath: resolveFlywheelStartBriefPath(cwd, options.brief ?? DEFAULT_BRIEF_PATH).displayPath,
|
|
414
|
-
agentModel: agent.model
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
async function pauseFlywheelRunForDashboard() {
|
|
418
|
-
const before = await readGateSnapshot();
|
|
419
|
-
if (before.paused) return {
|
|
420
|
-
before,
|
|
421
|
-
after: before,
|
|
422
|
-
changed: false
|
|
423
|
-
};
|
|
424
|
-
if (before.activeRunId) try {
|
|
425
|
-
const { readFile } = await import("node:fs/promises");
|
|
426
|
-
const { join } = await import("node:path");
|
|
427
|
-
const { getAgentDir } = await import("./agents-wi9NWzs2.js");
|
|
428
|
-
const sessionId = (await readFile(join(getAgentDir(FLYWHEEL_ORCHESTRATOR_AGENT_ID), "session.id"), "utf-8")).trim();
|
|
429
|
-
if (sessionId) await saveResumeSessionId(before.activeRunId, sessionId);
|
|
430
|
-
} catch {}
|
|
431
|
-
await setPaused(true);
|
|
432
|
-
await import("./agents-wi9NWzs2.js").then(({ stopAgent }) => Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID)));
|
|
433
|
-
return {
|
|
434
|
-
before,
|
|
435
|
-
after: await readGateSnapshot(),
|
|
436
|
-
changed: true
|
|
437
|
-
};
|
|
438
|
-
}
|
|
439
|
-
async function abortFlywheelRunForDashboard() {
|
|
440
|
-
const candidate = await getActiveRunId();
|
|
441
|
-
if (!candidate) return { aborted: await resolveLiveFlywheelRunId() ?? null };
|
|
442
|
-
const { stopAgent } = await import("./agents-wi9NWzs2.js");
|
|
443
|
-
await Effect.runPromise(stopAgent(FLYWHEEL_ORCHESTRATOR_AGENT_ID));
|
|
444
|
-
await abortFlywheelRun(candidate);
|
|
445
|
-
return { aborted: candidate };
|
|
446
|
-
}
|
|
447
|
-
async function resumeFlywheelRunForDashboard() {
|
|
448
|
-
const before = await readGateSnapshot();
|
|
449
|
-
if (!before.paused && await Effect.runPromise(sessionExists("flywheel-orchestrator"))) return {
|
|
450
|
-
before,
|
|
451
|
-
after: before,
|
|
452
|
-
changed: false
|
|
453
|
-
};
|
|
454
|
-
if (!before.activeRunId) throw new Error("No active flywheel run to resume");
|
|
455
|
-
const launch = await readFlywheelLaunchMetadata(before.activeRunId);
|
|
456
|
-
if (!launch) throw new Error(`Flywheel run ${before.activeRunId} is missing launch metadata; cannot resume safely`);
|
|
457
|
-
const brief = await requireFlywheelBrief(launch.workspace, launch.briefPath);
|
|
458
|
-
const roleConfig = await resolveFlywheelRoleConfig();
|
|
459
|
-
const resumeSessionId = await loadResumeSessionId(before.activeRunId) ?? void 0;
|
|
460
|
-
await spawnFlywheelAgent(before.activeRunId, {
|
|
461
|
-
workspace: launch.workspace,
|
|
462
|
-
briefPath: brief.absolutePath,
|
|
463
|
-
model: roleConfig.model,
|
|
464
|
-
harness: roleConfig.harness,
|
|
465
|
-
effort: roleConfig.effort,
|
|
466
|
-
minAgents: roleConfig.minAgents,
|
|
467
|
-
maxAgents: roleConfig.maxAgents,
|
|
468
|
-
scope: roleConfig.scope,
|
|
469
|
-
resumeSessionId
|
|
470
|
-
});
|
|
471
|
-
await setPaused(false);
|
|
472
|
-
return {
|
|
473
|
-
before,
|
|
474
|
-
after: await readGateSnapshot(),
|
|
475
|
-
changed: true
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
function getPlatformOpenCommand() {
|
|
479
|
-
switch (process.platform) {
|
|
480
|
-
case "linux": return "xdg-open";
|
|
481
|
-
case "darwin": return "open";
|
|
482
|
-
case "win32": return "explorer";
|
|
483
|
-
default: throw new Error(`Opening files is not supported on ${process.platform}`);
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
function openPathDetached(path) {
|
|
487
|
-
spawn(getPlatformOpenCommand(), [path], {
|
|
488
|
-
detached: true,
|
|
489
|
-
stdio: "ignore"
|
|
490
|
-
}).unref();
|
|
491
|
-
}
|
|
492
|
-
async function resolveReportOpenRunId(runId) {
|
|
493
|
-
if (runId) return runId;
|
|
494
|
-
const runs = await listFlywheelRuns();
|
|
495
|
-
const run = runs.find((candidate) => candidate.status === "complete") ?? runs[0];
|
|
496
|
-
if (!run) throw new Error("no flywheel run report to open");
|
|
497
|
-
return run.id;
|
|
498
|
-
}
|
|
499
|
-
async function openFlywheelRunReportForDashboard(options = {}) {
|
|
500
|
-
const runId = await resolveReportOpenRunId(options.runId);
|
|
501
|
-
const detail = await getFlywheelRunDetail(runId);
|
|
502
|
-
if (!detail) throw new Error(`Flywheel run not found: ${runId}`);
|
|
503
|
-
if (!detail.paths.report) throw new Error(`No report exists for ${runId}`);
|
|
504
|
-
await (options.opener ?? openPathDetached)(detail.paths.report);
|
|
505
|
-
return {
|
|
506
|
-
runId,
|
|
507
|
-
path: detail.paths.report
|
|
508
|
-
};
|
|
509
|
-
}
|
|
510
|
-
async function readCurrentFlywheelStatusForDashboard() {
|
|
511
|
-
const activeRunId = await resolveLiveFlywheelRunId();
|
|
512
|
-
if (!activeRunId) return null;
|
|
513
|
-
const activeRun = await getFlywheelRunDetail(activeRunId);
|
|
514
|
-
return activeRun?.status === "running" ? activeRun.latest : null;
|
|
515
|
-
}
|
|
516
|
-
//#endregion
|
|
517
|
-
//#region ../../lib/cloister/uat-candidate-name.ts
|
|
518
|
-
/**
|
|
519
|
-
* UAT candidate branch naming (PAN-1691) — codename + short date.
|
|
520
|
-
*
|
|
521
|
-
* One candidate branch is assembled per label/day and force-reset on rebuild.
|
|
522
|
-
* Format: `uat/<label>-<codename>-<MMDD>`, e.g. `uat/pan-otter-0609`. Codenames
|
|
523
|
-
* are short, memorable words you can say out loud ("ship the otter candidate").
|
|
524
|
-
*
|
|
525
|
-
* The core is pure and fully injectable (wordlist / index pick / collision
|
|
526
|
-
* date) so it's deterministically testable. The default codename index is a
|
|
527
|
-
* stable hash of label + day so rebuilds for the same day reuse the same branch.
|
|
528
|
-
*/
|
|
529
|
-
const UAT_CODENAMES = [
|
|
530
|
-
"otter",
|
|
531
|
-
"falcon",
|
|
532
|
-
"cedar",
|
|
533
|
-
"quartz",
|
|
534
|
-
"ember",
|
|
535
|
-
"marlin",
|
|
536
|
-
"willow",
|
|
537
|
-
"cobalt",
|
|
538
|
-
"sable",
|
|
539
|
-
"heron",
|
|
540
|
-
"onyx",
|
|
541
|
-
"larch",
|
|
542
|
-
"perch",
|
|
543
|
-
"flint",
|
|
544
|
-
"dune",
|
|
545
|
-
"reef",
|
|
546
|
-
"vale",
|
|
547
|
-
"crow",
|
|
548
|
-
"moss",
|
|
549
|
-
"thorn",
|
|
550
|
-
"birch",
|
|
551
|
-
"koi",
|
|
552
|
-
"slate",
|
|
553
|
-
"wren"
|
|
554
|
-
];
|
|
555
|
-
function mmdd(dateIso) {
|
|
556
|
-
const [, month, day] = dateIso.slice(0, 10).split("-");
|
|
557
|
-
return `${month ?? "00"}${day ?? "00"}`;
|
|
558
|
-
}
|
|
559
|
-
/**
|
|
560
|
-
* Build the deterministic `uat/<label>-<codename>-<MMDD>` branch name. Pure.
|
|
561
|
-
*/
|
|
562
|
-
function makeUatCandidateName(deps) {
|
|
563
|
-
const day = mmdd(deps.dateIso);
|
|
564
|
-
const words = deps.codenames ?? UAT_CODENAMES;
|
|
565
|
-
const pick = deps.pick ?? ((n) => stableIndex(`${deps.label}-${day}`, n));
|
|
566
|
-
const word = words[words.length > 0 ? pick(words.length) % words.length : 0] ?? "candidate";
|
|
567
|
-
return `uat/${deps.label}-${word}-${day}`;
|
|
568
|
-
}
|
|
569
|
-
function stableIndex(seed, modulo) {
|
|
570
|
-
if (modulo <= 0) return 0;
|
|
571
|
-
let hash = 2166136261;
|
|
572
|
-
for (let i = 0; i < seed.length; i++) {
|
|
573
|
-
hash ^= seed.charCodeAt(i);
|
|
574
|
-
hash = Math.imul(hash, 16777619);
|
|
575
|
-
}
|
|
576
|
-
return (hash >>> 0) % modulo;
|
|
577
|
-
}
|
|
578
|
-
//#endregion
|
|
579
|
-
//#region ../../lib/cloister/uat-generation-engine.ts
|
|
580
|
-
/**
|
|
581
|
-
* UAT generation assembly engine (PAN-1737: UAT batch trains).
|
|
582
|
-
*
|
|
583
|
-
* Builds one *generation* — a deterministic-per-day `uat/<label>-<codename>-<MMDD>` branch
|
|
584
|
-
* off current main containing as many ready features as possible, merged in
|
|
585
|
-
* queue order, with cross-feature conflicts resolved on the branch by the
|
|
586
|
-
* injected `resolveConflict` hook (the assembly agent). A feature whose
|
|
587
|
-
* conflict cannot be resolved is *held out* with a human-readable reason and
|
|
588
|
-
* assembly continues — a single bad merge never blocks the batch.
|
|
589
|
-
*
|
|
590
|
-
* Each rebuild for the same label/day force-resets the same branch + persistent
|
|
591
|
-
* worktree under `<projectRoot>/workspaces/`, keeping one authoritative current
|
|
592
|
-
* UAT candidate instead of proliferating stale branches.
|
|
593
|
-
*
|
|
594
|
-
* Pure orchestrator: all git and store I/O is injected (see
|
|
595
|
-
* uat-generation-deps.ts for the real wiring), so every path — happy,
|
|
596
|
-
* conflict-resolved, held-out, failed — is unit-testable with fake deps.
|
|
597
|
-
*/
|
|
598
|
-
/** Branch name → worktree folder name (slashes collapse to dashes). */
|
|
599
|
-
function generationFolderName(branchName) {
|
|
600
|
-
return branchName.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
601
|
-
}
|
|
602
|
-
/**
|
|
603
|
-
* Assemble the next generation from the given ready set. Always returns the
|
|
604
|
-
* generation row (status 'ready' or 'failed'); throws only on store failures.
|
|
605
|
-
* On success, older 'ready' generations for the project flip to 'superseded'.
|
|
606
|
-
*/
|
|
607
|
-
async function assembleUatGeneration(input, deps) {
|
|
608
|
-
const log = deps.log ?? (() => {});
|
|
609
|
-
const baseSha = await deps.git.fetchMain();
|
|
610
|
-
const name = makeUatCandidateName({
|
|
611
|
-
label: input.label,
|
|
612
|
-
dateIso: input.dateIso
|
|
613
|
-
});
|
|
614
|
-
const worktreePath = `${input.projectRoot}/workspaces/${generationFolderName(name)}`;
|
|
615
|
-
deps.store.insert({
|
|
616
|
-
name,
|
|
617
|
-
worktreePath,
|
|
618
|
-
projectRoot: input.projectRoot,
|
|
619
|
-
baseSha,
|
|
620
|
-
status: "assembling",
|
|
621
|
-
members: [],
|
|
622
|
-
heldOut: [],
|
|
623
|
-
resolutions: [],
|
|
624
|
-
stackStartedAt: null,
|
|
625
|
-
cleanedAt: null
|
|
626
|
-
});
|
|
627
|
-
const members = [];
|
|
628
|
-
const heldOut = [];
|
|
629
|
-
const resolutions = [];
|
|
630
|
-
const finish = (status) => {
|
|
631
|
-
deps.store.update(name, {
|
|
632
|
-
status,
|
|
633
|
-
members,
|
|
634
|
-
heldOut,
|
|
635
|
-
resolutions
|
|
636
|
-
});
|
|
637
|
-
return {
|
|
638
|
-
name,
|
|
639
|
-
worktreePath,
|
|
640
|
-
projectRoot: input.projectRoot,
|
|
641
|
-
baseSha,
|
|
642
|
-
status,
|
|
643
|
-
members,
|
|
644
|
-
heldOut,
|
|
645
|
-
resolutions,
|
|
646
|
-
stackStartedAt: null,
|
|
647
|
-
cleanedAt: null,
|
|
648
|
-
createdAt: "",
|
|
649
|
-
updatedAt: ""
|
|
650
|
-
};
|
|
651
|
-
};
|
|
652
|
-
try {
|
|
653
|
-
await deps.git.createWorktree(name, worktreePath);
|
|
654
|
-
} catch (err) {
|
|
655
|
-
log(`[uat-generation] ${name}: worktree creation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
656
|
-
return finish("failed");
|
|
657
|
-
}
|
|
658
|
-
for (const feature of input.features) {
|
|
659
|
-
const mergedIssueIds = members.map((m) => m.issueId);
|
|
660
|
-
const attemptedHeadSha = async () => deps.git.branchHeadSha(feature.branch).catch(() => "unknown");
|
|
661
|
-
const recordMember = async () => ({
|
|
662
|
-
issueId: feature.issueId,
|
|
663
|
-
title: feature.title,
|
|
664
|
-
branch: feature.branch,
|
|
665
|
-
headSha: await attemptedHeadSha(),
|
|
666
|
-
mergeOrder: members.length + 1,
|
|
667
|
-
...feature.pr !== void 0 ? { pr: feature.pr } : {},
|
|
668
|
-
...feature.prUrl !== void 0 ? { prUrl: feature.prUrl } : {}
|
|
669
|
-
});
|
|
670
|
-
const holdOut = async (reason) => {
|
|
671
|
-
heldOut.push({
|
|
672
|
-
issueId: feature.issueId,
|
|
673
|
-
branch: feature.branch,
|
|
674
|
-
headSha: await attemptedHeadSha(),
|
|
675
|
-
reason
|
|
676
|
-
});
|
|
677
|
-
};
|
|
678
|
-
let result;
|
|
679
|
-
try {
|
|
680
|
-
result = await deps.git.mergeBranch(feature.branch);
|
|
681
|
-
} catch (err) {
|
|
682
|
-
await deps.git.abortMerge().catch(() => {});
|
|
683
|
-
await holdOut(`merge failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
|
|
684
|
-
deps.store.update(name, {
|
|
685
|
-
members,
|
|
686
|
-
heldOut,
|
|
687
|
-
resolutions
|
|
688
|
-
});
|
|
689
|
-
continue;
|
|
690
|
-
}
|
|
691
|
-
if (result.ok) {
|
|
692
|
-
members.push(await recordMember());
|
|
693
|
-
deps.store.update(name, {
|
|
694
|
-
members,
|
|
695
|
-
heldOut,
|
|
696
|
-
resolutions
|
|
697
|
-
});
|
|
698
|
-
continue;
|
|
699
|
-
}
|
|
700
|
-
if (!result.conflict || !deps.resolveConflict) {
|
|
701
|
-
await deps.git.abortMerge().catch(() => {});
|
|
702
|
-
await holdOut(result.conflict ? `conflicts with ${conflictingWith(feature, mergedIssueIds).join(", ") || "an earlier member"} — no assembly agent available` : result.reason);
|
|
703
|
-
deps.store.update(name, {
|
|
704
|
-
members,
|
|
705
|
-
heldOut,
|
|
706
|
-
resolutions
|
|
707
|
-
});
|
|
708
|
-
continue;
|
|
709
|
-
}
|
|
710
|
-
const conflictingIssueIds = conflictingWith(feature, mergedIssueIds);
|
|
711
|
-
log(`[uat-generation] ${name}: resolving conflict ${feature.issueId} <-> ${conflictingIssueIds.join(", ") || "(unknown member)"}`);
|
|
712
|
-
let resolution = null;
|
|
713
|
-
try {
|
|
714
|
-
resolution = await deps.resolveConflict({
|
|
715
|
-
feature,
|
|
716
|
-
mergedIssueIds,
|
|
717
|
-
conflictingIssueIds,
|
|
718
|
-
branchName: name,
|
|
719
|
-
worktreePath
|
|
720
|
-
});
|
|
721
|
-
} catch (err) {
|
|
722
|
-
log(`[uat-generation] ${name}: conflict agent threw: ${err instanceof Error ? err.message : String(err)}`);
|
|
723
|
-
resolution = null;
|
|
724
|
-
}
|
|
725
|
-
if (resolution) {
|
|
726
|
-
members.push(await recordMember());
|
|
727
|
-
resolutions.push({
|
|
728
|
-
issueIds: [feature.issueId, ...conflictingIssueIds],
|
|
729
|
-
files: resolution.files,
|
|
730
|
-
commitSha: resolution.commitSha
|
|
731
|
-
});
|
|
732
|
-
} else {
|
|
733
|
-
await deps.git.abortMerge().catch(() => {});
|
|
734
|
-
await holdOut(`conflict with ${conflictingIssueIds.join(", ") || "an earlier member"} could not be auto-resolved — waits for the next generation`);
|
|
735
|
-
}
|
|
736
|
-
deps.store.update(name, {
|
|
737
|
-
members,
|
|
738
|
-
heldOut,
|
|
739
|
-
resolutions
|
|
740
|
-
});
|
|
741
|
-
}
|
|
742
|
-
if (members.length === 0) {
|
|
743
|
-
log(`[uat-generation] ${name}: nothing merged (${heldOut.length} held out) — marking failed`);
|
|
744
|
-
return finish("failed");
|
|
745
|
-
}
|
|
746
|
-
try {
|
|
747
|
-
await deps.git.push(name);
|
|
748
|
-
} catch (err) {
|
|
749
|
-
log(`[uat-generation] ${name}: push failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
750
|
-
return finish("failed");
|
|
751
|
-
}
|
|
752
|
-
for (const older of deps.store.listChain(input.projectRoot, ["ready"])) if (older.name !== name) deps.store.update(older.name, { status: "superseded" });
|
|
753
|
-
log(`[uat-generation] ${name}: ready — ${members.length} member(s), ${resolutions.length} resolution(s), ${heldOut.length} held out`);
|
|
754
|
-
return finish("ready");
|
|
755
|
-
}
|
|
756
|
-
function conflictingWith(feature, mergedIssueIds) {
|
|
757
|
-
const merged = new Set(mergedIssueIds.map((id) => id.toUpperCase()));
|
|
758
|
-
return (feature.conflictsWith ?? []).filter((id) => merged.has(id.toUpperCase()));
|
|
759
|
-
}
|
|
760
|
-
/**
|
|
761
|
-
* Reap dead generations' branches/worktrees, and trim the live chain to the
|
|
762
|
-
* newest `keep` (default 3). Trimmed superseded generations flip to
|
|
763
|
-
* 'invalidated' (their branch is gone — no longer testable). Rows are never
|
|
764
|
-
* deleted: the chain is the audit trail. Promoted generations keep their row
|
|
765
|
-
* but lose branch/worktree/stack — their content lives on main now.
|
|
766
|
-
*/
|
|
767
|
-
async function cleanupUatGenerations(projectRoot, deps, options = {}) {
|
|
768
|
-
const log = deps.log ?? (() => {});
|
|
769
|
-
const keep = options.keep ?? 3;
|
|
770
|
-
const trimmed = deps.store.listChain(projectRoot, ["ready", "superseded"]).slice(keep);
|
|
771
|
-
const dead = deps.store.listChain(projectRoot, [
|
|
772
|
-
"invalidated",
|
|
773
|
-
"promoted",
|
|
774
|
-
"failed"
|
|
775
|
-
]).filter((gen) => !gen.cleanedAt);
|
|
776
|
-
for (const gen of [...trimmed, ...dead]) {
|
|
777
|
-
let cleaned = true;
|
|
778
|
-
if (gen.stackStartedAt && deps.teardownStack) await deps.teardownStack(gen).catch((err) => {
|
|
779
|
-
cleaned = false;
|
|
780
|
-
log(`[uat-generation] cleanup ${gen.name}: stack teardown failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
781
|
-
});
|
|
782
|
-
await deps.removeWorktree(gen.worktreePath).catch((err) => {
|
|
783
|
-
cleaned = false;
|
|
784
|
-
log(`[uat-generation] cleanup ${gen.name}: worktree removal failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
785
|
-
});
|
|
786
|
-
await deps.deleteBranch(gen.name).catch((err) => {
|
|
787
|
-
cleaned = false;
|
|
788
|
-
log(`[uat-generation] cleanup ${gen.name}: branch deletion failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
789
|
-
});
|
|
790
|
-
if (!cleaned) continue;
|
|
791
|
-
const cleanedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
792
|
-
if (gen.status === "ready" || gen.status === "superseded") {
|
|
793
|
-
deps.store.update(gen.name, {
|
|
794
|
-
status: "invalidated",
|
|
795
|
-
cleanedAt
|
|
796
|
-
});
|
|
797
|
-
log(`[uat-generation] cleanup: trimmed ${gen.name} (beyond newest ${keep})`);
|
|
798
|
-
} else deps.store.update(gen.name, { cleanedAt });
|
|
799
|
-
}
|
|
800
|
-
}
|
|
801
|
-
//#endregion
|
|
802
|
-
//#region ../../lib/cloister/uat-generation-deps.ts
|
|
803
|
-
/**
|
|
804
|
-
* Real git + store wiring for the UAT generation engine (PAN-1737).
|
|
805
|
-
*
|
|
806
|
-
* Generation worktrees are PERSISTENT under `<projectRoot>/workspaces/` — the
|
|
807
|
-
* live UAT stack serves from them, and the deterministic daily folder name yields the Traefik
|
|
808
|
-
* host (`uat-<label>-<codename>-<mmdd>.overdeck.localhost`) via the standard
|
|
809
|
-
* FEATURE_FOLDER devcontainer template.
|
|
810
|
-
*
|
|
811
|
-
* Pure I/O — exercised live; the orchestrator (uat-generation-engine.ts) holds
|
|
812
|
-
* the tested logic.
|
|
813
|
-
*/
|
|
814
|
-
const execFileAsync$4 = promisify$1(execFile$1);
|
|
815
|
-
const runGit = (args, cwd) => execFileAsync$4("git", args, {
|
|
816
|
-
cwd,
|
|
817
|
-
maxBuffer: 16 * 1024 * 1024
|
|
818
|
-
});
|
|
819
|
-
function safeBranchName(branchName, prefix) {
|
|
820
|
-
if (!(prefix === "feature" ? /^feature\/[A-Za-z0-9][A-Za-z0-9._-]*$/ : /^uat\/[A-Za-z0-9][A-Za-z0-9._-]*$/).test(branchName)) throw new Error(`unsafe ${prefix} branch name: ${branchName}`);
|
|
821
|
-
return branchName;
|
|
822
|
-
}
|
|
823
|
-
function safeGenerationWorktreePath(projectRoot, worktreePath) {
|
|
824
|
-
const workspacesRoot = resolve$1(projectRoot, "workspaces");
|
|
825
|
-
const target = resolve$1(worktreePath);
|
|
826
|
-
const rel = relative$1(workspacesRoot, target);
|
|
827
|
-
if (rel === "" || rel.startsWith("..") || isAbsolute$1(rel)) throw new Error(`unsafe UAT worktree path outside project workspaces: ${worktreePath}`);
|
|
828
|
-
return target;
|
|
829
|
-
}
|
|
830
|
-
/** Store port backed by uat-generations-db. */
|
|
831
|
-
function buildUatGenerationStore() {
|
|
832
|
-
return {
|
|
833
|
-
insert: (gen) => {
|
|
834
|
-
insertUatGenerationSync(gen);
|
|
835
|
-
},
|
|
836
|
-
update: (name, patch) => {
|
|
837
|
-
updateUatGenerationSync(name, patch);
|
|
838
|
-
},
|
|
839
|
-
listNames: () => listUatGenerationNamesSync(),
|
|
840
|
-
listChain: (projectRoot, statuses) => listUatGenerationsSync({
|
|
841
|
-
projectRoot,
|
|
842
|
-
...statuses ? { statuses } : {}
|
|
843
|
-
})
|
|
844
|
-
};
|
|
845
|
-
}
|
|
846
|
-
/**
|
|
847
|
-
* Git deps for one assembly run. The worktree path is bound on createWorktree
|
|
848
|
-
* and reused by the merge/push operations that follow.
|
|
849
|
-
*/
|
|
850
|
-
function buildUatGenerationGitDeps(projectRoot) {
|
|
851
|
-
let worktreePath = "";
|
|
852
|
-
return {
|
|
853
|
-
fetchMain: async () => {
|
|
854
|
-
await runGit([
|
|
855
|
-
"fetch",
|
|
856
|
-
"origin",
|
|
857
|
-
"main"
|
|
858
|
-
], projectRoot);
|
|
859
|
-
const { stdout } = await runGit(["rev-parse", "origin/main"], projectRoot);
|
|
860
|
-
return stdout.trim();
|
|
861
|
-
},
|
|
862
|
-
createWorktree: async (branchName, path) => {
|
|
863
|
-
const safeBranch = safeBranchName(branchName, "uat");
|
|
864
|
-
worktreePath = safeGenerationWorktreePath(projectRoot, path);
|
|
865
|
-
await runGit([
|
|
866
|
-
"worktree",
|
|
867
|
-
"remove",
|
|
868
|
-
"--force",
|
|
869
|
-
worktreePath
|
|
870
|
-
], projectRoot).catch(() => {});
|
|
871
|
-
await runGit(["worktree", "prune"], projectRoot).catch(() => {});
|
|
872
|
-
await runGit([
|
|
873
|
-
"worktree",
|
|
874
|
-
"add",
|
|
875
|
-
"-B",
|
|
876
|
-
safeBranch,
|
|
877
|
-
worktreePath,
|
|
878
|
-
"origin/main"
|
|
879
|
-
], projectRoot);
|
|
880
|
-
},
|
|
881
|
-
branchHeadSha: async (branch) => {
|
|
882
|
-
const safeBranch = safeBranchName(branch, "feature");
|
|
883
|
-
const tryRef = async (ref) => (await runGit(["rev-parse", ref], projectRoot)).stdout.trim();
|
|
884
|
-
return tryRef(`origin/${safeBranch}`).catch(() => tryRef(safeBranch));
|
|
885
|
-
},
|
|
886
|
-
mergeBranch: async (featureBranch) => {
|
|
887
|
-
const safeFeatureBranch = safeBranchName(featureBranch, "feature");
|
|
888
|
-
const originRef = `origin/${safeFeatureBranch}`;
|
|
889
|
-
const ref = await runGit([
|
|
890
|
-
"rev-parse",
|
|
891
|
-
"--verify",
|
|
892
|
-
originRef
|
|
893
|
-
], worktreePath).then(() => originRef).catch(() => safeFeatureBranch);
|
|
894
|
-
try {
|
|
895
|
-
await runGit([
|
|
896
|
-
"merge",
|
|
897
|
-
"--no-edit",
|
|
898
|
-
ref
|
|
899
|
-
], worktreePath);
|
|
900
|
-
return { ok: true };
|
|
901
|
-
} catch (err) {
|
|
902
|
-
const { stdout } = await runGit(["ls-files", "-u"], worktreePath).catch(() => ({ stdout: "" }));
|
|
903
|
-
return {
|
|
904
|
-
ok: false,
|
|
905
|
-
conflict: stdout.trim().length > 0,
|
|
906
|
-
reason: err instanceof Error ? err.message.split("\n")[0] ?? "merge failed" : String(err)
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
},
|
|
910
|
-
abortMerge: async () => {
|
|
911
|
-
await runGit(["merge", "--abort"], worktreePath).catch(() => {});
|
|
912
|
-
},
|
|
913
|
-
push: async (branchName) => {
|
|
914
|
-
await runGit([
|
|
915
|
-
"push",
|
|
916
|
-
"-u",
|
|
917
|
-
"--force-with-lease",
|
|
918
|
-
"origin",
|
|
919
|
-
safeBranchName(branchName, "uat")
|
|
920
|
-
], worktreePath);
|
|
921
|
-
}
|
|
922
|
-
};
|
|
923
|
-
}
|
|
924
|
-
/** Branch names currently present on origin under uat/* — naming collision input. */
|
|
925
|
-
async function listRemoteUatBranches(projectRoot) {
|
|
926
|
-
const { stdout } = await runGit([
|
|
927
|
-
"ls-remote",
|
|
928
|
-
"--heads",
|
|
929
|
-
"origin",
|
|
930
|
-
"uat/*"
|
|
931
|
-
], projectRoot).catch(() => ({ stdout: "" }));
|
|
932
|
-
return stdout.split("\n").map((line) => line.split(" ")[1] ?? "").filter((ref) => ref.startsWith("refs/heads/")).map((ref) => ref.slice(11));
|
|
933
|
-
}
|
|
934
|
-
/** Cleanup deps: remove a generation worktree and delete its branch everywhere. */
|
|
935
|
-
function buildUatGenerationCleanupGit(projectRoot) {
|
|
936
|
-
return {
|
|
937
|
-
removeWorktree: async (path) => {
|
|
938
|
-
await runGit([
|
|
939
|
-
"worktree",
|
|
940
|
-
"remove",
|
|
941
|
-
"--force",
|
|
942
|
-
safeGenerationWorktreePath(projectRoot, path)
|
|
943
|
-
], projectRoot).catch(() => {});
|
|
944
|
-
await runGit(["worktree", "prune"], projectRoot).catch(() => {});
|
|
945
|
-
},
|
|
946
|
-
deleteBranch: async (branchName) => {
|
|
947
|
-
const safeBranch = safeBranchName(branchName, "uat");
|
|
948
|
-
await runGit([
|
|
949
|
-
"branch",
|
|
950
|
-
"-D",
|
|
951
|
-
safeBranch
|
|
952
|
-
], projectRoot).catch(() => {});
|
|
953
|
-
await runGit([
|
|
954
|
-
"push",
|
|
955
|
-
"origin",
|
|
956
|
-
"--delete",
|
|
957
|
-
safeBranch
|
|
958
|
-
], projectRoot).catch(() => {});
|
|
959
|
-
}
|
|
960
|
-
};
|
|
961
|
-
}
|
|
962
|
-
//#endregion
|
|
963
|
-
//#region ../../lib/cloister/uat-conflict-agent.ts
|
|
964
|
-
/**
|
|
965
|
-
* Assembly-agent conflict resolution (PAN-1737: UAT batch trains).
|
|
966
|
-
*
|
|
967
|
-
* Implements the engine's `resolveConflict` hook: when merging a feature onto
|
|
968
|
-
* a UAT generation branch conflicts, a TIMEBOXED headless agent run is given
|
|
969
|
-
* the mid-conflict worktree with one mission — resolve THIS merge's conflict
|
|
970
|
-
* markers, changing nothing beyond the resolution.
|
|
971
|
-
*
|
|
972
|
-
* Division of labor, deliberately strict:
|
|
973
|
-
* - The agent only EDITS FILES (headless `claude -p` with acceptEdits; no
|
|
974
|
-
* shell access). It cannot stage, commit, push, or wander.
|
|
975
|
-
* - This module verifies the result in code (no leftover conflict markers,
|
|
976
|
-
* no unmerged index entries), stages, and concludes the merge commit.
|
|
977
|
-
* - Any failure — agent missing, timeout, markers left, commit rejected —
|
|
978
|
-
* returns null; the engine then aborts the merge and holds the feature
|
|
979
|
-
* out. The assembly NEVER wedges on a conflict.
|
|
980
|
-
*
|
|
981
|
-
* The merge commit subject keeps git's standard `Merge branch ...` form (it
|
|
982
|
-
* survives commitlint's default-ignores when this history is later promoted
|
|
983
|
-
* to main); the auditable `uat-assembly:` marker lives in the body.
|
|
984
|
-
*/
|
|
985
|
-
const execFileAsync$3 = promisify$1(execFile$1);
|
|
986
|
-
function splitNul(stdout) {
|
|
987
|
-
return stdout.split("\0").filter((p) => p.length > 0);
|
|
988
|
-
}
|
|
989
|
-
function allowlist(files) {
|
|
990
|
-
return new Set(files.map((f) => f.replace(/\\/g, "/")));
|
|
991
|
-
}
|
|
992
|
-
function buildConflictResolutionPrompt(ctx, conflictedFiles) {
|
|
993
|
-
const counterparts = ctx.conflictingIssueIds.length > 0 ? ctx.conflictingIssueIds.join(", ") : "an earlier batch member";
|
|
994
|
-
return [
|
|
995
|
-
`You are resolving a git merge conflict inside a UAT batch assembly worktree.`,
|
|
996
|
-
``,
|
|
997
|
-
`The branch ${ctx.branchName} bundles already-reviewed features for human testing.`,
|
|
998
|
-
`Merging ${ctx.feature.branch} (${ctx.feature.issueId}: ${ctx.feature.title}) conflicted with ${counterparts}.`,
|
|
999
|
-
``,
|
|
1000
|
-
`Conflicted files:`,
|
|
1001
|
-
...conflictedFiles.map((f) => ` - ${f}`),
|
|
1002
|
-
``,
|
|
1003
|
-
`Your ONLY job: edit these files to resolve every conflict marker (<<<<<<<, =======, >>>>>>>)`,
|
|
1004
|
-
`so that BOTH features' intended behavior is preserved. Both sides passed review —`,
|
|
1005
|
-
`combine them; do not pick one side wholesale unless the changes are genuinely identical in intent.`,
|
|
1006
|
-
``,
|
|
1007
|
-
`Rules:`,
|
|
1008
|
-
`- Change NOTHING beyond what resolving the conflict requires.`,
|
|
1009
|
-
`- Do not refactor, reformat, or "improve" surrounding code.`,
|
|
1010
|
-
`- Do not run git commands; the caller stages and commits.`,
|
|
1011
|
-
`- If the conflict cannot be safely resolved, leave the markers in place and say so.`
|
|
1012
|
-
].join("\n");
|
|
1013
|
-
}
|
|
1014
|
-
function buildMergeCommitMessage(ctx, files) {
|
|
1015
|
-
const pair = [ctx.feature.issueId, ...ctx.conflictingIssueIds].join(" <-> ");
|
|
1016
|
-
return [
|
|
1017
|
-
`Merge branch '${ctx.feature.branch}' into ${ctx.branchName}`,
|
|
1018
|
-
``,
|
|
1019
|
-
`uat-assembly: resolve ${pair}`,
|
|
1020
|
-
`files: ${files.join(", ")}`
|
|
1021
|
-
].join("\n");
|
|
1022
|
-
}
|
|
1023
|
-
function defaultDeps$1() {
|
|
1024
|
-
const run = (cmd, args, cwd, timeoutMs) => execFileAsync$3(cmd, args, {
|
|
1025
|
-
cwd,
|
|
1026
|
-
maxBuffer: 16 * 1024 * 1024,
|
|
1027
|
-
...timeoutMs ? { timeout: timeoutMs } : {}
|
|
1028
|
-
});
|
|
1029
|
-
return {
|
|
1030
|
-
listConflictedFiles: async (cwd) => {
|
|
1031
|
-
const { stdout } = await run("git", [
|
|
1032
|
-
"diff",
|
|
1033
|
-
"--name-only",
|
|
1034
|
-
"--diff-filter=U"
|
|
1035
|
-
], cwd);
|
|
1036
|
-
return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1037
|
-
},
|
|
1038
|
-
stagedFiles: async (cwd) => {
|
|
1039
|
-
const { stdout } = await run("git", [
|
|
1040
|
-
"diff",
|
|
1041
|
-
"--cached",
|
|
1042
|
-
"--name-only",
|
|
1043
|
-
"-z"
|
|
1044
|
-
], cwd);
|
|
1045
|
-
return splitNul(stdout);
|
|
1046
|
-
},
|
|
1047
|
-
runAgent: async ({ prompt, cwd, timeoutMs }) => {
|
|
1048
|
-
await run("claude", [
|
|
1049
|
-
"-p",
|
|
1050
|
-
prompt,
|
|
1051
|
-
"--permission-mode",
|
|
1052
|
-
"acceptEdits"
|
|
1053
|
-
], cwd, timeoutMs);
|
|
1054
|
-
},
|
|
1055
|
-
filesWithConflictMarkers: async (cwd, files) => {
|
|
1056
|
-
if (files.length === 0) return [];
|
|
1057
|
-
const { stdout } = await execFileAsync$3("grep", [
|
|
1058
|
-
"-lE",
|
|
1059
|
-
"^(<<<<<<<|=======$|>>>>>>>)",
|
|
1060
|
-
"--",
|
|
1061
|
-
...files
|
|
1062
|
-
], {
|
|
1063
|
-
cwd,
|
|
1064
|
-
maxBuffer: 16 * 1024 * 1024
|
|
1065
|
-
}).catch((err) => {
|
|
1066
|
-
if (err && err.code === 1) return { stdout: "" };
|
|
1067
|
-
throw err;
|
|
1068
|
-
});
|
|
1069
|
-
return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1070
|
-
},
|
|
1071
|
-
unsafeChangedFiles: async (cwd, files) => {
|
|
1072
|
-
const allowed = allowlist(files);
|
|
1073
|
-
const [{ stdout: unstaged }, { stdout: untracked }] = await Promise.all([run("git", [
|
|
1074
|
-
"diff",
|
|
1075
|
-
"--name-only",
|
|
1076
|
-
"-z"
|
|
1077
|
-
], cwd), run("git", [
|
|
1078
|
-
"ls-files",
|
|
1079
|
-
"--others",
|
|
1080
|
-
"--exclude-standard",
|
|
1081
|
-
"-z"
|
|
1082
|
-
], cwd)]);
|
|
1083
|
-
return [...new Set([...splitNul(unstaged), ...splitNul(untracked)])].filter((file) => !allowed.has(file.replace(/\\/g, "/")));
|
|
1084
|
-
},
|
|
1085
|
-
discardFiles: async (cwd, files) => {
|
|
1086
|
-
if (files.length === 0) return;
|
|
1087
|
-
await run("git", [
|
|
1088
|
-
"restore",
|
|
1089
|
-
"--staged",
|
|
1090
|
-
"--worktree",
|
|
1091
|
-
"--",
|
|
1092
|
-
...files
|
|
1093
|
-
], cwd).catch(() => {});
|
|
1094
|
-
await run("git", [
|
|
1095
|
-
"clean",
|
|
1096
|
-
"-fd",
|
|
1097
|
-
"--",
|
|
1098
|
-
...files
|
|
1099
|
-
], cwd).catch(() => {});
|
|
1100
|
-
},
|
|
1101
|
-
stageFiles: async (cwd, files) => {
|
|
1102
|
-
if (files.length === 0) return;
|
|
1103
|
-
await run("git", [
|
|
1104
|
-
"add",
|
|
1105
|
-
"--",
|
|
1106
|
-
...files
|
|
1107
|
-
], cwd);
|
|
1108
|
-
},
|
|
1109
|
-
hasUnmergedEntries: async (cwd) => {
|
|
1110
|
-
const { stdout } = await run("git", ["ls-files", "-u"], cwd);
|
|
1111
|
-
return stdout.trim().length > 0;
|
|
1112
|
-
},
|
|
1113
|
-
commitMerge: async (cwd, message) => {
|
|
1114
|
-
await run("git", [
|
|
1115
|
-
"commit",
|
|
1116
|
-
"-m",
|
|
1117
|
-
message
|
|
1118
|
-
], cwd);
|
|
1119
|
-
},
|
|
1120
|
-
headSha: async (cwd) => (await run("git", ["rev-parse", "HEAD"], cwd)).stdout.trim()
|
|
1121
|
-
};
|
|
1122
|
-
}
|
|
1123
|
-
/**
|
|
1124
|
-
* Build the engine's `resolveConflict` hook. Returns the resolution on
|
|
1125
|
-
* success; null on ANY failure (the engine aborts the merge and holds the
|
|
1126
|
-
* feature out — never throws into the assembly loop).
|
|
1127
|
-
*/
|
|
1128
|
-
function buildConflictAgentHook(options = {}) {
|
|
1129
|
-
const deps = {
|
|
1130
|
-
...defaultDeps$1(),
|
|
1131
|
-
...options.deps ?? {}
|
|
1132
|
-
};
|
|
1133
|
-
const timeoutMs = options.timeoutMs ?? 3e5;
|
|
1134
|
-
const log = deps.log ?? (() => {});
|
|
1135
|
-
return async (ctx) => {
|
|
1136
|
-
const tag = `[uat-conflict-agent] ${ctx.branchName} ${ctx.feature.issueId}`;
|
|
1137
|
-
let files;
|
|
1138
|
-
let stagedBefore;
|
|
1139
|
-
try {
|
|
1140
|
-
files = await deps.listConflictedFiles(ctx.worktreePath);
|
|
1141
|
-
stagedBefore = await deps.stagedFiles(ctx.worktreePath);
|
|
1142
|
-
} catch (err) {
|
|
1143
|
-
log(`${tag}: could not inspect conflicted state: ${err instanceof Error ? err.message : String(err)}`);
|
|
1144
|
-
return null;
|
|
1145
|
-
}
|
|
1146
|
-
if (files.length === 0) {
|
|
1147
|
-
log(`${tag}: merge failed without unmerged paths — not a content conflict, giving up`);
|
|
1148
|
-
return null;
|
|
1149
|
-
}
|
|
1150
|
-
try {
|
|
1151
|
-
await deps.runAgent({
|
|
1152
|
-
prompt: buildConflictResolutionPrompt(ctx, files),
|
|
1153
|
-
cwd: ctx.worktreePath,
|
|
1154
|
-
timeoutMs
|
|
1155
|
-
});
|
|
1156
|
-
} catch (err) {
|
|
1157
|
-
log(`${tag}: agent run failed (${err instanceof Error ? err.message.split("\n")[0] : String(err)})`);
|
|
1158
|
-
return null;
|
|
1159
|
-
}
|
|
1160
|
-
try {
|
|
1161
|
-
const unsafe = await deps.unsafeChangedFiles(ctx.worktreePath, files);
|
|
1162
|
-
if (unsafe.length > 0) {
|
|
1163
|
-
log(`${tag}: agent touched non-conflicted path(s): ${unsafe.join(", ")}`);
|
|
1164
|
-
await deps.discardFiles(ctx.worktreePath, unsafe).catch((err) => {
|
|
1165
|
-
log(`${tag}: cleanup of non-conflicted path(s) failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1166
|
-
});
|
|
1167
|
-
return null;
|
|
1168
|
-
}
|
|
1169
|
-
const unresolved = await deps.filesWithConflictMarkers(ctx.worktreePath, files);
|
|
1170
|
-
if (unresolved.length > 0) {
|
|
1171
|
-
log(`${tag}: conflict markers remain in ${unresolved.join(", ")}`);
|
|
1172
|
-
return null;
|
|
1173
|
-
}
|
|
1174
|
-
await deps.stageFiles(ctx.worktreePath, files);
|
|
1175
|
-
if (await deps.hasUnmergedEntries(ctx.worktreePath)) {
|
|
1176
|
-
log(`${tag}: unmerged index entries remain after staging`);
|
|
1177
|
-
return null;
|
|
1178
|
-
}
|
|
1179
|
-
const stagedBeforeSet = new Set(stagedBefore);
|
|
1180
|
-
const allowed = allowlist(files);
|
|
1181
|
-
const newlyStagedOutsideAllowlist = (await deps.stagedFiles(ctx.worktreePath)).filter((file) => !stagedBeforeSet.has(file) && !allowed.has(file.replace(/\\/g, "/")));
|
|
1182
|
-
if (newlyStagedOutsideAllowlist.length > 0) {
|
|
1183
|
-
log(`${tag}: cached diff includes non-conflicted path(s): ${newlyStagedOutsideAllowlist.join(", ")}`);
|
|
1184
|
-
return null;
|
|
1185
|
-
}
|
|
1186
|
-
await deps.commitMerge(ctx.worktreePath, buildMergeCommitMessage(ctx, files));
|
|
1187
|
-
const commitSha = await deps.headSha(ctx.worktreePath);
|
|
1188
|
-
log(`${tag}: resolved ${files.length} file(s) at ${commitSha.slice(0, 9)}`);
|
|
1189
|
-
return {
|
|
1190
|
-
files,
|
|
1191
|
-
commitSha
|
|
1192
|
-
};
|
|
1193
|
-
} catch (err) {
|
|
1194
|
-
log(`${tag}: verification/commit failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
|
|
1195
|
-
return null;
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Single-flight per project: a tick that arrives while one is running gets
|
|
1201
|
-
* 'in-flight' immediately instead of queuing (assemblies are minutes-long;
|
|
1202
|
-
* the interval would pile up).
|
|
1203
|
-
*/
|
|
1204
|
-
const inFlightReconciles = /* @__PURE__ */ new Map();
|
|
1205
|
-
/** Stable signature of "what we want assembled" — backoff bookkeeping. */
|
|
1206
|
-
function desiredSignature(features, headShas, mainSha) {
|
|
1207
|
-
return `${mainSha}|${[...features].map((f) => `${f.issueId.toUpperCase()}@${headShas.get(f.branch) ?? "unknown"}`).sort().join(",")}`;
|
|
1208
|
-
}
|
|
1209
|
-
function generationSignature(gen) {
|
|
1210
|
-
const parts = [...gen.members.map((m) => `${m.issueId.toUpperCase()}@${m.headSha}`), ...gen.heldOut.map((h) => `${h.issueId.toUpperCase()}@${h.headSha ?? "held"}`)].sort();
|
|
1211
|
-
return `${gen.baseSha}|${parts.join(",")}`;
|
|
1212
|
-
}
|
|
1213
|
-
function liveSignatureMatches(gen, features, headShas, mainSha) {
|
|
1214
|
-
if (gen.baseSha !== mainSha) return false;
|
|
1215
|
-
const desiredIds = new Set(features.map((f) => f.issueId.toUpperCase()));
|
|
1216
|
-
const genIds = new Set([...gen.members.map((m) => m.issueId.toUpperCase()), ...gen.heldOut.map((h) => h.issueId.toUpperCase())]);
|
|
1217
|
-
if (desiredIds.size !== genIds.size) return false;
|
|
1218
|
-
for (const id of desiredIds) if (!genIds.has(id)) return false;
|
|
1219
|
-
for (const member of gen.members) if (headShas.get(member.branch) !== member.headSha) return false;
|
|
1220
|
-
for (const held of gen.heldOut) {
|
|
1221
|
-
if (!held.branch || !held.headSha) return false;
|
|
1222
|
-
if (headShas.get(held.branch) !== held.headSha) return false;
|
|
1223
|
-
}
|
|
1224
|
-
return true;
|
|
1225
|
-
}
|
|
1226
|
-
/**
|
|
1227
|
-
* A live generation is INVALIDATED only when its own CONTENT can no longer be
|
|
1228
|
-
* trusted for promotion: a bundled member left the ready queue (its work must
|
|
1229
|
-
* not merge) or a member's branch gained commits (the tested tree is outdated).
|
|
1230
|
-
*
|
|
1231
|
-
* Deliberately NOT invalidation triggers (the first live run, 2026-06-10,
|
|
1232
|
-
* churned every ready batch to death within minutes):
|
|
1233
|
-
* - main advancing past baseSha — an active flywheel run lands strike commits
|
|
1234
|
-
* on main continuously; the batch tree is still perfectly testable. Base
|
|
1235
|
-
* movement makes liveSignatureMatches() fail, so a FRESH superset assembles
|
|
1236
|
-
* and supersedes this one when ready; promotion safety against a moved base
|
|
1237
|
-
* is enforced at promote time (member-file overlap check + clean merge).
|
|
1238
|
-
* - held-out drift (departed / branch moved / metadata missing) — held-out
|
|
1239
|
-
* features are not in the tree; their changes also flow through
|
|
1240
|
-
* liveSignatureMatches() into a fresh assembly, never destruction of a
|
|
1241
|
-
* testable batch.
|
|
1242
|
-
*/
|
|
1243
|
-
function isStale(gen, desiredIds, headShas) {
|
|
1244
|
-
for (const member of gen.members) {
|
|
1245
|
-
if (!desiredIds.has(member.issueId.toUpperCase())) return `${member.issueId} left the ready queue`;
|
|
1246
|
-
if (headShas.get(member.branch) !== member.headSha) return `${member.issueId} branch gained commits`;
|
|
1247
|
-
}
|
|
1248
|
-
return null;
|
|
1249
|
-
}
|
|
1250
|
-
async function reconcileUatGenerations(projectRoot, deps, options = {}) {
|
|
1251
|
-
const log = deps.log ?? (() => {});
|
|
1252
|
-
const now = deps.now ?? Date.now;
|
|
1253
|
-
const invalidated = [];
|
|
1254
|
-
if (!deps.isEnabled()) return {
|
|
1255
|
-
action: "disabled",
|
|
1256
|
-
invalidated
|
|
1257
|
-
};
|
|
1258
|
-
if (inFlightReconciles.has(projectRoot)) return {
|
|
1259
|
-
action: "in-flight",
|
|
1260
|
-
invalidated
|
|
1261
|
-
};
|
|
1262
|
-
const tick = async () => {
|
|
1263
|
-
const readySet = await deps.getReadySet();
|
|
1264
|
-
if (readySet === null) return {
|
|
1265
|
-
action: "no-queue",
|
|
1266
|
-
invalidated
|
|
1267
|
-
};
|
|
1268
|
-
const mainSha = await deps.getMainHeadSha();
|
|
1269
|
-
const headShas = /* @__PURE__ */ new Map();
|
|
1270
|
-
for (const f of readySet) headShas.set(f.branch, await deps.getBranchHeadSha(f.branch).catch(() => "unknown"));
|
|
1271
|
-
const desiredIds = new Set(readySet.map((f) => f.issueId.toUpperCase()));
|
|
1272
|
-
for (const gen of deps.store.listChain(projectRoot, ["ready", "superseded"])) {
|
|
1273
|
-
const staleReason = isStale(gen, desiredIds, headShas);
|
|
1274
|
-
if (!staleReason) continue;
|
|
1275
|
-
log(`[uat-reconciler] invalidating ${gen.name}: ${staleReason}`);
|
|
1276
|
-
deps.store.update(gen.name, { status: "invalidated" });
|
|
1277
|
-
invalidated.push(gen.name);
|
|
1278
|
-
await deps.teardownStack(gen).catch((err) => {
|
|
1279
|
-
log(`[uat-reconciler] stack teardown for ${gen.name} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1280
|
-
});
|
|
1281
|
-
}
|
|
1282
|
-
const assembling = deps.store.listChain(projectRoot, ["assembling"]);
|
|
1283
|
-
for (const gen of assembling) {
|
|
1284
|
-
const lastProgress = Date.parse(gen.updatedAt || gen.createdAt || "");
|
|
1285
|
-
const sinceProgress = Number.isFinite(lastProgress) ? now() - lastProgress : Number.POSITIVE_INFINITY;
|
|
1286
|
-
if (sinceProgress > 36e5) {
|
|
1287
|
-
log(`[uat-reconciler] marking stuck assembling generation ${gen.name} failed (no progress for ${Math.round(sinceProgress / 6e4)}m)`);
|
|
1288
|
-
deps.store.update(gen.name, { status: "failed" });
|
|
1289
|
-
}
|
|
1290
|
-
}
|
|
1291
|
-
if (!options.force && deps.store.listChain(projectRoot, ["assembling"]).length > 0) return {
|
|
1292
|
-
action: "in-flight",
|
|
1293
|
-
invalidated
|
|
1294
|
-
};
|
|
1295
|
-
if (readySet.length === 0) {
|
|
1296
|
-
await deps.cleanup().catch(() => {});
|
|
1297
|
-
return {
|
|
1298
|
-
action: "idle",
|
|
1299
|
-
invalidated
|
|
1300
|
-
};
|
|
1301
|
-
}
|
|
1302
|
-
const desired = desiredSignature(readySet, headShas, mainSha);
|
|
1303
|
-
if (!options.force) {
|
|
1304
|
-
if (deps.store.listChain(projectRoot, ["ready", "superseded"]).some((gen) => liveSignatureMatches(gen, readySet, headShas, mainSha))) {
|
|
1305
|
-
await deps.cleanup().catch(() => {});
|
|
1306
|
-
return {
|
|
1307
|
-
action: "idle",
|
|
1308
|
-
invalidated
|
|
1309
|
-
};
|
|
1310
|
-
}
|
|
1311
|
-
const recentFailure = deps.store.listChain(projectRoot, ["failed"]).find((gen) => generationSignature(gen) === desired && now() - Date.parse(gen.updatedAt || gen.createdAt || "") < 6e5);
|
|
1312
|
-
if (recentFailure) {
|
|
1313
|
-
log(`[uat-reconciler] backing off — ${recentFailure.name} failed recently for the same input`);
|
|
1314
|
-
return {
|
|
1315
|
-
action: "backoff",
|
|
1316
|
-
invalidated
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
}
|
|
1320
|
-
log(`[uat-reconciler] assembling next generation for ${readySet.length} ready feature(s)`);
|
|
1321
|
-
const generation = await deps.assemble(readySet);
|
|
1322
|
-
await deps.cleanup().catch(() => {});
|
|
1323
|
-
return {
|
|
1324
|
-
action: generation.status === "ready" ? "assembled" : "assembly-failed",
|
|
1325
|
-
invalidated,
|
|
1326
|
-
generation
|
|
1327
|
-
};
|
|
1328
|
-
};
|
|
1329
|
-
const pending = tick().finally(() => inFlightReconciles.delete(projectRoot));
|
|
1330
|
-
inFlightReconciles.set(projectRoot, pending);
|
|
1331
|
-
return pending;
|
|
1332
|
-
}
|
|
1333
|
-
//#endregion
|
|
1334
|
-
//#region ../../lib/cloister/uat-stack.ts
|
|
1335
|
-
/**
|
|
1336
|
-
* Live UAT stack lifecycle for batch generations (PAN-1737, absorbs PAN-1738).
|
|
1337
|
-
*
|
|
1338
|
-
* Each ready generation can serve a real dashboard stack from its persistent
|
|
1339
|
-
* worktree: the devcontainer renders via the standard FEATURE_FOLDER template,
|
|
1340
|
-
* so the folder name (`uat-<label>-<codename>-<mmdd>`) yields the Traefik host
|
|
1341
|
-
* `uat-<label>-<codename>-<mmdd>.overdeck.localhost` with zero new infra.
|
|
1342
|
-
*
|
|
1343
|
-
* HARD INVARIANT — max 2 UAT stacks run concurrently. Docker's default
|
|
1344
|
-
* address pool fits ~31 bridge networks; accumulating UAT stacks would
|
|
1345
|
-
* eventually block ALL workspace creation ("all predefined address pools
|
|
1346
|
-
* have been fully subnetted"). Starting a third stack tears down the oldest
|
|
1347
|
-
* first, and invalidation/promotion always tear the generation's stack down.
|
|
1348
|
-
*
|
|
1349
|
-
* All process exec is async (server-reachable code — never execSync).
|
|
1350
|
-
*/
|
|
1351
|
-
init_merge_sync();
|
|
1352
|
-
const execFileAsync$2 = promisify$1(execFile$1);
|
|
1353
|
-
const COMPOSE_FILES = [
|
|
1354
|
-
"docker-compose.devcontainer.yml",
|
|
1355
|
-
"docker-compose.yml",
|
|
1356
|
-
"docker-compose.yaml",
|
|
1357
|
-
"compose.yml",
|
|
1358
|
-
"compose.yaml"
|
|
1359
|
-
];
|
|
1360
|
-
const stackMutationLocks = /* @__PURE__ */ new Map();
|
|
1361
|
-
async function withStackMutationLock(projectRoot, fn) {
|
|
1362
|
-
const previous = stackMutationLocks.get(projectRoot) ?? Promise.resolve();
|
|
1363
|
-
let release;
|
|
1364
|
-
const current = new Promise((resolve) => {
|
|
1365
|
-
release = resolve;
|
|
1366
|
-
});
|
|
1367
|
-
const tail = previous.catch(() => {}).then(() => current);
|
|
1368
|
-
stackMutationLocks.set(projectRoot, tail);
|
|
1369
|
-
await previous.catch(() => {});
|
|
1370
|
-
try {
|
|
1371
|
-
return await fn();
|
|
1372
|
-
} finally {
|
|
1373
|
-
release();
|
|
1374
|
-
if (stackMutationLocks.get(projectRoot) === tail) stackMutationLocks.delete(projectRoot);
|
|
1375
|
-
}
|
|
1376
|
-
}
|
|
1377
|
-
function defaultDeps() {
|
|
1378
|
-
const compose = (args, cwd) => execFileAsync$2("docker", ["compose", ...args], {
|
|
1379
|
-
cwd,
|
|
1380
|
-
encoding: "utf-8",
|
|
1381
|
-
timeout: 3e5,
|
|
1382
|
-
maxBuffer: 10 * 1024 * 1024
|
|
1383
|
-
});
|
|
1384
|
-
return {
|
|
1385
|
-
ensureDevcontainer: (workspacePath, issueId) => {
|
|
1386
|
-
const result = ensureDevcontainerSync({
|
|
1387
|
-
workspacePath,
|
|
1388
|
-
issueId
|
|
1389
|
-
});
|
|
1390
|
-
return result.step.success ? { ok: true } : {
|
|
1391
|
-
ok: false,
|
|
1392
|
-
error: result.step.error ?? "devcontainer render failed"
|
|
1393
|
-
};
|
|
1394
|
-
},
|
|
1395
|
-
composeUp: async (composeFile, projectName) => {
|
|
1396
|
-
await compose([
|
|
1397
|
-
"-f",
|
|
1398
|
-
composeFile,
|
|
1399
|
-
"-p",
|
|
1400
|
-
projectName,
|
|
1401
|
-
"up",
|
|
1402
|
-
"-d",
|
|
1403
|
-
"--build"
|
|
1404
|
-
], dirname$1(composeFile));
|
|
1405
|
-
},
|
|
1406
|
-
composeDown: async (composeFile, projectName) => {
|
|
1407
|
-
await compose([
|
|
1408
|
-
"-f",
|
|
1409
|
-
composeFile,
|
|
1410
|
-
"-p",
|
|
1411
|
-
projectName,
|
|
1412
|
-
"down",
|
|
1413
|
-
"-v",
|
|
1414
|
-
"--remove-orphans"
|
|
1415
|
-
], dirname$1(composeFile));
|
|
1416
|
-
},
|
|
1417
|
-
composePsCount: async (composeFile, projectName) => {
|
|
1418
|
-
const { stdout } = await compose([
|
|
1419
|
-
"-f",
|
|
1420
|
-
composeFile,
|
|
1421
|
-
"-p",
|
|
1422
|
-
projectName,
|
|
1423
|
-
"ps",
|
|
1424
|
-
"-q"
|
|
1425
|
-
], dirname$1(composeFile));
|
|
1426
|
-
return stdout.split("\n").filter((l) => l.trim().length > 0).length;
|
|
1427
|
-
},
|
|
1428
|
-
findComposeFile: (workspacePath) => {
|
|
1429
|
-
const devcontainerDir = join$1(workspacePath, ".devcontainer");
|
|
1430
|
-
for (const file of COMPOSE_FILES) {
|
|
1431
|
-
const fullPath = join$1(devcontainerDir, file);
|
|
1432
|
-
if (existsSync(fullPath)) return fullPath;
|
|
1433
|
-
}
|
|
1434
|
-
return null;
|
|
1435
|
-
},
|
|
1436
|
-
readComposeFile: (composeFile) => readFile(composeFile, "utf-8"),
|
|
1437
|
-
store: {
|
|
1438
|
-
setStack: (name, startedAt) => setUatGenerationStackStartedAtSync(name, startedAt),
|
|
1439
|
-
listWithStacks: () => listUatGenerationsWithStacksSync()
|
|
1440
|
-
}
|
|
1441
|
-
};
|
|
1442
|
-
}
|
|
1443
|
-
function uatStackFolderName(gen) {
|
|
1444
|
-
return basename$1(gen.worktreePath);
|
|
1445
|
-
}
|
|
1446
|
-
function composeProjectName(gen) {
|
|
1447
|
-
return `overdeck-${uatStackFolderName(gen)}`;
|
|
1448
|
-
}
|
|
1449
|
-
/**
|
|
1450
|
-
* The frontend URL the generation's stack serves (or will serve). Prefers the
|
|
1451
|
-
* Host(`…`) Traefik label in the rendered compose file; falls back to the
|
|
1452
|
-
* FEATURE_FOLDER convention.
|
|
1453
|
-
*/
|
|
1454
|
-
async function uatFrontendUrl(gen, deps = {}) {
|
|
1455
|
-
const d = {
|
|
1456
|
-
...defaultDeps(),
|
|
1457
|
-
...deps
|
|
1458
|
-
};
|
|
1459
|
-
const folder = uatStackFolderName(gen);
|
|
1460
|
-
const composeFile = d.findComposeFile(gen.worktreePath);
|
|
1461
|
-
if (composeFile) try {
|
|
1462
|
-
const hostMatch = (await d.readComposeFile(composeFile)).match(new RegExp("Host\\(`(" + folder.replace(/[.*+?^${}()|[\]\\]/g, "\\$&") + "\\.[^`]+)`\\)"));
|
|
1463
|
-
if (hostMatch?.[1]) return `https://${hostMatch[1]}`;
|
|
1464
|
-
} catch {}
|
|
1465
|
-
return `https://${folder}.overdeck.localhost`;
|
|
1466
|
-
}
|
|
1467
|
-
/** Probe a generation's stack. Self-corrects stale stackStartedAt records. */
|
|
1468
|
-
async function probeUatStack(gen, deps = {}) {
|
|
1469
|
-
const d = {
|
|
1470
|
-
...defaultDeps(),
|
|
1471
|
-
...deps
|
|
1472
|
-
};
|
|
1473
|
-
const frontendUrl = await uatFrontendUrl(gen, d);
|
|
1474
|
-
const composeFile = d.findComposeFile(gen.worktreePath);
|
|
1475
|
-
if (!composeFile || !gen.stackStartedAt) return {
|
|
1476
|
-
status: "absent",
|
|
1477
|
-
frontendUrl
|
|
1478
|
-
};
|
|
1479
|
-
if (await d.composePsCount(composeFile, composeProjectName(gen)).catch(() => 0) === 0) {
|
|
1480
|
-
try {
|
|
1481
|
-
d.store.setStack(gen.name, null);
|
|
1482
|
-
} catch {}
|
|
1483
|
-
return {
|
|
1484
|
-
status: "absent",
|
|
1485
|
-
frontendUrl
|
|
1486
|
-
};
|
|
1487
|
-
}
|
|
1488
|
-
return {
|
|
1489
|
-
status: "running",
|
|
1490
|
-
frontendUrl
|
|
1491
|
-
};
|
|
1492
|
-
}
|
|
1493
|
-
/**
|
|
1494
|
-
* Bring up the live stack for a generation (idempotent — compose up on a
|
|
1495
|
-
* running project is a no-op). Enforces MAX_UAT_STACKS by tearing down the
|
|
1496
|
-
* oldest running UAT stack(s) first.
|
|
1497
|
-
*/
|
|
1498
|
-
async function ensureUatStack(gen, deps = {}) {
|
|
1499
|
-
return withStackMutationLock(gen.projectRoot, async () => {
|
|
1500
|
-
const d = {
|
|
1501
|
-
...defaultDeps(),
|
|
1502
|
-
...deps
|
|
1503
|
-
};
|
|
1504
|
-
const log = d.log ?? (() => {});
|
|
1505
|
-
const evicted = [];
|
|
1506
|
-
const member = gen.members[0];
|
|
1507
|
-
if (!member) return {
|
|
1508
|
-
success: false,
|
|
1509
|
-
error: "generation has no members",
|
|
1510
|
-
evicted
|
|
1511
|
-
};
|
|
1512
|
-
const others = d.store.listWithStacks().filter((g) => g.name !== gen.name);
|
|
1513
|
-
while (others.length > 0 && others.length >= 2) {
|
|
1514
|
-
const oldest = others.shift();
|
|
1515
|
-
log(`[uat-stack] cap 2 reached — tearing down oldest stack ${oldest.name}`);
|
|
1516
|
-
try {
|
|
1517
|
-
await teardownUatStackUnlocked(oldest, d);
|
|
1518
|
-
} catch (err) {
|
|
1519
|
-
return {
|
|
1520
|
-
success: false,
|
|
1521
|
-
error: err instanceof Error ? err.message.split("\n")[0] ?? "stack teardown failed" : String(err),
|
|
1522
|
-
evicted
|
|
1523
|
-
};
|
|
1524
|
-
}
|
|
1525
|
-
evicted.push(oldest.name);
|
|
1526
|
-
}
|
|
1527
|
-
const rendered = d.ensureDevcontainer(gen.worktreePath, member.issueId);
|
|
1528
|
-
if (!rendered.ok) return {
|
|
1529
|
-
success: false,
|
|
1530
|
-
error: rendered.error ?? "devcontainer render failed",
|
|
1531
|
-
evicted
|
|
1532
|
-
};
|
|
1533
|
-
const composeFile = d.findComposeFile(gen.worktreePath);
|
|
1534
|
-
if (!composeFile) return {
|
|
1535
|
-
success: false,
|
|
1536
|
-
error: `no compose file under ${gen.worktreePath}/.devcontainer`,
|
|
1537
|
-
evicted
|
|
1538
|
-
};
|
|
1539
|
-
try {
|
|
1540
|
-
await d.composeUp(composeFile, composeProjectName(gen));
|
|
1541
|
-
} catch (err) {
|
|
1542
|
-
return {
|
|
1543
|
-
success: false,
|
|
1544
|
-
error: err instanceof Error ? err.message.split("\n")[0] ?? "compose up failed" : String(err),
|
|
1545
|
-
evicted
|
|
1546
|
-
};
|
|
1547
|
-
}
|
|
1548
|
-
d.store.setStack(gen.name, (/* @__PURE__ */ new Date()).toISOString());
|
|
1549
|
-
const frontendUrl = await uatFrontendUrl(gen, d);
|
|
1550
|
-
log(`[uat-stack] ${gen.name}: stack up at ${frontendUrl}`);
|
|
1551
|
-
return {
|
|
1552
|
-
success: true,
|
|
1553
|
-
frontendUrl,
|
|
1554
|
-
evicted
|
|
1555
|
-
};
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
async function teardownUatStackUnlocked(gen, d) {
|
|
1559
|
-
const composeFile = d.findComposeFile(gen.worktreePath);
|
|
1560
|
-
if (composeFile) try {
|
|
1561
|
-
await d.composeDown(composeFile, composeProjectName(gen));
|
|
1562
|
-
} catch (err) {
|
|
1563
|
-
d.log?.(`[uat-stack] ${gen.name}: compose down failed: ${err instanceof Error ? err.message.split("\n")[0] : String(err)}`);
|
|
1564
|
-
throw err;
|
|
1565
|
-
}
|
|
1566
|
-
try {
|
|
1567
|
-
d.store.setStack(gen.name, null);
|
|
1568
|
-
} catch {}
|
|
1569
|
-
}
|
|
1570
|
-
/**
|
|
1571
|
-
* Tear the generation's stack down (idempotent) and clear its stack record.
|
|
1572
|
-
* MUST be called on invalidation, promotion, and generation cleanup —
|
|
1573
|
-
* orphaned uat networks eventually block all workspace creation.
|
|
1574
|
-
*/
|
|
1575
|
-
async function teardownUatStack(gen, deps = {}) {
|
|
1576
|
-
await withStackMutationLock(gen.projectRoot, async () => {
|
|
1577
|
-
await teardownUatStackUnlocked(gen, {
|
|
1578
|
-
...defaultDeps(),
|
|
1579
|
-
...deps
|
|
1580
|
-
});
|
|
1581
|
-
});
|
|
1582
|
-
}
|
|
1583
|
-
//#endregion
|
|
1584
|
-
//#region ../../lib/cloister/file-size-reconcile.ts
|
|
1585
|
-
const execFileAsync$1 = promisify(execFile);
|
|
1586
|
-
async function reconcileFileSizeBaseline(worktreePath, runGit) {
|
|
1587
|
-
await execFileAsync$1("bash", ["scripts/lint-file-size.sh", "--update"], {
|
|
1588
|
-
cwd: worktreePath,
|
|
1589
|
-
maxBuffer: 16 * 1024 * 1024
|
|
1590
|
-
});
|
|
1591
|
-
const { stdout } = await runGit([
|
|
1592
|
-
"status",
|
|
1593
|
-
"--porcelain",
|
|
1594
|
-
"--",
|
|
1595
|
-
"scripts/file-size-baseline.txt"
|
|
1596
|
-
], worktreePath);
|
|
1597
|
-
if (stdout.trim() === "") return { changed: false };
|
|
1598
|
-
await runGit(["add", "scripts/file-size-baseline.txt"], worktreePath);
|
|
1599
|
-
return { changed: true };
|
|
1600
|
-
}
|
|
1601
|
-
//#endregion
|
|
1602
|
-
//#region ../../lib/cloister/uat-promote.ts
|
|
1603
|
-
/**
|
|
1604
|
-
* Batch promotion (PAN-1737: UAT batch trains) — "merge what you tested".
|
|
1605
|
-
*
|
|
1606
|
-
* Promoting a generation merges the uat/* branch itself into main (one no-ff
|
|
1607
|
-
* merge), so main receives EXACTLY the tree the operator exercised — including
|
|
1608
|
-
* the assembly agent's conflict resolutions. The deliberate exception is
|
|
1609
|
-
* file-size baseline reconciliation: scripts/file-size-baseline.txt is CI
|
|
1610
|
-
* metadata, so stale-high entries may be lowered or dropped before push.
|
|
1611
|
-
* Per-feature PRs are marked merged by GitHub automatically (their head commits
|
|
1612
|
-
* become reachable from main), and each member issue then flows through the
|
|
1613
|
-
* standard per-issue post-merge lifecycle exactly once, behind the PAN-328
|
|
1614
|
-
* in-flight guard.
|
|
1615
|
-
*
|
|
1616
|
-
* Hard precondition: the generation's baseSha must still equal origin/main —
|
|
1617
|
-
* promoting a stale tree would silently drop commits that landed since
|
|
1618
|
-
* assembly. Stale promotes are rejected with reassemble guidance (the
|
|
1619
|
-
* reconciler rebuilds automatically anyway).
|
|
1620
|
-
*
|
|
1621
|
-
* Pure orchestration with injected deps; real git in buildUatPromoteGitDeps.
|
|
1622
|
-
*/
|
|
1623
|
-
const execFileAsync = promisify$1(execFile$1);
|
|
1624
|
-
function safeUatBranchName(branchName) {
|
|
1625
|
-
if (!/^uat\/[A-Za-z0-9][A-Za-z0-9._-]*$/.test(branchName)) throw new Error(`unsafe UAT branch name: ${branchName}`);
|
|
1626
|
-
return branchName;
|
|
1627
|
-
}
|
|
1628
|
-
const PROMOTABLE = ["ready", "superseded"];
|
|
1629
|
-
function buildPromoteMergeMessage(gen) {
|
|
1630
|
-
const ids = gen.members.map((m) => m.issueId).join(", ");
|
|
1631
|
-
const resolutionNote = gen.resolutions.length > 0 ? `\n\nIncludes ${gen.resolutions.length} uat-assembly conflict resolution(s): ` + gen.resolutions.map((r) => r.issueIds.join(" <-> ")).join("; ") : "";
|
|
1632
|
-
return `Merge UAT batch ${gen.name} (${ids})${resolutionNote}`;
|
|
1633
|
-
}
|
|
1634
|
-
async function promoteUatGeneration(name, projectRoot, deps) {
|
|
1635
|
-
const log = deps.log ?? (() => {});
|
|
1636
|
-
const gen = deps.store.get(name);
|
|
1637
|
-
if (!gen || gen.projectRoot !== projectRoot) return {
|
|
1638
|
-
success: false,
|
|
1639
|
-
reason: "not-found",
|
|
1640
|
-
message: `No UAT generation named ${name}`
|
|
1641
|
-
};
|
|
1642
|
-
if (!PROMOTABLE.includes(gen.status)) return {
|
|
1643
|
-
success: false,
|
|
1644
|
-
reason: "wrong-status",
|
|
1645
|
-
message: `${name} is ${gen.status} — only a ready or superseded batch can be merged to main`
|
|
1646
|
-
};
|
|
1647
|
-
const notReady = gen.members.map((m) => ({
|
|
1648
|
-
issueId: m.issueId,
|
|
1649
|
-
gate: deps.memberEligibility(m.issueId)
|
|
1650
|
-
})).filter(({ gate }) => !gate.eligible);
|
|
1651
|
-
if (notReady.length > 0) return {
|
|
1652
|
-
success: false,
|
|
1653
|
-
reason: "member-not-ready",
|
|
1654
|
-
message: `${name} contains member(s) the pipeline has not cleared to merge: ${notReady.map(({ issueId, gate }) => `${issueId} (${gate.reason ?? "not eligible"})`).join(", ")} — wait for them to pass review+test, or for the reconciler to rebuild the batch without them.`
|
|
1655
|
-
};
|
|
1656
|
-
const mainSha = await deps.git.fetchMain();
|
|
1657
|
-
if (gen.baseSha !== mainSha) {
|
|
1658
|
-
const [mainChanged, batchChanged] = await Promise.all([deps.git.changedFilesSince(gen.baseSha), deps.git.batchChangedFiles(gen.name)]);
|
|
1659
|
-
const batchSet = new Set(batchChanged);
|
|
1660
|
-
const overlap = mainChanged.filter((f) => batchSet.has(f));
|
|
1661
|
-
if (overlap.length > 0) return {
|
|
1662
|
-
success: false,
|
|
1663
|
-
reason: "stale-base",
|
|
1664
|
-
message: `${name} was assembled off ${gen.baseSha.slice(0, 9)} but main is now at ${mainSha.slice(0, 9)}, and the new commits touch ${overlap.length} file(s) this batch also changes (${overlap.slice(0, 3).join(", ")}${overlap.length > 3 ? ", …" : ""}) — the tree you tested may not match what would land. A fresh batch reassembles automatically; re-test before merging.`
|
|
1665
|
-
};
|
|
1666
|
-
log(`[uat-promote] ${name}: base moved ${gen.baseSha.slice(0, 9)} → ${mainSha.slice(0, 9)} with no member-file overlap — proceeding`);
|
|
1667
|
-
}
|
|
1668
|
-
let mergeSha;
|
|
1669
|
-
try {
|
|
1670
|
-
mergeSha = await deps.git.mergeIntoMain(gen.name, buildPromoteMergeMessage(gen));
|
|
1671
|
-
} catch (err) {
|
|
1672
|
-
return {
|
|
1673
|
-
success: false,
|
|
1674
|
-
reason: "merge-failed",
|
|
1675
|
-
message: err instanceof Error ? err.message.split("\n")[0] ?? "merge failed" : String(err)
|
|
1676
|
-
};
|
|
1677
|
-
}
|
|
1678
|
-
log(`[uat-promote] ${name}: merged to main at ${mergeSha.slice(0, 9)} (${gen.members.length} member(s))`);
|
|
1679
|
-
deps.store.update(gen.name, { status: "promoted" });
|
|
1680
|
-
await deps.teardownStack(gen).catch((err) => {
|
|
1681
|
-
log(`[uat-promote] ${name}: stack teardown failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1682
|
-
});
|
|
1683
|
-
const invalidated = [];
|
|
1684
|
-
for (const other of deps.store.listChain(projectRoot, ["ready", "superseded"])) {
|
|
1685
|
-
if (other.name === gen.name) continue;
|
|
1686
|
-
deps.store.update(other.name, { status: "invalidated" });
|
|
1687
|
-
invalidated.push(other.name);
|
|
1688
|
-
await deps.teardownStack(other).catch(() => {});
|
|
1689
|
-
}
|
|
1690
|
-
const postMergeStarted = [];
|
|
1691
|
-
for (const member of gen.members) if (deps.firePostMerge(member.issueId, {
|
|
1692
|
-
sourceBranch: member.branch,
|
|
1693
|
-
verifiedMergedRef: member.headSha || member.branch
|
|
1694
|
-
})) postMergeStarted.push(member.issueId);
|
|
1695
|
-
else log(`[uat-promote] ${name}: post-merge for ${member.issueId} already in flight — skipped`);
|
|
1696
|
-
return {
|
|
1697
|
-
success: true,
|
|
1698
|
-
generation: gen.name,
|
|
1699
|
-
mergeSha,
|
|
1700
|
-
members: gen.members.map((m) => m.issueId),
|
|
1701
|
-
postMergeStarted,
|
|
1702
|
-
invalidated
|
|
1703
|
-
};
|
|
1704
|
-
}
|
|
1705
|
-
/** Real git wiring: throwaway detached worktree off origin/main, no-ff merge, baseline reconcile, push. */
|
|
1706
|
-
function buildUatPromoteGitDeps(projectRoot) {
|
|
1707
|
-
const runGit = (args, cwd) => execFileAsync("git", args, {
|
|
1708
|
-
cwd,
|
|
1709
|
-
maxBuffer: 16 * 1024 * 1024
|
|
1710
|
-
});
|
|
1711
|
-
return {
|
|
1712
|
-
fetchMain: async () => {
|
|
1713
|
-
await runGit([
|
|
1714
|
-
"fetch",
|
|
1715
|
-
"origin",
|
|
1716
|
-
"main"
|
|
1717
|
-
], projectRoot);
|
|
1718
|
-
return (await runGit(["rev-parse", "origin/main"], projectRoot)).stdout.trim();
|
|
1719
|
-
},
|
|
1720
|
-
mergeIntoMain: async (branchName, message) => {
|
|
1721
|
-
const safeBranch = safeUatBranchName(branchName);
|
|
1722
|
-
const worktreePath = join$1(tmpdir(), `uat-promote-${safeBranch.replace(/[^a-z0-9]/gi, "-")}`);
|
|
1723
|
-
await runGit([
|
|
1724
|
-
"worktree",
|
|
1725
|
-
"remove",
|
|
1726
|
-
"--force",
|
|
1727
|
-
worktreePath
|
|
1728
|
-
], projectRoot).catch(() => {});
|
|
1729
|
-
await runGit(["worktree", "prune"], projectRoot).catch(() => {});
|
|
1730
|
-
await runGit([
|
|
1731
|
-
"worktree",
|
|
1732
|
-
"add",
|
|
1733
|
-
"--detach",
|
|
1734
|
-
worktreePath,
|
|
1735
|
-
"origin/main"
|
|
1736
|
-
], projectRoot);
|
|
1737
|
-
try {
|
|
1738
|
-
const originRef = `origin/${safeBranch}`;
|
|
1739
|
-
await runGit([
|
|
1740
|
-
"merge",
|
|
1741
|
-
"--no-ff",
|
|
1742
|
-
await runGit([
|
|
1743
|
-
"rev-parse",
|
|
1744
|
-
"--verify",
|
|
1745
|
-
originRef
|
|
1746
|
-
], worktreePath).then(() => originRef).catch(() => safeBranch),
|
|
1747
|
-
"-m",
|
|
1748
|
-
message
|
|
1749
|
-
], worktreePath);
|
|
1750
|
-
const { changed } = await reconcileFileSizeBaseline(worktreePath, runGit);
|
|
1751
|
-
if (changed) await runGit([
|
|
1752
|
-
"commit",
|
|
1753
|
-
"--amend",
|
|
1754
|
-
"--no-edit"
|
|
1755
|
-
], worktreePath);
|
|
1756
|
-
await runGit([
|
|
1757
|
-
"push",
|
|
1758
|
-
"origin",
|
|
1759
|
-
"HEAD:main"
|
|
1760
|
-
], worktreePath);
|
|
1761
|
-
return (await runGit(["rev-parse", "HEAD"], worktreePath)).stdout.trim();
|
|
1762
|
-
} finally {
|
|
1763
|
-
await runGit([
|
|
1764
|
-
"worktree",
|
|
1765
|
-
"remove",
|
|
1766
|
-
"--force",
|
|
1767
|
-
worktreePath
|
|
1768
|
-
], projectRoot).catch(() => {});
|
|
1769
|
-
await runGit(["worktree", "prune"], projectRoot).catch(() => {});
|
|
1770
|
-
}
|
|
1771
|
-
},
|
|
1772
|
-
changedFilesSince: async (baseSha) => {
|
|
1773
|
-
if (!/^[0-9a-f]{7,40}$/i.test(baseSha)) throw new Error(`unsafe base sha: ${baseSha}`);
|
|
1774
|
-
const { stdout } = await runGit([
|
|
1775
|
-
"diff",
|
|
1776
|
-
"--name-only",
|
|
1777
|
-
`${baseSha}..origin/main`
|
|
1778
|
-
], projectRoot);
|
|
1779
|
-
return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1780
|
-
},
|
|
1781
|
-
batchChangedFiles: async (branchName) => {
|
|
1782
|
-
const safeBranch = safeUatBranchName(branchName);
|
|
1783
|
-
const ref = await runGit([
|
|
1784
|
-
"rev-parse",
|
|
1785
|
-
"--verify",
|
|
1786
|
-
`origin/${safeBranch}`
|
|
1787
|
-
], projectRoot).then(() => `origin/${safeBranch}`).catch(() => safeBranch);
|
|
1788
|
-
const { stdout } = await runGit([
|
|
1789
|
-
"diff",
|
|
1790
|
-
"--name-only",
|
|
1791
|
-
`${(await runGit([
|
|
1792
|
-
"merge-base",
|
|
1793
|
-
"origin/main",
|
|
1794
|
-
ref
|
|
1795
|
-
], projectRoot)).stdout.trim()}..${ref}`
|
|
1796
|
-
], projectRoot);
|
|
1797
|
-
return stdout.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
1798
|
-
}
|
|
1799
|
-
};
|
|
1800
|
-
}
|
|
1801
|
-
//#endregion
|
|
1802
|
-
//#region ../../lib/cloister/uat-promote-notify.ts
|
|
1803
|
-
init_control_settings();
|
|
1804
|
-
init_tmux();
|
|
1805
|
-
function buildPromoteNudge(result) {
|
|
1806
|
-
const members = result.members.length > 0 ? result.members.join(", ") : "(no members recorded)";
|
|
1807
|
-
return `The operator just promoted UAT generation ${result.generation} to main at ${result.mergeSha}. Run a fresh Observe->Act loop NOW. Fetch GET /api/registered-projects, then for every returned project key call GET /api/pipeline/membership?project=<URL-encoded-project-key>. Combine the responses and re-derive activePipeline from only rows where inPipeline === true; clean_terminal rows are audit-only and excluded. EXCLUDE the merged member(s): ${members}. Re-assemble a clean UAT batch with only members that are currently review+test passed, close out the promoted issue(s), emit a fresh status snapshot, and re-arm the next tick. Do NOT ask the operator a question, do NOT pause, and do NOT reuse the stale pre-promote ready set.`;
|
|
1808
|
-
}
|
|
1809
|
-
async function notifyFlywheelOfUatPromote(result, deps = {}) {
|
|
1810
|
-
try {
|
|
1811
|
-
if (result.success !== true) return;
|
|
1812
|
-
const getActiveRunId = deps.getActiveRunId ?? getFlywheelActiveRunId;
|
|
1813
|
-
const isPaused = deps.isPaused ?? isFlywheelGloballyPaused;
|
|
1814
|
-
const sessionExistsDep = deps.sessionExists ?? ((name) => Effect.runPromise(sessionExists(name)));
|
|
1815
|
-
const message = deps.message ?? messageAgent;
|
|
1816
|
-
const recordNudge = deps.recordNudge ?? recordDeaconNudge;
|
|
1817
|
-
if (!getActiveRunId()) return;
|
|
1818
|
-
if (isPaused()) return;
|
|
1819
|
-
const delivered = await sessionExistsDep(FLYWHEEL_ORCHESTRATOR_AGENT_ID);
|
|
1820
|
-
if (delivered) try {
|
|
1821
|
-
await message(FLYWHEEL_ORCHESTRATOR_AGENT_ID, buildPromoteNudge(result), "uat-promote-notify");
|
|
1822
|
-
} catch {}
|
|
1823
|
-
recordNudge({
|
|
1824
|
-
patrol: "uat-promote-notify",
|
|
1825
|
-
issueId: result.members[0] ?? result.generation,
|
|
1826
|
-
action: "notified flywheel-orchestrator to re-derive ready set after UAT promote",
|
|
1827
|
-
reason: "operator promoted a UAT batch; flywheel must immediately rebuild to drop merged + regressed members before its next tick",
|
|
1828
|
-
state: {
|
|
1829
|
-
generation: result.generation,
|
|
1830
|
-
members: result.members,
|
|
1831
|
-
mergeSha: result.mergeSha,
|
|
1832
|
-
delivered
|
|
1833
|
-
}
|
|
1834
|
-
});
|
|
1835
|
-
} catch {}
|
|
1836
|
-
}
|
|
1837
|
-
//#endregion
|
|
1838
|
-
//#region services/uat-train.ts
|
|
1839
|
-
/**
|
|
1840
|
-
* UAT batch train service (PAN-1737) — real wiring for the reconciler,
|
|
1841
|
-
* assembly engine, stacks, and promote path, plus the payload builders the
|
|
1842
|
-
* flywheel routes expose.
|
|
1843
|
-
*
|
|
1844
|
-
* The reconciler interval is the heartbeat of "always one batch ready":
|
|
1845
|
-
* every 60s (gated per-tick on flywheel.merge_train_enabled, no-op without an
|
|
1846
|
-
* active flywheel run) it compares the ready set against the generation chain
|
|
1847
|
-
* and assembles/invalidates as needed. Assemblies run minutes — the reconciler
|
|
1848
|
-
* is single-flight per project, so ticks never pile up.
|
|
1849
|
-
*/
|
|
1850
|
-
init_merge_sync();
|
|
1851
|
-
const RECONCILE_INTERVAL_MS = 6e4;
|
|
1852
|
-
const CHAIN_PAYLOAD_LIMIT = 10;
|
|
1853
|
-
const ACCEPTANCE_CRITERIA_READ_CONCURRENCY = 4;
|
|
1854
|
-
const acceptanceCriteriaByIssue = /* @__PURE__ */ new Map();
|
|
1855
|
-
function resolveUatProjectRoot(cwdPath = process.cwd()) {
|
|
1856
|
-
const registeredProject = findProjectByPathSync(cwdPath);
|
|
1857
|
-
if (registeredProject) return resolve(registeredProject.path);
|
|
1858
|
-
const normalized = resolve(cwdPath);
|
|
1859
|
-
const workspaceMatch = normalized.match(/^(.*)\/workspaces\/feature-[^/]+(?:\/.*)?$/i);
|
|
1860
|
-
return workspaceMatch?.[1] ? resolve(workspaceMatch[1]) : normalized;
|
|
1861
|
-
}
|
|
1862
|
-
function projectRoot() {
|
|
1863
|
-
return resolveUatProjectRoot();
|
|
1864
|
-
}
|
|
1865
|
-
function canStartUatTrainReconciler() {
|
|
1866
|
-
const identity = getDashboardIdentity();
|
|
1867
|
-
return identity.mode === "primary" && resolve(identity.repoRoot) === projectRoot();
|
|
1868
|
-
}
|
|
1869
|
-
/** Ready set in merge order, or null when no flywheel run is active. */
|
|
1870
|
-
async function getReadySet() {
|
|
1871
|
-
const status = await readCurrentFlywheelStatusForDashboard();
|
|
1872
|
-
if (!status) return null;
|
|
1873
|
-
const { computeMergeQueue, resolveMergeQueuePrUrl } = await import("./flywheel-merge-order-UUcsCDb6.js");
|
|
1874
|
-
const fromVerbs = (await Effect.runPromise(computeMergeQueue(status.activePipeline, projectRoot(), {
|
|
1875
|
-
getPrUrl: resolveMergeQueuePrUrl,
|
|
1876
|
-
onIneligible: (issueId, reason) => console.log(`[uat-train] ${issueId} has a merge verb but is not merge-eligible (${reason}) — excluded from the ready set`)
|
|
1877
|
-
}).pipe(Effect.provide(layer)))).map((item) => ({
|
|
1878
|
-
issueId: item.issueId,
|
|
1879
|
-
title: item.title,
|
|
1880
|
-
branch: item.branchName,
|
|
1881
|
-
...item.pr !== void 0 ? { pr: item.pr } : {},
|
|
1882
|
-
...item.prUrl !== void 0 ? { prUrl: item.prUrl } : {},
|
|
1883
|
-
conflictsWith: item.conflictsWith
|
|
1884
|
-
}));
|
|
1885
|
-
const swept = await sweepEligibleFeatures(new Set(fromVerbs.map((f) => f.issueId)));
|
|
1886
|
-
return [...fromVerbs, ...swept];
|
|
1887
|
-
}
|
|
1888
|
-
/**
|
|
1889
|
-
* PAN-2484: the verb-based ready set misses merge-eligible issues that entered
|
|
1890
|
-
* the pipeline outside the active flywheel run (operator revivals, strikes,
|
|
1891
|
-
* externally-requested reviews) — those sat at readyForMerge with no train ever
|
|
1892
|
-
* assembling around them. Sweep review status for THIS project's merge-eligible
|
|
1893
|
-
* issues whose feature branch exists and append any the verb set missed. The
|
|
1894
|
-
* flywheel's ordering still leads; swept extras ride at the tail (the assembly
|
|
1895
|
-
* engine's conflict hook owns any cross-feature conflicts).
|
|
1896
|
-
*/
|
|
1897
|
-
async function sweepEligibleFeatures(alreadyIncluded) {
|
|
1898
|
-
try {
|
|
1899
|
-
const { loadReviewStatuses, mergeGateEligibility } = await import("./review-status-btEYlaxA.js");
|
|
1900
|
-
const { resolveProjectFromIssueSync } = await import("./projects-vjjcmiN8.js");
|
|
1901
|
-
const { execFile } = await import("node:child_process");
|
|
1902
|
-
const { promisify } = await import("node:util");
|
|
1903
|
-
const execFileAsync = promisify(execFile);
|
|
1904
|
-
const root = projectRoot();
|
|
1905
|
-
const extras = [];
|
|
1906
|
-
for (const [issueId, rs] of Object.entries(loadReviewStatuses())) {
|
|
1907
|
-
if (alreadyIncluded.has(issueId)) continue;
|
|
1908
|
-
if (rs.readyForMerge !== true) continue;
|
|
1909
|
-
if (!mergeGateEligibility(rs).eligible) continue;
|
|
1910
|
-
const project = resolveProjectFromIssueSync(issueId);
|
|
1911
|
-
if (!project || resolve(project.projectPath) !== root) continue;
|
|
1912
|
-
const branch = `feature/${issueId.toLowerCase()}`;
|
|
1913
|
-
try {
|
|
1914
|
-
await execFileAsync("git", [
|
|
1915
|
-
"show-ref",
|
|
1916
|
-
"--verify",
|
|
1917
|
-
"--quiet",
|
|
1918
|
-
`refs/heads/${branch}`
|
|
1919
|
-
], {
|
|
1920
|
-
cwd: root,
|
|
1921
|
-
timeout: 1e4
|
|
1922
|
-
});
|
|
1923
|
-
} catch {
|
|
1924
|
-
continue;
|
|
1925
|
-
}
|
|
1926
|
-
console.log(`[uat-train] ${issueId} is merge-eligible without a flywheel merge verb — swept into the ready set (PAN-2484)`);
|
|
1927
|
-
extras.push({
|
|
1928
|
-
issueId,
|
|
1929
|
-
title: issueId,
|
|
1930
|
-
branch,
|
|
1931
|
-
conflictsWith: []
|
|
1932
|
-
});
|
|
1933
|
-
}
|
|
1934
|
-
return extras;
|
|
1935
|
-
} catch (err) {
|
|
1936
|
-
console.warn(`[uat-train] eligibility sweep failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1937
|
-
return [];
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
function codenameLabel(features) {
|
|
1941
|
-
return (features[0]?.issueId.split("-")[0] ?? "uat").toLowerCase();
|
|
1942
|
-
}
|
|
1943
|
-
async function assembleFromReadySet(features) {
|
|
1944
|
-
const root = projectRoot();
|
|
1945
|
-
const store = buildUatGenerationStore();
|
|
1946
|
-
return assembleUatGeneration({
|
|
1947
|
-
projectRoot: root,
|
|
1948
|
-
label: codenameLabel(features),
|
|
1949
|
-
dateIso: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1950
|
-
features,
|
|
1951
|
-
takenBranchNames: await listRemoteUatBranches(root)
|
|
1952
|
-
}, {
|
|
1953
|
-
git: buildUatGenerationGitDeps(root),
|
|
1954
|
-
store,
|
|
1955
|
-
resolveConflict: buildConflictAgentHook(),
|
|
1956
|
-
log: (msg) => console.log(msg)
|
|
1957
|
-
});
|
|
1958
|
-
}
|
|
1959
|
-
async function runCleanup() {
|
|
1960
|
-
const root = projectRoot();
|
|
1961
|
-
await cleanupUatGenerations(root, {
|
|
1962
|
-
store: buildUatGenerationStore(),
|
|
1963
|
-
...buildUatGenerationCleanupGit(root),
|
|
1964
|
-
teardownStack: (gen) => teardownUatStack(gen),
|
|
1965
|
-
log: (msg) => console.log(msg)
|
|
1966
|
-
});
|
|
1967
|
-
}
|
|
1968
|
-
/** One reconciler pass. `force` rebuilds even when a live generation matches. */
|
|
1969
|
-
async function runUatTrainReconcile(options = {}) {
|
|
1970
|
-
const root = projectRoot();
|
|
1971
|
-
const gitDeps = buildUatGenerationGitDeps(root);
|
|
1972
|
-
return reconcileUatGenerations(root, {
|
|
1973
|
-
isEnabled: () => isMergeTrainEnabled(),
|
|
1974
|
-
getReadySet,
|
|
1975
|
-
getMainHeadSha: () => gitDeps.fetchMain(),
|
|
1976
|
-
getBranchHeadSha: (branch) => gitDeps.branchHeadSha(branch),
|
|
1977
|
-
store: buildUatGenerationStore(),
|
|
1978
|
-
assemble: assembleFromReadySet,
|
|
1979
|
-
teardownStack: (gen) => teardownUatStack(gen),
|
|
1980
|
-
cleanup: runCleanup,
|
|
1981
|
-
log: (msg) => console.log(msg)
|
|
1982
|
-
}, options);
|
|
1983
|
-
}
|
|
1984
|
-
let reconcilerTimer = null;
|
|
1985
|
-
function startUatTrainReconciler() {
|
|
1986
|
-
if (!canStartUatTrainReconciler()) return false;
|
|
1987
|
-
if (reconcilerTimer) return true;
|
|
1988
|
-
reconcilerTimer = setInterval(() => {
|
|
1989
|
-
runUatTrainReconcile().catch((err) => {
|
|
1990
|
-
console.warn("[uat-train] reconcile tick failed:", err instanceof Error ? err.message : err);
|
|
1991
|
-
});
|
|
1992
|
-
}, RECONCILE_INTERVAL_MS);
|
|
1993
|
-
reconcilerTimer.unref?.();
|
|
1994
|
-
runUatTrainReconcile().catch((err) => {
|
|
1995
|
-
console.warn("[uat-train] initial reconcile failed:", err instanceof Error ? err.message : err);
|
|
1996
|
-
});
|
|
1997
|
-
return true;
|
|
1998
|
-
}
|
|
1999
|
-
function stopUatTrainReconciler() {
|
|
2000
|
-
if (reconcilerTimer) {
|
|
2001
|
-
clearInterval(reconcilerTimer);
|
|
2002
|
-
reconcilerTimer = null;
|
|
2003
|
-
}
|
|
2004
|
-
}
|
|
2005
|
-
async function mapBounded(items, concurrency, worker) {
|
|
2006
|
-
let next = 0;
|
|
2007
|
-
const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {
|
|
2008
|
-
while (next < items.length) {
|
|
2009
|
-
const item = items[next++];
|
|
2010
|
-
await worker(item);
|
|
2011
|
-
}
|
|
2012
|
-
});
|
|
2013
|
-
await Promise.all(workers);
|
|
2014
|
-
}
|
|
2015
|
-
async function loadAcceptanceCriteriaCache(issueIds) {
|
|
2016
|
-
const cache = /* @__PURE__ */ new Map();
|
|
2017
|
-
if (issueIds.size === 0) return cache;
|
|
2018
|
-
const root = projectRoot();
|
|
2019
|
-
await mapBounded([...issueIds], ACCEPTANCE_CRITERIA_READ_CONCURRENCY, async (issueId) => {
|
|
2020
|
-
const upperIssueId = issueId.toUpperCase();
|
|
2021
|
-
const existing = acceptanceCriteriaByIssue.get(upperIssueId);
|
|
2022
|
-
if (existing) try {
|
|
2023
|
-
const { mtimeMs } = await stat(existing.path);
|
|
2024
|
-
if (existing.mtimeMs === mtimeMs) {
|
|
2025
|
-
cache.set(upperIssueId, existing.criteria);
|
|
2026
|
-
return;
|
|
2027
|
-
}
|
|
2028
|
-
} catch {
|
|
2029
|
-
acceptanceCriteriaByIssue.delete(upperIssueId);
|
|
2030
|
-
}
|
|
2031
|
-
try {
|
|
2032
|
-
const found = await Effect.runPromise(findXBriefByIssue(root, upperIssueId));
|
|
2033
|
-
if (!found) {
|
|
2034
|
-
acceptanceCriteriaByIssue.delete(upperIssueId);
|
|
2035
|
-
cache.set(upperIssueId, []);
|
|
2036
|
-
return;
|
|
2037
|
-
}
|
|
2038
|
-
const { mtimeMs } = await stat(found.path);
|
|
2039
|
-
const criteria = extractACFromDocument(await Effect.runPromise(readXBriefDocument(found.path))).map((ac) => ({
|
|
2040
|
-
title: ac.title,
|
|
2041
|
-
status: ac.status
|
|
2042
|
-
}));
|
|
2043
|
-
acceptanceCriteriaByIssue.set(upperIssueId, {
|
|
2044
|
-
path: found.path,
|
|
2045
|
-
mtimeMs,
|
|
2046
|
-
criteria
|
|
2047
|
-
});
|
|
2048
|
-
cache.set(upperIssueId, criteria);
|
|
2049
|
-
} catch {
|
|
2050
|
-
cache.set(upperIssueId, []);
|
|
2051
|
-
}
|
|
2052
|
-
});
|
|
2053
|
-
return cache;
|
|
2054
|
-
}
|
|
2055
|
-
/** The generation chain, newest first, enriched for the UAT batches card. */
|
|
2056
|
-
async function getUatGenerationsPayload() {
|
|
2057
|
-
if (!await readCurrentFlywheelStatusForDashboard()) return [];
|
|
2058
|
-
const chain = listUatGenerationsSync({
|
|
2059
|
-
projectRoot: projectRoot(),
|
|
2060
|
-
limit: CHAIN_PAYLOAD_LIMIT
|
|
2061
|
-
});
|
|
2062
|
-
if (chain.length === 0) return [];
|
|
2063
|
-
const acCache = await loadAcceptanceCriteriaCache(new Set(chain.flatMap((gen) => gen.members.map((member) => member.issueId.toUpperCase()))));
|
|
2064
|
-
const payload = [];
|
|
2065
|
-
for (const gen of chain) {
|
|
2066
|
-
const probe = await probeUatStack(gen);
|
|
2067
|
-
const members = [];
|
|
2068
|
-
for (const member of gen.members) members.push({
|
|
2069
|
-
issueId: member.issueId,
|
|
2070
|
-
title: member.title,
|
|
2071
|
-
branch: member.branch,
|
|
2072
|
-
...member.pr !== void 0 ? { pr: member.pr } : {},
|
|
2073
|
-
...member.prUrl !== void 0 ? { prUrl: member.prUrl } : {},
|
|
2074
|
-
mergeOrder: member.mergeOrder,
|
|
2075
|
-
acceptanceCriteria: acCache.get(member.issueId.toUpperCase()) ?? []
|
|
2076
|
-
});
|
|
2077
|
-
payload.push({
|
|
2078
|
-
name: gen.name,
|
|
2079
|
-
status: gen.status,
|
|
2080
|
-
baseSha: gen.baseSha,
|
|
2081
|
-
createdAt: gen.createdAt,
|
|
2082
|
-
updatedAt: gen.updatedAt,
|
|
2083
|
-
members,
|
|
2084
|
-
heldOut: gen.heldOut,
|
|
2085
|
-
resolutions: gen.resolutions,
|
|
2086
|
-
stack: {
|
|
2087
|
-
status: probe.status,
|
|
2088
|
-
frontendUrl: probe.frontendUrl
|
|
2089
|
-
}
|
|
2090
|
-
});
|
|
2091
|
-
}
|
|
2092
|
-
return payload;
|
|
2093
|
-
}
|
|
2094
|
-
/** The authoritative active UAT candidate, if one is ready to test/ship. */
|
|
2095
|
-
async function getUatCandidatePayload() {
|
|
2096
|
-
const [candidate] = listUatGenerationsSync({
|
|
2097
|
-
projectRoot: projectRoot(),
|
|
2098
|
-
statuses: ["ready"],
|
|
2099
|
-
limit: 1
|
|
2100
|
-
});
|
|
2101
|
-
if (!candidate) return null;
|
|
2102
|
-
return {
|
|
2103
|
-
branchName: candidate.name,
|
|
2104
|
-
bundled: candidate.members.map((member) => member.issueId),
|
|
2105
|
-
status: "ready"
|
|
2106
|
-
};
|
|
2107
|
-
}
|
|
2108
|
-
async function postUatGenerationStackPayload(name) {
|
|
2109
|
-
const gen = getUatGenerationSync(name);
|
|
2110
|
-
if (!gen) return {
|
|
2111
|
-
ok: false,
|
|
2112
|
-
error: `No UAT generation named ${name}`,
|
|
2113
|
-
status: 404
|
|
2114
|
-
};
|
|
2115
|
-
if (gen.status !== "ready" && gen.status !== "superseded") return {
|
|
2116
|
-
ok: false,
|
|
2117
|
-
error: `${name} is ${gen.status} — only live batches can serve a stack`,
|
|
2118
|
-
status: 409
|
|
2119
|
-
};
|
|
2120
|
-
const result = await ensureUatStack(gen);
|
|
2121
|
-
if (!result.success) return {
|
|
2122
|
-
ok: false,
|
|
2123
|
-
error: result.error ?? "stack start failed",
|
|
2124
|
-
status: 500
|
|
2125
|
-
};
|
|
2126
|
-
return {
|
|
2127
|
-
ok: true,
|
|
2128
|
-
frontendUrl: result.frontendUrl,
|
|
2129
|
-
evicted: result.evicted
|
|
2130
|
-
};
|
|
2131
|
-
}
|
|
2132
|
-
async function postUatGenerationPromotePayload(name, firePostMerge) {
|
|
2133
|
-
const root = projectRoot();
|
|
2134
|
-
const { reviewRecordEligibility } = await import("./flywheel-merge-order-UUcsCDb6.js");
|
|
2135
|
-
const result = await promoteUatGeneration(name, root, {
|
|
2136
|
-
git: buildUatPromoteGitDeps(root),
|
|
2137
|
-
store: {
|
|
2138
|
-
...buildUatGenerationStore(),
|
|
2139
|
-
get: (n) => getUatGenerationSync(n)
|
|
2140
|
-
},
|
|
2141
|
-
teardownStack: (gen) => teardownUatStack(gen),
|
|
2142
|
-
firePostMerge,
|
|
2143
|
-
memberEligibility: reviewRecordEligibility,
|
|
2144
|
-
log: (msg) => console.log(msg)
|
|
2145
|
-
});
|
|
2146
|
-
await notifyFlywheelOfUatPromote(result).catch(() => {});
|
|
2147
|
-
return result;
|
|
2148
|
-
}
|
|
2149
|
-
//#endregion
|
|
2150
|
-
export { postUatGenerationStackPayload as a, startUatTrainReconciler as c, openFlywheelRunReportForDashboard as d, pauseFlywheelRunForDashboard as f, runDashboardDbJob as g, startFlywheelRunForDashboard as h, postUatGenerationPromotePayload as i, stopUatTrainReconciler as l, resumeFlywheelRunForDashboard as m, getUatCandidatePayload as n, resolveUatProjectRoot as o, readCurrentFlywheelStatusForDashboard as p, getUatGenerationsPayload as r, runUatTrainReconcile as s, canStartUatTrainReconciler as t, abortFlywheelRunForDashboard as u };
|
|
2151
|
-
|
|
2152
|
-
//# sourceMappingURL=uat-train-DDSrSoV1.js.map
|