chainlesschain 0.162.163 → 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-BQ0kgfA1.js → AIOps-BqRkaHGb.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Dr1MDANe.js → ActionButton-DWO7OZLC.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CZ5vsaXW.js → Analytics-C5oaVezM.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-BIBga881.js → AppLayout-CsUESDoZ.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-vpXHRU3u.js → Artifacts-Dragqu5k.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-B8exuoub.js → Audit-CQsaHctY.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-923evRRN.js → BackgroundAgents-DdG1jNSM.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-sr_sswlQ.js → Backup-DN6sjK4z.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CnW-4XKT.js → BaseInput-Cxm_ZTCl.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-FZCsTZIL.js → Chat-DVXGmA56.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDPd9qT0.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DFrxB59H.js → Checkbox-DEc4vD_A.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-_b6WEel8.js → Codegen-CKiTcv36.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CkbnnZ-K.js → Col-Br9UUNN0.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BxM_82zW.js → Community-NeUH_70T.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DYinP45R.js → Compact-BqR-z2Df.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DyXp3sSP.js → Compliance-DcWB_SgZ.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BEr2DhW7.js → Cowork-BfhgcudF.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-B-Vwo1hN.js → Cron-LaU29LgD.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DbLxKddd.js → Crosschain-B2ZB9uwW.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BsQ_LFw9.js → DID-BuHl38A2.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-lVlXQHu5.js → Dashboard-m627yqvZ.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Beo_foO7.js → Dropdown-C7bxF8F5.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DszHIXED.js → EmailListRenderer-DPLPQzLp.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DcAwoh81.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CuJLa2eb.js → Federation-Bvkzg88z.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cxd1lxhc.js → FormItemContext-BTsiA7h_.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BBvq5Jsw.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
- package/src/assets/web-panel/assets/{Git-WqPKetx6.js → Git-DS_j6r25.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-CWYtY3ke.js → Governance-CKRG3Rsl.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-BnX1iiNV.js → Inference-DW8INoNQ.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-BJuM8GN5.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DqLMNntm.js → Logs-wqi0uxuR.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BqYwEYou.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-_vGfmhNl.js → Marketplace-CMlTuPLT.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BeJDOkDj.js → McpTools-CSx4m5JJ.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Da0EEZDO.js → Memory-vnQCUy-_.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DSuzcCCy.js → MobileBridge-BV2NA742.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-BE9YgdA0.js → Mtc-CzK_FPB1.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-fC2BpO5S.js → MtcAudit-Cb7G9oL3.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-D24Xxqw3.js → Multisig-qJxlUyzr.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Bs3oGvS8.js → NLProgramming-DqaBJ7Te.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BFjmAA-3.js → Notes-CwhTJkRi.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BX_9Bhv0.js → NotificationSettings-YDs4Fal4.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-wjkZwJXI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DBcdsVEP.js → Organization-CEmQm9n8.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-DDyaF9vC.js → Overflow-zby_P_LA.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BB78L4he.js → P2P-DsmDNFNT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DqTiZFum.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-INbnWv4j.js → Permissions-DuzQ6me5.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-BST-VmTq.js → PersonalDataHub-DZbeN613.js} +2 -2
- package/src/assets/web-panel/assets/{Pipeline-CE7_tS2k.js → Pipeline-CoNSVAUS.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-hK5L8dOB.js → Privacy-OYJEAaMC.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BvkkUoDn.js → ProjectInit-CWIapV--.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-u7_EUtkm.js → ProjectSettings-CX8xMxZn.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-5Fmt_8Nw.js → Projects-BLCN8YyY.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B9eanarR.js → Providers-BV_sxp75.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-C0dX5N2f.js → QrScannerModal-BTnRrHRp.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CKTpG-PZ.js → QuickAsk-D1KZg018.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Px_w_XQM.js → Recommend-COm5cuNg.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Di0fnYkM.js → RemoteSession-0saWEkYI.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-vesnWjXt.js → Reputation-Dr7iKLS2.js} +1 -1
- package/src/assets/web-panel/assets/{Row-exbY6LkT.js → Row-C1vpPBcr.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-B3O3x70t.js → RssFeed-CxlWrtNj.js} +2 -2
- package/src/assets/web-panel/assets/{Search-uRA0DhNz.js → Search-BT2V8dPN.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CViqdH6K.js → Security-bm9V3c5k.js} +4 -4
- package/src/assets/web-panel/assets/{Services-CkXS97WN.js → Services-CocH1yyX.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DxW5WYnE.js → Skeleton-DHItlMdd.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-CLxmGFse.js → Skills-C59sLc4w.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-Cr5fziXq.js → Sla-Crj4BMc8.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-H0JSb3cp.js → SpeechSettings-DX4KFceY.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-DMYWMae7.js → SyncSettings-BD_iwQLr.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CbYyDdF4.js → Tasks-BmCTzYsH.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-Cpc8JjiK.js → Templates-BGUaxZYl.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CRh77Jda.js → Tenant-BS0tJx2O.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-4D8tNBO5.js → Terminal--tHSAsld.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-D41jzzbG.js → TimelineRenderer-D9-MFxS4.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-C_jWEy6b.js → Tokens-CiCB485f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-Dk1ZUhsy.js → Trigger-SmCFuW_l.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLVy4l7m.js → Trust-DkK3Rqe0.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C-FeqFDj.js → UkeySign-CDM4LGdB.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CqVZzyMm.js → VideoEditing-IDB7DJXt.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DX9BW9pu.js → Wallet-Bikj5cmt.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-7hQDpWw_.js → WebAuthn-C9abWlAw.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-6XqUAO0j.js → WorkflowEditor-DrbRKVky.js} +1 -1
- package/src/assets/web-panel/assets/{chat-5R_vkaJG.js → chat-CccCSDQf.js} +1 -1
- package/src/assets/web-panel/assets/{colors-huUPZdC-.js → colors-CXObXLhb.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CfJlgisE.js → compact-item-B5kfYAxC.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-jO8heofr.js → createContext-DmiPBfip.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DY8Vxqx7.js → hasIn-Q6lYteAc.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtEXSoPL.js → index-25Wlqps4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BsBtRWbA.js → index-33U29UNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQS-uPOK.js → index-4oXBDczR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DXBcvUnf.js → index-BGYXvMc5.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2iVSFpy.js → index-BJCCa0s_.js} +1 -1
- package/src/assets/web-panel/assets/{index-A48PtXw9.js → index-BJJ5DhKI.js} +1 -1
- package/src/assets/web-panel/assets/{index-Db1cLWir.js → index-BLOzViJ6.js} +1 -1
- package/src/assets/web-panel/assets/{index-vXs3B8GJ.js → index-BRrbUMoG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHE8OIls.js → index-BUFpT17Q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmqGerpC.js → index-BkRDiqGc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzKmteNS.js → index-BsfvW6my.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAON7TzU.js → index-By-kfPED.js} +1 -1
- package/src/assets/web-panel/assets/{index-DoI2sZm7.js → index-BzFTcQsc.js} +1 -1
- package/src/assets/web-panel/assets/{index-j3O2EEJe.js → index-C2FXdboq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRElRhyS.js → index-C5XOlTHN.js} +1 -1
- package/src/assets/web-panel/assets/{index-KRHroryI.js → index-CLhBDS2w.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bxax9vQ6.js → index-CMq7UeoT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BfCx4pFT.js → index-CVMTWNKl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA6sO5VD.js → index-Caghi-yL.js} +1 -1
- package/src/assets/web-panel/assets/{index-CA90TZxG.js → index-CdB6FuSH.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cbe9G0V4.js → index-CoK8-kWQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0pet5Uy.js → index-CsO3GUAY.js} +1 -1
- package/src/assets/web-panel/assets/{index-ChvHubX3.js → index-CwZBa20Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRXOP09X.js → index-D-kASucw.js} +1 -1
- package/src/assets/web-panel/assets/{index-B74OXFxC.js → index-D8eBw7vW.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1VLuww0.js → index-D9wgifk4.js} +1 -1
- package/src/assets/web-panel/assets/{index-p85YaaKw.js → index-DAYCTc07.js} +1 -1
- package/src/assets/web-panel/assets/{index-BliaT02i.js → index-DF6Q8l2h.js} +1 -1
- package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
- package/src/assets/web-panel/assets/{index-Cjxbu68P.js → index-DgTLApS9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cis_Ip7H.js → index-DgxVJ8pg.js} +1 -1
- package/src/assets/web-panel/assets/{index-v3pTaEQm.js → index-DlJvIqoR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BYLBPpt8.js → index-Dw4eHfsG.js} +1 -1
- package/src/assets/web-panel/assets/{index-B300RPnT.js → index-Rh-ytwow.js} +3 -3
- package/src/assets/web-panel/assets/{index-CLhBh-qE.js → index-StAEt2pq.js} +1 -1
- package/src/assets/web-panel/assets/{index-d1xkbtNz.js → index-bzfw951k.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCqxyvqp.js → index-fSUMbMvv.js} +1 -1
- package/src/assets/web-panel/assets/{index-dKx57io7.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-MCB7-raZ.js → initDefaultProps-BNq7X72N.js} +1 -1
- package/src/assets/web-panel/assets/{motion-UWDF9swV.js → motion-DOf9viTc.js} +1 -1
- package/src/assets/web-panel/assets/{move-CsTmgXZU.js → move-CRHTcW4x.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-fQy6KGLg.js → mtc-parser-Cyoozgm2.js} +1 -1
- package/src/assets/web-panel/assets/{omit-woPnEjd1.js → omit-6-TjFNru.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-Bs2dZ1gf.js → pickAttrs-DlUmO1zi.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C7EVgPkg.js → placementArrow-CFOL8mIR.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BH2kYCTN.js → responsiveObserve-BtaFK5zd.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BFBP2V_e.js → slide-BIbc2bur.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-xdDxDYse.js → statusUtils-BXX9d6-w.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-aAWgPPQf.js → styleChecker-BGv10Bpn.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DkGSAWoO.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-BrOih40N.js → useFs-FY4IL4jn.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-eIYrgMqO.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-B8lutNSE.js → vnode-BM3OHxAT.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CffNre7V.js → zoom-D1fbFiHB.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/agenda.js +71 -14
- package/src/commands/doctor.js +85 -1
- package/src/commands/plugin.js +226 -9
- package/src/commands/review.js +17 -1
- package/src/commands/session.js +191 -0
- package/src/commands/team.js +19 -1
- package/src/data/changelog.json +5 -4
- package/src/gateways/ws/session-protocol.js +50 -0
- 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 +30 -1
- 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/diagnostic-bundle.js +230 -0
- package/src/lib/doctor-checkup.js +223 -4
- package/src/lib/execution-location.js +286 -0
- package/src/lib/headless-manifest.js +13 -40
- package/src/lib/hook-stats-store.cjs +197 -0
- package/src/lib/idempotency.js +109 -0
- 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 +28 -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/pr-automation-policy.js +224 -0
- package/src/lib/remote-approval-bridge.js +66 -13
- package/src/lib/repl-rewind.js +101 -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/secret-scan.js +409 -0
- package/src/lib/session-branch.js +0 -0
- package/src/lib/side-effect-ledger.js +105 -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 +36 -4
- package/src/lib/sub-agent-registry.js +19 -0
- package/src/lib/telemetry/span-recorder.js +17 -4
- package/src/lib/terminal-context.js +195 -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 +152 -38
- package/src/runtime/agent-core.js +191 -13
- package/src/runtime/diagnostics.js +60 -0
- package/src/runtime/headless-runner.js +126 -1
- package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +0 -1
- package/src/assets/web-panel/assets/index-BxUKMRpP.js +0 -1
- package/src/assets/web-panel/assets/index-DscU9iPI.js +0 -1
package/src/commands/doctor.js
CHANGED
|
@@ -1,12 +1,80 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
+
import { existsSync, readdirSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join as pathJoin } from "node:path";
|
|
2
4
|
import logger from "../lib/logger.js";
|
|
3
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
collectDoctorReport,
|
|
7
|
+
collectWorktrees,
|
|
8
|
+
buildDoctorDiagnosticInput,
|
|
9
|
+
} from "../runtime/diagnostics.js";
|
|
10
|
+
import { getHomeDir } from "../lib/paths.js";
|
|
11
|
+
import { exportDiagnosticBundle } from "../lib/diagnostic-bundle.js";
|
|
4
12
|
import {
|
|
5
13
|
collectCheckupSections,
|
|
6
14
|
runCheckupFixes,
|
|
7
15
|
unsafeFixCommands,
|
|
8
16
|
} from "../lib/doctor-checkup.js";
|
|
9
17
|
|
|
18
|
+
/** Best-effort top-level `*.lock` files under the config home dir. */
|
|
19
|
+
function collectLockfiles() {
|
|
20
|
+
try {
|
|
21
|
+
const home = getHomeDir();
|
|
22
|
+
if (!home || !existsSync(home)) return [];
|
|
23
|
+
return readdirSync(home)
|
|
24
|
+
.filter((f) => f.endsWith(".lock"))
|
|
25
|
+
.map((f) => pathJoin(home, f));
|
|
26
|
+
} catch {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Assemble a de-identified diagnostic bundle from the live doctor report and
|
|
33
|
+
* run the mandatory secret-scan gate. On a clean gate, writes JSON to `outPath`
|
|
34
|
+
* (or prints it) and returns `{ ok: true }`. If the gate blocks (residual
|
|
35
|
+
* secret — defense-in-depth, should not happen post-redaction), prints the leak
|
|
36
|
+
* locations and returns `{ ok: false }` without exporting.
|
|
37
|
+
*/
|
|
38
|
+
export async function runExportBundle(report, outPath) {
|
|
39
|
+
const input = buildDoctorDiagnosticInput(report, {
|
|
40
|
+
env: process.env,
|
|
41
|
+
worktrees: collectWorktrees(),
|
|
42
|
+
lockfiles: collectLockfiles(),
|
|
43
|
+
});
|
|
44
|
+
const { ok, bundle, findings } = exportDiagnosticBundle(input);
|
|
45
|
+
|
|
46
|
+
if (!ok) {
|
|
47
|
+
logger.log(
|
|
48
|
+
chalk.red(
|
|
49
|
+
"\n ✖ Diagnostic bundle blocked by secret scan (not exported).",
|
|
50
|
+
),
|
|
51
|
+
);
|
|
52
|
+
for (const f of findings) {
|
|
53
|
+
logger.log(chalk.gray(` ${f.path || "?"}: ${f.category || "secret"}`));
|
|
54
|
+
}
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return { ok: false };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const json = JSON.stringify(bundle, null, 2);
|
|
60
|
+
if (outPath) {
|
|
61
|
+
writeFileSync(outPath, json, "utf-8");
|
|
62
|
+
logger.log(
|
|
63
|
+
chalk.green(
|
|
64
|
+
`\n ✔ De-identified diagnostic bundle written to ${outPath}`,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
logger.log(
|
|
68
|
+
chalk.gray(
|
|
69
|
+
" Excludes source, API keys, cookies, env values and terminal output.",
|
|
70
|
+
),
|
|
71
|
+
);
|
|
72
|
+
} else {
|
|
73
|
+
console.log(json);
|
|
74
|
+
}
|
|
75
|
+
return { ok: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
10
78
|
const LEVEL_ICON = {
|
|
11
79
|
ok: () => chalk.green("✔"),
|
|
12
80
|
warn: () => chalk.yellow("⚠"),
|
|
@@ -21,8 +89,24 @@ export function registerDoctorCommand(program) {
|
|
|
21
89
|
.description("Diagnose your ChainlessChain environment (alias: checkup)")
|
|
22
90
|
.option("--json", "Output as machine-readable JSON")
|
|
23
91
|
.option("--fix", "Apply safe fixes (stale job files, git worktree prune)")
|
|
92
|
+
.option(
|
|
93
|
+
"--export-bundle [path]",
|
|
94
|
+
"Export a de-identified diagnostic bundle (secret-scanned; excludes source/keys/env values). Writes to <path>, or stdout when no path is given.",
|
|
95
|
+
)
|
|
24
96
|
.action(async (options) => {
|
|
25
97
|
const report = await collectDoctorReport();
|
|
98
|
+
|
|
99
|
+
// One-click de-identified diagnostic bundle. Bypasses the human-readable
|
|
100
|
+
// render entirely: it is a support artifact, not a terminal read-out.
|
|
101
|
+
if (options.exportBundle !== undefined) {
|
|
102
|
+
const outPath =
|
|
103
|
+
typeof options.exportBundle === "string"
|
|
104
|
+
? options.exportBundle
|
|
105
|
+
: null;
|
|
106
|
+
await runExportBundle(report, outPath);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
|
|
26
110
|
const sections = await collectCheckupSections({ cwd: process.cwd() });
|
|
27
111
|
const fixResults = options.fix ? await runCheckupFixes(sections) : null;
|
|
28
112
|
|
package/src/commands/plugin.js
CHANGED
|
@@ -64,8 +64,12 @@ async function resolvePluginCapabilityNotice(name, scope, cwd) {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Print the capability notice (text mode) after an install/upgrade. Pass
|
|
69
|
+
* `{ hint: false }` to omit the trailing `cc plugin consent … --grant` pointer
|
|
70
|
+
* when consent is being granted inline (explicit flag or interactive prompt).
|
|
71
|
+
*/
|
|
72
|
+
function printPluginCapabilityNotice(name, notice, { hint = true } = {}) {
|
|
69
73
|
if (!notice) return;
|
|
70
74
|
logger.log(chalk.bold("\nCapabilities (declared):"));
|
|
71
75
|
for (const l of notice.declared) logger.log(` ${chalk.magenta(l)}`);
|
|
@@ -78,9 +82,81 @@ function printPluginCapabilityNotice(name, notice) {
|
|
|
78
82
|
);
|
|
79
83
|
if (notice.added.length)
|
|
80
84
|
logger.log(chalk.yellow(` new: ${notice.added.join(", ")}`));
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
if (hint)
|
|
86
|
+
logger.log(
|
|
87
|
+
chalk.dim(` run \`cc plugin consent ${name} --grant\` to allow them`),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Record consent for a freshly installed/upgraded plugin's currently-declared
|
|
93
|
+
* capabilities (re-discovers to get the raw declared set + version). Best-effort
|
|
94
|
+
* — returns false rather than throwing so it can never break an install.
|
|
95
|
+
*/
|
|
96
|
+
async function grantInstalledPluginCapabilities(name, scope, cwd) {
|
|
97
|
+
try {
|
|
98
|
+
const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
|
|
99
|
+
const consent = await import("../lib/plugin-runtime/capability-consent.js");
|
|
100
|
+
const installed = discoverPlugins({ cwd, skipPolicy: true }).find(
|
|
101
|
+
(p) => p.name === name && p.scope === scope,
|
|
102
|
+
);
|
|
103
|
+
if (!installed) return false;
|
|
104
|
+
const declared = installed.manifest?.capabilities;
|
|
105
|
+
if (!declared || consent.capabilitiesAreEmpty(declared)) return false;
|
|
106
|
+
consent.consentPluginCapabilities(name, {
|
|
107
|
+
scope,
|
|
108
|
+
version: installed.version,
|
|
109
|
+
capabilities: declared,
|
|
110
|
+
});
|
|
111
|
+
return true;
|
|
112
|
+
} catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Handle the capability-consent gate after a text-mode install/upgrade: print
|
|
119
|
+
* the notice and, when consent is required, either auto-grant (explicit
|
|
120
|
+
* `--grant-capabilities`) or block interactively (TTY) to grant it right away
|
|
121
|
+
* instead of forcing a separate `cc plugin consent --grant` step. Returns true
|
|
122
|
+
* if consent was recorded during this call.
|
|
123
|
+
*/
|
|
124
|
+
async function applyCapabilityConsentGate(name, scope, notice, cwd, opts = {}) {
|
|
125
|
+
const consent = await import("../lib/plugin-runtime/capability-consent.js");
|
|
126
|
+
const action = consent.resolveConsentAction(notice, {
|
|
127
|
+
grant: opts.grant === true,
|
|
128
|
+
interactive: opts.interactive === true,
|
|
129
|
+
});
|
|
130
|
+
if (action === "advisory") {
|
|
131
|
+
printPluginCapabilityNotice(name, notice);
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
// Show the ⚠ notice + new-capability diff before deciding; suppress the
|
|
135
|
+
// "run cc plugin consent" pointer since we grant inline here.
|
|
136
|
+
printPluginCapabilityNotice(name, notice, { hint: false });
|
|
137
|
+
if (action === "prompt") {
|
|
138
|
+
let ok = false;
|
|
139
|
+
try {
|
|
140
|
+
const { confirm } = await import("@inquirer/prompts");
|
|
141
|
+
ok = await confirm({
|
|
142
|
+
message: `Grant these capabilities to ${name} now?`,
|
|
143
|
+
default: false,
|
|
144
|
+
});
|
|
145
|
+
} catch {
|
|
146
|
+
ok = false; // Ctrl-C / non-interactive → treat as decline (fail closed)
|
|
147
|
+
}
|
|
148
|
+
if (!ok) {
|
|
149
|
+
logger.log(
|
|
150
|
+
chalk.dim(
|
|
151
|
+
" capabilities not granted — components needing them stay disabled",
|
|
152
|
+
),
|
|
153
|
+
);
|
|
154
|
+
return false;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const granted = await grantInstalledPluginCapabilities(name, scope, cwd);
|
|
158
|
+
if (granted) logger.success(` ✔ capability consent granted for ${name}`);
|
|
159
|
+
return granted;
|
|
84
160
|
}
|
|
85
161
|
|
|
86
162
|
export function registerPluginCommand(program) {
|
|
@@ -738,6 +814,10 @@ export function registerPluginCommand(program) {
|
|
|
738
814
|
"--allow-insecure-registry",
|
|
739
815
|
"Allow a plain-HTTP registry URL (MITM risk — trusted networks only)",
|
|
740
816
|
)
|
|
817
|
+
.option(
|
|
818
|
+
"--grant-capabilities",
|
|
819
|
+
"Grant the plugin's declared capabilities at install time (no separate consent step)",
|
|
820
|
+
)
|
|
741
821
|
.option("--json", "Output as JSON")
|
|
742
822
|
.action(async (source, options) => {
|
|
743
823
|
const { installFromSource } =
|
|
@@ -806,8 +886,21 @@ export function registerPluginCommand(program) {
|
|
|
806
886
|
process.cwd(),
|
|
807
887
|
);
|
|
808
888
|
if (options.json) {
|
|
889
|
+
// Non-interactive JSON path: honor an explicit --grant-capabilities so
|
|
890
|
+
// scripted installs can consent atomically; never prompt.
|
|
891
|
+
let capabilitiesGranted = false;
|
|
892
|
+
if (options.grantCapabilities && capNotice && !capNotice.consented)
|
|
893
|
+
capabilitiesGranted = await grantInstalledPluginCapabilities(
|
|
894
|
+
res.name,
|
|
895
|
+
res.scope,
|
|
896
|
+
process.cwd(),
|
|
897
|
+
);
|
|
809
898
|
console.log(
|
|
810
|
-
JSON.stringify(
|
|
899
|
+
JSON.stringify(
|
|
900
|
+
{ ...res, capabilities: capNotice, capabilitiesGranted },
|
|
901
|
+
null,
|
|
902
|
+
2,
|
|
903
|
+
),
|
|
811
904
|
);
|
|
812
905
|
} else {
|
|
813
906
|
logger.success(
|
|
@@ -817,7 +910,16 @@ export function registerPluginCommand(program) {
|
|
|
817
910
|
logger.log(chalk.gray(` → ${res.dir}`));
|
|
818
911
|
for (const w of res.warnings || [])
|
|
819
912
|
logger.log(chalk.yellow(` ⚠ ${w}`));
|
|
820
|
-
|
|
913
|
+
await applyCapabilityConsentGate(
|
|
914
|
+
res.name,
|
|
915
|
+
res.scope,
|
|
916
|
+
capNotice,
|
|
917
|
+
process.cwd(),
|
|
918
|
+
{
|
|
919
|
+
grant: options.grantCapabilities === true,
|
|
920
|
+
interactive: Boolean(process.stdin.isTTY),
|
|
921
|
+
},
|
|
922
|
+
);
|
|
821
923
|
}
|
|
822
924
|
} catch (err) {
|
|
823
925
|
logger.error(`Install failed: ${err.message}`);
|
|
@@ -1132,6 +1234,110 @@ export function registerPluginCommand(program) {
|
|
|
1132
1234
|
}
|
|
1133
1235
|
});
|
|
1134
1236
|
|
|
1237
|
+
// plugin options — view a plugin's resolved typed options, or set values at a
|
|
1238
|
+
// scope. The security-critical rule: a SENSITIVE option can never be set from
|
|
1239
|
+
// PROJECT scope (a checked-in file must not inject secrets) — such values are
|
|
1240
|
+
// dropped + warned; use `--scope user`.
|
|
1241
|
+
plugin
|
|
1242
|
+
.command("options <name>")
|
|
1243
|
+
.description(
|
|
1244
|
+
"View or set a plugin's typed options (--set key=value, --scope user|project)",
|
|
1245
|
+
)
|
|
1246
|
+
.option("--scope <scope>", "Scope for --set (user|project)", "user")
|
|
1247
|
+
.option(
|
|
1248
|
+
"--set <pair>",
|
|
1249
|
+
"Set an option value (key=value); repeatable",
|
|
1250
|
+
(v, acc) => {
|
|
1251
|
+
acc.push(v);
|
|
1252
|
+
return acc;
|
|
1253
|
+
},
|
|
1254
|
+
[],
|
|
1255
|
+
)
|
|
1256
|
+
.option("--json", "Output as JSON")
|
|
1257
|
+
.action(async (name, options) => {
|
|
1258
|
+
const { discoverPlugins } =
|
|
1259
|
+
await import("../lib/plugin-runtime/scopes.js");
|
|
1260
|
+
const optsMod = await import("../lib/plugin-runtime/plugin-options.js");
|
|
1261
|
+
|
|
1262
|
+
let installed = null;
|
|
1263
|
+
try {
|
|
1264
|
+
installed = discoverPlugins({
|
|
1265
|
+
cwd: process.cwd(),
|
|
1266
|
+
skipPolicy: true,
|
|
1267
|
+
}).find((p) => p.name === name);
|
|
1268
|
+
} catch {
|
|
1269
|
+
/* discovery best-effort */
|
|
1270
|
+
}
|
|
1271
|
+
if (!installed) {
|
|
1272
|
+
logger.error(`${name} is not installed`);
|
|
1273
|
+
process.exitCode = 1;
|
|
1274
|
+
return;
|
|
1275
|
+
}
|
|
1276
|
+
const schema = installed.manifest?.optionsSchema || {};
|
|
1277
|
+
|
|
1278
|
+
// --set: persist values at the requested scope (then fall through to show).
|
|
1279
|
+
if (options.set && options.set.length) {
|
|
1280
|
+
const scope = options.scope === "project" ? "project" : "user";
|
|
1281
|
+
const current = optsMod.loadPluginOptionValues(name, scope, {
|
|
1282
|
+
cwd: process.cwd(),
|
|
1283
|
+
});
|
|
1284
|
+
for (const pair of options.set) {
|
|
1285
|
+
const eq = pair.indexOf("=");
|
|
1286
|
+
if (eq <= 0) {
|
|
1287
|
+
logger.error(`Invalid --set "${pair}" (expected key=value)`);
|
|
1288
|
+
process.exitCode = 1;
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1291
|
+
current[pair.slice(0, eq)] = pair.slice(eq + 1);
|
|
1292
|
+
}
|
|
1293
|
+
optsMod.setPluginOptionValues(name, current, scope, {
|
|
1294
|
+
cwd: process.cwd(),
|
|
1295
|
+
});
|
|
1296
|
+
logger.success(`Set option(s) for ${name} at ${scope} scope`);
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
const resolved = optsMod.getResolvedPluginOptions(name, schema, {
|
|
1300
|
+
cwd: process.cwd(),
|
|
1301
|
+
});
|
|
1302
|
+
if (options.json) {
|
|
1303
|
+
console.log(
|
|
1304
|
+
JSON.stringify(
|
|
1305
|
+
{
|
|
1306
|
+
name,
|
|
1307
|
+
scope: installed.scope,
|
|
1308
|
+
options: resolved.redacted,
|
|
1309
|
+
sources: resolved.sources,
|
|
1310
|
+
warnings: resolved.warnings,
|
|
1311
|
+
droppedFromProject: resolved.droppedFromProject,
|
|
1312
|
+
},
|
|
1313
|
+
null,
|
|
1314
|
+
2,
|
|
1315
|
+
),
|
|
1316
|
+
);
|
|
1317
|
+
return;
|
|
1318
|
+
}
|
|
1319
|
+
logger.log(chalk.bold(`Options: ${chalk.cyan(name)}`));
|
|
1320
|
+
const keys = Object.keys(resolved.redacted);
|
|
1321
|
+
if (keys.length === 0) logger.log(chalk.gray(" (no options)"));
|
|
1322
|
+
for (const k of keys) {
|
|
1323
|
+
logger.log(
|
|
1324
|
+
` ${chalk.cyan(k)} = ${resolved.redacted[k]} ${chalk.gray(
|
|
1325
|
+
`[${resolved.sources[k] || "default"}]`,
|
|
1326
|
+
)}`,
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
if (resolved.droppedFromProject.length) {
|
|
1330
|
+
logger.log(
|
|
1331
|
+
chalk.yellow(
|
|
1332
|
+
` ⚠ dropped from project config (sensitive/user-only): ${resolved.droppedFromProject.join(
|
|
1333
|
+
", ",
|
|
1334
|
+
)} — set with --scope user`,
|
|
1335
|
+
),
|
|
1336
|
+
);
|
|
1337
|
+
}
|
|
1338
|
+
for (const w of resolved.warnings) logger.log(chalk.gray(` ${w}`));
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1135
1341
|
// plugin monitors — list (and optionally run) trusted plugins' background
|
|
1136
1342
|
// monitors. `--run --seconds N` actually starts the supervisor for N seconds,
|
|
1137
1343
|
// prints captured output, then reaps everything (verifies no leaked process).
|
|
@@ -1218,6 +1424,10 @@ export function registerPluginCommand(program) {
|
|
|
1218
1424
|
)
|
|
1219
1425
|
.option("--scope <scope>", `Scope to update in (${SCOPES})`, "user")
|
|
1220
1426
|
.option("--force", "Reinstall even if the version is unchanged")
|
|
1427
|
+
.option(
|
|
1428
|
+
"--grant-capabilities",
|
|
1429
|
+
"Grant any newly declared capabilities during the upgrade (no separate consent step)",
|
|
1430
|
+
)
|
|
1221
1431
|
.action(async (source, options) => {
|
|
1222
1432
|
const { updatePlugin } = await import("../lib/plugin-runtime/install.js");
|
|
1223
1433
|
try {
|
|
@@ -1241,14 +1451,21 @@ export function registerPluginCommand(program) {
|
|
|
1241
1451
|
}
|
|
1242
1452
|
// Surface the (possibly widened) capability set + re-consent hint. An
|
|
1243
1453
|
// upgrade that adds a capability shows a `⚠ capability consent required`
|
|
1244
|
-
// notice diffed against the prior consent
|
|
1245
|
-
|
|
1454
|
+
// notice diffed against the prior consent, then either auto-grants
|
|
1455
|
+
// (--grant-capabilities) or blocks interactively to re-consent now.
|
|
1456
|
+
await applyCapabilityConsentGate(
|
|
1246
1457
|
res.name,
|
|
1458
|
+
options.scope,
|
|
1247
1459
|
await resolvePluginCapabilityNotice(
|
|
1248
1460
|
res.name,
|
|
1249
1461
|
options.scope,
|
|
1250
1462
|
process.cwd(),
|
|
1251
1463
|
),
|
|
1464
|
+
process.cwd(),
|
|
1465
|
+
{
|
|
1466
|
+
grant: options.grantCapabilities === true,
|
|
1467
|
+
interactive: Boolean(process.stdin.isTTY),
|
|
1468
|
+
},
|
|
1252
1469
|
);
|
|
1253
1470
|
} catch (err) {
|
|
1254
1471
|
logger.error(`Upgrade failed: ${err.message}`);
|
package/src/commands/review.js
CHANGED
|
@@ -848,7 +848,23 @@ export async function runMultiFinderReview(ctx = {}, deps = {}) {
|
|
|
848
848
|
verifiedCount = Object.values(verdicts).filter((v) => v.verified).length;
|
|
849
849
|
}
|
|
850
850
|
|
|
851
|
-
|
|
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 });
|
|
852
868
|
const meta = {
|
|
853
869
|
byDimension,
|
|
854
870
|
...(ctx.verify ? { verified: verifiedCount } : {}),
|
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)",
|