chainlesschain 0.162.162 → 0.162.164

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/package.json +3 -1
  2. package/src/assets/web-panel/assets/{AIOps-ClI2ALgv.js → AIOps-BqRkaHGb.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-BDjRcJaK.js → ActionButton-DWO7OZLC.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-DikSRsaT.js → Analytics-C5oaVezM.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-9mLhMFmz.js → AppLayout-CsUESDoZ.js} +3 -3
  6. package/src/assets/web-panel/assets/{Artifacts-BSr88BrH.js → Artifacts-Dragqu5k.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CdvyimOF.js → Audit-CQsaHctY.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-D3_hCedh.js → BackgroundAgents-DdG1jNSM.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-Cyvv9RHf.js → Backup-DN6sjK4z.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BZM14kt8.js → BaseInput-Cxm_ZTCl.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-DrQSokVz.js → Chat-DVXGmA56.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-HwJR-C6B.js → ChatBubbleRenderer-BdaCL_iE.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-bFLYeWMV.js → Checkbox-DEc4vD_A.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-7mN7xJXT.js → Codegen-CKiTcv36.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-DAB5ZWm7.js → Col-Br9UUNN0.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-B_1TymaK.js → Community-NeUH_70T.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-Bq1uSWLJ.js → Compact-BqR-z2Df.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CyiE1jEe.js → Compliance-DcWB_SgZ.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Cpk0D7vm.js → Cowork-BfhgcudF.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-d_Lv3rdB.js → Cron-LaU29LgD.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-DMqhHjMR.js → Crosschain-B2ZB9uwW.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-Br-oPuBw.js → DID-BuHl38A2.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CfLW_RaP.js → Dashboard-m627yqvZ.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-DZnTOExV.js → Dropdown-C7bxF8F5.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DHIUHDv1.js → EmailListRenderer-DPLPQzLp.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bw3t3myu.js → FamilyGuardDashboard-cOiH6ike.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-D9cpQPAY.js → Federation-Bvkzg88z.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Jdczr9Mr.js → FormItemContext-BTsiA7h_.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-BT8ZMpC5.js → GenericCardRenderer-BGnVE5qu.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-DjARip2b.js → Git-DS_j6r25.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BgWbSRcj.js → Governance-CKRG3Rsl.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CfouJciM.js → Inference-DW8INoNQ.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-50l7mrAr.js → KnowledgeGraph-BhEYeFhC.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-rq50H84B.js → Logs-wqi0uxuR.js} +2 -2
  35. package/src/assets/web-panel/assets/{MarkdownRenderer-DUPIEdQH.js → MarkdownRenderer-DS8dE8ty.js} +1 -1
  36. package/src/assets/web-panel/assets/{Marketplace-BYgPDmHU.js → Marketplace-CMlTuPLT.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-5DVgcFdx.js → McpTools-CSx4m5JJ.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-B70LPk7f.js → Memory-vnQCUy-_.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-De8ssA5Y.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-RJsOTsIA.js → Mtc-CzK_FPB1.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-Crh-6BEK.js → MtcAudit-Cb7G9oL3.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-wuoR5l3x.js → Multisig-qJxlUyzr.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-U2Mji4fZ.js → NLProgramming-DqaBJ7Te.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CdTZAyDH.js → Notes-CwhTJkRi.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-C-FucWE7.js → NotificationSettings-YDs4Fal4.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-DCYFTeaI.js → OrderTableRenderer-CvEALpq7.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-an3OF85L.js → Organization-CEmQm9n8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-C-1vmYFe.js → Overflow-zby_P_LA.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-Cwh47xKg.js → P2P-DsmDNFNT.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-Dm38GT0k.js → PdhVaultBrowser-FWwFlZGF.js} +3 -3
  52. package/src/assets/web-panel/assets/{Permissions-DS2Y8awD.js → Permissions-DuzQ6me5.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-BQNziA0I.js → PersonalDataHub-DZbeN613.js} +3 -3
  54. package/src/assets/web-panel/assets/{Pipeline-DF85L1ue.js → Pipeline-CoNSVAUS.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BuLornF-.js → Privacy-OYJEAaMC.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-C3Y31IwX.js → ProjectInit-CWIapV--.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-BCPiI8aD.js → ProjectSettings-CX8xMxZn.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-BZ6s4k6g.js → Projects-BLCN8YyY.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-D3Wp022X.js → Providers-BV_sxp75.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CUJfQkH4.js → QrScannerModal-BTnRrHRp.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-UAc0EO8l.js → QuickAsk-D1KZg018.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BaxKupcs.js → Recommend-COm5cuNg.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-C75NNnXZ.js → RemoteSession-0saWEkYI.js} +2 -2
  64. package/src/assets/web-panel/assets/{Reputation-BVXynZAk.js → Reputation-Dr7iKLS2.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-C5MRZ0bi.js → Row-C1vpPBcr.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-DAKkEYka.js → RssFeed-CxlWrtNj.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-DvWv9YOA.js → Search-BT2V8dPN.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-B5peafW1.js → Security-bm9V3c5k.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-UQmvPEaG.js → Services-CocH1yyX.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-C3bhWpuF.js → Skeleton-DHItlMdd.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-DfkKf9_H.js → Skills-C59sLc4w.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DqMSjlBM.js → Sla-Crj4BMc8.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-C-WbxWUX.js → SpeechSettings-DX4KFceY.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CYuHZ4N2.js → SyncSettings-BD_iwQLr.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-CyeAyB9d.js → Tasks-BmCTzYsH.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-BTCeQnpq.js → Templates-BGUaxZYl.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-BifQuA9Y.js → Tenant-BS0tJx2O.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-Bx_9Bocf.js → Terminal--tHSAsld.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-BX0RFCOm.js → TimelineRenderer-D9-MFxS4.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CGHrCmUS.js → Tokens-CiCB485f.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DlqaY4SO.js → Trigger-SmCFuW_l.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DfOmj-ay.js → Trust-DkK3Rqe0.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-B72jH5fE.js → UkeySign-CDM4LGdB.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BT-63fqv.js → VideoEditing-IDB7DJXt.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-Dsul3sHr.js → Wallet-Bikj5cmt.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-Izkwwdgy.js → WebAuthn-C9abWlAw.js} +2 -2
  87. package/src/assets/web-panel/assets/{WorkflowEditor-D3FHSiH1.js → WorkflowEditor-DrbRKVky.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CmVz5z3U.js → chat-CccCSDQf.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-CuLgyJBp.js → colors-CXObXLhb.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-Ml1YkXpw.js → compact-item-B5kfYAxC.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-BvjJ48bs.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-DVC0157C.js → hasIn-Q6lYteAc.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-C229ABfy.js → index-25Wlqps4.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-Ck_52dz_.js → index-33U29UNV.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-QXzlT9KD.js → index-4oXBDczR.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-4SBta3d7.js → index-BGYXvMc5.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-B0S8NzO6.js → index-BJCCa0s_.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-DJyN28Et.js → index-BJJ5DhKI.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DC_OI9H2.js → index-BLOzViJ6.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-C_XuRgGn.js → index-BRrbUMoG.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-wvb_FtaV.js → index-BUFpT17Q.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-KMqhcF7l.js → index-BkRDiqGc.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-vG4XSJ1d.js → index-BsfvW6my.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DYXHFr4r.js → index-By-kfPED.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DuGszaWY.js → index-BzFTcQsc.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CXkJH-lK.js → index-C2FXdboq.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Dt5KMjwJ.js → index-C5XOlTHN.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-4wcgvPoF.js → index-CLhBDS2w.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-C_H_S-DY.js → index-CMq7UeoT.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-BydjdZnl.js → index-CVMTWNKl.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-BjHBpRYZ.js → index-Caghi-yL.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Bzr0rB5T.js → index-CdB6FuSH.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BbPtzB8M.js → index-CoK8-kWQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-BUt8kTIB.js → index-CsO3GUAY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-C_vRgTl9.js → index-CwZBa20Z.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-DukDVXCv.js → index-D-kASucw.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BbepULSc.js → index-D8eBw7vW.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BfuKP4qe.js → index-D9wgifk4.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-BiC6dQZG.js → index-DAYCTc07.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-CmFNvNLZ.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-KjgKdZe-.js → index-DgTLApS9.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-BpFJHiTv.js → index-DgxVJ8pg.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cw0Rjy87.js → index-DlJvIqoR.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-CFYVQMvu.js → index-Dw4eHfsG.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CudmwNBV.js → index-Rh-ytwow.js} +3 -3
  128. package/src/assets/web-panel/assets/{index-DDGaOWBV.js → index-StAEt2pq.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-D3lD5qap.js → index-bzfw951k.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-BzG-x9p4.js → index-fSUMbMvv.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B_EvG_GW.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-BDgd2YZ8.js → initDefaultProps-BNq7X72N.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-ErtrXdYe.js → motion-DOf9viTc.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-_GPPNCgs.js → move-CRHTcW4x.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-DRLL-8V2.js → mtc-parser-Cyoozgm2.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-p86sl023.js → omit-6-TjFNru.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BTu7cMLH.js → pickAttrs-DlUmO1zi.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BEKz6Isr.js → placementArrow-CFOL8mIR.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-Ck42c9MP.js → responsiveObserve-BtaFK5zd.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-1CEvuPsB.js → slide-BIbc2bur.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BDfdx1GJ.js → statusUtils-BXX9d6-w.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-B4K46m5w.js → styleChecker-BGv10Bpn.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-DoIVpJNq.js → useFlexGapSupport-bKnKT8tw.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-DdAWi-ln.js → useFs-FY4IL4jn.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-iUu6k8dx.js → usePersonalDataHub-Bwl9TEgu.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-DZz4R6Tw.js → vnode-BM3OHxAT.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DXlf9sA3.js → zoom-D1fbFiHB.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agenda.js +320 -32
  151. package/src/commands/agent.js +105 -11
  152. package/src/commands/context.js +125 -14
  153. package/src/commands/doctor.js +85 -1
  154. package/src/commands/hook.js +181 -7
  155. package/src/commands/plugin.js +456 -1
  156. package/src/commands/review.js +363 -2
  157. package/src/commands/session.js +191 -0
  158. package/src/commands/team.js +19 -1
  159. package/src/data/changelog.json +10 -4
  160. package/src/gateways/ws/remote-session-protocol.js +39 -0
  161. package/src/gateways/ws/session-protocol.js +55 -2
  162. package/src/gateways/ws/ws-agent-handler.js +50 -0
  163. package/src/harness/jsonl-session-store.js +60 -0
  164. package/src/harness/worktree-isolator.js +140 -5
  165. package/src/lib/agent-schedule-store.js +318 -10
  166. package/src/lib/agent-team/team-worktree.js +37 -2
  167. package/src/lib/agent-tool-admission.js +125 -0
  168. package/src/lib/app-preview.js +360 -0
  169. package/src/lib/async-hook-supervisor.cjs +54 -0
  170. package/src/lib/capability-manifest.js +319 -0
  171. package/src/lib/capability-negotiation.js +23 -12
  172. package/src/lib/channels/channel-manager.js +54 -2
  173. package/src/lib/cli-context-engineering.js +9 -2
  174. package/src/lib/context-breakdown.js +116 -0
  175. package/src/lib/diagnostic-bundle.js +230 -0
  176. package/src/lib/doctor-checkup.js +275 -4
  177. package/src/lib/execution-location.js +286 -0
  178. package/src/lib/headless-manifest.js +13 -40
  179. package/src/lib/hook-event-log.cjs +193 -0
  180. package/src/lib/hook-stats-store.cjs +197 -0
  181. package/src/lib/idempotency.js +109 -0
  182. package/src/lib/interaction-adapter.js +34 -4
  183. package/src/lib/json-schema-output.js +46 -12
  184. package/src/lib/json-schema-validate.js +143 -3
  185. package/src/lib/jsonl-session-store.js +1 -0
  186. package/src/lib/lsp/__tests__/lsp-manager.test.js +58 -0
  187. package/src/lib/lsp/lsp-manager.js +56 -0
  188. package/src/lib/monitor-event.js +290 -0
  189. package/src/lib/operation-fingerprint.js +217 -0
  190. package/src/lib/plugin-runtime/bin.js +37 -0
  191. package/src/lib/plugin-runtime/capabilities.js +19 -0
  192. package/src/lib/plugin-runtime/capability-consent.js +203 -0
  193. package/src/lib/plugin-runtime/hooks.js +37 -0
  194. package/src/lib/plugin-runtime/lsp.js +38 -0
  195. package/src/lib/plugin-runtime/mcp.js +40 -0
  196. package/src/lib/plugin-runtime/monitors.js +37 -0
  197. package/src/lib/plugin-runtime/plugin-options.js +151 -0
  198. package/src/lib/plugin-runtime/policy.js +62 -0
  199. package/src/lib/plugin-runtime/scopes.js +20 -4
  200. package/src/lib/pr-automation-policy.js +224 -0
  201. package/src/lib/project-instructions.js +128 -0
  202. package/src/lib/remote-approval-bridge.js +66 -13
  203. package/src/lib/repl-goal.js +132 -0
  204. package/src/lib/repl-rewind.js +153 -0
  205. package/src/lib/review-comment-anchor.js +241 -0
  206. package/src/lib/review-pipeline.js +46 -7
  207. package/src/lib/runtime-checkup.js +221 -0
  208. package/src/lib/schedule-planner.js +17 -1
  209. package/src/lib/secret-scan.js +409 -0
  210. package/src/lib/session-branch.js +0 -0
  211. package/src/lib/settings-hook-events.cjs +13 -0
  212. package/src/lib/settings-loader.cjs +42 -0
  213. package/src/lib/side-effect-ledger-store.js +88 -0
  214. package/src/lib/side-effect-ledger.js +401 -0
  215. package/src/lib/skill-loader.js +32 -0
  216. package/src/lib/skill-path-scope.js +63 -0
  217. package/src/lib/sub-agent-context.js +110 -9
  218. package/src/lib/sub-agent-registry.js +19 -0
  219. package/src/lib/subagent-inheritance.js +112 -0
  220. package/src/lib/telemetry/span-recorder.js +17 -4
  221. package/src/lib/terminal-context.js +195 -0
  222. package/src/lib/turn-binding-store.js +119 -0
  223. package/src/lib/unattended-action-policy.js +182 -0
  224. package/src/lib/worktree-cleanup-safety.js +138 -0
  225. package/src/lib/worktree-isolator.js +8 -0
  226. package/src/lib/worktree-sparse.js +131 -0
  227. package/src/repl/agent-repl.js +280 -30
  228. package/src/repl/permission-tier.js +28 -0
  229. package/src/runtime/agent-core.js +565 -21
  230. package/src/runtime/coding-agent-contract-shared.cjs +60 -1
  231. package/src/runtime/diagnostics.js +60 -0
  232. package/src/runtime/headless-runner.js +393 -1
  233. package/src/runtime/headless-stream.js +60 -1
  234. package/src/assets/web-panel/assets/MobileProjects-D10VISps.js +0 -1
  235. package/src/assets/web-panel/assets/devWarning-DhiGrbsh.js +0 -1
  236. package/src/assets/web-panel/assets/index-B7a1TrU6.js +0 -1
  237. package/src/assets/web-panel/assets/index-C2B1KRja.js +0 -1
@@ -0,0 +1,151 @@
1
+ /**
2
+ * Plugin OPTIONS resolution (Phase 3 gap: typed optionsSchema + sensitive
3
+ * project-scope gate).
4
+ *
5
+ * A plugin declares `optionsSchema` (typed config with `scope` and `sensitive`
6
+ * in [[capabilities.js]]). Users supply option VALUES at two scopes:
7
+ * - USER scope → user data dir `plugin-options.json` (may hold secrets)
8
+ * - PROJECT scope → repo-local `.chainlesschain/plugin-options.json`
9
+ * (checked-in-able → MUST NOT hold secrets)
10
+ *
11
+ * The security-critical rule (already enforced by `validateOptions`): a
12
+ * SENSITIVE option can never come from project config. `resolvePluginOptions`
13
+ * layers defaults < project (non-sensitive only) < user, DROPS + WARNS on any
14
+ * sensitive/user-only option that appears in project config, and returns the
15
+ * merged values with a redacted view for logging.
16
+ *
17
+ * Store IO is injected (`_deps`) so unit tests never touch the real dirs; the
18
+ * resolver itself is pure.
19
+ */
20
+
21
+ import fs from "fs";
22
+ import path from "path";
23
+ import { getElectronUserDataDir } from "../paths.js";
24
+ import {
25
+ optionDefaults,
26
+ validateOptions,
27
+ redactSensitiveOptions,
28
+ } from "./capabilities.js";
29
+
30
+ export const _deps = {
31
+ existsSync: fs.existsSync,
32
+ readFileSync: fs.readFileSync,
33
+ writeFileSync: fs.writeFileSync,
34
+ mkdirSync: fs.mkdirSync,
35
+ userStorePath: () =>
36
+ path.join(getElectronUserDataDir(), "plugin-options.json"),
37
+ projectStorePath: (cwd) =>
38
+ path.join(cwd || process.cwd(), ".chainlesschain", "plugin-options.json"),
39
+ };
40
+
41
+ function readJsonObject(p) {
42
+ try {
43
+ if (!_deps.existsSync(p)) return {};
44
+ const data = JSON.parse(_deps.readFileSync(p, "utf8"));
45
+ return data && typeof data === "object" && !Array.isArray(data) ? data : {};
46
+ } catch {
47
+ return {};
48
+ }
49
+ }
50
+
51
+ function writeJsonObject(p, obj) {
52
+ _deps.mkdirSync(path.dirname(p), { recursive: true });
53
+ _deps.writeFileSync(p, JSON.stringify(obj, null, 2), "utf8");
54
+ }
55
+
56
+ /** Load a plugin's option VALUES at the given scope (`user` | `project`). */
57
+ export function loadPluginOptionValues(name, scope, { cwd } = {}) {
58
+ const p =
59
+ scope === "project" ? _deps.projectStorePath(cwd) : _deps.userStorePath();
60
+ const store = readJsonObject(p);
61
+ const v = store[name];
62
+ return v && typeof v === "object" && !Array.isArray(v) ? v : {};
63
+ }
64
+
65
+ /** Persist a plugin's option VALUES at a scope (replaces the plugin's entry). */
66
+ export function setPluginOptionValues(name, values, scope, { cwd } = {}) {
67
+ const p =
68
+ scope === "project" ? _deps.projectStorePath(cwd) : _deps.userStorePath();
69
+ const store = readJsonObject(p);
70
+ store[name] = values && typeof values === "object" ? values : {};
71
+ writeJsonObject(p, store);
72
+ return { name, scope, path: p };
73
+ }
74
+
75
+ /** Provided keys of `values` (that exist in schema) accepted at `scope`. */
76
+ function acceptedKeys(schema, values, scope) {
77
+ const res = validateOptions(schema, values, { scope });
78
+ const keys = new Set();
79
+ for (const key of Object.keys(values || {})) {
80
+ if (!Object.prototype.hasOwnProperty.call(schema, key)) continue; // unknown → ignored
81
+ // A provided key was accepted iff no error names it. validateOptions always
82
+ // quotes the offending key as `"<key>"`, so this is an exact match.
83
+ if (!res.errors.some((e) => e.includes(`"${key}"`))) keys.add(key);
84
+ }
85
+ return { res, keys };
86
+ }
87
+
88
+ /**
89
+ * PURE: resolve effective option values from defaults + project + user config.
90
+ *
91
+ * Precedence: defaults < project (non-sensitive, non-user-only) < user.
92
+ * A sensitive or user-only option present in project config is DROPPED and a
93
+ * warning is recorded — a checked-in project file can never inject a secret.
94
+ *
95
+ * @returns {{ options:object, redacted:object, warnings:string[],
96
+ * sources:Record<string,'default'|'project'|'user'>,
97
+ * droppedFromProject:string[] }}
98
+ */
99
+ export function resolvePluginOptions(
100
+ schema,
101
+ { userValues, projectValues } = {},
102
+ ) {
103
+ const s = schema || {};
104
+ const options = optionDefaults(s);
105
+ const sources = {};
106
+ for (const key of Object.keys(options)) sources[key] = "default";
107
+
108
+ const warnings = [];
109
+ const droppedFromProject = [];
110
+
111
+ // Project layer first (lower precedence). validateOptions errors on sensitive/
112
+ // user-only keys at project scope — those are the drops we surface + refuse.
113
+ const proj = acceptedKeys(s, projectValues, "project");
114
+ for (const err of proj.res.errors) {
115
+ if (/sensitive|user-scoped/.test(err)) {
116
+ const m = err.match(/"([^"]+)"/);
117
+ if (m) droppedFromProject.push(m[1]);
118
+ }
119
+ warnings.push(`project: ${err}`);
120
+ }
121
+ for (const w of proj.res.warnings) warnings.push(`project: ${w}`);
122
+ for (const key of proj.keys) {
123
+ options[key] = proj.res.normalized[key];
124
+ sources[key] = "project";
125
+ }
126
+
127
+ // User layer on top (highest precedence; may carry secrets).
128
+ const user = acceptedKeys(s, userValues, "user");
129
+ for (const err of user.res.errors) warnings.push(`user: ${err}`);
130
+ for (const w of user.res.warnings) warnings.push(`user: ${w}`);
131
+ for (const key of user.keys) {
132
+ options[key] = user.res.normalized[key];
133
+ sources[key] = "user";
134
+ }
135
+
136
+ return {
137
+ options,
138
+ redacted: redactSensitiveOptions(s, options),
139
+ warnings,
140
+ sources,
141
+ droppedFromProject: [...new Set(droppedFromProject)],
142
+ };
143
+ }
144
+
145
+ /** Load both scopes' values from disk and resolve against a schema. */
146
+ export function getResolvedPluginOptions(name, schema, { cwd } = {}) {
147
+ return resolvePluginOptions(schema, {
148
+ userValues: loadPluginOptionValues(name, "user", { cwd }),
149
+ projectValues: loadPluginOptionValues(name, "project", { cwd }),
150
+ });
151
+ }
@@ -25,6 +25,7 @@
25
25
  import { enforcePluginPolicy } from "../plugin-security.js";
