@overdeck/desktop 0.45.12 → 0.45.13
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 +33 -0
- package/package.json +7 -2
- package/server/{acceptance-criteria-1LHgD9vg.js → acceptance-criteria-CfujPRrz.js} +2 -2
- package/server/{advancing-selfheal-BqsLWqEM.js → advancing-selfheal-Bn5QiLtn.js} +4 -4
- package/server/{agent-gc-CUb52n7s.js → agent-gc-Cl9Nm0Zu.js} +3 -3
- package/server/{agent-state-D-moNwhA.js → agent-state-C6Rm6FPR.js} +1 -1
- package/server/{agent-state-service-BzESvLOf.js → agent-state-service-JhFzYCP_.js} +5 -5
- package/server/{agent-state-service-BaZH7sXJ.js → agent-state-service-U8feBf8t.js} +1 -1
- package/server/{agents-C4kZ8a2r.js → agents-CxbuBjID.js} +1 -1
- package/server/{agents-B5LfpLPv.js → agents-DqQa4Dq_.js} +88 -64
- package/server/{archive-planning-oX1pj6nY.js → archive-planning-CPitEZo4.js} +3 -3
- package/server/{archive-planning-BbZe-8IK.js → archive-planning-DmpT1_14.js} +1 -1
- package/server/{artifact-index-jobs-DGr6bxev.js → artifact-index-jobs-sMnNtPhy.js} +2 -2
- package/server/{auto-commit-CGQlb4rV.js → auto-commit-wZ9X7Isd.js} +1 -1
- package/server/{backlog-auto-trigger-BlJHikOm.js → backlog-auto-trigger-Cf9C8fwD.js} +2 -2
- package/server/{bd-process-lock-i3CYMS-B.js → bd-process-lock-D1aJxeYv.js} +2 -2
- package/server/{beads-BNU-fb48.js → beads-CivpXztb.js} +1 -1
- package/server/{beads-DD7fYCl5.js → beads-DRoj6-e1.js} +4 -4
- package/server/{beads-query-e6oku4P5.js → beads-query-tOsAYP5h.js} +2 -2
- package/server/{behavior-CPf0pbOt.js → behavior-D3d2mVyD.js} +3 -3
- package/server/{briefing-freshness-B2FdLwIv.js → briefing-freshness-BL3Q6p7R.js} +4 -4
- package/server/{checkpoint-manager-JryW6MpM.js → checkpoint-manager-DCxjQvP_.js} +2 -2
- package/server/{checkpoint-manager-eFgaMMu4.js → checkpoint-manager-j5URlFY7.js} +1 -1
- package/server/checkpoint-worker.js +1 -1
- package/server/{checkpoints-qvOqvMuC.js → checkpoints-BoX1H2Hf.js} +2 -2
- package/server/{checkpoints-BK17oicz.js → checkpoints-Bu_M_Q9W.js} +1 -1
- package/server/claude-hooks-provision-D-jGXmu1.js +360 -0
- package/server/{claude-permissions-B6bWGh9d.js → claude-permissions-DxIjkOVl.js} +2 -2
- package/server/{claude-settings-overlay-BPdhTPjo.js → claude-settings-overlay-5b9X2S0C.js} +1 -1
- package/server/{claude-settings-overlay-CRyyKKXQ.js → claude-settings-overlay-BPDS6GfE.js} +2 -2
- package/server/cli-CqgiSAU8.js +3 -0
- package/server/{cli-B75AieAM.js → cli-s7bPWy-E.js} +9 -9
- package/server/{cliproxy-CCzwuSNe.js → cliproxy-BituqWtR.js} +2 -2
- package/server/{cliproxy-DOQf66G9.js → cliproxy-D3sHB0sI.js} +1 -1
- package/server/{close-issue-_X3MXJyG.js → close-issue-CrBE7voz.js} +4 -4
- package/server/{closed-issue-reaper-CLR7ClsO.js → closed-issue-reaper-BRQBc7Xa.js} +1 -1
- package/server/{closed-issue-reaper-BsUfBSjM.js → closed-issue-reaper-DX9mYUji.js} +9 -9
- package/server/{codex-CY-QRuU9.js → codex-Bi1ITSOo.js} +1 -1
- package/server/{codex-B4KEpX5V.js → codex-CO0HrK3j.js} +5 -5
- package/server/{compact-beads-cZds2MTr.js → compact-beads-CrA8-k10.js} +1 -1
- package/server/{compact-beads-uEDccC76.js → compact-beads-DUkCUFxA.js} +3 -3
- package/server/{config-DARrpYWA.js → config-B4_d7z7z.js} +1 -1
- package/server/{config-8sjaU6jZ.js → config-DsCxzjXK.js} +2 -2
- package/server/{config-yaml-ClpGgrf6.js → config-yaml-C_djSurJ.js} +2 -2
- package/server/{conflict-gate-Ds9_2yjd.js → conflict-gate-CpOn9KC-.js} +1 -1
- package/server/{conflict-gate-BtB_o-Vi.js → conflict-gate-D47Kl78u.js} +3 -3
- package/server/{context-layers-r2ywnQYA.js → context-layers-BNhUBTkf.js} +3 -3
- package/server/{context-layers-CRCk1QG7.js → context-layers-CHl3Nroz.js} +2 -2
- package/server/{control-settings-D1B3u6HP.js → control-settings-KyYNXZLv.js} +4 -4
- package/server/{conversation-compaction-D86Zo2Ci.js → conversation-compaction-D3aS5PnS.js} +8 -8
- package/server/{conversation-compaction-CZRVk4hg.js → conversation-compaction-D4Wed1BJ.js} +1 -1
- package/server/{conversation-service-CuTVU8dV.js → conversation-service-CFZIjs-M.js} +5 -5
- package/server/{conversation-service-CkL-6MuG.js → conversation-service-DcDB5usR.js} +1 -1
- package/server/{conversations-DfJKBDwB.js → conversations-BSOU5qnu.js} +4 -4
- package/server/{conversations-BmWEluPL.js → conversations-BbDPrz89.js} +1 -1
- package/server/{cost-DAPYN6iA.js → cost-BE_ulOrw.js} +3 -3
- package/server/{cost-CCnqa5xK.js → cost-CaiG63FG.js} +2 -2
- package/server/{cost-sync-BDL10Nwu.js → cost-sync-D6AX8jv-.js} +3 -3
- package/server/{dag-Dv2r5bVD.js → dag-BQFtXOuJ.js} +3 -3
- package/server/{dangerous-git-ops-Dcw7PbYo.js → dangerous-git-ops-04Ba-Qu3.js} +1 -1
- package/server/dashboard-db-worker.js +6 -6
- package/server/{deacon-CM0MxbS8.js → deacon-2EvA9Ci3.js} +97 -97
- package/server/{deacon-DYnZH7kC.js → deacon-Bz-6Wjmn.js} +3 -3
- package/server/deacon-swarm-B8pgK2a7.js +2 -0
- package/server/{deacon-swarm-BstYoz8Q.js → deacon-swarm-DYYOzHRH.js} +10 -10
- package/server/deacon.js +7 -7
- package/server/{devcontainer-renderer-CaYOG9JN.js → devcontainer-renderer-Dx0C7VWJ.js} +2 -2
- package/server/dist-BYSj075e.js +3 -0
- package/server/{dist-BIC8spJ0.js → dist-ya66D9ze.js} +1 -1
- package/server/{docker-DDI1shu3.js → docker-D2TbXDO6.js} +2 -2
- package/server/{ensure-devcontainer-CbSXvN9W.js → ensure-devcontainer-BIShzs3M.js} +1 -1
- package/server/{ensure-devcontainer-DCAg3ksz.js → ensure-devcontainer-BTiMfFp5.js} +3 -3
- package/server/{event-store-DU3HkZlN.js → event-store-C02Gprug.js} +1 -1
- package/server/{event-store-DCMqyI07.js → event-store-C71x6h0i.js} +3 -3
- package/server/{events-BfiosVAn.js → events-BFDuPV6n.js} +3 -3
- package/server/{events-DLRxpSuV.js → events-D2gP2-Yv.js} +1 -1
- package/server/{factory-BMtWV9iv.js → factory-ByfbrCab.js} +4 -4
- package/server/{feature-registry-population-DA7bO6Zn.js → feature-registry-population-CSH66UO8.js} +4 -4
- package/server/{feature-registry-storage-nAY1fbBt.js → feature-registry-storage-DW7dUefc.js} +2 -2
- package/server/{features-DMu7eGmU.js → features-x_aLR7U9.js} +2 -2
- package/server/{feedback-target-DHqcw3cN.js → feedback-target-BFo9fScH.js} +6 -6
- package/server/{feedback-target-B2SEai8-.js → feedback-target-C4VMPTZu.js} +1 -1
- package/server/{feedback-writer-qvm7UON7.js → feedback-writer-B9ZqnnX3.js} +4 -4
- package/server/{feedback-writer-DMBdS_jL.js → feedback-writer-DCyd75yJ.js} +1 -1
- package/server/{flywheel-Dfpx4bIV.js → flywheel-D9NKvWqb.js} +11 -11
- package/server/{flywheel-k4FPtCQ_.js → flywheel-DB48X7Nn.js} +1 -1
- package/server/{flywheel-D2LdPSic.js → flywheel-DwQCoZQG.js} +13 -13
- package/server/{flywheel-merge-order-CB4lbXeJ.js → flywheel-merge-order-DrjmFXg4.js} +6 -6
- package/server/{flywheel-merge-order-CttTazRY.js → flywheel-merge-order-f1bi5XIf.js} +1 -1
- package/server/{forge-CoPFXFyl.js → forge-C-SoKy8U.js} +2 -2
- package/server/forge-CUCYK7h_.js +2 -0
- package/server/{from-B0YoJl9-.js → from-fu7ZumnY.js} +1 -1
- package/server/{fts-db-DAHW1yA_.js → fts-db-CL-YzSRG.js} +2 -2
- package/server/{fts-operations-DDXDBi0d.js → fts-operations-Bylu5NjM.js} +2 -2
- package/server/{git-utils-B90Zjzfl.js → git-utils-C8koBs8j.js} +2 -2
- package/server/{git-utils-DbfC0zls.js → git-utils-Ym380hJZ.js} +1 -1
- package/server/{github-app-C7lftMNj.js → github-app-DIr70YLg.js} +1 -1
- package/server/{github-app-BecAnXRX.js → github-app-DhycHVLF.js} +2 -2
- package/server/{github-client-D21rCIXk.js → github-client-7gkcXcBS.js} +2 -2
- package/server/{github-client-BwtKZtM0.js → github-client-Ji9lUTNF.js} +1 -1
- package/server/{github-pr-lookup-CzVWuysl.js → github-pr-lookup-rt7T9FwA.js} +2 -2
- package/server/{harness-resolve-SkRJm4a7.js → harness-resolve-CvGXUnAF.js} +3 -3
- package/server/{health-events-BT0aAfeA.js → health-events-D4E1DLN-.js} +2 -2
- package/server/{health-events-DA_JLpgZ.js → health-events-DDfkoO5a.js} +1 -1
- package/server/{hooks-DxEn2BoY.js → hooks-D_6pAbuJ.js} +2 -2
- package/server/{hooks-BQxRXtRb.js → hooks-DvdnCVv6.js} +1 -1
- package/server/{idle-stack-reaper-C7fJL8jH.js → idle-stack-reaper-DNbV9-fj.js} +1 -1
- package/server/{idle-stack-reaper-DGy7tvC9.js → idle-stack-reaper-cDRXDpgE.js} +2 -2
- package/server/{infra-DE3C4PMr.js → infra-Clew3DpF.js} +50 -3
- package/server/{injection-Dt22tGvS.js → injection-QCIHOpk6.js} +1 -1
- package/server/{injection-Cc4Qt8LZ.js → injection-ql0Nddcn.js} +7 -7
- package/server/inspect-agent-D8UBDgfR.js +2 -0
- package/server/{inspect-agent-DOaCDkCF.js → inspect-agent-DY18KEx4.js} +9 -9
- package/server/{internal-token-B_1YLWLI.js → internal-token-BeXjPLKy.js} +2 -2
- package/server/{internal-token-nobT6FhA.js → internal-token-C90jBp-y.js} +1 -1
- package/server/{io-A7ZuOBkz.js → io-CbAFXQge.js} +2 -2
- package/server/{issue-closed-Da3DeomA.js → issue-closed-BYC2TCk-.js} +4 -4
- package/server/{issue-lifecycle-543pQX9D.js → issue-lifecycle-BoJa6qIt.js} +21 -21
- package/server/{issue-lifecycle-Bd9XBJAQ.js → issue-lifecycle-CLHbj8RS.js} +1 -1
- package/server/{issue-service-singleton-BC2JLZi7.js → issue-service-singleton-3g_CCY-1.js} +1 -1
- package/server/{issue-service-singleton-Brc3t0SV.js → issue-service-singleton-BS9NENuR.js} +8 -8
- package/server/{issues-D3kt9UWE.js → issues-BPcqPqfV.js} +2 -2
- package/server/{label-cleanup-w9X_T3AQ.js → label-cleanup-CwiCSZrb.js} +2 -2
- package/server/{layers-Cwe1J-9o.js → layers-OHHLphl0.js} +2 -2
- package/server/{lifecycle-f1GbDK33.js → lifecycle-B8IdvUbt.js} +6 -6
- package/server/{lifecycle-io-D56q8zlO.js → lifecycle-io-BMCH1Bxg.js} +1 -1
- package/server/{lifecycle-io-Bqvj0m3i.js → lifecycle-io-C73m5e6R.js} +4 -4
- package/server/{linear-client-Bh4T4S-c.js → linear-client-BMxgycoy.js} +1 -1
- package/server/{linear-client-CfJMTAYd.js → linear-client-CgQlM4aT.js} +2 -2
- package/server/memory-fts-worker.js +1 -1
- package/server/{merge-ZrXFM3jv.js → merge-B7Oi-g6k.js} +3 -3
- package/server/{merge-DRMPa8CQ.js → merge-BRkVaIx-.js} +1 -1
- package/server/merge-agent-Bwm34MZ5.js +3 -0
- package/server/{merge-agent-CHHs5S_0.js → merge-agent-DNFFsiEm.js} +61 -26
- package/server/{merge-batch-BwhGdlh4.js → merge-batch-BpYXUaqJ.js} +1 -1
- package/server/{merge-blockers-BMhDpMl_.js → merge-blockers-DIOZmqdK.js} +2 -2
- package/server/merge-ops-B7m-hBEZ.js +2 -0
- package/server/{merge-rebase-R7MfqpiE.js → merge-rebase-C8WH39MB.js} +2 -2
- package/server/{merge-set-B3Breacq.js → merge-set-CsEE2CyQ.js} +9 -5
- package/server/{merge-set-D-iXNP0I.js → merge-set-DOsBTBQc.js} +1 -1
- package/server/{merge-sync-DHWbGtjR.js → merge-sync-C2B322Ot.js} +2 -2
- package/server/{merge-sync-bOOtfeXR.js → merge-sync-CcLM2vwW.js} +1 -1
- package/server/{merge-train-98ptZrPT.js → merge-train-BIm7-7cy.js} +3 -3
- package/server/{merge-train-deps-B5FWJTE2.js → merge-train-deps-DG1oQ2DT.js} +4 -4
- package/server/{merge-verification-BGiuiXAK.js → merge-verification-BAbjxEd5.js} +4 -4
- package/server/{multipart-parser-D7mzNNDm.js → multipart-parser-jKtKAXtl.js} +2 -2
- package/server/node_modules/.package-lock.json +1 -1
- package/server/{orphan-proposed-reconciler-CSuxivr9.js → orphan-proposed-reconciler-BqVGaOIj.js} +1 -1
- package/server/{orphan-proposed-reconciler-BSGCNJ6X.js → orphan-proposed-reconciler-zHrwJ3xr.js} +9 -9
- package/server/package-lock.json +2 -2
- package/server/package.json +1 -1
- package/server/{pan-dir-DoPtdHrp.js → pan-dir-BlNrLzW7.js} +3 -3
- package/server/{paths-CVDLPxXp.js → paths-CiVeo4N_.js} +2 -2
- package/server/{pipeline-notifier-WD2dnOsR.js → pipeline-notifier-DBF6dHxH.js} +2 -2
- package/server/{pipeline-notifier-BtYHbBPl.js → pipeline-notifier-nkWCe042.js} +1 -1
- package/server/{projects-Bqfwuqet.js → projects-BqRMlDOv.js} +1 -1
- package/server/{prompts-Cg7e9FPb.js → prompts-CXbQvevY.js} +2 -2
- package/server/{providers-fLduChnX.js → providers-CUdAb5Qu.js} +7 -7
- package/server/{providers-JjK6izqX.js → providers-gZ0cUgQM.js} +1 -1
- package/server/public/assets/{chunk-BO2N2NFS-aNlsGDCu.js → chunk-BO2N2NFS-C-6y8VFI.js} +4 -4
- package/server/public/assets/{diagram-OG6HWLK6-CnjkrvT8.js → diagram-OG6HWLK6-NACO1xW5.js} +1 -1
- package/server/public/assets/{dist-CGKBCieA.js → dist-D1FjqGgm.js} +1 -1
- package/server/public/assets/{dist-Bg3Sbsx4.js → dist-DQD-ZjQY.js} +3 -3
- package/server/public/assets/{ganttDiagram-6RSMTGT7-C8LLKj23.js → ganttDiagram-6RSMTGT7-CEMAKgmE.js} +1 -1
- package/server/public/assets/{highlighted-body-OFNGDK62-BSDEUvhW.js → highlighted-body-OFNGDK62-D5lC7FW8.js} +1 -1
- package/server/public/assets/{index-LQIfhmgI.js → index-BDqWyuZQ.js} +140 -140
- package/server/public/assets/{linear-DEbYZoxg.js → linear-DDHAMRPB.js} +1 -1
- package/server/public/assets/mermaid-GHXKKRXX-DRM2ANcR.js +1 -0
- package/server/public/assets/{pieDiagram-4H26LBE5-BtZL5pXE.js → pieDiagram-4H26LBE5-DwBdeKBb.js} +1 -1
- package/server/public/assets/{prod-DYVvIyuh.js → prod-DAZt0c4G.js} +4 -4
- package/server/public/assets/{quadrantDiagram-W4KKPZXB-a7vS8Jc8.js → quadrantDiagram-W4KKPZXB-CNDLp-_m.js} +1 -1
- package/server/public/assets/{sankeyDiagram-5OEKKPKP-Ckbq2SWH.js → sankeyDiagram-5OEKKPKP-oIUL_uO1.js} +1 -1
- package/server/public/assets/{xychartDiagram-2RQKCTM6-DJyeNa99.js → xychartDiagram-2RQKCTM6-B-IM6Cck.js} +1 -1
- package/server/public/index.html +1 -1
- package/server/{pull-requests-D2SjJIwY.js → pull-requests-DZ-V7_LB.js} +1 -1
- package/server/{pull-requests-1WXmTBlo.js → pull-requests-kTRl7cWC.js} +3 -3
- package/server/rally-CRteqXe0.js +3 -0
- package/server/{rally-api-H5I2CBO0.js → rally-api-DpZo3WwF.js} +2 -2
- package/server/{rally-api-BtvwdX6d.js → rally-api-jOk4vit_.js} +1 -1
- package/server/{rally-client-Bt-qHiOh.js → rally-client-BQjm3aaq.js} +1 -1
- package/server/{rally-client-33CYZYYu.js → rally-client-CoWZeVEj.js} +4 -4
- package/server/{rally-8aQlLLVj.js → rally-xCTZ-UcI.js} +3 -3
- package/server/{rebuild-stack-KHkkrGjx.js → rebuild-stack-BZ7j27xY.js} +5 -5
- package/server/{rebuild-stack-CXSgAGPR.js → rebuild-stack-r-cxcr26.js} +1 -1
- package/server/{reconstruct-cache-D_Ml65_6.js → reconstruct-cache-13EkMg_i.js} +6 -6
- package/server/{record-De2syxNB.js → record-CJsjvRO4.js} +1 -1
- package/server/release-engine-YLTnEO_K.js +306 -0
- package/server/release-set-DwvT_Opu.js +125 -0
- package/server/{remote-CGuognN3.js → remote-9u9WL0NJ.js} +3 -3
- package/server/{remote-D0Old0Cv.js → remote-B062S1Kc.js} +3 -3
- package/server/{remote-agents-UHoi4oGr.js → remote-agents-B4eWgwNd.js} +1 -1
- package/server/{remote-agents-DfhMq6kQ.js → remote-agents-BBTg-vLT.js} +5 -5
- package/server/{remote-completion-RZYePR_0.js → remote-completion-B0rmBnbL.js} +12 -12
- package/server/resume-R7xkSmLW.js +3 -0
- package/server/{review-agent-uwVftH98.js → review-agent-Bf9XjLnl.js} +1 -1
- package/server/{review-agent-sZBCpEVv.js → review-agent-tfR5RDzG.js} +22 -22
- package/server/{review-artifacts-C7a45b1_.js → review-artifacts-D0_IE6DQ.js} +6 -6
- package/server/{review-context-CErbQpKL.js → review-context-CXO5ml5D.js} +7 -7
- package/server/{review-monitor-DHD9m_kh.js → review-monitor-DfFUSx0J.js} +2 -2
- package/server/{review-status-JhQ3qlDS.js → review-status-B0Axo-Ma.js} +1 -1
- package/server/{review-status-gPndPUBc.js → review-status-BcfwcAcO.js} +23 -16
- package/server/{review-status-enrichment-qWvWckYN.js → review-status-enrichment-Cp5kFTGl.js} +3 -3
- package/server/{review-status-sync-B_GrEke8.js → review-status-sync-Bc0c_u12.js} +1 -1
- package/server/{review-status-sync-D3A9KX95.js → review-status-sync-CEFty1pW.js} +9 -5
- package/server/{review-verdict-feedback-4WZ5ZdbC.js → review-verdict-feedback-BOKRLLDf.js} +6 -6
- package/server/{review-verdict-feedback-Zo0r_z4m.js → review-verdict-feedback-BnGtT0vN.js} +1 -1
- package/server/{rollup-Bmhn80tB.js → rollup-BVqFuiS9.js} +1 -1
- package/server/{rollup-BrUIKMv_.js → rollup-C22QDVxh.js} +8 -8
- package/server/{scanner-BIGumel_.js → scanner-BLCnuXqY.js} +7 -7
- package/server/{sequence-io-DA0akEQN.js → sequence-io-BozlUb6f.js} +4 -4
- package/server/{sequencer-agent-DeflKafB.js → sequencer-agent-BEDEHfd2.js} +5 -5
- package/server/server.js +251 -160
- package/server/{service-V0sUK-OR.js → service-C8jKYZ0w.js} +42 -42
- package/server/{session-fork-Yx3UcAQf.js → session-fork-BDGksK4e.js} +2 -2
- package/server/{settings-CFEeQRj2.js → settings-CSLhh6qR.js} +2 -2
- package/server/settings-CSaXtkoA.js +2 -0
- package/server/{settings-CxVate0h.js → settings-DrRQCren.js} +3 -3
- package/server/{shadow-engineering-BRxnVSDk.js → shadow-engineering-BMcBN-Of.js} +2 -2
- package/server/{shadow-state-BiMwdyl1.js → shadow-state-B4ayC7nx.js} +2 -2
- package/server/{shadow-state-1aS8FijU.js → shadow-state-CHqWHD3T.js} +1 -1
- package/server/{ship-log-CMWH3H5-.js → ship-log-CDVmOlZP.js} +1 -1
- package/server/{smart-compaction-Bw1K_Sbl.js → smart-compaction-C-F5sP9P.js} +7 -7
- package/server/{smart-compaction-xblMyVpT.js → smart-compaction-V9DBrGcr.js} +1 -1
- package/server/{spawn-planning-session-CzDFSKAQ.js → spawn-planning-session-Bz4_JES2.js} +1 -1
- package/server/{spawn-planning-session-DxLxcM7m.js → spawn-planning-session-dSpOU0lP.js} +15 -15
- package/server/{specialist-completion-BiEXUHfD.js → specialist-completion-Coz3eTSr.js} +1 -1
- package/server/{specialist-context-BfkTA81h.js → specialist-context-fmb03vXQ.js} +5 -5
- package/server/{specialist-handoff-logger-BiKuLtNm.js → specialist-handoff-logger-B9HK_lRu.js} +2 -2
- package/server/{specialist-logs-CFJjGsLC.js → specialist-logs-B24R2B9Y.js} +3 -3
- package/server/{specialists-BLIreoE7.js → specialists-B6f5-5nG.js} +1 -1
- package/server/{specialists-DjHbwQ_p.js → specialists-BGNZ0_ZW.js} +18 -18
- package/server/{specialists-B4byf18k.js → specialists-CL7E8yUg.js} +75 -75
- package/server/{specialists-DdvpFNt1.js → specialists-Chvvwf5z.js} +1 -1
- package/server/specs-oaaYaA5c.js +3 -0
- package/server/{src-CALBEUMX.js → src-BVxZuHMg.js} +3 -3
- package/server/{src-mK_M_ZIj.js → src-D-VXCC9C.js} +5 -5
- package/server/{src-fgbU-jJ-.js → src-DGAEtABV.js} +13 -2
- package/server/{stack-health-BFM8YlHw.js → stack-health-BKbTTOT2.js} +1 -1
- package/server/{stack-health-Qd7rPiVe.js → stack-health-C-le5erA.js} +2 -2
- package/server/{state-home-C2Bicw9N.js → state-home-CoyWsECg.js} +6 -6
- package/server/{summary-fork-EWWBvzgE.js → summary-fork-BF_0vxrq.js} +2 -2
- package/server/{summary-fork-DezPkJSm.js → summary-fork-BlE30Q-I.js} +8 -8
- package/server/supervisor-03Tj_Wra.js +2 -0
- package/server/{supervisor-D5GC5jUM.js → supervisor-BSeML_p0.js} +2 -2
- package/server/{swarm-readiness-Cf9mommA.js → swarm-readiness-CQ4sjrXc.js} +3 -3
- package/server/{system-prerequisites-BsI9hxh9.js → system-prerequisites-DnmLYOnU.js} +13 -1
- package/server/{teardown-workspace-CWz1pbEK.js → teardown-workspace-CpcjDrZ4.js} +12 -12
- package/server/{test-agent-queue-TiVXe3ZT.js → test-agent-queue-Bq298zZl.js} +5 -5
- package/server/{tldr-daemon-C_1frSDj.js → tldr-daemon-CaSUXd9r.js} +2 -2
- package/server/{tldr-daemon-Big5IZ_M.js → tldr-daemon-Rn1Zue0P.js} +1 -1
- package/server/{tmux-D57f-XNv.js → tmux-BVc-NeKM.js} +2 -2
- package/server/{tmux-DClqWwb8.js → tmux-CbcrkUx5.js} +1 -1
- package/server/{token-_qujO3JZ.js → token-D2OC_9ms.js} +1 -1
- package/server/{tracker-config-BmdJU_w6.js → tracker-config-C1Y_2CIo.js} +2 -2
- package/server/{tracker-config-B_Ubts1Z.js → tracker-config-EsBoOWEy.js} +1 -1
- package/server/{tracker-utils-Bg-Sevcv.js → tracker-utils-DSb9cfrF.js} +1 -1
- package/server/{tracker-utils-CWFt6jjV.js → tracker-utils-lph9C3Pw.js} +2 -2
- package/server/{tunnel-UBf2vyAL.js → tunnel-BFBCeo97.js} +2 -2
- package/server/{tunnel-hOBNQbmF.js → tunnel-DDe-ATFF.js} +1 -1
- package/server/{uat-train-DGkIkLxu.js → uat-train-8T24rPzc.js} +1 -1
- package/server/{uat-train-qNanv5hN.js → uat-train-DtKHHy1U.js} +23 -23
- package/server/{validation-B0QaLIHP.js → validation-COOXwNXU.js} +1 -1
- package/server/{validation-C4wegCvH.js → validation-lyudzPvD.js} +3 -3
- package/server/{verification-runner-CntudBes.js → verification-runner-DO0nYTJx.js} +11 -11
- package/server/{verification-runner-DdleMNLq.js → verification-runner-Dd1FX8Qk.js} +1 -1
- package/server/{vtt-parser-CKc-smWM.js → vtt-parser-C57tEF7-.js} +1 -1
- package/server/{work-agent-prompt-CG5jFV9A.js → work-agent-prompt-B2F3ZwKm.js} +11 -11
- package/server/workflows-B4fIN8VP.js +2 -0
- package/server/{workflows-BK2xs_a8.js → workflows-Clz_AFwR.js} +13 -13
- package/server/{workspace-CLrSIRCx.js → workspace-CY_2-Ddv.js} +3 -3
- package/server/{workspace-C9lC5o3E.js → workspace-D0rCFemz.js} +1 -1
- package/server/{workspace-manager-Cb2a7bg9.js → workspace-manager-BIfpzqdU.js} +1 -1
- package/server/{workspace-manager-BJhW2sky.js → workspace-manager-D6NgGkiF.js} +10 -10
- package/server/{workspace-metadata-QQmo_Z0l.js → workspace-metadata-C8yn8kAH.js} +2 -2
- package/server/{workspace-metadata-CTRnr323.js → workspace-metadata-DfgR40YU.js} +1 -1
- package/server/{workspaces-Ca4zZs12.js → workspaces-DOeHyKmA.js} +104 -92
- package/sync-sources/hooks/ask-user-question-hook +87 -0
- package/sync-sources/hooks/auto-approve-hook +96 -0
- package/sync-sources/hooks/codex-notify-hook +62 -0
- package/sync-sources/hooks/gh-issue-trailer-hook +4 -0
- package/sync-sources/hooks/gh-issue-trailer-hook.d.ts +7 -0
- package/sync-sources/hooks/gh-issue-trailer-hook.d.ts.map +1 -0
- package/sync-sources/hooks/gh-issue-trailer-hook.js +239 -0
- package/sync-sources/hooks/gh-issue-trailer-hook.js.map +1 -0
- package/sync-sources/hooks/gh-issue-trailer-hook.ts +296 -0
- package/sync-sources/hooks/git-hooks/post-checkout +109 -0
- package/sync-sources/hooks/heartbeat-hook +113 -0
- package/sync-sources/hooks/notification-hook +70 -0
- package/sync-sources/hooks/notify-complete +79 -0
- package/sync-sources/hooks/pan-hook-lib.sh +153 -0
- package/sync-sources/hooks/permission-event-hook +24 -0
- package/sync-sources/hooks/post-compact-hook +25 -0
- package/sync-sources/hooks/pre-compact-hook +26 -0
- package/sync-sources/hooks/pre-tool-hook +35 -0
- package/sync-sources/hooks/record-cost-event.d.ts +1 -0
- package/sync-sources/hooks/record-cost-event.js +31858 -0
- package/sync-sources/hooks/record-cost-event.js.map +1 -0
- package/sync-sources/hooks/record-cost-event.ts +267 -0
- package/sync-sources/hooks/rtk-bash-filter +152 -0
- package/sync-sources/hooks/session-start-hook +80 -0
- package/sync-sources/hooks/specialist-stop-hook +106 -0
- package/sync-sources/hooks/statusline.sh +169 -0
- package/sync-sources/hooks/stop-hook +217 -0
- package/sync-sources/hooks/tldr-post-edit +72 -0
- package/sync-sources/hooks/tldr-read-enforcer +340 -0
- package/sync-sources/hooks/tmux-send-keys-guard +87 -0
- package/sync-sources/hooks/tsdown.config.ts +14 -0
- package/sync-sources/hooks/user-prompt-submit-hook +57 -0
- package/sync-sources/hooks/work-agent-stop-hook +424 -0
- package/server/cli-Ch_BWJB2.js +0 -3
- package/server/deacon-swarm-CSWGRfzr.js +0 -2
- package/server/dist-BUplB3Od.js +0 -3
- package/server/forge-IgLR0Ist.js +0 -2
- package/server/inspect-agent-CYoWAeeB.js +0 -2
- package/server/merge-agent-CfohxFWM.js +0 -3
- package/server/merge-ops-Czke6UAp.js +0 -2
- package/server/public/assets/mermaid-GHXKKRXX-CZOsRYoT.js +0 -1
- package/server/rally-Ba9QoNYc.js +0 -3
- package/server/resume-CmxF3Kjz.js +0 -3
- package/server/settings-DEhYpW0k.js +0 -2
- package/server/specs-CVH1kvz3.js +0 -3
- package/server/supervisor-DQMK2e3I.js +0 -2
- package/server/workflows-pKw9yRSV.js +0 -2
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { homedir, tmpdir } from 'node:os';
|
|
4
|
+
import { basename, join } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
|
|
7
|
+
interface PreToolUseEvent {
|
|
8
|
+
tool_name?: string;
|
|
9
|
+
tool_input?: {
|
|
10
|
+
command?: string;
|
|
11
|
+
[key: string]: unknown;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Token {
|
|
16
|
+
text: string;
|
|
17
|
+
start: number;
|
|
18
|
+
end: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
type HookEnv = Record<string, string | undefined>;
|
|
22
|
+
|
|
23
|
+
const FLYWHEEL_RUN_ID_PATTERN = /^RUN-\d+$/;
|
|
24
|
+
|
|
25
|
+
function shellQuote(value: string): string {
|
|
26
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function tokenize(command: string): Token[] {
|
|
30
|
+
const tokens: Token[] = [];
|
|
31
|
+
let text = '';
|
|
32
|
+
let start = -1;
|
|
33
|
+
let quote: 'single' | 'double' | null = null;
|
|
34
|
+
let escape = false;
|
|
35
|
+
|
|
36
|
+
const flush = (end: number): void => {
|
|
37
|
+
if (start !== -1) tokens.push({ text, start, end });
|
|
38
|
+
text = '';
|
|
39
|
+
start = -1;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
43
|
+
const char = command[index];
|
|
44
|
+
if (start === -1 && !/\s/.test(char)) start = index;
|
|
45
|
+
|
|
46
|
+
if (escape) {
|
|
47
|
+
text += char;
|
|
48
|
+
escape = false;
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (quote === 'single') {
|
|
53
|
+
if (char === "'") quote = null;
|
|
54
|
+
else text += char;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (quote === 'double') {
|
|
59
|
+
if (char === '"') quote = null;
|
|
60
|
+
else if (char === '\\') escape = true;
|
|
61
|
+
else text += char;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (/\s/.test(char)) {
|
|
66
|
+
flush(index);
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (char === "'") {
|
|
71
|
+
quote = 'single';
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (char === '"') {
|
|
76
|
+
quote = 'double';
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (char === '\\') {
|
|
81
|
+
escape = true;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
text += char;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
flush(command.length);
|
|
89
|
+
return tokens;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function findGhIssueCreate(tokens: Token[]): number {
|
|
93
|
+
for (let index = 0; index <= tokens.length - 3; index += 1) {
|
|
94
|
+
if (tokens[index].text === 'gh' && tokens[index + 1].text === 'issue' && tokens[index + 2].text === 'create') {
|
|
95
|
+
return index;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
return -1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function containsFlywheelRunId(body: string): boolean {
|
|
102
|
+
return /^Flywheel-Run-Id:/m.test(body);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function resolveDiscoveredIn(env: HookEnv): string | null {
|
|
106
|
+
const agentId = env.OVERDECK_AGENT_ID;
|
|
107
|
+
if (!agentId || basename(agentId) !== agentId) return null;
|
|
108
|
+
|
|
109
|
+
const overdeckHome = env.OVERDECK_HOME ?? join(env.HOME ?? homedir(), '.overdeck');
|
|
110
|
+
const statePath = join(overdeckHome, 'agents', agentId, 'state.json');
|
|
111
|
+
if (!existsSync(statePath)) return null;
|
|
112
|
+
|
|
113
|
+
try {
|
|
114
|
+
const parsed = JSON.parse(readFileSync(statePath, 'utf-8')) as { issueId?: unknown };
|
|
115
|
+
return typeof parsed.issueId === 'string' && parsed.issueId.trim() ? parsed.issueId.trim() : null;
|
|
116
|
+
} catch {
|
|
117
|
+
return null;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function buildTrailer(env: HookEnv): string | null {
|
|
122
|
+
const runId = env.OVERDECK_FLYWHEEL_RUN_ID?.trim();
|
|
123
|
+
if (!runId || !FLYWHEEL_RUN_ID_PATTERN.test(runId)) return null;
|
|
124
|
+
|
|
125
|
+
const filedBy = env.OVERDECK_FLYWHEEL_AGENT_ROLE === 'flywheel' ? 'agent' : 'operator';
|
|
126
|
+
const discoveredIn = resolveDiscoveredIn(env);
|
|
127
|
+
const lines = [
|
|
128
|
+
'---',
|
|
129
|
+
`Flywheel-Run-Id: ${runId}`,
|
|
130
|
+
`Flywheel-Filed-By: ${filedBy}`,
|
|
131
|
+
discoveredIn ? `Flywheel-Discovered-In: ${discoveredIn}` : undefined,
|
|
132
|
+
].filter((line): line is string => line !== undefined);
|
|
133
|
+
return `\n\n${lines.join('\n')}\n`;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function replaceSpan(command: string, start: number, end: number, replacement: string): string {
|
|
137
|
+
return `${command.slice(0, start)}${replacement}${command.slice(end)}`;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function findFlag(tokens: Token[], ghIndex: number, flagName: '--body' | '--body-file'): { flag: Token; value?: Token; inline: boolean } | null {
|
|
141
|
+
for (let index = ghIndex + 3; index < tokens.length; index += 1) {
|
|
142
|
+
const token = tokens[index];
|
|
143
|
+
if (token.text === flagName) return { flag: token, value: tokens[index + 1], inline: false };
|
|
144
|
+
if (token.text.startsWith(`${flagName}=`)) return { flag: token, inline: true };
|
|
145
|
+
}
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function inlineFlagValue(flag: Token): { value: string; valueStart: number; valueEnd: number } | null {
|
|
150
|
+
const equalsOffset = flag.text.indexOf('=');
|
|
151
|
+
if (equalsOffset === -1) return null;
|
|
152
|
+
const valueStart = flag.start + equalsOffset + 1;
|
|
153
|
+
return { value: flag.text.slice(equalsOffset + 1), valueStart, valueEnd: flag.end };
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function appendBodyFlag(command: string, tokens: Token[], ghIndex: number, trailer: string): string | null {
|
|
157
|
+
const bodyFlag = findFlag(tokens, ghIndex, '--body');
|
|
158
|
+
if (!bodyFlag) return null;
|
|
159
|
+
|
|
160
|
+
const body = bodyFlag.inline ? inlineFlagValue(bodyFlag.flag) : bodyFlag.value;
|
|
161
|
+
if (!body) return null;
|
|
162
|
+
const bodyText = 'text' in body ? body.text : body.value;
|
|
163
|
+
if (containsFlywheelRunId(bodyText)) return command;
|
|
164
|
+
|
|
165
|
+
const appended = `${bodyText}${trailer}`;
|
|
166
|
+
if ('text' in body) return replaceSpan(command, body.start, body.end, shellQuote(appended));
|
|
167
|
+
return replaceSpan(command, body.valueStart, body.valueEnd, shellQuote(appended));
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function writeTempBodyFile(body: string): string {
|
|
171
|
+
const dir = mkdtempSync(join(tmpdir(), 'pan-gh-issue-body-'));
|
|
172
|
+
const path = join(dir, 'body.md');
|
|
173
|
+
writeFileSync(path, body, 'utf-8');
|
|
174
|
+
return path;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function appendBodyFileFlag(command: string, tokens: Token[], ghIndex: number, trailer: string): string | null {
|
|
178
|
+
const bodyFileFlag = findFlag(tokens, ghIndex, '--body-file');
|
|
179
|
+
if (!bodyFileFlag) return null;
|
|
180
|
+
|
|
181
|
+
const pathValue = bodyFileFlag.inline ? inlineFlagValue(bodyFileFlag.flag) : bodyFileFlag.value;
|
|
182
|
+
if (!pathValue) return null;
|
|
183
|
+
const pathText = 'text' in pathValue ? pathValue.text : pathValue.value;
|
|
184
|
+
|
|
185
|
+
if (pathText === '-') return appendStdinBody(command, tokens[ghIndex].start, trailer);
|
|
186
|
+
if (!existsSync(pathText)) return null;
|
|
187
|
+
|
|
188
|
+
const body = readFileSync(pathText, 'utf-8');
|
|
189
|
+
if (containsFlywheelRunId(body)) return command;
|
|
190
|
+
|
|
191
|
+
const tempPath = writeTempBodyFile(`${body}${trailer}`);
|
|
192
|
+
if ('text' in pathValue) return replaceSpan(command, pathValue.start, pathValue.end, shellQuote(tempPath));
|
|
193
|
+
return replaceSpan(command, pathValue.valueStart, pathValue.valueEnd, shellQuote(tempPath));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function findPipeBefore(command: string, beforeIndex: number): number {
|
|
197
|
+
let quote: 'single' | 'double' | null = null;
|
|
198
|
+
let escape = false;
|
|
199
|
+
let pipeIndex = -1;
|
|
200
|
+
|
|
201
|
+
for (let index = 0; index < beforeIndex; index += 1) {
|
|
202
|
+
const char = command[index];
|
|
203
|
+
if (escape) {
|
|
204
|
+
escape = false;
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (quote === 'single') {
|
|
208
|
+
if (char === "'") quote = null;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
if (quote === 'double') {
|
|
212
|
+
if (char === '"') quote = null;
|
|
213
|
+
else if (char === '\\') escape = true;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (char === "'") quote = 'single';
|
|
217
|
+
else if (char === '"') quote = 'double';
|
|
218
|
+
else if (char === '\\') escape = true;
|
|
219
|
+
else if (char === '|' && command[index + 1] !== '|' && command[index - 1] !== '|') pipeIndex = index;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return pipeIndex;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function appendHereDoc(command: string, trailer: string): string | null {
|
|
226
|
+
const match = command.match(/(<<-?\s*['"]?([A-Za-z_][A-Za-z0-9_-]*)['"]?[^\n]*\n)([\s\S]*?)(\n\2\s*)$/);
|
|
227
|
+
if (!match || match.index === undefined) return null;
|
|
228
|
+
const body = match[3];
|
|
229
|
+
if (containsFlywheelRunId(body)) return command;
|
|
230
|
+
const bodyStart = match.index + match[1].length;
|
|
231
|
+
const bodyEnd = bodyStart + body.length;
|
|
232
|
+
return replaceSpan(command, bodyStart, bodyEnd, `${body}${trailer}`);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function appendStdinBody(command: string, ghStart: number, trailer: string): string | null {
|
|
236
|
+
if (command.includes('Flywheel-Run-Id:')) return command;
|
|
237
|
+
|
|
238
|
+
const hereDoc = appendHereDoc(command, trailer);
|
|
239
|
+
if (hereDoc) return hereDoc;
|
|
240
|
+
|
|
241
|
+
const pipeIndex = findPipeBefore(command, ghStart);
|
|
242
|
+
if (pipeIndex === -1) return null;
|
|
243
|
+
|
|
244
|
+
const producer = command.slice(0, pipeIndex).trim();
|
|
245
|
+
const consumer = command.slice(pipeIndex + 1).trimStart();
|
|
246
|
+
if (!producer || !consumer) return null;
|
|
247
|
+
return `{ ${producer}; printf %s ${shellQuote(trailer)}; } | ${consumer}`;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function buildUpdatedInput(command: string): Record<string, unknown> {
|
|
251
|
+
return {
|
|
252
|
+
hookSpecificOutput: {
|
|
253
|
+
hookEventName: 'PreToolUse',
|
|
254
|
+
permissionDecisionReason: 'Flywheel issue provenance trailers added',
|
|
255
|
+
updatedInput: { command },
|
|
256
|
+
},
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
export function rewriteGhIssueCreateCommand(command: string, env: HookEnv = process.env): string | null {
|
|
261
|
+
const trailer = buildTrailer(env);
|
|
262
|
+
if (!trailer) return null;
|
|
263
|
+
|
|
264
|
+
const tokens = tokenize(command);
|
|
265
|
+
const ghIndex = findGhIssueCreate(tokens);
|
|
266
|
+
if (ghIndex === -1) return null;
|
|
267
|
+
|
|
268
|
+
const bodyCommand = appendBodyFlag(command, tokens, ghIndex, trailer);
|
|
269
|
+
if (bodyCommand !== null) return bodyCommand === command ? null : bodyCommand;
|
|
270
|
+
|
|
271
|
+
const bodyFileCommand = appendBodyFileFlag(command, tokens, ghIndex, trailer);
|
|
272
|
+
if (bodyFileCommand !== null) return bodyFileCommand === command ? null : bodyFileCommand;
|
|
273
|
+
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
export function runGhIssueTrailerHook(input: string, env: HookEnv = process.env): string {
|
|
278
|
+
let event: PreToolUseEvent;
|
|
279
|
+
try {
|
|
280
|
+
event = JSON.parse(input) as PreToolUseEvent;
|
|
281
|
+
} catch {
|
|
282
|
+
return '{}';
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const command = event.tool_name === 'Bash' ? event.tool_input?.command : undefined;
|
|
286
|
+
if (typeof command !== 'string') return '{}';
|
|
287
|
+
|
|
288
|
+
const updatedCommand = rewriteGhIssueCreateCommand(command, env);
|
|
289
|
+
if (!updatedCommand) return '{}';
|
|
290
|
+
return JSON.stringify(buildUpdatedInput(updatedCommand));
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]) {
|
|
294
|
+
const input = readFileSync(0, 'utf-8');
|
|
295
|
+
process.stdout.write(runGhIssueTrailerHook(input));
|
|
296
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
#
|
|
3
|
+
# Git post-checkout hook to guard main project directories
|
|
4
|
+
# Prevents accidental checkouts away from main in the primary project directory
|
|
5
|
+
#
|
|
6
|
+
# Install: ln -sf /path/to/overdeck/scripts/git-hooks/post-checkout .git/hooks/post-checkout
|
|
7
|
+
# Or: pan projects add /path/to/project (auto-installs hooks)
|
|
8
|
+
#
|
|
9
|
+
# This hook only activates in the MAIN project directory (not worktrees).
|
|
10
|
+
# Worktrees can freely checkout any branch - that's their purpose.
|
|
11
|
+
#
|
|
12
|
+
# Auto-revert behavior:
|
|
13
|
+
# - Agents (OVERDECK_AGENT=1): Always auto-revert (agents should never checkout in main)
|
|
14
|
+
# - Manual (OVERDECK_AUTO_REVERT_CHECKOUT=1): Opt-in auto-revert for humans
|
|
15
|
+
# - Default: Warn only, don't revert
|
|
16
|
+
|
|
17
|
+
# Get the previous and new HEAD refs
|
|
18
|
+
PREV_HEAD=$1
|
|
19
|
+
NEW_HEAD=$2
|
|
20
|
+
IS_BRANCH_CHECKOUT=$3 # 1 if branch checkout, 0 if file checkout
|
|
21
|
+
|
|
22
|
+
# Only care about branch checkouts
|
|
23
|
+
if [ "$IS_BRANCH_CHECKOUT" != "1" ]; then
|
|
24
|
+
exit 0
|
|
25
|
+
fi
|
|
26
|
+
|
|
27
|
+
# Check if we're in a worktree (worktrees have .git as a file, not directory)
|
|
28
|
+
if [ -f ".git" ]; then
|
|
29
|
+
# This is a worktree - allow any checkout
|
|
30
|
+
exit 0
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
# Check if .git is a directory (main repo)
|
|
34
|
+
if [ ! -d ".git" ]; then
|
|
35
|
+
# Not a git repo at all
|
|
36
|
+
exit 0
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
# Get current branch name
|
|
40
|
+
CURRENT_BRANCH=$(git branch --show-current 2>/dev/null)
|
|
41
|
+
|
|
42
|
+
# If we're on main or master, all good
|
|
43
|
+
if [ "$CURRENT_BRANCH" = "main" ] || [ "$CURRENT_BRANCH" = "master" ]; then
|
|
44
|
+
exit 0
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# We're in the main project directory and NOT on main - this is bad!
|
|
48
|
+
echo ""
|
|
49
|
+
echo "╔══════════════════════════════════════════════════════════════════════════════╗"
|
|
50
|
+
echo "║ ⚠️ WARNING: Main project directory is not on 'main' branch! ║"
|
|
51
|
+
echo "╠══════════════════════════════════════════════════════════════════════════════╣"
|
|
52
|
+
echo "║ ║"
|
|
53
|
+
echo "║ Current branch: $CURRENT_BRANCH"
|
|
54
|
+
echo "║ Location: $(pwd)"
|
|
55
|
+
echo "║ ║"
|
|
56
|
+
echo "║ The main project directory should ALWAYS stay on 'main'. ║"
|
|
57
|
+
echo "║ All feature work should happen in workspaces (git worktrees). ║"
|
|
58
|
+
echo "║ ║"
|
|
59
|
+
echo "║ To fix this: ║"
|
|
60
|
+
echo "║ git checkout main ║"
|
|
61
|
+
echo "║ ║"
|
|
62
|
+
echo "║ To work on a feature: ║"
|
|
63
|
+
echo "║ pan workspace create ISSUE-ID ║"
|
|
64
|
+
echo "║ ║"
|
|
65
|
+
echo "╚══════════════════════════════════════════════════════════════════════════════╝"
|
|
66
|
+
echo ""
|
|
67
|
+
|
|
68
|
+
# Auto-revert for agents and planning sessions
|
|
69
|
+
# They should NEVER be checking out in main project
|
|
70
|
+
IS_OVERDECK_SESSION=0
|
|
71
|
+
|
|
72
|
+
# Check explicit environment variables
|
|
73
|
+
if [ "$OVERDECK_AGENT" = "1" ] || [ -n "$OVERDECK_AGENT_ID" ]; then
|
|
74
|
+
IS_OVERDECK_SESSION=1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
# Check for planning session
|
|
78
|
+
if [ "$OVERDECK_PLANNING" = "1" ] || [ -n "$OVERDECK_PLANNING_ISSUE" ]; then
|
|
79
|
+
IS_OVERDECK_SESSION=1
|
|
80
|
+
fi
|
|
81
|
+
|
|
82
|
+
# Check tmux session name pattern (agent-* or planning-* or specialist-*)
|
|
83
|
+
if [ -n "$TMUX" ]; then
|
|
84
|
+
TMUX_SESSION=$(tmux display-message -p '#{session_name}' 2>/dev/null || true)
|
|
85
|
+
case "$TMUX_SESSION" in
|
|
86
|
+
agent-*|planning-*|specialist-*)
|
|
87
|
+
IS_OVERDECK_SESSION=1
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
if [ "$IS_OVERDECK_SESSION" = "1" ]; then
|
|
93
|
+
echo "🤖 Overdeck session detected - auto-reverting to main branch..."
|
|
94
|
+
git checkout main --quiet
|
|
95
|
+
echo "✓ Reverted to main branch. Agents/planning must use workspaces for feature branches."
|
|
96
|
+
exit 0
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Check for manual auto-revert environment variable
|
|
100
|
+
if [ "$OVERDECK_AUTO_REVERT_CHECKOUT" = "1" ]; then
|
|
101
|
+
echo "Auto-reverting to main branch (OVERDECK_AUTO_REVERT_CHECKOUT=1)..."
|
|
102
|
+
git checkout main --quiet
|
|
103
|
+
echo "Reverted to main branch."
|
|
104
|
+
exit 0
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
# Return success but with warning - don't block the checkout
|
|
108
|
+
# The warning is enough for humans to notice
|
|
109
|
+
exit 0
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ~/.overdeck/bin/heartbeat-hook (PAN-800)
|
|
3
|
+
#
|
|
4
|
+
# Fires on Claude Code PostToolUse. POSTs agent.activity_changed(working, tool)
|
|
5
|
+
# to the dashboard, then records the cost event.
|
|
6
|
+
#
|
|
7
|
+
# Rewritten from the ~200-line jq read-modify-write version to a pure POST
|
|
8
|
+
# emitter. runtime.json and heartbeats/<id>.json are no longer written —
|
|
9
|
+
# AgentStateService's SubscriptionRef + projection_cache is canonical.
|
|
10
|
+
# activity.jsonl is retained as a forensic grep-able artifact (cheap to keep).
|
|
11
|
+
# The sessions.json append-only list is retained because it's a bookkeeping
|
|
12
|
+
# record of Claude session UUIDs, not runtime state.
|
|
13
|
+
# Cost-event recording stays on its own flock-serialized path.
|
|
14
|
+
|
|
15
|
+
set +e
|
|
16
|
+
|
|
17
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
18
|
+
# shellcheck source=pan-hook-lib.sh
|
|
19
|
+
. "$SCRIPT_DIR/pan-hook-lib.sh" 2>/dev/null || exit 0
|
|
20
|
+
|
|
21
|
+
pan_resolve_agent_id || exit 0
|
|
22
|
+
|
|
23
|
+
TOOL_INFO=$(cat 2>/dev/null || echo '{}')
|
|
24
|
+
|
|
25
|
+
HAVE_JQ=0
|
|
26
|
+
if command -v jq >/dev/null 2>&1; then HAVE_JQ=1; fi
|
|
27
|
+
|
|
28
|
+
TOOL_NAME="unknown"
|
|
29
|
+
TOOL_INPUT=""
|
|
30
|
+
CLAUDE_SESSION_ID=""
|
|
31
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
32
|
+
TOOL_NAME=$(echo "$TOOL_INFO" | jq -r '.tool_name // "unknown"' 2>/dev/null || echo "unknown")
|
|
33
|
+
TOOL_INPUT=$(echo "$TOOL_INFO" | jq -r '.tool_input | tostring | .[0:100] // ""' 2>/dev/null || echo "")
|
|
34
|
+
CLAUDE_SESSION_ID=$(echo "$TOOL_INFO" | jq -r '.session_id // ""' 2>/dev/null || echo "")
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
TS=$(date -Iseconds)
|
|
38
|
+
|
|
39
|
+
# ── Emit agent.activity_changed(working, tool) ─────────────────────────────
|
|
40
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
41
|
+
BODY=$(jq -n --arg tool "$TOOL_NAME" --arg ts "$TS" \
|
|
42
|
+
'{kind: "activity", activity: "working", tool: $tool, timestamp: $ts}')
|
|
43
|
+
else
|
|
44
|
+
BODY="{\"kind\":\"activity\",\"activity\":\"working\",\"tool\":\"$TOOL_NAME\",\"timestamp\":\"$TS\"}"
|
|
45
|
+
fi
|
|
46
|
+
pan_emit_event "$AGENT_ID" "$BODY"
|
|
47
|
+
|
|
48
|
+
# ── Activity log (retained for forensics) ─────────────────────────────────
|
|
49
|
+
AGENT_DIR="$HOME/.overdeck/agents/$AGENT_ID"
|
|
50
|
+
mkdir -p "$AGENT_DIR" 2>/dev/null
|
|
51
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
52
|
+
ENTRY=$(jq -n --arg ts "$TS" --arg tool "$TOOL_NAME" --arg action "$TOOL_INPUT" \
|
|
53
|
+
'{ts: $ts, tool: $tool, action: $action}' 2>/dev/null || echo "")
|
|
54
|
+
if [ -n "$ENTRY" ]; then
|
|
55
|
+
echo "$ENTRY" >> "$AGENT_DIR/activity.jsonl" 2>/dev/null || true
|
|
56
|
+
# Prune to last 100 entries.
|
|
57
|
+
if [ -f "$AGENT_DIR/activity.jsonl" ]; then
|
|
58
|
+
TMP="$AGENT_DIR/activity.jsonl.tmp"
|
|
59
|
+
tail -n 100 "$AGENT_DIR/activity.jsonl" > "$TMP" 2>/dev/null \
|
|
60
|
+
&& mv "$TMP" "$AGENT_DIR/activity.jsonl" 2>/dev/null || true
|
|
61
|
+
fi
|
|
62
|
+
fi
|
|
63
|
+
fi
|
|
64
|
+
|
|
65
|
+
# ── Claude session history (append-only bookkeeping) ──────────────────────
|
|
66
|
+
if [ "$HAVE_JQ" = "1" ] && [ -n "$CLAUDE_SESSION_ID" ]; then
|
|
67
|
+
SESSIONS_FILE="$AGENT_DIR/sessions.json"
|
|
68
|
+
if [ -f "$SESSIONS_FILE" ]; then
|
|
69
|
+
if ! jq -e --arg sid "$CLAUDE_SESSION_ID" 'any(.[]; . == $sid)' "$SESSIONS_FILE" >/dev/null 2>&1; then
|
|
70
|
+
TMP="$SESSIONS_FILE.tmp"
|
|
71
|
+
jq --arg sid "$CLAUDE_SESSION_ID" '. + [$sid]' "$SESSIONS_FILE" > "$TMP" 2>/dev/null \
|
|
72
|
+
&& mv "$TMP" "$SESSIONS_FILE" 2>/dev/null || true
|
|
73
|
+
fi
|
|
74
|
+
else
|
|
75
|
+
echo "[\"$CLAUDE_SESSION_ID\"]" > "$SESSIONS_FILE" 2>/dev/null || true
|
|
76
|
+
fi
|
|
77
|
+
fi
|
|
78
|
+
|
|
79
|
+
# ── Cost-event recording (unchanged, its own flock-serialized path) ──────
|
|
80
|
+
COST_SCRIPT=""
|
|
81
|
+
if [ -f "$HOME/.overdeck/bin/record-cost-event.js" ]; then
|
|
82
|
+
COST_SCRIPT="$HOME/.overdeck/bin/record-cost-event.js"
|
|
83
|
+
elif [ -f "$SCRIPT_DIR/record-cost-event.js" ]; then
|
|
84
|
+
COST_SCRIPT="$SCRIPT_DIR/record-cost-event.js"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [ -n "$COST_SCRIPT" ] && [ -n "$CLAUDE_SESSION_ID" ]; then
|
|
88
|
+
GIT_BRANCH=$(git branch --show-current 2>/dev/null || echo "")
|
|
89
|
+
WORKSPACE=$(pwd)
|
|
90
|
+
|
|
91
|
+
if [ -z "$OVERDECK_ISSUE_ID" ]; then
|
|
92
|
+
if [[ "$GIT_BRANCH" =~ (pan|min|aud|krux|cli|PAN|MIN|AUD|KRUX|CLI)[-]([0-9]+) ]]; then
|
|
93
|
+
OVERDECK_ISSUE_ID="${BASH_REMATCH[1]^^}-${BASH_REMATCH[2]}"
|
|
94
|
+
fi
|
|
95
|
+
fi
|
|
96
|
+
if [ -z "$OVERDECK_ISSUE_ID" ]; then
|
|
97
|
+
if [[ "$WORKSPACE" =~ (pan|min|aud|krux|cli|PAN|MIN|AUD|KRUX|CLI)[-]([0-9]+) ]]; then
|
|
98
|
+
OVERDECK_ISSUE_ID="${BASH_REMATCH[1]^^}-${BASH_REMATCH[2]}"
|
|
99
|
+
fi
|
|
100
|
+
fi
|
|
101
|
+
export OVERDECK_ISSUE_ID="${OVERDECK_ISSUE_ID:-UNKNOWN}"
|
|
102
|
+
export OVERDECK_SESSION_TYPE="${OVERDECK_SESSION_TYPE:-implementation}"
|
|
103
|
+
|
|
104
|
+
STATE_DIR="$HOME/.overdeck/costs/state"
|
|
105
|
+
mkdir -p "$STATE_DIR" 2>/dev/null
|
|
106
|
+
LOCK_FILE="$STATE_DIR/${CLAUDE_SESSION_ID}.lock"
|
|
107
|
+
{
|
|
108
|
+
flock -x -w 30 200
|
|
109
|
+
echo "$TOOL_INFO" | node "$COST_SCRIPT" 2>/dev/null || true
|
|
110
|
+
} 200>"$LOCK_FILE"
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
exit 0
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# ~/.overdeck/bin/notification-hook (PAN-800)
|
|
3
|
+
#
|
|
4
|
+
# Fires on Claude Code Notification events. If the notification looks like a
|
|
5
|
+
# permission prompt or a request for human input, emits
|
|
6
|
+
# agent.waiting_started(reason=...). Logs the raw payload for future analysis.
|
|
7
|
+
#
|
|
8
|
+
# PAN-709 note: the Notification event fires for system/desktop notifications
|
|
9
|
+
# from Claude Code, NOT for in-terminal tool approval dialogs. In-terminal
|
|
10
|
+
# prompts are detected separately by the deacon tmux-tail scanner (still
|
|
11
|
+
# active during the PAN-800 migration; scheduled for replacement in Phase 5).
|
|
12
|
+
|
|
13
|
+
set +e
|
|
14
|
+
|
|
15
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
16
|
+
# shellcheck source=pan-hook-lib.sh
|
|
17
|
+
. "$SCRIPT_DIR/pan-hook-lib.sh" 2>/dev/null || exit 0
|
|
18
|
+
|
|
19
|
+
pan_resolve_agent_id || exit 0
|
|
20
|
+
|
|
21
|
+
NOTIF_INFO=$(cat 2>/dev/null || echo '{}')
|
|
22
|
+
|
|
23
|
+
HAVE_JQ=0
|
|
24
|
+
if command -v jq >/dev/null 2>&1; then HAVE_JQ=1; fi
|
|
25
|
+
|
|
26
|
+
NOTIF_MSG=""
|
|
27
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
28
|
+
NOTIF_MSG=$(echo "$NOTIF_INFO" | jq -r '.message // ""' 2>/dev/null || echo "")
|
|
29
|
+
fi
|
|
30
|
+
|
|
31
|
+
TS=$(date -Iseconds)
|
|
32
|
+
|
|
33
|
+
# Log the raw notification for forensics. Kept at 500 entries.
|
|
34
|
+
LOG_DIR="$HOME/.overdeck/logs"
|
|
35
|
+
mkdir -p "$LOG_DIR" 2>/dev/null
|
|
36
|
+
LOG_FILE="$LOG_DIR/notifications.jsonl"
|
|
37
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
38
|
+
jq -n --arg ts "$TS" --arg agent "$AGENT_ID" --argjson payload "$NOTIF_INFO" \
|
|
39
|
+
'{ts: $ts, agent: $agent, payload: $payload}' >> "$LOG_FILE" 2>/dev/null || true
|
|
40
|
+
if [ -f "$LOG_FILE" ]; then
|
|
41
|
+
TMP="$LOG_FILE.tmp"
|
|
42
|
+
tail -n 500 "$LOG_FILE" > "$TMP" 2>/dev/null && mv "$TMP" "$LOG_FILE" 2>/dev/null || true
|
|
43
|
+
fi
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
# Classify the notification. The message strings come from Claude Code's own
|
|
47
|
+
# notification payloads; we're pattern-matching the keywords that indicate the
|
|
48
|
+
# agent is stalled waiting for the human.
|
|
49
|
+
REASON=""
|
|
50
|
+
if echo "$NOTIF_MSG" | grep -qiE '(permission|approval|allow.*tool|confirm.*tool)'; then
|
|
51
|
+
REASON="tool_permission"
|
|
52
|
+
elif echo "$NOTIF_MSG" | grep -qiE '(question|needs.*input|waiting.*for|needs your)'; then
|
|
53
|
+
REASON="user_question"
|
|
54
|
+
elif echo "$NOTIF_MSG" | grep -qiE '(disambiguat|choose|select|which)'; then
|
|
55
|
+
REASON="disambiguation"
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
if [ -n "$REASON" ]; then
|
|
59
|
+
if [ "$HAVE_JQ" = "1" ]; then
|
|
60
|
+
BODY=$(jq -n --arg reason "$REASON" --arg msg "$NOTIF_MSG" --arg ts "$TS" \
|
|
61
|
+
'{kind: "waiting_start", reason: $reason, message: $msg, timestamp: $ts}')
|
|
62
|
+
else
|
|
63
|
+
# Fallback: minimal body, no message field. Escaping arbitrary NOTIF_MSG
|
|
64
|
+
# into JSON by hand is error-prone.
|
|
65
|
+
BODY="{\"kind\":\"waiting_start\",\"reason\":\"$REASON\",\"timestamp\":\"$TS\"}"
|
|
66
|
+
fi
|
|
67
|
+
pan_emit_event "$AGENT_ID" "$BODY"
|
|
68
|
+
fi
|
|
69
|
+
|
|
70
|
+
exit 0
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# notify-complete: Send desktop notification when agent completes work
|
|
3
|
+
# Part of Overdeck - works on WSL2/Windows, Linux, and macOS
|
|
4
|
+
#
|
|
5
|
+
# Usage: notify-complete <issue-id> <title> [mr-url]
|
|
6
|
+
#
|
|
7
|
+
# Examples:
|
|
8
|
+
# notify-complete MIN-665 "Fixed login button"
|
|
9
|
+
# notify-complete PAN-96 "Implemented templates" "https://gitlab.com/mr/123"
|
|
10
|
+
|
|
11
|
+
ISSUE_ID="${1:-UNKNOWN}"
|
|
12
|
+
TITLE="${2:-Agent completed work}"
|
|
13
|
+
MR_URL="${3:-}"
|
|
14
|
+
|
|
15
|
+
# Log to completion file
|
|
16
|
+
COMPLETION_LOG="$HOME/.overdeck/agent-completed.log"
|
|
17
|
+
mkdir -p "$(dirname "$COMPLETION_LOG")"
|
|
18
|
+
echo "$(date '+%Y-%m-%d %H:%M:%S') | ${ISSUE_ID} | ${TITLE} | ${MR_URL}" >> "$COMPLETION_LOG"
|
|
19
|
+
|
|
20
|
+
# Detect platform and send notification
|
|
21
|
+
send_notification() {
|
|
22
|
+
local title="$1"
|
|
23
|
+
local message="$2"
|
|
24
|
+
|
|
25
|
+
# WSL2/Windows - use PowerShell toast notifications
|
|
26
|
+
if command -v powershell.exe &>/dev/null; then
|
|
27
|
+
powershell.exe -Command "
|
|
28
|
+
\$xml = @\"
|
|
29
|
+
<toast>
|
|
30
|
+
<visual>
|
|
31
|
+
<binding template=\"ToastText02\">
|
|
32
|
+
<text id=\"1\">${title}</text>
|
|
33
|
+
<text id=\"2\">${message}</text>
|
|
34
|
+
</binding>
|
|
35
|
+
</visual>
|
|
36
|
+
<audio src=\"ms-winsoundevent:Notification.Default\"/>
|
|
37
|
+
</toast>
|
|
38
|
+
\"@
|
|
39
|
+
|
|
40
|
+
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
|
|
41
|
+
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null
|
|
42
|
+
|
|
43
|
+
\$template = New-Object Windows.Data.Xml.Dom.XmlDocument
|
|
44
|
+
\$template.LoadXml(\$xml)
|
|
45
|
+
\$toast = New-Object Windows.UI.Notifications.ToastNotification \$template
|
|
46
|
+
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier('Overdeck').Show(\$toast)
|
|
47
|
+
" 2>/dev/null && return 0
|
|
48
|
+
|
|
49
|
+
# Fallback: MessageBox
|
|
50
|
+
powershell.exe -Command "[System.Reflection.Assembly]::LoadWithPartialName('System.Windows.Forms'); [System.Windows.Forms.MessageBox]::Show('${message}', '${title}', 'OK', 'Information')" 2>/dev/null &
|
|
51
|
+
return 0
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
# macOS - use osascript
|
|
55
|
+
if command -v osascript &>/dev/null; then
|
|
56
|
+
osascript -e "display notification \"${message}\" with title \"${title}\"" 2>/dev/null
|
|
57
|
+
return 0
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Linux - use notify-send
|
|
61
|
+
if command -v notify-send &>/dev/null; then
|
|
62
|
+
notify-send "${title}" "${message}" 2>/dev/null
|
|
63
|
+
return 0
|
|
64
|
+
fi
|
|
65
|
+
|
|
66
|
+
# Fallback - just print to console
|
|
67
|
+
echo "[NOTIFICATION] ${title}: ${message}"
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Build notification message
|
|
71
|
+
if [ -n "$MR_URL" ]; then
|
|
72
|
+
MESSAGE="${TITLE} - Ready for review"
|
|
73
|
+
else
|
|
74
|
+
MESSAGE="${TITLE} - Work complete"
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
send_notification "Overdeck: ${ISSUE_ID}" "$MESSAGE"
|
|
78
|
+
|
|
79
|
+
echo "Notification sent for ${ISSUE_ID}"
|