higpertext-cli 0.8.0__py3-none-any.whl

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 (335) hide show
  1. config/adapters_config.json +450 -0
  2. config/antigravity_agent_template.json +31 -0
  3. config/app_config.json +174 -0
  4. config/context_engine.json +33 -0
  5. config/environments/model_defaults.json +5 -0
  6. config/governance/branching_strategy.json +36 -0
  7. config/governance/deployment_gates.json +30 -0
  8. config/governance/guidelines_contract.json +54 -0
  9. config/governance/quality_gates.json +39 -0
  10. config/governance/section_rules.json +22 -0
  11. config/governance/security_guardrails.json +52 -0
  12. config/hooks/README.md +35 -0
  13. config/hooks/custom/test_output_limiter.json +9 -0
  14. config/hooks/global/session_prompt.json +9 -0
  15. config/htx_config.json +24 -0
  16. config/profile_learner.json +18 -0
  17. config/profiles/base_agent.json +40 -0
  18. config/profiles/base_auditor.json +19 -0
  19. config/profiles/base_developer.json +19 -0
  20. config/profiles/base_operator.json +16 -0
  21. config/profiles/global.json +33 -0
  22. config/profiles/software_developer.json +23 -0
  23. config/router_content.json +137 -0
  24. config/semantic_graph.json +66 -0
  25. config/workflows/ado_release_flow.json +38 -0
  26. config/workflows/docs-update.json +33 -0
  27. config/workflows/governance-check.yaml +26 -0
  28. config/workflows/guidelines-sync.json +40 -0
  29. config/workflows/higpertext-build.json +73 -0
  30. config/workflows/higpertext-plan.json +38 -0
  31. config/workflows/higpertext-review.json +41 -0
  32. config/workflows/pr-quality-check.json +56 -0
  33. config/workflows/quality-remediation.json +57 -0
  34. higpertext/__init__.py +18 -0
  35. higpertext/adapters/__init__.py +27 -0
  36. higpertext/adapters/adapter_utils.py +604 -0
  37. higpertext/adapters/claude_adapter/__init__.py +0 -0
  38. higpertext/adapters/claude_adapter/claude_adapter.py +154 -0
  39. higpertext/adapters/copilot_adapter/__init__.py +0 -0
  40. higpertext/adapters/copilot_adapter/copilot_adapter.py +231 -0
  41. higpertext/adapters/gemini_adapter/__init__.py +0 -0
  42. higpertext/adapters/gemini_adapter/gemini_adapter.py +211 -0
  43. higpertext/adapters/llm_formatter.py +46 -0
  44. higpertext/adapters/open_code_adapter/__init__.py +0 -0
  45. higpertext/adapters/open_code_adapter/open_code_adapter.py +480 -0
  46. higpertext/capabilities/capabilities_runner.py +216 -0
  47. higpertext/capabilities/common/agent-builder.json +54 -0
  48. higpertext/capabilities/common/agent-sync.json +34 -0
  49. higpertext/capabilities/common/code-skeletonizer.json +35 -0
  50. higpertext/capabilities/common/commit-report.json +42 -0
  51. higpertext/capabilities/common/context-assembler.json +37 -0
  52. higpertext/capabilities/common/context-budget-report.json +15 -0
  53. higpertext/capabilities/common/dep-manager.json +43 -0
  54. higpertext/capabilities/common/docs-sync.json +14 -0
  55. higpertext/capabilities/common/doctor.json +18 -0
  56. higpertext/capabilities/common/efficiency-meter.json +31 -0
  57. higpertext/capabilities/common/env-catalog.json +13 -0
  58. higpertext/capabilities/common/env-clean.json +14 -0
  59. higpertext/capabilities/common/env-logs.json +16 -0
  60. higpertext/capabilities/common/env-runner.json +23 -0
  61. higpertext/capabilities/common/env-status.json +13 -0
  62. higpertext/capabilities/common/env-stop.json +14 -0
  63. higpertext/capabilities/common/env-template.json +14 -0
  64. higpertext/capabilities/common/error-context-locator.json +23 -0
  65. higpertext/capabilities/common/eval-agent.json +33 -0
  66. higpertext/capabilities/common/file-map.json +17 -0
  67. higpertext/capabilities/common/governance-exception.json +54 -0
  68. higpertext/capabilities/common/graph-query.json +59 -0
  69. higpertext/capabilities/common/graph-rebuild.json +31 -0
  70. higpertext/capabilities/common/graph-visualize.json +37 -0
  71. higpertext/capabilities/common/grep-search.json +176 -0
  72. higpertext/capabilities/common/higpertext-tester.json +25 -0
  73. higpertext/capabilities/common/hook-health.json +19 -0
  74. higpertext/capabilities/common/hook-sync-check.json +19 -0
  75. higpertext/capabilities/common/hooks-manager.json +55 -0
  76. higpertext/capabilities/common/knowledge-asker.json +27 -0
  77. higpertext/capabilities/common/list-rules.json +27 -0
  78. higpertext/capabilities/common/llm-invoke.json +59 -0
  79. higpertext/capabilities/common/load-rules.json +37 -0
  80. higpertext/capabilities/common/memory-manager.json +65 -0
  81. higpertext/capabilities/common/quality-scan.json +21 -0
  82. higpertext/capabilities/common/quality-updater.json +35 -0
  83. higpertext/capabilities/common/rag-index.json +17 -0
  84. higpertext/capabilities/common/report-viewer.json +24 -0
  85. higpertext/capabilities/common/roadmap-report.json +37 -0
  86. higpertext/capabilities/common/scripts/_env_cli.py +65 -0
  87. higpertext/capabilities/common/scripts/agent_builder.py +60 -0
  88. higpertext/capabilities/common/scripts/agent_sync.py +56 -0
  89. higpertext/capabilities/common/scripts/ask_higpertext.py +38 -0
  90. higpertext/capabilities/common/scripts/code_skeletonizer.py +225 -0
  91. higpertext/capabilities/common/scripts/commit_report.py +134 -0
  92. higpertext/capabilities/common/scripts/context_assembler.py +70 -0
  93. higpertext/capabilities/common/scripts/context_budget_report.py +53 -0
  94. higpertext/capabilities/common/scripts/dep_manager.py +81 -0
  95. higpertext/capabilities/common/scripts/docs_sync.py +981 -0
  96. higpertext/capabilities/common/scripts/doctor.py +144 -0
  97. higpertext/capabilities/common/scripts/efficiency_meter.py +83 -0
  98. higpertext/capabilities/common/scripts/env_catalog.py +47 -0
  99. higpertext/capabilities/common/scripts/env_clean.py +30 -0
  100. higpertext/capabilities/common/scripts/env_logs.py +32 -0
  101. higpertext/capabilities/common/scripts/env_runner.py +53 -0
  102. higpertext/capabilities/common/scripts/env_status.py +38 -0
  103. higpertext/capabilities/common/scripts/env_stop.py +30 -0
  104. higpertext/capabilities/common/scripts/env_template.py +73 -0
  105. higpertext/capabilities/common/scripts/error_context_locator.py +138 -0
  106. higpertext/capabilities/common/scripts/eval_agent.py +80 -0
  107. higpertext/capabilities/common/scripts/file_map.py +95 -0
  108. higpertext/capabilities/common/scripts/governance_exception.py +116 -0
  109. higpertext/capabilities/common/scripts/graph_query.py +104 -0
  110. higpertext/capabilities/common/scripts/graph_rebuild.py +107 -0
  111. higpertext/capabilities/common/scripts/graph_visualize.py +76 -0
  112. higpertext/capabilities/common/scripts/grep_search.py +648 -0
  113. higpertext/capabilities/common/scripts/higpertext_tester.py +102 -0
  114. higpertext/capabilities/common/scripts/hook_health.py +149 -0
  115. higpertext/capabilities/common/scripts/hook_sync_check.py +134 -0
  116. higpertext/capabilities/common/scripts/hooks_manager.py +171 -0
  117. higpertext/capabilities/common/scripts/list_rules.py +175 -0
  118. higpertext/capabilities/common/scripts/llm_invoke.py +135 -0
  119. higpertext/capabilities/common/scripts/load_rules.py +379 -0
  120. higpertext/capabilities/common/scripts/memory_manager.py +210 -0
  121. higpertext/capabilities/common/scripts/presentation_engine.py +63 -0
  122. higpertext/capabilities/common/scripts/quality_scan.py +132 -0
  123. higpertext/capabilities/common/scripts/rag_index.py +39 -0
  124. higpertext/capabilities/common/scripts/report_viewer.py +106 -0
  125. higpertext/capabilities/common/scripts/roadmap_report.py +73 -0
  126. higpertext/capabilities/common/scripts/search_router.py +111 -0
  127. higpertext/capabilities/common/scripts/semantic_diff.py +166 -0
  128. higpertext/capabilities/common/scripts/semantic_search.py +43 -0
  129. higpertext/capabilities/common/scripts/session_control.py +136 -0
  130. higpertext/capabilities/common/scripts/smart_read.py +232 -0
  131. higpertext/capabilities/common/scripts/subagent_executor.py +143 -0
  132. higpertext/capabilities/common/scripts/sync_agents.py +353 -0
  133. higpertext/capabilities/common/scripts/task_decomposer.py +78 -0
  134. higpertext/capabilities/common/scripts/telemetry_report.py +36 -0
  135. higpertext/capabilities/common/search-router.json +24 -0
  136. higpertext/capabilities/common/semantic-diff.json +40 -0
  137. higpertext/capabilities/common/semantic-search.json +19 -0
  138. higpertext/capabilities/common/session-clean.json +20 -0
  139. higpertext/capabilities/common/session-start.json +44 -0
  140. higpertext/capabilities/common/smart-read.json +28 -0
  141. higpertext/capabilities/common/subagent-executor.json +25 -0
  142. higpertext/capabilities/common/sync-agents.json +32 -0
  143. higpertext/capabilities/common/task-decomposer.json +37 -0
  144. higpertext/capabilities/common/telemetry-report.json +23 -0
  145. higpertext/capabilities/git/__init__.py +0 -0
  146. higpertext/capabilities/git/committer.json +61 -0
  147. higpertext/capabilities/git/diff.json +33 -0
  148. higpertext/capabilities/git/ls-files.json +44 -0
  149. higpertext/capabilities/git/rm.json +27 -0
  150. higpertext/capabilities/git/scripts/__init__.py +0 -0
  151. higpertext/capabilities/git/scripts/commit_changes.py +1077 -0
  152. higpertext/capabilities/git/scripts/git_diff.py +171 -0
  153. higpertext/capabilities/git/scripts/git_ls_files.py +376 -0
  154. higpertext/capabilities/git/scripts/git_rm.py +62 -0
  155. higpertext/capabilities/security/k8s-auditor.json +33 -0
  156. higpertext/capabilities/security/scripts/k8s_auditor.py +307 -0
  157. higpertext/capabilities/security/scripts/secret_scanner.py +235 -0
  158. higpertext/capabilities/security/secret-scanner.json +32 -0
  159. higpertext/hooks/__init__.py +28 -0
  160. higpertext/hooks/_compat.py +27 -0
  161. higpertext/hooks/hook_tasks/__init__.py +1 -0
  162. higpertext/hooks/hook_tasks/_rules/__init__.py +0 -0
  163. higpertext/hooks/hook_tasks/_rules/bash_rules.py +635 -0
  164. higpertext/hooks/hook_tasks/_rules/context_engine_rule.py +79 -0
  165. higpertext/hooks/hook_tasks/_rules/context_rules.py +199 -0
  166. higpertext/hooks/hook_tasks/_rules/governance_adapter.py +72 -0
  167. higpertext/hooks/hook_tasks/_rules/profile_rules.json +25 -0
  168. higpertext/hooks/hook_tasks/_rules/quality_rules.py +86 -0
  169. higpertext/hooks/hook_tasks/_rules/security_rules.py +214 -0
  170. higpertext/hooks/hook_tasks/_rules/session_rules.py +316 -0
  171. higpertext/hooks/hook_tasks/_rules/telemetry_rules.py +121 -0
  172. higpertext/hooks/hook_tasks/audit_logger_hook.py +28 -0
  173. higpertext/hooks/hook_tasks/hook_bash_guard.py +101 -0
  174. higpertext/hooks/hook_tasks/hook_code_quality.py +48 -0
  175. higpertext/hooks/hook_tasks/hook_context_hint.py +46 -0
  176. higpertext/hooks/hook_tasks/hook_context_manager.py +44 -0
  177. higpertext/hooks/hook_tasks/hook_io.py +122 -0
  178. higpertext/hooks/hook_tasks/hook_loop_guard.py +182 -0
  179. higpertext/hooks/hook_tasks/hook_post_observer.py +54 -0
  180. higpertext/hooks/hook_tasks/hook_read_guard.py +85 -0
  181. higpertext/hooks/hook_tasks/hook_security_guard.py +81 -0
  182. higpertext/hooks/hook_tasks/hook_session_prompt.py +83 -0
  183. higpertext/hooks/hook_tasks/hook_session_stop.py +115 -0
  184. higpertext/hooks/hook_tasks/hook_utils.py +144 -0
  185. higpertext/hooks/hook_tasks/session_guard_hook.py +23 -0
  186. higpertext/hooks/hook_tasks/telemetry_utils.py +176 -0
  187. higpertext/hooks/hook_tasks/test_echo_hook.py +33 -0
  188. higpertext/hooks/hook_tasks/webhook_hook.py +54 -0
  189. higpertext/hooks/hook_tasks/workflow_runner_hook.py +49 -0
  190. higpertext/hooks/hooks_catalog.json +116 -0
  191. higpertext/kernel/__init__.py +63 -0
  192. higpertext/kernel/_compat.py +138 -0
  193. higpertext/kernel/app_config.py +117 -0
  194. higpertext/kernel/application/__init__.py +13 -0
  195. higpertext/kernel/application/agent_registry.py +102 -0
  196. higpertext/kernel/application/capability_manager.py +61 -0
  197. higpertext/kernel/application/commit_reporter.py +247 -0
  198. higpertext/kernel/application/context_builder.py +166 -0
  199. higpertext/kernel/application/context_engine.py +409 -0
  200. higpertext/kernel/application/engine.py +41 -0
  201. higpertext/kernel/application/env_runtime.py +174 -0
  202. higpertext/kernel/application/environment_manager.py +154 -0
  203. higpertext/kernel/application/governance.py +192 -0
  204. higpertext/kernel/application/hook_registry.py +102 -0
  205. higpertext/kernel/application/hook_renderer.py +720 -0
  206. higpertext/kernel/application/ports.py +49 -0
  207. higpertext/kernel/application/profile_learner.py +358 -0
  208. higpertext/kernel/application/profile_service.py +205 -0
  209. higpertext/kernel/application/profile_services.py +6 -0
  210. higpertext/kernel/application/profile_use_cases.py +93 -0
  211. higpertext/kernel/application/rag_service.py +75 -0
  212. higpertext/kernel/application/roadmap_reporter.py +178 -0
  213. higpertext/kernel/application/semantic_engine.py +258 -0
  214. higpertext/kernel/application/session_services.py +33 -0
  215. higpertext/kernel/application/skill_hook_compiler.py +85 -0
  216. higpertext/kernel/application/telemetry.py +326 -0
  217. higpertext/kernel/application/workflow_manager.py +176 -0
  218. higpertext/kernel/config_paths.py +66 -0
  219. higpertext/kernel/domain/__init__.py +12 -0
  220. higpertext/kernel/domain/agent_registry.py +23 -0
  221. higpertext/kernel/domain/commit_reporter.py +155 -0
  222. higpertext/kernel/domain/compilers.py +7 -0
  223. higpertext/kernel/domain/context_engine.py +319 -0
  224. higpertext/kernel/domain/entities.py +51 -0
  225. higpertext/kernel/domain/env_runtime.py +62 -0
  226. higpertext/kernel/domain/governance.py +198 -0
  227. higpertext/kernel/domain/hook_models.py +29 -0
  228. higpertext/kernel/domain/profile_learner.py +186 -0
  229. higpertext/kernel/domain/rag.py +70 -0
  230. higpertext/kernel/domain/repositories.py +8 -0
  231. higpertext/kernel/domain/roadmap_reporter.py +80 -0
  232. higpertext/kernel/domain/semantic_engine.py +107 -0
  233. higpertext/kernel/engine.py +42 -0
  234. higpertext/kernel/htx_resolver.py +69 -0
  235. higpertext/kernel/infrastructure/__init__.py +13 -0
  236. higpertext/kernel/infrastructure/agent_registry.py +40 -0
  237. higpertext/kernel/infrastructure/cache/capability_cache.py +319 -0
  238. higpertext/kernel/infrastructure/capability_helper.py +40 -0
  239. higpertext/kernel/infrastructure/cli/__init__.py +1 -0
  240. higpertext/kernel/infrastructure/cli/agent_commands.py +62 -0
  241. higpertext/kernel/infrastructure/cli/arguments.py +39 -0
  242. higpertext/kernel/infrastructure/cli/capability_command_builder.py +86 -0
  243. higpertext/kernel/infrastructure/cli/capability_task_service.py +234 -0
  244. higpertext/kernel/infrastructure/cli/cli_search.py +234 -0
  245. higpertext/kernel/infrastructure/cli/parameter_contracts.py +83 -0
  246. higpertext/kernel/infrastructure/cli/parser_builder.py +122 -0
  247. higpertext/kernel/infrastructure/cli/profile_commands.py +89 -0
  248. higpertext/kernel/infrastructure/cli/roadmap_commands.py +117 -0
  249. higpertext/kernel/infrastructure/cli/router.py +1110 -0
  250. higpertext/kernel/infrastructure/cli/session_commands.py +36 -0
  251. higpertext/kernel/infrastructure/cli/task_commands.py +23 -0
  252. higpertext/kernel/infrastructure/cli/task_result_reporter.py +56 -0
  253. higpertext/kernel/infrastructure/cli/workflow_commands.py +25 -0
  254. higpertext/kernel/infrastructure/compilers/__init__.py +3 -0
  255. higpertext/kernel/infrastructure/compilers/factory.py +27 -0
  256. higpertext/kernel/infrastructure/compilers/graph_compiler.py +20 -0
  257. higpertext/kernel/infrastructure/compilers/guide_compiler.py +50 -0
  258. higpertext/kernel/infrastructure/compilers/hook_compiler.py +69 -0
  259. higpertext/kernel/infrastructure/compilers/playbook_compiler.py +154 -0
  260. higpertext/kernel/infrastructure/context_engine.py +303 -0
  261. higpertext/kernel/infrastructure/database/local_vector_store.py +99 -0
  262. higpertext/kernel/infrastructure/deployment/__init__.py +1 -0
  263. higpertext/kernel/infrastructure/deployment/resource_deployer.py +283 -0
  264. higpertext/kernel/infrastructure/diagnostics/__init__.py +1 -0
  265. higpertext/kernel/infrastructure/diagnostics/health.py +191 -0
  266. higpertext/kernel/infrastructure/env_runtime.py +227 -0
  267. higpertext/kernel/infrastructure/execution/__init__.py +1 -0
  268. higpertext/kernel/infrastructure/execution/parallel.py +188 -0
  269. higpertext/kernel/infrastructure/execution/resilience.py +155 -0
  270. higpertext/kernel/infrastructure/file_repositories.py +213 -0
  271. higpertext/kernel/infrastructure/governance.py +198 -0
  272. higpertext/kernel/infrastructure/hook_config_loader.py +53 -0
  273. higpertext/kernel/infrastructure/hook_webhook_dispatcher.py +61 -0
  274. higpertext/kernel/infrastructure/hook_workflow_bridge.py +60 -0
  275. higpertext/kernel/infrastructure/llm/__init__.py +6 -0
  276. higpertext/kernel/infrastructure/llm/provider.py +46 -0
  277. higpertext/kernel/infrastructure/llm/providers/__init__.py +0 -0
  278. higpertext/kernel/infrastructure/llm/providers/anthropic_provider.py +94 -0
  279. higpertext/kernel/infrastructure/llm/providers/gemini_embeddings.py +74 -0
  280. higpertext/kernel/infrastructure/llm/providers/gemini_provider.py +101 -0
  281. higpertext/kernel/infrastructure/llm/providers/ollama_provider.py +110 -0
  282. higpertext/kernel/infrastructure/llm/providers/openai_provider.py +98 -0
  283. higpertext/kernel/infrastructure/llm/registry.py +81 -0
  284. higpertext/kernel/infrastructure/logger.py +303 -0
  285. higpertext/kernel/infrastructure/output_store.py +70 -0
  286. higpertext/kernel/infrastructure/parser/__init__.py +1 -0
  287. higpertext/kernel/infrastructure/parser/code_chunker.py +144 -0
  288. higpertext/kernel/infrastructure/parser/language/__init__.py +14 -0
  289. higpertext/kernel/infrastructure/parser/language/base.py +41 -0
  290. higpertext/kernel/infrastructure/parser/language/powershell_parser.py +35 -0
  291. higpertext/kernel/infrastructure/parser/language/python_parser.py +98 -0
  292. higpertext/kernel/infrastructure/parser/language/typescript_parser.py +91 -0
  293. higpertext/kernel/infrastructure/parser/semantic_graph.py +409 -0
  294. higpertext/kernel/infrastructure/presentation/__init__.py +1 -0
  295. higpertext/kernel/infrastructure/presentation/html_renderer.py +137 -0
  296. higpertext/kernel/infrastructure/presentation/markdown_renderer.py +84 -0
  297. higpertext/kernel/infrastructure/presentation/markdown_report_renderer.py +97 -0
  298. higpertext/kernel/infrastructure/profile_store.py +28 -0
  299. higpertext/kernel/infrastructure/semantic_engine.py +289 -0
  300. higpertext/kernel/infrastructure/telemetry_reporter.py +132 -0
  301. higpertext/kernel/infrastructure/validation/__init__.py +1 -0
  302. higpertext/kernel/infrastructure/validation/contract_validator.py +163 -0
  303. higpertext/kernel/pkg_resources.py +38 -0
  304. higpertext/kernel/session_manager.py +319 -0
  305. higpertext/templates/env/generic-shell.yaml +21 -0
  306. higpertext/templates/env/node-vitest.yaml +27 -0
  307. higpertext/templates/env/python-pytest.yaml +29 -0
  308. higpertext/templates/html/commit_body.html +20 -0
  309. higpertext/templates/html/commit_diff.html +4 -0
  310. higpertext/templates/html/commit_index.html +29 -0
  311. higpertext/templates/html/commit_layer.html +11 -0
  312. higpertext/templates/html/commit_shell.html +28 -0
  313. higpertext/templates/html/graph_visualize.html +86 -0
  314. higpertext/templates/html/roadmap_body.html +12 -0
  315. higpertext/templates/html/roadmap_phase.html +5 -0
  316. higpertext/templates/html/roadmap_shell.html +29 -0
  317. higpertext/templates/markdown/commit_report.md +18 -0
  318. higpertext/templates/markdown/efficiency_report.md +12 -0
  319. higpertext/templates/markdown/roadmap_report.md +25 -0
  320. higpertext/templates/skills/best-practices.md +7 -0
  321. higpertext/templates/skills/clean-code.md +8 -0
  322. higpertext/templates/skills/ddd-standards.md +7 -0
  323. higpertext/templates/skills/tdd-practices.md +7 -0
  324. higpertext/templates/subagents/architect.md +7 -0
  325. higpertext/templates/subagents/test-engineer.md +7 -0
  326. higpertext/templates/workflows/build.json +23 -0
  327. higpertext/templates/workflows/compact.json +21 -0
  328. higpertext/templates/workflows/plan.json +59 -0
  329. higpertext/templates/workflows/review.json +26 -0
  330. higpertext/templates/workflows/spec.json +27 -0
  331. higpertext_cli-0.8.0.dist-info/METADATA +35 -0
  332. higpertext_cli-0.8.0.dist-info/RECORD +335 -0
  333. higpertext_cli-0.8.0.dist-info/WHEEL +5 -0
  334. higpertext_cli-0.8.0.dist-info/entry_points.txt +2 -0
  335. higpertext_cli-0.8.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,720 @@