26
26
  import { loadManagedSettings } from "../settings-loader.cjs";
27
27
  import { verifyInstalledSignature } from "./signature.js";
28
+ import { isPluginCapabilityConsented } from "./capability-consent.js";
28
29
 
29
30
  // Managed settings are org-admin controlled and effectively static for a
30
31
  // session, and discoverPlugins is called by every collector — memoize the load
@@ -142,6 +143,67 @@ export function filterByManagedPolicy(plugins, managed) {
142
143
  return { kept, dropped };
143
144
  }
144
145
 
146
+ /**
147
+ * Whether capability-consent must be ENFORCED at load time. The consent gate
148
+ * ([[capability-consent.js]]) already exists but was consulted only by `cc
149
+ * plugin consent`/`validate`/`doctor` — a plugin that declared capabilities and
150
+ * was never consented still loaded all six component types. Enforcement is
151
+ * OPT-IN (byte-identical default): an org sets `requirePluginCapabilityConsent`
152
+ * in managed settings, or a single user exports `CC_REQUIRE_PLUGIN_CONSENT=1`.
153
+ *
154
+ * @param {object|null} managed managed settings
155
+ * @param {object} [env] process env (injectable)
156
+ * @returns {boolean}
157
+ */
158
+ export function capabilityConsentRequired(managed, env) {
159
+ const raw = (env || process.env).CC_REQUIRE_PLUGIN_CONSENT;
160
+ if (raw != null && /^(1|true|yes|on)$/i.test(String(raw).trim())) return true;
161
+ return (
162
+ managed?.requirePluginCapabilityConsent === true ||
163
+ managed?.requirePluginCapabilityConsent === "require"
164
+ );
165
+ }
166
+
167
+ /**
168
+ * Drop plugins that DECLARE capabilities but whose current capability SET has
169
+ * not been consented (first request or any widening). Fail-closed: a consent
170
+ * check that throws drops the plugin. Legacy plugins that declare no
171
+ * `permissions` block are UNAFFECTED — the same "未声明 legacy 不受限" rule the
172
+ * declaration audit uses. A plugin whose declared set is empty (all-deny) is
173
+ * auto-consented by `isPluginCapabilityConsented`, so it stays.
174
+ *
175
+ * @param {Array<{name, scope, manifest}>} plugins
176
+ * @returns {{ kept: Array, dropped: Array<{name, reason}> }}
177
+ */
178
+ export function filterByCapabilityConsent(plugins) {
179
+ const kept = [];
180
+ const dropped = [];
181
+ for (const p of plugins) {
182
+ if (!p.manifest?.capabilitiesDeclared) {
183
+ kept.push(p); // legacy plugin, no declaration → not gated
184
+ continue;
185
+ }
186
+ let consented = false;
187
+ try {
188
+ consented = isPluginCapabilityConsented(
189
+ { name: p.name, scope: p.scope },
190
+ p.manifest.capabilities,
191
+ );
192
+ } catch {
193
+ consented = false; // fail closed
194
+ }
195
+ if (consented) {
196
+ kept.push(p);
197
+ } else {
198
+ dropped.push({
199
+ name: p.name,
200
+ reason: `capabilities not consented — run \`cc plugin consent ${p.name} --grant\``,
201
+ });
202
+ }
203
+ }
204
+ return { kept, dropped };
205
+ }
206
+
145
207
  let _warnedDropped = new Set();
