@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,1030 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Lógica de comandos del bot de Telegram — módulo testeable.
5
+ *
6
+ * Maneja el dispatch de comandos, la generación de respuestas HTML
7
+ * y la gestión de proyectos silenciados. Las funciones aquí no
8
+ * hacen llamadas de red — reciben el resultado de getUpdates como
9
+ * input y retornan el texto a enviar.
10
+ *
11
+ * @module bin/lib/bot-comandos
12
+ */
13
+
14
+ 'use strict';
15
+
16
+ const fs = require('node:fs');
17
+ const path = require('node:path');
18
+
19
+ const { descubrirProyectos, resolverProyecto } = require('./bot-discovery');
20
+ const { obtenerEstado, obtenerLog, obtenerDiff } = require('./bot-git');
21
+ const { agregarMute, eliminarMute } = require('../../hooks/lib/telegram-config');
22
+
23
+ // Límite de chars antes de truncar respuestas largas
24
+ const LIMITE_RESPUESTA = 3500;
25
+ const SUFIJO_TRUNCADO = '\n… [truncado]';
26
+
27
+ /**
28
+ * Lista de comandos disponibles (whitelist de texto de respuesta para /help).
29
+ */
30
+ const TEXTO_AYUDA = `<b>Comandos disponibles:</b>
31
+
32
+ /proyectos — Lista proyectos detectados
33
+ /estado &lt;proyecto&gt; — Rama, estado y último commit
34
+ /log &lt;proyecto&gt; [N] — Últimos N commits (default 5, máx 25)
35
+ /diff &lt;proyecto&gt; — Archivos modificados (staged y no-staged)
36
+ /ultimo &lt;proyecto&gt; — Último mensaje del asistente
37
+ /resumen &lt;proyecto&gt; — Resumen del proyecto
38
+ /inbox &lt;proyecto&gt; — Comandos pendientes en el inbox del proyecto
39
+ /costo [d] — Tokens y USD del día o últimos d días
40
+ /salud &lt;proyecto&gt; — Score y advertencias del sistema (SALUD.md)
41
+ /silencio &lt;proyecto&gt; — Silenciar notificaciones del proyecto
42
+ /activar &lt;proyecto&gt; — Reactivar notificaciones del proyecto
43
+ /mutes — Lista proyectos silenciados
44
+ /quien — Mostrar tu chat_id`;
45
+
46
+ /**
47
+ * Trunca texto si supera el límite para respuestas de Telegram.
48
+ *
49
+ * @param {string} texto - Texto a truncar.
50
+ * @returns {string} Texto truncado o el original si cabe.
51
+ */
52
+ function truncarRespuesta(texto) {
53
+ const s = String(texto || '');
54
+ if (s.length <= LIMITE_RESPUESTA) return s;
55
+ const corte = LIMITE_RESPUESTA - SUFIJO_TRUNCADO.length;
56
+ return s.slice(0, corte).trimEnd() + SUFIJO_TRUNCADO;
57
+ }
58
+
59
+ /**
60
+ * Escapa caracteres HTML para respuestas en parse_mode HTML de Telegram.
61
+ *
62
+ * @param {string} texto - Texto a escapar.
63
+ * @returns {string} Texto escapado.
64
+ */
65
+ function escaparHtml(texto) {
66
+ return String(texto || '')
67
+ .replace(/&/g, '&amp;')
68
+ .replace(/</g, '&lt;')
69
+ .replace(/>/g, '&gt;');
70
+ }
71
+
72
+ /**
73
+ * Parsea el texto del comando y el argumento de proyecto de un mensaje.
74
+ *
75
+ * @param {string} texto - Texto completo del mensaje (ej: "/estado mi-proyecto").
76
+ * @returns {{ comando: string, argProyecto: string, argExtra: string }}
77
+ */
78
+ function parsearMensaje(texto) {
79
+ const partes = (texto || '').trim().split(/\s+/);
80
+ // El comando puede traer @nombrebot: "/estado@mibot" → limpiar
81
+ const comandoRaw = (partes[0] || '').toLowerCase().split('@')[0];
82
+ const argProyecto = partes[1] || '';
83
+ const argExtra = partes[2] || '';
84
+ return { comando: comandoRaw, argProyecto, argExtra };
85
+ }
86
+
87
+ /**
88
+ * Resuelve un proyecto y devuelve texto de error si no hay match.
89
+ *
90
+ * @param {string} termino - Término de búsqueda.
91
+ * @returns {{ proyecto: object|null, errorTexto: string|null }}
92
+ */
93
+ function _resolverOError(termino) {
94
+ if (!termino) {
95
+ return { proyecto: null, errorTexto: 'Falta el nombre del proyecto. Uso: /comando &lt;proyecto&gt;' };
96
+ }
97
+
98
+ const { proyecto, candidatos, ambiguo } = resolverProyecto(termino);
99
+
100
+ if (ambiguo) {
101
+ const lista = candidatos.map((p) => `• ${escaparHtml(p.nombre)}`).join('\n');
102
+ return {
103
+ proyecto: null,
104
+ errorTexto: `Varios proyectos coinciden con "<b>${escaparHtml(termino)}</b>":\n${lista}\n\nSé más específico.`,
105
+ };
106
+ }
107
+
108
+ if (!proyecto) {
109
+ return {
110
+ proyecto: null,
111
+ errorTexto: `Proyecto no encontrado: <b>${escaparHtml(termino)}</b>`,
112
+ };
113
+ }
114
+
115
+ return { proyecto, errorTexto: null };
116
+ }
117
+
118
+ // -----------------------------------------------------------------
119
+ // Handlers de cada comando
120
+ // -----------------------------------------------------------------
121
+
122
+ /**
123
+ * Maneja /help y /start.
124
+ *
125
+ * @returns {string} Texto de respuesta.
126
+ */
127
+ function cmdHelp() {
128
+ return TEXTO_AYUDA;
129
+ }
130
+
131
+ /**
132
+ * Maneja /proyectos.
133
+ *
134
+ * @returns {string} Lista de proyectos en HTML.
135
+ */
136
+ function cmdProyectos() {
137
+ const proyectos = descubrirProyectos();
138
+ if (proyectos.length === 0) {
139
+ return 'No se encontraron proyectos en <code>~/.claude/projects/</code>.';
140
+ }
141
+ const lista = proyectos
142
+ .map((p) => `• <b>${escaparHtml(p.nombre)}</b>\n <code>${escaparHtml(p.cwd)}</code>`)
143
+ .join('\n');
144
+ return `<b>Proyectos detectados (${proyectos.length}):</b>\n\n${lista}`;
145
+ }
146
+
147
+ /**
148
+ * Maneja /estado &lt;proyecto&gt;.
149
+ *
150
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
151
+ * @returns {string} Texto de respuesta HTML.
152
+ */
153
+ function cmdEstado(argProyecto) {
154
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
155
+ if (errorTexto) return errorTexto;
156
+
157
+ const { ok, rama, estado, ultimoCommit, error } = obtenerEstado(proyecto.cwd);
158
+
159
+ if (!ok) {
160
+ return `Error al consultar git en <b>${escaparHtml(proyecto.nombre)}</b>:\n<code>${escaparHtml(error)}</code>`;
161
+ }
162
+
163
+ return truncarRespuesta(
164
+ `<b>${escaparHtml(proyecto.nombre)}</b>\n\n` +
165
+ `<b>Rama:</b> <code>${escaparHtml(rama)}</code>\n\n` +
166
+ `<b>Estado:</b>\n<pre>${escaparHtml(estado || '(limpio)')}</pre>\n` +
167
+ `<b>Último commit:</b>\n<code>${escaparHtml(ultimoCommit || '(sin commits)')}</code>`
168
+ );
169
+ }
170
+
171
+ /**
172
+ * Maneja /log &lt;proyecto&gt; [N].
173
+ *
174
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
175
+ * @param {string} argN - Número de commits (opcional).
176
+ * @returns {string} Texto de respuesta HTML.
177
+ */
178
+ function cmdLog(argProyecto, argN) {
179
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
180
+ if (errorTexto) return errorTexto;
181
+
182
+ const n = parseInt(argN, 10) || 5;
183
+ const { ok, lineas, error } = obtenerLog(proyecto.cwd, n);
184
+
185
+ if (!ok) {
186
+ return `Error al consultar log en <b>${escaparHtml(proyecto.nombre)}</b>:\n<code>${escaparHtml(error)}</code>`;
187
+ }
188
+
189
+ if (lineas.length === 0) {
190
+ return `<b>${escaparHtml(proyecto.nombre)}</b>: no hay commits.`;
191
+ }
192
+
193
+ const cuerpo = lineas.map((l) => `• <code>${escaparHtml(l)}</code>`).join('\n');
194
+ return truncarRespuesta(`<b>Log de ${escaparHtml(proyecto.nombre)}:</b>\n\n${cuerpo}`);
195
+ }
196
+
197
+ /**
198
+ * Maneja /diff &lt;proyecto&gt;.
199
+ *
200
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
201
+ * @returns {string} Texto de respuesta HTML.
202
+ */
203
+ function cmdDiff(argProyecto) {
204
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
205
+ if (errorTexto) return errorTexto;
206
+
207
+ const { diffNoStaged, diffStaged } = obtenerDiff(proyecto.cwd);
208
+
209
+ let cuerpo = `<b>Diff — ${escaparHtml(proyecto.nombre)}</b>\n\n`;
210
+
211
+ if (diffNoStaged) {
212
+ cuerpo += `<b>Sin stage:</b>\n<pre>${escaparHtml(diffNoStaged)}</pre>\n`;
213
+ } else {
214
+ cuerpo += '<b>Sin stage:</b> (sin cambios)\n';
215
+ }
216
+
217
+ if (diffStaged) {
218
+ cuerpo += `<b>Staged:</b>\n<pre>${escaparHtml(diffStaged)}</pre>`;
219
+ } else {
220
+ cuerpo += '<b>Staged:</b> (sin cambios)';
221
+ }
222
+
223
+ return truncarRespuesta(cuerpo);
224
+ }
225
+
226
+ /**
227
+ * Busca el último mensaje del asistente en el .jsonl más reciente del proyecto.
228
+ *
229
+ * @param {string} cwd - Directorio del proyecto.
230
+ * @returns {string|null} El texto del mensaje o null si no se encuentra.
231
+ */
232
+ function _ultimoMensajeAsistente(cwd) {
233
+ // Buscar .jsonl en el directorio de proyectos Claude Code
234
+ const os = require('node:os');
235
+ const dirProyectos = path.join(os.homedir(), '.claude', 'projects');
236
+
237
+ let jsonlMasReciente = null;
238
+ let mtimeMasReciente = 0;
239
+
240
+ try {
241
+ const dirs = fs.readdirSync(dirProyectos);
242
+ for (const dir of dirs) {
243
+ const dirCompleto = path.join(dirProyectos, dir);
244
+ try {
245
+ if (!fs.statSync(dirCompleto).isDirectory()) continue;
246
+ const archivos = fs.readdirSync(dirCompleto);
247
+ for (const archivo of archivos) {
248
+ if (!archivo.endsWith('.jsonl')) continue;
249
+ const ruta = path.join(dirCompleto, archivo);
250
+ try {
251
+ const stat = fs.statSync(ruta);
252
+ // Verificar que el jsonl pertenece al proyecto buscado
253
+ const contenido = fs.readFileSync(ruta, 'utf8');
254
+ const lineas = contenido.split('\n');
255
+ for (const linea of lineas) {
256
+ try {
257
+ const obj = JSON.parse(linea);
258
+ if (obj && obj.cwd === cwd && stat.mtimeMs > mtimeMasReciente) {
259
+ jsonlMasReciente = ruta;
260
+ mtimeMasReciente = stat.mtimeMs;
261
+ }
262
+ } catch (_) {}
263
+ }
264
+ } catch (_) {}
265
+ }
266
+ } catch (_) {}
267
+ }
268
+ } catch (_) {
269
+ return null;
270
+ }
271
+
272
+ if (!jsonlMasReciente) return null;
273
+
274
+ try {
275
+ const lineas = fs.readFileSync(jsonlMasReciente, 'utf8').split('\n').reverse();
276
+ for (const linea of lineas) {
277
+ try {
278
+ const obj = JSON.parse(linea);
279
+ // Buscar mensajes del asistente en formato de sesión Claude Code
280
+ if (obj && obj.message && obj.message.role === 'assistant') {
281
+ const content = obj.message.content;
282
+ if (typeof content === 'string') return content;
283
+ if (Array.isArray(content)) {
284
+ // Concatenar bloques de texto
285
+ const textos = content
286
+ .filter((b) => b && b.type === 'text')
287
+ .map((b) => b.text)
288
+ .join('\n');
289
+ if (textos) return textos;
290
+ }
291
+ }
292
+ } catch (_) {}
293
+ }
294
+ } catch (_) {}
295
+
296
+ return null;
297
+ }
298
+
299
+ /**
300
+ * Maneja /ultimo &lt;proyecto&gt;.
301
+ *
302
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
303
+ * @returns {string} Texto de respuesta HTML.
304
+ */
305
+ function cmdUltimo(argProyecto) {
306
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
307
+ if (errorTexto) return errorTexto;
308
+
309
+ const msg = _ultimoMensajeAsistente(proyecto.cwd);
310
+
311
+ if (!msg) {
312
+ return `No se encontró ningún mensaje del asistente en <b>${escaparHtml(proyecto.nombre)}</b>.`;
313
+ }
314
+
315
+ return truncarRespuesta(
316
+ `<b>Último mensaje — ${escaparHtml(proyecto.nombre)}:</b>\n\n${escaparHtml(msg)}`
317
+ );
318
+ }
319
+
320
+ /**
321
+ * Maneja /resumen &lt;proyecto&gt;.
322
+ *
323
+ * Busca en orden: .planning/RESUMEN.md, .planning/ESTADO.md, README.md.
324
+ *
325
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
326
+ * @returns {string} Texto de respuesta HTML.
327
+ */
328
+ function cmdResumen(argProyecto) {
329
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
330
+ if (errorTexto) return errorTexto;
331
+
332
+ const candidatos = [
333
+ path.join(proyecto.cwd, '.planning', 'RESUMEN.md'),
334
+ path.join(proyecto.cwd, '.planning', 'ESTADO.md'),
335
+ path.join(proyecto.cwd, 'README.md'),
336
+ ];
337
+
338
+ for (const ruta of candidatos) {
339
+ try {
340
+ if (fs.existsSync(ruta)) {
341
+ const contenido = fs.readFileSync(ruta, 'utf8');
342
+ if (contenido.trim()) {
343
+ const nombreArchivo = path.basename(ruta);
344
+ return truncarRespuesta(
345
+ `<b>Resumen de ${escaparHtml(proyecto.nombre)} (${nombreArchivo}):</b>\n\n${escaparHtml(contenido)}`
346
+ );
347
+ }
348
+ }
349
+ } catch (_) {
350
+ // Siguiente candidato
351
+ }
352
+ }
353
+
354
+ return `No se encontró resumen en <b>${escaparHtml(proyecto.nombre)}</b>.\n` +
355
+ `Archivos buscados: .planning/RESUMEN.md, .planning/ESTADO.md, README.md`;
356
+ }
357
+
358
+ /**
359
+ * Maneja /silencio &lt;proyecto&gt;.
360
+ *
361
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
362
+ * @returns {string} Texto de respuesta.
363
+ */
364
+ function cmdSilencio(argProyecto) {
365
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
366
+ if (errorTexto) return errorTexto;
367
+
368
+ agregarMute(proyecto.nombre);
369
+ return `Proyecto <b>${escaparHtml(proyecto.nombre)}</b> silenciado. Las notificaciones no se enviarán hasta ejecutar /activar.`;
370
+ }
371
+
372
+ /**
373
+ * Maneja /activar &lt;proyecto&gt;.
374
+ *
375
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
376
+ * @returns {string} Texto de respuesta.
377
+ */
378
+ function cmdActivar(argProyecto) {
379
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
380
+ if (errorTexto) return errorTexto;
381
+
382
+ eliminarMute(proyecto.nombre);
383
+ return `Proyecto <b>${escaparHtml(proyecto.nombre)}</b> activado. Volveras a recibir notificaciones.`;
384
+ }
385
+
386
+ /**
387
+ * Maneja /mutes.
388
+ *
389
+ * @returns {string} Lista de proyectos silenciados.
390
+ */
391
+ function cmdMutes() {
392
+ // Leer directamente desde el módulo de config
393
+ const telegramConfig = require('../../hooks/lib/telegram-config');
394
+ const os = require('node:os');
395
+ const rutaMuted = path.join(os.homedir(), '.claude', 'notifications', 'muted.json');
396
+
397
+ let muted = [];
398
+ try {
399
+ if (fs.existsSync(rutaMuted)) {
400
+ const contenido = fs.readFileSync(rutaMuted, 'utf8');
401
+ const datos = JSON.parse(contenido);
402
+ muted = Array.isArray(datos) ? datos : [];
403
+ }
404
+ } catch (_) {
405
+ muted = [];
406
+ }
407
+
408
+ if (muted.length === 0) {
409
+ return 'No hay proyectos silenciados.';
410
+ }
411
+
412
+ const lista = muted.map((p) => `• ${escaparHtml(String(p))}`).join('\n');
413
+ return `<b>Proyectos silenciados (${muted.length}):</b>\n\n${lista}`;
414
+ }
415
+
416
+ /**
417
+ * Valida que el path de un proyecto resuelto no escapa del directorio de proyectos.
418
+ *
419
+ * Previene path traversal: el cwd del proyecto DEBE pertenecer a un proyecto
420
+ * conocido, y el subpath derivado DEBE comenzar con ese cwd.
421
+ *
422
+ * @param {string} cwdProyecto - cwd real del proyecto (desde descubrirProyectos).
423
+ * @param {string} subpath - Subpath a validar dentro del proyecto.
424
+ * @returns {boolean} true si es seguro, false si hay traversal.
425
+ */
426
+ function _esCaminoSeguro(cwdProyecto, subpath) {
427
+ try {
428
+ const base = path.resolve(cwdProyecto);
429
+ const objetivo = path.resolve(subpath);
430
+ return objetivo.startsWith(base + path.sep) || objetivo === base;
431
+ } catch (_) {
432
+ return false;
433
+ }
434
+ }
435
+
436
+ // Directorio de proyectos Claude Code (mismo que bot-discovery)
437
+ const DIR_PROYECTOS_CC = path.join(require('node:os').homedir(), '.claude', 'projects');
438
+
439
+ /**
440
+ * Valida que el cwd de un proyecto conocido no fue manipulado para escapar
441
+ * del directorio de proyectos Claude Code.
442
+ *
443
+ * @param {string} cwd - cwd a verificar.
444
+ * @returns {boolean} true si es seguro.
445
+ */
446
+ function _cwdEsSeguro(cwd) {
447
+ // El cwd debe ser un path real, sin intentar salir con ".."
448
+ // Comprobamos que al resolverlo no introduzca segmentos ".."
449
+ try {
450
+ const resuelto = path.resolve(cwd);
451
+ if (cwd.includes('..') || resuelto !== path.normalize(resuelto)) {
452
+ return false;
453
+ }
454
+
455
+ // REM-5: blacklist de paths sensibles del sistema.
456
+ // Un proyecto Claude Code nunca debería tener cwd en estas rutas.
457
+ const homeDir = require('os').homedir();
458
+ const _pathsSensibles = [
459
+ path.join(homeDir, '.claude'),
460
+ path.join(homeDir, '.ssh'),
461
+ path.join(homeDir, '.aws'),
462
+ path.join(homeDir, '.gnupg'),
463
+ '/etc',
464
+ '/etc/shadow',
465
+ '/etc/passwd',
466
+ 'C:\\Windows',
467
+ 'C:\\Windows\\System32',
468
+ ];
469
+
470
+ const resueltoNorm = resuelto.toLowerCase();
471
+ for (const sensible of _pathsSensibles) {
472
+ const sensibleNorm = sensible.toLowerCase();
473
+ if (resueltoNorm === sensibleNorm || resueltoNorm.startsWith(sensibleNorm + path.sep)) {
474
+ return false;
475
+ }
476
+ }
477
+
478
+ return true;
479
+ } catch (_) {
480
+ return false;
481
+ }
482
+ }
483
+
484
+ // -----------------------------------------------------------------
485
+ // Lazy require de better-sqlite3 (dependencia opt-in)
486
+ // -----------------------------------------------------------------
487
+
488
+ let _dbModulo;
489
+
490
+ /**
491
+ * Carga better-sqlite3 de forma lazy. Devuelve el constructor o null si
492
+ * no está instalado.
493
+ *
494
+ * @returns {Function|null} Constructor de better-sqlite3 o null.
495
+ */
496
+ function _cargarDb() {
497
+ if (_dbModulo === undefined) {
498
+ try {
499
+ // La carga se hace con require() dinámico para permitir que el módulo
500
+ // funcione incluso cuando better-sqlite3 no está instalado (opt-in).
501
+ // eslint-disable-next-line global-require
502
+ _dbModulo = require('better-sqlite3');
503
+ } catch (_) {
504
+ _dbModulo = null;
505
+ }
506
+ }
507
+ return _dbModulo;
508
+ }
509
+
510
+ // -----------------------------------------------------------------
511
+ // Handler /inbox
512
+ // -----------------------------------------------------------------
513
+
514
+ /**
515
+ * Maneja /inbox &lt;proyecto&gt;.
516
+ *
517
+ * Lee <proyecto>/.planning/inbox/cmd-*.json, agrupa pendientes y procesados,
518
+ * devuelve top 10 pendientes con preview de 120 chars del campo `comando`
519
+ * o `mensaje`.
520
+ *
521
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
522
+ * @returns {string} Texto de respuesta HTML.
523
+ */
524
+ function cmdInbox(argProyecto) {
525
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
526
+ if (errorTexto) return errorTexto;
527
+
528
+ if (!_cwdEsSeguro(proyecto.cwd)) {
529
+ console.error(`[bot] cmdInbox: path traversal rechazado para cwd="${proyecto.cwd}"`);
530
+ return 'path traversal rechazado';
531
+ }
532
+
533
+ const dirInbox = path.join(proyecto.cwd, '.planning', 'inbox');
534
+
535
+ try {
536
+ if (!fs.existsSync(dirInbox)) {
537
+ return `<b>${escaparHtml(proyecto.nombre)}</b> no tiene inbox todavía.`;
538
+ }
539
+
540
+ let entradas;
541
+ try {
542
+ entradas = fs.readdirSync(dirInbox);
543
+ } catch (_) {
544
+ return `Error al leer inbox de <b>${escaparHtml(proyecto.nombre)}</b>.`;
545
+ }
546
+
547
+ // Filtrar archivos cmd-*.json
548
+ const archivosCmds = entradas.filter(
549
+ (e) => e.startsWith('cmd-') && e.endsWith('.json')
550
+ );
551
+
552
+ if (archivosCmds.length === 0) {
553
+ return `<b>${escaparHtml(proyecto.nombre)}</b> sin comandos pendientes.`;
554
+ }
555
+
556
+ // Separar pendientes (sin .processed.json) y procesados
557
+ const pendientes = archivosCmds.filter((a) => !a.endsWith('.processed.json'));
558
+ const procesados = archivosCmds.filter((a) => a.endsWith('.processed.json'));
559
+
560
+ if (pendientes.length === 0) {
561
+ return (
562
+ `<b>${escaparHtml(proyecto.nombre)}</b> sin comandos pendientes.\n` +
563
+ `(${procesados.length} procesados)`
564
+ );
565
+ }
566
+
567
+ // Top 10 pendientes, ordenados por nombre (que incluye timestamp)
568
+ const top10 = pendientes.sort().slice(0, 10);
569
+
570
+ const lineas = top10.map((archivo) => {
571
+ const ruta = path.join(dirInbox, archivo);
572
+ let preview = archivo;
573
+ try {
574
+ // Validar path: debe estar dentro del dirInbox
575
+ const rutaResuelta = path.resolve(ruta);
576
+ const baseResuelta = path.resolve(dirInbox);
577
+ if (!rutaResuelta.startsWith(baseResuelta + path.sep) && rutaResuelta !== baseResuelta) {
578
+ return `• <code>${escaparHtml(archivo)}</code> (path inválido)`;
579
+ }
580
+
581
+ const contenido = fs.readFileSync(ruta, 'utf8');
582
+ const obj = JSON.parse(contenido);
583
+ // Intentar campo `comando` o `mensaje`
584
+ const texto = String(obj.comando || obj.mensaje || JSON.stringify(obj) || '');
585
+ // Preview de 120 chars
586
+ preview = texto.length > 120 ? texto.slice(0, 120) + '…' : texto;
587
+ } catch (_) {
588
+ preview = '(sin parsear)';
589
+ }
590
+ return `• <code>${escaparHtml(archivo)}</code>\n ${escaparHtml(preview)}`;
591
+ });
592
+
593
+ const encabezado =
594
+ `<b>Inbox de ${escaparHtml(proyecto.nombre)}:</b> ` +
595
+ `${pendientes.length} pendiente(s), ${procesados.length} procesado(s)`;
596
+
597
+ return truncarRespuesta(encabezado + '\n\n' + lineas.join('\n\n'));
598
+
599
+ } catch (err) {
600
+ console.error(`[bot] cmdInbox error: ${err && err.message}`);
601
+ return `Error al consultar inbox de <b>${escaparHtml(proyecto.nombre)}</b>.`;
602
+ }
603
+ }
604
+
605
+ // -----------------------------------------------------------------
606
+ // Handler /costo
607
+ // -----------------------------------------------------------------
608
+
609
+ /**
610
+ * Maneja /costo [d].
611
+ *
612
+ * Lee ~/.claude/usage.db (SQLite con better-sqlite3) y devuelve tokens y USD
613
+ * del día actual o de los últimos d días.
614
+ *
615
+ * Nota: requiere `better-sqlite3` instalado manualmente en `bin/`:
616
+ * cd bin && npm install --save better-sqlite3
617
+ * Si no está instalado, devuelve mensaje explicativo sin crashear.
618
+ *
619
+ * @param {string} argDias - Número de días (opcional, default 1).
620
+ * @returns {string} Texto de respuesta HTML.
621
+ */
622
+ function cmdCosto(argDias) {
623
+ const BetterSqlite3 = _cargarDb();
624
+ if (!BetterSqlite3) {
625
+ return (
626
+ 'instalación incompleta: falta better-sqlite3\n' +
627
+ '<code>npm install --save better-sqlite3</code> en <code>bin/</code>'
628
+ );
629
+ }
630
+
631
+ const os = require('node:os');
632
+ const rutaDb = path.join(os.homedir(), '.claude', 'usage.db');
633
+
634
+ if (!fs.existsSync(rutaDb)) {
635
+ return 'no hay datos de uso registrados todavía.';
636
+ }
637
+
638
+ // Clamp silencioso: máximo 90 días
639
+ const diasRaw = parseInt(argDias, 10);
640
+ const dias = isNaN(diasRaw) || diasRaw < 1 ? 1 : Math.min(diasRaw, 90);
641
+
642
+ let db;
643
+ try {
644
+ db = new BetterSqlite3(rutaDb, { readonly: true });
645
+
646
+ // Inspeccionar schema: obtener columnas de la tabla usage
647
+ const infoTabla = db.prepare("PRAGMA table_info('usage')").all();
648
+ const columnas = new Set(infoTabla.map((c) => c.name));
649
+
650
+ if (columnas.size === 0) {
651
+ db.close();
652
+ return 'La base de datos usage.db no tiene la tabla <code>usage</code>.';
653
+ }
654
+
655
+ // Construir SELECT dinámico solo con columnas existentes
656
+ // (el schema puede variar entre versiones de Claude Code)
657
+ const colInput = columnas.has('input_tokens') ? 'input_tokens' : null;
658
+ const colOutput = columnas.has('output_tokens') ? 'output_tokens' : null;
659
+ const colCacheCreate = columnas.has('cache_creation_tokens') ? 'cache_creation_tokens' : null;
660
+ const colCacheRead = columnas.has('cache_read_tokens') ? 'cache_read_tokens' : null;
661
+ const colCost = columnas.has('cost_usd') ? 'cost_usd' : null;
662
+ const colModel = columnas.has('model') ? 'model' : null;
663
+ const colTimestamp = columnas.has('timestamp') ? 'timestamp' : null;
664
+
665
+ if (!colTimestamp) {
666
+ db.close();
667
+ return 'La tabla <code>usage</code> no tiene columna <code>timestamp</code> — schema inesperado.';
668
+ }
669
+
670
+ // Calcular límite de tiempo: hace N días en unix epoch (segundos o milisegundos)
671
+ // Claude Code guarda el timestamp como ISO 8601 o epoch entero — probar ambos
672
+ const ahora = Date.now();
673
+ const limiteMs = ahora - dias * 24 * 60 * 60 * 1000;
674
+ const limiteSeg = Math.floor(limiteMs / 1000);
675
+
676
+ // Probar formato del timestamp con la primera fila
677
+ const primeraFila = db.prepare(`SELECT ${colTimestamp} AS ts FROM usage LIMIT 1`).get();
678
+ if (!primeraFila) {
679
+ db.close();
680
+ return `Sin registros de uso en los últimos ${dias} día(s).`;
681
+ }
682
+
683
+ const tsVal = primeraFila.ts;
684
+ // REM-4: usar bind parameters de SQLite en lugar de interpolación de valores numéricos.
685
+ // Aunque los valores son internos (Date.now(), no input de usuario), bind params
686
+ // es la práctica correcta para prevenir inyección si el código evolucionara.
687
+ let whereClause;
688
+ let bindParam;
689
+ if (typeof tsVal === 'number' && tsVal > 1_000_000_000_000) {
690
+ // Epoch en milisegundos
691
+ whereClause = `WHERE ${colTimestamp} >= ?`;
692
+ bindParam = limiteMs;
693
+ } else if (typeof tsVal === 'number') {
694
+ // Epoch en segundos
695
+ whereClause = `WHERE ${colTimestamp} >= ?`;
696
+ bindParam = limiteSeg;
697
+ } else {
698
+ // ISO 8601 string — usar comparación de string (funciona para fechas ISO)
699
+ const fechaIso = new Date(limiteMs).toISOString().slice(0, 10);
700
+ whereClause = `WHERE ${colTimestamp} >= ?`;
701
+ bindParam = fechaIso;
702
+ }
703
+
704
+ // Query agregada con bind parameters (sin interpolación de valores)
705
+ const partesSel = [];
706
+ if (colInput) partesSel.push(`SUM(${colInput}) AS total_input`);
707
+ if (colOutput) partesSel.push(`SUM(${colOutput}) AS total_output`);
708
+ if (colCacheCreate) partesSel.push(`SUM(${colCacheCreate}) AS total_cache_create`);
709
+ if (colCacheRead) partesSel.push(`SUM(${colCacheRead}) AS total_cache_read`);
710
+ if (colCost) partesSel.push(`SUM(${colCost}) AS total_costo`);
711
+ partesSel.push('COUNT(*) AS total_llamadas');
712
+
713
+ const queryTotal = db.prepare(`SELECT ${partesSel.join(', ')} FROM usage ${whereClause}`);
714
+ const totales = queryTotal.get(bindParam);
715
+
716
+ if (!totales || totales.total_llamadas === 0) {
717
+ db.close();
718
+ return `Sin registros de uso en los últimos ${dias} día(s).`;
719
+ }
720
+
721
+ // Breakdown por modelo si hay más de uno
722
+ let breakdownHtml = '';
723
+ if (colModel) {
724
+ const partesMod = [];
725
+ if (colInput) partesMod.push(`SUM(${colInput}) AS tot_in`);
726
+ if (colOutput) partesMod.push(`SUM(${colOutput}) AS tot_out`);
727
+ if (colCost) partesMod.push(`SUM(${colCost}) AS tot_costo`);
728
+ partesMod.push(`${colModel} AS modelo`, 'COUNT(*) AS llamadas');
729
+
730
+ const queryMod = db.prepare(
731
+ `SELECT ${partesMod.join(', ')} FROM usage ${whereClause} GROUP BY ${colModel} ORDER BY tot_costo DESC`
732
+ );
733
+ const filasMod = queryMod.all(bindParam);
734
+
735
+ if (filasMod.length > 1) {
736
+ const lineasMod = filasMod.map((f) => {
737
+ const costoMod = colCost ? ` · $${Number(f.tot_costo || 0).toFixed(4)}` : '';
738
+ const inOut = (colInput && colOutput)
739
+ ? ` (${_fmtTokens(f.tot_in)}/${_fmtTokens(f.tot_out)})`
740
+ : '';
741
+ return ` • <code>${escaparHtml(String(f.modelo || '?'))}</code>${inOut}${costoMod} · ${f.llamadas} llamadas`;
742
+ });
743
+ breakdownHtml = '\n\n<b>Por modelo:</b>\n' + lineasMod.join('\n');
744
+ }
745
+ }
746
+
747
+ db.close();
748
+
749
+ // Construir respuesta — no incluye USD en log (solo en respuesta a Telegram)
750
+ const titulo = dias === 1
751
+ ? '<b>Uso de hoy:</b>'
752
+ : `<b>Uso de los últimos ${dias} días:</b>`;
753
+
754
+ const lineas = [];
755
+ if (colInput) lineas.push(`Tokens entrada: ${_fmtTokens(totales.total_input)}`);
756
+ if (colOutput) lineas.push(`Tokens salida: ${_fmtTokens(totales.total_output)}`);
757
+ if (colCacheCreate) lineas.push(`Cache creación: ${_fmtTokens(totales.total_cache_create)}`);
758
+ if (colCacheRead) lineas.push(`Cache lectura: ${_fmtTokens(totales.total_cache_read)}`);
759
+ if (colCost) lineas.push(`Costo total USD: $${Number(totales.total_costo || 0).toFixed(4)}`);
760
+ lineas.push(`Llamadas: ${totales.total_llamadas}`);
761
+
762
+ return truncarRespuesta(
763
+ titulo + '\n\n<pre>' + lineas.join('\n') + '</pre>' + breakdownHtml
764
+ );
765
+
766
+ } catch (err) {
767
+ if (db) { try { db.close(); } catch (_) {} }
768
+ console.error(`[bot] cmdCosto error: ${err && err.message}`);
769
+ return `Error al leer datos de uso: <code>${escaparHtml(String(err && err.message || err))}</code>`;
770
+ }
771
+ }
772
+
773
+ /**
774
+ * Formatea un número de tokens con separadores de miles.
775
+ *
776
+ * @param {number|null|undefined} n - Número de tokens.
777
+ * @returns {string} Número formateado.
778
+ */
779
+ function _fmtTokens(n) {
780
+ const num = Number(n) || 0;
781
+ return num.toLocaleString('es-MX');
782
+ }
783
+
784
+ // -----------------------------------------------------------------
785
+ // Handler /salud
786
+ // -----------------------------------------------------------------
787
+
788
+ /**
789
+ * Maneja /salud &lt;proyecto&gt;.
790
+ *
791
+ * Busca SALUD.md en <p>/ o <p>/.planning/ y extrae el score y las
792
+ * primeras 3 advertencias encontradas.
793
+ *
794
+ * @param {string} argProyecto - Término de búsqueda del proyecto.
795
+ * @returns {string} Texto de respuesta HTML.
796
+ */
797
+ function cmdSalud(argProyecto) {
798
+ const { proyecto, errorTexto } = _resolverOError(argProyecto);
799
+ if (errorTexto) return errorTexto;
800
+
801
+ if (!_cwdEsSeguro(proyecto.cwd)) {
802
+ console.error(`[bot] cmdSalud: path traversal rechazado para cwd="${proyecto.cwd}"`);
803
+ return 'path traversal rechazado';
804
+ }
805
+
806
+ const candidatos = [
807
+ path.join(proyecto.cwd, 'SALUD.md'),
808
+ path.join(proyecto.cwd, '.planning', 'SALUD.md'),
809
+ ];
810
+
811
+ for (const ruta of candidatos) {
812
+ // Validar que el path candidato no escapa del cwd del proyecto
813
+ if (!_esCaminoSeguro(proyecto.cwd, ruta)) {
814
+ console.error(`[bot] cmdSalud: path traversal rechazado: "${ruta}"`);
815
+ continue;
816
+ }
817
+
818
+ try {
819
+ if (!fs.existsSync(ruta)) continue;
820
+ const contenido = fs.readFileSync(ruta, 'utf8');
821
+ if (!contenido.trim()) continue;
822
+
823
+ return truncarRespuesta(_parsearSalud(proyecto.nombre, contenido));
824
+ } catch (_) {
825
+ // Siguiente candidato
826
+ }
827
+ }
828
+
829
+ return `<b>${escaparHtml(proyecto.nombre)}</b> no tiene SALUD.md (correr /swl:salud)`;
830
+ }
831
+
832
+ /**
833
+ * Parsea el contenido de un SALUD.md y devuelve HTML con score y advertencias.
834
+ *
835
+ * @param {string} nombreProyecto - Nombre del proyecto para el encabezado.
836
+ * @param {string} contenido - Contenido completo del SALUD.md.
837
+ * @returns {string} HTML con score y top 3 advertencias.
838
+ */
839
+ function _parsearSalud(nombreProyecto, contenido) {
840
+ // Extraer score — patrones conocidos:
841
+ // "Score Global: 100% — ..."
842
+ // "Score: 95/100"
843
+ // "## Score Global: 100%"
844
+ const reScore = /score\s*(?:global)?[:\s]+(\d+(?:\.\d+)?)\s*(?:%|\/\s*100)/i;
845
+ const matchScore = contenido.match(reScore);
846
+ const scoreTexto = matchScore ? matchScore[0].trim() : null;
847
+
848
+ // Extraer advertencias: líneas que contengan palabras clave de problema
849
+ // (buscamos en filas de tabla, listas o líneas con palabras indicativas)
850
+ const lineas = contenido.split('\n');
851
+ const advertencias = [];
852
+
853
+ for (const linea of lineas) {
854
+ const l = linea.trim();
855
+ if (!l) continue;
856
+
857
+ // Buscar filas de tabla con problemas (columna Problemas > 0)
858
+ // Formato: | Componente | Score | Estado | Problemas |
859
+ // o líneas con estados de advertencia/error
860
+ if (
861
+ /\|\s*\d+\s*\|/.test(l) && // tiene número en tabla
862
+ /[1-9]\d*/.test(l) && // hay al menos un dígito no-cero
863
+ !/\|\s*0\s*\|/.test(l.replace(/(\d+%)/, '')) // no es solo ceros
864
+ ) {
865
+ const columnas = l.split('|').map((c) => c.trim()).filter(Boolean);
866
+ // La última columna suele ser el count de problemas
867
+ const ultimo = columnas[columnas.length - 1];
868
+ if (ultimo && /^[1-9]\d*$/.test(ultimo)) {
869
+ advertencias.push(l.replace(/\|/g, ' ').replace(/\s+/g, ' ').trim());
870
+ if (advertencias.length >= 3) break;
871
+ }
872
+ }
873
+
874
+ // Líneas de sección "Problemas críticos" o "Advertencias"
875
+ if (advertencias.length < 3 && /(?:problem|advertencia|error|crítico|warning)/i.test(l)) {
876
+ // Solo incluir si parece contenido real (no solo el encabezado de sección)
877
+ if (l.length > 10 && !l.startsWith('#')) {
878
+ advertencias.push(l);
879
+ if (advertencias.length >= 3) break;
880
+ }
881
+ }
882
+ }
883
+
884
+ const encabezado = `<b>Salud de ${escaparHtml(nombreProyecto)}</b>`;
885
+ const scoreHtml = scoreTexto
886
+ ? `\n<b>Score:</b> ${escaparHtml(scoreTexto)}`
887
+ : '\n<i>(score no encontrado)</i>';
888
+
889
+ let advertenciasHtml = '';
890
+ if (advertencias.length > 0) {
891
+ const lista = advertencias
892
+ .slice(0, 3)
893
+ .map((a) => `• ${escaparHtml(a)}`)
894
+ .join('\n');
895
+ advertenciasHtml = '\n\n<b>Advertencias (top 3):</b>\n' + lista;
896
+ } else {
897
+ advertenciasHtml = '\n\n<i>Sin advertencias detectadas.</i>';
898
+ }
899
+
900
+ return encabezado + scoreHtml + advertenciasHtml;
901
+ }
902
+
903
+ /**
904
+ * Maneja /quien.
905
+ *
906
+ * @param {string|number} chatId - ID del chat del remitente.
907
+ * @returns {string} Texto con el chat_id.
908
+ */
909
+ function cmdQuien(chatId) {
910
+ return `Tu chat_id es: <code>${escaparHtml(String(chatId))}</code>`;
911
+ }
912
+
913
+ /**
914
+ * Despacha un mensaje de Telegram al handler correspondiente.
915
+ *
916
+ * @param {object} mensaje - Objeto mensaje de Telegram.
917
+ * @param {string} ownerChatId - Chat ID autorizado (como string).
918
+ * @returns {{ respuesta: string, rechazado: boolean }}
919
+ */
920
+ function despachar(mensaje, ownerChatId) {
921
+ const chatId = String(mensaje?.chat?.id || '');
922
+ const texto = String(mensaje?.text || '').trim();
923
+ const ownerStr = String(ownerChatId || '');
924
+
925
+ // Filtro de acceso
926
+ if (chatId !== ownerStr) {
927
+ return { respuesta: 'Acceso denegado', rechazado: true };
928
+ }
929
+
930
+ if (!texto.startsWith('/')) {
931
+ // Mensaje sin comando: ignorar silenciosamente
932
+ return { respuesta: '', rechazado: false };
933
+ }
934
+
935
+ const { comando, argProyecto, argExtra } = parsearMensaje(texto);
936
+
937
+ let respuesta = '';
938
+
939
+ switch (comando) {
940
+ case '/start':
941
+ case '/help':
942
+ respuesta = cmdHelp();
943
+ break;
944
+
945
+ case '/proyectos':
946
+ respuesta = cmdProyectos();
947
+ break;
948
+
949
+ case '/estado':
950
+ respuesta = cmdEstado(argProyecto);
951
+ break;
952
+
953
+ case '/log':
954
+ respuesta = cmdLog(argProyecto, argExtra);
955
+ break;
956
+
957
+ case '/diff':
958
+ respuesta = cmdDiff(argProyecto);
959
+ break;
960
+
961
+ case '/ultimo':
962
+ respuesta = cmdUltimo(argProyecto);
963
+ break;
964
+
965
+ case '/resumen':
966
+ respuesta = cmdResumen(argProyecto);
967
+ break;
968
+
969
+ case '/inbox':
970
+ respuesta = cmdInbox(argProyecto);
971
+ break;
972
+
973
+ case '/costo':
974
+ // argProyecto realmente es el número de días cuando se usa /costo [d]
975
+ respuesta = cmdCosto(argProyecto);
976
+ break;
977
+
978
+ case '/salud':
979
+ respuesta = cmdSalud(argProyecto);
980
+ break;
981
+
982
+ case '/silencio':
983
+ respuesta = cmdSilencio(argProyecto);
984
+ break;
985
+
986
+ case '/activar':
987
+ respuesta = cmdActivar(argProyecto);
988
+ break;
989
+
990
+ case '/mutes':
991
+ respuesta = cmdMutes();
992
+ break;
993
+
994
+ case '/quien':
995
+ respuesta = cmdQuien(chatId);
996
+ break;
997
+
998
+ default:
999
+ respuesta = `Comando no reconocido: <code>${escaparHtml(comando)}</code>\n\nEscribe /help para ver los comandos disponibles.`;
1000
+ }
1001
+
1002
+ return { respuesta, rechazado: false };
1003
+ }
1004
+
1005
+ module.exports = {
1006
+ despachar,
1007
+ parsearMensaje,
1008
+ truncarRespuesta,
1009
+ escaparHtml,
1010
+ cmdHelp,
1011
+ cmdProyectos,
1012
+ cmdEstado,
1013
+ cmdLog,
1014
+ cmdDiff,
1015
+ cmdUltimo,
1016
+ cmdResumen,
1017
+ cmdInbox,
1018
+ cmdCosto,
1019
+ cmdSalud,
1020
+ cmdSilencio,
1021
+ cmdActivar,
1022
+ cmdMutes,
1023
+ cmdQuien,
1024
+ LIMITE_RESPUESTA,
1025
+ // Exportar para tests
1026
+ _parsearSalud,
1027
+ _esCaminoSeguro,
1028
+ _cwdEsSeguro,
1029
+ _cargarDb,
1030
+ };