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,86 @@
1
+ """Construcción de comandos de subproceso para capacidades."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import shutil
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
10
+
11
+
12
+ def extra_args(params: dict, prefix: str = "--") -> list[str]:
13
+ return [arg for key, value in params.items() for arg in [f"{prefix}{key}", str(value)]]
14
+
15
+
16
+ def resolve_entrypoint(
17
+ entrypoint: str,
18
+ pkg_data_dir: Path,
19
+ workspace_root: Path,
20
+ base_dir: str = "",
21
+ ) -> Path:
22
+ """Resuelve el path del entrypoint manteniendo prioridad legacy."""
23
+ ep = Path(entrypoint)
24
+ if ep.is_absolute() and ep.exists():
25
+ return ep
26
+ if base_dir:
27
+ candidate = Path(base_dir).parent.parent / entrypoint
28
+ if candidate.exists():
29
+ return candidate
30
+ higpertext_relative = workspace_root / WORKSPACE_DIR_NAME / entrypoint
31
+ if higpertext_relative.exists():
32
+ return higpertext_relative
33
+ cwd_relative = workspace_root / entrypoint
34
+ if cwd_relative.exists():
35
+ return cwd_relative
36
+ return pkg_data_dir / entrypoint
37
+
38
+
39
+ def build_command(
40
+ entrypoint: str,
41
+ language: str,
42
+ params: dict,
43
+ root_dir: Path,
44
+ pkg_data_dir: Path,
45
+ workspace_root: Path,
46
+ base_dir: str = "",
47
+ cap_id: str = "",
48
+ ) -> list[str]:
49
+ """Construye la lista de argumentos del subproceso."""
50
+ args = extra_args(params)
51
+ if language == "powershell":
52
+ path_to_script = resolve_entrypoint(entrypoint, pkg_data_dir, workspace_root, base_dir)
53
+ return build_powershell_command(path_to_script, params)
54
+ if language == "cli":
55
+ return entrypoint.split() + args
56
+ if language == "bash":
57
+ path_to_script = resolve_entrypoint(entrypoint, pkg_data_dir, workspace_root, base_dir)
58
+ script = entrypoint if Path(entrypoint).is_absolute() else str(path_to_script)
59
+ return ["bash", script] + args
60
+
61
+ htx_bin = _resolve_htx_bin(root_dir, pkg_data_dir)
62
+ runner = pkg_data_dir / "higpertext" / "capabilities" / "capabilities_runner.py"
63
+ if cap_id and runner.exists() and entrypoint.startswith("capabilities/"):
64
+ if Path(htx_bin).exists():
65
+ return [htx_bin, "task", cap_id] + args
66
+ return [sys.executable, str(runner), cap_id] + args
67
+ path_to_script = resolve_entrypoint(entrypoint, pkg_data_dir, workspace_root, base_dir)
68
+ return [sys.executable, str(path_to_script)] + args
69
+
70
+
71
+ def build_powershell_command(path_to_script: Path, params: dict) -> list[str]:
72
+ pwsh = "powershell" if shutil.which("powershell") else "pwsh"
73
+ return [
74
+ pwsh,
75
+ "-ExecutionPolicy",
76
+ "Bypass",
77
+ "-File",
78
+ str(path_to_script),
79
+ ] + extra_args(params, prefix="-")
80
+
81
+
82
+ def _resolve_htx_bin(root_dir: Path, pkg_data_dir: Path) -> str:
83
+ _ = root_dir
84
+ venv_htx = pkg_data_dir.parents[1] / ".venv" / "bin" / "htx"
85
+
86
+ return str(venv_htx) if venv_htx.exists() else (shutil.which("htx") or str(venv_htx))
@@ -0,0 +1,234 @@
1
+
2
+ """Servicio de infraestructura para ejecutar capacidades desde el CLI."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import os
8
+ # Runner central usa listas de argumentos y shell=False.
9
+ import subprocess # nosec B404
10
+ from pathlib import Path
11
+
12
+ from higpertext.kernel.infrastructure.cli.capability_command_builder import build_command
13
+ from higpertext.kernel.infrastructure.cli.task_result_reporter import (
14
+ build_memory_notes,
15
+ report_task_result,
16
+ )
17
+
18
+
19
+ class CapabilityTaskService:
20
+ """Resuelve, ejecuta y reporta capacidades técnicas."""
21
+
22
+ def __init__(self, engine, root_dir: Path, pkg_data_dir: Path, logger, dep) -> None:
23
+ self.engine = engine
24
+ self.root_dir = root_dir
25
+ self.pkg_data_dir = pkg_data_dir
26
+ self.logger = logger
27
+ self.dep = dep
28
+
29
+ def resolve_from_profiles(self, task_name: str, profiles: list[str]) -> tuple[dict | None, str | None]:
30
+ for profile_name in profiles:
31
+ ctx = self.engine.get_agent_context(profile_name)
32
+ cap = next(
33
+ (c for c in ctx["active_capabilities"] if c["id"].endswith(task_name)),
34
+ None,
35
+ )
36
+ if cap:
37
+ return cap, profile_name
38
+ return None, None
39
+
40
+ def resolve_custom_capability(self, task_name: str) -> tuple[dict | None, str | None]:
41
+ try:
42
+ cap = self.engine.capabilities.load_capability(task_name)
43
+ custom_dir = self.engine.capabilities._get_custom_capabilities_dir()
44
+ cap_file = next(
45
+ (
46
+ f
47
+ for f in self.engine.capabilities._get_all_capability_files()
48
+ if f.stem.endswith(task_name)
49
+ ),
50
+ None,
51
+ )
52
+ if cap_file and custom_dir in cap_file.parents:
53
+ return cap, "custom"
54
+ except (OSError, StopIteration, AttributeError): # nosec B110
55
+ pass
56
+ return None, None
57
+
58
+ def resolve_capability(self, task_name: str, active_profile: str) -> tuple[dict | None, str | None]:
59
+ profiles = [active_profile, "global"] if active_profile != "global" else ["global"]
60
+ cap, profile_name = self.resolve_from_profiles(task_name, profiles)
61
+ if cap:
62
+ return cap, profile_name
63
+ return self.resolve_custom_capability(task_name)
64
+
65
+ def build_cmd(
66
+ self,
67
+ entrypoint: str,
68
+ language: str,
69
+ params: dict,
70
+ base_dir: str = "",
71
+ cap_id: str = "",
72
+ ) -> list[str]:
73
+ return build_command(
74
+ entrypoint,
75
+ language,
76
+ params,
77
+ self.root_dir,
78
+ self.pkg_data_dir,
79
+ Path(os.getcwd()),
80
+ base_dir=base_dir,
81
+ cap_id=cap_id,
82
+ )
83
+
84
+ def get_active_profile(self) -> str:
85
+ try:
86
+ env_mgr = self.dep("EnvironmentManager")(Path.cwd())
87
+ if env_mgr.is_initialized():
88
+ return env_mgr.load_environment().get("active_profile", "global")
89
+ except (OSError, json.JSONDecodeError): # nosec B110
90
+ pass
91
+ return "global"
92
+
93
+ def execute_task_cmd(
94
+ self, task_name: str, target_cap: dict, params: dict, stream_runner, stream: bool = False
95
+ ) -> subprocess.CompletedProcess:
96
+ cap_id = target_cap.get("id", task_name)
97
+ cmd = self.build_cmd(
98
+ target_cap["entrypoint"],
99
+ target_cap.get("language", "python"),
100
+ params,
101
+ base_dir=target_cap.get("_base_dir", ""),
102
+ cap_id=cap_id,
103
+ )
104
+ entrypoint = target_cap.get("entrypoint", "")
105
+
106
+ cache = self.dep("get_capability_cache")()
107
+ cached = cache.get(cap_id, params, entrypoint)
108
+ if cached is not None:
109
+ self.logger.info(f"[Cache] HIT para '{cap_id}' — usando resultado previo.")
110
+ return subprocess.CompletedProcess(
111
+ args=cmd,
112
+ returncode=cached.returncode,
113
+ stdout=cached.stdout,
114
+ stderr=cached.stderr,
115
+ )
116
+
117
+ self.logger.info(f"[>] Ejecutando: {' '.join(cmd)}")
118
+ resilience = self.dep("get_resilience_manager")()
119
+ max_retries = target_cap.get("retries", 1)
120
+
121
+ def _run_once() -> subprocess.CompletedProcess:
122
+ if stream:
123
+ return stream_runner(cmd)
124
+ return subprocess.run( # nosec B603 B607
125
+ cmd, cwd=os.getcwd(), capture_output=True, text=True, check=False
126
+ )
127
+
128
+ result = resilience.execute_with_resilience(
129
+ capability_id=cap_id,
130
+ fn=_run_once,
131
+ max_retries=max_retries,
132
+ )
133
+ if result is None:
134
+ return subprocess.CompletedProcess(
135
+ args=cmd,
136
+ returncode=1,
137
+ stdout="",
138
+ stderr=f"[Circuit] '{cap_id}' bloqueado por circuit breaker.",
139
+ )
140
+
141
+ cache.set(
142
+ cap_id,
143
+ params,
144
+ stdout=result.stdout,
145
+ stderr=result.stderr,
146
+ returncode=result.returncode,
147
+ entrypoint=entrypoint,
148
+ )
149
+ return result
150
+
151
+ def validate_contract(
152
+ self, target_cap: dict, params: dict, result: subprocess.CompletedProcess
153
+ ) -> tuple[bool, list]:
154
+ if result.returncode != 0:
155
+ return True, []
156
+ return self.dep("ContractValidator").validate(
157
+ params, result.stdout, result.stderr, result.returncode, target_cap
158
+ )
159
+
160
+ def save_memory(
161
+ self,
162
+ task_name: str,
163
+ params: dict,
164
+ result: subprocess.CompletedProcess,
165
+ contract_success: bool,
166
+ contract_errors: list,
167
+ ) -> None:
168
+ try:
169
+ mem_script = (
170
+ self.pkg_data_dir
171
+ / "higpertext"
172
+ / "capabilities"
173
+ / "common"
174
+ / "scripts"
175
+ / "memory_manager.py"
176
+ )
177
+ status = "success" if result.returncode == 0 and contract_success else "failure"
178
+ notes = build_memory_notes(task_name, params, result, contract_success, contract_errors)
179
+ subprocess.run( # nosec B603 B607
180
+ [
181
+ "python",
182
+ str(mem_script),
183
+ "--action",
184
+ f"Auto-run: {task_name}",
185
+ "--status",
186
+ status,
187
+ "--notes",
188
+ notes,
189
+ ],
190
+ capture_output=True,
191
+ check=False,
192
+ )
193
+ except OSError as mem_e:
194
+ self.logger.warning(f"[WARNING] Falló guardado de memoria: {mem_e}")
195
+
196
+ def warn_missing_pat(self, task_name: str) -> None:
197
+ if "ado" in task_name or "sre" in task_name and not os.getenv("ADO_PAT"):
198
+ self.logger.warning("[WARNING] No se detectó ADO_PAT en .env.")
199
+
200
+ def run_task(self, task_name: str, params: dict, stream_runner, stream: bool = False, no_cache: bool = False) -> bool:
201
+ try:
202
+ active_profile = self.get_active_profile()
203
+ target_cap, found_profile = self.resolve_capability(task_name, active_profile)
204
+ if not target_cap:
205
+ self.logger.warning(
206
+ f"[!] Capacidad '{task_name}' no disponible para el perfil '{active_profile.upper()}'."
207
+ )
208
+ return False
209
+ self.logger.info(f"[*] Capacidad '{task_name}' resuelta: {found_profile.upper()}")
210
+ validation = self.dep("normalize_and_validate_params")(target_cap, params)
211
+ if not validation.ok:
212
+ self.logger.error(f"[ERROR] Parámetros inválidos para '{task_name}':")
213
+ for error in validation.errors:
214
+ self.logger.info(f" - {error}")
215
+ return False
216
+ for warning in validation.warnings:
217
+ self.logger.warning(f"[PARAM] {warning}")
218
+ params = validation.params
219
+ self.warn_missing_pat(task_name)
220
+ self.logger.info(f"[*] Ejecutando tarea: {task_name}")
221
+
222
+ if no_cache:
223
+ cache = self.dep("get_capability_cache")()
224
+ cache.invalidate(target_cap.get("id", task_name))
225
+
226
+ result = self.execute_task_cmd(task_name, target_cap, params, stream_runner, stream=stream)
227
+ ok, errors = self.validate_contract(target_cap, params, result)
228
+ status = report_task_result(task_name, result, ok, errors, self.logger)
229
+ if task_name != "memory-manager":
230
+ self.save_memory(task_name, params, result, ok, errors)
231
+ return status
232
+ except (OSError, ValueError) as exc:
233
+ self.logger.warning(f"[!] Error inesperado: {exc}")
234
+ return False
@@ -0,0 +1,234 @@
1
+ """higpertext CLI — Knowledge search mixin (Infraestructura)."""
2
+
3
+ from __future__ import annotations
4
+ import os
5
+ import json
6
+ from collections.abc import Iterable
7
+ from pathlib import Path
8
+
9
+ from higpertext.kernel.infrastructure.logger import get_logger
10
+ _log = get_logger()
11
+
12
+
13
+ class HigpertextSearchMixin:
14
+ """Provides knowledge-base search capabilities to HigpertextHub."""
15
+
16
+ def _search_guidelines_contract(self, full_path: Path, query_terms: Iterable[str]) -> list:
17
+ matches = []
18
+ try:
19
+ data = json.loads(full_path.read_text(encoding="utf-8"))
20
+ for section, rules in data.get("guidelines", {}).items():
21
+ for rule in rules:
22
+ if any(t in rule.lower() or t in section.lower() for t in query_terms):
23
+ label = section.upper().replace("_", " ")
24
+ card = f"⚖️ [CONTRATO: {label}]\n" f" Regla: {rule}"
25
+ matches.append((0, card))
26
+ except (OSError, json.JSONDecodeError): # nosec B110
27
+ pass
28
+ return matches
29
+
30
+ def _search_learnings_json(self, full_path: Path, query_terms: Iterable[str]) -> list:
31
+ matches = []
32
+ try:
33
+ data = json.loads(full_path.read_text(encoding="utf-8"))
34
+ if not isinstance(data, dict):
35
+ return matches
36
+ for tag, entries in data.items():
37
+ for entry in entries:
38
+ blob = self._entry_blob(entry, [tag])
39
+ if any(t in blob for t in query_terms):
40
+ learned = ", ".join(entry.get("learned", []))
41
+ card = (
42
+ f"💡 [MEMORIA: APRENDIZAJE] (Tag: {tag})\n"
43
+ f" Acción: {entry.get('action')} "
44
+ f"({entry.get('action_id')})\n"
45
+ f" Aprendido: {learned or 'N/A'}\n"
46
+ f" Notas: {entry.get('notes')}"
47
+ )
48
+ matches.append((0, card))
49
+ except (OSError, json.JSONDecodeError): # nosec B110
50
+ pass
51
+ return matches
52
+
53
+ def _search_journal_json(self, full_path: Path, query_terms: Iterable[str]) -> list:
54
+ matches = []
55
+ try:
56
+ data = json.loads(full_path.read_text(encoding="utf-8"))
57
+ if not isinstance(data, list):
58
+ return matches
59
+ for entry in data:
60
+ blob = self._entry_blob(entry, [])
61
+ if any(t in blob for t in query_terms):
62
+ matches.append((0, self._journal_card(entry)))
63
+ except (OSError, json.JSONDecodeError): # nosec B110
64
+ pass
65
+ return matches
66
+
67
+ @staticmethod
68
+ def _entry_blob(entry: dict, extra: list) -> str:
69
+ parts = (
70
+ extra
71
+ + [entry.get("action", ""), entry.get("notes", "")]
72
+ + entry.get("tags", [])
73
+ + entry.get("learned", [])
74
+ )
75
+ if entry.get("failure_root_cause"):
76
+ parts.append(entry["failure_root_cause"])
77
+ return " ".join(str(v).lower() for v in parts)
78
+
79
+ @staticmethod
80
+ def _journal_card(entry: dict) -> str:
81
+ tags_str = ", ".join(entry.get("tags", []))
82
+ status = "🟢 SUCCESS" if entry.get("status") == "success" else "🔴 FAILURE"
83
+ ts = (entry.get("timestamp") or "")[:19]
84
+ card = (
85
+ f"🪵 [MEMORIA: JOURNAL] ({ts})\n"
86
+ f" Acción: {entry.get('action')} "
87
+ f"({entry.get('action_id')}) -> {status}\n"
88
+ f" Tags: {tags_str}\n"
89
+ f" Notas: {entry.get('notes')}"
90
+ )
91
+ if entry.get("failure_root_cause"):
92
+ card += f"\n Causa Raíz: {entry['failure_root_cause']}"
93
+ return card
94
+
95
+ def _search_text_file(self, full_path: Path, query_terms: list[str]) -> list:
96
+ matches = []
97
+ try:
98
+ lines = full_path.read_text(encoding="utf-8", errors="ignore").splitlines()
99
+ for idx, line in enumerate(lines, 1):
100
+ if any(t in line.lower() for t in query_terms):
101
+ matches.append((idx, line.strip()))
102
+ except OSError: # nosec B110
103
+ pass
104
+ return matches
105
+
106
+ def _search_file(self, full_path: Path, query_terms: list[str]) -> list:
107
+ name = full_path.name
108
+ if name == "guidelines_contract.json":
109
+ return self._search_guidelines_contract(full_path, query_terms)
110
+ if name == "learnings.json":
111
+ return self._search_learnings_json(full_path, query_terms)
112
+ if name == "journal.json":
113
+ return self._search_journal_json(full_path, query_terms)
114
+ if name.endswith((".md", ".txt")):
115
+ return self._search_text_file(full_path, query_terms)
116
+ return []
117
+
118
+ def _search_semantic_graph(
119
+ self, graph_path: Path, query_terms: list[str], root_dir: Path
120
+ ) -> dict:
121
+ try:
122
+ data = json.loads(graph_path.read_text(encoding="utf-8"))
123
+ except (OSError, json.JSONDecodeError):
124
+ return {}
125
+
126
+ matches: dict = {}
127
+ files = data.get("files", {})
128
+
129
+ for rel_path, file_data in files.items():
130
+ hits: list[tuple[int, str]] = []
131
+
132
+ summary = file_data.get("summary", "").lower()
133
+ if summary and any(t in summary for t in query_terms):
134
+ hits.append((0, f"📝 módulo: {file_data['summary'].strip()[:120]}"))
135
+
136
+ # Buscar en clases y sus métodos
137
+ hits.extend(self._search_code_elements(file_data.get("classes", []), query_terms, "🔷 class", "methods"))
138
+
139
+ # Buscar en funciones
140
+ hits.extend(self._search_code_elements(file_data.get("functions", []), query_terms, "🔹 def"))
141
+
142
+ for imp in file_data.get("imports", []):
143
+ if any(t in imp.lower() for t in query_terms):
144
+ hits.append((0, f"📦 import {imp}"))
145
+
146
+ if hits:
147
+ try:
148
+ key = f"[grafo] ./{Path(rel_path).as_posix()}"
149
+ except ValueError:
150
+ key = f"[grafo] {rel_path}"
151
+ matches[key] = hits
152
+
153
+ return matches
154
+
155
+ def _walk_search(
156
+ self, search_paths: list[Path], query_terms: list[str], root_dir: Path
157
+ ) -> dict:
158
+ matches: dict = {}
159
+ query_terms_set = set(query_terms)
160
+
161
+ for path in search_paths:
162
+ if not path.exists():
163
+ continue
164
+ for full_path in path.rglob("*"): # Use rglob for recursive search
165
+ if not self._is_searchable_file(full_path):
166
+ continue
167
+ try:
168
+ found = self._search_file(full_path, query_terms_set)
169
+ if not found:
170
+ continue
171
+ key = self._make_search_key(full_path, root_dir)
172
+ matches[key] = found
173
+ except OSError: # nosec B110
174
+ pass
175
+ return matches
176
+
177
+ @staticmethod
178
+ def _is_searchable_file(full_path: Path) -> bool:
179
+ if not full_path.is_file() or full_path.suffix not in (".md", ".txt", ".json"):
180
+ return False
181
+ try:
182
+ return full_path.stat().st_size <= 1024 * 1024
183
+ except OSError:
184
+ return False
185
+
186
+ @staticmethod
187
+ def _make_search_key(full_path: Path, root_dir: Path) -> str:
188
+ return f"./{full_path.relative_to(root_dir)}" if root_dir in full_path.parents else str(full_path)
189
+
190
+ @staticmethod
191
+ def _print_search_results(matches: dict) -> None:
192
+ if not matches:
193
+ _log.warning("[!] No se encontró información relevante.")
194
+ _log.info(
195
+ "[TIP] Consulta al administrador de gobernanza si buscas " "una norma no listada."
196
+ )
197
+ return
198
+ _log.ok("[SUCCESS] Se encontraron coincidencias:")
199
+ for filepath, snippets in list(matches.items())[:5]:
200
+ _log.info(f"\n📄 Coincidencias en {filepath}:")
201
+ for line_num, snippet in snippets[:3]:
202
+ if line_num == 0:
203
+ _log.info(f"{snippet}")
204
+ else:
205
+ if len(snippet) > 120:
206
+ snippet = snippet[:117] + "..."
207
+ _log.info(f" L{line_num}: {snippet}")
208
+ if len(snippets) > 3:
209
+ _log.info(f" ... y {len(snippets) - 3} coincidencias más.")
210
+
211
+ @staticmethod
212
+ def _search_code_elements(
213
+ elements: list[dict],
214
+ query_terms: list[str],
215
+ prefix: str,
216
+ nested_key: str | None = None,
217
+ ) -> list[tuple[int, str]]:
218
+ hits = []
219
+ for elem in elements:
220
+ name = elem.get("name", "")
221
+ doc = elem.get("docstring", "")
222
+ if any(t in name.lower() or t in doc.lower() for t in query_terms):
223
+ doc_snippet = f" — *{doc.strip()[:60]}*" if doc else ""
224
+ hits.append((0, f"{prefix} {name}{doc_snippet}"))
225
+
226
+ if nested_key:
227
+ nested_elements = elem.get(nested_key, [])
228
+ for ne in nested_elements:
229
+ ne_name = ne.get("name", "")
230
+ ne_doc = ne.get("docstring", "")
231
+ if any(t in ne_name.lower() or t in ne_doc.lower() for t in query_terms):
232
+ ne_doc_snippet = f" — *{ne_doc.strip()[:60]}*" if ne_doc else ""
233
+ hits.append((0, f" └─ def {ne_name}{ne_doc_snippet}"))
234
+ return hits
@@ -0,0 +1,83 @@
1
+ """Contratos y aliases de parámetros para capabilities higpertext."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass
6
+ from typing import Any
7
+
8
+ _ALIASES: dict[str, tuple[str, ...]] = {
9
+ "path": (
10
+ "file",
11
+ "file_path",
12
+ "filePath",
13
+ "source",
14
+ "source_path",
15
+ "tests_path",
16
+ "target",
17
+ ),
18
+ "tests_path": ("path", "test_path", "test", "target"),
19
+ "source_path": ("path", "source", "target"),
20
+ "target": ("path", "source_path", "target_dir"),
21
+ "output": ("output_report", "report_path", "out"),
22
+ "json": ("as_json", "format_json"),
23
+ }
24
+
25
+
26
+ @dataclass(frozen=True)
27
+ class ParameterValidation:
28
+ params: dict[str, Any]
29
+ errors: list[str]
30
+ warnings: list[str]
31
+
32
+ @property
33
+ def ok(self) -> bool:
34
+ return not self.errors
35
+
36
+
37
+ def normalize_and_validate_params(capability: dict, params: dict[str, Any]) -> ParameterValidation:
38
+ """Normaliza aliases declarados y valida parámetros requeridos."""
39
+ declared = _declared_parameters(capability)
40
+ normalized = dict(params)
41
+ warnings: list[str] = []
42
+
43
+ for canonical in declared:
44
+ if canonical in normalized:
45
+ continue
46
+ alias = _first_present_alias(canonical, normalized)
47
+ if alias:
48
+ normalized[canonical] = normalized[alias]
49
+ normalized.pop(alias, None)
50
+ warnings.append(f"Alias --{alias} normalizado a --{canonical}")
51
+
52
+ errors = _missing_required_errors(capability, normalized)
53
+ return ParameterValidation(params=normalized, errors=errors, warnings=warnings)
54
+
55
+
56
+ def _declared_parameters(capability: dict) -> set[str]:
57
+ return {item.get("name", "") for item in capability.get("parameters", []) if item.get("name")}
58
+
59
+
60
+ def _first_present_alias(canonical: str, params: dict[str, Any]) -> str:
61
+ for alias in _ALIASES.get(canonical, ()):
62
+ if alias in params:
63
+ return alias
64
+ return ""
65
+
66
+
67
+ def _missing_required_errors(capability: dict, params: dict[str, Any]) -> list[str]:
68
+ errors: list[str] = []
69
+ for item in capability.get("parameters", []):
70
+ name = item.get("name", "")
71
+ if not name or not item.get("required", False):
72
+ continue
73
+ if params.get(name) not in (None, ""):
74
+ continue
75
+ errors.append(_format_missing_required(name, item))
76
+ return errors
77
+
78
+
79
+ def _format_missing_required(name: str, spec: dict) -> str:
80
+ description = spec.get("description", "sin descripción")
81
+ aliases = ", ".join(f"--{alias}" for alias in _ALIASES.get(name, ()))
82
+ alias_hint = f" Alias aceptados: {aliases}." if aliases else ""
83
+ return f"Falta parámetro requerido --{name}: {description}.{alias_hint}"