146
208
 
147
209
  /** One-time stderr warning naming plugins an org policy blocked from loading. */
@@ -27,6 +27,8 @@ import { parsePluginManifest } from "./manifest.js";
27
27
  import {
28
28
  loadManagedPluginPolicy,
29
29
  filterByManagedPolicy,
30
+ filterByCapabilityConsent,
31
+ capabilityConsentRequired,
30
32
  warnDroppedOnce,
31
33
  } from "./policy.js";
32
34
 
@@ -160,10 +162,24 @@ export function discoverPlugins(opts = {}) {
160
162
  env: opts.env,
161
163
  managedSettingsFile: opts.managedSettingsFile,
162
164
  });
163
- if (!managed) return all;
164
- const { kept, dropped } = filterByManagedPolicy(all, managed);
165
- if (dropped.length > 0) warnDroppedOnce(dropped);
166
- return kept;
165
+ const consentGate = capabilityConsentRequired(managed, opts.env);
166
+ // Default (no managed policy AND consent enforcement off): byte-identical —
167
+ // return every discovered plugin, exactly as before.
168
+ if (!managed && !consentGate) return all;
169
+ let list = all;
170
+ if (managed) {
171
+ const { kept, dropped } = filterByManagedPolicy(list, managed);
172
+ if (dropped.length > 0) warnDroppedOnce(dropped);
173
+ list = kept;
174
+ }
175
+ // Capability-consent enforcement funnels through the SAME chokepoint, so an
176
+ // un-consented plugin loads NONE of its six component types (opt-in).
177
+ if (consentGate) {
178
+ const { kept, dropped } = filterByCapabilityConsent(list);
179
+ if (dropped.length > 0) warnDroppedOnce(dropped);
180
+ list = kept;
181
+ }
182
+ return list;
167
183
  }
