chainlesschain 0.162.71 → 0.162.73

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 (176) hide show
  1. package/package.json +3 -2
  2. package/src/assets/web-panel/assets/{AIOps-pes7_jGr.js → AIOps-DTI_-iZ_.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-DiHWdeH1.js → ActionButton-CtpbGicW.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-C51UX-V_.js → Analytics-C1HwMd7E.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-BE6LJLw9.js → AppLayout-Db953rqB.js} +5 -5
  6. package/src/assets/web-panel/assets/{Audit-CtTkLTe2.js → Audit-CuIi9Vup.js} +1 -1
  7. package/src/assets/web-panel/assets/{Backup-lmpVwPxc.js → Backup-DwR7Wlve.js} +1 -1
  8. package/src/assets/web-panel/assets/{BaseInput-Dr7DR1E1.js → BaseInput-B5wNMp7S.js} +1 -1
  9. package/src/assets/web-panel/assets/{Chat-DXph6klA.js → Chat-DivalHgk.js} +6 -6
  10. package/src/assets/web-panel/assets/{ChatBubbleRenderer-RefrXxqp.js → ChatBubbleRenderer-BwPSr02C.js} +1 -1
  11. package/src/assets/web-panel/assets/{Checkbox-BF0aS5R9.js → Checkbox-CJrXPCJH.js} +1 -1
  12. package/src/assets/web-panel/assets/{Codegen-B-oHWNTV.js → Codegen-BieZvOkC.js} +1 -1
  13. package/src/assets/web-panel/assets/{Col-B6KdyRTE.js → Col-BzvPsQgb.js} +1 -1
  14. package/src/assets/web-panel/assets/{Community-D5ac0KyO.js → Community-CWcAMlpQ.js} +1 -1
  15. package/src/assets/web-panel/assets/{Compact-Dc61O2eQ.js → Compact-Bj_BLBpw.js} +1 -1
  16. package/src/assets/web-panel/assets/{Compliance-a48qvNmd.js → Compliance-DKvo0WAi.js} +1 -1
  17. package/src/assets/web-panel/assets/{Cowork-CIF9lSRj.js → Cowork-DRajk8JO.js} +3 -3
  18. package/src/assets/web-panel/assets/{Cron-C_gkf4xd.js → Cron-DPz9MoUo.js} +2 -2
  19. package/src/assets/web-panel/assets/{Crosschain-CJt15PzW.js → Crosschain-BpSuvmns.js} +1 -1
  20. package/src/assets/web-panel/assets/{DID-D8I_okEj.js → DID-Co98G00H.js} +2 -2
  21. package/src/assets/web-panel/assets/{Dashboard-O4q-qCF2.js → Dashboard-8TSG0W7f.js} +2 -2
  22. package/src/assets/web-panel/assets/{Dropdown-flRziiaw.js → Dropdown-CKViu7w3.js} +1 -1
  23. package/src/assets/web-panel/assets/{EmailListRenderer-CnnkfGPw.js → EmailListRenderer-BqfdADvJ.js} +1 -1
  24. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CYAJpGqd.js → FamilyGuardDashboard-aWu1imGZ.js} +1 -1
  25. package/src/assets/web-panel/assets/{Federation-Dm3WqzQ_.js → Federation-w4YpT8EN.js} +1 -1
  26. package/src/assets/web-panel/assets/{FormItemContext-CpAIter6.js → FormItemContext-CAk4CXid.js} +1 -1
  27. package/src/assets/web-panel/assets/{GenericCardRenderer-ChzXcyuN.js → GenericCardRenderer-MoJw4J2L.js} +1 -1
  28. package/src/assets/web-panel/assets/{Git-DElmMsL4.js → Git-HNU_a9Jz.js} +2 -2
  29. package/src/assets/web-panel/assets/{Governance-EWmgn9io.js → Governance-BkIXWYUS.js} +1 -1
  30. package/src/assets/web-panel/assets/{Inference-VtOR_sef.js → Inference-CnquB88h.js} +1 -1
  31. package/src/assets/web-panel/assets/{KnowledgeGraph-OMqlGkMR.js → KnowledgeGraph-PGSMF-uq.js} +1 -1
  32. package/src/assets/web-panel/assets/{Logs-CTgYDsTZ.js → Logs-DCc-nYzu.js} +2 -2
  33. package/src/assets/web-panel/assets/{Marketplace-DZIUTbn8.js → Marketplace-CyGX8XEt.js} +1 -1
  34. package/src/assets/web-panel/assets/{McpTools-DWeOErCA.js → McpTools-DBkZ5yUL.js} +4 -4
  35. package/src/assets/web-panel/assets/{Memory-DwFBp-ev.js → Memory-DmYLRCwo.js} +2 -2
  36. package/src/assets/web-panel/assets/{MobileBridge-D42xBfE3.js → MobileBridge-CszJjeNc.js} +2 -2
  37. package/src/assets/web-panel/assets/{MobileProjects-BHCHauUl.js → MobileProjects-DWgtDsta.js} +1 -1
  38. package/src/assets/web-panel/assets/{Mtc-CabnNsyS.js → Mtc-Bmt_Pmg4.js} +3 -3
  39. package/src/assets/web-panel/assets/{MtcAudit-Ddx9Awu5.js → MtcAudit-BmG0ys0l.js} +2 -2
  40. package/src/assets/web-panel/assets/{Multisig-CJpYBZ8f.js → Multisig-DNd5iMmD.js} +3 -3
  41. package/src/assets/web-panel/assets/{NLProgramming-BvPJskVG.js → NLProgramming-yxbNl09B.js} +1 -1
  42. package/src/assets/web-panel/assets/{Notes-92ELvJM2.js → Notes-CQ0qAzI4.js} +3 -3
  43. package/src/assets/web-panel/assets/{NotificationSettings-CXz0SUzP.js → NotificationSettings-CIzkE4ao.js} +1 -1
  44. package/src/assets/web-panel/assets/{OrderTableRenderer-4iWRkMr-.js → OrderTableRenderer-e1VMUxTi.js} +1 -1
  45. package/src/assets/web-panel/assets/{Organization-DblqJPeY.js → Organization-BNBIlf_R.js} +4 -4
  46. package/src/assets/web-panel/assets/{Overflow-CHfS3HwD.js → Overflow-C5GzzJTq.js} +1 -1
  47. package/src/assets/web-panel/assets/{P2P-DIg0pLIG.js → P2P-DjL2jIED.js} +2 -2
  48. package/src/assets/web-panel/assets/{PdhVaultBrowser-BhD3DGjk.js → PdhVaultBrowser-C2EYDmpW.js} +3 -3
  49. package/src/assets/web-panel/assets/{Permissions-Ct7bwrz8.js → Permissions-BVGCXxDp.js} +4 -4
  50. package/src/assets/web-panel/assets/{PersonalDataHub-DG9c1eas.js → PersonalDataHub-B1xBro16.js} +4 -4
  51. package/src/assets/web-panel/assets/{Pipeline-BwQnEqG0.js → Pipeline-DIYWiDnt.js} +1 -1
  52. package/src/assets/web-panel/assets/{Privacy-BvWaf-Ba.js → Privacy--Qx3QdO1.js} +1 -1
  53. package/src/assets/web-panel/assets/{ProjectInit-BFzMHtiW.js → ProjectInit-DYOEcV04.js} +2 -2
  54. package/src/assets/web-panel/assets/{ProjectSettings-DDpokSpH.js → ProjectSettings-BcOx5-KS.js} +2 -2
  55. package/src/assets/web-panel/assets/{Projects-3IL8lyPl.js → Projects-CO69txk4.js} +1 -1
  56. package/src/assets/web-panel/assets/{Providers-C6BsEzSR.js → Providers-Bj8pCgzo.js} +1 -1
  57. package/src/assets/web-panel/assets/{QuickAsk-Mh6690Ey.js → QuickAsk-Cukpq-4s.js} +1 -1
  58. package/src/assets/web-panel/assets/{Recommend-CJbZ6wgA.js → Recommend-DJB5wUfM.js} +1 -1
  59. package/src/assets/web-panel/assets/{Reputation-GDcyl6iQ.js → Reputation-B9t0-nT0.js} +1 -1
  60. package/src/assets/web-panel/assets/{Row-6p4DKFSi.js → Row-4cniRgxC.js} +1 -1
  61. package/src/assets/web-panel/assets/{RssFeed-BLhrkoDq.js → RssFeed-Y4db_T9t.js} +3 -3
  62. package/src/assets/web-panel/assets/{Search-Ckiyt35t.js → Search-DggZDzlc.js} +1 -1
  63. package/src/assets/web-panel/assets/{Security-qhh-B5Qt.js → Security-C67A6kED.js} +4 -4
  64. package/src/assets/web-panel/assets/{Services-D7a2OwKm.js → Services-BocBjvaa.js} +2 -2
  65. package/src/assets/web-panel/assets/{Skeleton-CSMxupKd.js → Skeleton-C9jI-iOy.js} +1 -1
  66. package/src/assets/web-panel/assets/{Skills-CGpMng8w.js → Skills-2baFHKOW.js} +1 -1
  67. package/src/assets/web-panel/assets/{Sla-CYtaU3Ar.js → Sla-CnowXeRb.js} +1 -1
  68. package/src/assets/web-panel/assets/{SpeechSettings-BTCsu1uM.js → SpeechSettings-DWGBkmUt.js} +1 -1
  69. package/src/assets/web-panel/assets/{SyncSettings-Cnzx2L5I.js → SyncSettings-B89qwCgX.js} +2 -2
  70. package/src/assets/web-panel/assets/Tasks-C1_WvNc0.js +1 -0
  71. package/src/assets/web-panel/assets/{Templates-Ccdx_C3w.js → Templates-P8dBhbxa.js} +1 -1
  72. package/src/assets/web-panel/assets/{Tenant-ZN8c86j3.js → Tenant-DHo0pxrm.js} +1 -1
  73. package/src/assets/web-panel/assets/Terminal-Dr-HvjH-.js +3 -0
  74. package/src/assets/web-panel/assets/{TimelineRenderer-CjgirE9d.js → TimelineRenderer-DM7dOaUR.js} +1 -1
  75. package/src/assets/web-panel/assets/{Tokens-QoRpi6Wf.js → Tokens-DnJxqOPM.js} +1 -1
  76. package/src/assets/web-panel/assets/{Trigger-CEQKJkhs.js → Trigger-C-FLgMzr.js} +1 -1
  77. package/src/assets/web-panel/assets/{Trust-UTuAzV1q.js → Trust-CIT_RVqA.js} +1 -1
  78. package/src/assets/web-panel/assets/{UkeySign-JDs9I7fl.js → UkeySign-Cg9VS6y6.js} +1 -1
  79. package/src/assets/web-panel/assets/{VideoEditing-BACYQQSd.js → VideoEditing-BzoxE8mA.js} +1 -1
  80. package/src/assets/web-panel/assets/{Wallet-C-SKluhH.js → Wallet-ZkmCII8R.js} +3 -3
  81. package/src/assets/web-panel/assets/{WebAuthn-9chizTFy.js → WebAuthn-CptQdp3u.js} +5 -5
  82. package/src/assets/web-panel/assets/{WorkflowEditor-0mF-LAUo.js → WorkflowEditor-Vkz28khO.js} +1 -1
  83. package/src/assets/web-panel/assets/{chat-Di7QINiP.js → chat-DXdmvvEA.js} +1 -1
  84. package/src/assets/web-panel/assets/{colors-SBmhHMM9.js → colors-De7FCj5l.js} +1 -1
  85. package/src/assets/web-panel/assets/{compact-item-BmbETbNj.js → compact-item-DnT3XzME.js} +1 -1
  86. package/src/assets/web-panel/assets/{createContext-C7hFnsJ7.js → createContext-ECZfCCqd.js} +1 -1
  87. package/src/assets/web-panel/assets/devWarning-gaA8VqZf.js +1 -0
  88. package/src/assets/web-panel/assets/{hasIn-CFtthKDD.js → hasIn-Uodg6pAt.js} +1 -1
  89. package/src/assets/web-panel/assets/{index-DRBc1Ewn.js → index-1dWvOqru.js} +1 -1
  90. package/src/assets/web-panel/assets/{index-WjZVyLn7.js → index-BC8SOuGi.js} +1 -1
  91. package/src/assets/web-panel/assets/{index-CovTrPpI.js → index-BUMNJRCl.js} +1 -1
  92. package/src/assets/web-panel/assets/{index-CSnTUPQx.js → index-Bg1RsfAJ.js} +3 -3
  93. package/src/assets/web-panel/assets/{index-NxxQTlPJ.js → index-Blo85ZKd.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-ClIp4Vin.js → index-BtJ8BHU0.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-B4etIqbf.js → index-C01WM7Pk.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-DWfObC0n.js → index-C4KhsGXo.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-sz0w-D-C.js → index-C6JgjkY7.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-lRVjtXeH.js → index-C9_Cnvha.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-BCcCs7LJ.js → index-CCguvASR.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-C6tZzsb9.js → index-CLzPxAp4.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-D0yO3vWh.js → index-CMuqxCVw.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-BcawAm_i.js → index-COTabJQd.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-Cib-RTws.js → index-CPjI3AGE.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C07kpleB.js → index-CaDZ-LMg.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DEYnWiq1.js → index-CbbfcGeJ.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CgXQwqXr.js → index-CcLDjTgI.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-C2CN7coN.js → index-CffZBdA1.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-PN02VlUB.js → index-CwXP0KcT.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-_hO8-EnW.js → index-D2YOclGO.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-Dk4ez5rf.js → index-DBsl2SuZ.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-D6vNUp6c.js → index-DCvmjxu9.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-sA4vr5WV.js → index-DGwgEqwl.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-DI2xH1fU.js → index-DOIeo8Qs.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-_a2NG3iP.js → index-DPcpxSQt.js} +1 -1
  115. package/src/assets/web-panel/assets/index-DTpmiTGK.js +1 -0
  116. package/src/assets/web-panel/assets/{index-CWDk3nDZ.js → index-Db33l8Ix.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-CNpb8m5a.js → index-Dbfj4QL5.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-Dww6gCzI.js → index-Dd9Bx3tJ.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-Bpo7UVJe.js → index-Dkvf5upf.js} +1 -1
  120. package/src/assets/web-panel/assets/index-DwpvMYUU.js +1 -0
  121. package/src/assets/web-panel/assets/{index-BT2uOwmA.js → index-Dy0AT-uc.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-C2fhXmhW.js → index-FU8Zo5cp.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BcRTX_WO.js → index-JVwNC4yP.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-Dps9xdE8.js → index-JqSxr1A7.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-D134XFWR.js → index-KV6dXwKC.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D8CeUlN0.js → index-LwNszM-5.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CcBhsVYn.js → index-cHnmjGqB.js} +1 -1
  128. package/src/assets/web-panel/assets/{initDefaultProps-B1zCwkvT.js → initDefaultProps-B3H1sf95.js} +1 -1
  129. package/src/assets/web-panel/assets/{motion-BDGIV9KA.js → motion-DreYO5eb.js} +1 -1
  130. package/src/assets/web-panel/assets/{move-BITqgluK.js → move-B0OVU04M.js} +1 -1
  131. package/src/assets/web-panel/assets/{omit-uD97QKBF.js → omit-PcEEXHLZ.js} +1 -1
  132. package/src/assets/web-panel/assets/{pickAttrs-CZpXhSIE.js → pickAttrs-DH6-hDkF.js} +1 -1
  133. package/src/assets/web-panel/assets/{placementArrow-ByUvT_08.js → placementArrow-DFamUSEw.js} +1 -1
  134. package/src/assets/web-panel/assets/{responsiveObserve-BQG9LlBs.js → responsiveObserve-DT1grAyh.js} +1 -1
  135. package/src/assets/web-panel/assets/{slide-Bx9TyeEE.js → slide-41lCsY6d.js} +1 -1
  136. package/src/assets/web-panel/assets/{statusUtils-XWaMqYM8.js → statusUtils-FzJ7j2T7.js} +1 -1
  137. package/src/assets/web-panel/assets/{styleChecker-BZ-nMyDB.js → styleChecker-D0Zvn50h.js} +1 -1
  138. package/src/assets/web-panel/assets/{useFlexGapSupport-D_iwA3vx.js → useFlexGapSupport-BTxsJE5n.js} +1 -1
  139. package/src/assets/web-panel/assets/{useFs-DuvW_aRX.js → useFs-DRVttEr3.js} +1 -1
  140. package/src/assets/web-panel/assets/{usePersonalDataHub-BO7orS0d.js → usePersonalDataHub-BSe7NVv0.js} +1 -1
  141. package/src/assets/web-panel/assets/{vnode-Cc_fqAL4.js → vnode-D8O7tUXh.js} +1 -1
  142. package/src/assets/web-panel/assets/{zoom-BGkBp90M.js → zoom-CMQc1RZ4.js} +1 -1
  143. package/src/assets/web-panel/index.html +1 -1
  144. package/src/commands/agent.js +12 -0
  145. package/src/commands/cowork.js +8 -0
  146. package/src/commands/crosschain.js +32 -4
  147. package/src/commands/init.js +10 -10
  148. package/src/commands/loop.js +9 -3
  149. package/src/commands/memory.js +6 -4
  150. package/src/commands/orchestrate.js +5 -2
  151. package/src/commands/video.js +5 -1
  152. package/src/lib/agents.js +16 -5
  153. package/src/lib/cowork-workflow.js +357 -136
  154. package/src/lib/micro-compact.js +52 -0
  155. package/src/lib/output-styles.js +41 -7
  156. package/src/lib/permission-rules.cjs +39 -0
  157. package/src/lib/project-root.cjs +87 -0
  158. package/src/lib/settings-hooks.cjs +18 -6
  159. package/src/lib/settings-loader.cjs +12 -5
  160. package/src/lib/skill-loader.js +62 -43
  161. package/src/lib/slash-commands.js +18 -2
  162. package/src/repl/agent-repl.js +228 -20
  163. package/src/repl/chat-repl.js +4 -2
  164. package/src/repl/permission-tier.js +60 -0
  165. package/src/repl/stream-decision.js +16 -0
  166. package/src/repl/think-command.js +36 -0
  167. package/src/runtime/agent-core.js +67 -10
  168. package/src/runtime/file-ref-expander.js +209 -18
  169. package/src/runtime/headless-runner.js +3 -3
  170. package/src/runtime/headless-stream.js +16 -3
  171. package/src/runtime/mcp-config.js +78 -1
  172. package/src/assets/web-panel/assets/Tasks-BhnGtqaZ.js +0 -1
  173. package/src/assets/web-panel/assets/Terminal-BfgM0oyN.js +0 -3
  174. package/src/assets/web-panel/assets/devWarning-gNvbyy4j.js +0 -1
  175. package/src/assets/web-panel/assets/index-BNXpMnIY.js +0 -1
  176. package/src/assets/web-panel/assets/index-gfHxqT1Q.js +0 -1
