chainlesschain 0.162.162 → 0.162.163

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 (192) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BQ0kgfA1.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-Dr1MDANe.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-CZ5vsaXW.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-BIBga881.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-vpXHRU3u.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-B8exuoub.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-923evRRN.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-sr_sswlQ.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-CnW-4XKT.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-FZCsTZIL.js} +6 -6
  12. package/src/assets/web-panel/assets/ChatBubbleRenderer-DDPd9qT0.js +1 -0
  13. package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DFrxB59H.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-_b6WEel8.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-CkbnnZ-K.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-BxM_82zW.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-DYinP45R.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DyXp3sSP.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BEr2DhW7.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-B-Vwo1hN.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-DbLxKddd.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BsQ_LFw9.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-lVlXQHu5.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-Beo_foO7.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DszHIXED.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-DcAwoh81.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-CuJLa2eb.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-Cxd1lxhc.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BBvq5Jsw.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-WqPKetx6.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CWYtY3ke.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-BnX1iiNV.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BJuM8GN5.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-DqLMNntm.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-BqYwEYou.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-_vGfmhNl.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-BeJDOkDj.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-Da0EEZDO.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.js → MobileBridge-DSuzcCCy.js} +1 -1
  40. package/src/assets/web-panel/assets/MobileProjects-D0RAk4e8.js +1 -0
  41. package/src/assets/web-panel/assets/{Mtc-RJsOTsIA.js → Mtc-BE9YgdA0.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-fC2BpO5S.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-D24Xxqw3.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-Bs3oGvS8.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-BFjmAA-3.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-BX_9Bhv0.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-wjkZwJXI.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-DBcdsVEP.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-DDyaF9vC.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-BB78L4he.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-DqTiZFum.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-INbnWv4j.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-BST-VmTq.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CE7_tS2k.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-hK5L8dOB.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-BvkkUoDn.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-u7_EUtkm.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-5Fmt_8Nw.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-B9eanarR.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-C0dX5N2f.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-CKTpG-PZ.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-Px_w_XQM.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-Di0fnYkM.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-vesnWjXt.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-exbY6LkT.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-B3O3x70t.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-uRA0DhNz.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-CViqdH6K.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CkXS97WN.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DxW5WYnE.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-CLxmGFse.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Cr5fziXq.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-H0JSb3cp.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-DMYWMae7.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-CbYyDdF4.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-Cpc8JjiK.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-CRh77Jda.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal-4D8tNBO5.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D41jzzbG.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-C_jWEy6b.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-Dk1ZUhsy.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-CLVy4l7m.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-C-FeqFDj.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-CqVZzyMm.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-DX9BW9pu.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-7hQDpWw_.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-6XqUAO0j.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-5R_vkaJG.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-huUPZdC-.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-CfJlgisE.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BvjJ48bs.js → createContext-jO8heofr.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DeIrCfH-.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-DVC0157C.js → hasIn-DY8Vxqx7.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-A48PtXw9.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-B300RPnT.js} +3 -3
  96. package/src/assets/web-panel/assets/{index-BbepULSc.js → index-B74OXFxC.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-BAON7TzU.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-BCqxyvqp.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BHE8OIls.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-BQS-uPOK.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-BRXOP09X.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-BYLBPpt8.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-BfCx4pFT.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CmFNvNLZ.js → index-BliaT02i.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BmqGerpC.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-BsBtRWbA.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C229ABfy.js → index-BtEXSoPL.js} +1 -1
  108. package/src/assets/web-panel/assets/index-BxUKMRpP.js +1 -0
  109. package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-Bxax9vQ6.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-C2iVSFpy.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-CA6sO5VD.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CA90TZxG.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-CLhBh-qE.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-CRElRhyS.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-Cbe9G0V4.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-ChvHubX3.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-Cis_Ip7H.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-KjgKdZe-.js → index-Cjxbu68P.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-CzKmteNS.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-D0pet5Uy.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D1VLuww0.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-DXBcvUnf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-Db1cLWir.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-DoI2sZm7.js} +1 -1
  125. package/src/assets/web-panel/assets/index-DscU9iPI.js +1 -0
  126. package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-KRHroryI.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-d1xkbtNz.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-B_EvG_GW.js → index-dKx57io7.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-j3O2EEJe.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-p85YaaKw.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-v3pTaEQm.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-vXs3B8GJ.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BDgd2YZ8.js → initDefaultProps-MCB7-raZ.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-UWDF9swV.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CsTmgXZU.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-fQy6KGLg.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-woPnEjd1.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-Bs2dZ1gf.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-C7EVgPkg.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BH2kYCTN.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BFBP2V_e.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-xdDxDYse.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-aAWgPPQf.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-DkGSAWoO.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-BrOih40N.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-eIYrgMqO.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-B8lutNSE.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-CffNre7V.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +254 -23
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/hook.js +181 -7
  154. package/src/commands/plugin.js +239 -1
  155. package/src/commands/review.js +347 -2
  156. package/src/data/changelog.json +9 -4
  157. package/src/gateways/ws/remote-session-protocol.js +39 -0
  158. package/src/gateways/ws/session-protocol.js +5 -2
  159. package/src/lib/agent-schedule-store.js +289 -10
  160. package/src/lib/channels/channel-manager.js +54 -2
  161. package/src/lib/cli-context-engineering.js +9 -2
  162. package/src/lib/context-breakdown.js +116 -0
  163. package/src/lib/doctor-checkup.js +52 -0
  164. package/src/lib/hook-event-log.cjs +193 -0
  165. package/src/lib/interaction-adapter.js +34 -4
  166. package/src/lib/json-schema-output.js +46 -12
  167. package/src/lib/json-schema-validate.js +143 -3
  168. package/src/lib/plugin-runtime/capability-consent.js +175 -0
  169. package/src/lib/plugin-runtime/policy.js +62 -0
  170. package/src/lib/plugin-runtime/scopes.js +20 -4
  171. package/src/lib/project-instructions.js +128 -0
  172. package/src/lib/repl-goal.js +132 -0
  173. package/src/lib/repl-rewind.js +52 -0
  174. package/src/lib/schedule-planner.js +17 -1
  175. package/src/lib/settings-hook-events.cjs +13 -0
  176. package/src/lib/settings-loader.cjs +42 -0
  177. package/src/lib/side-effect-ledger-store.js +88 -0
  178. package/src/lib/side-effect-ledger.js +296 -0
  179. package/src/lib/sub-agent-context.js +74 -5
  180. package/src/lib/subagent-inheritance.js +112 -0
  181. package/src/lib/turn-binding-store.js +119 -0
  182. package/src/repl/agent-repl.js +136 -0
  183. package/src/repl/permission-tier.js +28 -0
  184. package/src/runtime/agent-core.js +376 -10
  185. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  186. package/src/runtime/headless-runner.js +267 -0
  187. package/src/runtime/headless-stream.js +60 -1
  188. package/src/assets/web-panel/assets/ChatBubbleRenderer-HwJR-C6B.js +0 -1
  189. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
  190. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
  191. package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
  192. package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
