@saulwade/swl-ses 1.0.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 (702) hide show
  1. package/CLAUDE.md +238 -0
  2. package/README.md +560 -0
  3. package/_userland/agentes/.gitkeep +0 -0
  4. package/_userland/habilidades/.gitkeep +0 -0
  5. package/agentes/.evolved.json +9 -0
  6. package/agentes/accesibilidad-wcag-swl.md +692 -0
  7. package/agentes/arquitecto-swl.md +238 -0
  8. package/agentes/auto-evolucion-swl.md +854 -0
  9. package/agentes/backend-api-swl.md +470 -0
  10. package/agentes/backend-csharp-swl.md +418 -0
  11. package/agentes/backend-go-swl.md +388 -0
  12. package/agentes/backend-java-swl.md +279 -0
  13. package/agentes/backend-node-swl.md +477 -0
  14. package/agentes/backend-python-swl.md +608 -0
  15. package/agentes/backend-rust-swl.md +362 -0
  16. package/agentes/backend-workers-swl.md +480 -0
  17. package/agentes/cloud-infra-swl.md +485 -0
  18. package/agentes/consolidador-swl.md +539 -0
  19. package/agentes/datos-swl.md +584 -0
  20. package/agentes/depurador-swl.md +349 -0
  21. package/agentes/devops-ci-swl.md +374 -0
  22. package/agentes/disenador-ui-swl.md +558 -0
  23. package/agentes/documentador-swl.md +343 -0
  24. package/agentes/evals/arquitecto-swl.evals.json +56 -0
  25. package/agentes/evals/auto-evolucion-swl.evals.json +68 -0
  26. package/agentes/evals/implementador-swl.evals.json +56 -0
  27. package/agentes/evals/orquestador-swl.evals.json +60 -0
  28. package/agentes/evals/perfilador-usuario-swl.evals.json +60 -0
  29. package/agentes/evals/red-team-swl.evals.json +59 -0
  30. package/agentes/evals/revisor-codigo-swl.evals.json +59 -0
  31. package/agentes/frontend-angular-swl.md +627 -0
  32. package/agentes/frontend-css-swl.md +720 -0
  33. package/agentes/frontend-react-swl.md +696 -0
  34. package/agentes/frontend-swl.md +500 -0
  35. package/agentes/frontend-tailwind-swl.md +830 -0
  36. package/agentes/implementador-swl.md +328 -0
  37. package/agentes/investigador-swl.md +430 -0
  38. package/agentes/investigador-ux-swl.md +500 -0
  39. package/agentes/llm-apps-swl.md +276 -0
  40. package/agentes/migrador-swl.md +417 -0
  41. package/agentes/mobile-android-swl.md +509 -0
  42. package/agentes/mobile-cross-swl.md +539 -0
  43. package/agentes/mobile-ios-swl.md +500 -0
  44. package/agentes/mobile-testing-swl.md +300 -0
  45. package/agentes/notificador-swl.md +916 -0
  46. package/agentes/observabilidad-swl.md +436 -0
  47. package/agentes/orquestador-swl.md +884 -0
  48. package/agentes/pagos-swl.md +283 -0
  49. package/agentes/perfilador-usuario-swl.md +306 -0
  50. package/agentes/planificador-swl.md +402 -0
  51. package/agentes/producto-prd-swl.md +587 -0
  52. package/agentes/red-team-swl.md +216 -0
  53. package/agentes/release-manager-swl.md +568 -0
  54. package/agentes/rendimiento-swl.md +714 -0
  55. package/agentes/resolutor-build-swl.md +243 -0
  56. package/agentes/revisor-angular-swl.md +276 -0
  57. package/agentes/revisor-codigo-swl.md +348 -0
  58. package/agentes/revisor-csharp-swl.md +262 -0
  59. package/agentes/revisor-go-swl.md +257 -0
  60. package/agentes/revisor-java-swl.md +255 -0
  61. package/agentes/revisor-kotlin-swl.md +271 -0
  62. package/agentes/revisor-nextjs-swl.md +279 -0
  63. package/agentes/revisor-php-swl.md +269 -0
  64. package/agentes/revisor-react-swl.md +276 -0
  65. package/agentes/revisor-rust-swl.md +344 -0
  66. package/agentes/revisor-seguridad-swl.md +390 -0
  67. package/agentes/revisor-swift-swl.md +266 -0
  68. package/agentes/revisor-typescript-swl.md +344 -0
  69. package/agentes/sre-swl.md +265 -0
  70. package/agentes/tdd-qa-swl.md +354 -0
  71. package/agentes/ux-disenador-swl.md +501 -0
  72. package/bin/lib/bot-comandos.js +1030 -0
  73. package/bin/lib/bot-discovery.js +182 -0
  74. package/bin/lib/bot-git.js +142 -0
  75. package/bin/swl-ses.js +325 -0
  76. package/bin/swl-telegram-bot.js +442 -0
  77. package/bin/swl-telegram-bot.plist +21 -0
  78. package/bin/swl-telegram-bot.service +14 -0
  79. package/comandos/swl/.evolved.json +23 -0
  80. package/comandos/swl/actualizar.md +174 -0
  81. package/comandos/swl/adoptar-proyecto.md +207 -0
  82. package/comandos/swl/aprender.md +701 -0
  83. package/comandos/swl/auditar-deps.md +134 -0
  84. package/comandos/swl/autoresearch.md +170 -0
  85. package/comandos/swl/ayuda.md +224 -0
  86. package/comandos/swl/brainstorm.md +50 -0
  87. package/comandos/swl/checkpoint.md +330 -0
  88. package/comandos/swl/compactar.md +283 -0
  89. package/comandos/swl/configurar-ci.md +227 -0
  90. package/comandos/swl/contexto.md +112 -0
  91. package/comandos/swl/contribuir.md +233 -0
  92. package/comandos/swl/crear-skill.md +292 -0
  93. package/comandos/swl/cron.md +196 -0
  94. package/comandos/swl/dashboard.md +146 -0
  95. package/comandos/swl/discutir-fase.md +230 -0
  96. package/comandos/swl/ejecutar-fase.md +135 -0
  97. package/comandos/swl/evaluar-skill.md +487 -0
  98. package/comandos/swl/evolucion-estado.md +142 -0
  99. package/comandos/swl/evolucionar.md +259 -0
  100. package/comandos/swl/exportar-vault.md +189 -0
  101. package/comandos/swl/gateway.md +158 -0
  102. package/comandos/swl/inbox.md +116 -0
  103. package/comandos/swl/instalar.md +220 -0
  104. package/comandos/swl/instintos.md +86 -0
  105. package/comandos/swl/mapear-codebase.md +312 -0
  106. package/comandos/swl/mcp-status.md +175 -0
  107. package/comandos/swl/metricas.md +270 -0
  108. package/comandos/swl/modelo.md +102 -0
  109. package/comandos/swl/notificaciones.md +396 -0
  110. package/comandos/swl/nuevo-proyecto.md +154 -0
  111. package/comandos/swl/planear-fase.md +221 -0
  112. package/comandos/swl/plugins.md +256 -0
  113. package/comandos/swl/reflect-skills.md +125 -0
  114. package/comandos/swl/release.md +217 -0
  115. package/comandos/swl/revisar-impacto.md +206 -0
  116. package/comandos/swl/revisar.md +330 -0
  117. package/comandos/swl/salud.md +363 -0
  118. package/comandos/swl/sesiones.md +200 -0
  119. package/comandos/swl/skill-search.md +113 -0
  120. package/comandos/swl/verificar.md +585 -0
  121. package/comandos/swl/wiki.md +620 -0
  122. package/contextos/dev.md +32 -0
  123. package/contextos/research.md +30 -0
  124. package/contextos/review.md +31 -0
  125. package/habilidades/accesibilidad-a11y/SKILL.md +201 -0
  126. package/habilidades/accesibilidad-a11y/evals/evals.json +56 -0
  127. package/habilidades/accesibilidad-a11y/recursos/ejemplos-y-checklist-completo.md +441 -0
  128. package/habilidades/agent-browser/SKILL.md +218 -0
  129. package/habilidades/agentes-como-servicio/SKILL.md +218 -0
  130. package/habilidades/ai-runtime-security/SKILL.md +273 -0
  131. package/habilidades/angular-avanzado/SKILL.md +164 -0
  132. package/habilidades/angular-avanzado/recursos/ejemplos-avanzados.md +219 -0
  133. package/habilidades/angular-moderno/SKILL.md +186 -0
  134. package/habilidades/angular-moderno/evals/evals.json +45 -0
  135. package/habilidades/angular-moderno/recursos/ejemplos-avanzados.md +106 -0
  136. package/habilidades/api-rest-diseno/SKILL.md +191 -0
  137. package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
  138. package/habilidades/api-rest-diseno/recursos/referencia-api.md +140 -0
  139. package/habilidades/aprendizaje-continuo/SKILL.md +151 -0
  140. package/habilidades/aprendizaje-continuo/evals/evals.json +53 -0
  141. package/habilidades/aprendizaje-continuo/recursos/referencia-instintos.md +290 -0
  142. package/habilidades/async-python/SKILL.md +149 -0
  143. package/habilidades/async-python/evals/evals.json +47 -0
  144. package/habilidades/async-python/recursos/patrones-y-ejemplos-completos.md +292 -0
  145. package/habilidades/auth-patrones/.evolved.json +9 -0
  146. package/habilidades/auth-patrones/SKILL.md +413 -0
  147. package/habilidades/auth-patrones/recursos/implementaciones-completas.md +229 -0
  148. package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -0
  149. package/habilidades/auto-evolucion-protocolo/evals/evals.json +55 -0
  150. package/habilidades/auto-evolucion-protocolo/recursos/referencia-completa.md +145 -0
  151. package/habilidades/autoresearch/SKILL.md +268 -0
  152. package/habilidades/autoresearch/evals/evals.json +41 -0
  153. package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
  154. package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
  155. package/habilidades/azure-cloud/SKILL.md +308 -0
  156. package/habilidades/azure-cloud/recursos/aks.md +327 -0
  157. package/habilidades/backend-mcp-servidor/SKILL.md +270 -0
  158. package/habilidades/backend-production-resilience/SKILL.md +288 -0
  159. package/habilidades/brainstorming/SKILL.md +295 -0
  160. package/habilidades/brainstorming/recursos/componentes-html.md +247 -0
  161. package/habilidades/build-errors-cpp/SKILL.md +270 -0
  162. package/habilidades/build-errors-csharp/SKILL.md +265 -0
  163. package/habilidades/build-errors-go/SKILL.md +306 -0
  164. package/habilidades/build-errors-java/SKILL.md +278 -0
  165. package/habilidades/build-errors-kotlin/SKILL.md +303 -0
  166. package/habilidades/build-errors-nextjs/SKILL.md +312 -0
  167. package/habilidades/build-errors-php/SKILL.md +270 -0
  168. package/habilidades/build-errors-python/SKILL.md +292 -0
  169. package/habilidades/build-errors-rust/SKILL.md +284 -0
  170. package/habilidades/build-errors-swift/SKILL.md +272 -0
  171. package/habilidades/build-errors-typescript/SKILL.md +369 -0
  172. package/habilidades/checklist-calidad/SKILL.md +271 -0
  173. package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
  174. package/habilidades/checklist-seguridad/SKILL.md +285 -0
  175. package/habilidades/checkpoints-verificacion/SKILL.md +298 -0
  176. package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
  177. package/habilidades/ci-cd-pipelines/SKILL.md +157 -0
  178. package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
  179. package/habilidades/ci-cd-pipelines/recursos/pipelines-completos.md +487 -0
  180. package/habilidades/cloud-aws/SKILL.md +142 -0
  181. package/habilidades/cloud-aws/recursos/servicios-aws-referencia.md +321 -0
  182. package/habilidades/compactacion-contexto/SKILL.md +247 -0
  183. package/habilidades/contenedores-docker/SKILL.md +137 -0
  184. package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
  185. package/habilidades/contenedores-docker/recursos/ejemplos-y-configuraciones.md +327 -0
  186. package/habilidades/context-builder/SKILL.md +170 -0
  187. package/habilidades/control-profundidad/SKILL.md +128 -0
  188. package/habilidades/csharp-experto/SKILL.md +322 -0
  189. package/habilidades/csharp-patrones/SKILL.md +316 -0
  190. package/habilidades/csharp-testing/SKILL.md +286 -0
  191. package/habilidades/css-moderno/SKILL.md +166 -0
  192. package/habilidades/css-moderno/evals/evals.json +43 -0
  193. package/habilidades/css-moderno/recursos/ejemplos-y-patrones-completos.md +337 -0
  194. package/habilidades/datos-etl/SKILL.md +129 -0
  195. package/habilidades/datos-etl/recursos/implementaciones-completas.md +322 -0
  196. package/habilidades/dbml-experto/SKILL.md +339 -0
  197. package/habilidades/dbml-experto/evals/evals.json +56 -0
  198. package/habilidades/dependencias-auditoria/SKILL.md +320 -0
  199. package/habilidades/deprecacion-migracion/SKILL.md +169 -0
  200. package/habilidades/deprecacion-migracion/recursos/implementaciones-completas.md +220 -0
  201. package/habilidades/design-tokens/SKILL.md +158 -0
  202. package/habilidades/design-tokens/recursos/tokens-y-configuracion.md +363 -0
  203. package/habilidades/devsecops-pipeline-security/SKILL.md +309 -0
  204. package/habilidades/diagrama-arquitectura/SKILL.md +165 -0
  205. package/habilidades/diagrama-arquitectura/assets/template.html +276 -0
  206. package/habilidades/discutir-fase/SKILL.md +188 -0
  207. package/habilidades/diseno-herramientas-agente/SKILL.md +199 -0
  208. package/habilidades/diseno-responsivo/SKILL.md +186 -0
  209. package/habilidades/diseno-responsivo/recursos/ejemplos-layouts.md +156 -0
  210. package/habilidades/django-experto/SKILL.md +205 -0
  211. package/habilidades/django-experto/recursos/async-django.md +390 -0
  212. package/habilidades/django-experto/recursos/drf-patrones.md +438 -0
  213. package/habilidades/django-experto/recursos/orm-avanzado.md +382 -0
  214. package/habilidades/django-experto/recursos/referencia-completa.md +188 -0
  215. package/habilidades/django-experto/recursos/testing-django.md +415 -0
  216. package/habilidades/doc-sync/SKILL.md +280 -0
  217. package/habilidades/drift-detection/SKILL.md +179 -0
  218. package/habilidades/ejecutar-fase/SKILL.md +468 -0
  219. package/habilidades/estilo-sin-ai-isms/SKILL.md +775 -0
  220. package/habilidades/estilo-sin-ai-isms/evals/evals.json +63 -0
  221. package/habilidades/estilo-sin-ai-isms/scripts/detectar_aiisms.py +500 -0
  222. package/habilidades/estructura-proyecto-claude/SKILL.md +215 -0
  223. package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
  224. package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +176 -0
  225. package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +289 -0
  226. package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
  227. package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
  228. package/habilidades/evaluacion-agentes/SKILL.md +314 -0
  229. package/habilidades/event-driven/SKILL.md +153 -0
  230. package/habilidades/event-driven/recursos/implementaciones-completas.md +423 -0
  231. package/habilidades/extraccion-documentos/SKILL.md +221 -0
  232. package/habilidades/extractor-de-aprendizajes/.evolved.json +9 -0
  233. package/habilidades/extractor-de-aprendizajes/SKILL.md +311 -0
  234. package/habilidades/extractor-de-aprendizajes/evals/evals.json +55 -0
  235. package/habilidades/fastapi-experto/SKILL.md +221 -0
  236. package/habilidades/fastapi-experto/recursos/async-patterns.md +438 -0
  237. package/habilidades/fastapi-experto/recursos/dependency-injection.md +330 -0
  238. package/habilidades/fastapi-experto/recursos/referencia-completa.md +79 -0
  239. package/habilidades/fastapi-experto/recursos/testing-httpx.md +420 -0
  240. package/habilidades/filament-admin/SKILL.md +290 -0
  241. package/habilidades/frontend-avanzado/SKILL.md +257 -0
  242. package/habilidades/frontend-avanzado/recursos/apis-nativas-ejemplos.md +341 -0
  243. package/habilidades/gcp-cloud/SKILL.md +260 -0
  244. package/habilidades/gcp-cloud/recursos/gke.md +234 -0
  245. package/habilidades/gcp-cloud/recursos/terraform-gcp.md +307 -0
  246. package/habilidades/generacion-mermaid/SKILL.md +229 -0
  247. package/habilidades/git-worktrees-paralelo/SKILL.md +270 -0
  248. package/habilidades/go-experto/SKILL.md +305 -0
  249. package/habilidades/go-patrones/SKILL.md +299 -0
  250. package/habilidades/go-testing/SKILL.md +291 -0
  251. package/habilidades/graphql-experto/SKILL.md +323 -0
  252. package/habilidades/guardrail-semantico/SKILL.md +282 -0
  253. package/habilidades/harness-claude-code/SKILL.md +299 -0
  254. package/habilidades/iam-secretos/SKILL.md +265 -0
  255. package/habilidades/iam-secretos/recursos/implementaciones-completas.md +356 -0
  256. package/habilidades/infra-github-actions/SKILL.md +166 -0
  257. package/habilidades/instalar-sistema/.evolved.json +9 -0
  258. package/habilidades/instalar-sistema/SKILL.md +221 -0
  259. package/habilidades/java-experto/SKILL.md +290 -0
  260. package/habilidades/java-patrones/SKILL.md +275 -0
  261. package/habilidades/java-testing/SKILL.md +288 -0
  262. package/habilidades/kotlin-compose/SKILL.md +278 -0
  263. package/habilidades/kotlin-compose/recursos/animaciones-performance.md +93 -0
  264. package/habilidades/kotlin-experto/SKILL.md +318 -0
  265. package/habilidades/kotlin-testing/SKILL.md +267 -0
  266. package/habilidades/kotlin-testing/recursos/testing-avanzado.md +74 -0
  267. package/habilidades/kubernetes-orquestacion/SKILL.md +152 -0
  268. package/habilidades/kubernetes-orquestacion/recursos/manifiestos-completos.md +452 -0
  269. package/habilidades/langchain-langraph/SKILL.md +386 -0
  270. package/habilidades/langchain-langraph/recursos/evaluacion-rag.md +321 -0
  271. package/habilidades/langchain-langraph/recursos/rag-maturity-model.md +225 -0
  272. package/habilidades/langchain-langraph/recursos/vectorstores.md +306 -0
  273. package/habilidades/legacy-code-rescue/SKILL.md +267 -0
  274. package/habilidades/likec4-experto/SKILL.md +412 -0
  275. package/habilidades/likec4-experto/evals/evals.json +69 -0
  276. package/habilidades/manejo-errores/.evolved.json +9 -0
  277. package/habilidades/manejo-errores/SKILL.md +407 -0
  278. package/habilidades/manejo-errores/recursos/implementaciones-completas.md +248 -0
  279. package/habilidades/mapear-codebase/SKILL.md +275 -0
  280. package/habilidades/memoria-busqueda/SKILL.md +194 -0
  281. package/habilidades/memoria-busqueda/evals/evals.json +44 -0
  282. package/habilidades/meta-skills-estandar/SKILL.md +298 -0
  283. package/habilidades/meta-skills-estandar/recursos/anti-patrones-y-leyes.md +205 -0
  284. package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +107 -0
  285. package/habilidades/meta-skills-estandar/recursos/idiomas-framework.md +60 -0
  286. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -0
  287. package/habilidades/microservicios/SKILL.md +155 -0
  288. package/habilidades/microservicios/recursos/patrones-y-ejemplos-completos.md +325 -0
  289. package/habilidades/mobile-flutter/SKILL.md +199 -0
  290. package/habilidades/mobile-flutter/recursos/ejemplos-completos.md +319 -0
  291. package/habilidades/mobile-react-native/SKILL.md +176 -0
  292. package/habilidades/mobile-react-native/recursos/ejemplos-completos.md +216 -0
  293. package/habilidades/mongodb-experto/SKILL.md +302 -0
  294. package/habilidades/monitoring-alertas/SKILL.md +201 -0
  295. package/habilidades/monitoring-alertas/recursos/instrumentacion-y-alertas.md +301 -0
  296. package/habilidades/nestjs-experto/SKILL.md +307 -0
  297. package/habilidades/nestjs-experto/recursos/guards-interceptors.md +339 -0
  298. package/habilidades/nestjs-experto/recursos/modulos-di.md +287 -0
  299. package/habilidades/nestjs-experto/recursos/testing-nestjs.md +354 -0
  300. package/habilidades/nextjs-experto/SKILL.md +335 -0
  301. package/habilidades/nextjs-patrones/SKILL.md +303 -0
  302. package/habilidades/nextjs-testing/SKILL.md +331 -0
  303. package/habilidades/node-experto/.evolved.json +9 -0
  304. package/habilidades/node-experto/SKILL.md +266 -0
  305. package/habilidades/node-experto/recursos/patrones-completos.md +283 -0
  306. package/habilidades/notificaciones-multicanal/SKILL.md +159 -0
  307. package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
  308. package/habilidades/notificaciones-multicanal/recursos/configuracion-y-templates.md +303 -0
  309. package/habilidades/nuevo-proyecto/SKILL.md +204 -0
  310. package/habilidades/orquestacion-async/SKILL.md +303 -0
  311. package/habilidades/paid-media-tracking/SKILL.md +269 -0
  312. package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +220 -0
  313. package/habilidades/paid-media-tracking/recursos/google-ads-api.md +215 -0
  314. package/habilidades/patrones-python/SKILL.md +228 -0
  315. package/habilidades/patrones-python/evals/evals.json +56 -0
  316. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -0
  317. package/habilidades/patrones-python/recursos/referencia-completa.md +202 -0
  318. package/habilidades/perfil-usuario/SKILL.md +200 -0
  319. package/habilidades/perfil-usuario/evals/evals.json +55 -0
  320. package/habilidades/performance-baseline/SKILL.md +297 -0
  321. package/habilidades/php-experto/SKILL.md +291 -0
  322. package/habilidades/php-patrones/SKILL.md +306 -0
  323. package/habilidades/php-testing/SKILL.md +280 -0
  324. package/habilidades/planear-fase/SKILL.md +269 -0
  325. package/habilidades/postgresql-experto/SKILL.md +151 -0
  326. package/habilidades/postgresql-experto/evals/evals.json +53 -0
  327. package/habilidades/postgresql-experto/recursos/referencia-completa.md +215 -0
  328. package/habilidades/prevencion-racionalizacion/SKILL.md +175 -0
  329. package/habilidades/prevencion-sobreingenieria/SKILL.md +323 -0
  330. package/habilidades/privacy-memoria/SKILL.md +141 -0
  331. package/habilidades/privacy-memoria/evals/evals.json +43 -0
  332. package/habilidades/prompt-engineering/SKILL.md +518 -0
  333. package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +467 -0
  334. package/habilidades/rag-arquitectura/SKILL.md +338 -0
  335. package/habilidades/rails-experto/SKILL.md +237 -0
  336. package/habilidades/rails-experto/recursos/active-record.md +260 -0
  337. package/habilidades/rails-experto/recursos/hotwire-turbo.md +293 -0
  338. package/habilidades/rails-experto/recursos/testing-rspec.md +362 -0
  339. package/habilidades/react-experto/SKILL.md +209 -0
  340. package/habilidades/react-experto/evals/evals.json +55 -0
  341. package/habilidades/react-experto/recursos/patrones-y-ejemplos-completos.md +240 -0
  342. package/habilidades/react-optimizacion/SKILL.md +174 -0
  343. package/habilidades/react-optimizacion/recursos/patrones-avanzados.md +138 -0
  344. package/habilidades/redis-experto/SKILL.md +305 -0
  345. package/habilidades/release-semver/.evolved.json +9 -0
  346. package/habilidades/release-semver/SKILL.md +248 -0
  347. package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
  348. package/habilidades/rust-experto/SKILL.md +400 -0
  349. package/habilidades/rust-patrones/SKILL.md +296 -0
  350. package/habilidades/rust-testing/SKILL.md +311 -0
  351. package/habilidades/seguridad-skills-ia/SKILL.md +262 -0
  352. package/habilidades/sql-optimizacion/SKILL.md +200 -0
  353. package/habilidades/sql-optimizacion/evals/evals.json +54 -0
  354. package/habilidades/sql-optimizacion/recursos/patrones-sql-avanzados.md +131 -0
  355. package/habilidades/sre-patrones/SKILL.md +333 -0
  356. package/habilidades/sre-patrones/recursos/chaos-engineering.md +241 -0
  357. package/habilidades/sre-patrones/recursos/oncall-design.md +236 -0
  358. package/habilidades/stripe-pagos/SKILL.md +550 -0
  359. package/habilidades/stripe-pagos/recursos/errores-reintentos.md +390 -0
  360. package/habilidades/stripe-pagos/recursos/stripe-connect.md +290 -0
  361. package/habilidades/structured-outputs/SKILL.md +343 -0
  362. package/habilidades/swift-experto/SKILL.md +320 -0
  363. package/habilidades/swift-experto/recursos/keychain-y-wrappers.md +110 -0
  364. package/habilidades/swift-patrones/SKILL.md +313 -0
  365. package/habilidades/swift-patrones/recursos/tca-ejemplo-completo.md +113 -0
  366. package/habilidades/swift-testing/SKILL.md +254 -0
  367. package/habilidades/swift-testing/recursos/xcuitest-planes.md +143 -0
  368. package/habilidades/swl-dashboard/SKILL.md +370 -0
  369. package/habilidades/swl-markitdown/SKILL.md +285 -0
  370. package/habilidades/swl-markitdown/evals/evals.json +52 -0
  371. package/habilidades/swl-revisar-impacto/SKILL.md +233 -0
  372. package/habilidades/tailwind-experto/SKILL.md +240 -0
  373. package/habilidades/tailwind-experto/recursos/referencia-completa.md +184 -0
  374. package/habilidades/tdd-workflow/SKILL.md +293 -0
  375. package/habilidades/terraform-experto/SKILL.md +321 -0
  376. package/habilidades/testing-python/SKILL.md +340 -0
  377. package/habilidades/testing-python/recursos/ejemplos-completos.md +167 -0
  378. package/habilidades/threat-model-lite/SKILL.md +246 -0
  379. package/habilidades/tracing-processor/SKILL.md +212 -0
  380. package/habilidades/tracking-measurement/SKILL.md +239 -0
  381. package/habilidades/tracking-measurement/recursos/consent-mode.md +231 -0
  382. package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +216 -0
  383. package/habilidades/tracking-measurement/recursos/meta-capi.md +262 -0
  384. package/habilidades/typescript-avanzado/SKILL.md +144 -0
  385. package/habilidades/typescript-avanzado/evals/evals.json +55 -0
  386. package/habilidades/typescript-avanzado/recursos/patrones-y-ejemplos-completos.md +298 -0
  387. package/habilidades/typescript-diagnosticos/SKILL.md +513 -0
  388. package/habilidades/ux-diseno/SKILL.md +116 -0
  389. package/habilidades/ux-diseno/evals/evals.json +43 -0
  390. package/habilidades/ux-diseno/recursos/patrones-ux-referencia.md +214 -0
  391. package/habilidades/validacion-ci-sistema/SKILL.md +136 -0
  392. package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +369 -0
  393. package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
  394. package/habilidades/verificacion-evidencia/SKILL.md +160 -0
  395. package/habilidades/verificar-trabajo/SKILL.md +303 -0
  396. package/habilidades/verificar-trabajo/recursos/plantilla-verificacion.md +60 -0
  397. package/habilidades/wiki-conocimiento/SKILL.md +276 -0
  398. package/habilidades/wireframes-flujos/SKILL.md +212 -0
  399. package/habilidades/wireframes-flujos/recursos/referencia-completa.md +192 -0
  400. package/habilidades/workflow-claude-code/SKILL.md +260 -0
  401. package/habilidades/workflow-claude-code/recursos/referencia-completa.md +109 -0
  402. package/hooks/_run-hook.sh +57 -0
  403. package/hooks/actualizar-perfil-usuario.js +364 -0
  404. package/hooks/agente-lifecycle.js +71 -0
  405. package/hooks/aiisms-detector.js +173 -0
  406. package/hooks/audit-trail.js +204 -0
  407. package/hooks/auto-background.js +97 -0
  408. package/hooks/auto-consolidacion.js +178 -0
  409. package/hooks/auto-evolucion.js +666 -0
  410. package/hooks/auto-restaurar-settings.js +360 -0
  411. package/hooks/calidad-pre-commit.js +929 -0
  412. package/hooks/calidad-typescript.js +511 -0
  413. package/hooks/captura-feedback-usuario.js +148 -0
  414. package/hooks/check-update.js +211 -0
  415. package/hooks/clasificador-mensajes.js +271 -0
  416. package/hooks/degradacion-instintos.js +272 -0
  417. package/hooks/escaneo-secretos.js +389 -0
  418. package/hooks/extraccion-aprendizajes.js +763 -0
  419. package/hooks/grafo-contexto.js +129 -0
  420. package/hooks/graph-update.js +67 -0
  421. package/hooks/guardrail-modelo.js +247 -0
  422. package/hooks/inbox-aviso.js +75 -0
  423. package/hooks/inyeccion-contexto.js +246 -0
  424. package/hooks/lib/abort-registry.js +214 -0
  425. package/hooks/lib/agent-backend.js +210 -0
  426. package/hooks/lib/agent-comms.js +263 -0
  427. package/hooks/lib/agent-issue-codes.js +284 -0
  428. package/hooks/lib/agent-matcher.js +189 -0
  429. package/hooks/lib/async-hook-registry.js +252 -0
  430. package/hooks/lib/atomic-write.js +130 -0
  431. package/hooks/lib/auto-consolidator.js +335 -0
  432. package/hooks/lib/canary-skills.js +187 -0
  433. package/hooks/lib/consolidation-lock.js +291 -0
  434. package/hooks/lib/context-builder.js +430 -0
  435. package/hooks/lib/context-compressor.js +657 -0
  436. package/hooks/lib/convergence-detector.js +105 -0
  437. package/hooks/lib/delegation-tracker.js +198 -0
  438. package/hooks/lib/detectar-package-manager.js +423 -0
  439. package/hooks/lib/edit-accumulator.js +171 -0
  440. package/hooks/lib/error-classifier.js +308 -0
  441. package/hooks/lib/event-bus.js +112 -0
  442. package/hooks/lib/evolution-tracker.js +442 -0
  443. package/hooks/lib/execution-state.js +316 -0
  444. package/hooks/lib/fingerprint-id.js +135 -0
  445. package/hooks/lib/gateway-notify.js +116 -0
  446. package/hooks/lib/graph-security.js +75 -0
  447. package/hooks/lib/guardrail-metrics.js +202 -0
  448. package/hooks/lib/hook-circuit-breaker.js +206 -0
  449. package/hooks/lib/loop-detector.js +267 -0
  450. package/hooks/lib/mcp-health.js +184 -0
  451. package/hooks/lib/mcp-pool.js +436 -0
  452. package/hooks/lib/memory-search.js +506 -0
  453. package/hooks/lib/merkle-audit.js +96 -0
  454. package/hooks/lib/model-router.js +222 -0
  455. package/hooks/lib/normalize-error.js +324 -0
  456. package/hooks/lib/normalize-input.js +65 -0
  457. package/hooks/lib/nudge-tracker.js +306 -0
  458. package/hooks/lib/otlp-exporter.js +365 -0
  459. package/hooks/lib/performance-marks.js +239 -0
  460. package/hooks/lib/privacy-filter.js +128 -0
  461. package/hooks/lib/prompt-injection-scanner.js +209 -0
  462. package/hooks/lib/provenance-tracker.js +183 -0
  463. package/hooks/lib/rate-limit-tracker.js +253 -0
  464. package/hooks/lib/reflect-classifier.js +164 -0
  465. package/hooks/lib/resource-quota.js +122 -0
  466. package/hooks/lib/retry-jitter.js +165 -0
  467. package/hooks/lib/risk-engine.js +368 -0
  468. package/hooks/lib/run-log.js +408 -0
  469. package/hooks/lib/session-fts.js +379 -0
  470. package/hooks/lib/session-store.js +293 -0
  471. package/hooks/lib/singleton-guard.js +159 -0
  472. package/hooks/lib/skill-auditor.js +588 -0
  473. package/hooks/lib/sync-status.js +228 -0
  474. package/hooks/lib/taint-tracker.js +107 -0
  475. package/hooks/lib/task-service.js +295 -0
  476. package/hooks/lib/tech-skills-map.js +146 -0
  477. package/hooks/lib/telegram-cliente.js +159 -0
  478. package/hooks/lib/telegram-config.js +170 -0
  479. package/hooks/lib/token-budget.js +156 -0
  480. package/hooks/lib/token-estimator.js +420 -0
  481. package/hooks/lib/toon-compressor.js +245 -0
  482. package/hooks/lib/usage-model.js +183 -0
  483. package/hooks/lib/variable-resolver.js +230 -0
  484. package/hooks/linea-estado.js +324 -0
  485. package/hooks/metricas-evolucion.js +209 -0
  486. package/hooks/monitor-contexto.js +325 -0
  487. package/hooks/notificacion-sesion-stop.js +198 -0
  488. package/hooks/notificacion-telegram-notification.js +4 -0
  489. package/hooks/notificacion-telegram-subagent.js +4 -0
  490. package/hooks/notificacion-telegram.js +267 -0
  491. package/hooks/preservar-estado-pre-compact.js +150 -0
  492. package/hooks/proteccion-rutas.js +366 -0
  493. package/hooks/registro-turnos.js +209 -0
  494. package/hooks/resumen-sesion.js +249 -0
  495. package/hooks/risk-scoring.js +323 -0
  496. package/hooks/rotar-audit-auto.js +122 -0
  497. package/hooks/sugerir-regenerar-inventario.js +170 -0
  498. package/hooks/telemetria-agentes.js +167 -0
  499. package/hooks/tracking-costos.js +688 -0
  500. package/instintos/global.yaml +8 -0
  501. package/instintos/perfil-usuario.yaml +53 -0
  502. package/instintos/prompt-appendices.yaml +57 -0
  503. package/instintos/proyecto.yaml +372 -0
  504. package/manifiestos/gateway-config.json +77 -0
  505. package/manifiestos/handoff-context.json +223 -0
  506. package/manifiestos/hook-profiles.json +44 -0
  507. package/manifiestos/hooks-config.json +360 -0
  508. package/manifiestos/modulos.json +1173 -0
  509. package/manifiestos/perfiles.json +404 -0
  510. package/package.json +86 -0
  511. package/plantillas/ESTADO.md +109 -0
  512. package/plantillas/HOJA-RUTA.md +143 -0
  513. package/plantillas/PROYECTO.md +122 -0
  514. package/plantillas/REQUISITOS.md +132 -0
  515. package/plantillas/auditor-veto-template.md +105 -0
  516. package/plantillas/github-workflows/README.md +47 -0
  517. package/plantillas/github-workflows/release-please.yml +44 -0
  518. package/plantillas/github-workflows/swl-ci.yml +107 -0
  519. package/plantillas/github-workflows/swl-security.yml +51 -0
  520. package/plantillas/mcp-mineru.json +13 -0
  521. package/plantillas/research/ARQUITECTURA.md +220 -0
  522. package/plantillas/research/FUNCIONALIDADES.md +175 -0
  523. package/plantillas/research/RESUMEN.md +165 -0
  524. package/plantillas/research/STACK.md +233 -0
  525. package/plantillas/research/TRAMPAS.md +299 -0
  526. package/plantillas/skill-evals-template.json +44 -0
  527. package/plugin.json +343 -0
  528. package/reglas/accesibilidad.md +269 -0
  529. package/reglas/api-diseno.md +400 -0
  530. package/reglas/arquitectura.md +352 -0
  531. package/reglas/brevedad-output.md +124 -0
  532. package/reglas/cloud-infra.md +247 -0
  533. package/reglas/docs.md +245 -0
  534. package/reglas/estilo-codigo.md +201 -0
  535. package/reglas/git-workflow.md +245 -0
  536. package/reglas/gobernanza.md +271 -0
  537. package/reglas/harness-claude-code.md +213 -0
  538. package/reglas/hooks.md +186 -0
  539. package/reglas/lenguajes/csharp/estilo-codigo.md +231 -0
  540. package/reglas/lenguajes/csharp/hooks.md +281 -0
  541. package/reglas/lenguajes/csharp/patrones.md +226 -0
  542. package/reglas/lenguajes/csharp/seguridad.md +258 -0
  543. package/reglas/lenguajes/csharp/testing.md +176 -0
  544. package/reglas/lenguajes/go/estilo-codigo.md +195 -0
  545. package/reglas/lenguajes/go/hooks.md +249 -0
  546. package/reglas/lenguajes/go/patrones.md +249 -0
  547. package/reglas/lenguajes/go/seguridad.md +225 -0
  548. package/reglas/lenguajes/go/testing.md +272 -0
  549. package/reglas/lenguajes/java/estilo-codigo.md +217 -0
  550. package/reglas/lenguajes/java/hooks.md +251 -0
  551. package/reglas/lenguajes/java/patrones.md +226 -0
  552. package/reglas/lenguajes/java/seguridad.md +233 -0
  553. package/reglas/lenguajes/java/testing.md +238 -0
  554. package/reglas/lenguajes/kotlin/estilo-codigo.md +208 -0
  555. package/reglas/lenguajes/kotlin/hooks.md +245 -0
  556. package/reglas/lenguajes/kotlin/patrones.md +201 -0
  557. package/reglas/lenguajes/kotlin/seguridad.md +202 -0
  558. package/reglas/lenguajes/kotlin/testing.md +236 -0
  559. package/reglas/lenguajes/nextjs/estilo-codigo.md +175 -0
  560. package/reglas/lenguajes/nextjs/hooks.md +186 -0
  561. package/reglas/lenguajes/nextjs/patrones.md +225 -0
  562. package/reglas/lenguajes/nextjs/seguridad.md +216 -0
  563. package/reglas/lenguajes/nextjs/testing.md +193 -0
  564. package/reglas/lenguajes/php/estilo-codigo.md +228 -0
  565. package/reglas/lenguajes/php/hooks.md +165 -0
  566. package/reglas/lenguajes/php/patrones.md +233 -0
  567. package/reglas/lenguajes/php/seguridad.md +186 -0
  568. package/reglas/lenguajes/php/testing.md +205 -0
  569. package/reglas/lenguajes/rust/estilo-codigo.md +207 -0
  570. package/reglas/lenguajes/rust/hooks.md +240 -0
  571. package/reglas/lenguajes/rust/patrones.md +250 -0
  572. package/reglas/lenguajes/rust/seguridad.md +221 -0
  573. package/reglas/lenguajes/rust/testing.md +194 -0
  574. package/reglas/lenguajes/swift/estilo-codigo.md +238 -0
  575. package/reglas/lenguajes/swift/hooks.md +257 -0
  576. package/reglas/lenguajes/swift/patrones.md +235 -0
  577. package/reglas/lenguajes/swift/seguridad.md +248 -0
  578. package/reglas/lenguajes/swift/testing.md +242 -0
  579. package/reglas/markitdown.md +60 -0
  580. package/reglas/memoria-consolidada.md +209 -0
  581. package/reglas/patrones.md +225 -0
  582. package/reglas/performance.md +195 -0
  583. package/reglas/pruebas.md +159 -0
  584. package/reglas/seguridad-agentes.md +351 -0
  585. package/reglas/seguridad.md +151 -0
  586. package/reglas/skills-estandar.md +373 -0
  587. package/reglas/testing.md +193 -0
  588. package/schemas/agent-contract.json +176 -0
  589. package/schemas/agent-frontmatter.schema.json +149 -0
  590. package/schemas/agent-message.schema.json +53 -0
  591. package/schemas/agent-output-implementacion.schema.json +85 -0
  592. package/schemas/agent-output-planificacion.schema.json +113 -0
  593. package/schemas/agent-output-review.schema.json +78 -0
  594. package/schemas/diary-entry.schema.json +80 -0
  595. package/schemas/hook-profiles.schema.json +39 -0
  596. package/schemas/hooks-config.schema.json +74 -0
  597. package/schemas/instinct.schema.json +115 -0
  598. package/schemas/modulos.schema.json +29 -0
  599. package/schemas/perfiles.schema.json +28 -0
  600. package/schemas/plugin.schema.json +64 -0
  601. package/schemas/skill-evals.schema.json +95 -0
  602. package/schemas/skill-frontmatter.schema.json +170 -0
  603. package/scripts/actualizar.js +145 -0
  604. package/scripts/audit-skills.sh +78 -0
  605. package/scripts/auditar-agentes-gaps.js +149 -0
  606. package/scripts/auditar-cobertura-frameworks.js +241 -0
  607. package/scripts/auditar-skills-gaps.js +206 -0
  608. package/scripts/bootstrap-instintos.js +259 -0
  609. package/scripts/check-update.js +109 -0
  610. package/scripts/comandos/agents.js +105 -0
  611. package/scripts/comandos/info.js +108 -0
  612. package/scripts/comandos/install-asistido.js +186 -0
  613. package/scripts/comandos/skills.js +211 -0
  614. package/scripts/configurar-branch-protection.js +418 -0
  615. package/scripts/daemon-swl.py +388 -0
  616. package/scripts/desinstalar.js +130 -0
  617. package/scripts/doctor.js +559 -0
  618. package/scripts/field-report.js +199 -0
  619. package/scripts/generar-inventario.js +317 -0
  620. package/scripts/inbox-tmux-inject.js +161 -0
  621. package/scripts/inferir-herramientas-permitidas.js +586 -0
  622. package/scripts/inicializar.js +133 -0
  623. package/scripts/instalador.js +1031 -0
  624. package/scripts/instalar-git-hook.js +122 -0
  625. package/scripts/lib/agp-frontmatter.js +222 -0
  626. package/scripts/lib/append-con-marcadores.js +199 -0
  627. package/scripts/lib/artefactos-python.js +43 -0
  628. package/scripts/lib/audit-query.js +221 -0
  629. package/scripts/lib/autostart-linux.js +347 -0
  630. package/scripts/lib/autostart-macos.js +360 -0
  631. package/scripts/lib/autostart-windows.js +307 -0
  632. package/scripts/lib/budget-enforcer.js +252 -0
  633. package/scripts/lib/claude-sessions.js +285 -0
  634. package/scripts/lib/configurar-ci.js +380 -0
  635. package/scripts/lib/console-span-exporter.js +92 -0
  636. package/scripts/lib/contadores-inventario.js +217 -0
  637. package/scripts/lib/dashboard-widgets.js +290 -0
  638. package/scripts/lib/detectar-runtime.js +279 -0
  639. package/scripts/lib/detectar-stack.js +187 -0
  640. package/scripts/lib/diary-entry.js +234 -0
  641. package/scripts/lib/drift-detector.js +545 -0
  642. package/scripts/lib/estado.js +124 -0
  643. package/scripts/lib/gestor-componentes.js +243 -0
  644. package/scripts/lib/gitignore-manifest.js +305 -0
  645. package/scripts/lib/graph-analyze.py +556 -0
  646. package/scripts/lib/graph-builder.py +485 -0
  647. package/scripts/lib/graph-cluster.py +259 -0
  648. package/scripts/lib/health-row.js +168 -0
  649. package/scripts/lib/hooks-settings.js +789 -0
  650. package/scripts/lib/manifiestos.js +138 -0
  651. package/scripts/lib/mc-client.js +137 -0
  652. package/scripts/lib/notificaciones-telegram.js +1107 -0
  653. package/scripts/lib/npm-version.js +261 -0
  654. package/scripts/lib/paquetes-conocidos.js +50 -0
  655. package/scripts/lib/preservar-usuario.js +586 -0
  656. package/scripts/lib/prompt-builder.js +264 -0
  657. package/scripts/lib/resolver-externo.js +332 -0
  658. package/scripts/lib/schedule-parser.js +305 -0
  659. package/scripts/lib/scoring-instintos.js +240 -0
  660. package/scripts/lib/seguridad.js +160 -0
  661. package/scripts/lib/selector-interactivo.js +152 -0
  662. package/scripts/lib/semantic-search.js +242 -0
  663. package/scripts/lib/skill-discovery.js +234 -0
  664. package/scripts/lib/skill-metrics.js +246 -0
  665. package/scripts/lib/skill-normalizer.js +112 -0
  666. package/scripts/lib/skills-hub.js +340 -0
  667. package/scripts/lib/span-schema.js +134 -0
  668. package/scripts/lib/tool-cost-analyzer.js +255 -0
  669. package/scripts/lib/tracing-processor-interface.js +286 -0
  670. package/scripts/lib/transformadores/base.js +80 -0
  671. package/scripts/lib/transformadores/claude.js +124 -0
  672. package/scripts/lib/transformadores/codex.js +115 -0
  673. package/scripts/lib/transformadores/copilot.js +106 -0
  674. package/scripts/lib/transformadores/gemini.js +74 -0
  675. package/scripts/lib/transformadores/index.js +35 -0
  676. package/scripts/lib/transformadores/opencode.js +75 -0
  677. package/scripts/lib/ui.js +259 -0
  678. package/scripts/limpiar-artefactos-python.js +131 -0
  679. package/scripts/mcp-orchestrator.py +386 -0
  680. package/scripts/mcp-pool-manager.py +352 -0
  681. package/scripts/mcp-telemetry.py +378 -0
  682. package/scripts/poblar-evolvable.js +226 -0
  683. package/scripts/publicar.js +287 -0
  684. package/scripts/reflect-skills.js +403 -0
  685. package/scripts/rotar-audit-logs.js +185 -0
  686. package/scripts/run-skill-evals.js +242 -0
  687. package/scripts/smoke-test.js +374 -0
  688. package/scripts/token-analysis.py +471 -0
  689. package/scripts/validar-manifest.js +195 -0
  690. package/scripts/validar-memoria.js +321 -0
  691. package/scripts/validar-tests-aislamiento.js +184 -0
  692. package/scripts/validar-tokens-test.js +208 -0
  693. package/scripts/validar.js +147 -0
  694. package/scripts/validate-markdown.py +339 -0
  695. package/scripts/validate-skills.py +385 -0
  696. package/scripts/vendor/claude-usage/README.md +116 -0
  697. package/scripts/vendor/claude-usage/cli.py +334 -0
  698. package/scripts/vendor/claude-usage/dashboard.py +795 -0
  699. package/scripts/vendor/claude-usage/scanner.py +467 -0
  700. package/scripts/vendor/markitdown/cli.py +194 -0
  701. package/scripts/verificar-evolucion.js +289 -0
  702. package/scripts/verificar-release.js +494 -0
