xonecode 0.2.0 → 0.5.0

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 (406) hide show
  1. package/README.md +49 -7
  2. package/THIRD_PARTY_NOTICES.md +128 -0
  3. package/apps/web/dist/assets/KaTeX_AMS-Regular-BQhdFMY1.woff2 +0 -0
  4. package/apps/web/dist/assets/KaTeX_AMS-Regular-DMm9YOAa.woff +0 -0
  5. package/apps/web/dist/assets/KaTeX_AMS-Regular-DRggAlZN.ttf +0 -0
  6. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-ATXxdsX0.ttf +0 -0
  7. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-BEiXGLvX.woff +0 -0
  8. package/apps/web/dist/assets/KaTeX_Caligraphic-Bold-Dq_IR9rO.woff2 +0 -0
  9. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-CTRA-rTL.woff +0 -0
  10. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-Di6jR-x-.woff2 +0 -0
  11. package/apps/web/dist/assets/KaTeX_Caligraphic-Regular-wX97UBjC.ttf +0 -0
  12. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BdnERNNW.ttf +0 -0
  13. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-BsDP51OF.woff +0 -0
  14. package/apps/web/dist/assets/KaTeX_Fraktur-Bold-CL6g_b3V.woff2 +0 -0
  15. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CB_wures.ttf +0 -0
  16. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-CTYiF6lA.woff2 +0 -0
  17. package/apps/web/dist/assets/KaTeX_Fraktur-Regular-Dxdc4cR9.woff +0 -0
  18. package/apps/web/dist/assets/KaTeX_Main-Bold-Cx986IdX.woff2 +0 -0
  19. package/apps/web/dist/assets/KaTeX_Main-Bold-Jm3AIy58.woff +0 -0
  20. package/apps/web/dist/assets/KaTeX_Main-Bold-waoOVXN0.ttf +0 -0
  21. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DxDJ3AOS.woff2 +0 -0
  22. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-DzxPMmG6.ttf +0 -0
  23. package/apps/web/dist/assets/KaTeX_Main-BoldItalic-SpSLRI95.woff +0 -0
  24. package/apps/web/dist/assets/KaTeX_Main-Italic-3WenGoN9.ttf +0 -0
  25. package/apps/web/dist/assets/KaTeX_Main-Italic-BMLOBm91.woff +0 -0
  26. package/apps/web/dist/assets/KaTeX_Main-Italic-NWA7e6Wa.woff2 +0 -0
  27. package/apps/web/dist/assets/KaTeX_Main-Regular-B22Nviop.woff2 +0 -0
  28. package/apps/web/dist/assets/KaTeX_Main-Regular-Dr94JaBh.woff +0 -0
  29. package/apps/web/dist/assets/KaTeX_Main-Regular-ypZvNtVU.ttf +0 -0
  30. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-B3XSjfu4.ttf +0 -0
  31. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-CZnvNsCZ.woff2 +0 -0
  32. package/apps/web/dist/assets/KaTeX_Math-BoldItalic-iY-2wyZ7.woff +0 -0
  33. package/apps/web/dist/assets/KaTeX_Math-Italic-DA0__PXp.woff +0 -0
  34. package/apps/web/dist/assets/KaTeX_Math-Italic-flOr_0UB.ttf +0 -0
  35. package/apps/web/dist/assets/KaTeX_Math-Italic-t53AETM-.woff2 +0 -0
  36. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-CFMepnvq.ttf +0 -0
  37. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-D1sUS0GD.woff2 +0 -0
  38. package/apps/web/dist/assets/KaTeX_SansSerif-Bold-DbIhKOiC.woff +0 -0
  39. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-C3H0VqGB.woff2 +0 -0
  40. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-DN2j7dab.woff +0 -0
  41. package/apps/web/dist/assets/KaTeX_SansSerif-Italic-YYjJ1zSn.ttf +0 -0
  42. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-BNo7hRIc.ttf +0 -0
  43. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-CS6fqUqJ.woff +0 -0
  44. package/apps/web/dist/assets/KaTeX_SansSerif-Regular-DDBCnlJ7.woff2 +0 -0
  45. package/apps/web/dist/assets/KaTeX_Script-Regular-C5JkGWo-.ttf +0 -0
  46. package/apps/web/dist/assets/KaTeX_Script-Regular-D3wIWfF6.woff2 +0 -0
  47. package/apps/web/dist/assets/KaTeX_Script-Regular-D5yQViql.woff +0 -0
  48. package/apps/web/dist/assets/KaTeX_Size1-Regular-C195tn64.woff +0 -0
  49. package/apps/web/dist/assets/KaTeX_Size1-Regular-Dbsnue_I.ttf +0 -0
  50. package/apps/web/dist/assets/KaTeX_Size1-Regular-mCD8mA8B.woff2 +0 -0
  51. package/apps/web/dist/assets/KaTeX_Size2-Regular-B7gKUWhC.ttf +0 -0
  52. package/apps/web/dist/assets/KaTeX_Size2-Regular-Dy4dx90m.woff2 +0 -0
  53. package/apps/web/dist/assets/KaTeX_Size2-Regular-oD1tc_U0.woff +0 -0
  54. package/apps/web/dist/assets/KaTeX_Size3-Regular-CTq5MqoE.woff +0 -0
  55. package/apps/web/dist/assets/KaTeX_Size3-Regular-DgpXs0kz.ttf +0 -0
  56. package/apps/web/dist/assets/KaTeX_Size4-Regular-BF-4gkZK.woff +0 -0
  57. package/apps/web/dist/assets/KaTeX_Size4-Regular-DWFBv043.ttf +0 -0
  58. package/apps/web/dist/assets/KaTeX_Size4-Regular-Dl5lxZxV.woff2 +0 -0
  59. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-C0xS9mPB.woff +0 -0
  60. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-CO6r4hn1.woff2 +0 -0
  61. package/apps/web/dist/assets/KaTeX_Typewriter-Regular-D3Ib7_Hf.ttf +0 -0
  62. package/apps/web/dist/assets/c-BIGW1oBm.js +1 -0
  63. package/apps/web/dist/assets/cpp-DddhVWhK.js +1 -0
  64. package/apps/web/dist/assets/csharp-DSvCPggb.js +1 -0
  65. package/apps/web/dist/assets/css-CLj8gQPS.js +1 -0
  66. package/apps/web/dist/assets/go-C27-OAKa.js +1 -0
  67. package/apps/web/dist/assets/html-CfGypltT.js +1 -0
  68. package/apps/web/dist/assets/index-BB3SamhR.css +1 -0
  69. package/apps/web/dist/assets/index-CFYAoHdw.js +477 -0
  70. package/apps/web/dist/assets/ini-BEwlwnbL.js +1 -0
  71. package/apps/web/dist/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  72. package/apps/web/dist/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  73. package/apps/web/dist/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  74. package/apps/web/dist/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  75. package/apps/web/dist/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  76. package/apps/web/dist/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  77. package/apps/web/dist/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  78. package/apps/web/dist/assets/java-CylS5w8V.js +1 -0
  79. package/apps/web/dist/assets/jetbrains-mono-cyrillic-wght-normal-D73BlboJ.woff2 +0 -0
  80. package/apps/web/dist/assets/jetbrains-mono-greek-wght-normal-Bw9x6K1M.woff2 +0 -0
  81. package/apps/web/dist/assets/jetbrains-mono-latin-ext-wght-normal-DBQx-q_a.woff2 +0 -0
  82. package/apps/web/dist/assets/jetbrains-mono-latin-wght-normal-B9CIFXIH.woff2 +0 -0
  83. package/apps/web/dist/assets/jetbrains-mono-vietnamese-wght-normal-Bt-aOZkq.woff2 +0 -0
  84. package/apps/web/dist/assets/kotlin-BdnUsdx6.js +1 -0
  85. package/apps/web/dist/assets/less-B1dDrJ26.js +1 -0
  86. package/apps/web/dist/assets/lua-BaeVxFsk.js +1 -0
  87. package/apps/web/dist/assets/markdown-Cvjx9yec.js +1 -0
  88. package/apps/web/dist/assets/mdx-Cmh6b_Ma.js +1 -0
  89. package/apps/web/dist/assets/php-D_2--9WK.js +1 -0
  90. package/apps/web/dist/assets/python-B6aJPvgy.js +1 -0
  91. package/apps/web/dist/assets/ruby-DSHSDbk1.js +1 -0
  92. package/apps/web/dist/assets/rust-B1yitclQ.js +1 -0
  93. package/apps/web/dist/assets/scss-D5BDwBP9.js +1 -0
  94. package/apps/web/dist/assets/sql-CRqJ_cUM.js +1 -0
  95. package/apps/web/dist/assets/swift-C2oV4EkX.js +1 -0
  96. package/apps/web/dist/assets/toml-vGWfd6FD.js +1 -0
  97. package/apps/web/dist/assets/xml-sdJ4AIDG.js +1 -0
  98. package/apps/web/dist/assets/yaml-Buea-lGh.js +1 -0
  99. package/apps/web/dist/iconos/xonecode.png +0 -0
  100. package/apps/web/dist/index.html +20 -0
  101. package/dist/agent/agentesEnDisco.js +539 -0
  102. package/dist/agent/agentesEnDisco.js.map +1 -0
  103. package/dist/agent/arbolDeProyecto.js +245 -0
  104. package/dist/agent/arbolDeProyecto.js.map +1 -0
  105. package/dist/agent/artefactosEnDisco.js +139 -0
  106. package/dist/agent/artefactosEnDisco.js.map +1 -0
  107. package/dist/agent/aumentador.js +175 -0
  108. package/dist/agent/aumentador.js.map +1 -0
  109. package/dist/agent/authEnDisco.js +76 -1
  110. package/dist/agent/authEnDisco.js.map +1 -1
  111. package/dist/agent/busquedaRegex.js +92 -0
  112. package/dist/agent/busquedaRegex.js.map +1 -0
  113. package/dist/agent/catalogoModelos.js +345 -0
  114. package/dist/agent/catalogoModelos.js.map +1 -0
  115. package/dist/agent/checkpointer.js +127 -0
  116. package/dist/agent/checkpointer.js.map +1 -0
  117. package/dist/agent/cloudstudioClient.js +194 -0
  118. package/dist/agent/cloudstudioClient.js.map +1 -0
  119. package/dist/agent/cloudstudioMcp.js +722 -0
  120. package/dist/agent/cloudstudioMcp.js.map +1 -0
  121. package/dist/agent/configEnDisco.js +309 -11
  122. package/dist/agent/configEnDisco.js.map +1 -1
  123. package/dist/agent/consumoExterno.js +99 -0
  124. package/dist/agent/consumoExterno.js.map +1 -0
  125. package/dist/agent/descarga.js +149 -0
  126. package/dist/agent/descarga.js.map +1 -0
  127. package/dist/agent/diagnosticoDeTools.js +51 -0
  128. package/dist/agent/diagnosticoDeTools.js.map +1 -0
  129. package/dist/agent/dispositivosEnMaquina.js +596 -0
  130. package/dist/agent/dispositivosEnMaquina.js.map +1 -0
  131. package/dist/agent/escrituraDeCodex.js +134 -0
  132. package/dist/agent/escrituraDeCodex.js.map +1 -0
  133. package/dist/agent/escrituraDeOpencode.js +80 -0
  134. package/dist/agent/escrituraDeOpencode.js.map +1 -0
  135. package/dist/agent/escrituraExterna.js +742 -0
  136. package/dist/agent/escrituraExterna.js.map +1 -0
  137. package/dist/agent/gemini.js +81 -0
  138. package/dist/agent/gemini.js.map +1 -0
  139. package/dist/agent/git.js +83 -0
  140. package/dist/agent/git.js.map +1 -0
  141. package/dist/agent/gitSync.js +441 -0
  142. package/dist/agent/gitSync.js.map +1 -0
  143. package/dist/agent/hotswap.js +448 -0
  144. package/dist/agent/hotswap.js.map +1 -0
  145. package/dist/agent/instalacionEnMaquina.js +252 -0
  146. package/dist/agent/instalacionEnMaquina.js.map +1 -0
  147. package/dist/agent/instantanea.js +7 -10
  148. package/dist/agent/instantanea.js.map +1 -1
  149. package/dist/agent/interrupts.js +11 -2
  150. package/dist/agent/interrupts.js.map +1 -1
  151. package/dist/agent/juezDeTarea.js +345 -0
  152. package/dist/agent/juezDeTarea.js.map +1 -0
  153. package/dist/agent/lanzamientoEnMaquina.js +377 -0
  154. package/dist/agent/lanzamientoEnMaquina.js.map +1 -0
  155. package/dist/agent/manifiesto.js +59 -0
  156. package/dist/agent/manifiesto.js.map +1 -0
  157. package/dist/agent/memoriaDeProyecto.js +60 -0
  158. package/dist/agent/memoriaDeProyecto.js.map +1 -0
  159. package/dist/agent/modelos.js +105 -17
  160. package/dist/agent/modelos.js.map +1 -1
  161. package/dist/agent/modelosDeMotor.js +204 -0
  162. package/dist/agent/modelosDeMotor.js.map +1 -0
  163. package/dist/agent/paqueteDelProyecto.js +181 -0
  164. package/dist/agent/paqueteDelProyecto.js.map +1 -0
  165. package/dist/agent/perfiles.js +92 -31
  166. package/dist/agent/perfiles.js.map +1 -1
  167. package/dist/agent/persona.js +40 -0
  168. package/dist/agent/persona.js.map +1 -0
  169. package/dist/agent/procesosEnMaquina.js +204 -0
  170. package/dist/agent/procesosEnMaquina.js.map +1 -0
  171. package/dist/agent/proyecto.js +311 -13
  172. package/dist/agent/proyecto.js.map +1 -1
  173. package/dist/agent/puente.js +63 -6
  174. package/dist/agent/puente.js.map +1 -1
  175. package/dist/agent/resumenDeContexto.js +30 -0
  176. package/dist/agent/resumenDeContexto.js.map +1 -0
  177. package/dist/agent/resumenDeTool.js +64 -18
  178. package/dist/agent/resumenDeTool.js.map +1 -1
  179. package/dist/agent/sesionGit.js +481 -0
  180. package/dist/agent/sesionGit.js.map +1 -0
  181. package/dist/agent/settingsEnDisco.js +189 -0
  182. package/dist/agent/settingsEnDisco.js.map +1 -0
  183. package/dist/agent/subagenteCodex.js +368 -0
  184. package/dist/agent/subagenteCodex.js.map +1 -0
  185. package/dist/agent/subagenteExterno.js +423 -0
  186. package/dist/agent/subagenteExterno.js.map +1 -0
  187. package/dist/agent/subagenteOpencode.js +389 -0
  188. package/dist/agent/subagenteOpencode.js.map +1 -0
  189. package/dist/agent/subida.js +202 -0
  190. package/dist/agent/subida.js.map +1 -0
  191. package/dist/agent/tareasEnDisco.js +466 -0
  192. package/dist/agent/tareasEnDisco.js.map +1 -0
  193. package/dist/agent/turnoReal.js +663 -50
  194. package/dist/agent/turnoReal.js.map +1 -1
  195. package/dist/agent/versionEnDisco.js +48 -0
  196. package/dist/agent/versionEnDisco.js.map +1 -0
  197. package/dist/agent/xoneAgent.js +290 -49
  198. package/dist/agent/xoneAgent.js.map +1 -1
  199. package/dist/agent/zip.js +52 -0
  200. package/dist/agent/zip.js.map +1 -0
  201. package/dist/cli/acuseDeModelo.js +30 -0
  202. package/dist/cli/acuseDeModelo.js.map +1 -0
  203. package/dist/cli/aprobar.js +55 -2
  204. package/dist/cli/aprobar.js.map +1 -1
  205. package/dist/cli/config.js +9 -13
  206. package/dist/cli/config.js.map +1 -1
  207. package/dist/cli/consola.js +750 -19
  208. package/dist/cli/consola.js.map +1 -1
  209. package/dist/cli/main.js +740 -81
  210. package/dist/cli/main.js.map +1 -1
  211. package/dist/cli/markdown.js +188 -2
  212. package/dist/cli/markdown.js.map +1 -1
  213. package/dist/cli/run.js +32 -4
  214. package/dist/cli/run.js.map +1 -1
  215. package/dist/cli/stdio.js +46 -19
  216. package/dist/cli/stdio.js.map +1 -1
  217. package/dist/cli/tema.js +96 -8
  218. package/dist/cli/tema.js.map +1 -1
  219. package/dist/cli/tokens.js +29 -0
  220. package/dist/cli/tokens.js.map +1 -0
  221. package/dist/cli/tui/app.js +171 -0
  222. package/dist/cli/tui/app.js.map +1 -0
  223. package/dist/cli/tui/aprobarTui.js +80 -0
  224. package/dist/cli/tui/aprobarTui.js.map +1 -0
  225. package/dist/cli/tui/barra.js +37 -0
  226. package/dist/cli/tui/barra.js.map +1 -0
  227. package/dist/cli/tui/correrTui.js +478 -0
  228. package/dist/cli/tui/correrTui.js.map +1 -0
  229. package/dist/cli/tui/entrada.js +166 -0
  230. package/dist/cli/tui/entrada.js.map +1 -0
  231. package/dist/cli/tui/filas.js +24 -0
  232. package/dist/cli/tui/filas.js.map +1 -0
  233. package/dist/cli/tui/logo.js +49 -0
  234. package/dist/cli/tui/logo.js.map +1 -0
  235. package/dist/cli/tui/pielTui.js +14 -0
  236. package/dist/cli/tui/pielTui.js.map +1 -0
  237. package/dist/cli/tui/raton.js +126 -0
  238. package/dist/cli/tui/raton.js.map +1 -0
  239. package/dist/cli/tui/sidebar.js +86 -0
  240. package/dist/cli/tui/sidebar.js.map +1 -0
  241. package/dist/cli/tui/store.js +143 -0
  242. package/dist/cli/tui/store.js.map +1 -0
  243. package/dist/cli/tui/tarjeta.js +19 -0
  244. package/dist/cli/tui/tarjeta.js.map +1 -0
  245. package/dist/cli/tui/temaInk.js +114 -0
  246. package/dist/cli/tui/temaInk.js.map +1 -0
  247. package/dist/cli/tui/transcript.js +254 -0
  248. package/dist/cli/tui/transcript.js.map +1 -0
  249. package/dist/cli/wizardInicial.js +240 -0
  250. package/dist/cli/wizardInicial.js.map +1 -0
  251. package/dist/core/actos.js +113 -0
  252. package/dist/core/actos.js.map +1 -0
  253. package/dist/core/adjuntos.js +97 -0
  254. package/dist/core/adjuntos.js.map +1 -0
  255. package/dist/core/agentes.js +227 -0
  256. package/dist/core/agentes.js.map +1 -0
  257. package/dist/core/artefactos.js +149 -0
  258. package/dist/core/artefactos.js.map +1 -0
  259. package/dist/core/cloudstudio.js +7 -0
  260. package/dist/core/cloudstudio.js.map +1 -0
  261. package/dist/core/config.js +212 -5
  262. package/dist/core/config.js.map +1 -1
  263. package/dist/core/contextos.js +53 -1
  264. package/dist/core/contextos.js.map +1 -1
  265. package/dist/core/descargas.js +106 -0
  266. package/dist/core/descargas.js.map +1 -0
  267. package/dist/core/descriptoresDeApp.js +279 -0
  268. package/dist/core/descriptoresDeApp.js.map +1 -0
  269. package/dist/core/dispositivos.js +466 -0
  270. package/dist/core/dispositivos.js.map +1 -0
  271. package/dist/core/entrega.js +141 -0
  272. package/dist/core/entrega.js.map +1 -0
  273. package/dist/core/entrelazar.js +90 -0
  274. package/dist/core/entrelazar.js.map +1 -0
  275. package/dist/core/esqueleto.js +10 -0
  276. package/dist/core/esqueleto.js.map +1 -1
  277. package/dist/core/modelos.js +235 -3
  278. package/dist/core/modelos.js.map +1 -1
  279. package/dist/core/notify.js +27 -9
  280. package/dist/core/notify.js.map +1 -1
  281. package/dist/core/planDeSubida.js +127 -0
  282. package/dist/core/planDeSubida.js.map +1 -0
  283. package/dist/core/ports.js +189 -1
  284. package/dist/core/ports.js.map +1 -1
  285. package/dist/core/puedeLanzarse.js +239 -0
  286. package/dist/core/puedeLanzarse.js.map +1 -0
  287. package/dist/core/rutaVirtual.js +42 -0
  288. package/dist/core/rutaVirtual.js.map +1 -0
  289. package/dist/core/settings.js +246 -0
  290. package/dist/core/settings.js.map +1 -0
  291. package/dist/core/tareas.js +230 -0
  292. package/dist/core/tareas.js.map +1 -0
  293. package/dist/core/textos.js +36 -0
  294. package/dist/core/textos.js.map +1 -0
  295. package/dist/core/turno.js +70 -10
  296. package/dist/core/turno.js.map +1 -1
  297. package/dist/core/version.js +31 -0
  298. package/dist/core/version.js.map +1 -0
  299. package/dist/vendor/tokenTracking.js +22 -1
  300. package/dist/vendor/tokenTracking.js.map +1 -1
  301. package/dist/web/servidor/arranque.js +3964 -0
  302. package/dist/web/servidor/arranque.js.map +1 -0
  303. package/dist/web/servidor/consolaDeTarea.js +161 -0
  304. package/dist/web/servidor/consolaDeTarea.js.map +1 -0
  305. package/dist/web/servidor/consolaWeb.js +389 -0
  306. package/dist/web/servidor/consolaWeb.js.map +1 -0
  307. package/dist/web/servidor/corredorDeTareas.js +1119 -0
  308. package/dist/web/servidor/corredorDeTareas.js.map +1 -0
  309. package/dist/web/servidor/pielWeb.js +269 -0
  310. package/dist/web/servidor/pielWeb.js.map +1 -0
  311. package/dist/web/servidor/servidor.js +257 -0
  312. package/dist/web/servidor/servidor.js.map +1 -0
  313. package/dist/web/servidor/sesiones.js +440 -0
  314. package/dist/web/servidor/sesiones.js.map +1 -0
  315. package/dist/web/servidor/transporte.js +169 -0
  316. package/dist/web/servidor/transporte.js.map +1 -0
  317. package/dist/web/servidor/vestibulo.js +1276 -0
  318. package/dist/web/servidor/vestibulo.js.map +1 -0
  319. package/package.json +30 -8
  320. package/skills/archify/LICENSE +22 -0
  321. package/skills/archify/SKILL.md +109 -0
  322. package/skills/archify/THIRD_PARTY_NOTICES.md +56 -0
  323. package/skills/archify/assets/template.html +14787 -0
  324. package/skills/archify/bin/archify.mjs +1990 -0
  325. package/skills/archify/bin/open-artifact.mjs +86 -0
  326. package/skills/archify/bin/preview.mjs +648 -0
  327. package/skills/archify/bin/visual-check.mjs +829 -0
  328. package/skills/archify/brand-marks/README.md +31 -0
  329. package/skills/archify/brand-marks/catalog.json +131 -0
  330. package/skills/archify/delta/architecture-delta.mjs +1214 -0
  331. package/skills/archify/examples/agent-run.lifecycle.json +71 -0
  332. package/skills/archify/examples/agent-tool-call.workflow.json +94 -0
  333. package/skills/archify/examples/async-job-roundtrip.sequence.json +61 -0
  334. package/skills/archify/examples/brand-aware-delivery.architecture.json +47 -0
  335. package/skills/archify/examples/cache-miss-request.sequence.json +81 -0
  336. package/skills/archify/examples/checkout-platform.base.architecture.json +31 -0
  337. package/skills/archify/examples/checkout-platform.head.architecture.json +31 -0
  338. package/skills/archify/examples/dataflow-product-analytics.html +14898 -0
  339. package/skills/archify/examples/deployment-release.lifecycle.json +49 -0
  340. package/skills/archify/examples/event-stream.dataflow.json +57 -0
  341. package/skills/archify/examples/incident-response.workflow.json +64 -0
  342. package/skills/archify/examples/lifecycle-agent-run.html +14847 -0
  343. package/skills/archify/examples/product-analytics.dataflow.json +76 -0
  344. package/skills/archify/examples/production-deployment.architecture.json +71 -0
  345. package/skills/archify/examples/release-delivery.workflow.json +62 -0
  346. package/skills/archify/examples/sequence-cache-miss-request.html +14913 -0
  347. package/skills/archify/examples/web-app-rendered.html +14862 -0
  348. package/skills/archify/examples/web-app.architecture.json +46 -0
  349. package/skills/archify/examples/workflow-agent-tool-call-rendered.html +14904 -0
  350. package/skills/archify/migrations/workflow-v2.mjs +279 -0
  351. package/skills/archify/package.json +17 -0
  352. package/skills/archify/recipes/scenarios.mjs +391 -0
  353. package/skills/archify/references/authoring-contract.md +198 -0
  354. package/skills/archify/references/brand-marks.md +69 -0
  355. package/skills/archify/references/delivery-contract.md +116 -0
  356. package/skills/archify/references/viewer-runtime.md +45 -0
  357. package/skills/archify/renderers/architecture/grid.mjs +62 -0
  358. package/skills/archify/renderers/architecture/render-architecture.mjs +1078 -0
  359. package/skills/archify/renderers/dataflow/README.md +104 -0
  360. package/skills/archify/renderers/dataflow/render-dataflow.mjs +483 -0
  361. package/skills/archify/renderers/lifecycle/README.md +115 -0
  362. package/skills/archify/renderers/lifecycle/render-lifecycle.mjs +561 -0
  363. package/skills/archify/renderers/sequence/README.md +114 -0
  364. package/skills/archify/renderers/sequence/render-sequence.mjs +453 -0
  365. package/skills/archify/renderers/shared/brand-marks.mjs +563 -0
  366. package/skills/archify/renderers/shared/cli.mjs +218 -0
  367. package/skills/archify/renderers/shared/desktop-readability.mjs +26 -0
  368. package/skills/archify/renderers/shared/diagnostics.mjs +127 -0
  369. package/skills/archify/renderers/shared/engineering-profiles.mjs +157 -0
  370. package/skills/archify/renderers/shared/generated-brand-marks.mjs +2003 -0
  371. package/skills/archify/renderers/shared/generated-validators.mjs +13 -0
  372. package/skills/archify/renderers/shared/geometry.mjs +1423 -0
  373. package/skills/archify/renderers/shared/i18n.mjs +594 -0
  374. package/skills/archify/renderers/shared/layout-report.mjs +40 -0
  375. package/skills/archify/renderers/shared/legend.mjs +217 -0
  376. package/skills/archify/renderers/shared/output-path.mjs +321 -0
  377. package/skills/archify/renderers/shared/repository-evidence.mjs +235 -0
  378. package/skills/archify/renderers/shared/text-fit.mjs +49 -0
  379. package/skills/archify/renderers/shared/utils.mjs +232 -0
  380. package/skills/archify/renderers/shared/validator.mjs +86 -0
  381. package/skills/archify/renderers/workflow/README.md +223 -0
  382. package/skills/archify/renderers/workflow/render-workflow.mjs +35 -0
  383. package/skills/archify/renderers/workflow/workflow-compiler.mjs +4400 -0
  384. package/skills/archify/renderers/workflow/workflow-migration-geometry.mjs +144 -0
  385. package/skills/archify/schemas/README.md +211 -0
  386. package/skills/archify/schemas/architecture.schema.json +176 -0
  387. package/skills/archify/schemas/common.schema.json +115 -0
  388. package/skills/archify/schemas/dataflow.schema.json +243 -0
  389. package/skills/archify/schemas/lifecycle.schema.json +266 -0
  390. package/skills/archify/schemas/sequence.schema.json +223 -0
  391. package/skills/archify/schemas/workflow.schema.json +428 -0
  392. package/skills/archify/scripts/check-render-output.mjs +835 -0
  393. package/skills/archify/scripts/check-update.mjs +1667 -0
  394. package/skills/archify/scripts/render-examples.mjs +26 -0
  395. package/skills/archify/scripts/update-contract.mjs +182 -0
  396. package/skills/archify/skill-release.json +10 -0
  397. package/skills/artifacts-builder/SKILL.md +120 -0
  398. package/skills/artifacts-builder/reference/diagramas.md +123 -0
  399. package/skills/artifacts-builder/reference/estilo.md +123 -0
  400. package/skills/artifacts-builder/reference/exportar-pdf.md +56 -0
  401. package/skills/artifacts-builder/reference/graficas.md +22 -0
  402. package/skills/artifacts-builder/scripts/bundle_artifact.py +109 -0
  403. package/skills/artifacts-builder/scripts/validate_artifact.py +345 -0
  404. package/skills/xone-hotswap/SKILL.md +185 -0
  405. package/skills/xone-hotswap/references/comandos.md +706 -0
  406. package/skills/xone-hotswap/references/conexion-y-despliegue.md +226 -0