@@ -66,22 +66,21 @@ export function registerContextCommand(program) {
66
66
  .argument("[id]", "Session ID (omit for the most-recent headless session)")
67
67
  .option("--model <model>", "Size against this model's context window")
68
68
  .option("--provider <provider>", "Provider (for the window default)")
69
+ .option(
70
+ "--sources",
71
+ "Also break tokens down by source (project-memory instruction files + message classes)",
72
+ )
69
73
  .option("--json", "Output as JSON")
70
74
  .action(async (id, options) => {
71
- const {
72
- rebuildMessages,
73
- getLastSessionId,
74
- sessionExists,
75
- readEvents,
76
- } = await import("../harness/jsonl-session-store.js");
77
- const { estimateTokens, getContextWindow } = await import(
78
- "../harness/prompt-compressor.js"
79
- );
75
+ const { rebuildMessages, getLastSessionId, sessionExists, readEvents } =
76
+ await import("../harness/jsonl-session-store.js");
77
+ const { estimateTokens, getContextWindow } =
78
+ await import("../harness/prompt-compressor.js");
80
79
 
81
80
  const sessionId = id || getLastSessionId();
82
81
  if (!sessionId) {
83
82
  logger.error(
84
- 'No session found. Run a headless agent with `--session <id>` first, or pass a session id.',
83
+ "No session found. Run a headless agent with `--session <id>` first, or pass a session id.",
85
84
  );
86
85
  process.exitCode = 1;
87
86
  return;
@@ -121,6 +120,55 @@ export function registerContextCommand(program) {
121
120
  const used = window > 0 ? total / window : 0;
122
121
  const remaining = Math.max(0, window - total);
123
122
 
123
+ // --sources (opt-in): attribute the project-memory instruction files that
124
+ // WOULD load for the current cwd (same instructionExcludes the agent
125
+ // honors), folded in with the session's message-role buckets. The base
126
+ // view is byte-for-byte unchanged when the flag is absent.
127
+ let sourceReport = null;
128
+ if (options.sources) {
129
+ try {
130
+ const { loadProjectInstructions } =
131
+ await import("../lib/project-instructions.js");
132
+ const { breakdownInstructionSources, rankContextSources } =
133
+ await import("../lib/context-breakdown.js");
134
+ const cwd = process.cwd();
135
+ let instructionExcludes;
136
+ try {
137
+ const { readStringArraySetting } =
138
+ await import("../lib/settings-loader.cjs");
139
+ instructionExcludes = readStringArraySetting(
140
+ "instructionExcludes",
141
+ {
142
+ cwd,
143
+ },
144
+ );
145
+ } catch {
146
+ instructionExcludes = undefined; // fail-open — load everything
147
+ }
148
+ const loaded = loadProjectInstructions({ cwd, instructionExcludes });
149
+ const instr = breakdownInstructionSources(
150
+ loaded.files,
151
+ estimateTokens,
152
+ cwd,
153
+ );
154
+ const ranked = rankContextSources({
155
+ instructionTotal: instr.total,
156
+ buckets,
157
+ counts,
158
+ });
159
+ sourceReport = {
160
+ cwd,
161
+ instructions: instr.sources,
162
+ instructionTokens: instr.total,
163
+ ranked: ranked.sources,
164
+ combinedTotal: ranked.total,
165
+ };
166
+ } catch {
167
+ // Best-effort — a source breakdown must never break the base view.
168
+ sourceReport = null;
169
+ }
170
+ }
171
+
124
172
  if (options.json) {
125
173
  console.log(
126
174
  JSON.stringify(
@@ -136,6 +184,15 @@ export function registerContextCommand(program) {
136
184
  breakdown: buckets,
137
185
  counts,
138
186
  overflows: total > window,
187
+ ...(sourceReport
188
+ ? {
189
+ sources: sourceReport.ranked,
190
+ instructions: sourceReport.instructions,
191
+ instructionTokens: sourceReport.instructionTokens,
192
+ combinedTotal: sourceReport.combinedTotal,
193
+ cwd: sourceReport.cwd,
194
+ }
195
+ : {}),
139
196
  },
140
197
  null,
141
198
  2,
@@ -144,7 +201,9 @@ export function registerContextCommand(program) {
144
201
  return;
145
202
  }
146
203
 
147
- logger.log(chalk.bold(`Context — session ${chalk.gray(sessionId.slice(0, 28))}`));
204
+ logger.log(
205
+ chalk.bold(`Context — session ${chalk.gray(sessionId.slice(0, 28))}`),
206
+ );
148
207
  logger.log(
149
208
  chalk.gray(
150
209
  ` model ${model || "(default)"} · provider ${provider} · ` +
@@ -172,17 +231,69 @@ export function registerContextCommand(program) {
172
231
 
173
232
  logger.log("");
174
233
  const pct = (used * 100).toFixed(1);
175
- const color = used > 0.9 ? chalk.red : used > 0.7 ? chalk.yellow : chalk.green;
234
+ const color =
235
+ used > 0.9 ? chalk.red : used > 0.7 ? chalk.yellow : chalk.green;
176
236
  logger.log(
177
237
  ` ${chalk.bold("total".padEnd(11))} ${String(total).padStart(7)} ` +
178
238
  `${color(bar(used))} ${color(`${pct}% of window`)}`,
179
239
  );
180
240
  logger.log(
181
- chalk.gray(` headroom ${String(remaining).padStart(7)} tokens remaining`),
241
+ chalk.gray(
242
+ ` headroom ${String(remaining).padStart(7)} tokens remaining`,
243
+ ),
182
244
  );
183
245
  if (total > window) {
184
246
  logger.log(
185
- chalk.red(" ⚠ exceeds the model context window — compaction required"),
247
+ chalk.red(
248
+ " ⚠ exceeds the model context window — compaction required",
249
+ ),
250
+ );
251
+ }
252
+
253
+ if (sourceReport) {
254
+ logger.log("");
255
+ logger.log(
256
+ chalk.bold("By source") +
257
+ chalk.gray(` (project memory for ${sourceReport.cwd})`),
258
+ );
259
+ if (sourceReport.instructions.length) {
260
+ for (const s of sourceReport.instructions) {
261
+ logger.log(
262
+ chalk.gray(` · ${String(s.scope).padEnd(7)} `) +
263
+ `${String(s.tokens).padStart(7)} ${s.source}` +
264
+ (s.truncated ? chalk.yellow(" (truncated)") : ""),
265
+ );
266
+ }
267
+ logger.log(
268
+ chalk.gray(" project-memory subtotal ") +
269
+ `${String(sourceReport.instructionTokens).padStart(7)} tokens`,
270
+ );
271
+ } else {
272
+ logger.log(
273
+ chalk.gray(" (no project-memory instruction files for this cwd)"),
274
+ );
275
+ }
276
+ logger.log("");
277
+ for (const s of sourceReport.ranked) {
278
+ logger.log(
279
+ ` ${String(s.source).padEnd(30)} ${String(s.tokens).padStart(7)} ` +
280
+ `${chalk.cyan(bar(s.share))} ${String(Math.round(s.share * 100)).padStart(3)}%`,
281
+ );
282
+ }
283
+ logger.log(
284
+ chalk.gray(
285
+ " note: instructions reflect the current cwd (what a new run here",
286
+ ),
287
+ );
288
+ logger.log(
289
+ chalk.gray(
290
+ " would load); messages are this session's stored turns. Skill / MCP",
291
+ ),
292
+ );
293
+ logger.log(
294
+ chalk.gray(
295
+ " tool schemas load at runtime and are not counted here.",
296
+ ),
186
297
  );
187
298
  }
188
299
  });
@@ -101,10 +101,7 @@ export function registerHookCommand(program) {
101
101
  }
102
102
  const events = Object.keys(settingsHooks);
103
103
  if (events.length > 0) {
104
- const n = events.reduce(
105
- (a, e) => a + settingsHooks[e].length,
106
- 0,
107
- );
104
+ const n = events.reduce((a, e) => a + settingsHooks[e].length, 0);
108
105
  logger.log(chalk.bold(`\n.claude/settings.json hooks (${n}):`));
109
106
  for (const ev of events) {
110
107
  if (options.event && ev !== options.event) continue;
@@ -145,7 +142,8 @@ export function registerHookCommand(program) {
145
142
  settingsFile: options.settings,
146
143
  });
147
144
  const matched = collectHooks(hooks, event, tool);
148
- const toolInput = args && args.length ? { command: args.join(" "), args } : {};
145
+ const toolInput =
146
+ args && args.length ? { command: args.join(" "), args } : {};
149
147
  const payload = {
150
148
  hook_event_name: event,
151
149
  tool_name: tool,
@@ -156,14 +154,18 @@ export function registerHookCommand(program) {
156
154
 
157
155
  if (!options.run) {
158
156
  if (options.json) {
159
- console.log(JSON.stringify({ event, tool, matched, payload }, null, 2));
157
+ console.log(
158
+ JSON.stringify({ event, tool, matched, payload }, null, 2),
159
+ );
160
160
  } else if (matched.length === 0) {
161
161
  logger.log(
162
162
  chalk.gray(`no settings.json hooks match ${event} / ${tool}`),
163
163
  );
164
164
  } else {
165
165
  logger.log(
166
- chalk.bold(`${matched.length} hook(s) would fire for ${event} / ${tool}:`),
166
+ chalk.bold(
167
+ `${matched.length} hook(s) would fire for ${event} / ${tool}:`,
168
+ ),
167
169
  );
168
170
  for (const h of matched) logger.log(` ${chalk.gray(h.command)}`);
169
171
  logger.log(chalk.dim(" (use --run to execute and see decisions)"));
@@ -201,6 +203,178 @@ export function registerHookCommand(program) {
201
203
  }
202
204
  });
203
205
 
206
+ // hook replay — replay a recorded hook delivery by event id (P2 event bus).
207
+ // Dry-run by default; --run executes observe-only hooks. A DECISION hook can
208
+ // re-gate control flow, so its replay is refused without an explicit sandbox
209
+ // (and, since a real sandbox executor is not yet available, is never executed
210
+ // — only its dry-run plan is shown).
211
+ hook
212
+ .command("replay <event-id>")
213
+ .description(
214
+ "Replay a recorded hook delivery (needs CC_HOOK_EVENT_LOG); dry-run by default, --run executes observe-only hooks",
215
+ )
216
+ .option("--run", "Execute the replayed hooks (observe-only events only)")
217
+ .option(
218
+ "--sandbox",
219
+ "Assert an explicit sandbox for a decision-hook replay",
220
+ )
221
+ .option("--file <path>", "Read from a specific hook event log file")
222
+ .option("--json", "Output as JSON")
223
+ .action(async (eventId, options) => {
224
+ try {
225
+ const { findHookEvent } = await import("../lib/hook-event-log.cjs");
226
+ const { planHookReplay } = await import("../lib/hook-event-bus.cjs");
227
+ const envelope = findHookEvent(eventId, { filePath: options.file });
228
+ if (!envelope) {
229
+ logger.error(
230
+ `hook event "${eventId}" not found` +
231
+ (options.file
232
+ ? ""
233
+ : " (enable recording with CC_HOOK_EVENT_LOG=1)"),
234
+ );
235
+ process.exitCode = 1;
236
+ return;
237
+ }
238
+ const plan = planHookReplay(envelope, {
239
+ sandbox: Boolean(options.sandbox),
240
+ });
241
+ if (!plan.ok) {
242
+ if (options.json) {
243
+ console.log(JSON.stringify(plan, null, 2));
244
+ } else {
245
+ logger.error(`cannot replay: ${plan.reason}`);
246
+ }
247
+ process.exitCode = 1;
248
+ return;
249
+ }
250
+ // Decision events re-gate flow; a real sandbox executor is still owed,
251
+ // so we never EXECUTE them — the dry-run plan is shown instead.
252
+ if (options.run && plan.requiresSandbox) {
253
+ const note =
254
+ "decision-hook execution needs a real sandbox executor (not yet available); showing dry-run plan";
255
+ if (options.json) {
256
+ console.log(
257
+ JSON.stringify({ ...plan, executed: false, note }, null, 2),
258
+ );
259
+ } else {
260
+ logger.warn(`${note}:`);
261
+ logger.log(JSON.stringify(plan.payload, null, 2));
262
+ }
263
+ return;
264
+ }
265
+ if (!options.run) {
266
+ if (options.json) {
267
+ console.log(JSON.stringify({ ...plan, executed: false }, null, 2));
268
+ } else {
269
+ logger.log(
270
+ chalk.bold(
271
+ `replay (dry-run) of ${plan.event_type} [${plan.event_id}]`,
272
+ ),
273
+ );
274
+ if (plan.requiresSandbox)
275
+ logger.log(chalk.dim(" (decision event — sandboxed)"));
276
+ logger.log(JSON.stringify(plan.payload, null, 2));
277
+ logger.log(
278
+ chalk.dim(" (use --run to execute observe-only hooks)"),
279
+ );
280
+ }
281
+ return;
282
+ }
283
+ // observe-only + --run: re-collect and execute the matching hooks.
284
+ const { loadHooks, collectHooks } =
285
+ await import("../lib/settings-hooks.cjs");
286
+ const { runHooks } = await import("../lib/hook-runner.cjs");
287
+ const { hooks } = loadHooks({ cwd: process.cwd() });
288
+ const matched = collectHooks(
289
+ hooks,
290
+ plan.event_type,
291
+ plan.payload.tool_name || "",
292
+ );
293
+ const outcome = runHooks(matched, plan.payload, {
294
+ cwd: process.cwd(),
295
+ event: plan.event_type,
296
+ });
297
+ if (options.json) {
298
+ console.log(
299
+ JSON.stringify({ ...plan, executed: true, outcome }, null, 2),
300
+ );
301
+ } else {
302
+ logger.log(
303
+ chalk.bold(
304
+ `replayed ${plan.event_type} [${plan.event_id}] → ${outcome.results.length} hook(s)`,
305
+ ),
306
+ );
307
+ for (const r of outcome.results) {
308
+ logger.log(
309
+ ` ${chalk.gray(r.command)} → ${r.decision}` +
310
+ (r.exitCode != null ? ` (exit ${r.exitCode})` : ""),
311
+ );
312
+ }
313
+ }
314
+ } catch (err) {
315
+ logger.error(`hook replay failed: ${err.message}`);
316
+ process.exitCode = 1;
317
+ }
318
+ });
319
+
320
+ // hook events-log — list / verify the recorded hook delivery log.
321
+ hook
322
+ .command("events-log")
323
+ .description(
324
+ "List or verify the recorded hook delivery log (CC_HOOK_EVENT_LOG)",
325
+ )
326
+ .option("--limit <n>", "Max entries to show", "20")
327
+ .option("--verify", "Verify the hash chain instead of listing")
328
+ .option("--file <path>", "Read from a specific hook event log file")
329
+ .option("--json", "Output as JSON")
330
+ .action(async (options) => {
331
+ try {
332
+ const { listHookEvents, verifyHookEventChain, hookEventLogPath } =
333
+ await import("../lib/hook-event-log.cjs");
334
+ if (options.verify) {
335
+ const result = verifyHookEventChain({ filePath: options.file });
336
+ if (options.json) {
337
+ console.log(JSON.stringify(result, null, 2));
338
+ } else if (result.ok) {
339
+ logger.success(
340
+ `hook event log chain OK (${result.length} record(s))`,
341
+ );
342
+ } else {
343
+ logger.error(
344
+ `hook event log chain BROKEN at #${result.brokenAt}: ${result.reason}`,
345
+ );
346
+ process.exitCode = 1;
347
+ }
348
+ return;
349
+ }
350
+ const events = listHookEvents({
351
+ limit: parseInt(options.limit, 10) || 20,
352
+ filePath: options.file,
353
+ });
354
+ const logPath = options.file || hookEventLogPath();
355
+ if (options.json) {
356
+ console.log(JSON.stringify({ events, path: logPath }, null, 2));
357
+ return;
358
+ }
359
+ if (events.length === 0) {
360
+ logger.info(
361
+ `No recorded hook events (enable with CC_HOOK_EVENT_LOG=1). Log: ${logPath}`,
362
+ );
363
+ return;
364
+ }
365
+ logger.log(chalk.bold(`Recorded hook events (${events.length}):\n`));
366
+ for (const e of events) {
367
+ logger.log(
368
+ ` ${chalk.cyan(e.event_id)} ${chalk.yellow(e.event_type)}` +
369
+ (e.session_id ? ` session=${e.session_id}` : ""),
370
+ );
371
+ }
372
+ } catch (err) {
373
+ logger.error(`hook events-log failed: ${err.message}`);
374
+ process.exitCode = 1;
375
+ }
376
+ });
377
+
204
378
  // hook add
205
379
  hook
206
380
  .command("add")
@@ -30,6 +30,59 @@ import {
30
30
  getPluginSkills,
31
31
  } from "../harness/plugin-manager.js";
32
32
 
33
+ /**
34
+ * After an install/upgrade, resolve the just-installed plugin's DECLARED
35
+ * capabilities and its current consent status, so `cc plugin add`/`upgrade`
36
+ * surface a capability list + diff and tell the user when a WIDENING requires
37
+ * re-consent (mirroring `cc plugin consent`). Best-effort: capability rendering
38
+ * must never fail an otherwise-successful install. Returns a structured summary
39
+ * (for --json folding) or null when the plugin declares nothing / can't be
40
+ * resolved.
41
+ */
42
+ async function resolvePluginCapabilityNotice(name, scope, cwd) {
43
+ try {
44
+ const { discoverPlugins } = await import("../lib/plugin-runtime/scopes.js");
45
+ const { describeCapabilities } =
46
+ await import("../lib/plugin-runtime/capabilities.js");
47
+ const consent = await import("../lib/plugin-runtime/capability-consent.js");
48
+ const installed = discoverPlugins({ cwd, skipPolicy: true }).find(
49
+ (p) => p.name === name && p.scope === scope,
50
+ );
51
+ if (!installed) return null;
52
+ const declared = installed.manifest?.capabilities;
53
+ if (!declared || consent.capabilitiesAreEmpty(declared)) return null;
54
+ const entry = consent.loadConsentStore()[`${scope}:${name}`] || null;
55
+ const status = consent.capabilityConsentStatus(declared, entry);
56
+ return {
57
+ declared: describeCapabilities(declared),
58
+ consented: status.consented,
59
+ reason: status.reason,
60
+ added: status.added,
61
+ };
62
+ } catch {
63
+ return null; // capability rendering is advisory — never break an install
64
+ }
65
+ }
66
+
67
+ /** Print the capability notice (text mode) after an install/upgrade. */
68
+ function printPluginCapabilityNotice(name, notice) {
69
+ if (!notice) return;
70
+ logger.log(chalk.bold("\nCapabilities (declared):"));
71
+ for (const l of notice.declared) logger.log(` ${chalk.magenta(l)}`);
72
+ if (notice.consented) {
73
+ logger.log(chalk.gray(` capability consent: ${notice.reason}`));
74
+ return;
75
+ }
76
+ logger.log(
77
+ chalk.yellow(` ⚠ capability consent required (${notice.reason})`),
78
+ );
79
+ if (notice.added.length)
80
+ logger.log(chalk.yellow(` new: ${notice.added.join(", ")}`));
81
+ logger.log(
82
+ chalk.dim(` run \`cc plugin consent ${name} --grant\` to allow them`),
83
+ );
84
+ }
85
+
33
86
  export function registerPluginCommand(program) {
34
87
  const plugin = program
35
88
  .command("plugin")
@@ -632,6 +685,17 @@ export function registerPluginCommand(program) {
632
685
  logger.log(chalk.yellow(` ⚠ ${issue}`));
633
686
  }
634
687
  }
688
+ // Declared capabilities (Phase 3 gap): show what the plugin says it needs
689
+ // so a user can compare against the components it ships (the audit findings
690
+ // for an under-declared manifest already surface in manifest.warnings).
691
+ if (manifest.capabilitiesDeclared) {
692
+ const { describeCapabilities } =
693
+ await import("../lib/plugin-runtime/capabilities.js");
694
+ const capLines = describeCapabilities(manifest.capabilities);
695
+ logger.log(chalk.bold("\nCapabilities (declared):"));
696
+ if (capLines.length === 0) logger.log(chalk.gray(" (none)"));
697
+ for (const l of capLines) logger.log(` ${chalk.magenta(l)}`);
698
+ }
635
699
  for (const w of manifest.warnings) logger.log(chalk.yellow(` ⚠ ${w}`));
636
700
  for (const e of manifest.errors) logger.log(chalk.red(` ✖ ${e}`));
637
701
  if (manifest.ok) {
@@ -736,8 +800,15 @@ export function registerPluginCommand(program) {
736
800
  force: options.force === true,
737
801
  signature,
738
802
  });
803
+ const capNotice = await resolvePluginCapabilityNotice(
804
+ res.name,
805
+ res.scope,
806
+ process.cwd(),
807
+ );
739
808
  if (options.json) {
740
- console.log(JSON.stringify(res, null, 2));
809
+ console.log(
810
+ JSON.stringify({ ...res, capabilities: capNotice }, null, 2),
811
+ );
741
812
  } else {
742
813
  logger.success(
743
814
  `Installed ${res.name} v${res.version} (${res.scope} scope)` +
@@ -746,6 +817,7 @@ export function registerPluginCommand(program) {
746
817
  logger.log(chalk.gray(` → ${res.dir}`));
747
818
  for (const w of res.warnings || [])
748
819
  logger.log(chalk.yellow(` ⚠ ${w}`));
820
+ printPluginCapabilityNotice(res.name, capNotice);
749
821
  }
750
822
  } catch (err) {
751
823
  logger.error(`Install failed: ${err.message}`);
@@ -905,6 +977,161 @@ export function registerPluginCommand(program) {
905
977
  }
906
978
  });
907
979
 
980
+ // plugin consent [name] — view / grant a plugin's CAPABILITY consent
981
+ // (process / network / filesystem / mcp / monitor / credential). Distinct from
982
+ // `trust`, which pins the code VERSION: consent pins the capability SET, and
983
+ // any widening re-prompts even for an already-trusted plugin.
984
+ plugin
985
+ .command("consent [name]")
986
+ .description(
987
+ "View or grant a plugin's capability consent (--grant / --revoke / --list)",
988
+ )
989
+ .option("--scope <scope>", "Scope of the plugin", "project")
990
+ .option(
991
+ "--grant",
992
+ "Grant consent for the plugin's currently-declared capabilities",
993
+ )
994
+ .option("--revoke", "Revoke capability consent")
995
+ .option("--list", "List all capability consent entries")
996
+ .option("--json", "Output as JSON")
997
+ .action(async (name, options) => {
998
+ const consent =
999
+ await import("../lib/plugin-runtime/capability-consent.js");
1000
+ const { describeCapabilities } =
1001
+ await import("../lib/plugin-runtime/capabilities.js");
1002
+
1003
+ if (!name || options.list) {
1004
+ const rows = consent.listCapabilityConsent();
1005
+ if (options.json) {
1006
+ console.log(JSON.stringify(rows, null, 2));
1007
+ return;
1008
+ }
1009
+ if (rows.length === 0) {
1010
+ logger.info("No capability consent recorded.");
1011
+ return;
1012
+ }
1013
+ logger.log(chalk.bold(`Capability consent (${rows.length}):`));
1014
+ for (const r of rows) {
1015
+ logger.log(` ${chalk.cyan(r.name)} v${r.version} [${r.scope}]`);
1016
+ for (const c of r.capabilities) logger.log(` ${chalk.magenta(c)}`);
1017
+ }
1018
+ return;
1019
+ }
1020
+
1021
+ if (options.revoke) {
1022
+ const res = consent.revokeCapabilityConsent(name, {
1023
+ scope: options.scope,
1024
+ });
1025
+ if (res.removed) {
1026
+ logger.success(
1027
+ `Revoked capability consent for ${name} (${options.scope} scope)`,
1028
+ );
1029
+ } else {
1030
+ logger.info(
1031
+ `${name} had no capability consent at ${options.scope} scope`,
1032
+ );
1033
+ }
1034
+ return;
1035
+ }
1036
+
1037
+ // Resolve the installed plugin + its declared capabilities.
1038
+ const { discoverPlugins } =
1039
+ await import("../lib/plugin-runtime/scopes.js");
1040
+ let installed = null;
1041
+ try {
1042
+ installed = discoverPlugins({
1043
+ cwd: process.cwd(),
1044
+ skipPolicy: true,
1045
+ }).find((p) => p.name === name && p.scope === options.scope);
1046
+ } catch {
1047
+ /* discovery best-effort */
1048
+ }
1049
+ if (!installed) {
1050
+ logger.error(`${name} is not installed at ${options.scope} scope`);
1051
+ process.exitCode = 1;
1052
+ return;
1053
+ }
1054
+ const declared = installed.manifest?.capabilities;
1055
+ const entry =
1056
+ consent.loadConsentStore()[`${options.scope}:${name}`] || null;
1057
+ const status = consent.capabilityConsentStatus(declared, entry);
1058
+
1059
+ if (options.grant) {
1060
+ consent.consentPluginCapabilities(name, {
1061
+ scope: options.scope,
1062
+ version: installed.version,
1063
+ capabilities: declared,
1064
+ });
1065
+ const capLines = describeCapabilities(declared);
1066
+ if (options.json) {
1067
+ console.log(
1068
+ JSON.stringify(
1069
+ {
1070
+ granted: true,
1071
+ name,
1072
+ scope: options.scope,
1073
+ version: installed.version,
1074
+ capabilities: capLines,
1075
+ },
1076
+ null,
1077
+ 2,
1078
+ ),
1079
+ );
1080
+ return;
1081
+ }
1082
+ logger.success(
1083
+ `Granted capability consent for ${name} v${installed.version} (${options.scope} scope)`,
1084
+ );
1085
+ for (const l of capLines) logger.log(` ${chalk.magenta(l)}`);
1086
+ return;
1087
+ }
1088
+
1089
+ // Show status.
1090
+ const capLines = describeCapabilities(declared);
1091
+ if (options.json) {
1092
+ console.log(
1093
+ JSON.stringify(
1094
+ {
1095
+ name,
1096
+ scope: options.scope,
1097
+ version: installed.version,
1098
+ declared: capLines,
1099
+ ...status,
1100
+ },
1101
+ null,
1102
+ 2,
1103
+ ),
1104
+ );
1105
+ return;
1106
+ }
1107
+ logger.log(
1108
+ chalk.bold("Capability consent: ") +
1109
+ `${chalk.cyan(name)} v${installed.version} [${options.scope}]`,
1110
+ );
1111
+ logger.log(chalk.bold("Declared capabilities:"));
1112
+ if (capLines.length === 0) logger.log(chalk.gray(" (none)"));
1113
+ for (const l of capLines) logger.log(` ${chalk.magenta(l)}`);
1114
+ logger.log(
1115
+ chalk.bold("Status: ") +
1116
+ (status.consented
1117
+ ? chalk.green("consented")
1118
+ : chalk.yellow("NEEDS CONSENT")),
1119
+ );
1120
+ logger.log(chalk.gray(` ${status.reason}`));
1121
+ if (status.added.length) {
1122
+ logger.log(
1123
+ chalk.yellow(` new since consent: ${status.added.join(", ")}`),
1124
+ );
1125
+ }
1126
+ if (!status.consented) {
1127
+ logger.log(
1128
+ chalk.dim(
1129
+ ` run \`cc plugin consent ${name} --grant\` to allow them`,
1130
+ ),
1131
+ );
1132
+ }
1133
+ });
1134
+
908
1135
  // plugin monitors — list (and optionally run) trusted plugins' background
909
1136
  // monitors. `--run --seconds N` actually starts the supervisor for N seconds,
910
1137
  // prints captured output, then reaps everything (verifies no leaked process).
@@ -1012,6 +1239,17 @@ export function registerPluginCommand(program) {
1012
1239
  `${res.name} is already up to date at v${res.version} (use --force to reinstall)`,
1013
1240
  );
1014
1241
  }
1242
+ // Surface the (possibly widened) capability set + re-consent hint. An
1243
+ // upgrade that adds a capability shows a `⚠ capability consent required`
1244
+ // notice diffed against the prior consent.
1245
+ printPluginCapabilityNotice(
1246
+ res.name,
1247
+ await resolvePluginCapabilityNotice(
1248
+ res.name,
1249
+ options.scope,
1250
+ process.cwd(),
1251
+ ),
1252
+ );
1015
1253
  } catch (err) {
1016
1254
  logger.error(`Upgrade failed: ${err.message}`);
1017
1255
  process.exitCode = 1;