168
184
 
169
185
  // ── internals ────────────────────────────────────────────────────────────
@@ -0,0 +1,224 @@
1
+ /**
2
+ * PR/CI automation policy — the fail-closed decision engine behind "P1-4 PR/CI
3
+ * Monitor、Auto-fix 与受控合并" of CLAUDE_CODE_IDE_INCREMENTAL_GAP_ANALYSIS_2026-
4
+ * 07-13.md.
5
+ *
6
+ * Letting an agent iterate on CI failures and merge PRs is useful and dangerous.
7
+ * The gap doc pins the guardrails precisely: each failing check may be auto-
8
+ * fixed at most N times within a token/time budget; a fix must read only the
9
+ * CURRENT commit's logs (never chase a stale failure); auto-merge is OFF by
10
+ * default and, when on, requires branch protection, all required checks, an
11
+ * approving review and zero pending approvals — and never pushes a protected
12
+ * branch directly or bypasses protection.
13
+ *
14
+ * This module is those decisions as pure predicates. It holds no state and does
15
+ * no I/O — a caller feeds the observed PR/CI signals and gets an allow/deny with
16
+ * a named reason. Everything defaults to DENY: an unknown or missing signal
17
+ * blocks the automation rather than assuming it is safe.
18
+ */
19
+
20
+ /** Canonical CI check states. */
21
+ export const CHECK_STATE = Object.freeze({
22
+ PASSED: "passed",
23
+ FAILED: "failed",
24
+ PENDING: "pending",
25
+ NEUTRAL: "neutral",
26
+ ERROR: "error",
27
+ });
28
+
29
+ const CHECK_ALIASES = new Map([
30
+ ["passed", CHECK_STATE.PASSED],
31
+ ["pass", CHECK_STATE.PASSED],
32
+ ["success", CHECK_STATE.PASSED],
33
+ ["succeeded", CHECK_STATE.PASSED],
34
+ ["green", CHECK_STATE.PASSED],
35
+ ["failed", CHECK_STATE.FAILED],
36
+ ["fail", CHECK_STATE.FAILED],
37
+ ["failure", CHECK_STATE.FAILED],
38
+ ["red", CHECK_STATE.FAILED],
39
+ ["pending", CHECK_STATE.PENDING],
40
+ ["queued", CHECK_STATE.PENDING],
41
+ ["in_progress", CHECK_STATE.PENDING],
42
+ ["running", CHECK_STATE.PENDING],
43
+ ["neutral", CHECK_STATE.NEUTRAL],
44
+ ["skipped", CHECK_STATE.NEUTRAL],
45
+ ["cancelled", CHECK_STATE.NEUTRAL],
46
+ ["canceled", CHECK_STATE.NEUTRAL],
47
+ ["error", CHECK_STATE.ERROR],
48
+ ["timed_out", CHECK_STATE.ERROR],
49
+ ["action_required", CHECK_STATE.ERROR],
50
+ ]);
51
+
52
+ /** Normalize any provider's check conclusion/status to a canonical state. */
53
+ export function normalizeCheckState(value) {
54
+ if (typeof value !== "string") return null;
55
+ return CHECK_ALIASES.get(value.trim().toLowerCase()) || null;
56
+ }
57
+
58
+ /** A failing state that auto-fix could act on. */
59
+ function isFailing(state) {
60
+ return state === CHECK_STATE.FAILED || state === CHECK_STATE.ERROR;
61
+ }
62
+
63
+ /**
64
+ * Summarize a set of checks. `allPassed` is true only when there is at least one
65
+ * check and every one is passed or neutral (a bare PR with no checks is NOT
66
+ * "all passed" — required checks must actually exist and run).
67
+ *
68
+ * @param {Array<{name?:string, state?:string, conclusion?:string, status?:string}>} checks
69
+ */
70
+ export function summarizeChecks(checks = []) {
71
+ const list = Array.isArray(checks) ? checks : [];
72
+ let passed = 0;
73
+ let failed = 0;
74
+ let pending = 0;
75
+ const byName = new Map();
76
+ for (const c of list) {
77
+ const state =
78
+ normalizeCheckState(c?.state) ??
79
+ normalizeCheckState(c?.conclusion) ??
80
+ normalizeCheckState(c?.status);
81
+ if (c?.name != null) byName.set(String(c.name), state);
82
+ if (state === CHECK_STATE.PASSED || state === CHECK_STATE.NEUTRAL) passed++;
83
+ else if (isFailing(state)) failed++;
84
+ else pending++; // pending / unknown → not yet decided
85
+ }
86
+ return {
87
+ total: list.length,
88
+ passed,
89
+ failed,
90
+ pending,
91
+ anyFailed: failed > 0,
92
+ allPassed: list.length > 0 && failed === 0 && pending === 0,
93
+ byName,
94
+ };
95
+ }
96
+
97
+ function budgetExhausted(budget) {
98
+ if (!budget || typeof budget !== "object") return false;
99
+ const over = (spent, cap) =>
100
+ cap != null && Number.isFinite(Number(cap)) && Number(spent) >= Number(cap);
101
+ return (
102
+ over(budget.tokensSpent, budget.tokenBudget) ||
103
+ over(budget.timeSpentMs, budget.timeBudgetMs)
104
+ );
105
+ }
106
+
107
+ /**
108
+ * Decide whether an agent may auto-fix ONE failing check right now. Fail-closed:
109
+ * anything unproven denies.
110
+ *
111
+ * @param {object} params
112
+ * @param {object} params.check {name, state|conclusion, logCommitSha}
113
+ * @param {string} params.headCommitSha the PR head the fix would target
114
+ * @param {number} params.iteration fixes already attempted for this check
115
+ * @param {number} params.maxIterations cap (default 3)
116
+ * @param {object} [params.budget] {tokensSpent, tokenBudget, timeSpentMs, timeBudgetMs}
117
+ * @returns {{allow:boolean, reason:string}}
118
+ */
119
+ export function autoFixDecision(params = {}) {
120
+ const check = params.check || {};
121
+ const state =
122
+ normalizeCheckState(check.state) ?? normalizeCheckState(check.conclusion);
123
+ if (!isFailing(state)) {
124
+ return { allow: false, reason: "check-not-failing" };
125
+ }
126
+
127
+ const maxIterations = Number.isFinite(Number(params.maxIterations))
128
+ ? Number(params.maxIterations)
129
+ : 3;
130
+ const iteration = Number(params.iteration) || 0;
131
+ if (iteration >= maxIterations) {
132
+ return { allow: false, reason: "max-iterations-reached" };
133
+ }
134
+
135
+ // Only fix the CURRENT failure: the log we would read must belong to the head
136
+ // commit. A missing/older log sha means we cannot prove freshness → deny.
137
+ const logSha = check.logCommitSha == null ? "" : String(check.logCommitSha);
138
+ const headSha =
139
+ params.headCommitSha == null ? "" : String(params.headCommitSha);
140
+ if (!logSha || !headSha) {
141
+ return { allow: false, reason: "commit-sha-unverifiable" };
142
+ }
143
+ if (logSha !== headSha) {
144
+ return { allow: false, reason: "stale-failure-log" };
145
+ }
146
+
147
+ if (budgetExhausted(params.budget)) {
148
+ return { allow: false, reason: "budget-exhausted" };
149
+ }
150
+
151
+ return { allow: true, reason: "ok" };
152
+ }
153
+
154
+ /**
155
+ * Decide whether a PR may be auto-merged. Fail-closed and exhaustive: every
156
+ * unmet requirement is collected, and merge is allowed ONLY when the list is
157
+ * empty. Auto-merge is off unless `enabled === true`.
158
+ *
159
+ * @param {object} params
160
+ * @param {boolean} params.enabled auto-merge explicitly turned on
161
+ * @param {boolean} params.hasOpenPr a real PR exists (no direct-to-branch)
162
+ * @param {boolean} params.branchProtectionSatisfied
163
+ * @param {boolean} params.reviewApproved
164
+ * @param {number} params.pendingApprovals permission approvals still waiting
165
+ * @param {string[]} params.requiredChecks names that must be present AND passed
166
+ * @param {Array} params.checks observed checks (summarizeChecks input)
167
+ * @param {string} [params.targetBranch]
168
+ * @returns {{allow:boolean, reason:string, unmet:string[]}}
169
+ */
170
+ export function autoMergeDecision(params = {}) {
171
+ const unmet = [];
172
+
173
+ if (params.enabled !== true) unmet.push("auto-merge-disabled");
174
+ if (params.hasOpenPr !== true) unmet.push("no-open-pr");
175
+ if (params.branchProtectionSatisfied !== true) {
176
+ unmet.push("branch-protection-unsatisfied");
177
+ }
178
+ if (params.reviewApproved !== true) unmet.push("review-not-approved");
179
+ if (Number(params.pendingApprovals) > 0) unmet.push("pending-approvals");
180
+
181
+ const summary = summarizeChecks(params.checks);
182
+ if (summary.anyFailed) unmet.push("checks-failing");
183
+ if (summary.pending > 0) unmet.push("checks-pending");
184
+
185
+ // Every REQUIRED check must be present and passed — a required check that
186
+ // never ran is a block, not a pass.
187
+ const required = Array.isArray(params.requiredChecks)
188
+ ? params.requiredChecks
189
+ : [];
190
+ for (const name of required) {
191
+ const state = summary.byName.get(String(name));
192
+ if (state !== CHECK_STATE.PASSED && state !== CHECK_STATE.NEUTRAL) {
193
+ unmet.push(`required-check-missing:${name}`);
194
+ }
195
+ }
196
+
197
+ return {
198
+ allow: unmet.length === 0,
199
+ reason: unmet.length === 0 ? "ok" : unmet[0],
200
+ unmet,
201
+ };
202
+ }
203
+
204
+ /**
205
+ * A compact, log-safe status line for the PR status bar (P1-4 "Session 顶部提供
206
+ * PR 状态条"). Carries only coordinates + a checks tally, never a token.
207
+ *
208
+ * @param {object} params {branch, prNumber, checks, reviewApproved, mergeable}
209
+ * @returns {string}
210
+ */
211
+ export function describePrStatusBar(params = {}) {
212
+ const parts = [];
213
+ if (params.branch) parts.push(`branch:${params.branch}`);
214
+ if (params.prNumber != null) parts.push(`PR#${params.prNumber}`);
215
+ const s = summarizeChecks(params.checks);
216
+ parts.push(`checks:${s.passed}/${s.total}${s.anyFailed ? " ✗" : ""}`);
217
+ parts.push(
218
+ `review:${params.reviewApproved === true ? "approved" : "pending"}`,
219
+ );
220
+ if (params.mergeable != null) {
221
+ parts.push(`merge:${params.mergeable ? "ready" : "blocked"}`);
222
+ }
223
+ return parts.join(" · ");
224
+ }
@@ -477,4 +477,132 @@ export function loadProjectInstructionsBlock(opts = {}) {
477
477
  }