@@ -13,6 +13,11 @@
13
13
  * Read(./src/**) → read_file/list_dir on a path under <cwd>/src
14
14
  * Edit(//etc/**) → edit_file on an absolute path under /etc
15
15
  * WebFetch(domain:example.com) → web_fetch of https://example.com/…
16
+ * Bash(command:rm*) → run_shell whose `command` arg matches `rm*` (CC
17
+ * 2.1.178 `Tool(param:value)` — matches any named
18
+ * input parameter, incl. arbitrary MCP tool args;
19
+ * `*` crosses `/` here, unlike path globs)
20
+ * mcp__db__query(table:users) → that MCP tool whose `table` arg is "users"
16
21
  * Bash → every run_shell call
17
22
  * * → every tool call (Claude-Code deny-all idiom)
18
23
  * Bash(*) → every run_shell call (lone-`*` pattern = match-all)
@@ -197,6 +202,30 @@ function matchUrl(pattern, url) {
197
202
  return globToRegExp(pattern).test(u);
198
203
  }
199
204
 
205
+ /** Match a single named-parameter value — `Tool(param:value)`. `prefix:*` →
206
+ * starts-with; otherwise glob/exact. Unlike path globs, `*` here crosses `/`
207
+ * because param values are arbitrary strings (commands often embed paths),
208
+ * not filesystem path segments. */
209
+ function matchParamValue(pattern, value) {
210
+ const v = value == null ? "" : String(value);
211
+ if (pattern === "*" || pattern === "**") return true;
212
+ if (pattern.endsWith(":*")) {
213
+ const prefix = pattern.slice(0, -2).trim();
214
+ return v === prefix || v.startsWith(prefix);
215
+ }
216
+ if (pattern.includes("*") || pattern.includes("?")) {
217
+ const re =
218
+ "^" +
219
+ pattern
220
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
221
+ .replace(/\*/g, ".*")
222
+ .replace(/\?/g, ".") +
223
+ "$";
224
+ return new RegExp(re).test(v);
225
+ }
226
+ return v === pattern;
227
+ }
228
+
200
229
  /**
201
230
  * Does `pattern` (the inside of `Tool(...)`, or null for a bare rule) match the
202
231
  * given tool call? `null` pattern always matches.
@@ -208,6 +237,15 @@ function matchPattern(pattern, actualTool, args, cwd) {
208
237
  // command/url/path containing `/` can't slip past a deny-all (globToRegExp's
209
238
  // `*` → `[^/]*` otherwise excludes slashes).
210
239
  if (pattern === "*" || pattern === "**") return true;
240
+ // Claude-Code 2.1.178: `Tool(param:value)` matches a NAMED input parameter,
241
+ // generalizing the positional command/path/url targets below. Treated as a
242
+ // param rule only when `param` is a bare identifier AND an actual key in the
243
+ // tool's args — so command idioms (`git push:*`) and `domain:host` (whose
244
+ // prefix is not an arg key) correctly fall through to the matching below.
245
+ const pv = pattern.match(/^([A-Za-z_]\w*):([\s\S]*)$/);
246
+ if (pv && Object.prototype.hasOwnProperty.call(args || {}, pv[1])) {
247
+ return matchParamValue(pv[2].trim(), (args || {})[pv[1]]);
248
+ }
211
249
  const target = extractTarget(actualTool, args, cwd);
212
250
  if (target.kind === "command") {
213
251
  return target.value ? matchCommand(pattern, target.value) : false;
@@ -328,6 +366,7 @@ module.exports = {
328
366
  extractTarget,
329
367
  matchCommand,
330
368
  matchUrl,
369
+ matchParamValue,
331
370
  matchPattern,
332
371
  evaluatePermissionRules,
333
372
  suggestAllowRule,
@@ -0,0 +1,87 @@
1
+ /**
2
+ * project-root — locate the nearest ancestor project root by walking up from a
3
+ * starting directory until a `.git` marker is found.
4
+ *
5
+ * Shared by the `.claude` / `.chainlesschain` discovery layers (settings.json
6
+ * permission rules, output-styles, slash-commands, sub-agents) so that running
7
+ * `cc` from a project SUBDIRECTORY still picks up the project-root config —
8
+ * Claude-Code 2.1.178 "closest `.claude` directory wins" parity. This mirrors
9
+ * the git-root resolution already used by skill-loader + project-instructions,
10
+ * which previously left these four layers as the only cwd-only stragglers (so a
11
+ * subdir run silently dropped the project's permission rules — a safety gap).
12
+ *
13
+ * A single `.cjs` module so the CJS `settings-loader` can `require` it and the
14
+ * ESM loaders can `import` it — one source of truth for the walk.
15
+ *
16
+ * DEFENSIVE BY DESIGN: callers thread their own (often mocked) `fs` / `path`
17
+ * through `deps`. If `.git` is not found via that `fs`, or the injected `path`
18
+ * lacks `dirname` / `resolve`, the walk bails to `null` and the caller keeps its
19
+ * existing cwd-only behavior unchanged — keeping the heavily-mocked unit tests
20
+ * green while real runs (real `node:path`) walk up correctly.
21
+ */
22
+
23
+ const fsDefault = require("node:fs");
24
+ const pathDefault = require("node:path");
25
+
26
+ const _deps = { fs: fsDefault, path: pathDefault };
27
+
28
+ /**
29
+ * Walk up from `cwd` to the nearest directory containing `.git`. Returns the
30
+ * resolved root path, or `null` when none is found (or deps are unusable).
31
+ * @param {string} cwd
32
+ * @param {{ fs?: object, path?: object }} [deps]
33
+ */
34
+ function findGitProjectRoot(cwd, deps = {}) {
35
+ const fs = deps.fs || _deps.fs;
36
+ const path = deps.path || _deps.path;
37
+ if (!fs || typeof fs.existsSync !== "function") return null;
38
+ if (!path || typeof path.dirname !== "function" || typeof path.join !== "function") {
39
+ return null;
40
+ }
41
+ let dir;
42
+ try {
43
+ dir = typeof path.resolve === "function" ? path.resolve(cwd || ".") : String(cwd || ".");
44
+ } catch {
45
+ return null;
46
+ }
47
+ // Bounded walk (64 levels) so a malformed `dirname` can never spin forever.
48
+ for (let i = 0; i < 64 && dir; i++) {
49
+ try {
50
+ if (fs.existsSync(path.join(dir, ".git"))) return dir;
51
+ } catch {
52
+ /* unreadable dir → keep walking up */
53
+ }
54
+ let parent;
55
+ try {
56
+ parent = path.dirname(dir);
57
+ } catch {
58
+ return null;
59
+ }
60
+ if (!parent || parent === dir) break;
61
+ dir = parent;
62
+ }
63
+ return null;
64
+ }
65
+
66
+ /**
67
+ * The extra project-root base directory to ADD to a cwd-based discovery list
68
+ * when running from a subdirectory. Returns the git root when it exists AND is a
69
+ * strict ancestor of `cwd`; returns `null` when `cwd` is itself the root (its
70
+ * own dirs already cover it) or no root is found.
71
+ * @param {string} cwd
72
+ * @param {{ fs?: object, path?: object }} [deps]
73
+ */
74
+ function projectRootBase(cwd, deps = {}) {
75
+ const root = findGitProjectRoot(cwd, deps);
76
+ if (!root) return null;
77
+ const path = deps.path || _deps.path;
78
+ let resolvedCwd = cwd;
79
+ try {
80
+ if (path && typeof path.resolve === "function") resolvedCwd = path.resolve(cwd || ".");
81
+ } catch {
82
+ /* fall through with raw cwd */
83
+ }
84
+ return root === resolvedCwd ? null : root;
85
+ }
86
+
87
+ module.exports = { findGitProjectRoot, projectRootBase, _deps };
@@ -28,6 +28,7 @@ const path = require("node:path");
28
28
  const os = require("node:os");
29
29
  const fsDefault = require("node:fs");
30
30
  const { SUGGEST_UMBRELLA } = require("./permission-rules.cjs");
31
+ const { projectRootBase } = require("./project-root.cjs");
31
32
 
32
33
  const _deps = { fs: fsDefault, homedir: () => os.homedir() };
33
34
 
@@ -44,14 +45,25 @@ const HOOK_EVENTS = Object.freeze([
44
45
  "Notification",
45
46
  ]);
46
47
 
47
- /** Same hierarchy as settings-loader (user < project < .local < --settings). */
48
+ /**
49
+ * Same hierarchy as settings-loader (user < project-root < cwd < --settings).
50
+ * When run from a SUBDIRECTORY, the project-root `.claude` hooks apply (below
51
+ * cwd's, above the user layer) — same walk-up as settings-loader's settingsPaths
52
+ * (shared `projectRootBase`, threaded through this loader's own `_deps.fs` to
53
+ * keep test injection intact). Hooks RUN SHELL COMMANDS, so a guard hook in the
54
+ * project-root settings must NOT silently vanish when `cc` is invoked from a
55
+ * subdirectory. `projectRootBase` returns null when cwd IS the root.
56
+ */
48
57
  function settingsFiles(cwd, explicitFile) {
49
58
  const home = _deps.homedir();
50
- const list = [
51
- path.join(home, ".claude", "settings.json"),
52
- path.join(cwd, ".claude", "settings.json"),
53
- path.join(cwd, ".claude", "settings.local.json"),
54
- ];
59
+ const list = [path.join(home, ".claude", "settings.json")];
60
+ const root = projectRootBase(cwd, { fs: _deps.fs, path });
61
+ if (root) {
62
+ list.push(path.join(root, ".claude", "settings.json"));
63
+ list.push(path.join(root, ".claude", "settings.local.json"));
64
+ }
65
+ list.push(path.join(cwd, ".claude", "settings.json"));
66
+ list.push(path.join(cwd, ".claude", "settings.local.json"));
55
67
  if (explicitFile) list.push(path.resolve(cwd, explicitFile));
56
68
  return list;
57
69
  }
@@ -27,6 +27,7 @@
27
27
  const fsDefault = require("node:fs");
28
28
  const path = require("node:path");
29
29
  const os = require("node:os");
30
+ const { projectRootBase } = require("./project-root.cjs");
30
31
 
31
32
  const _deps = { fs: fsDefault, homedir: () => os.homedir() };
32
33
 
@@ -72,11 +73,17 @@ function accrete(target, sources, arr, file, kind) {
72
73
  /** The ordered list of candidate settings files for a cwd. */
73
74
  function settingsPaths(cwd, explicitFile) {
74
75
  const home = _deps.homedir();
75
- const list = [
76
- path.join(home, ".claude", "settings.json"),
77
- path.join(cwd, ".claude", "settings.json"),
78
- path.join(cwd, ".claude", "settings.local.json"),
79
- ];
76
+ const list = [path.join(home, ".claude", "settings.json")];
77
+ // When run from a subdirectory, the project-root `.claude` sits BELOW cwd's
78
+ // own (closest wins) but ABOVE the user layer — so its rules apply yet a
79
+ // cwd-local settings file still overrides them. Null when cwd IS the root.
80
+ const root = projectRootBase(cwd, { fs: _deps.fs, path });
81
+ if (root) {
82
+ list.push(path.join(root, ".claude", "settings.json"));
83
+ list.push(path.join(root, ".claude", "settings.local.json"));
84
+ }
85
+ list.push(path.join(cwd, ".claude", "settings.json"));
86
+ list.push(path.join(cwd, ".claude", "settings.local.json"));
80
87
  if (explicitFile) list.push(path.resolve(cwd, explicitFile));
81
88
  return list;
82
89
  }
@@ -78,6 +78,10 @@ export const LAYER_NAMES = [
78
78
  "workspace",
79
79
  ];
80
80
 
81
+ /** Max directory depth to descend when scanning a skill layer for nested
82
+ * `<group>/<skill>/SKILL.md` layouts (infinite-recursion / deep-tree guard). */
83
+ export const MAX_SKILL_NEST_DEPTH = 5;
84
+
81
85
  /**
82
86
  * Simple YAML frontmatter parser (no dependencies)
83
87
  * Shared utility extracted from skill.js
@@ -348,54 +352,69 @@ export class CLISkillLoader {
348
352
  */
349
353
  _loadFromDir(dir, layer) {
350
354
  const skills = [];
351
- if (!dir || !fs.existsSync(dir)) return skills;
355
+ this._collectSkills(dir, layer, skills, 0);
356
+ return skills;
357
+ }
352
358
 
359
+ /**
360
+ * Recursively collect skills under `dir`. A directory containing a SKILL.md
361
+ * is a skill (a leaf — we do NOT descend into its asset/script subdirs); a
362
+ * directory WITHOUT one is treated as a grouping/category folder and is
363
+ * descended into, so nested layouts like `.claude/skills/<group>/<skill>/`
364
+ * load too (Claude-Code 2.1.178 nested-skills parity). Depth-capped.
365
+ */
366
+ _collectSkills(dir, layer, out, depth) {
367
+ if (!dir || !fs.existsSync(dir)) return;
368
+ let entries;
353
369
  try {
354
- const dirs = fs.readdirSync(dir, { withFileTypes: true });
355
- for (const entry of dirs) {
356
- if (!entry.isDirectory()) continue;
357
-
358
- const skillMd = path.join(dir, entry.name, "SKILL.md");
359
- if (!fs.existsSync(skillMd)) continue;
360
-
361
- try {
362
- const content = fs.readFileSync(skillMd, "utf-8");
363
- const { data, body } = parseSkillMd(content);
364
-
365
- skills.push({
366
- id: data.name || entry.name,
367
- displayName: data.displayName || entry.name,
368
- description: data.description || "",
369
- version: data.version || "1.0.0",
370
- category: data.category || "uncategorized",
371
- activation: data.activation || "manual",
372
- tags: data.tags || [],
373
- userInvocable: data.userInvocable !== false,
374
- handler: data.handler || null,
375
- capabilities: data.capabilities || [],
376
- os: data.os || [],
377
- // CLI pack extended fields
378
- executionMode: data.executionMode || null,
379
- cliDomain: data.cliDomain || null,
380
- cliVersionHash: data.cliVersionHash || null,
381
- dirName: entry.name,
382
- hasHandler: fs.existsSync(path.join(dir, entry.name, "handler.js")),
383
- body,
384
- // Skill-Embedded MCP: inline server declarations in a
385
- // ```mcp-servers fenced code block. Empty array if absent.
386
- mcpServers: parseSkillMcpServers(body),
387
- source: layer,
388
- skillDir: path.join(dir, entry.name),
389
- });
390
- } catch {
391
- // Skip malformed skill files
370
+ entries = fs.readdirSync(dir, { withFileTypes: true });
371
+ } catch {
372
+ return; // Directory unreadable
373
+ }
374
+ for (const entry of entries) {
375
+ if (!entry.isDirectory()) continue;
376
+ const skillDir = path.join(dir, entry.name);
377
+ const skillMd = path.join(skillDir, "SKILL.md");
378
+ if (!fs.existsSync(skillMd)) {
379
+ // Grouping folder descend (bounded) to find nested skills.
380
+ if (depth < MAX_SKILL_NEST_DEPTH) {
381
+ this._collectSkills(skillDir, layer, out, depth + 1);
392
382
  }
383
+ continue;
384
+ }
385
+ try {
386
+ const content = fs.readFileSync(skillMd, "utf-8");
387
+ const { data, body } = parseSkillMd(content);
388
+
389
+ out.push({
390
+ id: data.name || entry.name,
391
+ displayName: data.displayName || entry.name,
392
+ description: data.description || "",
393
+ version: data.version || "1.0.0",
394
+ category: data.category || "uncategorized",
395
+ activation: data.activation || "manual",
396
+ tags: data.tags || [],
397
+ userInvocable: data.userInvocable !== false,
398
+ handler: data.handler || null,
399
+ capabilities: data.capabilities || [],
400
+ os: data.os || [],
401
+ // CLI pack extended fields
402
+ executionMode: data.executionMode || null,
403
+ cliDomain: data.cliDomain || null,
404
+ cliVersionHash: data.cliVersionHash || null,
405
+ dirName: entry.name,
406
+ hasHandler: fs.existsSync(path.join(skillDir, "handler.js")),
407
+ body,
408
+ // Skill-Embedded MCP: inline server declarations in a
409
+ // ```mcp-servers fenced code block. Empty array if absent.
410
+ mcpServers: parseSkillMcpServers(body),
411
+ source: layer,
412
+ skillDir,
413
+ });
414
+ } catch {
415
+ // Skip malformed skill files
393
416
  }
394
- } catch {
395
- // Directory unreadable
396
417
  }
397
-
398
- return skills;
399
418
  }
400
419
 
401
420
  /**
@@ -23,6 +23,7 @@ import pathDefault from "node:path";
23
23
  import { homedir } from "node:os";
24
24
  import { execSync as execSyncDefault } from "node:child_process";
25
25
  import { expandFileRefs } from "../runtime/file-ref-expander.js";
26
+ import { projectRootBase } from "./project-root.cjs";
26
27
 
27
28
  const _deps = { fs: fsDefault, path: pathDefault, execSync: execSyncDefault };
28
29
 
@@ -62,16 +63,31 @@ export const BANG_TIMEOUT_MS = 10_000;
62
63
  * on a name clash). `opts.home` overrides the personal root for tests.
63
64
  */
64
65
  export function commandDirs(cwd = process.cwd(), opts = {}) {
66
+ const fs = opts.deps?.fs || _deps.fs;
65
67
  const path = opts.deps?.path || _deps.path;
66
68
  const home = opts.home || homedir();
67
69
  // Project-native `.chainlesschain/commands/` takes precedence, then the
68
70
  // Claude-Code-portable `.claude/commands/` (so existing definitions work
69
71
  // unchanged), then personal. Both project dirs share the "project" scope.
70
- return [
72
+ const dirs = [
71
73
  { dir: path.join(cwd, ".chainlesschain", "commands"), scope: "project" },
72
74
  { dir: path.join(cwd, ".claude", "commands"), scope: "project" },
73
- { dir: path.join(home, ".claude", "commands"), scope: "personal" },
74
75
  ];
76
+ // Subdirectory run: also scan the project-root `.claude` (cwd stays closest
77
+ // and wins on a name clash via discoverCommands' reverse + last-write-wins).
78
+ const root = projectRootBase(cwd, { fs, path });
79
+ if (root) {
80
+ dirs.push({
81
+ dir: path.join(root, ".chainlesschain", "commands"),
82
+ scope: "project",
83
+ });
84
+ dirs.push({
85
+ dir: path.join(root, ".claude", "commands"),
86
+ scope: "project",
87
+ });
88
+ }
89
+ dirs.push({ dir: path.join(home, ".claude", "commands"), scope: "personal" });
90
+ return dirs;
75
91
  }
76
92
 
77
93
  /** Recursively collect `*.md` files under `dir` as `{file, rel}` (rel uses /). */