@@ -0,0 +1,1107 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Flujo reutilizable de configuración de notificaciones Telegram.
5
+ *
6
+ * Usado por:
7
+ * - scripts/instalador.js (paso 9 opt-in)
8
+ * - /swl:notificaciones init (Slice 005)
9
+ *
10
+ * SEGURIDAD:
11
+ * - El token NUNCA se loggea. Solo se muestra longitud y primeros 3 chars.
12
+ * - El .env se escribe en ~/.claude/notifications/ (fuera del repo).
13
+ * - Permisos 0o600 en POSIX; en Windows la ACL del sistema ya protege
14
+ * %USERPROFILE%\.claude\.
15
+ * - Validación post-escritura con JSON.parse + rollback si falla.
16
+ * - Backup obligatorio de settings.json antes de cada merge.
17
+ *
18
+ * ADR-0009: esta función es la ÚNICA excepción a la regla
19
+ * "SWL solo registra hooks a nivel proyecto" (hooks-settings.js).
20
+ * Aplica exclusivamente bajo opt-in explícito del usuario.
21
+ *
22
+ * @module scripts/lib/notificaciones-telegram
23
+ */
24
+
25
+ const fs = require('node:fs');
26
+ const path = require('node:path');
27
+ const os = require('node:os');
28
+ const readline = require('node:readline');
29
+ const { execFileSync } = require('node:child_process');
30
+
31
+ const { atomicWriteSync, atomicWriteJSON } = require('../../hooks/lib/atomic-write');
32
+ // IMPORTANTE: usar require sin destructuring para que los mocks de tests funcionen.
33
+ // El destructuring captura la referencia local original y los mocks posteriores
34
+ // no tienen efecto. Con el módulo completo, hooksSettings.mergeHooksGlobales(...)
35
+ // siempre resuelve la propiedad en tiempo de ejecución. (Fix Slice 015-A)
36
+ const hooksSettings = require('./hooks-settings');
37
+
38
+ // ---------------------------------------------------------------------------
39
+ // Rutas canónicas (NUNCA dentro del repo)
40
+ // ---------------------------------------------------------------------------
41
+
42
+ const DIR_NOTIFICACIONES = path.join(os.homedir(), '.claude', 'notifications');
43
+ const RUTA_PID_DAEMON = path.join(DIR_NOTIFICACIONES, 'bot.pid');
44
+ const RUTA_BIN_DAEMON = path.resolve(__dirname, '..', '..', 'bin', 'swl-telegram-bot.js');
45
+ const RUTA_ENV = path.join(DIR_NOTIFICACIONES, '.env');
46
+ const RUTA_BASELINE = path.join(DIR_NOTIFICACIONES, 'baseline-hooks.json');
47
+ const RUTA_SETTINGS_GLOBAL = path.join(os.homedir(), '.claude', 'settings.json');
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Directorio global de hooks (~/.claude/hooks/)
51
+ // ---------------------------------------------------------------------------
52
+
53
+ /** Directorio destino donde se copian los hooks para ejecución global. */
54
+ const HOOKS_GLOBAL_DIR = path.join(os.homedir(), '.claude', 'hooks');
55
+
56
+ /**
57
+ * Lista de archivos a copiar desde <repo>/hooks/ a ~/.claude/hooks/.
58
+ * Se preserva la estructura lib/ para que los requires relativos funcionen.
59
+ *
60
+ * Orden importa para resolución de dependencias: lib/ primero, hooks raíz después.
61
+ */
62
+ const HOOKS_A_COPIAR = [
63
+ 'lib/atomic-write.js', // dependencia de auto-restaurar y notificacion
64
+ 'lib/telegram-cliente.js',
65
+ 'lib/telegram-config.js',
66
+ 'notificacion-telegram.js',
67
+ 'notificacion-telegram-notification.js',
68
+ 'notificacion-telegram-subagent.js',
69
+ 'auto-restaurar-settings.js',
70
+ ];
71
+
72
+ // Hooks SWL que se registran en el settings global bajo opt-in
73
+ const HOOKS_SWL_NOTIFICACIONES = [
74
+ {
75
+ evento: 'Stop',
76
+ matcher: '',
77
+ archivo: 'notificacion-telegram.js',
78
+ },
79
+ {
80
+ evento: 'Notification',
81
+ matcher: '',
82
+ archivo: 'notificacion-telegram.js',
83
+ },
84
+ {
85
+ evento: 'SubagentStop',
86
+ matcher: '',
87
+ archivo: 'notificacion-telegram.js',
88
+ },
89
+ {
90
+ evento: 'SessionStart',
91
+ matcher: '',
92
+ archivo: 'auto-restaurar-settings.js',
93
+ },
94
+ ];
95
+
96
+ // ---------------------------------------------------------------------------
97
+ // Copia de hooks a ~/.claude/hooks/ (REM-A, REM-E)
98
+ // ---------------------------------------------------------------------------
99
+
100
+ /**
101
+ * Copia los archivos de hook desde el repo swl-ses a ~/.claude/hooks/.
102
+ * Preserva la estructura lib/ para que los requires relativos funcionen.
103
+ *
104
+ * Verifica que cada archivo origen existe ANTES de copiar (REM-E).
105
+ * Si algún origen falta, lanza con mensaje claro que indica la ruta esperada.
106
+ *
107
+ * @param {string} [hooksOrigenOverride] - Para tests: sobreescribir el directorio origen.
108
+ * @param {string} [hooksGlobalDirOverride] - Para tests: sobreescribir el directorio destino.
109
+ * @returns {{ directorio: string, archivos: string[] }}
110
+ * @throws {Error} Si algún archivo origen no existe o la copia falla.
111
+ */
112
+ function _copiarHooksGlobales(hooksOrigenOverride, hooksGlobalDirOverride) {
113
+ // __dirname apunta a <repo-swl>/scripts/lib/ → subir 2 niveles → <repo-swl>/hooks/
114
+ const hooksOrigen = hooksOrigenOverride || path.resolve(__dirname, '..', '..', 'hooks');
115
+ const hooksDestino = hooksGlobalDirOverride || HOOKS_GLOBAL_DIR;
116
+
117
+ // Crear directorios destino si no existen
118
+ if (!fs.existsSync(hooksDestino)) {
119
+ fs.mkdirSync(hooksDestino, { recursive: true });
120
+ }
121
+ const libDestino = path.join(hooksDestino, 'lib');
122
+ if (!fs.existsSync(libDestino)) {
123
+ fs.mkdirSync(libDestino, { recursive: true });
124
+ }
125
+
126
+ const copiados = [];
127
+ for (const rel of HOOKS_A_COPIAR) {
128
+ const origen = path.join(hooksOrigen, rel);
129
+ const destino = path.join(hooksDestino, rel);
130
+
131
+ // REM-E: verificar que el archivo origen existe
132
+ if (!fs.existsSync(origen)) {
133
+ throw new Error(
134
+ `Hook origen no encontrado: ${origen}. ` +
135
+ `Verifica que swl-ses está instalado correctamente en: ${hooksOrigen}`
136
+ );
137
+ }
138
+
139
+ fs.copyFileSync(origen, destino);
140
+
141
+ // REM-E: verificar integridad post-copia (existe + tamaño > 0)
142
+ const stats = fs.statSync(destino);
143
+ if (stats.size === 0) {
144
+ throw new Error(
145
+ `Hook copiado con tamaño 0: ${destino}. ` +
146
+ `El archivo origen puede estar corrupto: ${origen}`
147
+ );
148
+ }
149
+
150
+ copiados.push(rel);
151
+ }
152
+
153
+ // Loggear en init.log solo paths y nombres — NUNCA secrets
154
+ const logPath = path.join(DIR_NOTIFICACIONES, 'init.log');
155
+ const logLinea = `[${new Date().toISOString()}] hooks copiados a ${hooksDestino}: ${copiados.join(', ')}\n`;
156
+ try { fs.appendFileSync(logPath, logLinea, 'utf8'); } catch (_) {}
157
+
158
+ return { directorio: hooksDestino, archivos: copiados };
159
+ }
160
+
161
+ // ---------------------------------------------------------------------------
162
+ // Helpers internos
163
+ // ---------------------------------------------------------------------------
164
+
165
+ /**
166
+ * Verifica que una ruta está dentro del directorio ~/.claude/notifications/
167
+ * y nunca dentro de un repositorio git.
168
+ *
169
+ * @param {string} ruta - Ruta absoluta a verificar.
170
+ * @returns {boolean} true si la ruta es segura.
171
+ */
172
+ function _esRutaSegura(ruta) {
173
+ const rutaNorm = path.resolve(ruta);
174
+ const dirNorm = path.resolve(DIR_NOTIFICACIONES);
175
+ return rutaNorm.startsWith(dirNorm);
176
+ }
177
+
178
+ /**
179
+ * Oculta un token para logging: muestra primeros 3 chars + ***.
180
+ * NUNCA loggear el token completo.
181
+ *
182
+ * @param {string} token
183
+ * @returns {string}
184
+ */
185
+ function _ocultarToken(token) {
186
+ if (!token || token.length < 4) return '***';
187
+ return token.slice(0, 3) + '*** (long: ' + token.length + ')';
188
+ }
189
+
190
+ /**
191
+ * Genera el contenido del archivo .env con las credenciales.
192
+ * Preserva caracteres especiales (`:`, `=`, `_`) sin escape adicional.
193
+ *
194
+ * @param {string} token
195
+ * @param {string} chatId
196
+ * @returns {string}
197
+ */
198
+ function _generarContenidoEnv(token, chatId) {
199
+ return [
200
+ '# Credenciales de notificaciones Telegram — generado por swl-ses',
201
+ '# Este archivo vive en ~/.claude/notifications/ (fuera del repo, permisos 600)',
202
+ '# El token NO se commitea al repositorio.',
203
+ '# Para revocar: /swl:notificaciones disable',
204
+ '',
205
+ `TELEGRAM_BOT_TOKEN=${token}`,
206
+ `TELEGRAM_CHAT_ID=${chatId}`,
207
+ '',
208
+ ].join('\n');
209
+ }
210
+
211
+ // ---------------------------------------------------------------------------
212
+ // Validación de formato de credenciales Telegram
213
+ // ---------------------------------------------------------------------------
214
+
215
+ /**
216
+ * Regex para validar el formato del token de bot de Telegram.
217
+ * Formato esperado: <digits>:<alphanum~35chars> — entregado por BotFather.
218
+ * Ejemplo válido: 7123456789:AAHkjdhsjkhd-sdfJKH987jklsd_xyzABCDEF
219
+ *
220
+ * NUNCA loggear el valor del token — solo longitud y booleano de validez.
221
+ */
222
+ const RX_TOKEN_TELEGRAM = /^\d+:[A-Za-z0-9_-]{20,}$/;
223
+
224
+ /**
225
+ * Regex para validar el chat_id de Telegram.
226
+ * Puede ser positivo (chat privado) o negativo (grupo/supergrupo).
227
+ */
228
+ const RX_CHAT_ID = /^-?\d+$/;
229
+
230
+ /**
231
+ * Valida el formato del token de Telegram sin loggear su valor.
232
+ *
233
+ * @param {string} token
234
+ * @returns {boolean}
235
+ */
236
+ function _validarFormatoToken(token) {
237
+ if (!token || typeof token !== 'string') return false;
238
+ return RX_TOKEN_TELEGRAM.test(token);
239
+ }
240
+
241
+ /**
242
+ * Valida el formato del chat_id de Telegram.
243
+ *
244
+ * @param {string} chatId
245
+ * @returns {boolean}
246
+ */
247
+ function _validarFormatoChatId(chatId) {
248
+ if (!chatId || typeof chatId !== 'string') return false;
249
+ return RX_CHAT_ID.test(chatId);
250
+ }
251
+
252
+ /**
253
+ * Crea un backup del .env existente antes de sobreescribirlo.
254
+ * El backup se almacena en el mismo directorio con timestamp Unix en el nombre.
255
+ * NUNCA loggea el contenido del .env — solo el path del backup.
256
+ *
257
+ * @param {string} rutaEnv - Ruta del .env a respaldar.
258
+ * @returns {{ ok: boolean, rutaBackup?: string, error?: string }}
259
+ */
260
+ function _respaldarEnvExistente(rutaEnv) {
261
+ try {
262
+ // Solo respaldar si existe y tiene contenido de credenciales válidas
263
+ if (!fs.existsSync(rutaEnv)) return { ok: true };
264
+ const contenido = fs.readFileSync(rutaEnv, 'utf8');
265
+ if (!contenido.includes('TELEGRAM_BOT_TOKEN=')) return { ok: true };
266
+
267
+ const ts = Date.now();
268
+ const rutaBackup = rutaEnv + `.swl-backup-${ts}`;
269
+ // Copiar con permisos 600 en POSIX
270
+ const writeOpts = { encoding: 'utf8' };
271
+ if (process.platform !== 'win32') writeOpts.mode = 0o600;
272
+ fs.writeFileSync(rutaBackup, contenido, writeOpts);
273
+
274
+ // Loggear en init.log solo el path y timestamp — NUNCA el contenido
275
+ const logPath = path.join(DIR_NOTIFICACIONES, 'init.log');
276
+ const logLinea = `[${new Date(ts).toISOString()}] backup creado: ${rutaBackup}\n`;
277
+ try { fs.appendFileSync(logPath, logLinea, 'utf8'); } catch (_) {}
278
+
279
+ return { ok: true, rutaBackup };
280
+ } catch (err) {
281
+ return { ok: false, error: err.message };
282
+ }
283
+ }
284
+
285
+ /**
286
+ * Lee credenciales de forma interactiva usando node:readline.
287
+ * Valida el formato antes de aceptar. Permite hasta MAX_REINTENTOS intentos.
288
+ * Muestra instrucciones cortas con links a BotFather y /quien.
289
+ *
290
+ * @returns {Promise<{ token: string, chatId: string } | null>} null si el
291
+ * usuario cancela o agota los reintentos.
292
+ */
293
+ async function leerCredencialesInteractivo() {
294
+ const MAX_REINTENTOS = 3;
295
+
296
+ const rl = readline.createInterface({
297
+ input: process.stdin,
298
+ output: process.stdout,
299
+ });
300
+
301
+ const preguntar = (msg) => new Promise((resolve) => {
302
+ rl.question(msg, (resp) => resolve(resp.trim()));
303
+ });
304
+
305
+ try {
306
+ console.log('');
307
+ console.log(' Instrucciones para obtener las credenciales:');
308
+ console.log(' 1. Crea un bot en https://t.me/BotFather con /newbot');
309
+ console.log(' 2. Copia el token que te entrega.');
310
+ console.log(' 3. Envía /quien al bot para obtener tu chat_id.');
311
+ console.log('');
312
+ console.log(' El token NO se commitea al repositorio.');
313
+ console.log(' Se almacena en ~/.claude/notifications/.env (fuera del repo, permisos 600).');
314
+ console.log('');
315
+
316
+ // Leer y validar token con retry
317
+ let token = null;
318
+ for (let intento = 1; intento <= MAX_REINTENTOS; intento++) {
319
+ const valorToken = await preguntar(' TELEGRAM_BOT_TOKEN: ');
320
+ if (!valorToken) {
321
+ console.log(' Token vacío — cancelando.');
322
+ return null;
323
+ }
324
+ if (_validarFormatoToken(valorToken)) {
325
+ token = valorToken;
326
+ break;
327
+ }
328
+ const restantes = MAX_REINTENTOS - intento;
329
+ if (restantes > 0) {
330
+ console.log(` Token con formato inválido. Esperado: <digits>:<alphanum> (~46 chars de BotFather)`);
331
+ console.log(` Ejemplo: 7123456789:AAHkjdhsjkhd-sdfJKH987jklsd_xyzABCDEF`);
332
+ console.log(` (longitud=${valorToken.length}, formato_valido=false) — ${restantes} intento(s) restante(s).`);
333
+ } else {
334
+ console.log(` Token con formato inválido (longitud=${valorToken.length}, formato_valido=false). Máximo de reintentos alcanzado.`);
335
+ }
336
+ }
337
+ if (!token) return null;
338
+
339
+ // Leer y validar chatId con retry
340
+ let chatId = null;
341
+ for (let intento = 1; intento <= MAX_REINTENTOS; intento++) {
342
+ const valorChatId = await preguntar(' TELEGRAM_CHAT_ID: ');
343
+ if (!valorChatId) {
344
+ console.log(' Chat ID vacío — cancelando.');
345
+ return null;
346
+ }
347
+ if (_validarFormatoChatId(valorChatId)) {
348
+ chatId = valorChatId;
349
+ break;
350
+ }
351
+ const restantes = MAX_REINTENTOS - intento;
352
+ if (restantes > 0) {
353
+ console.log(` Chat ID con formato inválido. Esperado: número entero positivo o negativo.`);
354
+ console.log(` Ejemplo: -1001234567890 o 123456789`);
355
+ console.log(` (formato_valido=false) — ${restantes} intento(s) restante(s).`);
356
+ } else {
357
+ console.log(` Chat ID con formato inválido. Máximo de reintentos alcanzado.`);
358
+ }
359
+ }
360
+ if (!chatId) return null;
361
+
362
+ return { token, chatId };
363
+ } finally {
364
+ rl.close();
365
+ }
366
+ }
367
+
368
+ /**
369
+ * Genera el JSON de baseline-hooks: la lista de hooks que el hook
370
+ * SessionStart usará para restaurar settings.json si se pierden.
371
+ *
372
+ * SIEMPRE usa paths absolutos a ~/.claude/hooks/ para que los hooks
373
+ * funcionen desde cualquier directorio de trabajo (cwd).
374
+ *
375
+ * @param {string} [hooksGlobalDirOverride] - Para tests: sobreescribir el dir global.
376
+ * @returns {object} Objeto JSON listo para escribir.
377
+ */
378
+ function _generarBaselineHooks(hooksGlobalDirOverride) {
379
+ const hooksDir = hooksGlobalDirOverride || HOOKS_GLOBAL_DIR;
380
+ const baseline = { hooks: {} };
381
+
382
+ for (const h of HOOKS_SWL_NOTIFICACIONES) {
383
+ if (!baseline.hooks[h.evento]) {
384
+ baseline.hooks[h.evento] = [];
385
+ }
386
+ // Evitar duplicar el mismo archivo en el mismo evento
387
+ const yaExiste = baseline.hooks[h.evento].some(
388
+ entry => (entry.hooks || []).some(x => x.command && x.command.includes(h.archivo))
389
+ );
390
+ if (!yaExiste) {
391
+ // REM-B: SIEMPRE path absoluto — nunca path relativo
392
+ const rutaAbs = path.join(hooksDir, h.archivo).replace(/\\/g, '/');
393
+ const comandoNode = `node "${rutaAbs}"`;
394
+ baseline.hooks[h.evento].push({
395
+ matcher: h.matcher,
396
+ hooks: [{ type: 'command', command: comandoNode }],
397
+ });
398
+ }
399
+ }
400
+
401
+ return baseline;
402
+ }
403
+
404
+ // ---------------------------------------------------------------------------
405
+ // API pública
406
+ // ---------------------------------------------------------------------------
407
+
408
+ /**
409
+ * Inicializa la configuración de notificaciones Telegram.
410
+ *
411
+ * Flujo:
412
+ * 1. Verificar feature flag SWL_NOTIFICACIONES_TELEGRAM=0
413
+ * 2. Si no-TTY y sin flags headless → saltar
414
+ * 3. Preguntar opt-in (TTY) o usar credenciales de flags (headless)
415
+ * 4. Si respuesta n → omitir sin tocar nada
416
+ * 5. Obtener credenciales (interactivo o flags)
417
+ * 6. Verificar seguridad de ruta del .env
418
+ * 7. Crear ~/.claude/notifications/ si no existe
419
+ * 8. Escribir .env con modo 600 en POSIX
420
+ * 9. Generar baseline-hooks.json
421
+ * 10. Merge en ~/.claude/settings.json (backup + validación + rollback)
422
+ *
423
+ * @param {object} opciones
424
+ * @param {string} [opciones.token] - Token provisto por flag (modo headless).
425
+ * @param {string} [opciones.chatId] - Chat ID provisto por flag (modo headless).
426
+ * @param {boolean} [opciones.esTty] - Si el proceso corre en TTY.
427
+ * @param {boolean} [opciones.omitir] - Forzar omisión sin preguntar.
428
+ * @param {boolean} [opciones.sobreescribir] - Sobreescribir .env existente sin preguntar.
429
+ * @param {boolean} [opciones.dryRun] - Mostrar qué se haría sin tocar nada.
430
+ * @param {string} [opciones.hooksDir] - Directorio de hooks instalados.
431
+ * @param {string} [opciones._settingsPathOverride] - EXCLUSIVO para tests: ruta de settings.json
432
+ * alternativa. El prefijo _ señala que es parámetro de test — producción NUNCA lo pasa.
433
+ * Evita que los tests escriban al ~/.claude/settings.json REAL del usuario. (Fix Slice 015-B)
434
+ * @param {string} [opciones._envPathOverride] - EXCLUSIVO para tests: ruta de .env alternativa.
435
+ * El prefijo _ señala que es parámetro de test — producción NUNCA lo pasa.
436
+ * Evita que los tests escriban al ~/.claude/notifications/.env REAL del usuario. (Fix v5.12.2)
437
+ * @returns {Promise<{ resultado: string, detalle?: string }>}
438
+ * resultado: 'completado' | 'omitido' | 'error'
439
+ */
440
+ async function init(opciones = {}) {
441
+ const {
442
+ token: tokenFlag = null,
443
+ chatId: chatIdFlag = null,
444
+ esTty: esTty = !!process.stdin.isTTY,
445
+ omitir: omitir = false,
446
+ sobreescribir: sobreescribir = false,
447
+ dryRun: dryRun = false,
448
+ hooksDir: hooksDir = null,
449
+ // Overrides para tests — permiten inyectar directorios temporales
450
+ _hooksOrigenOverride: _hooksOrigenOverride = null,
451
+ _hooksGlobalDirOverride: _hooksGlobalDirOverride = null,
452
+ // EXCLUSIVO para tests: ruta alternativa de settings.json — ver Fix Slice 015-B
453
+ _settingsPathOverride: _settingsPathOverride = null,
454
+ // EXCLUSIVO para tests: ruta alternativa del .env — ver Fix v5.12.2
455
+ _envPathOverride: _envPathOverride = null,
456
+ } = opciones;
457
+
458
+ // Ruta efectiva del .env: override para tests, canónica en producción
459
+ const envPath = _envPathOverride || RUTA_ENV;
460
+
461
+ // Feature flag de emergencia — desactivación sin tocar settings
462
+ if (process.env.SWL_NOTIFICACIONES_TELEGRAM === '0') {
463
+ console.log(' [notificaciones] SWL_NOTIFICACIONES_TELEGRAM=0 — omitido por variable de entorno.');
464
+ return { resultado: 'omitido', detalle: 'feature flag desactivado' };
465
+ }
466
+
467
+ if (omitir) {
468
+ return { resultado: 'omitido', detalle: 'omisión forzada' };
469
+ }
470
+
471
+ const modoHeadless = tokenFlag !== null && chatIdFlag !== null;
472
+
473
+ // En modo no-TTY sin flags headless → saltar automáticamente
474
+ if (!esTty && !modoHeadless) {
475
+ console.log(' [notificaciones] omitido en modo no-interactivo (usar /swl:notificaciones init para activarlo después).');
476
+ return { resultado: 'omitido', detalle: 'modo no-TTY sin flags headless' };
477
+ }
478
+
479
+ let token = tokenFlag;
480
+ let chatId = chatIdFlag;
481
+
482
+ // Modo interactivo (TTY, sin flags headless)
483
+ if (!modoHeadless && esTty) {
484
+ // Detectar .env preexistente
485
+ if (fs.existsSync(envPath) && !sobreescribir) {
486
+ console.log('');
487
+ console.log(' [notificaciones] Ya existe ~/.claude/notifications/.env con credenciales.');
488
+ const rl2 = readline.createInterface({ input: process.stdin, output: process.stdout });
489
+ const reusar = await new Promise((resolve) => {
490
+ rl2.question(' ¿Reusar las credenciales existentes? [S/n] ', (resp) => {
491
+ rl2.close();
492
+ const r = resp.trim().toLowerCase();
493
+ resolve(r === '' || r === 's' || r === 'si' || r === 'sí' || r === 'y');
494
+ });
495
+ });
496
+
497
+ if (reusar) {
498
+ // Solo hacer merge en settings global sin reescribir .env
499
+ // REM-B: pasar override para que mergeHooksGlobales use ruta absoluta
500
+ return _soloMergeSettings(_hooksGlobalDirOverride, dryRun, _settingsPathOverride);
501
+ }
502
+ }
503
+
504
+ // Preguntar opt-in
505
+ console.log('');
506
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
507
+ const habilitarNotif = await new Promise((resolve) => {
508
+ rl.question(
509
+ ' ¿Habilitar notificaciones Telegram al final de cada turno? [s/N] ',
510
+ (resp) => {
511
+ rl.close();
512
+ const r = resp.trim().toLowerCase();
513
+ resolve(r === 's' || r === 'si' || r === 'sí' || r === 'y');
514
+ }
515
+ );
516
+ });
517
+
518
+ if (!habilitarNotif) {
519
+ console.log(' [notificaciones] omitidas.');
520
+ return { resultado: 'omitido', detalle: 'usuario respondió n' };
521
+ }
522
+
523
+ const creds = await leerCredencialesInteractivo();
524
+ if (!creds) {
525
+ return { resultado: 'omitido', detalle: 'usuario canceló ingreso de credenciales' };
526
+ }
527
+ token = creds.token;
528
+ chatId = creds.chatId;
529
+ }
530
+
531
+ // Headless con .env preexistente: reusar silenciosamente si no se pide sobreescribir
532
+ if (modoHeadless && fs.existsSync(envPath) && !sobreescribir) {
533
+ console.log(' [notificaciones] Reusando .env existente (usar --sobreescribir para reemplazar).');
534
+ // REM-B: pasar override para que mergeHooksGlobales use ruta absoluta
535
+ return _soloMergeSettings(_hooksGlobalDirOverride, dryRun, _settingsPathOverride);
536
+ }
537
+
538
+ // Validar que tenemos credenciales
539
+ if (!token || !chatId) {
540
+ return { resultado: 'error', detalle: 'credenciales incompletas (token y chatId son requeridos)' };
541
+ }
542
+
543
+ // Validar formato de token — NUNCA loggear el valor
544
+ if (!_validarFormatoToken(token)) {
545
+ const msg = `Token con formato inválido (longitud=${token.length}, formato_valido=false). Esperado: <digits>:<alphanum> (~46 chars de BotFather).`;
546
+ if (modoHeadless) {
547
+ process.stderr.write(`[notificaciones] Error: ${msg}\n`);
548
+ return { resultado: 'error', detalle: msg };
549
+ }
550
+ return { resultado: 'error', detalle: msg };
551
+ }
552
+
553
+ // Validar formato de chatId
554
+ if (!_validarFormatoChatId(chatId)) {
555
+ const msg = `Chat ID con formato inválido. Esperado: número entero positivo o negativo (ej: -1001234567890).`;
556
+ if (modoHeadless) {
557
+ process.stderr.write(`[notificaciones] Error: ${msg}\n`);
558
+ return { resultado: 'error', detalle: msg };
559
+ }
560
+ return { resultado: 'error', detalle: msg };
561
+ }
562
+
563
+ // Verificar seguridad de ruta — .env NUNCA dentro del repo
564
+ // En modo test con _envPathOverride, saltamos la validación de ruta canónica
565
+ // porque el override apunta a un tmpDir fuera de DIR_NOTIFICACIONES (eso es correcto).
566
+ if (!_envPathOverride && !_esRutaSegura(envPath)) {
567
+ return {
568
+ resultado: 'error',
569
+ detalle: `Ruta insegura detectada: ${envPath} no está dentro de ${DIR_NOTIFICACIONES}. Abortando.`,
570
+ };
571
+ }
572
+
573
+ if (dryRun) {
574
+ console.log('');
575
+ console.log(' [dry-run] Archivos que se crearían/modificarían:');
576
+ console.log(` COPIAR ${HOOKS_A_COPIAR.length} hook(s) → ~/.claude/hooks/`);
577
+ console.log(` CREAR ${envPath} (modo 600 en POSIX)`);
578
+ console.log(` CREAR ${RUTA_BASELINE}`);
579
+ console.log(` MERGE ${RUTA_SETTINGS_GLOBAL} (backup antes de modificar)`);
580
+ console.log('');
581
+ return { resultado: 'omitido', detalle: 'dry-run: sin cambios aplicados' };
582
+ }
583
+
584
+ // REM-A: copiar hooks al directorio global ANTES de registrar en settings.
585
+ // Garantiza que Claude Code siempre encuentre los archivos en ~/.claude/hooks/
586
+ // independientemente del proyecto activo al ejecutarse el Stop hook.
587
+ try {
588
+ const copiaRes = _copiarHooksGlobales(_hooksOrigenOverride, _hooksGlobalDirOverride);
589
+ console.log(` [notificaciones] ${copiaRes.archivos.length} hook(s) copiados a: ${copiaRes.directorio}`);
590
+ } catch (err) {
591
+ console.error(` ! [notificaciones] Error copiando hooks: ${err.message}`);
592
+ return { resultado: 'error', detalle: `No se pudieron copiar los hooks globales: ${err.message}` };
593
+ }
594
+
595
+ // Crear directorio si no existe
596
+ if (!fs.existsSync(DIR_NOTIFICACIONES)) {
597
+ fs.mkdirSync(DIR_NOTIFICACIONES, { recursive: true });
598
+ }
599
+
600
+ // Respaldar .env preexistente ANTES de sobreescribir (REM-2)
601
+ // Protege contra el incidente del 2026-04-24 donde init() sobreescribió
602
+ // credenciales válidas durante una validación manual del flujo.
603
+ const backupRes = _respaldarEnvExistente(envPath);
604
+ if (!backupRes.ok) {
605
+ console.error(` ! [notificaciones] Advertencia: no se pudo crear backup del .env: ${backupRes.error}`);
606
+ // No es fatal — continuamos con la escritura
607
+ } else if (backupRes.rutaBackup) {
608
+ console.log(` [notificaciones] Backup del .env anterior: ${backupRes.rutaBackup}`);
609
+ }
610
+
611
+ // Escribir .env — atómico, con permisos 0o600 aplicados desde la creación (REM-1)
612
+ // Elimina la race condition entre escritura y chmod posterior.
613
+ const contenidoEnv = _generarContenidoEnv(token, chatId);
614
+ atomicWriteSync(envPath, contenidoEnv, 'utf8', { mode: 0o600 });
615
+
616
+ console.log(` [notificaciones] Token configurado: ${_ocultarToken(token)}`);
617
+ console.log(` [notificaciones] Chat ID configurado: ${chatId.slice(0, -4).replace(/./g, '*')}${chatId.slice(-4)}`);
618
+ console.log(` [notificaciones] .env escrito en: ${envPath}`);
619
+
620
+ // Generar baseline-hooks.json — REM-B: siempre usa path absoluto a ~/.claude/hooks/
621
+ const baseline = _generarBaselineHooks(_hooksGlobalDirOverride);
622
+ atomicWriteJSON(RUTA_BASELINE, baseline);
623
+ console.log(` [notificaciones] baseline-hooks.json escrito en: ${RUTA_BASELINE}`);
624
+
625
+ // Merge en settings global
626
+ const mergeResult = await _soloMergeSettings(_hooksGlobalDirOverride, false, _settingsPathOverride);
627
+ if (mergeResult.resultado === 'error') {
628
+ return mergeResult;
629
+ }
630
+
631
+ return {
632
+ resultado: 'completado',
633
+ detalle: `token=${_ocultarToken(token)}, chatId configurado, baseline y settings actualizados`,
634
+ };
635
+ }
636
+
637
+ /**
638
+ * Hace el merge de hooks SWL en settings global sin tocar el .env.
639
+ * Usado cuando el .env ya existe y solo se necesita restaurar la entrada en settings.
640
+ *
641
+ * @param {string|null} hooksGlobalDirOverride - Override de directorio para tests; si null usa HOOKS_GLOBAL_DIR.
642
+ * @param {boolean} dryRun
643
+ * @param {string|null} settingsPathOverride - EXCLUSIVO para tests: sobreescribir ruta del settings.json.
644
+ * En producción, pasar null (usa RUTA_SETTINGS_GLOBAL). El guion bajo en los llamadores
645
+ * señala que es parámetro de test únicamente.
646
+ * @returns {Promise<{ resultado: string, detalle?: string }>}
647
+ */
648
+ async function _soloMergeSettings(hooksGlobalDirOverride, dryRun, settingsPathOverride) {
649
+ const settingsPath = settingsPathOverride || RUTA_SETTINGS_GLOBAL;
650
+
651
+ if (dryRun) {
652
+ console.log(` [dry-run] MERGE ${settingsPath} (backup antes de modificar)`);
653
+ return { resultado: 'omitido', detalle: 'dry-run: merge de settings no aplicado' };
654
+ }
655
+
656
+ // REM-B: siempre usar directorio global absoluto — nunca ruta relativa
657
+ const hooksDir = hooksGlobalDirOverride || HOOKS_GLOBAL_DIR;
658
+
659
+ try {
660
+ const entries = HOOKS_SWL_NOTIFICACIONES.map(h => ({
661
+ evento: h.evento,
662
+ matcher: h.matcher,
663
+ archivo: h.archivo,
664
+ hooksDir: hooksDir,
665
+ }));
666
+
667
+ const res = hooksSettings.mergeHooksGlobales(settingsPath, entries);
668
+ if (res.error) {
669
+ console.error(` ! [notificaciones] Error en merge de settings: ${res.error}`);
670
+ return { resultado: 'error', detalle: res.error };
671
+ }
672
+ const detalleMigrados = res.migrados > 0 ? `, ${res.migrados} migrados a ruta absoluta` : '';
673
+ console.log(` [notificaciones] settings.json global actualizado (${res.agregados} hook(s) agregados${detalleMigrados}, ${res.preservados} preservados).`);
674
+ return { resultado: 'completado', detalle: `settings actualizado (${res.agregados} hooks)` };
675
+ } catch (err) {
676
+ console.error(` ! [notificaciones] Error inesperado en merge de settings: ${err.message}`);
677
+ return { resultado: 'error', detalle: err.message };
678
+ }
679
+ }
680
+
681
+ /**
682
+ * Retorna el estado actual de la configuración de notificaciones.
683
+ * NUNCA incluye el valor del token — solo metadata.
684
+ *
685
+ * @returns {object}
686
+ */
687
+ function status() {
688
+ const envExiste = fs.existsSync(RUTA_ENV);
689
+ let tokenConfigurado = false;
690
+ let chatIdConfigurado = false;
691
+ let chatIdParcial = null;
692
+ let proyectosSilenciados = [];
693
+
694
+ if (envExiste) {
695
+ try {
696
+ const contenido = fs.readFileSync(RUTA_ENV, 'utf8');
697
+ for (const linea of contenido.split(/\r?\n/)) {
698
+ const recortada = linea.trim();
699
+ if (!recortada || recortada.startsWith('#')) continue;
700
+ const posIgual = recortada.indexOf('=');
701
+ if (posIgual === -1) continue;
702
+ const nombre = recortada.slice(0, posIgual).trim();
703
+ const valor = recortada.slice(posIgual + 1).trim().replace(/^["']|["']$/g, '');
704
+ if (nombre === 'TELEGRAM_BOT_TOKEN' && valor) {
705
+ tokenConfigurado = true;
706
+ // NUNCA incluir el token en el retorno
707
+ }
708
+ if (nombre === 'TELEGRAM_CHAT_ID' && valor) {
709
+ chatIdConfigurado = true;
710
+ // Solo últimos 4 dígitos
711
+ chatIdParcial = valor.length > 4
712
+ ? '...' + valor.slice(-4)
713
+ : valor;
714
+ }
715
+ }
716
+ } catch (_) {
717
+ // Si no se puede leer, dejar flags en false
718
+ }
719
+ }
720
+
721
+ // Leer proyectos silenciados
722
+ const rutaMuted = path.join(DIR_NOTIFICACIONES, 'muted.json');
723
+ try {
724
+ if (fs.existsSync(rutaMuted)) {
725
+ const muted = JSON.parse(fs.readFileSync(rutaMuted, 'utf8'));
726
+ if (Array.isArray(muted)) proyectosSilenciados = muted;
727
+ }
728
+ } catch (_) { /* muted.json corrupto — ignorar */ }
729
+
730
+ return {
731
+ envExiste,
732
+ tokenConfigurado,
733
+ chatIdConfigurado,
734
+ chatIdParcial, // últimos 4 dígitos o null
735
+ proyectosSilenciados,
736
+ // Campos reservados para Slices futuros
737
+ botDaemonActivo: null, // Slice 006
738
+ autostartInstalado: null, // Slice 008
739
+ };
740
+ }
741
+
742
+ /**
743
+ * Deshabilita las notificaciones Telegram:
744
+ * - Quita entries SWL de ~/.claude/settings.json (preserva ajenos)
745
+ * - Si conservarEnv=false, elimina el .env
746
+ * - NO detiene el daemon (Slice 006)
747
+ *
748
+ * @param {object} [opciones]
749
+ * @param {boolean} [opciones.confirmar=false] - Si ya se confirmó por UI.
750
+ * @param {boolean} [opciones.conservarEnv=true] - Si conservar el .env.
751
+ * @param {string|null} [opciones._settingsPathOverride] - EXCLUSIVO para tests: ruta alternativa del settings.json.
752
+ * @param {string|null} [opciones._envPathOverride] - EXCLUSIVO para tests: ruta alternativa del .env.
753
+ * El prefijo _ señala que es parámetro de test — producción NUNCA lo pasa. (Fix v5.12.2)
754
+ * @returns {{ resultado: string, detalle?: string }}
755
+ */
756
+ function disable(opciones = {}) {
757
+ const {
758
+ confirmar = false,
759
+ conservarEnv = true,
760
+ _settingsPathOverride = null, // EXCLUSIVO para tests — ver Fix Slice 015-B
761
+ _envPathOverride = null, // EXCLUSIVO para tests — ver Fix v5.12.2
762
+ } = opciones;
763
+
764
+ if (!confirmar) {
765
+ return {
766
+ resultado: 'error',
767
+ detalle: 'Se requiere confirmar (pasar confirmar: true). Ejecuta /swl:notificaciones disable para el flujo interactivo.',
768
+ };
769
+ }
770
+
771
+ const settingsPath = _settingsPathOverride || RUTA_SETTINGS_GLOBAL;
772
+ const envPath = _envPathOverride || RUTA_ENV;
773
+
774
+ // Quitar entries SWL del settings global
775
+ try {
776
+ const res = hooksSettings.desregistrarHooksGlobales(settingsPath);
777
+ console.log(` [notificaciones] ${res.eliminados} hook(s) SWL eliminados de settings.json (${res.preservados} preservados).`);
778
+ } catch (err) {
779
+ console.error(` ! [notificaciones] Error al quitar hooks: ${err.message}`);
780
+ return { resultado: 'error', detalle: err.message };
781
+ }
782
+
783
+ // Eliminar .env si se pide
784
+ if (!conservarEnv && fs.existsSync(envPath)) {
785
+ try {
786
+ fs.unlinkSync(envPath);
787
+ console.log(` [notificaciones] .env eliminado.`);
788
+ } catch (err) {
789
+ console.error(` ! [notificaciones] No se pudo eliminar .env: ${err.message}`);
790
+ }
791
+ }
792
+
793
+ return { resultado: 'completado', detalle: 'hooks removidos de settings global' };
794
+ }
795
+
796
+ // ---------------------------------------------------------------------------
797
+ // Gestión del daemon bidireccional (bot.*)
798
+ // ---------------------------------------------------------------------------
799
+
800
+ /**
801
+ * Lee el PID almacenado en el archivo de PID del daemon.
802
+ * Retorna null si no existe o no es un número válido.
803
+ *
804
+ * @returns {number|null}
805
+ */
806
+ function _leerPid() {
807
+ try {
808
+ if (!fs.existsSync(RUTA_PID_DAEMON)) return null;
809
+ const contenido = fs.readFileSync(RUTA_PID_DAEMON, 'utf8').trim();
810
+ const pid = parseInt(contenido, 10);
811
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
812
+ } catch (_) {
813
+ return null;
814
+ }
815
+ }
816
+
817
+ /**
818
+ * Verifica si un proceso con el PID dado está activo.
819
+ * Usa `process.kill(pid, 0)` que lanza si el proceso no existe.
820
+ *
821
+ * @param {number} pid
822
+ * @returns {boolean}
823
+ */
824
+ function _procesoActivo(pid) {
825
+ if (!pid) return false;
826
+ try {
827
+ process.kill(pid, 0);
828
+ return true;
829
+ } catch (_) {
830
+ return false;
831
+ }
832
+ }
833
+
834
+ /**
835
+ * Retorna si el daemon del bot está activo según el archivo PID.
836
+ *
837
+ * @returns {{ activo: boolean, pid: number|null }}
838
+ */
839
+ function botStatus() {
840
+ const pid = _leerPid();
841
+ const activo = _procesoActivo(pid);
842
+
843
+ // Limpiar PID obsoleto si el proceso ya no existe
844
+ if (pid && !activo) {
845
+ try { fs.unlinkSync(RUTA_PID_DAEMON); } catch (_) {}
846
+ }
847
+
848
+ return { activo, pid: activo ? pid : null };
849
+ }
850
+
851
+ /**
852
+ * Inicia el daemon del bot en background.
853
+ *
854
+ * El daemon se inicia desacoplado del proceso actual (detached: true, stdio: 'ignore')
855
+ * para que sobreviva al cierre del proceso padre. El PID se escribe en bot.pid.
856
+ *
857
+ * @returns {{ ok: boolean, mensaje?: string, error?: string, pid?: number }}
858
+ */
859
+ function botStart() {
860
+ // Verificar que el binario existe
861
+ if (!fs.existsSync(RUTA_BIN_DAEMON)) {
862
+ return { ok: false, error: `El daemon no existe: ${RUTA_BIN_DAEMON}` };
863
+ }
864
+
865
+ // Verificar que no está ya corriendo
866
+ const { activo, pid: pidExistente } = botStatus();
867
+ if (activo) {
868
+ return { ok: true, mensaje: `El bot ya está corriendo (PID ${pidExistente}).` };
869
+ }
870
+
871
+ // Crear directorio de logs si no existe
872
+ try {
873
+ if (!fs.existsSync(DIR_NOTIFICACIONES)) {
874
+ fs.mkdirSync(DIR_NOTIFICACIONES, { recursive: true });
875
+ }
876
+ } catch (err) {
877
+ return { ok: false, error: `No se pudo crear directorio de logs: ${err.message}` };
878
+ }
879
+
880
+ // Iniciar el proceso desacoplado
881
+ let child;
882
+ try {
883
+ const stdoutLog = path.join(DIR_NOTIFICACIONES, 'bot.stdout.log');
884
+ const stderrLog = path.join(DIR_NOTIFICACIONES, 'bot.stderr.log');
885
+ const outFd = fs.openSync(stdoutLog, 'a');
886
+ const errFd = fs.openSync(stderrLog, 'a');
887
+
888
+ const { spawn } = require('node:child_process');
889
+ child = spawn(process.execPath, [RUTA_BIN_DAEMON], {
890
+ detached: true,
891
+ stdio: ['ignore', outFd, errFd],
892
+ });
893
+ child.unref();
894
+ fs.closeSync(outFd);
895
+ fs.closeSync(errFd);
896
+ } catch (err) {
897
+ return { ok: false, error: `No se pudo iniciar el daemon: ${err.message}` };
898
+ }
899
+
900
+ const pid = child.pid;
901
+
902
+ // Escribir el PID en archivo
903
+ try {
904
+ fs.writeFileSync(RUTA_PID_DAEMON, String(pid), 'utf8');
905
+ } catch (err) {
906
+ return { ok: false, error: `Daemon iniciado (PID ${pid}) pero no se pudo escribir bot.pid: ${err.message}` };
907
+ }
908
+
909
+ return { ok: true, mensaje: `Bot iniciado (PID ${pid}).`, pid };
910
+ }
911
+
912
+ /**
913
+ * Detiene el daemon del bot enviando SIGTERM al PID almacenado.
914
+ * Elimina el archivo bot.pid al terminar.
915
+ *
916
+ * @returns {{ ok: boolean, mensaje?: string, error?: string }}
917
+ */
918
+ function botStop() {
919
+ const { activo, pid } = botStatus();
920
+
921
+ if (!activo || !pid) {
922
+ return { ok: true, mensaje: 'El bot no estaba corriendo.' };
923
+ }
924
+
925
+ try {
926
+ process.kill(pid, 'SIGTERM');
927
+ } catch (err) {
928
+ return { ok: false, error: `No se pudo detener el proceso (PID ${pid}): ${err.message}` };
929
+ }
930
+
931
+ // Eliminar el archivo PID
932
+ try {
933
+ if (fs.existsSync(RUTA_PID_DAEMON)) {
934
+ fs.unlinkSync(RUTA_PID_DAEMON);
935
+ }
936
+ } catch (_) {
937
+ // No es fatal si no se puede eliminar el PID
938
+ }
939
+
940
+ return { ok: true, mensaje: `Bot detenido (PID ${pid}).` };
941
+ }
942
+
943
+ /**
944
+ * Reinicia el daemon: detiene si está corriendo y lo vuelve a iniciar.
945
+ *
946
+ * @returns {{ ok: boolean, mensaje?: string, error?: string, pid?: number }}
947
+ */
948
+ function botRestart() {
949
+ const stopRes = botStop();
950
+ if (!stopRes.ok) {
951
+ return stopRes;
952
+ }
953
+
954
+ // Pequeña espera síncrona para que el proceso termine de limpiar recursos
955
+ // Usar un loop de spin corto sin setTimeout (tests deterministas)
956
+ const inicio = Date.now();
957
+ while (Date.now() - inicio < 500) {
958
+ const { activo } = botStatus();
959
+ if (!activo) break;
960
+ }
961
+
962
+ return botStart();
963
+ }
964
+
965
+ /**
966
+ * Despacha la instalación del autostart al módulo del OS correspondiente.
967
+ *
968
+ * @returns {{ ok: boolean, mensaje?: string, error?: string }}
969
+ */
970
+ function botEnableAutostart() {
971
+ const platform = process.platform;
972
+
973
+ if (platform === 'win32') {
974
+ const { install } = require('./autostart-windows');
975
+ return install();
976
+ }
977
+ if (platform === 'linux') {
978
+ const { install } = require('./autostart-linux');
979
+ return install();
980
+ }
981
+ if (platform === 'darwin') {
982
+ const { install } = require('./autostart-macos');
983
+ return install();
984
+ }
985
+
986
+ return {
987
+ ok: false,
988
+ error: `Plataforma no soportada para autostart: ${platform}. Soportadas: win32, linux, darwin.`,
989
+ };
990
+ }
991
+
992
+ /**
993
+ * Despacha la desinstalación del autostart al módulo del OS correspondiente.
994
+ *
995
+ * @returns {{ ok: boolean, mensaje?: string, error?: string }}
996
+ */
997
+ function botDisableAutostart() {
998
+ const platform = process.platform;
999
+
1000
+ if (platform === 'win32') {
1001
+ const { uninstall } = require('./autostart-windows');
1002
+ return uninstall();
1003
+ }
1004
+ if (platform === 'linux') {
1005
+ const { uninstall } = require('./autostart-linux');
1006
+ return uninstall();
1007
+ }
1008
+ if (platform === 'darwin') {
1009
+ const { uninstall } = require('./autostart-macos');
1010
+ return uninstall();
1011
+ }
1012
+
1013
+ return {
1014
+ ok: false,
1015
+ error: `Plataforma no soportada para autostart: ${platform}. Soportadas: win32, linux, darwin.`,
1016
+ };
1017
+ }
1018
+
1019
+ /**
1020
+ * Repara una instalación rota de hooks de Telegram.
1021
+ *
1022
+ * Ejecuta dos acciones correctivas:
1023
+ * 1. Re-copia los 7 archivos de hook a ~/.claude/hooks/ (idempotente).
1024
+ * 2. Llama a _soloMergeSettings() que invoca mergeHooksGlobales con el
1025
+ * mecanismo de migración REM-C: detecta entradas con ruta relativa del
1026
+ * mismo hook y las reemplaza por la ruta absoluta sin duplicar.
1027
+ *
1028
+ * Diseñado para usuarios que activaron notificaciones antes de v5.12.0 y
1029
+ * tienen entradas como `node hooks/notificacion-telegram.js` en settings.json.
1030
+ *
1031
+ * @param {object} opciones
1032
+ * @param {string} [opciones._hooksOrigenOverride] - Override para tests.
1033
+ * @param {string} [opciones._hooksGlobalDirOverride] - Override para tests.
1034
+ * @param {string|null} [opciones._settingsPathOverride] - EXCLUSIVO para tests: ruta alternativa del settings.json.
1035
+ * @param {string|null} [opciones._envPathOverride] - EXCLUSIVO para tests: ruta alternativa del .env.
1036
+ * repair() actualmente no escribe el .env (solo re-copia hooks y merge settings),
1037
+ * pero el parámetro se acepta para uniformidad con init()/disable() y tests futuros.
1038
+ * (Fix v5.12.2)
1039
+ * @returns {Promise<{ resultado: string, detalle?: string }>}
1040
+ */
1041
+ async function repair(opciones = {}) {
1042
+ const {
1043
+ _hooksOrigenOverride: _hooksOrigenOverride = null,
1044
+ _hooksGlobalDirOverride: _hooksGlobalDirOverride = null,
1045
+ _settingsPathOverride: _settingsPathOverride = null, // EXCLUSIVO para tests — ver Fix Slice 015-B
1046
+ // _envPathOverride aceptado por uniformidad — repair no escribe .env actualmente
1047
+ // eslint-disable-next-line no-unused-vars
1048
+ _envPathOverride: _envPathOverride = null, // EXCLUSIVO para tests — ver Fix v5.12.2
1049
+ } = opciones;
1050
+
1051
+ console.log(' [notificaciones] Iniciando reparación de hooks...');
1052
+
1053
+ // Paso 1: re-copiar hooks
1054
+ try {
1055
+ const copiaRes = _copiarHooksGlobales(_hooksOrigenOverride, _hooksGlobalDirOverride);
1056
+ console.log(` [notificaciones] ${copiaRes.archivos.length} hook(s) copiados a: ${copiaRes.directorio}`);
1057
+ } catch (err) {
1058
+ console.error(` ! [notificaciones] Error copiando hooks: ${err.message}`);
1059
+ return { resultado: 'error', detalle: `No se pudieron copiar los hooks: ${err.message}` };
1060
+ }
1061
+
1062
+ // Paso 2: merge/migrar entradas en settings.json (REM-C: relativo → absoluto)
1063
+ const mergeResult = await _soloMergeSettings(_hooksGlobalDirOverride, false, _settingsPathOverride);
1064
+ if (mergeResult.resultado === 'error') {
1065
+ return mergeResult;
1066
+ }
1067
+
1068
+ console.log(' [notificaciones] Reparación completada. Hooks registrados con rutas absolutas.');
1069
+ return {
1070
+ resultado: 'completado',
1071
+ detalle: 'hooks copiados y settings migrados a rutas absolutas',
1072
+ };
1073
+ }
1074
+
1075
+ module.exports = {
1076
+ init,
1077
+ repair,
1078
+ status,
1079
+ disable,
1080
+ leerCredencialesInteractivo,
1081
+ // API del daemon
1082
+ botStart,
1083
+ botStop,
1084
+ botStatus,
1085
+ botRestart,
1086
+ botEnableAutostart,
1087
+ botDisableAutostart,
1088
+ // Exportar para tests
1089
+ _esRutaSegura,
1090
+ _ocultarToken,
1091
+ _generarContenidoEnv,
1092
+ _generarBaselineHooks,
1093
+ _copiarHooksGlobales,
1094
+ _leerPid,
1095
+ _procesoActivo,
1096
+ _validarFormatoToken,
1097
+ _validarFormatoChatId,
1098
+ _respaldarEnvExistente,
1099
+ RUTA_ENV,
1100
+ RUTA_BASELINE,
1101
+ RUTA_SETTINGS_GLOBAL,
1102
+ DIR_NOTIFICACIONES,
1103
+ RUTA_PID_DAEMON,
1104
+ RUTA_BIN_DAEMON,
1105
+ HOOKS_GLOBAL_DIR,
1106
+ HOOKS_A_COPIAR,
1107
+ };