chainlesschain 0.162.162 → 0.162.164
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/package.json +3 -1
- package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-tFHy5Wol.js} +1 -1
- package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
- package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +320 -32
- package/src/commands/agent.js +105 -11
- package/src/commands/context.js +125 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/hook.js +181 -7
- package/src/commands/plugin.js +456 -1
- package/src/commands/review.js +363 -2
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +10 -4
- package/src/gateways/ws/remote-session-protocol.js +39 -0
- package/src/gateways/ws/session-protocol.js +55 -2
- package/src/gateways/ws/ws-agent-handler.js +50 -0
- package/src/harness/jsonl-session-store.js +60 -0
- package/src/harness/worktree-isolator.js +140 -5
- package/src/lib/agent-schedule-store.js +318 -10
- package/src/lib/agent-team/team-worktree.js +37 -2
- package/src/lib/agent-tool-admission.js +125 -0
- package/src/lib/app-preview.js +360 -0
- package/src/lib/async-hook-supervisor.cjs +54 -0
- package/src/lib/capability-manifest.js +319 -0
- package/src/lib/capability-negotiation.js +23 -12
- package/src/lib/channels/channel-manager.js +54 -2
- package/src/lib/cli-context-engineering.js +9 -2
- package/src/lib/context-breakdown.js +116 -0
- package/src/lib/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +275 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-event-log.cjs +193 -0
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- package/src/lib/interaction-adapter.js +34 -4
- package/src/lib/json-schema-output.js +46 -12
- package/src/lib/json-schema-validate.js +143 -3
- package/src/lib/jsonl-session-store.js +1 -0
- package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
- package/src/lib/lsp/lsp-manager.js +56 -0
- package/src/lib/monitor-event.js +290 -0
- package/src/lib/operation-fingerprint.js +217 -0
- package/src/lib/plugin-runtime/bin.js +37 -0
- package/src/lib/plugin-runtime/capabilities.js +19 -0
- package/src/lib/plugin-runtime/capability-consent.js +203 -0
- package/src/lib/plugin-runtime/hooks.js +37 -0
- package/src/lib/plugin-runtime/lsp.js +38 -0
- package/src/lib/plugin-runtime/mcp.js +40 -0
- package/src/lib/plugin-runtime/monitors.js +37 -0
- package/src/lib/plugin-runtime/plugin-options.js +151 -0
- package/src/lib/plugin-runtime/policy.js +62 -0
- package/src/lib/plugin-runtime/scopes.js +20 -4
- package/src/lib/pr-automation-policy.js +224 -0
- package/src/lib/project-instructions.js +128 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-goal.js +132 -0
- package/src/lib/repl-rewind.js +153 -0
- package/src/lib/review-comment-anchor.js +241 -0
- package/src/lib/review-pipeline.js +46 -7
- package/src/lib/runtime-checkup.js +221 -0
- package/src/lib/schedule-planner.js +17 -1
- package/src/lib/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/settings-hook-events.cjs +13 -0
- package/src/lib/settings-loader.cjs +42 -0
- package/src/lib/side-effect-ledger-store.js +88 -0
- package/src/lib/side-effect-ledger.js +401 -0
- package/src/lib/skill-loader.js +32 -0
- package/src/lib/skill-path-scope.js +63 -0
- package/src/lib/sub-agent-context.js +110 -9
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/subagent-inheritance.js +112 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -0
- package/src/lib/turn-binding-store.js +119 -0
- package/src/lib/unattended-action-policy.js +182 -0
- package/src/lib/worktree-cleanup-safety.js +138 -0
- package/src/lib/worktree-isolator.js +8 -0
- package/src/lib/worktree-sparse.js +131 -0
- package/src/repl/agent-repl.js +280 -30
- package/src/repl/permission-tier.js +28 -0
- package/src/runtime/agent-core.js +565 -21
- package/src/runtime/coding-agent-contract-shared.cjs +60 -1
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +393 -1
- package/src/runtime/headless-stream.js +60 -1
- package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
- package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
- package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
package/src/commands/review.js
CHANGED
|
@@ -34,7 +34,11 @@ import path from "node:path";
|
|
|
34
34
|
import chalk from "chalk";
|
|
35
35
|
import { logger } from "../lib/logger.js";
|
|
36
36
|
import { firstBalancedJson } from "../lib/json-schema-output.js";
|
|
37
|
-
import {
|
|
37
|
+
import {
|
|
38
|
+
dedupeFindings,
|
|
39
|
+
buildReviewReport,
|
|
40
|
+
findingKey,
|
|
41
|
+
} from "../lib/review-pipeline.js";
|
|
38
42
|
|
|
39
43
|
/** Diffs larger than this are truncated before going to the model. */
|
|
40
44
|
const MAX_DIFF_CHARS = 200_000;
|
|
@@ -121,6 +125,32 @@ export function normalizeEffort(value) {
|
|
|
121
125
|
return v;
|
|
122
126
|
}
|
|
123
127
|
|
|
128
|
+
/**
|
|
129
|
+
* Decide whether the multi-finder fan-out and its skeptic verifier pass run.
|
|
130
|
+
* Explicit flags always win; otherwise the top effort tier (`high`) auto-enables
|
|
131
|
+
* BOTH — a "high" review IS the thorough, verified pass — while low/medium stay
|
|
132
|
+
* single-pass (byte-identical default). `--single` forces single-pass even at
|
|
133
|
+
* high effort. Verify only ever runs with multi. Pure.
|
|
134
|
+
*
|
|
135
|
+
* @param {{multi?, verify?, effort?, single?}} opts Commander-shaped (undefined = unset)
|
|
136
|
+
* @returns {{multi:boolean, verify:boolean, auto:boolean}} auto = enabled by effort
|
|
137
|
+
*/
|
|
138
|
+
export function resolveMultiVerify({ multi, verify, effort, single } = {}) {
|
|
139
|
+
const highEffort = normalizeEffort(effort) === "high";
|
|
140
|
+
let multiOn;
|
|
141
|
+
if (single === true) multiOn = false;
|
|
142
|
+
else if (multi === true) multiOn = true;
|
|
143
|
+
else if (multi === false) multiOn = false;
|
|
144
|
+
else multiOn = highEffort; // unset → auto-enable at high effort
|
|
145
|
+
const verifyOn =
|
|
146
|
+
verify === true ? true : verify === false ? false : multiOn && highEffort;
|
|
147
|
+
return {
|
|
148
|
+
multi: multiOn,
|
|
149
|
+
verify: multiOn && verifyOn,
|
|
150
|
+
auto: multi == null && single !== true && multiOn,
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
124
154
|
/** Resolve the review lens from flags. Pure. */
|
|
125
155
|
export function resolveReviewMode({ security, simplify } = {}) {
|
|
126
156
|
if (security && simplify) {
|
|
@@ -585,8 +615,280 @@ export async function runReviewComment(options = {}, deps = {}) {
|
|
|
585
615
|
};
|
|
586
616
|
}
|
|
587
617
|
|
|
618
|
+
// ── multi-agent fan-out (opt-in `--multi`) ──────────────────────────────────
|
|
619
|
+
// A high-confidence review runs one focused FINDER per dimension, then dedupes
|
|
620
|
+
// and ranks their findings into a single structured report — instead of the
|
|
621
|
+
// default single agent that hunts everything at once. Each finder is blind to
|
|
622
|
+
// the others; overlapping findings merge (union categories, keep max severity).
|
|
623
|
+
|
|
624
|
+
/** The review dimensions fanned out, one finder agent each. */
|
|
625
|
+
export const REVIEW_DIMENSIONS = Object.freeze([
|
|
626
|
+
{
|
|
627
|
+
key: "correctness",
|
|
628
|
+
label: "correctness",
|
|
629
|
+
hint: "logic errors, off-by-one, null/undefined, wrong API usage, unhandled errors, race conditions",
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
key: "security",
|
|
633
|
+
label: "security",
|
|
634
|
+
hint: "injection, auth/authz gaps, secrets, path traversal, SSRF, unsafe deserialization, missing input validation",
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
key: "performance",
|
|
638
|
+
label: "performance",
|
|
639
|
+
hint: "N+1 queries, unbounded loops, sync IO on hot paths, memory leaks, needless allocations/copies",
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
key: "tests",
|
|
643
|
+
label: "test coverage",
|
|
644
|
+
hint: "untested branches, missing edge cases, weak assertions, changed behavior lacking a test",
|
|
645
|
+
},
|
|
646
|
+
]);
|
|
647
|
+
|
|
648
|
+
const JSON_FINDINGS_CONTRACT =
|
|
649
|
+
'Output ONLY a JSON array (no prose, no fences). Each element: {"path":"file","line":12,"severity":"Critical|High|Medium|Low|Note","title":"one-line issue","body":"why it is a bug and how to fix"}. Output [] if you find nothing.';
|
|
650
|
+
|
|
651
|
+
/** Build a single dimension finder's prompt from the shared diff context. Pure. */
|
|
652
|
+
export function buildDimensionPrompt(ctx = {}, dim = {}) {
|
|
653
|
+
const {
|
|
654
|
+
diff = "",
|
|
655
|
+
summary = "",
|
|
656
|
+
label = "",
|
|
657
|
+
untrackedBlocks = [],
|
|
658
|
+
truncated = false,
|
|
659
|
+
} = ctx;
|
|
660
|
+
return [
|
|
661
|
+
`You are a ${dim.label} code reviewer. Report ONLY ${dim.label} issues: ${dim.hint}.`,
|
|
662
|
+
`Review this diff (${label}). ${summary}`,
|
|
663
|
+
truncated ? "(diff truncated — review what is shown)" : "",
|
|
664
|
+
"```diff",
|
|
665
|
+
diff,
|
|
666
|
+
"```",
|
|
667
|
+
...(untrackedBlocks && untrackedBlocks.length
|
|
668
|
+
? ["", "New untracked files:", ...untrackedBlocks]
|
|
669
|
+
: []),
|
|
670
|
+
"",
|
|
671
|
+
JSON_FINDINGS_CONTRACT,
|
|
672
|
+
]
|
|
673
|
+
.filter((l) => l !== "")
|
|
674
|
+
.join("\n");
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
/** Render a structured review report to markdown. Pure. */
|
|
678
|
+
export function renderMultiFinderReport(report = {}, byDimension = {}) {
|
|
679
|
+
const findings = report.findings || [];
|
|
680
|
+
const summary = report.summary || { total: 0, bySeverity: {} };
|
|
681
|
+
const sevOrder = ["Critical", "High", "Medium", "Low", "Note"];
|
|
682
|
+
const lines = [];
|
|
683
|
+
lines.push(`# Multi-agent review — ${summary.total} finding(s)`);
|
|
684
|
+
lines.push("");
|
|
685
|
+
lines.push(
|
|
686
|
+
sevOrder.map((s) => `${s}: ${summary.bySeverity?.[s] || 0}`).join(" · "),
|
|
687
|
+
);
|
|
688
|
+
const dimLine = Object.entries(byDimension)
|
|
689
|
+
.map(([k, v]) => `${k} ${v}`)
|
|
690
|
+
.join(" · ");
|
|
691
|
+
if (dimLine) lines.push(`By dimension: ${dimLine}`);
|
|
692
|
+
lines.push("");
|
|
693
|
+
for (const f of findings) {
|
|
694
|
+
lines.push(
|
|
695
|
+
`## [${f.severity}] ${f.path}:${f.line}${f.category ? ` (${f.category})` : ""}`,
|
|
696
|
+
);
|
|
697
|
+
if (f.failure_scenario) lines.push(f.failure_scenario);
|
|
698
|
+
if (f.evidence) lines.push(`> ${f.evidence}`);
|
|
699
|
+
lines.push("");
|
|
700
|
+
}
|
|
701
|
+
if (findings.length === 0) lines.push("No findings.");
|
|
702
|
+
return lines.join("\n") + "\n";
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/** Build a skeptic verifier's prompt for one finding. Pure. */
|
|
706
|
+
export function buildVerifierPrompt(f = {}) {
|
|
707
|
+
return [
|
|
708
|
+
"You are a SKEPTICAL code reviewer. Independently verify the finding below by reading the actual code with your tools. Assume it is WRONG until you can reproduce it; default to REFUTED if you cannot.",
|
|
709
|
+
`Finding: [${f.severity}] ${f.path}:${f.line}`,
|
|
710
|
+
f.title ? `Claim: ${f.title}` : "",
|
|
711
|
+
f.evidence || f.failureScenario
|
|
712
|
+
? `Details: ${f.evidence || f.failureScenario}`
|
|
713
|
+
: "",
|
|
714
|
+
"",
|
|
715
|
+
'Reply with ONLY a JSON object (no prose): {"reproduced": true|false, "confidence": 0.0-1.0, "reason": "one line"}. Set reproduced=true ONLY if the defect is real and reachable.',
|
|
716
|
+
]
|
|
717
|
+
.filter((l) => l !== "")
|
|
718
|
+
.join("\n");
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
/** Parse a verifier reply into an applyVerdicts verdict. Pure; null if unparseable. */
|
|
722
|
+
export function parseVerdict(text) {
|
|
723
|
+
const balanced = firstBalancedJson(String(text || ""), "{");
|
|
724
|
+
if (!balanced) return null;
|
|
725
|
+
let obj;
|
|
726
|
+
try {
|
|
727
|
+
obj = JSON.parse(balanced);
|
|
728
|
+
} catch {
|
|
729
|
+
return null;
|
|
730
|
+
}
|
|
731
|
+
if (!obj || typeof obj !== "object") return null;
|
|
732
|
+
const reproduced = obj.reproduced === true || obj.verified === true;
|
|
733
|
+
const conf = Number(obj.confidence);
|
|
734
|
+
return {
|
|
735
|
+
verified: reproduced,
|
|
736
|
+
...(Number.isFinite(conf) ? { confidence: conf } : {}),
|
|
737
|
+
...(obj.reason ? { note: String(obj.reason) } : {}),
|
|
738
|
+
};
|
|
739
|
+
}
|
|
740
|
+
|
|
588
741
|
/**
|
|
589
|
-
*
|
|
742
|
+
* Run one skeptic verifier agent per (already-deduped) finding and collect a
|
|
743
|
+
* verdicts map keyed by findingKey. A verifier that throws/parses to nothing
|
|
744
|
+
* leaves the finding unverified (kept as-is). Deps: { runAgentHeadless }.
|
|
745
|
+
*/
|
|
746
|
+
export async function runVerifierPass(findings, ctx = {}, deps = {}) {
|
|
747
|
+
const run =
|
|
748
|
+
deps.runAgentHeadless ||
|
|
749
|
+
(await import("../runtime/headless-runner.js")).runAgentHeadless;
|
|
750
|
+
const writeErr = ctx.writeErr || (() => {});
|
|
751
|
+
const verdicts = {};
|
|
752
|
+
for (const f of findings || []) {
|
|
753
|
+
let captured = "";
|
|
754
|
+
let outcome;
|
|
755
|
+
try {
|
|
756
|
+
outcome = await run(
|
|
757
|
+
{
|
|
758
|
+
...(ctx.baseOptions || {}),
|
|
759
|
+
prompt: buildVerifierPrompt(f),
|
|
760
|
+
outputFormat: "text",
|
|
761
|
+
},
|
|
762
|
+
{
|
|
763
|
+
writeOut: (s) => {
|
|
764
|
+
captured += s;
|
|
765
|
+
},
|
|
766
|
+
writeErr,
|
|
767
|
+
},
|
|
768
|
+
);
|
|
769
|
+
} catch (e) {
|
|
770
|
+
writeErr(
|
|
771
|
+
`review --verify: verifier failed for ${f.path}:${f.line}: ${e.message}\n`,
|
|
772
|
+
);
|
|
773
|
+
continue;
|
|
774
|
+
}
|
|
775
|
+
const text = String(outcome?.result ?? captured ?? "");
|
|
776
|
+
const v = parseVerdict(text);
|
|
777
|
+
if (v) verdicts[findingKey(f)] = v;
|
|
778
|
+
}
|
|
779
|
+
return verdicts;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
/**
|
|
783
|
+
* Fan out one finder agent per review dimension, capture each finder's JSON
|
|
784
|
+
* findings, and dedupe/rank them into a structured report. With `ctx.verify`, a
|
|
785
|
+
* skeptic verifier agent then independently reproduces each finding and REFUTED
|
|
786
|
+
* ones are dropped. Each agent runs a captured headless turn (its raw output
|
|
787
|
+
* never hits stdout — only the final report does).
|
|
788
|
+
* Deps: { runAgentHeadless, dimensions }.
|
|
789
|
+
*/
|
|
790
|
+
export async function runMultiFinderReview(ctx = {}, deps = {}) {
|
|
791
|
+
const run =
|
|
792
|
+
deps.runAgentHeadless ||
|
|
793
|
+
(await import("../runtime/headless-runner.js")).runAgentHeadless;
|
|
794
|
+
const dimensions = deps.dimensions || REVIEW_DIMENSIONS;
|
|
795
|
+
const writeOut = ctx.writeOut || ((s) => process.stdout.write(s));
|
|
796
|
+
const writeErr = ctx.writeErr || ((s) => process.stderr.write(s));
|
|
797
|
+
|
|
798
|
+
const all = [];
|
|
799
|
+
const byDimension = {};
|
|
800
|
+
for (const dim of dimensions) {
|
|
801
|
+
let captured = "";
|
|
802
|
+
let outcome;
|
|
803
|
+
try {
|
|
804
|
+
outcome = await run(
|
|
805
|
+
{
|
|
806
|
+
...(ctx.baseOptions || {}),
|
|
807
|
+
prompt: buildDimensionPrompt(ctx, dim),
|
|
808
|
+
outputFormat: "text",
|
|
809
|
+
},
|
|
810
|
+
{
|
|
811
|
+
writeOut: (s) => {
|
|
812
|
+
captured += s;
|
|
813
|
+
},
|
|
814
|
+
writeErr,
|
|
815
|
+
},
|
|
816
|
+
);
|
|
817
|
+
} catch (e) {
|
|
818
|
+
writeErr(`review --multi: ${dim.key} finder failed: ${e.message}\n`);
|
|
819
|
+
byDimension[dim.key] = 0;
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
822
|
+
const text =
|
|
823
|
+
String(outcome?.result ?? captured ?? "").trim() || captured.trim();
|
|
824
|
+
// Carry title into `failureScenario` (the one-line issue) and body into
|
|
825
|
+
// `evidence` so the structured report keeps them; `title` stays on the
|
|
826
|
+
// object so cross-dimension dedup keys on path:line:title.
|
|
827
|
+
const found = parseFindings(text).map((f) => ({
|
|
828
|
+
path: f.path,
|
|
829
|
+
line: f.line,
|
|
830
|
+
severity: f.severity,
|
|
831
|
+
title: f.title,
|
|
832
|
+
category: dim.key,
|
|
833
|
+
failureScenario: f.title || f.body || "",
|
|
834
|
+
evidence: f.body || "",
|
|
835
|
+
}));
|
|
836
|
+
byDimension[dim.key] = found.length;
|
|
837
|
+
all.push(...found);
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
// Optional skeptic pass: verify each unique finding; refuted ones are dropped
|
|
841
|
+
// by buildReviewReport via applyVerdicts. Verify the DEDUPED set so a finding
|
|
842
|
+
// reported by several finders is only checked once.
|
|
843
|
+
let verdicts = {};
|
|
844
|
+
let verifiedCount = null;
|
|
845
|
+
if (ctx.verify) {
|
|
846
|
+
const unique = dedupeFindings(all);
|
|
847
|
+
verdicts = await runVerifierPass(unique, ctx, deps);
|
|
848
|
+
verifiedCount = Object.values(verdicts).filter((v) => v.verified).length;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// P1-1: read each referenced file's current content so every finding's output
|
|
852
|
+
// carries a re-anchorable comment anchor (file + base hash + line + context),
|
|
853
|
+
// not just a bare line number — an IDE diff review can then relocate or stale
|
|
854
|
+
// the comment after the agent's fix instead of reusing a dead coordinate.
|
|
855
|
+
// Best-effort: an unreadable path is simply skipped (no anchor for it).
|
|
856
|
+
const reviewCwd = ctx.baseOptions?.cwd || process.cwd();
|
|
857
|
+
const readFile = deps.readFileSync || fs.readFileSync;
|
|
858
|
+
const fileContents = new Map();
|
|
859
|
+
for (const p of new Set(all.map((f) => f && f.path).filter(Boolean))) {
|
|
860
|
+
try {
|
|
861
|
+
fileContents.set(p, readFile(path.resolve(reviewCwd, p), "utf-8"));
|
|
862
|
+
} catch {
|
|
863
|
+
/* file gone / binary / outside tree — no anchor, never fail the review */
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
const report = buildReviewReport(all, { verdicts, fileContents });
|
|
868
|
+
const meta = {
|
|
869
|
+
byDimension,
|
|
870
|
+
...(ctx.verify ? { verified: verifiedCount } : {}),
|
|
871
|
+
};
|
|
872
|
+
if (ctx.outputFormat === "json") {
|
|
873
|
+
writeOut(`${JSON.stringify({ ...report, ...meta }, null, 2)}\n`);
|
|
874
|
+
} else {
|
|
875
|
+
writeOut(renderMultiFinderReport(report, byDimension));
|
|
876
|
+
}
|
|
877
|
+
return {
|
|
878
|
+
exitCode: 0,
|
|
879
|
+
isError: false,
|
|
880
|
+
scope: ctx.scope,
|
|
881
|
+
empty: false,
|
|
882
|
+
multi: true,
|
|
883
|
+
report,
|
|
884
|
+
byDimension,
|
|
885
|
+
...(ctx.verify ? { verified: verifiedCount } : {}),
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
/**
|
|
890
|
+
* Core review run — collects the diff and dispatches one headless agent turn
|
|
891
|
+
* (or, with `--multi`, one finder agent per dimension merged into a report).
|
|
590
892
|
* Deps are injected for tests (git / runAgentHeadless / config helpers).
|
|
591
893
|
*
|
|
592
894
|
* @returns {Promise<{exitCode:number, isError:boolean, scope:string, empty?:boolean}>}
|
|
@@ -657,6 +959,53 @@ export async function runReview(options = {}, deps = {}) {
|
|
|
657
959
|
// fall back to runner defaults
|
|
658
960
|
}
|
|
659
961
|
|
|
962
|
+
// --multi: fan out one finder per dimension and merge into a structured
|
|
963
|
+
// report. Opt-in — or auto-enabled at high effort (a "high" review IS the
|
|
964
|
+
// thorough, verified pass). The default single-pass path below is
|
|
965
|
+
// byte-identical for low/medium effort with no flags.
|
|
966
|
+
const {
|
|
967
|
+
multi: useMulti,
|
|
968
|
+
verify: useVerify,
|
|
969
|
+
auto,
|
|
970
|
+
} = resolveMultiVerify({
|
|
971
|
+
multi: options.multi,
|
|
972
|
+
verify: options.verify,
|
|
973
|
+
effort: options.effort,
|
|
974
|
+
single: options.single,
|
|
975
|
+
});
|
|
976
|
+
if (useMulti) {
|
|
977
|
+
if (auto && deps.writeErr) {
|
|
978
|
+
deps.writeErr(
|
|
979
|
+
`high-effort review: multi-finder fan-out${useVerify ? " + verifier" : ""} auto-enabled (use --single to force single-pass)\n`,
|
|
980
|
+
);
|
|
981
|
+
}
|
|
982
|
+
return runMultiFinderReview(
|
|
983
|
+
{
|
|
984
|
+
diff: hasDiff ? diff : "(no tracked changes)",
|
|
985
|
+
summary,
|
|
986
|
+
label,
|
|
987
|
+
scope,
|
|
988
|
+
untrackedBlocks: untracked.blocks,
|
|
989
|
+
truncated,
|
|
990
|
+
verify: useVerify,
|
|
991
|
+
outputFormat: options.outputFormat || "text",
|
|
992
|
+
writeOut: deps.writeOut,
|
|
993
|
+
writeErr: deps.writeErr,
|
|
994
|
+
baseOptions: {
|
|
995
|
+
model: options.model,
|
|
996
|
+
provider: options.provider,
|
|
997
|
+
baseUrl: options.baseUrl,
|
|
998
|
+
apiKey: options.apiKey,
|
|
999
|
+
cwd,
|
|
1000
|
+
permissionMode: "plan",
|
|
1001
|
+
expandFileRefs: false,
|
|
1002
|
+
maxTurns: Number.isFinite(options.maxTurns) ? options.maxTurns : 20,
|
|
1003
|
+
},
|
|
1004
|
+
},
|
|
1005
|
+
deps,
|
|
1006
|
+
);
|
|
1007
|
+
}
|
|
1008
|
+
|
|
660
1009
|
// review-only → plan mode (clamped to read-only tools, cannot mutate).
|
|
661
1010
|
// --fix → acceptEdits + auto-checkpoint (reversible edits).
|
|
662
1011
|
const permissionMode = fix ? "acceptEdits" : "plan";
|
|
@@ -725,6 +1074,18 @@ export function registerReviewCommand(program) {
|
|
|
725
1074
|
"--dry-run",
|
|
726
1075
|
"With --comment: show what would be posted without posting",
|
|
727
1076
|
)
|
|
1077
|
+
.option(
|
|
1078
|
+
"--multi",
|
|
1079
|
+
"Fan out one finder agent per dimension (correctness/security/performance/tests) and merge into a structured report",
|
|
1080
|
+
)
|
|
1081
|
+
.option(
|
|
1082
|
+
"--verify",
|
|
1083
|
+
"With --multi: run a skeptic verifier agent per finding and drop the ones it cannot reproduce",
|
|
1084
|
+
)
|
|
1085
|
+
.option(
|
|
1086
|
+
"--single",
|
|
1087
|
+
"Force a single-pass review even at high effort (disables the multi-finder auto-enable)",
|
|
1088
|
+
)
|
|
728
1089
|
.option("--json", "Emit the agent result envelope as JSON")
|
|
729
1090
|
.action(async (effortArg, options) => {
|
|
730
1091
|
try {
|
package/src/commands/session.js
CHANGED
|
@@ -9,6 +9,7 @@ import path from "path";
|
|
|
9
9
|
import chalk from "chalk";
|
|
10
10
|
import { logger } from "../lib/logger.js";
|
|
11
11
|
import { parseJsonOption } from "../lib/parse-json-option.js";
|
|
12
|
+
import { scanSecrets, redactSecrets } from "../lib/secret-scan.js";
|
|
12
13
|
import { bootstrap, shutdown } from "../runtime/bootstrap.js";
|
|
13
14
|
import {
|
|
14
15
|
listSessions,
|
|
@@ -75,6 +76,97 @@ function _sessionEpoch(ts) {
|
|
|
75
76
|
return Number.isFinite(n) ? n : 0;
|
|
76
77
|
}
|
|
77
78
|
|
|
79
|
+
/**
|
|
80
|
+
* Render a PR's automation status (P1-4 "PR/CI Monitor + 受控合并") from a
|
|
81
|
+
* gathered signals object — PURE, so it is testable without gh / the network.
|
|
82
|
+
* `enabled` mirrors the "auto-merge is OFF by default" rule: only an explicit
|
|
83
|
+
* opt-in even considers a merge. Everything else defers to the fail-closed
|
|
84
|
+
* pr-automation-policy decision engine.
|
|
85
|
+
*
|
|
86
|
+
* @param {object} signals {branch, prNumber, hasOpenPr, headCommitSha,
|
|
87
|
+
* branchProtectionSatisfied, reviewApproved, pendingApprovals,
|
|
88
|
+
* requiredChecks, checks}
|
|
89
|
+
* @param {{enabled?:boolean}} [opts]
|
|
90
|
+
* @returns {{statusBar:string, autoMerge:{allow:boolean,reason:string,unmet:string[]}, lines:string[]}}
|
|
91
|
+
*/
|
|
92
|
+
export async function renderPrStatus(signals = {}, opts = {}) {
|
|
93
|
+
const { autoMergeDecision, describePrStatusBar } =
|
|
94
|
+
await import("../lib/pr-automation-policy.js");
|
|
95
|
+
const autoMerge = autoMergeDecision({
|
|
96
|
+
enabled: opts.enabled === true,
|
|
97
|
+
hasOpenPr: signals.hasOpenPr,
|
|
98
|
+
branchProtectionSatisfied: signals.branchProtectionSatisfied,
|
|
99
|
+
reviewApproved: signals.reviewApproved,
|
|
100
|
+
pendingApprovals: signals.pendingApprovals,
|
|
101
|
+
requiredChecks: signals.requiredChecks,
|
|
102
|
+
checks: signals.checks,
|
|
103
|
+
});
|
|
104
|
+
const statusBar = describePrStatusBar({
|
|
105
|
+
branch: signals.branch,
|
|
106
|
+
prNumber: signals.prNumber,
|
|
107
|
+
checks: signals.checks,
|
|
108
|
+
reviewApproved: signals.reviewApproved,
|
|
109
|
+
mergeable: autoMerge.allow,
|
|
110
|
+
});
|
|
111
|
+
const lines = [statusBar];
|
|
112
|
+
if (autoMerge.allow) {
|
|
113
|
+
lines.push("auto-merge: ✓ eligible");
|
|
114
|
+
} else {
|
|
115
|
+
lines.push(`auto-merge: ✗ blocked (${autoMerge.unmet.join(", ")})`);
|
|
116
|
+
}
|
|
117
|
+
return { statusBar, autoMerge, lines };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Best-effort map of `gh pr view --json …` output to the pr-automation signals.
|
|
122
|
+
* PURE (json → json); the command supplies the gh JSON (or a --checks-file). A
|
|
123
|
+
* missing field stays undefined so the fail-closed policy denies rather than
|
|
124
|
+
* assuming.
|
|
125
|
+
*/
|
|
126
|
+
export function mapGhPrToSignals(gh = {}) {
|
|
127
|
+
const rollup = Array.isArray(gh.statusCheckRollup)
|
|
128
|
+
? gh.statusCheckRollup
|
|
129
|
+
: [];
|
|
130
|
+
return {
|
|
131
|
+
branch: gh.headRefName || undefined,
|
|
132
|
+
prNumber: gh.number,
|
|
133
|
+
hasOpenPr: String(gh.state || "").toUpperCase() === "OPEN",
|
|
134
|
+
headCommitSha: gh.headRefOid || undefined,
|
|
135
|
+
reviewApproved: gh.reviewDecision === "APPROVED",
|
|
136
|
+
// gh doesn't expose branch-protection satisfaction or pending in-app
|
|
137
|
+
// approvals here; leave them undefined → fail-closed unless a --checks-file
|
|
138
|
+
// asserts them.
|
|
139
|
+
checks: rollup.map((c) => ({
|
|
140
|
+
name: c.name || c.context,
|
|
141
|
+
// check runs use `conclusion`; legacy statuses use `state`.
|
|
142
|
+
state: c.conclusion || c.state || c.status,
|
|
143
|
+
})),
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Best-effort live fetch of a PR's signals via `gh pr view --json`. Returns the
|
|
149
|
+
* mapped signals, or throws (caller degrades to a "pass --checks-file" hint).
|
|
150
|
+
* 8s timeout, stderr suppressed — a missing / unauthenticated gh just fails.
|
|
151
|
+
*/
|
|
152
|
+
async function fetchPrSignalsViaGh(target) {
|
|
153
|
+
const { execFileSync } = await import("node:child_process");
|
|
154
|
+
const args = [
|
|
155
|
+
"pr",
|
|
156
|
+
"view",
|
|
157
|
+
String(target.number),
|
|
158
|
+
"--json",
|
|
159
|
+
"number,state,headRefName,headRefOid,reviewDecision,statusCheckRollup",
|
|
160
|
+
];
|
|
161
|
+
if (target.repo) args.push("--repo", String(target.repo));
|
|
162
|
+
const out = execFileSync("gh", args, {
|
|
163
|
+
encoding: "utf-8",
|
|
164
|
+
timeout: 8000,
|
|
165
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
166
|
+
});
|
|
167
|
+
return mapGhPrToSignals(JSON.parse(out));
|
|
168
|
+
}
|
|
169
|
+
|
|
78
170
|
export function rankSessions(sessions, limit) {
|
|
79
171
|
// Dedup by id, preferring the JSONL copy when a session exists in both stores
|
|
80
172
|
// (the original "JSONL takes precedence" intent, previously achieved only as
|
|
@@ -254,6 +346,87 @@ export function registerSessionCommand(program) {
|
|
|
254
346
|
}
|
|
255
347
|
});
|
|
256
348
|
|
|
349
|
+
// session pr-status — PR/CI monitor + controlled auto-merge decision (P1-4)
|
|
350
|
+
// for a session's linked PR. Offline-testable via --checks-file; otherwise a
|
|
351
|
+
// best-effort `gh pr view` fetch for the session's newest linked PR.
|
|
352
|
+
session
|
|
353
|
+
.command("pr-status")
|
|
354
|
+
.description(
|
|
355
|
+
"PR/CI monitor + controlled auto-merge decision for a session's linked PR",
|
|
356
|
+
)
|
|
357
|
+
.argument("[id]", "Session id (default: most recent)")
|
|
358
|
+
.option("--pr <number>", "Which linked PR to assess (default: newest)")
|
|
359
|
+
.option(
|
|
360
|
+
"--checks-file <path>",
|
|
361
|
+
"JSON file of PR signals {branch,prNumber,hasOpenPr,branchProtectionSatisfied,reviewApproved,pendingApprovals,requiredChecks,checks} — bypasses gh",
|
|
362
|
+
)
|
|
363
|
+
.option("--enable", "Consider auto-merge eligibility (default: off)")
|
|
364
|
+
.option("--json", "Output as machine-readable JSON")
|
|
365
|
+
.action(async (id, options) => {
|
|
366
|
+
try {
|
|
367
|
+
let signals = null;
|
|
368
|
+
let source = "";
|
|
369
|
+
if (options.checksFile) {
|
|
370
|
+
// Offline / explicit path: the caller supplies the PR signals.
|
|
371
|
+
signals = JSON.parse(fs.readFileSync(options.checksFile, "utf-8"));
|
|
372
|
+
source = options.checksFile;
|
|
373
|
+
} else {
|
|
374
|
+
// Live path: resolve the session's linked PR and fetch via gh.
|
|
375
|
+
const { getPrLinks } = await import("../lib/pr-link-ledger.js");
|
|
376
|
+
const { getLastSessionId } =
|
|
377
|
+
await import("../harness/jsonl-session-store.js");
|
|
378
|
+
const sid = !id || id === "last" ? getLastSessionId() : id;
|
|
379
|
+
const links = sid ? getPrLinks(sid) : [];
|
|
380
|
+
if (links.length === 0) {
|
|
381
|
+
logger.error(
|
|
382
|
+
"No linked PRs for this session — pass --checks-file to assess a PR directly.",
|
|
383
|
+
);
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
let target;
|
|
387
|
+
if (options.pr) {
|
|
388
|
+
target = links.find((l) => String(l.number) === String(options.pr));
|
|
389
|
+
if (!target) {
|
|
390
|
+
logger.error(`Session has no linked PR #${options.pr}.`);
|
|
391
|
+
process.exit(1);
|
|
392
|
+
}
|
|
393
|
+
} else {
|
|
394
|
+
// Newest by updatedAt (epoch-normalized; unknowns sort last).
|
|
395
|
+
target = links
|
|
396
|
+
.slice()
|
|
397
|
+
.sort(
|
|
398
|
+
(a, b) =>
|
|
399
|
+
(Date.parse(b.updatedAt) || 0) -
|
|
400
|
+
(Date.parse(a.updatedAt) || 0),
|
|
401
|
+
)[0];
|
|
402
|
+
}
|
|
403
|
+
signals = await fetchPrSignalsViaGh(target).catch(() => null);
|
|
404
|
+
if (!signals) {
|
|
405
|
+
logger.error(
|
|
406
|
+
`Could not fetch PR #${target.number} via gh — authenticate gh or pass --checks-file.`,
|
|
407
|
+
);
|
|
408
|
+
process.exit(1);
|
|
409
|
+
}
|
|
410
|
+
source = `gh:${target.repo || "?"}#${target.number}`;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const result = await renderPrStatus(signals, {
|
|
414
|
+
enabled: options.enable === true,
|
|
415
|
+
});
|
|
416
|
+
if (options.json) {
|
|
417
|
+
console.log(JSON.stringify({ source, ...result }, null, 2));
|
|
418
|
+
} else {
|
|
419
|
+
logger.log(
|
|
420
|
+
chalk.bold("PR automation status") + chalk.gray(` (${source})`),
|
|
421
|
+
);
|
|
422
|
+
for (const line of result.lines) logger.log(` ${line}`);
|
|
423
|
+
}
|
|
424
|
+
} catch (err) {
|
|
425
|
+
logger.error(`Failed: ${err.message}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
257
430
|
// session resume
|
|
258
431
|
session
|
|
259
432
|
.command("resume")
|
|
@@ -368,6 +541,10 @@ export function registerSessionCommand(program) {
|
|
|
368
541
|
"Session ID (or prefix; `last` = most recent agent session)",
|
|
369
542
|
)
|
|
370
543
|
.option("-o, --output <file>", "Output file path")
|
|
544
|
+
.option(
|
|
545
|
+
"--no-redact",
|
|
546
|
+
"Do NOT redact detected secrets from the exported transcript (default: redact)",
|
|
547
|
+
)
|
|
371
548
|
.action(async (id, options) => {
|
|
372
549
|
try {
|
|
373
550
|
let markdown = null;
|
|
@@ -406,6 +583,20 @@ export function registerSessionCommand(program) {
|
|
|
406
583
|
process.exit(1);
|
|
407
584
|
}
|
|
408
585
|
|
|
586
|
+
// §8.1 export gate: a transcript can carry provider tokens, JWTs,
|
|
587
|
+
// connection strings or cookies (in tool commands / results). Redact by
|
|
588
|
+
// default — an export leaves the machine and must fail toward "no leak".
|
|
589
|
+
// `--no-redact` keeps raw values for the user's own trusted backup.
|
|
590
|
+
if (options.redact !== false) {
|
|
591
|
+
const findings = scanSecrets(markdown);
|
|
592
|
+
if (findings.length > 0) {
|
|
593
|
+
markdown = redactSecrets(markdown);
|
|
594
|
+
logger.warn(
|
|
595
|
+
`Redacted ${findings.length} secret${findings.length === 1 ? "" : "s"} from the export (use --no-redact to keep raw values).`,
|
|
596
|
+
);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
409
600
|
if (options.output) {
|
|
410
601
|
fs.writeFileSync(options.output, markdown, "utf8");
|
|
411
602
|
logger.success(`Exported to ${chalk.cyan(options.output)}`);
|
package/src/commands/team.js
CHANGED
|
@@ -215,6 +215,14 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
215
215
|
"--merge",
|
|
216
216
|
"With --worktree: merge each clean branch back to base (conflicts reported, not forced)",
|
|
217
217
|
)
|
|
218
|
+
.option(
|
|
219
|
+
"--sparse-paths <paths>",
|
|
220
|
+
"With --worktree: comma-separated packages to sparse-checkout (only these are materialized)",
|
|
221
|
+
)
|
|
222
|
+
.option(
|
|
223
|
+
"--symlink-dirs <dirs>",
|
|
224
|
+
"With --worktree: comma-separated approved dependency dirs to symlink from the main checkout (e.g. node_modules)",
|
|
225
|
+
)
|
|
218
226
|
.option("--max-tasks <n>", "Budget: total task executions across the team")
|
|
219
227
|
.option("--max-tokens <n>", "Budget: total LLM tokens across the team")
|
|
220
228
|
.option(
|
|
@@ -348,7 +356,17 @@ export function registerTeamCommand(program, { logger } = {}) {
|
|
|
348
356
|
let runTask;
|
|
349
357
|
let coord = null;
|
|
350
358
|
if (options.worktree) {
|
|
351
|
-
|
|
359
|
+
const splitList = (v) =>
|
|
360
|
+
v
|
|
361
|
+
? String(v)
|
|
362
|
+
.split(",")
|
|
363
|
+
.map((s) => s.trim())
|
|
364
|
+
.filter(Boolean)
|
|
365
|
+
: null;
|
|
366
|
+
coord = new TeamWorktreeCoordinator(process.cwd(), {
|
|
367
|
+
sparsePaths: splitList(options.sparsePaths),
|
|
368
|
+
symlinkDirectories: splitList(options.symlinkDirs),
|
|
369
|
+
});
|
|
352
370
|
if (!coord.isGitRepo()) {
|
|
353
371
|
(log.error || console.error)(
|
|
354
372
|
"--worktree requires a git repository (run inside one)",
|