@@ -0,0 +1,742 @@
1
+ /**
2
+ * Qué puede hacer un agente EXTERNO (Claude Code) dentro de la carpeta del proyecto.
3
+ *
4
+ * Este fichero existe porque conceder escritura a un agente externo no es levantar una
5
+ * bandera: sus `Write`/`Edit` van **directos al disco con una ruta ABSOLUTA**, así que se
6
+ * saltan por completo las cuatro capas que protegen al proyecto por el camino de LangGraph
7
+ * —`permisosDe`, el `virtualMode` del backend, las vistas aplanadas y las guardas de
8
+ * artefactos y descargas—. Sin volver a aplicarlas aquí, «el agente externo ya puede
9
+ * escribir» significaría también «ya puede escribir `.env`, `.git` y cualquier sitio fuera
10
+ * del proyecto», y eso no es una capacidad nueva: es el agujero más grande que este repo
11
+ * puede tener.
12
+ *
13
+ * Tres piezas, y las tres son puras o con su dependencia inyectada, para que `npm test` las
14
+ * pruebe sin lanzar un Claude Code:
15
+ * 1. **Tres listas** de tools (`claseDeToolExterna`): lectura, escritura y denegadas. Tres y
16
+ * no dos, porque conceder escritura NO puede conceder `Bash` ni `WebFetch`.
17
+ * 2. **El veredicto de una ruta** (`veredictoDeRuta`), que vuelve a aplicar las guardas del
18
+ * proyecto sobre la ruta absoluta del hijo — y las aplica DOS veces, sobre el texto y
19
+ * sobre el `realpath`, que es la misma regla que ya sostiene `arbolDeProyecto.ts`.
20
+ * 3. **El adaptador de política** (`politicaDeAprobacionExterna`), que convierte el
21
+ * `pedirAprobacion` que ya existe en la política de escritura del hijo.
22
+ */
23
+ import { realpathSync, readFileSync } from "node:fs";
24
+ import { dirname, basename, resolve, relative, sep } from "node:path";
25
+ import { artefactoFueraDeSitio } from "../core/artefactos.js";
26
+ import { descargaFueraDeSitio } from "../core/descargas.js";
27
+ import { puedeLeerRuta } from "./perfiles.js";
28
+ import { esVistaAplanada } from "./proyecto.js";
29
+ import { cambioDe } from "./interrupts.js";
30
+ /**
31
+ * Las tools del hijo que solo LEEN.
32
+ *
33
+ * Los nombres salen del SDK instalado y no de la memoria: se comprobaron contra
34
+ * `sdk-tools.d.ts` de `@anthropic-ai/claude-agent-sdk`, cuya unión `ToolInputSchemas`
35
+ * enumera hoy **cuarenta y cuatro** tools —`REPL`, `Workflow`, `Cron*`, `Artifact`,
36
+ * `RemoteTrigger`, `Monitor`, `Mcp`, `EnterWorktree`…— y crece con cada versión del
37
+ * producto. Esa cifra es el argumento entero de este fichero: con una lista negra, la tool
38
+ * que Claude Code añada mañana entraría permitida.
39
+ *
40
+ * `TodoWrite` está dentro pese al nombre: es la lista de tareas del propio hijo, vive en su
41
+ * memoria y no toca el disco del proyecto.
42
+ */
43
+ export const TOOLS_EXTERNAS_DE_LECTURA = new Set([
44
+ "Read",
45
+ "Glob",
46
+ "Grep",
47
+ "NotebookRead",
48
+ "TodoWrite",
49
+ /**
50
+ * **`ToolSearch` está aquí porque `Glob` y `Grep` vienen DIFERIDAS, y sin ella el agente
51
+ * trabaja a ciegas.** Medido espiando el hook en una ejecución real: el hijo intentó
52
+ * `Bash ls`, luego `Bash find`, y después `ToolSearch {"query":"select:Glob,Grep"}` — o
53
+ * sea que esas dos no las tiene cargadas y hay que pedírselas. Denegándola, el mensaje de
54
+ * denegación le ofrecía literalmente «puedes buscar con Glob, Grep» unas tools que no
55
+ * podía alcanzar, y acababa probando nombres de fichero uno a uno: SESENTA Y CINCO
56
+ * lecturas de ficheros que no existían (`README.md`, `CLAUDE.md`, `app.ini`…) para
57
+ * concluir que «el proyecto está prácticamente vacío para mí, porque no puedo listarlo».
58
+ *
59
+ * Y no abre nada: devuelve ESQUEMAS de tools, no ejecuta ninguna. Lo que consiga con ella
60
+ * vuelve a pasar por este mismo hook, que es el punto por el que pasan todas — así que la
61
+ * lista blanca sigue siendo la barrera, y ahora además es cierta.
62
+ */
63
+ "ToolSearch",
64
+ ]);
65
+ /**
66
+ * Las que escriben en el proyecto, y por tanto pasan por la política.
67
+ *
68
+ * **Solo `Write` y `Edit`**, y las dos por una razón que se puede comprobar: son las únicas
69
+ * cuyos argumentos encajan en `cambioDe()`, o sea las únicas de las que se puede COMPONER
70
+ * el diff que una persona tiene que ver antes de decidir. Medido en `sdk-tools.d.ts`:
71
+ * `FileWriteInput` es `{file_path, content}` y `FileEditInput` es
72
+ * `{file_path, old_string, new_string, replace_all?}`, que son exactamente las claves que
73
+ * `write_file` y `edit_file` ya traen por el camino de LangGraph.
74
+ *
75
+ * Lo que se queda fuera y por qué, dicho aquí para que no parezca un olvido:
76
+ * - **`NotebookEdit`** no lleva `file_path` sino `notebook_path`, y un proyecto XOne no
77
+ * tiene cuadernos de Jupyter. Conceder una escritura de la que no se sabe componer el
78
+ * diff sería aprobar a ciegas.
79
+ * - **`MultiEdit`** ya no existe en este SDK (cero apariciones en su unión de tools), y el
80
+ * día que vuelva tampoco entra de rebote: es una LISTA de ediciones, así que una sola
81
+ * aprobación cubriría N cambios — o se expande a N decisiones o se deniega. Hoy, denegada.
82
+ */
83
+ export const TOOLS_EXTERNAS_DE_ESCRITURA = new Set(["Write", "Edit"]);
84
+ /**
85
+ * Las que se deniegan SIEMPRE, con escritura concedida o sin ella.
86
+ *
87
+ * Aquí está la mitad del arreglo. Antes la decisión era
88
+ * `permitirEscritura || esDeLectura(nombre)`, o sea que el día que se concediera la
89
+ * escritura se concedía **todo**: `Bash` sola basta para escribir el proyecto entero
90
+ * —saltándose de paso la política, el diff y las guardas de ruta de este fichero—, y
91
+ * `WebFetch`/`WebSearch` no escriben pero sacan el contenido del proyecto fuera de la
92
+ * máquina, que no lo decide un especialista.
93
+ *
94
+ * La lista es ILUSTRATIVA y no la barrera: la barrera es que lo que no esté en las dos
95
+ * listas de arriba se deniega igual (`claseDeToolExterna` contesta «desconocida»). Existe
96
+ * para que el motivo pueda decir POR QUÉ se deniega esta en concreto, que es lo que evita
97
+ * que el hijo insista.
98
+ */
99
+ export const TOOLS_EXTERNAS_DENEGADAS = new Set([
100
+ "Bash",
101
+ "BashOutput",
102
+ "KillShell",
103
+ "WebFetch",
104
+ "WebSearch",
105
+ "NotebookEdit",
106
+ "MultiEdit",
107
+ ]);
108
+ /**
109
+ * En qué lista cae una tool. **Deniega por omisión**: lo que no se reconoce es
110
+ * «desconocida», que no es un tercer estado benigno — se trata igual que «denegada».
111
+ */
112
+ export function claseDeToolExterna(nombre) {
113
+ if (TOOLS_EXTERNAS_DE_LECTURA.has(nombre))
114
+ return "lectura";
115
+ if (TOOLS_EXTERNAS_DE_ESCRITURA.has(nombre))
116
+ return "escritura";
117
+ if (TOOLS_EXTERNAS_DENEGADAS.has(nombre))
118
+ return "denegada";
119
+ return "desconocida";
120
+ }
121
+ /**
122
+ * Las carpetas de la raíz que el hijo no puede tocar ni con escritura concedida, y que
123
+ * `puedeLeerRuta` no cubre porque por el camino de LangGraph son raíces MONTADAS y no
124
+ * carpetas del proyecto.
125
+ *
126
+ * Aquí sí son carpetas del proyecto —el hijo ve el disco, no un `CompositeBackend`—, así
127
+ * que el daño es otro y el mismo: `permisosDe` deniega `write` sobre `/skills/**` y
128
+ * `/adjuntos/**` incondicionalmente, y la interfaz presenta lo que hay ahí como
129
+ * instrucciones del harness y como los documentos que anexó una persona. Que aquí apunten
130
+ * a otro sitio no cambia lo que alguien leería al verlos cambiados.
131
+ *
132
+ * `escrituraExterna.test.ts` ATA esta lista con las filas de `write` de `DENEGADO_SIEMPRE`
133
+ * en vez de confiarla: son dos formatos de la misma regla (globs para deepagents, prefijos
134
+ * para aquí) y dos copias de una regla son dos copias que divergen.
135
+ */
136
+ const CARPETAS_DENEGADAS = ["/skills", "/adjuntos"];
137
+ /**
138
+ * La ruta virtual de una ruta absoluta del hijo, o `undefined` si no se puede afirmar que
139
+ * cae dentro del proyecto.
140
+ *
141
+ * Una relativa se rechaza en vez de resolverse contra el `cwd`: el SDK documenta que
142
+ * `file_path` es absoluta («must be absolute, not relative»), así que una relativa es algo
143
+ * que no entendemos, y lo que no se entiende se deniega.
144
+ */
145
+ export function rutaVirtualDeEscritura(cwd, filePath) {
146
+ const raiz = resolve(cwd);
147
+ const destino = resolve(filePath);
148
+ if (destino === raiz)
149
+ return undefined;
150
+ const dentro = relative(raiz, destino);
151
+ // `relative` da algo que empieza por `..` cuando el destino se sale, y una ruta absoluta
152
+ // cuando están en volúmenes distintos (Windows). Las dos cosas son «fuera».
153
+ if (dentro === "" || dentro.startsWith("..") || resolve(dentro) === dentro)
154
+ return undefined;
155
+ return `/${dentro.split(sep).join("/")}`;
156
+ }
157
+ /**
158
+ * ¿Puede el hijo escribir en esta ruta? **Las guardas del proyecto, reaplicadas.**
159
+ *
160
+ * Y se aplican DOS veces, que es la lección de `arbolDeProyecto.ts` medida en su día: sobre
161
+ * el TEXTO —de balde, antes de tocar el disco— y sobre el camino REAL. Sin la segunda no
162
+ * era verdad: en un sistema que no distingue mayúsculas (APFS, NTFS) `.ENV` no es `/.env`
163
+ * para `puedeLeerRuta` pero abre `.env`, y un enlace simbólico dentro de la raíz que apunte
164
+ * a un fichero denegado o a fuera del proyecto pasa la comprobación de «sigue dentro»
165
+ * porque su texto sí lo está. Lo que falla no es el sitio, es el destino.
166
+ *
167
+ * Del fichero que se va a escribir se canonicaliza el PADRE y no él: una escritura normal
168
+ * crea un fichero que todavía no existe, así que su `realpath` lanzaría y esto denegaría
169
+ * toda creación de ficheros nuevos.
170
+ *
171
+ * `real` entra por parámetro como el `leer` de `cambioDe`: en producción es `realpathSync`,
172
+ * y un test puede darle un doble. Que lance —el padre no existe— no es admitir: es denegar,
173
+ * porque entonces no se sabe dónde cae.
174
+ */
175
+ export function veredictoDeRuta(opciones) {
176
+ const { cwd, ruta, ficheros } = opciones;
177
+ const soloLeer = opciones.soloConfinamiento === true;
178
+ const real = opciones.real ?? realpathSync;
179
+ if (typeof ruta !== "string" || ruta === "") {
180
+ return { admitida: false, motivo: "no dijiste qué fichero, y sin ruta no hay nada que autorizar" };
181
+ }
182
+ /**
183
+ * **Quién decide «dentro o fuera» es UNA pasada, y es la de los caminos REALES.** La del
184
+ * texto no puede decidirlo, y eso salió de la primera ejecución VIVA de este camino
185
+ * (11-09-2026): el proyecto estaba en `/tmp/xc-vivo` y el hijo pidió
186
+ * `/private/tmp/xc-vivo/hola.txt` —canonicaliza él, y en macOS `/tmp` es un enlace a
187
+ * `/private/tmp`—, así que los dos textos no casaban y esto lo rechazaba como «fuera del
188
+ * proyecto»; el hijo solo se salvó reintentando con la ruta sin canonicalizar. Con
189
+ * cualquier componente enlazado en el camino del proyecto —ese `/tmp`, un home detrás de
190
+ * un enlace— escribir habría sido imposible y sin ningún motivo que lo explicara.
191
+ *
192
+ * Del destino se canonicaliza ÉL si existe —eso resuelve el enlace al propio fichero, que
193
+ * es como se llega a `.env` con un nombre inocente— y su PADRE si no, porque una escritura
194
+ * normal crea ficheros que todavía no están y canonicalizar solo él habría denegado toda
195
+ * creación. Que no se pueda preguntar no es admitir: es denegar, porque entonces no se
196
+ * sabe dónde cae.
197
+ *
198
+ * `real` entra por parámetro como el `leer` de `cambioDe`: en producción `realpathSync`, y
199
+ * un test puede darle un doble.
200
+ */
201
+ let destino;
202
+ let raizReal;
203
+ try {
204
+ destino = canonicalizarLoQueExista(resolve(ruta), real);
205
+ raizReal = real(resolve(cwd));
206
+ }
207
+ catch {
208
+ return {
209
+ admitida: false,
210
+ motivo: "no se pudo comprobar dónde cae esa ruta de verdad (¿falta la carpeta?), así que no se autoriza",
211
+ };
212
+ }
213
+ /**
214
+ * **La RAÍZ del proyecto es un destino válido para LEER, y rechazarla dejaba al agente
215
+ * ciego.** `rutaVirtualDeEscritura` la descarta a propósito —no es un fichero que
216
+ * escribir—, y eso es correcto para una escritura y falso para un listado: `Glob` y `Grep`
217
+ * apuntan a la carpeta del proyecto, que es exactamente la raíz.
218
+ *
219
+ * Medido en vivo antes de esto, y lo dijo el propio hijo: «el proyecto está prácticamente
220
+ * vacío para mí, porque no puedo listarlo» — y a cambio hizo SESENTA Y CINCO lecturas a
221
+ * ciegas probando nombres de fichero (`README.md`, `CLAUDE.md`, `app.ini`…), ninguno de los
222
+ * cuales existía. Una guarda que no deja mirar no protege: empuja a adivinar.
223
+ */
224
+ if (soloLeer && destino === raizReal)
225
+ return { admitida: true, ruta: "/" };
226
+ const virtual = rutaVirtualDeEscritura(raizReal, destino);
227
+ if (virtual === undefined) {
228
+ return {
229
+ admitida: false,
230
+ // El texto no nombra la operación, y eso lo señaló el propio hijo en la tercera
231
+ // ejecución viva: decía «solo puedes escribir dentro del proyecto» al denegar una
232
+ // LECTURA. De este mensaje sale lo que el modelo intenta a continuación, así que
233
+ // decirle mal qué se le deniega es empujarlo al camino equivocado.
234
+ motivo: "esa ruta cae fuera de la carpeta del proyecto (o no es absoluta), y xonecode acota este agente al proyecto",
235
+ };
236
+ }
237
+ /**
238
+ * Y las reglas se aplican DOS veces: sobre el texto que nombró el hijo y sobre el camino
239
+ * real. Con una sola no era verdad, y estaba medido en su día por `arbolDeProyecto.ts`: en
240
+ * un sistema que no distingue mayúsculas (APFS, NTFS) `.ENV` no es `/.env` para
241
+ * `puedeLeerRuta` pero abre `.env`, y un enlace dentro de la raíz que apunte a un fichero
242
+ * denegado pasa la comprobación de texto porque su nombre sí está dentro. Lo que falla no
243
+ * es el sitio, es el destino — y al revés también: un `.env` nombrado tal cual se rechaza
244
+ * aunque su `realpath` cayera en otra parte.
245
+ */
246
+ const porTexto = rutaVirtualDeEscritura(cwd, ruta);
247
+ if (porTexto !== undefined) {
248
+ const motivo = motivoDeRutaVirtual(porTexto, ficheros, soloLeer);
249
+ if (motivo !== undefined)
250
+ return { admitida: false, motivo };
251
+ }
252
+ const porReal = motivoDeRutaVirtual(virtual, ficheros, soloLeer);
253
+ if (porReal !== undefined)
254
+ return { admitida: false, motivo: porReal };
255
+ // Se devuelve la del camino REAL: es la única que está definida siempre —con la raíz
256
+ // nombrada por un enlace, la del texto no sale— y va desde la raíz del proyecto, así que
257
+ // no lleva nada de la máquina dentro.
258
+ return { admitida: true, ruta: virtual };
259
+ }
260
+ /**
261
+ * El camino real de algo que puede no existir todavía: se canonicaliza el trozo que SÍ
262
+ * existe y se le vuelven a pegar los segmentos que faltan.
263
+ *
264
+ * Canonicalizar solo el padre no basta, y no es un caso raro: `Write` crea las carpetas que
265
+ * falten, así que una escritura en una subcarpeta nueva —`/app/nueva/x.js`— tiene un padre
266
+ * que tampoco está. Con solo el padre eso contestaba «no se pudo comprobar dónde cae» en vez
267
+ * del motivo de verdad (medido: un `/artifacts/login.html` en un proyecto sin esa carpeta
268
+ * perdía su mensaje, que es el que le dice al hijo dónde iba).
269
+ *
270
+ * Se sube hasta el primer ancestro que existe y se para: así se resuelve cualquier enlace
271
+ * del tramo real —que es de donde viene el peligro— y el tramo que todavía no existe no
272
+ * puede esconder ninguno, porque no hay nada que enlazar. Si no existe ni la raíz del
273
+ * sistema, `real` lanza y quien llama deniega.
274
+ */
275
+ function canonicalizarLoQueExista(absoluta, real) {
276
+ const pendientes = [];
277
+ let actual = absoluta;
278
+ for (;;) {
279
+ try {
280
+ return resolve(real(actual), ...pendientes);
281
+ }
282
+ catch {
283
+ const padre = dirname(actual);
284
+ // `dirname("/") === "/"`: sin esta salida el bucle no termina nunca.
285
+ if (padre === actual)
286
+ throw new Error(`no existe ningún ancestro de ${absoluta}`);
287
+ pendientes.unshift(basename(actual));
288
+ actual = padre;
289
+ }
290
+ }
291
+ }
292
+ /** Las reglas sobre una ruta VIRTUAL ya normalizada. Las mismas del camino de LangGraph. */
293
+ function motivoDeRutaVirtual(ruta, ficheros, soloLeer = false) {
294
+ if (!puedeLeerRuta(ruta)) {
295
+ return `${ruta} está denegado siempre: ahí viven las credenciales, el historial de git y la carpeta interna de xonecode`;
296
+ }
297
+ if (esVistaAplanada(ruta, ficheros)) {
298
+ // También al LEER, y no es de más: el backend se las retira al agente entero, porque un
299
+ // agente que ve la vista aplanada edita el fichero equivocado.
300
+ return `${ruta} lo genera XOne Studio desde su .xne y no se toca: mira el .xne`;
301
+ }
302
+ if (soloLeer)
303
+ return undefined;
304
+ for (const carpeta of CARPETAS_DENEGADAS) {
305
+ if (ruta === carpeta || ruta.startsWith(`${carpeta}/`)) {
306
+ return `${carpeta}/ es de solo lectura: son instrucciones y material de entrada, no ficheros que reescribir`;
307
+ }
308
+ }
309
+ const artefacto = artefactoFueraDeSitio(ruta);
310
+ if (artefacto !== undefined) {
311
+ return `los artefactos no van dentro del proyecto. Eso iba en ${artefacto}, no en ${ruta}`;
312
+ }
313
+ const descarga = descargaFueraDeSitio(ruta);
314
+ if (descarga !== undefined) {
315
+ return `${ruta} es una carpeta interna del harness, no del proyecto`;
316
+ }
317
+ return undefined;
318
+ }
319
+ /**
320
+ * La decisión del hook `PreToolUse`, que es la que NO se puede ensombrecer.
321
+ *
322
+ * **Esto no es un cinturón de más: es el único sitio donde la denegación es cierta**, y sale
323
+ * de leer el SDK instalado en vez de suponerlo. Tres frases suyas, textuales:
324
+ * - «Allow rules from settings files can also shadow the callback» — o sea que `canUseTool`
325
+ * puede no invocarse nunca.
326
+ * - «Denials that resolve before canUseTool runs — PreToolUse hook denies, and deny-rule
327
+ * overrides of hook allow/ask decisions — are not covered here» — o sea que un deny del
328
+ * hook resuelve ANTES y gana.
329
+ * - «With a permission prompt surface (stdio/SDK canUseTool), the 'ask' path surfaces via a
330
+ * can_use_tool control_request» — o sea que `ask` es cómo se llega a nuestro callback.
331
+ *
332
+ * Y de ahí sale el reparto: el hook contesta las TRES clases explícitamente —`allow` para
333
+ * leer, `deny` para todo lo que no está en las listas, y `ask` para escribir, que es lo que
334
+ * empuja la decisión hacia `canUseTool`, donde sí se puede esperar a una persona— y no deja
335
+ * ninguna sin decidir. Dejar una sin decidir sería dejar que la decida el `permissionMode`,
336
+ * cuyas semánticas exactas NO están medidas: `dontAsk` («deny if not pre-approved») podría
337
+ * denegar la escritura antes del callback y dejar la función muerta con todo en verde, y
338
+ * `default` («prompts for dangerous operations») podría aprobar `WebSearch` por su cuenta
339
+ * sin consultarnos — que es el fallo en la dirección mala.
340
+ */
341
+ export function decisionDePreToolUse(opciones) {
342
+ const clase = claseDeToolExterna(opciones.nombre);
343
+ if (clase === "lectura") {
344
+ /**
345
+ * **La guarda de ruta de una lectura se resuelve AQUÍ, y eso salió de medirlo dos
346
+ * veces.** Primero el hook contestaba `allow` a secas y la ruta la miraba `canUseTool`:
347
+ * medido en vivo, el hijo leyó `.env`, `.xonecode/config.json`, la vista aplanada y
348
+ * `/etc/hosts` —cinco de cinco, «ninguna fue interceptada»—, porque un `allow` del hook
349
+ * es una PRE-APROBACIÓN y el callback ya no se consulta. O sea el mismo mecanismo de
350
+ * ensombrecimiento que el SDK documenta, construido por nosotros.
351
+ *
352
+ * Así que el hook decide la lectura entera: `allow` solo con la ruta comprobada, y
353
+ * `deny` con el motivo. `canUseTool` mantiene la MISMA comprobación como segunda llave
354
+ * para el día que el hook no corra.
355
+ */
356
+ const v = veredictoDeLectura(opciones);
357
+ return v.admitida
358
+ ? {
359
+ hookEventName: "PreToolUse",
360
+ permissionDecision: "allow",
361
+ permissionDecisionReason: "xonecode permite leer y buscar dentro del proyecto",
362
+ }
363
+ : { hookEventName: "PreToolUse", permissionDecision: "deny", permissionDecisionReason: v.motivo };
364
+ }
365
+ if (clase === "escritura") {
366
+ // `ask` y no `allow`: quien decide una escritura es la política de la sesión, y a ella
367
+ // se llega por `canUseTool`. Un `allow` aquí se saltaría el diff, la política y las
368
+ // guardas de ruta de un salto — que es exactamente lo que le pasó a la lectura.
369
+ return {
370
+ hookEventName: "PreToolUse",
371
+ permissionDecision: "ask",
372
+ permissionDecisionReason: "cada escritura de xonecode pasa por una autorización",
373
+ };
374
+ }
375
+ return {
376
+ hookEventName: "PreToolUse",
377
+ permissionDecision: "deny",
378
+ permissionDecisionReason: motivoDeToolDenegada(opciones.nombre, clase),
379
+ };
380
+ }
381
+ /**
382
+ * **Las tools de LECTURA también llevan guarda de ruta, y esto salió de correrlo.**
383
+ *
384
+ * Medido en la segunda ejecución viva (11-09-2026), dicho por el propio hijo: «`.env` — sí
385
+ * pude leerlo: contiene una línea `CLAVE=secreta`». La lista blanca permitía `Read` a secas,
386
+ * sin mirar la ruta — y era así desde el primer día, o sea que el agujero no lo abrió la
387
+ * escritura: ya estaba. Por el camino de LangGraph esto no pasa porque `DENEGADO_SIEMPRE`
388
+ * deniega `read` sobre `/.env`, `/.git` y `/.xonecode`; ahí dentro además vive el
389
+ * `checkpoint.sqlite`, que lleva la lista de mensajes ENTERA de cada conversación.
390
+ *
391
+ * Qué se deniega leer, y por qué no es la misma lista que para escribir:
392
+ * - `/.env`, `/.git`, `/.xonecode`: las de `DENEGADO_SIEMPRE` para `read`, por lo mismo.
393
+ * - Las **vistas aplanadas**: el backend se las RETIRA al agente entero
394
+ * (`sinVistasAplanadas`), no solo para escribir — si las ve, edita el fichero equivocado.
395
+ * - Y lo que caiga **fuera de la carpeta del proyecto**, que es la mitad que nadie había
396
+ * comprobado: el `file_path` de `Read` es absoluto y puede nombrar cualquier cosa.
397
+ * - `/skills/`, `/adjuntos/` y `/artefactos/` NO se deniegan: leerlos es su razón de ser.
398
+ *
399
+ * `Glob` y `Grep` llevan `path` OPCIONAL (medido en `sdk-tools.d.ts`), y ausente significa
400
+ * la raíz: entonces no hay nada que comprobar. **Límite declarado**: un `Grep` sobre la raíz
401
+ * puede devolver líneas de un fichero denegado, porque `canUseTool` decide sobre la LLAMADA
402
+ * y no puede filtrar su salida. Cerrarlo exigiría un hook `PostToolUse`, que no está puesto.
403
+ */
404
+ export function veredictoDeLectura(opciones) {
405
+ const { nombre, entrada } = opciones;
406
+ // La clave donde cada tool lleva la ruta. `Read` la exige; `Glob` y `Grep` no.
407
+ const clave = nombre === "Read" || nombre === "NotebookRead" ? "file_path" : "path";
408
+ const ruta = entrada[clave];
409
+ if (ruta === undefined)
410
+ return { admitida: true };
411
+ if (typeof ruta !== "string" || ruta === "") {
412
+ return { admitida: false, motivo: "esa ruta no se entiende, así que no se lee" };
413
+ }
414
+ const v = veredictoDeRuta({ ...opciones, ruta, soloConfinamiento: true });
415
+ return v.admitida ? { admitida: true } : { admitida: false, motivo: v.motivo };
416
+ }
417
+ /** El motivo que se le devuelve al hijo cuando la tool entera no está concedida. */
418
+ export function motivoDeToolDenegada(nombre, clase) {
419
+ if (clase === "denegada" || clase === "desconocida") {
420
+ return (`xonecode no te concede «${nombre}». Puedes leer y buscar (${[...TOOLS_EXTERNAS_DE_LECTURA].join(", ")}) ` +
421
+ `y, si tu papel lo permite, escribir con Write y Edit — cada escritura pasa por una autorización. ` +
422
+ `Todo lo demás está denegado: explica qué harías en vez de intentarlo por otro camino.`);
423
+ }
424
+ return ("xonecode no te concede escribir: tu papel es de solo lectura. " +
425
+ "Explica qué harías en vez de intentar hacerlo.");
426
+ }
427
+ /** Y el motivo cuando el propio SDK abortó mientras se esperaba la decisión. */
428
+ export const MOTIVO_DE_ESCRITURA_ABORTADA = "la petición se canceló antes de que nadie autorizara esa escritura, así que no se ha escrito nada.";
429
+ /** El motivo cuando la escritura se pidió bien y quien decide dijo que no. */
430
+ export const MOTIVO_DE_ESCRITURA_RECHAZADA = "la escritura no se ha autorizado. No la intentes por otro camino: explica qué querías cambiar y por qué.";
431
+ /**
432
+ * El `pedirAprobacion` de la sesión, convertido en la política de escritura del hijo.
433
+ *
434
+ * **Y aquí está la mitad buena del diseño: la política ya existía.** `pedirAprobacion` es el
435
+ * hueco que `abrirSesionReal` declara y que cada piel rellena, y sus implementaciones son
436
+ * exactamente las dos que hacían falta:
437
+ * - **La interactiva** (`consolaWeb.aprobacionesTui`, el modal de la TUI, `pedirDecisiones`
438
+ * por readline): enseña el diff y espera. Nace RECHAZADA entera, tiene plazo, y sin
439
+ * cliente enganchado contesta rechazo sin preguntar. Fail-closed por construcción, ya
440
+ * probado, ya cableado en las tres pieles.
441
+ * - **La autónoma** (`consolaDeTarea.aprobacionesTui`): concede —la autorización fue crear
442
+ * la tarea—, lo ANUNCIA con los nombres en el transcript y lo mete en `Tarea.autorizadas`
443
+ * por su callback `autorizado`. O sea que la escritura de un agente externo dentro de una
444
+ * tarea de fondo no puede ser muda ni quedarse fuera de la cuenta que lee el juez.
445
+ * Escribir un segundo tipo de política habría sido escribir una segunda forma de decidir
446
+ * sobre lo mismo, con dos sitios donde el fail-closed puede dejar de estarlo.
447
+ *
448
+ * Lo que esto añade es la TRADUCCIÓN, y nada más: un `PendienteDeAprobacion` sintético por
449
+ * escritura, con su ruta virtual y su diff, por los mismos dos mapas que ya viajan
450
+ * (`ficheros`, `diffs`).
451
+ * El id lleva el prefijo `externo:` para que no pueda colisionar con el de un interrupt del
452
+ * grafo — de esas claves se construye el `resume`, y una colisión resolvería el interrupt
453
+ * equivocado.
454
+ *
455
+ * **Fail-closed hasta el final**: si la promesa revienta, o si el mapa no trae `approve`
456
+ * para nuestro id, la respuesta es NO. Nunca se deja salir la excepción hacia el SDK, cuyo
457
+ * comportamiento ante un `canUseTool` que lanza no está medido.
458
+ */
459
+ export function politicaDeAprobacionExterna(pedirAprobacion) {
460
+ let contador = 0;
461
+ return async (escrituras) => {
462
+ /**
463
+ * **Una lista vacía es un NO, y no un «sí» por vacuidad.** Es la trampa de `every`: sin
464
+ * esta línea, un item sin cambios —o uno cuyo `changes` no se pudo leer— se concedería
465
+ * porque «todas las escrituras vinieron aprobadas», sin haber enseñado nada a nadie.
466
+ */
467
+ if (escrituras.length === 0)
468
+ return false;
469
+ const pendientes = [];
470
+ const ficheros = new Map();
471
+ const diffs = new Map();
472
+ for (const escritura of escrituras) {
473
+ contador += 1;
474
+ const id = `externo:${contador}`;
475
+ pendientes.push({
476
+ id,
477
+ // `origen` es el nombre del agente, igual que el `[dev]` que `hitlDe` mete en la
478
+ // descripción y que `aPendiente` extrae: las tres pieles pintan este campo.
479
+ origen: escritura.agente,
480
+ // Cuando son varias se DICE, porque el precio de decidir cambia: son de un mismo
481
+ // item que se aplica entero o nada, así que rechazar una las tumba todas.
482
+ descripcion: escrituras.length === 1
483
+ ? "quiere escribir un fichero del proyecto (agente externo)"
484
+ : `quiere escribir ${escrituras.length} ficheros del proyecto, todos o ninguno (agente externo)`,
485
+ // Las dos, y en este orden: es lo que las pieles leen para saber si hay botón de
486
+ // aprobar. `consolaDeTarea` además solo concede lo que declara `approve`.
487
+ decisionesPermitidas: ["approve", "reject"],
488
+ });
489
+ ficheros.set(id, escritura.ruta);
490
+ diffs.set(id, escritura.lineas);
491
+ }
492
+ try {
493
+ const decisiones = await pedirAprobacion(pendientes, ficheros, diffs);
494
+ // TODAS, o ninguna: una sola negativa tumba el conjunto. Es la dirección segura y la
495
+ // única que el `decision` único de Codex sabe expresar.
496
+ return pendientes.every((p) => decisiones.get(p.id)?.type === "approve");
497
+ }
498
+ catch {
499
+ // El «sin humano» de `run.ts` corta LANZANDO desde `pedirAprobacion`. Aquí eso es una
500
+ // respuesta y no un fallo: nadie ha autorizado nada.
501
+ return false;
502
+ }
503
+ };
504
+ }
505
+ /**
506
+ * Las guardas de ruta sobre una tanda de escrituras propuestas, sea cual sea el motor.
507
+ *
508
+ * **Vive aquí, en UN sitio, y los dos motores externos que escriben por petición la llaman.**
509
+ * Cada uno traduce lo suyo —Codex sus `changes` con `kind` y `diff`, OpenCode el `toolCall`
510
+ * de ACP con su `oldText`/`newText`— y a partir de ahí el camino es el mismo. Escribir este
511
+ * bucle una vez por motor sería un segundo (y un tercer) sitio donde el fail-closed puede
512
+ * dejar de estarlo, que es la única forma en la que estas guardas se han roto nunca.
513
+ *
514
+ * **Una sola ruta mala tumba la tanda entera.** Es la dirección conservadora, y además la
515
+ * única que los dos protocolos saben expresar: los dos contestan una petición con UNA
516
+ * decisión, así que «las que pasen sí» no se puede decir.
517
+ */
518
+ export function veredictoDeEscriturasExternas(opciones) {
519
+ if (opciones.propuestas.length === 0) {
520
+ return { admitidas: false, motivo: "no dijiste qué ficheros cambiabas, y sin eso no hay nada que autorizar" };
521
+ }
522
+ const escrituras = [];
523
+ for (const propuesta of opciones.propuestas) {
524
+ const veredicto = veredictoDeRuta({
525
+ cwd: opciones.cwd,
526
+ ruta: propuesta.ruta,
527
+ ficheros: opciones.ficheros,
528
+ ...(opciones.real === undefined ? {} : { real: opciones.real }),
529
+ });
530
+ if (!veredicto.admitida)
531
+ return { admitidas: false, motivo: veredicto.motivo };
532
+ escrituras.push({ agente: opciones.agente, ruta: veredicto.ruta, lineas: propuesta.lineas });
533
+ }
534
+ return { admitidas: true, escrituras };
535
+ }
536
+ /**
537
+ * La decisión entera de una tanda: guardas de ruta y DESPUÉS la política.
538
+ *
539
+ * **Vive extraída y no en el cierre de cada adaptador**, que es donde este repo ha visto
540
+ * fallar esta arquitectura nueve veces: una composición de producción dentro de algo que
541
+ * todos los tests doblan. Con los adaptadores externos sería literal, porque los tests no
542
+ * lanzan sus binarios.
543
+ *
544
+ * Fail-closed en los cuatro caminos: sin política, con una ruta rechazada, si la política
545
+ * revienta, o si contesta que no. Y el orden importa: preguntar por algo cuyo único final
546
+ * posible es un rechazo es sacar un modal inútil — la misma regla que el `when` de
547
+ * `seDetieneEn` en `agent/perfiles.ts`.
548
+ */
549
+ export async function decisionDeEscrituraExterna(opciones) {
550
+ if (opciones.aprobar === undefined) {
551
+ return {
552
+ concedida: false,
553
+ motivo: "xonecode no tiene a quién pedir la autorización de esta escritura en esta sesión, así que no se concede.",
554
+ };
555
+ }
556
+ const veredicto = veredictoDeEscriturasExternas(opciones);
557
+ if (!veredicto.admitidas)
558
+ return { concedida: false, motivo: veredicto.motivo };
559
+ try {
560
+ return { concedida: await opciones.aprobar(veredicto.escrituras) };
561
+ }
562
+ catch {
563
+ // El «sin humano» de `cli/run.ts` corta LANZANDO desde `pedirAprobacion`. Eso es una
564
+ // respuesta, no un fallo: nadie ha autorizado nada.
565
+ return { concedida: false };
566
+ }
567
+ }
568
+ /**
569
+ * El diff de una escritura del hijo, con la MISMA función que compone el de una aprobación
570
+ * del grafo (`interrupts.ts#cambioDe`).
571
+ *
572
+ * La traducción es solo el NOMBRE de la tool: las claves de los argumentos coinciden —
573
+ * medido en `sdk-tools.d.ts`—, así que `Write` es un `write_file` y `Edit` un `edit_file`.
574
+ * Compartir la función en vez de escribir un segundo diff es lo que impide que la ventana de
575
+ * aprobación de un agente externo enseñe algo distinto de la de siempre.
576
+ *
577
+ * Un matiz declarado: `Edit` sin `replace_all` cambia UNA aparición y falla si hay varias,
578
+ * mientras que `cambioDe` pinta el reemplazo de todas. Cuando la edición es válida para el
579
+ * hijo hay exactamente una, así que coinciden; cuando no lo es, el diff enseña de más y la
580
+ * escritura no llega a ocurrir.
581
+ */
582
+ export function diffDeEscrituraExterna(tool, entrada, rutaVirtual, leer = leerDelDisco) {
583
+ const nombre = tool === "Write" ? "write_file" : "edit_file";
584
+ const vista = cambioDe({
585
+ id: "externo",
586
+ tool: nombre,
587
+ // La ruta VIRTUAL y no la absoluta: de aquí sale lo que se pinta, y la ruta de la
588
+ // máquina no viaja por el cable (puede ir por un túnel).
589
+ args: { ...entrada, file_path: rutaVirtual },
590
+ description: "",
591
+ allowedDecisions: [],
592
+ },
593
+ // `cambioDe` lee por la ruta que le den, o sea la virtual; se traduce aquí, que es el
594
+ // único sitio que conoce la carpeta de verdad.
595
+ () => leer(entrada["file_path"]));
596
+ // Sin vista no hay diff que enseñar, y eso NO es un motivo para no preguntar: se pregunta
597
+ // igual con la lista vacía. Decidir sin diff es peor que decidir con diff, pero escribir
598
+ // sin decisión es lo que esto existe para evitar.
599
+ return vista?.lineas ?? [];
600
+ }
601
+ /** El ANTES de una escritura: el disco. Un fichero que no está es cadena vacía, no un fallo. */
602
+ function leerDelDisco(ruta) {
603
+ try {
604
+ return readFileSync(ruta, "utf8");
605
+ }
606
+ catch {
607
+ return "";
608
+ }
609
+ }
610
+ /**
611
+ * La política de la SESIÓN, derivada de su `pedirAprobacion`. **Extraída y exportada a
612
+ * propósito**: es el cableado, y el cableado es donde este repo lleva SIETE reglas que se
613
+ * quedaron sin montar con todo en verde. Vive fuera del cierre de `abrirSesionReal` para
614
+ * poder probarla contra lo real en vez de contra un doble.
615
+ *
616
+ * Ausente es «no hay a quién preguntar», y entonces no hay política — o sea que el hijo no
617
+ * escribe. Es el caso de `xonecode run` en CI y de cualquier sesión montada sin aprobación:
618
+ * el mismo camino por el que hoy el turno dice «piden aprobación y no hay quién apruebe».
619
+ */
620
+ export function politicaExternaDeSesion(pedirAprobacion) {
621
+ return pedirAprobacion === undefined ? undefined : politicaDeAprobacionExterna(pedirAprobacion);
622
+ }
623
+ /**
624
+ * Cómo se cuenta una tool del hijo en el flujo de eventos: con el nombre CANÓNICO del
625
+ * harness y el mismo campo que la lista blanca de `resumenDeTool.ts` elegiría.
626
+ *
627
+ * El nombre se traduce (`Read` → `read_file`) en vez de inventarse una familia nueva: así
628
+ * el colapsador de `core/notify.ts` las agrupa con las demás —«→ lee ×5 — a, b y 3 más»—,
629
+ * la bitácora las cuenta igual y ninguna piel se entera de que hay dos orígenes. Añadir
630
+ * `Read` a las tablas de icono y verbo habría sido la otra opción, y es la que deja dos
631
+ * nombres para lo mismo en el registro y en las Trazas.
632
+ *
633
+ * Y el detalle es exactamente lo que ya sale de una tool del grafo: una RUTA (virtual, la
634
+ * del proyecto) o un PATRÓN. Nunca contenido, nunca la ruta de la máquina — la misma regla,
635
+ * aplicada en el otro lado de la frontera.
636
+ */
637
+ const NOMBRE_CANONICO = {
638
+ Read: "read_file",
639
+ NotebookRead: "read_file",
640
+ Glob: "glob",
641
+ Grep: "grep",
642
+ Write: "write_file",
643
+ Edit: "edit_file",
644
+ };
645
+ export function eventoDeToolExterna(nombre, entrada, cwd, real = realpathSync) {
646
+ const canonico = NOMBRE_CANONICO[nombre] ?? nombre;
647
+ // Un patrón para las de búsqueda, una ruta para las de fichero. Igual que `detalleDe`.
648
+ if (canonico === "glob" || canonico === "grep") {
649
+ const patron = entrada["pattern"];
650
+ return typeof patron === "string" && patron !== ""
651
+ ? { nombre: canonico, detalle: patron }
652
+ : { nombre: canonico };
653
+ }
654
+ const ruta = entrada["file_path"];
655
+ if (typeof ruta !== "string" || ruta === "")
656
+ return { nombre: canonico };
657
+ /**
658
+ * La VIRTUAL: la de la máquina no sale de aquí ni al registro ni al cable. Si la ruta cae
659
+ * fuera del proyecto no se dice cuál era —sería decir dónde miró fuera—, solo la tool.
660
+ *
661
+ * Y contra las DOS formas de la raíz, que es el mismo tropiezo que ya costó una medida en
662
+ * `veredictoDeRuta`: el hijo canonicaliza, así que con el proyecto en `/tmp/x` pide
663
+ * `/private/tmp/x/...` y los textos no casan. Medido en vivo antes de esto: el pulso decía
664
+ * «→ lee ×65» sin UN SOLO nombre de fichero, que es la mitad de lo que esta línea existe
665
+ * para contar.
666
+ */
667
+ const suyo = rutaVirtualDeEscritura(cwd, ruta);
668
+ if (suyo !== undefined)
669
+ return { nombre: canonico, detalle: suyo };
670
+ try {
671
+ const porLaCanonica = rutaVirtualDeEscritura(real(resolve(cwd)), ruta);
672
+ if (porLaCanonica !== undefined)
673
+ return { nombre: canonico, detalle: porLaCanonica };
674
+ }
675
+ catch {
676
+ // Sin poder canonicalizar no se afirma ninguna ruta: se cuenta la tool y ya.
677
+ }
678
+ return { nombre: canonico };
679
+ }
680
+ /**
681
+ * Todo lo que un agente externo necesita de la SESIÓN, en un solo sitio y exportado.
682
+ *
683
+ * **Está extraída del cierre de `abrirSesionReal` a propósito**, y no por gusto: son cuatro
684
+ * campos opcionales que se componen dentro de algo que todos sus tests doblan, o sea el
685
+ * patrón de fallo que este repo lleva medido siete veces — una regla que deja de estar
686
+ * montada CON TODO EN VERDE. Aquí los tres que importan se pueden comprobar contra lo real:
687
+ * que sin `pedirAprobacion` no hay política (y por tanto no hay escritura), que la actividad
688
+ * del hijo acaba en la cola de eventos, y que la lista de ficheros es una FUNCIÓN y no una
689
+ * foto congelada al abrir la sesión.
690
+ */
691
+ export function opcionesDeSubagenteExterno(opciones) {
692
+ const politica = politicaExternaDeSesion(opciones.pedirAprobacion);
693
+ return {
694
+ ...(politica === undefined ? {} : { aprobarEscritura: politica }),
695
+ ficherosDelProyecto: opciones.ficherosDelProyecto,
696
+ // Un evento `tool` normal y corriente: el colapsador lo agrupa con los demás, la
697
+ // bitácora lo cuenta y ninguna piel se entera de que hay dos orígenes.
698
+ alUsarTool: ({ nombre, detalle }) => opciones.eventos.empujar({ tipo: "tool", nombre, ...(detalle === undefined ? {} : { detalle }) }),
699
+ ...(opciones.alConsumir === undefined ? {} : { alConsumir: opciones.alConsumir }),
700
+ };
701
+ }
702
+ /** Cuántos ficheros se le enumeran. Por encima, se dice cuántos quedan fuera. */
703
+ export const TOPE_DE_INVENTARIO = 400;
704
+ /**
705
+ * El inventario del proyecto, para un agente EXTERNO.
706
+ *
707
+ * **Existe porque un hijo de Claude Code no puede enumerar la carpeta, y eso está medido**
708
+ * (11-09-2026, espiando el hook en una ejecución real). Intentó `Bash ls`, `Bash find` —
709
+ * denegadas—, pidió `ToolSearch {"query":"select:Glob,Grep"}` y **no las encontró**: ese
710
+ * hijo no trae ninguna herramienta de listado. Después probó las tools MCP del usuario
711
+ * (denegadas también) y acabó leyendo a ciegas nombres inventados —`README.md`,
712
+ * `CLAUDE.md`, `app.ini`, `main.xml`…, ninguno existía— para concluir que «el proyecto está
713
+ * prácticamente vacío para mí, porque no puedo listarlo».
714
+ *
715
+ * La salida fácil habría sido concederle `Bash`, y es justo la que no se puede tomar: con
716
+ * `Bash` escribe el proyecto entero saltándose la política, el diff y las guardas de ruta.
717
+ * La buena es que el harness YA sabe qué ficheros hay —es la misma lista con la que
718
+ * reconoce una vista aplanada— así que se le DICE. Es el patrón de `core/adjuntos.ts`:
719
+ * montar no basta, hay que decir que están.
720
+ *
721
+ * Dos cosas que hereda de las reglas de siempre: las rutas van VIRTUALES (desde la raíz del
722
+ * proyecto, nunca las de la máquina) y la lista va ACOTADA con el total al lado, que es lo
723
+ * que impide leer los que caben como si fueran todos.
724
+ */
725
+ export function inventarioDelProyecto(ficheros, tope = TOPE_DE_INVENTARIO) {
726
+ const todos = [...ficheros].sort();
727
+ if (todos.length === 0) {
728
+ // Vacío es una afirmación, y aquí es cierta: la lista sale del disco. Decirlo evita que
729
+ // se ponga a adivinar nombres, que es exactamente lo que hacía sin inventario.
730
+ return "INVENTARIO DEL PROYECTO: está vacío (no hay ningún fichero que leer).";
731
+ }
732
+ const listados = todos.slice(0, tope);
733
+ const resto = todos.length - listados.length;
734
+ return [
735
+ `INVENTARIO DEL PROYECTO (${todos.length} fichero(s)). No tienes ninguna herramienta para`,
736
+ "listar carpetas, así que esta es la lista: no adivines nombres, lee de aquí. Las rutas van",
737
+ "desde la raíz del proyecto; para leerlas, quítales la barra inicial.",
738
+ ...listados.map((f) => `- ${f}`),
739
+ ...(resto === 0 ? [] : [`- … y ${resto} fichero(s) más que no caben en esta lista.`]),
740
+ ].join("\n");
741
+ }
742
+ //# sourceMappingURL=escrituraExterna.js.map