478
478
  }
479
479
 
480
+ function isDir(fs, p) {
481
+ try {
482
+ return fs.statSync(p).isDirectory();
483
+ } catch {
484
+ return false;
485
+ }
486
+ }
487
+
488
+ /**
489
+ * Lazy subtree-instruction discovery (module 99 §5.3 "子目录指令按首次访问子树
490
+ * 懒加载"). Startup only loads the instruction chain from the project root DOWN
491
+ * to the cwd (see `findInstructionFiles`); a large monorepo's per-package
492
+ * `cc.md`/`CLAUDE.md`/`AGENTS.md` that sit BELOW the cwd are intentionally NOT
493
+ * injected up front — they cost tokens for subtrees a turn may never touch.
494
+ *
495
+ * This resolver answers "given a path a tool just accessed, which subtree
496
+ * instruction files should now be injected?" — the files in every directory
497
+ * strictly between `baseDir` (the already-loaded cwd) and the accessed path's
498
+ * directory, minus anything already loaded or excluded. Pure + deterministic;
499
+ * the tool-time injection itself is the caller's job.
500
+ *
501
+ * @param {object} opts
502
+ * @param {string} opts.repoRoot excludes are matched relative to this
503
+ * @param {string} opts.baseDir the startup cwd (its own file already loaded)
504
+ * @param {string} opts.accessedPath a file/dir a tool touched (abs or rel to baseDir)
505
+ * @param {Set<string>|string[]} [opts.alreadyLoaded] abs paths already injected
506
+ * @param {string[]} [opts.instructionExcludes]
507
+ * @returns {Array<{path:string, scope:"project", dir:string}>} shallowest-first
508
+ */
509
+ export function resolveSubtreeInstructions(opts = {}) {
510
+ const { fs, path } = resolveDeps(opts);
511
+ const repoRoot = path.resolve(opts.repoRoot || opts.baseDir || process.cwd());
512
+ const baseDir = path.resolve(opts.baseDir || repoRoot);
513
+ const excludes = normalizeInstructionExcludes(opts.instructionExcludes);
514
+ const already =
515
+ opts.alreadyLoaded instanceof Set
516
+ ? opts.alreadyLoaded
517
+ : new Set((opts.alreadyLoaded || []).map((p) => path.resolve(p)));
518
+
519
+ if (!opts.accessedPath) return [];
520
+ // Resolve the accessed path to a directory inside the subtree. A not-yet-
521
+ // created file (about to be written) stats as neither file nor dir → treat it
522
+ // as a file and use its parent.
523
+ let target = path.resolve(baseDir, opts.accessedPath);
524
+ if (!isDir(fs, target)) target = path.dirname(target);
525
+
526
+ // Subtree instructions apply only to DESCENDANTS of baseDir (ancestors were
527
+ // loaded at startup). Reject a target that escapes baseDir or the repo.
528
+ const relFromBase = path.relative(baseDir, target);
529
+ if (
530
+ !relFromBase ||
531
+ relFromBase.startsWith("..") ||
532
+ path.isAbsolute(relFromBase)
533
+ ) {
534
+ return [];
535
+ }
536
+ const relFromRoot = path.relative(repoRoot, target);
537
+ if (relFromRoot.startsWith("..") || path.isAbsolute(relFromRoot)) return [];
538
+
539
+ // Directories strictly between baseDir (exclusive) and target (inclusive).
540
+ const dirs = [];
541
+ let dir = target;
542
+ while (dir !== baseDir) {
543
+ dirs.push(dir);
544
+ const parent = path.dirname(dir);
545
+ if (parent === dir) break; // hit fs root without meeting baseDir
546
+ dir = parent;
547
+ }
548
+ dirs.reverse(); // shallowest first
549
+
550
+ const out = [];
551
+ for (const d of dirs) {
552
+ const file = firstExisting(fs, path, d, PROJECT_FILE_NAMES);
553
+ if (!file) continue;
554
+ const abs = path.resolve(file);
555
+ if (already.has(abs)) continue;
556
+ if (
557
+ excludes.length > 0 &&
558
+ pathIsExcluded(path.relative(repoRoot, abs), excludes)
559
+ ) {
560
+ continue;
561
+ }
562
+ out.push({ path: abs, scope: "project", dir: d });
563
+ }
564
+ return out;
565
+ }
566
+
567
+ /**
568
+ * Stateful companion to `resolveSubtreeInstructions`: tracks which subtree
569
+ * directories have already been injected across a session so a second access to
570
+ * the same subtree is a no-op. `onAccess(path)` returns ONLY the freshly
571
+ * discovered files. Fail-open (any error → []).
572
+ */
573
+ export class SubtreeInstructionLoader {
574
+ constructor(opts = {}) {
575
+ const path = opts.deps?.path || pathDefault;
576
+ this._opts = opts;
577
+ this._loadedDirs = new Set();
578
+ this._loadedFiles = new Set(
579
+ (opts.alreadyLoaded || []).map((p) => path.resolve(p)),
580
+ );
581
+ }
582
+
583
+ /** @returns {Array<{path:string, scope:"project", dir:string}>} newly injected */
584
+ onAccess(accessedPath) {
585
+ try {
586
+ const found = resolveSubtreeInstructions({
587
+ ...this._opts,
588
+ accessedPath,
589
+ alreadyLoaded: this._loadedFiles,
590
+ });
591
+ const fresh = found.filter((f) => !this._loadedDirs.has(f.dir));
592
+ for (const f of fresh) {
593
+ this._loadedDirs.add(f.dir);
594
+ this._loadedFiles.add(f.path);
595
+ }
596
+ return fresh;
597
+ } catch {
598
+ return [];
599
+ }
600
+ }
601
+
602
+ /** Absolute paths injected so far (startup set + every onAccess discovery). */
603
+ loadedFiles() {
604
+ return [...this._loadedFiles];
605
+ }
606
+ }
607
+
480
608
  export const _deps = { fs: fsDefault, path: pathDefault, os: osDefault };