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.
Files changed (213) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-BQ0kgfA1.js → AIOps-BqRkaHGb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Dr1MDANe.js → ActionButton-DWO7OZLC.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-CZ5vsaXW.js → Analytics-C5oaVezM.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-BIBga881.js → AppLayout-CsUESDoZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-vpXHRU3u.js → Artifacts-Dragqu5k.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-B8exuoub.js → Audit-CQsaHctY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-923evRRN.js → BackgroundAgents-DdG1jNSM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-sr_sswlQ.js → Backup-DN6sjK4z.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-CnW-4XKT.js → BaseInput-Cxm_ZTCl.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-FZCsTZIL.js → Chat-DVXGmA56.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DDPd9qT0.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DFrxB59H.js → Checkbox-DEc4vD_A.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-_b6WEel8.js → Codegen-CKiTcv36.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CkbnnZ-K.js → Col-Br9UUNN0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-BxM_82zW.js → Community-NeUH_70T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DYinP45R.js → Compact-BqR-z2Df.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-DyXp3sSP.js → Compliance-DcWB_SgZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-BEr2DhW7.js → Cowork-BfhgcudF.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-B-Vwo1hN.js → Cron-LaU29LgD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DbLxKddd.js → Crosschain-B2ZB9uwW.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-BsQ_LFw9.js → DID-BuHl38A2.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-lVlXQHu5.js → Dashboard-m627yqvZ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-Beo_foO7.js → Dropdown-C7bxF8F5.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DszHIXED.js → EmailListRenderer-DPLPQzLp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-DcAwoh81.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-CuJLa2eb.js → Federation-Bvkzg88z.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Cxd1lxhc.js → FormItemContext-BTsiA7h_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BBvq5Jsw.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-WqPKetx6.js → Git-DS_j6r25.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-CWYtY3ke.js → Governance-CKRG3Rsl.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-BnX1iiNV.js → Inference-DW8INoNQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-BJuM8GN5.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DqLMNntm.js → Logs-wqi0uxuR.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-BqYwEYou.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-_vGfmhNl.js → Marketplace-CMlTuPLT.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BeJDOkDj.js → McpTools-CSx4m5JJ.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-Da0EEZDO.js → Memory-vnQCUy-_.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-DSuzcCCy.js → MobileBridge-BV2NA742.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-Bw0k8e1B.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-BE9YgdA0.js → Mtc-CzK_FPB1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-fC2BpO5S.js → MtcAudit-Cb7G9oL3.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-D24Xxqw3.js → Multisig-qJxlUyzr.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Bs3oGvS8.js → NLProgramming-DqaBJ7Te.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-BFjmAA-3.js → Notes-CwhTJkRi.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BX_9Bhv0.js → NotificationSettings-YDs4Fal4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-wjkZwJXI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-DBcdsVEP.js → Organization-CEmQm9n8.js} +2 -2
  49. package/src/assets/web-panel/assets/{Overflow-DDyaF9vC.js → Overflow-zby_P_LA.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-BB78L4he.js → P2P-DsmDNFNT.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-DqTiZFum.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-INbnWv4j.js → Permissions-DuzQ6me5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BST-VmTq.js → PersonalDataHub-DZbeN613.js} +2 -2
  54. package/src/assets/web-panel/assets/{Pipeline-CE7_tS2k.js → Pipeline-CoNSVAUS.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-hK5L8dOB.js → Privacy-OYJEAaMC.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-BvkkUoDn.js → ProjectInit-CWIapV--.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-u7_EUtkm.js → ProjectSettings-CX8xMxZn.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-5Fmt_8Nw.js → Projects-BLCN8YyY.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-B9eanarR.js → Providers-BV_sxp75.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-C0dX5N2f.js → QrScannerModal-BTnRrHRp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CKTpG-PZ.js → QuickAsk-D1KZg018.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-Px_w_XQM.js → Recommend-COm5cuNg.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-Di0fnYkM.js → RemoteSession-0saWEkYI.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-vesnWjXt.js → Reputation-Dr7iKLS2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-exbY6LkT.js → Row-C1vpPBcr.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-B3O3x70t.js → RssFeed-CxlWrtNj.js} +2 -2
  67. package/src/assets/web-panel/assets/{Search-uRA0DhNz.js → Search-BT2V8dPN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-CViqdH6K.js → Security-bm9V3c5k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-CkXS97WN.js → Services-CocH1yyX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-DxW5WYnE.js → Skeleton-DHItlMdd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-CLxmGFse.js → Skills-C59sLc4w.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-Cr5fziXq.js → Sla-Crj4BMc8.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-H0JSb3cp.js → SpeechSettings-DX4KFceY.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-DMYWMae7.js → SyncSettings-BD_iwQLr.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CbYyDdF4.js → Tasks-BmCTzYsH.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-Cpc8JjiK.js → Templates-BGUaxZYl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-CRh77Jda.js → Tenant-BS0tJx2O.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-4D8tNBO5.js → Terminal--tHSAsld.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-D41jzzbG.js → TimelineRenderer-D9-MFxS4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-C_jWEy6b.js → Tokens-CiCB485f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-Dk1ZUhsy.js → Trigger-SmCFuW_l.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-CLVy4l7m.js → Trust-DkK3Rqe0.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-C-FeqFDj.js → UkeySign-CDM4LGdB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-CqVZzyMm.js → VideoEditing-IDB7DJXt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-DX9BW9pu.js → Wallet-Bikj5cmt.js} +2 -2
  86. package/src/assets/web-panel/assets/{WebAuthn-7hQDpWw_.js → WebAuthn-C9abWlAw.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-6XqUAO0j.js → WorkflowEditor-DrbRKVky.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-5R_vkaJG.js → chat-CccCSDQf.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-huUPZdC-.js → colors-CXObXLhb.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-CfJlgisE.js → compact-item-B5kfYAxC.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-jO8heofr.js → createContext-DmiPBfip.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-CvKrADNa.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DY8Vxqx7.js → hasIn-Q6lYteAc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-BtEXSoPL.js → index-25Wlqps4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BsBtRWbA.js → index-33U29UNV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BQS-uPOK.js → index-4oXBDczR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DXBcvUnf.js → index-BGYXvMc5.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-C2iVSFpy.js → index-BJCCa0s_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-A48PtXw9.js → index-BJJ5DhKI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-Db1cLWir.js → index-BLOzViJ6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-vXs3B8GJ.js → index-BRrbUMoG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BHE8OIls.js → index-BUFpT17Q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BmqGerpC.js → index-BkRDiqGc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CzKmteNS.js → index-BsfvW6my.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-BAON7TzU.js → index-By-kfPED.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DoI2sZm7.js → index-BzFTcQsc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-j3O2EEJe.js → index-C2FXdboq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-CRElRhyS.js → index-C5XOlTHN.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-KRHroryI.js → index-CLhBDS2w.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Bxax9vQ6.js → index-CMq7UeoT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BfCx4pFT.js → index-CVMTWNKl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-CA6sO5VD.js → index-Caghi-yL.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-CA90TZxG.js → index-CdB6FuSH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Cbe9G0V4.js → index-CoK8-kWQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-D0pet5Uy.js → index-CsO3GUAY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-ChvHubX3.js → index-CwZBa20Z.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BRXOP09X.js → index-D-kASucw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-B74OXFxC.js → index-D8eBw7vW.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-D1VLuww0.js → index-D9wgifk4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-p85YaaKw.js → index-DAYCTc07.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BliaT02i.js → index-DF6Q8l2h.js} +1 -1
  122. package/src/assets/web-panel/assets/index-DTdFUWc_.js +1 -0
  123. package/src/assets/web-panel/assets/{index-Cjxbu68P.js → index-DgTLApS9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Cis_Ip7H.js → index-DgxVJ8pg.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-v3pTaEQm.js → index-DlJvIqoR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-BYLBPpt8.js → index-Dw4eHfsG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-B300RPnT.js → index-Rh-ytwow.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-CLhBh-qE.js → index-StAEt2pq.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-d1xkbtNz.js → index-bzfw951k.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BCqxyvqp.js → index-fSUMbMvv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-dKx57io7.js → index-tFHy5Wol.js} +1 -1
  132. package/src/assets/web-panel/assets/index-zQ0M_kz-.js +1 -0
  133. package/src/assets/web-panel/assets/{initDefaultProps-MCB7-raZ.js → initDefaultProps-BNq7X72N.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-UWDF9swV.js → motion-DOf9viTc.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-CsTmgXZU.js → move-CRHTcW4x.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-fQy6KGLg.js → mtc-parser-Cyoozgm2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-woPnEjd1.js → omit-6-TjFNru.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-Bs2dZ1gf.js → pickAttrs-DlUmO1zi.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-C7EVgPkg.js → placementArrow-CFOL8mIR.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-BH2kYCTN.js → responsiveObserve-BtaFK5zd.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-BFBP2V_e.js → slide-BIbc2bur.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-xdDxDYse.js → statusUtils-BXX9d6-w.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-aAWgPPQf.js → styleChecker-BGv10Bpn.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DkGSAWoO.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-BrOih40N.js → useFs-FY4IL4jn.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-eIYrgMqO.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-B8lutNSE.js → vnode-BM3OHxAT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-CffNre7V.js → zoom-D1fbFiHB.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +71 -14
  151. package/src/commands/doctor.js +85 -1
  152. package/src/commands/plugin.js +226 -9
  153. package/src/commands/review.js +17 -1
  154. package/src/commands/session.js +191 -0
  155. package/src/commands/team.js +19 -1
  156. package/src/data/changelog.json +5 -4
  157. package/src/gateways/ws/session-protocol.js +50 -0
  158. package/src/gateways/ws/ws-agent-handler.js +50 -0
  159. package/src/harness/jsonl-session-store.js +60 -0
  160. package/src/harness/worktree-isolator.js +140 -5
  161. package/src/lib/agent-schedule-store.js +30 -1
  162. package/src/lib/agent-team/team-worktree.js +37 -2
  163. package/src/lib/agent-tool-admission.js +125 -0
  164. package/src/lib/app-preview.js +360 -0
  165. package/src/lib/async-hook-supervisor.cjs +54 -0
  166. package/src/lib/capability-manifest.js +319 -0
  167. package/src/lib/capability-negotiation.js +23 -12
  168. package/src/lib/diagnostic-bundle.js +230 -0
  169. package/src/lib/doctor-checkup.js +223 -4
  170. package/src/lib/execution-location.js +286 -0
  171. package/src/lib/headless-manifest.js +13 -40
  172. package/src/lib/hook-stats-store.cjs +197 -0
  173. package/src/lib/idempotency.js +109 -0
  174. package/src/lib/jsonl-session-store.js +1 -0
  175. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  176. package/src/lib/lsp/lsp-manager.js +56 -0
  177. package/src/lib/monitor-event.js +290 -0
  178. package/src/lib/operation-fingerprint.js +217 -0
  179. package/src/lib/plugin-runtime/bin.js +37 -0
  180. package/src/lib/plugin-runtime/capabilities.js +19 -0
  181. package/src/lib/plugin-runtime/capability-consent.js +28 -0
  182. package/src/lib/plugin-runtime/hooks.js +37 -0
  183. package/src/lib/plugin-runtime/lsp.js +38 -0
  184. package/src/lib/plugin-runtime/mcp.js +40 -0
  185. package/src/lib/plugin-runtime/monitors.js +37 -0
  186. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  187. package/src/lib/pr-automation-policy.js +224 -0
  188. package/src/lib/remote-approval-bridge.js +66 -13
  189. package/src/lib/repl-rewind.js +101 -0
  190. package/src/lib/review-comment-anchor.js +241 -0
  191. package/src/lib/review-pipeline.js +46 -7
  192. package/src/lib/runtime-checkup.js +221 -0
  193. package/src/lib/secret-scan.js +409 -0
  194. package/src/lib/session-branch.js +0 -0
  195. package/src/lib/side-effect-ledger.js +105 -0
  196. package/src/lib/skill-loader.js +32 -0
  197. package/src/lib/skill-path-scope.js +63 -0
  198. package/src/lib/sub-agent-context.js +36 -4
  199. package/src/lib/sub-agent-registry.js +19 -0
  200. package/src/lib/telemetry/span-recorder.js +17 -4
  201. package/src/lib/terminal-context.js +195 -0
  202. package/src/lib/unattended-action-policy.js +182 -0
  203. package/src/lib/worktree-cleanup-safety.js +138 -0
  204. package/src/lib/worktree-isolator.js +8 -0
  205. package/src/lib/worktree-sparse.js +131 -0
  206. package/src/repl/agent-repl.js +152 -38
  207. package/src/runtime/agent-core.js +191 -13
  208. package/src/runtime/diagnostics.js +60 -0
  209. package/src/runtime/headless-runner.js +126 -1
  210. package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +0 -1
  211. package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +0 -1
  212. package/src/assets/web-panel/assets/index-BxUKMRpP.js +0 -1
  213. package/src/assets/web-panel/assets/index-DscU9iPI.js +0 -1
@@ -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 { collectDoctorReport } from "../runtime/diagnostics.js";
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
 
@@ -64,8 +64,12 @@ async function resolvePluginCapabilityNotice(name, scope, cwd) {
64
64
  }
65
65
  }
66
66
 
67
- /** Print the capability notice (text mode) after an install/upgrade. */
68
- function printPluginCapabilityNotice(name, notice) {
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
- logger.log(
82
- chalk.dim(` run \`cc plugin consent ${name} --grant\` to allow them`),
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({ ...res, capabilities: capNotice }, null, 2),
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
- printPluginCapabilityNotice(res.name, capNotice);
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
- printPluginCapabilityNotice(
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}`);
@@ -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
- const report = buildReviewReport(all, { verdicts });
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 } : {}),
@@ -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)}`);
@@ -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
- coord = new TeamWorktreeCoordinator(process.cwd());
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)",