1
+ """HookRenderer — genera config nativa de hooks por asistente."""
2
+
3
+ from __future__ import annotations
4
+ import json
5
+ import platform
6
+ import shutil
7
+ import sys
8
+ from pathlib import Path
9
+
10
+ from higpertext.kernel.application.hook_registry import HookRegistry
11
+ from higpertext.kernel.domain.hook_models import HookDefinition
12
+ from higpertext.kernel.pkg_resources import installed_src_root
13
+ from higpertext.kernel.htx_resolver import _load_config, get_htx_cmd
14
+ from higpertext.kernel.config_paths import PROJECT_ROOT as ENGINE_ROOT
15
+ from higpertext.kernel.infrastructure.logger import get_logger
16
+ from higpertext.kernel.app_config import (
17
+ SETTINGS_FILE as SETTINGS_JSON,
18
+ ASSISTANT_HOOKS_DIRS,
19
+ ASSISTANT_ALIASES,
20
+ )
21
+
22
+ _log = get_logger()
23
+
24
+
25
+ class HookRenderer:
26
+ """Convierte HookDefinitions en configuración nativa de cada asistente.
27
+
28
+ Al renderizar, copia los scripts de hook desde el repo de higpertext a la
29
+ carpeta de configuración del asistente en el proyecto destino
30
+ (p.ej. .claude/hooks/, .gemini/hooks/). Esto permite que los hooks
31
+ se ejecuten con CWD = raíz del proyecto destino, donde htx.py existe.
32
+ """
33
+
34
+ _HOOKS_DIR: dict[str, str] = ASSISTANT_HOOKS_DIRS
35
+ _ALIAS: dict[str, str] = ASSISTANT_ALIASES
36
+
37
+ def __init__(self, project_root: Path) -> None:
38
+ self.project_root = project_root
39
+ self.registry = HookRegistry(project_root)
40
+
41
+ def _shell_matchers(self) -> set[str]:
42
+ """Matchers de shell válidos para el OS actual."""
43
+ matchers = {"Bash"}
44
+ if platform.system() == "Windows":
45
+ matchers.add("PowerShell")
46
+ return matchers
47
+
48
+ def _get_venv_dir(self) -> Path:
49
+ """Retorna la ruta del directorio bin/Scripts de la venv configurada o por defecto."""
50
+ is_windows = platform.system() == "Windows"
51
+ default_dir = ".venv/Scripts" if is_windows else ".venv/bin"
52
+ try:
53
+ cfg = _load_config()
54
+ venv_key = "venv_bin_windows" if is_windows else "venv_bin"
55
+ cfg_venv = cfg.get(venv_key)
56
+ if cfg_venv:
57
+ return Path(cfg_venv).parent
58
+ except Exception as exc:
59
+ _log.warning(f"[hooks] Config de venv no disponible, usando default: {exc}")
60
+ return Path(default_dir)
61
+
62
+ def _htx_cmd(self) -> str:
63
+ """Retorna el ejecutable htx en formato POSIX, priorizando la venv."""
64
+ try:
65
+ cmds = get_htx_cmd(self.project_root)
66
+ if cmds:
67
+ return " ".join(cmds)
68
+ except Exception as exc:
69
+ _log.warning(f"[hooks] Resolución htx falló, usando fallback: {exc}")
70
+
71
+ is_windows = platform.system() == "Windows"
72
+ exe_name = "htx.exe" if is_windows else "htx"
73
+ venv_htx = self.project_root / self._get_venv_dir() / exe_name
74
+ if venv_htx.exists():
75
+ return venv_htx.as_posix()
76
+
77
+ system_htx = shutil.which("htx")
78
+ if system_htx:
79
+ return system_htx
80
+ return self._python_cmd()
81
+
82
+ def _python_cmd(self) -> str:
83
+ """Retorna el ejecutable Python en formato POSIX (Claude hooks corren en Bash)."""
84
+ is_windows = platform.system() == "Windows"
85
+ python_name = "python.exe" if is_windows else "python"
86
+ venv_python = self.project_root / self._get_venv_dir() / python_name
87
+ if venv_python.exists():
88
+ return venv_python.as_posix()
89
+ path = Path(sys.executable)
90
+ return path.as_posix()
91
+
92
+ def _script_path(self, script: str) -> str:
93
+ """Convierte la ruta del script a ruta absoluta POSIX (Claude hooks corren en Bash)."""
94
+ p = Path(script)
95
+ if not p.is_absolute():
96
+ p = self.project_root / p
97
+ return p.as_posix()
98
+
99
+ def render(self, assistant: str, profile: str) -> None:
100
+ hooks = self.registry.get_hooks_for(assistant, profile)
101
+ canonical = self._ALIAS.get(assistant, assistant)
102
+ deployed = self._deploy_scripts(canonical, hooks)
103
+ method = getattr(self, f"_render_{canonical}", None)
104
+ if method:
105
+ method(deployed)
106
+ _log.ok(
107
+ f"[*] Hooks nativos generados para '{assistant}' "
108
+ f"(perfil: {profile}, activos: {len(hooks)})"
109
+ )
110
+
111
+ def _resolve_engine_path(self, rel_path: str) -> Path:
112
+ """Resuelve una ruta relativa a src/ probando dev tree y luego paquete instalado."""
113
+ dev_path = ENGINE_ROOT / rel_path
114
+ if dev_path.exists():
115
+ return dev_path
116
+ src_root = installed_src_root()
117
+ if src_root is not None:
118
+ rel_parts = Path(rel_path).parts
119
+ if rel_parts and rel_parts[0] == "src":
120
+ pkg_path = src_root.joinpath(*rel_parts[1:])
121
+ if pkg_path.exists():
122
+ return pkg_path
123
+ return dev_path
124
+
125
+ def _deploy_scripts(self, canonical: str, hooks: list[HookDefinition]) -> list[HookDefinition]:
126
+ """Copia scripts al directorio de hooks del asistente en project_root.
127
+
128
+ Devuelve nuevas HookDefinitions con script apuntando a la ruta
129
+ relativa destino (usable desde el CWD del proyecto destino).
130
+ También copia hook_utils.py como dependencia compartida.
131
+ """
132
+ hooks_subdir = self._HOOKS_DIR.get(canonical, f".{canonical}/hooks")
133
+ dest_dir = self.project_root / hooks_subdir
134
+ dest_dir.mkdir(parents=True, exist_ok=True)
135
+
136
+ self._deploy_hook_utils(dest_dir)
137
+
138
+ deployed: list[HookDefinition] = []
139
+ for h in hooks:
140
+ src = self._resolve_engine_path(h.script)
141
+ if not src.exists():
142
+ _log.warning(f"[!] Script no encontrado, se omite copia: {src}")
143
+ continue
144
+ dest_file = dest_dir / src.name
145
+ self._safe_copy(src, dest_file, f"script {src.name}")
146
+ deployed.append(
147
+ HookDefinition(
148
+ id=h.id,
149
+ event=h.event,
150
+ script=str(Path(hooks_subdir) / src.name),
151
+ description=h.description,
152
+ matcher=h.matcher,
153
+ timeout=h.timeout,
154
+ enabled=h.enabled,
155
+ assistants=h.assistants,
156
+ profiles=h.profiles,
157
+ capability_id=h.capability_id,
158
+ )
159
+ )
160
+ return deployed
161
+
162
+ def _deploy_hook_utils(self, dest_dir: Path) -> None:
163
+ """Copia dependencias compartidas al directorio destino."""
164
+ hook_tasks = self._resolve_engine_path(
165
+ str(Path("src") / "higpertext" / "hooks" / "hook_tasks")
166
+ )
167
+ self._copy_util_files(hook_tasks, dest_dir)
168
+ self._copy_rule_files(hook_tasks, dest_dir)
169
+
170
+ def _copy_util_files(self, hook_tasks: Path, dest_dir: Path) -> None:
171
+ for dep in ("hook_utils.py", "hook_io.py"):
172
+ src = hook_tasks / dep
173
+ if src.exists():
174
+ dest_file = dest_dir / dep
175
+ self._safe_copy(src, dest_file, f"dependencia {dep}")
176
+
177
+ def _copy_rule_files(self, hook_tasks: Path, dest_dir: Path) -> None:
178
+ rules_src = hook_tasks / "_rules"
179
+ if rules_src.exists():
180
+ rules_dest = dest_dir / "_rules"
181
+ rules_dest.mkdir(parents=True, exist_ok=True)
182
+ for f in rules_src.iterdir():
183
+ if f.is_file():
184
+ dest_file = rules_dest / f.name
185
+ self._safe_copy(f, dest_file, f"regla {f.name}")
186
+
187
+ def _safe_copy(self, src: Path, dest: Path, label: str) -> None:
188
+ try:
189
+ if src.resolve() != dest.resolve():
190
+ shutil.copy2(src, dest)
191
+ except Exception as e:
192
+ _log.warning(f"[!] Error al copiar {label}: {e}")
193
+
194
+ # --- Claude: .claude/settings.json ---
195
+
196
+ def _render_claude(self, hooks: list[HookDefinition]) -> None:
197
+ settings_path = self.project_root / ".claude" / SETTINGS_JSON
198
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
199
+
200
+ existing = self._load_existing_settings(settings_path)
201
+
202
+ all_shell = {"Bash", "PowerShell"}
203
+ active_shell = self._shell_matchers()
204
+
205
+ hook_map: dict[str, list[dict]] = {}
206
+ for h in hooks:
207
+ matcher = h.matcher or ""
208
+ if matcher in all_shell and matcher not in active_shell:
209
+ continue
210
+ entry = {
211
+ "type": "command",
212
+ "command": f"{self._python_cmd()} {self._script_path(h.script)}",
213
+ "timeout": h.timeout,
214
+ "statusMessage": h.description[:60] if h.description else "",
215
+ }
216
+ group = hook_map.setdefault(h.event, [])
217
+ existing_group = next((g for g in group if g.get("matcher") == matcher), None)
218
+ if existing_group:
219
+ existing_group["hooks"].append(entry)
220
+ else:
221
+ group.append({"matcher": matcher, "hooks": [entry]})
222
+
223
+ # SonarQube S7500 compliant: use dict() constructor instead of dict comprehension
224
+ existing["hooks"] = dict(hook_map)
225
+ settings_path.write_text(
226
+ json.dumps(existing, indent=4, ensure_ascii=False), encoding="utf-8"
227
+ )
228
+
229
+ # --- Gemini: .gemini/settings.json ---
230
+
231
+ def _render_gemini(self, hooks: list[HookDefinition]) -> None:
232
+ settings_path = self.project_root / ".gemini" / SETTINGS_JSON
233
+ self._render_gemini_family(hooks, settings_path)
234
+
235
+ def _render_gemini_family(self, hooks: list[HookDefinition], settings_path: Path) -> None:
236
+ settings_path.parent.mkdir(parents=True, exist_ok=True)
237
+ existing = self._load_existing_settings(settings_path)
238
+
239
+ all_shell = {"Bash", "PowerShell"}
240
+ active_shell = self._shell_matchers()
241
+ hook_map: dict[str, list[dict]] = {}
242
+
243
+ for h in hooks:
244
+ if h.matcher in all_shell and h.matcher not in active_shell:
245
+ continue
246
+ self._process_gemini_hook(h, hook_map)
247
+
248
+ existing["hooks"] = hook_map
249
+ settings_path.write_text(
250
+ json.dumps(existing, indent=2, ensure_ascii=False), encoding="utf-8"
251
+ )
252
+
253
+ def _load_existing_settings(self, settings_path: Path) -> dict:
254
+ if settings_path.exists():
255
+ try:
256
+ return json.loads(settings_path.read_text(encoding="utf-8"))
257
+ except (OSError, json.JSONDecodeError):
258
+ pass
259
+ return {}
260
+
261
+ def _process_gemini_hook(self, h: HookDefinition, hook_map: dict[str, list[dict]]) -> None:
262
+ # Generic event mapping -> Gemini CLI
263
+ event_mapping = {
264
+ "PreToolUse": "BeforeTool",
265
+ "PostToolUse": "AfterTool",
266
+ "Stop": "SessionEnd",
267
+ "Notification": "Notification",
268
+ "UserPromptSubmit": "BeforeAgent",
269
+ "SessionStart": "SessionStart",
270
+ "BeforeModel": "BeforeModel",
271
+ "AfterModel": "AfterModel",
272
+ "AfterAgent": "AfterAgent",
273
+ "BeforeToolSelection": "BeforeToolSelection",
274
+ "PreCompact": "PreCompact",
275
+ }
276
+ gemini_event = event_mapping.get(h.event, h.event)
277
+
278
+ # SonarQube python:S3358 compliant: Extract nested conditional expression
279
+ hook_name = "higpertext-hook"
280
+ if h.id:
281
+ hook_name = h.id
282
+ elif h.description:
283
+ hook_name = h.description[:40]
284
+
285
+ entry = {
286
+ "name": hook_name,
287
+ "type": "command",
288
+ "command": f"{self._python_cmd()} {self._script_path(h.script)}",
289
+ "timeout": h.timeout * 1000,
290
+ }
291
+
292
+ matcher = h.matcher
293
+ if matcher in ("Bash", "PowerShell"):
294
+ matcher = "run_shell_command"
295
+ elif not matcher:
296
+ matcher = ".*"
297
+
298
+ group = hook_map.setdefault(gemini_event, [])
299
+ existing_group = next((g for g in group if g.get("matcher") == matcher), None)
300
+ if existing_group:
301
+ existing_group["hooks"].append(entry)
302
+ else:
303
+ group.append({"matcher": matcher, "hooks": [entry]})
304
+
305
+ # --- Antigravity AI: .agents/hooks + opencode.json plugin ---
306
+
307
+ def _render_antigravity(self, hooks: list[HookDefinition]) -> None:
308
+ settings_path = self.project_root / ".agents" / SETTINGS_JSON
309
+ self._render_gemini_family(hooks, settings_path)
310
+
311
+ # --- OpenCode (opencode.ai): .opencode/hooks + plugin JS nativo ---
312
+
313
+ def _render_opencode(self, hooks: list[HookDefinition]) -> None:
314
+ config_path = self.project_root / "opencode.json"
315
+ existing: dict = {}
316
+ if config_path.exists():
317
+ try:
318
+ existing = json.loads(config_path.read_text(encoding="utf-8"))
319
+ except (OSError, json.JSONDecodeError):
320
+ pass
321
+
322
+ plugin_path = self._render_opencode_plugin(hooks)
323
+ plugins = existing.get("plugin", [])
324
+ if not isinstance(plugins, list):
325
+ plugins = []
326
+ if plugin_path not in plugins:
327
+ plugins.append(plugin_path)
328
+ existing["plugin"] = plugins
329
+ existing.pop("hooks", None)
330
+ config_path.write_text(json.dumps(existing, indent=2, ensure_ascii=False), encoding="utf-8")
331
+
332
+ def _render_opencode_plugin(self, hooks: list[HookDefinition]) -> str:
333
+ plugin_dir = self.project_root / ".opencode" / "plugin"
334
+ plugin_dir.mkdir(parents=True, exist_ok=True)
335
+ self._ensure_opencode_plugin_package(plugin_dir.parent)
336
+ plugin_path = plugin_dir / "higpertext-hooks.js"
337
+ all_shell = {"Bash", "PowerShell"}
338
+ active_shell = self._shell_matchers()
339
+ entries = [
340
+ {
341
+ "id": h.id,
342
+ "event": h.event,
343
+ "matcher": h.matcher,
344
+ "script": self._script_path(h.script),
345
+ "timeout": h.timeout,
346
+ }
347
+ for h in hooks
348
+ if h.matcher not in all_shell or h.matcher in active_shell
349
+ ]
350
+ plugin_path.write_text(self._opencode_plugin_source(entries), encoding="utf-8")
351
+ return "./.opencode/plugin/higpertext-hooks.js"
352
+
353
+ def _ensure_opencode_plugin_package(self, opencode_dir: Path) -> None:
354
+ package_path = opencode_dir / "package.json"
355
+ data: dict = {}
356
+ if package_path.exists():
357
+ try:
358
+ data = json.loads(package_path.read_text(encoding="utf-8"))
359
+ except (OSError, json.JSONDecodeError):
360
+ data = {}
361
+ data["type"] = "module"
362
+ package_path.write_text(
363
+ json.dumps(data, indent=2, ensure_ascii=False) + "\n", encoding="utf-8"
364
+ )
365
+
366
+ def _opencode_plugin_source(self, hooks: list[dict]) -> str:
367
+ hooks_json = json.dumps(hooks, ensure_ascii=False, indent=2)
368
+ python_cmd = json.dumps(self._python_cmd())
369
+ python_path = json.dumps(self._resolve_engine_path("src").as_posix())
370
+ htx_path = json.dumps((self.project_root / "htx.py").as_posix())
371
+ return f"""import {{ spawnSync }} from "node:child_process";
372
+ import {{ tool }} from "@opencode-ai/plugin";
373
+
374
+ from higpertext.kernel.infrastructure.logger import get_logger
375
+ _log = get_logger()
376
+
377
+ const hooks = {hooks_json};
378
+ const pythonCmd = {python_cmd};
379
+ const pythonPath = {python_path};
380
+ const htxPath = {htx_path};
381
+
382
+ function canonicalToolName(input) {{
383
+ const raw = input?.tool || input?.toolID || input?.toolName || input?.name || "";
384
+ const map = {{ bash: "Bash", edit: "Edit", write: "Write", read: "Read" }};
385
+ return map[String(raw).toLowerCase()] || String(raw);
386
+ }}
387
+
388
+ function matches(hook, toolName) {{
389
+ if (!hook.matcher) return true;
390
+ return new RegExp(`^(${{hook.matcher}})$`, "i").test(toolName);
391
+ }}
392
+
393
+ function hooksForEvent(eventName) {{
394
+ return hooks.filter((h) => h.event === eventName);
395
+ }}
396
+
397
+ function runHook(hook, payload) {{
398
+ const result = spawnSync(pythonCmd, [hook.script], {{
399
+ input: JSON.stringify(payload),
400
+ encoding: "utf8",
401
+ timeout: hook.timeout * 1000,
402
+ cwd: process.cwd(),
403
+ env: {{
404
+ ...process.env,
405
+ PYTHONPATH: [pythonPath, process.env.PYTHONPATH].filter(Boolean).join(":"),
406
+ }},
407
+ }});
408
+ if (result.error) throw result.error;
409
+ if (result.status !== 0) throw new Error(result.stderr || `Hook ${{hook.id}} failed`);
410
+ if (!result.stdout) return {{ parsed: undefined, context: "", visibleContext: "" }};
411
+ const parsed = JSON.parse(result.stdout);
412
+ const context = parsed.hookSpecificOutput?.additionalContext || parsed.stopReason;
413
+ const visibleContext = context ? formatContextForOpencode(hook, context) : "";
414
+
415
+ if (parsed.continue === false) throw new Error(context || `Hook ${{hook.id}} blocked execution`);
416
+
417
+ return {{ parsed, context, visibleContext }};
418
+ }}
419
+
420
+ function addContextToBashCommand(output, context) {{
421
+ if (context) {{
422
+ const args = output?.args || {{}};
423
+ const command = args.command || args.CommandLine;
424
+ if (!command) return;
425
+
426
+ const banner = `printf '%s\\n' ${{shellQuote(`[HIGPERTEXT]\\n${{context}}`)}}`;
427
+ if (args.command) args.command = `${{banner}}\\n${{command}}`;
428
+ else args.CommandLine = `${{banner}}\\n${{command}}`;
429
+ }}
430
+ }}
431
+
432
+ function addContextToToolOutput(output, context) {{
433
+ if (!context || !output) return;
434
+ const banner = `\\n\\n[HIGPERTEXT]\\n${{context}}`;
435
+ output.output = `${{output.output || ""}}${{banner}}`;
436
+ }}
437
+
438
+ function addContextToCompaction(output, context) {{
439
+ if (!context || !output) return;
440
+ output.context = output.context || [];
441
+ output.context.push(`[HIGPERTEXT]\\n${{context}}`);
442
+ }}
443
+
444
+ function shellQuote(value) {{
445
+ return `'${{String(value).replace(/'/g, `'"'"'`)}}'`;
446
+ }}
447
+
448
+ function formatContextForOpencode(hook, context) {{
449
+ if (process.env.HIGPERTEXT_OPENCODE_VERBOSE === "1") return context;
450
+ if (hook.id !== "hook_session_prompt") return context;
451
+
452
+ // Keep command-specific warnings expanded; they contain required actions.
453
+ if (/ACCI[ÓO]N REQUERIDA|REQUERIDO|Checkpoint|Reiniciada/i.test(context)) {{
454
+ return context;
455
+ }}
456
+
457
+ const lines = String(context).split(/\\r?\\n/).map((line) => line.trim()).filter(Boolean);
458
+
459
+ if (lines.some((line) => line.includes("Sesión activa"))) {{
460
+ const sessionLine = lines.find((line) => line.includes("perfil:")) || "";
461
+ const sessionMatch = sessionLine.match(/│\\s*([^·]+?)\\s*·\\s*perfil:\\s*(.+)$/);
462
+ const sessionId = sessionMatch?.[1]?.trim() || "sesión activa";
463
+ const profile = sessionMatch?.[2]?.trim() || "perfil activo";
464
+ const skills = countListItems(extractValue(lines, "Skills"));
465
+ const subagents = countListItems(extractValue(lines, "Subagentes"));
466
+ const contextEngine = summarizeContextEngine(lines);
467
+
468
+ return [
469
+ `HIGPERTEXT · ${{profile}} · skills:${{skills}} · subagents:${{subagents}} · ${{sessionId}}`,
470
+ contextEngine,
471
+ ].filter(Boolean).join("\\n");
472
+ }}
473
+
474
+ if (lines.some((line) => line.includes("Sin sesión activa"))) {{
475
+ const profile = extractValue(lines, "Perfil") || "perfil no detectado";
476
+ return `HIGPERTEXT · sin sesión activa · ${{profile}} · usa session-start`;
477
+ }}
478
+
479
+ return context;
480
+ }}
481
+
482
+ function extractValue(lines, label) {{
483
+ const line = lines.find((entry) => new RegExp(`^│\\\\s*${{label}}\\\\s*:`, "i").test(entry));
484
+ return line?.replace(new RegExp(`^│\\\\s*${{label}}\\\\s*:\\\\s*`, "i"), "").trim() || "";
485
+ }}
486
+
487
+ function countListItems(value) {{
488
+ if (!value || value === "—") return 0;
489
+ return value.split(",").map((item) => item.trim()).filter(Boolean).length;
490
+ }}
491
+
492
+ function summarizeContextEngine(lines) {{
493
+ if (!lines.some((line) => line.includes("Context Engine"))) return "";
494
+ const tokens = extractValue(lines, "Tokens estimados");
495
+ const symbols = extractValue(lines, "Símbolos");
496
+ const memories = extractValue(lines, "Memorias");
497
+ return `HIGPERTEXT · context engine · tokens:${{tokens || "—"}}`
498
+ + ` · símbolos:${{symbols || 0}} · memorias:${{memories || 0}}`;
499
+ }}
500
+
501
+ function payloadFor(event, input, output) {{
502
+ const toolName = canonicalToolName(input);
503
+ const toolInput =
504
+ event === "PreToolUse"
505
+ ? input?.args || input?.parameters || input?.tool?.args || {{}}
506
+ : output?.args || input?.args || input?.parameters || {{}};
507
+ const toolResponse =
508
+ event === "PostToolUse"
509
+ ? output?.result || output?.output || input?.result || {{}}
510
+ : {{}};
511
+ return {{ event, tool_name: toolName, tool_input: toolInput, tool_response: toolResponse }};
512
+ }}
513
+
514
+ function runHigpertextTask(args, context) {{
515
+ const params = args.parameters || {{}};
516
+ const cliArgs = [htxPath, "task"];
517
+ if (args.no_cache) cliArgs.push("--no-cache");
518
+ cliArgs.push(args.name);
519
+
520
+ for (const [key, value] of Object.entries(params)) {{
521
+ if (value === undefined || value === null || value === false) continue;
522
+ const flag = `--${{key.replace(/_/g, "-")}}`;
523
+ if (value === true) cliArgs.push(flag);
524
+ else cliArgs.push(flag, String(value));
525
+ }}
526
+
527
+ const result = spawnSync(pythonCmd, cliArgs, {{
528
+ encoding: "utf8",
529
+ timeout: (args.timeout_seconds || 120) * 1000,
530
+ cwd: context?.directory || process.cwd(),
531
+ env: {{
532
+ ...process.env,
533
+ PYTHONPATH: [pythonPath, process.env.PYTHONPATH].filter(Boolean).join(":"),
534
+ }},
535
+ }});
536
+
537
+ const output = [
538
+ result.stdout || "",
539
+ result.stderr ? `\\n[stderr]\\n${{result.stderr}}` : "",
540
+ ].join("").trim();
541
+
542
+ if (result.error) throw result.error;
543
+
544
+ return {{
545
+ title: `higpertext task ${{args.name}}`,
546
+ output: output || `[higpertext] exit code ${{result.status}}`,
547
+ metadata: {{
548
+ capability: args.name,
549
+ returncode: result.status,
550
+ command: [pythonCmd, ...cliArgs].join(" "),
551
+ }},
552
+ }};
553
+ }}
554
+
555
+ function runHigpertextWorkflow(args, context) {{
556
+ const params = args.parameters || {{}};
557
+ const cliArgs = [htxPath, "workflow", "run", args.name];
558
+ if (args.no_cache) cliArgs.push("--no-cache");
559
+
560
+ for (const [key, value] of Object.entries(params)) {{
561
+ if (value === undefined || value === null || value === false) continue;
562
+ const flag = `--${{key.replace(/_/g, "-")}}`;
563
+ if (value === true) cliArgs.push(flag);
564
+ else cliArgs.push(flag, String(value));
565
+ }}
566
+
567
+ const result = spawnSync(pythonCmd, cliArgs, {{
568
+ encoding: "utf8",
569
+ timeout: (args.timeout_seconds || 120) * 1000,
570
+ cwd: context?.directory || process.cwd(),
571
+ env: {{
572
+ ...process.env,
573
+ PYTHONPATH: [pythonPath, process.env.PYTHONPATH].filter(Boolean).join(":"),
574
+ }},
575
+ }});
576
+
577
+ const output = [
578
+ result.stdout || "",
579
+ result.stderr ? `\\n[stderr]\\n${{result.stderr}}` : "",
580
+ ].join("").trim();
581
+
582
+ if (result.error) throw result.error;
583
+
584
+ return {{
585
+ title: `higpertext workflow ${{args.name}}`,
586
+ output: output || `[higpertext] exit code ${{result.status}}`,
587
+ metadata: {{
588
+ workflow: args.name,
589
+ returncode: result.status,
590
+ command: [pythonCmd, ...cliArgs].join(" "),
591
+ }},
592
+ }};
593
+ }}
594
+
595
+ function opencodeEventType(input) {{
596
+ return input?.event?.type || input?.type || "";
597
+ }}
598
+
599
+ export default async () => {{
600
+ return {{
601
+ tool: {{
602
+ higpertext_task: tool({{
603
+ description: "Ejecuta una capability de higpertext: python htx.py task <name> con parámetros opcionales.",
604
+ args: {{
605
+ name: tool.schema.string().describe(
606
+ "ID de capability, por ejemplo common.graph-query o common.session-start"),
607
+ parameters: tool.schema.record(tool.schema.string(), tool.schema.any())
608
+ .optional().describe("Parámetros de la capability como objeto {{ parametro: valor }}"),
609
+ no_cache: tool.schema.boolean().optional().describe("Ejecutar con --no-cache"),
610
+ timeout_seconds: tool.schema.number().optional()
611
+ .describe("Timeout de ejecución en segundos; default 120"),
612
+ }},
613
+ execute: async (args, context) => runHigpertextTask(args, context),
614
+ }}),
615
+ higpertext_workflow: tool({{
616
+ description: "Ejecuta un workflow de higpertext: python htx.py workflow run <name>.",
617
+ args: {{
618
+ name: tool.schema.string().describe("ID de workflow: spec, plan, build o review"),
619
+ parameters: tool.schema.record(tool.schema.string(), tool.schema.any())
620
+ .optional().describe("Parámetros del workflow como objeto {{ parametro: valor }}"),
621
+ no_cache: tool.schema.boolean().optional().describe("Ejecutar con --no-cache"),
622
+ timeout_seconds: tool.schema.number().optional()
623
+ .describe("Timeout de ejecución en segundos; default 120"),
624
+ }},
625
+ execute: async (args, context) => runHigpertextWorkflow(args, context),
626
+ }}),
627
+ }},
628
+ "tool.execute.before": async (input, output) => {{
629
+ const toolName = canonicalToolName(input);
630
+ for (const hook of hooksForEvent("PreToolUse").filter((h) => matches(h, toolName))) {{
631
+ const result = runHook(hook, payloadFor("PreToolUse", input, output));
632
+ if (toolName === "Bash") addContextToBashCommand(output, result?.visibleContext);
633
+ }}
634
+ }},
635
+ "tool.execute.after": async (input, output) => {{
636
+ const toolName = canonicalToolName(input);
637
+ if (!["Bash", "Edit", "Write", "Read"].includes(toolName)) return;
638
+ for (const hook of hooksForEvent("PostToolUse").filter((h) => matches(h, toolName))) {{
639
+ const result = runHook(hook, payloadFor("PostToolUse", input, output));
640
+ if (result?.parsed?.hookSpecificOutput?.replacementOutput) {{
641
+ output.output = result.parsed.hookSpecificOutput.replacementOutput;
642
+ }} else {{
643
+ addContextToToolOutput(output, result?.visibleContext);
644
+ }}
645
+ }}
646
+ }},
647
+ "chat.message": async (input, output) => {{
648
+ const text = output?.message?.text || input?.message?.text || input?.text || "";
649
+ for (const hook of hooksForEvent("UserPromptSubmit")) {{
650
+ const result = runHook(hook, {{ event: "UserPromptSubmit", prompt: text }});
651
+ if (result?.visibleContext && output?.message) {{
652
+ output.message.text = `${{output.message.text || text}}\\n\\n${{result.visibleContext}}`;
653
+ }}
654
+ }}
655
+ }},
656
+ "experimental.session.compacting": async (input, output) => {{
657
+ for (const hook of hooksForEvent("PreCompact")) {{
658
+ const result = runHook(hook, {{ event: "PreCompact", sessionID: input?.sessionID }});
659
+ addContextToCompaction(output, result?.visibleContext);
660
+ }}
661
+ }},
662
+ event: async (input) => {{
663
+ if (opencodeEventType(input) !== "session.idle") return;
664
+ for (const hook of hooksForEvent("Stop")) {{
665
+ runHook(hook, {{
666
+ event: "Stop",
667
+ opencode_event: opencodeEventType(input),
668
+ sessionID: input?.event?.properties?.sessionID,
669
+ }});
670
+ }}
671
+ }},
672
+ }};
673
+ }};
674
+ """
675
+
676
+ # --- Copilot: .github/hooks/higpertext-hooks.json ---
677
+
678
+ def _render_copilot(self, hooks: list[HookDefinition]) -> None:
679
+ hooks_dir = self.project_root / ".github" / "hooks"
680
+ hooks_dir.mkdir(parents=True, exist_ok=True)
681
+ config_path = hooks_dir / "higpertext-hooks.json"
682
+
683
+ # Mapeo de eventos genéricos → Copilot CLI
684
+ event_mapping = {
685
+ "PreToolUse": "preToolUse",
686
+ "PostToolUse": "postToolUse",
687
+ "Stop": "sessionEnd",
688
+ "SessionStart": "sessionStart",
689
+ "UserPromptSubmit": "userPromptSubmitted",
690
+ }
691
+
692
+ copilot_hooks: dict[str, list[dict]] = {
693
+ "sessionStart": [],
694
+ "sessionEnd": [],
695
+ "userPromptSubmitted": [],
696
+ "preToolUse": [],
697
+ "postToolUse": [],
698
+ "errorOccurred": [],
699
+ }
700
+
701
+ for h in hooks:
702
+ copilot_event = event_mapping.get(h.event)
703
+ if not copilot_event:
704
+ continue
705
+
706
+ entry = {
707
+ "type": "command",
708
+ "bash": f"{self._python_cmd()} {self._script_path(h.script)}",
709
+ "powershell": f"{self._python_cmd()} {self._script_path(h.script)}",
710
+ "timeoutSec": h.timeout,
711
+ }
712
+ copilot_hooks[copilot_event].append(entry)
713
+
714
+ # Limpiar listas vacías para mantener el JSON limpio
715
+ active_hooks = {k: v for k, v in copilot_hooks.items() if v}
716
+
717
+ output = {"version": 1, "hooks": active_hooks}
718
+
719
+ config_path.write_text(json.dumps(output, indent=2, ensure_ascii=False), encoding="utf-8")
720
+ _log.ok(f"[*] Hooks nativos de Copilot CLI generados en: {config_path}")