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
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin OPTIONS resolution (Phase 3 gap: typed optionsSchema + sensitive
|
|
3
|
+
* project-scope gate).
|
|
4
|
+
*
|
|
5
|
+
* A plugin declares `optionsSchema` (typed config with `scope` and `sensitive`
|
|
6
|
+
* in [[capabilities.js]]). Users supply option VALUES at two scopes:
|
|
7
|
+
* - USER scope → user data dir `plugin-options.json` (may hold secrets)
|
|
8
|
+
* - PROJECT scope → repo-local `.chainlesschain/plugin-options.json`
|
|
9
|
+
* (checked-in-able → MUST NOT hold secrets)
|
|
10
|
+
*
|
|
11
|
+
* The security-critical rule (already enforced by `validateOptions`): a
|
|
12
|
+
* SENSITIVE option can never come from project config. `resolvePluginOptions`
|
|
13
|
+
* layers defaults < project (non-sensitive only) < user, DROPS + WARNS on any
|
|
14
|
+
* sensitive/user-only option that appears in project config, and returns the
|
|
15
|
+
* merged values with a redacted view for logging.
|
|
16
|
+
*
|
|
17
|
+
* Store IO is injected (`_deps`) so unit tests never touch the real dirs; the
|
|
18
|
+
* resolver itself is pure.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import fs from "fs";
|
|
22
|
+
import path from "path";
|
|
23
|
+
import { getElectronUserDataDir } from "../paths.js";
|
|
24
|
+
import {
|
|
25
|
+
optionDefaults,
|
|
26
|
+
validateOptions,
|
|
27
|
+
redactSensitiveOptions,
|
|
28
|
+
} from "./capabilities.js";
|
|
29
|
+
|
|
30
|
+
export const _deps = {
|
|
31
|
+
existsSync: fs.existsSync,
|
|
32
|
+
readFileSync: fs.readFileSync,
|
|
33
|
+
writeFileSync: fs.writeFileSync,
|
|
34
|
+
mkdirSync: fs.mkdirSync,
|
|
35
|
+
userStorePath: () =>
|
|
36
|
+
path.join(getElectronUserDataDir(), "plugin-options.json"),
|
|
37
|
+
projectStorePath: (cwd) =>
|
|
38
|
+
path.join(cwd || process.cwd(), ".chainlesschain", "plugin-options.json"),
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function readJsonObject(p) {
|
|
42
|
+
try {
|
|
43
|
+
if (!_deps.existsSync(p)) return {};
|
|
44
|
+
const data = JSON.parse(_deps.readFileSync(p, "utf8"));
|
|
45
|
+
return data && typeof data === "object" && !Array.isArray(data) ? data : {};
|
|
46
|
+
} catch {
|
|
47
|
+
return {};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function writeJsonObject(p, obj) {
|
|
52
|
+
_deps.mkdirSync(path.dirname(p), { recursive: true });
|
|
53
|
+
_deps.writeFileSync(p, JSON.stringify(obj, null, 2), "utf8");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Load a plugin's option VALUES at the given scope (`user` | `project`). */
|
|
57
|
+
export function loadPluginOptionValues(name, scope, { cwd } = {}) {
|
|
58
|
+
const p =
|
|
59
|
+
scope === "project" ? _deps.projectStorePath(cwd) : _deps.userStorePath();
|
|
60
|
+
const store = readJsonObject(p);
|
|
61
|
+
const v = store[name];
|
|
62
|
+
return v && typeof v === "object" && !Array.isArray(v) ? v : {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Persist a plugin's option VALUES at a scope (replaces the plugin's entry). */
|
|
66
|
+
export function setPluginOptionValues(name, values, scope, { cwd } = {}) {
|
|
67
|
+
const p =
|
|
68
|
+
scope === "project" ? _deps.projectStorePath(cwd) : _deps.userStorePath();
|
|
69
|
+
const store = readJsonObject(p);
|
|
70
|
+
store[name] = values && typeof values === "object" ? values : {};
|
|
71
|
+
writeJsonObject(p, store);
|
|
72
|
+
return { name, scope, path: p };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Provided keys of `values` (that exist in schema) accepted at `scope`. */
|
|
76
|
+
function acceptedKeys(schema, values, scope) {
|
|
77
|
+
const res = validateOptions(schema, values, { scope });
|
|
78
|
+
const keys = new Set();
|
|
79
|
+
for (const key of Object.keys(values || {})) {
|
|
80
|
+
if (!Object.prototype.hasOwnProperty.call(schema, key)) continue; // unknown → ignored
|
|
81
|
+
// A provided key was accepted iff no error names it. validateOptions always
|
|
82
|
+
// quotes the offending key as `"<key>"`, so this is an exact match.
|
|
83
|
+
if (!res.errors.some((e) => e.includes(`"${key}"`))) keys.add(key);
|
|
84
|
+
}
|
|
85
|
+
return { res, keys };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* PURE: resolve effective option values from defaults + project + user config.
|
|
90
|
+
*
|
|
91
|
+
* Precedence: defaults < project (non-sensitive, non-user-only) < user.
|
|
92
|
+
* A sensitive or user-only option present in project config is DROPPED and a
|
|
93
|
+
* warning is recorded — a checked-in project file can never inject a secret.
|
|
94
|
+
*
|
|
95
|
+
* @returns {{ options:object, redacted:object, warnings:string[],
|
|
96
|
+
* sources:Record<string,'default'|'project'|'user'>,
|
|
97
|
+
* droppedFromProject:string[] }}
|
|
98
|
+
*/
|
|
99
|
+
export function resolvePluginOptions(
|
|
100
|
+
schema,
|
|
101
|
+
{ userValues, projectValues } = {},
|
|
102
|
+
) {
|
|
103
|
+
const s = schema || {};
|
|
104
|
+
const options = optionDefaults(s);
|
|
105
|
+
const sources = {};
|
|
106
|
+
for (const key of Object.keys(options)) sources[key] = "default";
|
|
107
|
+
|
|
108
|
+
const warnings = [];
|
|
109
|
+
const droppedFromProject = [];
|
|
110
|
+
|
|
111
|
+
// Project layer first (lower precedence). validateOptions errors on sensitive/
|
|
112
|
+
// user-only keys at project scope — those are the drops we surface + refuse.
|
|
113
|
+
const proj = acceptedKeys(s, projectValues, "project");
|
|
114
|
+
for (const err of proj.res.errors) {
|
|
115
|
+
if (/sensitive|user-scoped/.test(err)) {
|
|
116
|
+
const m = err.match(/"([^"]+)"/);
|
|
117
|
+
if (m) droppedFromProject.push(m[1]);
|
|
118
|
+
}
|
|
119
|
+
warnings.push(`project: ${err}`);
|
|
120
|
+
}
|
|
121
|
+
for (const w of proj.res.warnings) warnings.push(`project: ${w}`);
|
|
122
|
+
for (const key of proj.keys) {
|
|
123
|
+
options[key] = proj.res.normalized[key];
|
|
124
|
+
sources[key] = "project";
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// User layer on top (highest precedence; may carry secrets).
|
|
128
|
+
const user = acceptedKeys(s, userValues, "user");
|
|
129
|
+
for (const err of user.res.errors) warnings.push(`user: ${err}`);
|
|
130
|
+
for (const w of user.res.warnings) warnings.push(`user: ${w}`);
|
|
131
|
+
for (const key of user.keys) {
|
|
132
|
+
options[key] = user.res.normalized[key];
|
|
133
|
+
sources[key] = "user";
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
return {
|
|
137
|
+
options,
|
|
138
|
+
redacted: redactSensitiveOptions(s, options),
|
|
139
|
+
warnings,
|
|
140
|
+
sources,
|
|
141
|
+
droppedFromProject: [...new Set(droppedFromProject)],
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/** Load both scopes' values from disk and resolve against a schema. */
|
|
146
|
+
export function getResolvedPluginOptions(name, schema, { cwd } = {}) {
|
|
147
|
+
return resolvePluginOptions(schema, {
|
|
148
|
+
userValues: loadPluginOptionValues(name, "user", { cwd }),
|
|
149
|
+
projectValues: loadPluginOptionValues(name, "project", { cwd }),
|
|
150
|
+
});
|
|
151
|
+
}
|
|
@@ -25,6 +25,7 @@
|
|
|
25
25
|
import { enforcePluginPolicy } from "../plugin-security.js";
|
|
26
26
|
import { loadManagedSettings } from "../settings-loader.cjs";
|
|
27
27
|
import { verifyInstalledSignature } from "./signature.js";
|
|
28
|
+
import { isPluginCapabilityConsented } from "./capability-consent.js";
|
|
28
29
|
|
|
29
30
|
// Managed settings are org-admin controlled and effectively static for a
|
|
30
31
|
// session, and discoverPlugins is called by every collector — memoize the load
|
|
@@ -142,6 +143,67 @@ export function filterByManagedPolicy(plugins, managed) {
|
|
|
142
143
|
return { kept, dropped };
|
|
143
144
|
}
|
|
144
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Whether capability-consent must be ENFORCED at load time. The consent gate
|
|
148
|
+
* ([[capability-consent.js]]) already exists but was consulted only by `cc
|
|
149
|
+
* plugin consent`/`validate`/`doctor` — a plugin that declared capabilities and
|
|
150
|
+
* was never consented still loaded all six component types. Enforcement is
|
|
151
|
+
* OPT-IN (byte-identical default): an org sets `requirePluginCapabilityConsent`
|
|
152
|
+
* in managed settings, or a single user exports `CC_REQUIRE_PLUGIN_CONSENT=1`.
|
|
153
|
+
*
|
|
154
|
+
* @param {object|null} managed managed settings
|
|
155
|
+
* @param {object} [env] process env (injectable)
|
|
156
|
+
* @returns {boolean}
|
|
157
|
+
*/
|
|
158
|
+
export function capabilityConsentRequired(managed, env) {
|
|
159
|
+
const raw = (env || process.env).CC_REQUIRE_PLUGIN_CONSENT;
|
|
160
|
+
if (raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim())) return true;
|
|
161
|
+
return (
|
|
162
|
+
managed?.requirePluginCapabilityConsent === true ||
|
|
163
|
+
managed?.requirePluginCapabilityConsent === "require"
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Drop plugins that DECLARE capabilities but whose current capability SET has
|
|
169
|
+
* not been consented (first request or any widening). Fail-closed: a consent
|
|
170
|
+
* check that throws drops the plugin. Legacy plugins that declare no
|
|
171
|
+
* `permissions` block are UNAFFECTED — the same "未声明 legacy 不受限" rule the
|
|
172
|
+
* declaration audit uses. A plugin whose declared set is empty (all-deny) is
|
|
173
|
+
* auto-consented by `isPluginCapabilityConsented`, so it stays.
|
|
174
|
+
*
|
|
175
|
+
* @param {Array<{name, scope, manifest}>} plugins
|
|
176
|
+
* @returns {{ kept: Array, dropped: Array<{name, reason}> }}
|
|
177
|
+
*/
|
|
178
|
+
export function filterByCapabilityConsent(plugins) {
|
|
179
|
+
const kept = [];
|
|
180
|
+
const dropped = [];
|
|
181
|
+
for (const p of plugins) {
|
|
182
|
+
if (!p.manifest?.capabilitiesDeclared) {
|
|
183
|
+
kept.push(p); // legacy plugin, no declaration → not gated
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
let consented = false;
|
|
187
|
+
try {
|
|
188
|
+
consented = isPluginCapabilityConsented(
|
|
189
|
+
{ name: p.name, scope: p.scope },
|
|
190
|
+
p.manifest.capabilities,
|
|
191
|
+
);
|
|
192
|
+
} catch {
|
|
193
|
+
consented = false; // fail closed
|
|
194
|
+
}
|
|
195
|
+
if (consented) {
|
|
196
|
+
kept.push(p);
|
|
197
|
+
} else {
|
|
198
|
+
dropped.push({
|
|
199
|
+
name: p.name,
|
|
200
|
+
reason: `capabilities not consented — run \`cc plugin consent ${p.name} --grant\``,
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
return { kept, dropped };
|
|
205
|
+
}
|
|
206
|
+
|
|
145
207
|
let _warnedDropped = new Set();
|
|
146
208
|
|
|
147
209
|
/** One-time stderr warning naming plugins an org policy blocked from loading. */
|
|
@@ -27,6 +27,8 @@ import { parsePluginManifest } from "./manifest.js";
|
|
|
27
27
|
import {
|
|
28
28
|
loadManagedPluginPolicy,
|
|
29
29
|
filterByManagedPolicy,
|
|
30
|
+
filterByCapabilityConsent,
|
|
31
|
+
capabilityConsentRequired,
|
|
30
32
|
warnDroppedOnce,
|
|
31
33
|
} from "./policy.js";
|
|
32
34
|
|
|
@@ -160,10 +162,24 @@ export function discoverPlugins(opts = {}) {
|
|
|
160
162
|
env: opts.env,
|
|
161
163
|
managedSettingsFile: opts.managedSettingsFile,
|
|
162
164
|
});
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return
|
|
165
|
+
const consentGate = capabilityConsentRequired(managed, opts.env);
|
|
166
|
+
// Default (no managed policy AND consent enforcement off): byte-identical —
|
|
167
|
+
// return every discovered plugin, exactly as before.
|
|
168
|
+
if (!managed && !consentGate) return all;
|
|
169
|
+
let list = all;
|
|
170
|
+
if (managed) {
|
|
171
|
+
const { kept, dropped } = filterByManagedPolicy(list, managed);
|
|
172
|
+
if (dropped.length > 0) warnDroppedOnce(dropped);
|
|
173
|
+
list = kept;
|
|
174
|
+
}
|
|
175
|
+
// Capability-consent enforcement funnels through the SAME chokepoint, so an
|
|
176
|
+
// un-consented plugin loads NONE of its six component types (opt-in).
|
|
177
|
+
if (consentGate) {
|
|
178
|
+
const { kept, dropped } = filterByCapabilityConsent(list);
|
|
179
|
+
if (dropped.length > 0) warnDroppedOnce(dropped);
|
|
180
|
+
list = kept;
|
|
181
|
+
}
|
|
182
|
+
return list;
|
|
167
183
|
}
|
|
168
184
|
|
|
169
185
|
// ── internals ────────────────────────────────────────────────────────────
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PR/CI automation policy — the fail-closed decision engine behind "P1-4 PR/CI
|
|
3
|
+
* Monitor、Auto-fix 与受控合并" of CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-
|
|
4
|
+
* 07-13.md.
|
|
5
|
+
*
|
|
6
|
+
* Letting an agent iterate on CI failures and merge PRs is useful and dangerous.
|
|
7
|
+
* The gap doc pins the guardrails precisely: each failing check may be auto-
|
|
8
|
+
* fixed at most N times within a token/time budget; a fix must read only the
|
|
9
|
+
* CURRENT commit's logs (never chase a stale failure); auto-merge is OFF by
|
|
10
|
+
* default and, when on, requires branch protection, all required checks, an
|
|
11
|
+
* approving review and zero pending approvals — and never pushes a protected
|
|
12
|
+
* branch directly or bypasses protection.
|
|
13
|
+
*
|
|
14
|
+
* This module is those decisions as pure predicates. It holds no state and does
|
|
15
|
+
* no I/O — a caller feeds the observed PR/CI signals and gets an allow/deny with
|
|
16
|
+
* a named reason. Everything defaults to DENY: an unknown or missing signal
|
|
17
|
+
* blocks the automation rather than assuming it is safe.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/** Canonical CI check states. */
|
|
21
|
+
export const CHECK_STATE = Object.freeze({
|
|
22
|
+
PASSED: "passed",
|
|
23
|
+
FAILED: "failed",
|
|
24
|
+
PENDING: "pending",
|
|
25
|
+
NEUTRAL: "neutral",
|
|
26
|
+
ERROR: "error",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const CHECK_ALIASES = new Map([
|
|
30
|
+
["passed", CHECK_STATE.PASSED],
|
|
31
|
+
["pass", CHECK_STATE.PASSED],
|
|
32
|
+
["success", CHECK_STATE.PASSED],
|
|
33
|
+
["succeeded", CHECK_STATE.PASSED],
|
|
34
|
+
["green", CHECK_STATE.PASSED],
|
|
35
|
+
["failed", CHECK_STATE.FAILED],
|
|
36
|
+
["fail", CHECK_STATE.FAILED],
|
|
37
|
+
["failure", CHECK_STATE.FAILED],
|
|
38
|
+
["red", CHECK_STATE.FAILED],
|
|
39
|
+
["pending", CHECK_STATE.PENDING],
|
|
40
|
+
["queued", CHECK_STATE.PENDING],
|
|
41
|
+
["in_progress", CHECK_STATE.PENDING],
|
|
42
|
+
["running", CHECK_STATE.PENDING],
|
|
43
|
+
["neutral", CHECK_STATE.NEUTRAL],
|
|
44
|
+
["skipped", CHECK_STATE.NEUTRAL],
|
|
45
|
+
["cancelled", CHECK_STATE.NEUTRAL],
|
|
46
|
+
["canceled", CHECK_STATE.NEUTRAL],
|
|
47
|
+
["error", CHECK_STATE.ERROR],
|
|
48
|
+
["timed_out", CHECK_STATE.ERROR],
|
|
49
|
+
["action_required", CHECK_STATE.ERROR],
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
/** Normalize any provider's check conclusion/status to a canonical state. */
|
|
53
|
+
export function normalizeCheckState(value) {
|
|
54
|
+
if (typeof value !== "string") return null;
|
|
55
|
+
return CHECK_ALIASES.get(value.trim().toLowerCase()) || null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** A failing state that auto-fix could act on. */
|
|
59
|
+
function isFailing(state) {
|
|
60
|
+
return state === CHECK_STATE.FAILED || state === CHECK_STATE.ERROR;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Summarize a set of checks. `allPassed` is true only when there is at least one
|
|
65
|
+
* check and every one is passed or neutral (a bare PR with no checks is NOT
|
|
66
|
+
* "all passed" — required checks must actually exist and run).
|
|
67
|
+
*
|
|
68
|
+
* @param {Array<{name?:string, state?:string, conclusion?:string, status?:string}>} checks
|
|
69
|
+
*/
|
|
70
|
+
export function summarizeChecks(checks = []) {
|
|
71
|
+
const list = Array.isArray(checks) ? checks : [];
|
|
72
|
+
let passed = 0;
|
|
73
|
+
let failed = 0;
|
|
74
|
+
let pending = 0;
|
|
75
|
+
const byName = new Map();
|
|
76
|
+
for (const c of list) {
|
|
77
|
+
const state =
|
|
78
|
+
normalizeCheckState(c?.state) ??
|
|
79
|
+
normalizeCheckState(c?.conclusion) ??
|
|
80
|
+
normalizeCheckState(c?.status);
|
|
81
|
+
if (c?.name != null) byName.set(String(c.name), state);
|
|
82
|
+
if (state === CHECK_STATE.PASSED || state === CHECK_STATE.NEUTRAL) passed++;
|
|
83
|
+
else if (isFailing(state)) failed++;
|
|
84
|
+
else pending++; // pending / unknown → not yet decided
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
total: list.length,
|
|
88
|
+
passed,
|
|
89
|
+
failed,
|
|
90
|
+
pending,
|
|
91
|
+
anyFailed: failed > 0,
|
|
92
|
+
allPassed: list.length > 0 && failed === 0 && pending === 0,
|
|
93
|
+
byName,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function budgetExhausted(budget) {
|
|
98
|
+
if (!budget || typeof budget !== "object") return false;
|
|
99
|
+
const over = (spent, cap) =>
|
|
100
|
+
cap != null && Number.isFinite(Number(cap)) && Number(spent) >= Number(cap);
|
|
101
|
+
return (
|
|
102
|
+
over(budget.tokensSpent, budget.tokenBudget) ||
|
|
103
|
+
over(budget.timeSpentMs, budget.timeBudgetMs)
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Decide whether an agent may auto-fix ONE failing check right now. Fail-closed:
|
|
109
|
+
* anything unproven denies.
|
|
110
|
+
*
|
|
111
|
+
* @param {object} params
|
|
112
|
+
* @param {object} params.check {name, state|conclusion, logCommitSha}
|
|
113
|
+
* @param {string} params.headCommitSha the PR head the fix would target
|
|
114
|
+
* @param {number} params.iteration fixes already attempted for this check
|
|
115
|
+
* @param {number} params.maxIterations cap (default 3)
|
|
116
|
+
* @param {object} [params.budget] {tokensSpent, tokenBudget, timeSpentMs, timeBudgetMs}
|
|
117
|
+
* @returns {{allow:boolean, reason:string}}
|
|
118
|
+
*/
|
|
119
|
+
export function autoFixDecision(params = {}) {
|
|
120
|
+
const check = params.check || {};
|
|
121
|
+
const state =
|
|
122
|
+
normalizeCheckState(check.state) ?? normalizeCheckState(check.conclusion);
|
|
123
|
+
if (!isFailing(state)) {
|
|
124
|
+
return { allow: false, reason: "check-not-failing" };
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const maxIterations = Number.isFinite(Number(params.maxIterations))
|
|
128
|
+
? Number(params.maxIterations)
|
|
129
|
+
: 3;
|
|
130
|
+
const iteration = Number(params.iteration) || 0;
|
|
131
|
+
if (iteration >= maxIterations) {
|
|
132
|
+
return { allow: false, reason: "max-iterations-reached" };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Only fix the CURRENT failure: the log we would read must belong to the head
|
|
136
|
+
// commit. A missing/older log sha means we cannot prove freshness → deny.
|
|
137
|
+
const logSha = check.logCommitSha == null ? "" : String(check.logCommitSha);
|
|
138
|
+
const headSha =
|
|
139
|
+
params.headCommitSha == null ? "" : String(params.headCommitSha);
|
|
140
|
+
if (!logSha || !headSha) {
|
|
141
|
+
return { allow: false, reason: "commit-sha-unverifiable" };
|
|
142
|
+
}
|
|
143
|
+
if (logSha !== headSha) {
|
|
144
|
+
return { allow: false, reason: "stale-failure-log" };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (budgetExhausted(params.budget)) {
|
|
148
|
+
return { allow: false, reason: "budget-exhausted" };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return { allow: true, reason: "ok" };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Decide whether a PR may be auto-merged. Fail-closed and exhaustive: every
|
|
156
|
+
* unmet requirement is collected, and merge is allowed ONLY when the list is
|
|
157
|
+
* empty. Auto-merge is off unless `enabled === true`.
|
|
158
|
+
*
|
|
159
|
+
* @param {object} params
|
|
160
|
+
* @param {boolean} params.enabled auto-merge explicitly turned on
|
|
161
|
+
* @param {boolean} params.hasOpenPr a real PR exists (no direct-to-branch)
|
|
162
|
+
* @param {boolean} params.branchProtectionSatisfied
|
|
163
|
+
* @param {boolean} params.reviewApproved
|
|
164
|
+
* @param {number} params.pendingApprovals permission approvals still waiting
|
|
165
|
+
* @param {string[]} params.requiredChecks names that must be present AND passed
|
|
166
|
+
* @param {Array} params.checks observed checks (summarizeChecks input)
|
|
167
|
+
* @param {string} [params.targetBranch]
|
|
168
|
+
* @returns {{allow:boolean, reason:string, unmet:string[]}}
|
|
169
|
+
*/
|
|
170
|
+
export function autoMergeDecision(params = {}) {
|
|
171
|
+
const unmet = [];
|
|
172
|
+
|
|
173
|
+
if (params.enabled !== true) unmet.push("auto-merge-disabled");
|
|
174
|
+
if (params.hasOpenPr !== true) unmet.push("no-open-pr");
|
|
175
|
+
if (params.branchProtectionSatisfied !== true) {
|
|
176
|
+
unmet.push("branch-protection-unsatisfied");
|
|
177
|
+
}
|
|
178
|
+
if (params.reviewApproved !== true) unmet.push("review-not-approved");
|
|
179
|
+
if (Number(params.pendingApprovals) > 0) unmet.push("pending-approvals");
|
|
180
|
+
|
|
181
|
+
const summary = summarizeChecks(params.checks);
|
|
182
|
+
if (summary.anyFailed) unmet.push("checks-failing");
|
|
183
|
+
if (summary.pending > 0) unmet.push("checks-pending");
|
|
184
|
+
|
|
185
|
+
// Every REQUIRED check must be present and passed — a required check that
|
|
186
|
+
// never ran is a block, not a pass.
|
|
187
|
+
const required = Array.isArray(params.requiredChecks)
|
|
188
|
+
? params.requiredChecks
|
|
189
|
+
: [];
|
|
190
|
+
for (const name of required) {
|
|
191
|
+
const state = summary.byName.get(String(name));
|
|
192
|
+
if (state !== CHECK_STATE.PASSED && state !== CHECK_STATE.NEUTRAL) {
|
|
193
|
+
unmet.push(`required-check-missing:${name}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
allow: unmet.length === 0,
|
|
199
|
+
reason: unmet.length === 0 ? "ok" : unmet[0],
|
|
200
|
+
unmet,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* A compact, log-safe status line for the PR status bar (P1-4 "Session 顶部提供
|
|
206
|
+
* PR 状态条"). Carries only coordinates + a checks tally, never a token.
|
|
207
|
+
*
|
|
208
|
+
* @param {object} params {branch, prNumber, checks, reviewApproved, mergeable}
|
|
209
|
+
* @returns {string}
|
|
210
|
+
*/
|
|
211
|
+
export function describePrStatusBar(params = {}) {
|
|
212
|
+
const parts = [];
|
|
213
|
+
if (params.branch) parts.push(`branch:${params.branch}`);
|
|
214
|
+
if (params.prNumber != null) parts.push(`PR#${params.prNumber}`);
|
|
215
|
+
const s = summarizeChecks(params.checks);
|
|
216
|
+
parts.push(`checks:${s.passed}/${s.total}${s.anyFailed ? " ✗" : ""}`);
|
|
217
|
+
parts.push(
|
|
218
|
+
`review:${params.reviewApproved === true ? "approved" : "pending"}`,
|
|
219
|
+
);
|
|
220
|
+
if (params.mergeable != null) {
|
|
221
|
+
parts.push(`merge:${params.mergeable ? "ready" : "blocked"}`);
|
|
222
|
+
}
|
|
223
|
+
return parts.join(" · ");
|
|
224
|
+
}
|
|
@@ -477,4 +477,132 @@ export function loadProjectInstructionsBlock(opts = {}) {
|
|
|
477
477
|
}
|
|
478
478
|
}
|
|
479
479
|
|
|
480
|
+
function isDir(fs, p) {
|
|
481
|
+
try {
|
|
482
|
+
return fs.statSync(p).isDirectory();
|
|
483
|
+
} catch {
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Lazy subtree-instruction discovery (module 99 §5.3 "子目录指令按首次访问子树
|
|
490
|
+
* 懒加载"). Startup only loads the instruction chain from the project root DOWN
|
|
491
|
+
* to the cwd (see `findInstructionFiles`); a large monorepo's per-package
|
|
492
|
+
* `cc.md`/`CLAUDE.md`/`AGENTS.md` that sit BELOW the cwd are intentionally NOT
|
|
493
|
+
* injected up front — they cost tokens for subtrees a turn may never touch.
|
|
494
|
+
*
|
|
495
|
+
* This resolver answers "given a path a tool just accessed, which subtree
|
|
496
|
+
* instruction files should now be injected?" — the files in every directory
|
|
497
|
+
* strictly between `baseDir` (the already-loaded cwd) and the accessed path's
|
|
498
|
+
* directory, minus anything already loaded or excluded. Pure + deterministic;
|
|
499
|
+
* the tool-time injection itself is the caller's job.
|
|
500
|
+
*
|
|
501
|
+
* @param {object} opts
|
|
502
|
+
* @param {string} opts.repoRoot excludes are matched relative to this
|
|
503
|
+
* @param {string} opts.baseDir the startup cwd (its own file already loaded)
|
|
504
|
+
* @param {string} opts.accessedPath a file/dir a tool touched (abs or rel to baseDir)
|
|
505
|
+
* @param {Set<string>|string[]} [opts.alreadyLoaded] abs paths already injected
|
|
506
|
+
* @param {string[]} [opts.instructionExcludes]
|
|
507
|
+
* @returns {Array<{path:string, scope:"project", dir:string}>} shallowest-first
|
|
508
|
+
*/
|
|
509
|
+
export function resolveSubtreeInstructions(opts = {}) {
|
|
510
|
+
const { fs, path } = resolveDeps(opts);
|
|
511
|
+
const repoRoot = path.resolve(opts.repoRoot || opts.baseDir || process.cwd());
|
|
512
|
+
const baseDir = path.resolve(opts.baseDir || repoRoot);
|
|
513
|
+
const excludes = normalizeInstructionExcludes(opts.instructionExcludes);
|
|
514
|
+
const already =
|
|
515
|
+
opts.alreadyLoaded instanceof Set
|
|
516
|
+
? opts.alreadyLoaded
|
|
517
|
+
: new Set((opts.alreadyLoaded || []).map((p) => path.resolve(p)));
|
|
518
|
+
|
|
519
|
+
if (!opts.accessedPath) return [];
|
|
520
|
+
// Resolve the accessed path to a directory inside the subtree. A not-yet-
|
|
521
|
+
// created file (about to be written) stats as neither file nor dir → treat it
|
|
522
|
+
// as a file and use its parent.
|
|
523
|
+
let target = path.resolve(baseDir, opts.accessedPath);
|
|
524
|
+
if (!isDir(fs, target)) target = path.dirname(target);
|
|
525
|
+
|
|
526
|
+
// Subtree instructions apply only to DESCENDANTS of baseDir (ancestors were
|
|
527
|
+
// loaded at startup). Reject a target that escapes baseDir or the repo.
|
|
528
|
+
const relFromBase = path.relative(baseDir, target);
|
|
529
|
+
if (
|
|
530
|
+
!relFromBase ||
|
|
531
|
+
relFromBase.startsWith("..") ||
|
|
532
|
+
path.isAbsolute(relFromBase)
|
|
533
|
+
) {
|
|
534
|
+
return [];
|
|
535
|
+
}
|
|
536
|
+
const relFromRoot = path.relative(repoRoot, target);
|
|
537
|
+
if (relFromRoot.startsWith("..") || path.isAbsolute(relFromRoot)) return [];
|
|
538
|
+
|
|
539
|
+
// Directories strictly between baseDir (exclusive) and target (inclusive).
|
|
540
|
+
const dirs = [];
|
|
541
|
+
let dir = target;
|
|
542
|
+
while (dir !== baseDir) {
|
|
543
|
+
dirs.push(dir);
|
|
544
|
+
const parent = path.dirname(dir);
|
|
545
|
+
if (parent === dir) break; // hit fs root without meeting baseDir
|
|
546
|
+
dir = parent;
|
|
547
|
+
}
|
|
548
|
+
dirs.reverse(); // shallowest first
|
|
549
|
+
|
|
550
|
+
const out = [];
|
|
551
|
+
for (const d of dirs) {
|
|
552
|
+
const file = firstExisting(fs, path, d, PROJECT_FILE_NAMES);
|
|
553
|
+
if (!file) continue;
|
|
554
|
+
const abs = path.resolve(file);
|
|
555
|
+
if (already.has(abs)) continue;
|
|
556
|
+
if (
|
|
557
|
+
excludes.length > 0 &&
|
|
558
|
+
pathIsExcluded(path.relative(repoRoot, abs), excludes)
|
|
559
|
+
) {
|
|
560
|
+
continue;
|
|
561
|
+
}
|
|
562
|
+
out.push({ path: abs, scope: "project", dir: d });
|
|
563
|
+
}
|
|
564
|
+
return out;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Stateful companion to `resolveSubtreeInstructions`: tracks which subtree
|
|
569
|
+
* directories have already been injected across a session so a second access to
|
|
570
|
+
* the same subtree is a no-op. `onAccess(path)` returns ONLY the freshly
|
|
571
|
+
* discovered files. Fail-open (any error → []).
|
|
572
|
+
*/
|
|
573
|
+
export class SubtreeInstructionLoader {
|
|
574
|
+
constructor(opts = {}) {
|
|
575
|
+
const path = opts.deps?.path || pathDefault;
|
|
576
|
+
this._opts = opts;
|
|
577
|
+
this._loadedDirs = new Set();
|
|
578
|
+
this._loadedFiles = new Set(
|
|
579
|
+
(opts.alreadyLoaded || []).map((p) => path.resolve(p)),
|
|
580
|
+
);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
/** @returns {Array<{path:string, scope:"project", dir:string}>} newly injected */
|
|
584
|
+
onAccess(accessedPath) {
|
|
585
|
+
try {
|
|
586
|
+
const found = resolveSubtreeInstructions({
|
|
587
|
+
...this._opts,
|
|
588
|
+
accessedPath,
|
|
589
|
+
alreadyLoaded: this._loadedFiles,
|
|
590
|
+
});
|
|
591
|
+
const fresh = found.filter((f) => !this._loadedDirs.has(f.dir));
|
|
592
|
+
for (const f of fresh) {
|
|
593
|
+
this._loadedDirs.add(f.dir);
|
|
594
|
+
this._loadedFiles.add(f.path);
|
|
595
|
+
}
|
|
596
|
+
return fresh;
|
|
597
|
+
} catch {
|
|
598
|
+
return [];
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** Absolute paths injected so far (startup set + every onAccess discovery). */
|
|
603
|
+
loadedFiles() {
|
|
604
|
+
return [...this._loadedFiles];
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
480
608
|
export const _deps = { fs: fsDefault, path: pathDefault, os: osDefault };
|