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,635 @@
1
+ """Reglas de evaluación para comandos Bash (PreToolUse:Bash).
2
+
3
+ Cada función recibe (cmd: str, root: Path) y retorna una RuleResult.
4
+ El entrypoint hook_bash_guard.py evalúa todas en cadena.
5
+ """
6
+
7
+ from __future__ import annotations
8
+ from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
9
+ from .governance_adapter import get_bash_blocks, get_deployment_blocks
10
+ import json
11
+ import re
12
+ import shlex
13
+ import shutil
14
+ import subprocess # nosec B404
15
+ import sys
16
+ from dataclasses import dataclass
17
+ from pathlib import Path
18
+ from typing import Literal
19
+
20
+ from higpertext.kernel.session_manager import SessionManager
21
+
22
+ RuleSeverity = Literal["continue", "context", "block"]
23
+
24
+
25
+ @dataclass
26
+ class RuleResult:
27
+ severity: RuleSeverity
28
+ message: str = ""
29
+ capability: str = ""
30
+
31
+
32
+ # ── Patrones compartidos ───────────────────────────────────────────────────────
33
+
34
+ _HIGPERTEXT_DIR = WORKSPACE_DIR_NAME
35
+
36
+ _WHITELIST = re.compile(
37
+ r"python htx\.py|\.venv[/\\](bin|Scripts)[/\\]python"
38
+ r"|\.venv[/\\](bin|Scripts)[/\\]htx\b|\bhtx\s+(?:task|workflow)\b"
39
+ )
40
+ _WHITELIST_EXTENDED = re.compile(
41
+ _WHITELIST.pattern + r"|git\s+checkout|git\s+merge|git\s+rebase|git\s+stash|git\s+tag"
42
+ r"|\becho\b"
43
+ )
44
+ _GIT_ADD_ONLY = re.compile(r"\bgit\s+add\b")
45
+ _GIT_COMMIT_PRESENT = re.compile(r"\bgit\s+commit\b")
46
+
47
+
48
+ def is_whitelisted(cmd: str) -> bool:
49
+ if "git commit" in cmd:
50
+ return False
51
+ if _WHITELIST_EXTENDED.search(cmd):
52
+ return True
53
+ if _GIT_ADD_ONLY.search(cmd) and not _GIT_COMMIT_PRESENT.search(cmd):
54
+ return True
55
+ return False
56
+
57
+
58
+ # ── Reglas de Commit ──────────────────────────────────────────────────────────
59
+
60
+ _GIT_COMMIT_TRIGGER = re.compile(r"\bgit\s+commit\b")
61
+
62
+
63
+ def check_git_commit_block(cmd: str, *_, **__) -> RuleResult | None:
64
+ if _GIT_COMMIT_TRIGGER.search(cmd):
65
+ return RuleResult(
66
+ severity="block",
67
+ capability="git.committer",
68
+ message=render_box(
69
+ "HIGPERTEXT · Commit directo bloqueado",
70
+ [
71
+ " ⚠ El uso de 'git commit' nativo está prohibido.",
72
+ " Debes usar la capability de committer nativa del motor",
73
+ " para cumplir con la gobernanza y Conventional Commits.",
74
+ " → Uso correcto:",
75
+ ' htx task git.committer --message "feat(scope): msg" --rationale "..."',
76
+ ]
77
+ ),
78
+ )
79
+ return None
80
+
81
+
82
+ # ── Regla 1: Bloques duros (sudo, git push) ───────────────────────────────────
83
+
84
+
85
+ def check_hard_blocks(cmd: str, root: Path) -> RuleResult | None:
86
+ hard_blocks = get_bash_blocks(root)
87
+ for pattern, reason in hard_blocks:
88
+ if re.search(pattern, cmd):
89
+ return RuleResult(
90
+ severity="block",
91
+ message=render_box(
92
+ "HIGPERTEXT · Bloqueado",
93
+ [
94
+ f" ✗ {reason}",
95
+ ]
96
+ ),
97
+ )
98
+ return None
99
+
100
+
101
+ # ── Regla 2: Branch Protection (push a main/master) ──────────────────────────
102
+
103
+
104
+ def check_branch_protection(cmd: str, root: Path) -> RuleResult | None:
105
+ try:
106
+ from higpertext.kernel.infrastructure import ContractLoader
107
+
108
+ data = ContractLoader(root).load_branching_strategy()
109
+ except Exception:
110
+ return None
111
+ for rule in data.get("rules", []):
112
+ pattern = rule.get("pattern")
113
+ if pattern and re.search(pattern, cmd):
114
+ severity = rule.get("severity", "block")
115
+ reason = rule.get("reason", "Acción prohibida por branching strategy")
116
+ return RuleResult(
117
+ severity=severity,
118
+ message=render_box(
119
+ "HIGPERTEXT · Branch Protection",
120
+ [
121
+ f" ✗ {reason}",
122
+ f" Regla: {rule.get('id', 'branching')}",
123
+ ]
124
+ ),
125
+ )
126
+ return None
127
+
128
+
129
+ # ── Regla 1c: Deployment gate (deployment_gates.json) ────────────────────────
130
+
131
+
132
+ def check_deployment_gate(cmd: str, root: Path) -> RuleResult | None:
133
+ """Warn/block sobre comandos de deploy según deployment_gates.json."""
134
+ for pattern, reason, severity in get_deployment_blocks(root):
135
+ if re.search(pattern, cmd, re.IGNORECASE):
136
+ return RuleResult(
137
+ severity=severity,
138
+ message=render_box(
139
+ "HIGPERTEXT · Deployment Gate",
140
+ [
141
+ f" ⚠ {reason}",
142
+ ]
143
+ ),
144
+ )
145
+ return None
146
+
147
+
148
+ # ── Regla 2: Redirect ls → git.ls-files ──────────────────────────────────────
149
+
150
+ _LS_TRIGGER = re.compile(r"(^|[;&|]\s*)ls(\s|$)|\bgit\s+ls-files\b")
151
+ _LS_REASON = (
152
+ "ls directo lista el filesystem crudo y puede incluir carpetas generadas. "
153
+ "git.ls-files muestra archivos trackeados con resúmenes compactos y filtros."
154
+ )
155
+
156
+ _RESULTADO_LINE = "│ RESULTADO:"
157
+
158
+
159
+ def check_ls_redirect(cmd: str, root: Path) -> RuleResult | None:
160
+ if not _LS_TRIGGER.search(cmd):
161
+ return None
162
+ params = _extract_ls_params(cmd)
163
+ output = _run_higpertext("git.ls-files", params, root)
164
+ header = render_box(
165
+ "HIGPERTEXT · Capacidad ejecutada",
166
+ [
167
+ f" Comando interceptado : {cmd}",
168
+ " Capacidad usada : git.ls-files",
169
+ f" Motivo : {_LS_REASON}",
170
+ "",
171
+ " RESULTADO:",
172
+ ]
173
+ )
174
+ return RuleResult(severity="context", message=f"{header}\n{output}", capability="git.ls-files")
175
+
176
+
177
+ def _extract_ls_pattern(cmd: str) -> str:
178
+ """Extrae el primer path de ls ignorando flags; vacío lista todo."""
179
+ params = _extract_ls_params(cmd)
180
+ return params.get("path", "") or params.get("pattern", "")
181
+
182
+
183
+ def _extract_ls_params(cmd: str) -> dict:
184
+ """Traduce usos frecuentes de ls/git ls-files a parámetros gobernados."""
185
+ try:
186
+ tokens = shlex.split(cmd)
187
+ except ValueError:
188
+ return {"mode": "summary"}
189
+
190
+ params: dict[str, str] = {"mode": "summary"}
191
+ start = _ls_args_start(tokens)
192
+ if start is None:
193
+ return params
194
+
195
+ for tok in tokens[start:]:
196
+ if tok in ("--", ".", "./"):
197
+ continue
198
+ if tok.startswith("-"):
199
+ _apply_ls_flag(tok, params)
200
+ continue
201
+ clean = tok.rstrip("/")
202
+ if any(ch in clean for ch in "*?[]"):
203
+ params["include"] = clean
204
+ elif clean.startswith(".") and "/" not in clean:
205
+ params["extension"] = clean.lstrip(".")
206
+ else:
207
+ params["path"] = clean
208
+ if clean.startswith(("tests", "test")):
209
+ params["preset"] = "tests"
210
+ return params
211
+
212
+
213
+ def _ls_args_start(tokens: list[str]) -> int | None:
214
+ if "ls" in tokens:
215
+ return tokens.index("ls") + 1
216
+ for i, tok in enumerate(tokens[:-1]):
217
+ if tok == "git" and tokens[i + 1] == "ls-files":
218
+ return i + 2
219
+ return None
220
+
221
+
222
+ def _apply_ls_flag(flag: str, params: dict) -> None:
223
+ if "R" in flag:
224
+ params["mode"] = "tree"
225
+ if "l" in flag or "h" in flag or "s" in flag:
226
+ params["show_size"] = "true"
227
+ if params.get("mode") == "summary":
228
+ params["mode"] = "list"
229
+ if "d" in flag:
230
+ params["mode"] = "dirs"
231
+ if "1" in flag:
232
+ params["files_only"] = "true"
233
+
234
+
235
+ # ── Regla 3: Redirect grep/find → common.grep-search ─────────────────────────
236
+
237
+ _GREP_TRIGGER = re.compile(r"\b(grep|find)\s+")
238
+ _GREP_PATTERN = re.compile(r'grep\s+(?:-\w+\s+)*["\']?([^"\'\s]+)["\']?\s+([^|]+)')
239
+ _FIND_PATTERN = re.compile(r"find\s+(\S+)\s+([^|]+)")
240
+ _GREP_REASON = (
241
+ "grep/find directo omite exclusiones de .venv, __pycache__ y .git, "
242
+ "y no formatea los resultados con número de línea agrupado por archivo. "
243
+ "common.grep-search aplica estos filtros automáticamente."
244
+ )
245
+
246
+
247
+ def check_grep_redirect(cmd: str, root: Path) -> RuleResult | None:
248
+ if not _GREP_TRIGGER.search(cmd):
249
+ return None
250
+
251
+ m_grep = _GREP_PATTERN.search(cmd)
252
+ m_find = _FIND_PATTERN.search(cmd)
253
+
254
+ if m_grep:
255
+ pattern, path = m_grep.group(1), m_grep.group(2).strip().split()[0]
256
+ elif m_find:
257
+ path, pattern = m_find.group(1), m_find.group(2).strip()
258
+ else:
259
+ return None
260
+
261
+ output = _run_higpertext("common.grep-search", {"pattern": pattern, "path": path}, root)
262
+ header = render_box(
263
+ "HIGPERTEXT · Capacidad ejecutada",
264
+ [
265
+ f" Comando interceptado : {cmd}",
266
+ " Capacidad usada : common.grep-search",
267
+ f" Motivo : {_GREP_REASON}",
268
+ "",
269
+ " RESULTADO:",
270
+ ]
271
+ )
272
+ return RuleResult(
273
+ severity="context",
274
+ message=f"{header}\n{output}",
275
+ capability="common.grep-search",
276
+ )
277
+
278
+
279
+ # ── Regla 4: Redirect git diff/status/log → git.diff ─────────────────────────
280
+
281
+ _GIT_TRIGGER = re.compile(r"\bgit\s+(diff|status|log)\b")
282
+ _GIT_SKIP = re.compile(r"python htx\.py|git\s+add|git\s+commit|git\s+push")
283
+ _GIT_DETAIL = re.compile(r"\bgit\s+diff\b")
284
+ _GIT_REASON = (
285
+ "git diff/status/log nativo produce output sin clasificar. "
286
+ "git.diff agrupa los archivos por estado (Staged, Unstaged, Untracked) "
287
+ "y formatea el diff en markdown legible para el agente."
288
+ )
289
+
290
+
291
+ def check_git_redirect(cmd: str, root: Path) -> RuleResult | None:
292
+ if _GIT_SKIP.search(cmd) or not _GIT_TRIGGER.search(cmd):
293
+ return None
294
+
295
+ params = {"detail": "true"} if _GIT_DETAIL.search(cmd) else {}
296
+ output = _run_higpertext("git.diff", params, root)
297
+ header = render_box(
298
+ "HIGPERTEXT · Capacidad ejecutada",
299
+ [
300
+ f" Comando interceptado : {cmd}",
301
+ " Capacidad usada : git.diff",
302
+ f" Motivo : {_GIT_REASON}",
303
+ "",
304
+ " RESULTADO:",
305
+ ]
306
+ )
307
+ return RuleResult(severity="context", message=f"{header}\n{output}", capability="git.diff")
308
+
309
+
310
+ # ── Regla 4: Redirect docs/governance → common.knowledge-asker ───────────────
311
+
312
+ _KNOWLEDGE_TRIGGER = re.compile(
313
+ r"\bcat\s+.*(docs|governance|\.memory|AGENTS\.md|GEMINI\.md|README)|"
314
+ r"\bhead\s+.*(docs|governance)|"
315
+ r"\bless\s+.*(docs|governance)",
316
+ re.IGNORECASE,
317
+ )
318
+
319
+
320
+ def check_knowledge_redirect(cmd: str, root: Path) -> RuleResult | None:
321
+ if not root or not _KNOWLEDGE_TRIGGER.search(cmd):
322
+ return None
323
+ return RuleResult(
324
+ severity="context",
325
+ capability="common.knowledge-asker",
326
+ message=(
327
+ "[HIGPERTEXT] Para consultar gobernanza o documentación usa:\n"
328
+ 'htx task common.knowledge-asker --query "<pregunta>"'
329
+ ),
330
+ )
331
+
332
+
333
+ # ── Regla 4b: Bloquear cat/read directo de archivos grandes ────────────────────
334
+
335
+ _READ_TRIGGER = re.compile(r"\b(cat|head|less|tail)\s+([^\s|;&]+)")
336
+
337
+
338
+ def check_large_file_read_redirect(cmd: str, root: Path) -> RuleResult | None:
339
+ if "htx.py" in cmd:
340
+ return None
341
+ m = _READ_TRIGGER.search(cmd)
342
+ if not m:
343
+ return None
344
+ file_path_str = m.group(2).strip().strip('"').strip("'")
345
+ p = Path(file_path_str)
346
+ if not p.is_absolute():
347
+ p = root / p
348
+ if p.exists() and p.is_file():
349
+ size_kb = p.stat().st_size / 1024
350
+ # Si supera 100 KB lo bloqueamos / interceptamos
351
+ if size_kb > 100:
352
+ return RuleResult(
353
+ severity="block",
354
+ capability="common.code-skeletonizer",
355
+ message=render_box(
356
+ "HIGPERTEXT · Bloqueo de lectura masiva",
357
+ [
358
+ f" Archivo : {m.group(2)} ({size_kb:.1f} KB)",
359
+ " ⚠ La lectura directa de archivos > 100 KB está bloqueada",
360
+ " para evitar la saturación de contexto de tokens.",
361
+ " → Sugerencia: Usa offsets o usa la capability de",
362
+ " skeletons para ver solo las firmas del archivo:",
363
+ f" htx task common.code-skeletonizer --path {m.group(2)}",
364
+ ]
365
+ ),
366
+ )
367
+ return None
368
+
369
+
370
+ # ── Regla 5: Redirect ls capabilities → common.list-rules ────────────────────
371
+
372
+ _LIST_RULES_TRIGGER = re.compile(
373
+ r"\bls\s+.*(capabilities|rules|profiles|workflows)|"
374
+ r"\bcat\s+.*(list.rules|capabilities.*\.json)|"
375
+ r"\bfind\s+.*(capabilities|profiles)",
376
+ re.IGNORECASE,
377
+ )
378
+
379
+
380
+ def check_list_rules(cmd: str, root: Path) -> RuleResult | None:
381
+ if not _LIST_RULES_TRIGGER.search(cmd):
382
+ return None
383
+ output = _run_higpertext("common.list-rules", {"type": "all"}, root)
384
+ return RuleResult(
385
+ severity="block",
386
+ capability="common.list-rules",
387
+ message=(
388
+ f"⚠️ [HIGPERTEXT HOOK] `{cmd}` interceptado"
389
+ f" → ejecutando `common.list-rules`\n\n{output}"
390
+ ),
391
+ )
392
+
393
+
394
+ # ── Regla 6: Redirect escritura de reglas → common.load-rules ────────────────
395
+
396
+ _LOAD_RULES_TRIGGER = re.compile(
397
+ r"\bcat\s+.*(session.capabilities|\.claude/rules|\.opencode/rules)|"
398
+ r"\becho\s+.*session.capabilities|"
399
+ r"\btee\s+.*(rules/.*\.md)|"
400
+ r"\bwrite\s+.*session.capabilities",
401
+ re.IGNORECASE,
402
+ )
403
+ _RULES_ARG = re.compile(r'--rules\s+["\']?([^"\']+)["\']?')
404
+
405
+
406
+ def check_load_rules(cmd: str, root: Path) -> RuleResult | None:
407
+ if not _LOAD_RULES_TRIGGER.search(cmd):
408
+ return None
409
+ m = _RULES_ARG.search(cmd)
410
+ rules = m.group(1) if m else "all"
411
+ output = _run_higpertext("common.load-rules", {"rules": rules}, root)
412
+ return RuleResult(
413
+ severity="block",
414
+ capability="common.load-rules",
415
+ message=(
416
+ f"⚠️ [HIGPERTEXT HOOK] `{cmd}` interceptado"
417
+ f" → ejecutando `common.load-rules`\n\n{output}"
418
+ ),
419
+ )
420
+
421
+
422
+ # ── Regla 7: Intercepción de exit → cierre limpio de sesión ──────────────────
423
+
424
+
425
+ def check_exit_guard(cmd: str, root: Path) -> RuleResult | None:
426
+ is_exit = cmd.strip() == "exit" or (
427
+ cmd.strip().startswith("exit ") and cmd.strip()[5:].strip().isdigit()
428
+ )
429
+ if not is_exit:
430
+ return None
431
+
432
+ session = _read_json(root / WORKSPACE_DIR_NAME / "state" / "session.json")
433
+ env = _read_json(root / WORKSPACE_DIR_NAME / "config" / "environment.json")
434
+ sid = session.get("session_id", "—")
435
+ profile = env.get("active_profile", "global")
436
+ active = session.get("status") == "active"
437
+
438
+ if active:
439
+ _close_session(root)
440
+
441
+ files = _pending_files(root)
442
+ lines = []
443
+ if files:
444
+ lines.append(f" ⚠ {len(files)} archivo(s) sin commitear:")
445
+ for f in files:
446
+ lines.append(f" • {f}")
447
+ lines.append(" → commiteálos antes de salir")
448
+ else:
449
+ lines.append(" ✓ Working tree limpio")
450
+ lines += [
451
+ f" Sesión cerrada : {sid}",
452
+ f" Perfil cerrado : {profile}",
453
+ " ✓ Skills y subagentes eliminados",
454
+ ]
455
+ return RuleResult(severity="continue", message=render_box("HIGPERTEXT · Cierre de sesión", lines))
456
+
457
+
458
+ # ── Regla 8: higpertext enforcer dinámico (desde JSONs de capabilities) ────────────
459
+
460
+
461
+ def check_higpertext_enforcer(cmd: str, root: Path) -> RuleResult | None:
462
+ caps_roots = _find_capabilities_roots(root)
463
+ all_json: list[Path] = []
464
+ for caps_root in caps_roots:
465
+ all_json.extend(sorted(caps_root.rglob("*.json")))
466
+ for json_file in all_json:
467
+ try:
468
+ data = json.loads(json_file.read_text(encoding="utf-8"))
469
+ except (OSError, json.JSONDecodeError):
470
+ continue
471
+ intercept = data.get("bash_intercept")
472
+ if not intercept:
473
+ continue
474
+ pattern = intercept.get("pattern", "")
475
+ if not pattern or not re.search(pattern, cmd):
476
+ continue
477
+ if intercept.get("has_dedicated_hook", False):
478
+ return None
479
+ cap_id = data.get("id", "")
480
+ reason = intercept.get("reason", intercept.get("description", ""))
481
+ example = intercept.get("example", f"htx task {cap_id}")
482
+ return RuleResult(
483
+ severity="context",
484
+ capability=cap_id,
485
+ message=render_box(
486
+ "HIGPERTEXT · Capacidad sugerida",
487
+ [
488
+ f" Comando detectado : {cmd}",
489
+ f" Capacidad : {cap_id}",
490
+ f" Motivo : {reason}",
491
+ f" Uso : {example}",
492
+ ]
493
+ ),
494
+ )
495
+ return None
496
+
497
+
498
+ # ── Regla 9: reglas de perfil externo (desde _rules/profile_rules.json) ──────
499
+
500
+
501
+ def check_profile_rules(cmd: str, root: Path) -> RuleResult | None:
502
+ if not root:
503
+ return None
504
+ rules_file = Path(__file__).parent / "profile_rules.json"
505
+ if not rules_file.exists():
506
+ return None
507
+ try:
508
+ data = json.loads(rules_file.read_text(encoding="utf-8"))
509
+ except (OSError, json.JSONDecodeError):
510
+ return None
511
+ for rule in data.get("rules", []):
512
+ pattern = rule.get("pattern", "")
513
+ if not pattern or not re.search(pattern, cmd):
514
+ continue
515
+ severity = rule.get("severity", "context")
516
+ cap_id = rule.get("capability", "")
517
+ reason = rule.get("reason", "")
518
+ example = rule.get("example", f"htx task {cap_id}" if cap_id else "")
519
+ return RuleResult(
520
+ severity=severity,
521
+ capability=cap_id,
522
+ message=render_box(
523
+ "HIGPERTEXT · Regla de perfil",
524
+ [
525
+ f" Comando detectado : {cmd}",
526
+ *([f" Capacidad : {cap_id}"] if cap_id else []),
527
+ f" Motivo : {reason}",
528
+ *([f" Uso : {example}"] if example else []),
529
+ ]
530
+ ),
531
+ )
532
+ return None
533
+
534
+
535
+ # ── Helpers internos ──────────────────────────────────────────────────────────
536
+
537
+
538
+ def _get_htx(root: Path) -> list[str]:
539
+ import platform
540
+ try:
541
+ from higpertext.kernel.htx_resolver import get_htx_cmd
542
+
543
+ return get_htx_cmd(root)
544
+ except ImportError:
545
+ venv_htx = root / ".venv" / ("Scripts" if platform.system() == "Windows" else "bin") / "htx"
546
+ if venv_htx.exists():
547
+ return [str(venv_htx)]
548
+ if htx := shutil.which("htx"):
549
+ return [htx]
550
+ return [str(root / ".venv" / "bin" / "python"), str(root / "htx.py")]
551
+
552
+
553
+ def _run_higpertext(cap_id: str, params: dict, root: Path) -> str:
554
+ base = _get_htx(root) + ["task", cap_id]
555
+ args = base + [arg for k, v in params.items() for arg in (f"--{k}", str(v))]
556
+ try:
557
+ r = subprocess.run(
558
+ args,
559
+ capture_output=True,
560
+ text=True, # nosec B603
561
+ cwd=str(root),
562
+ timeout=15,
563
+ )
564
+ return (r.stdout or r.stderr or "").strip()
565
+ except Exception as exc:
566
+ return f"[error] {exc}"
567
+
568
+
569
+ def _read_json(path: Path) -> dict:
570
+ try:
571
+ return json.loads(path.read_text(encoding="utf-8"))
572
+ except (OSError, json.JSONDecodeError):
573
+ return {}
574
+
575
+
576
+ def _pending_files(root: Path) -> list[str]:
577
+ import shutil
578
+ git_path = shutil.which("git") or "git"
579
+ try:
580
+ r = subprocess.run(
581
+ [git_path, "status", "--porcelain"], # nosec B603 B607
582
+ capture_output=True,
583
+ text=True,
584
+ cwd=str(root),
585
+ timeout=10,
586
+ )
587
+ return [line[3:] for line in r.stdout.strip().splitlines() if line.strip()][:5]
588
+ except (OSError, subprocess.TimeoutExpired):
589
+ return []
590
+
591
+
592
+ def _find_capabilities_root(root: Path) -> Path:
593
+ candidate = root / "src" / "higpertext" / "capabilities"
594
+ return candidate if candidate.exists() else root
595
+
596
+
597
+ def _find_capabilities_roots(root: Path) -> list[Path]:
598
+ roots: list[Path] = []
599
+ engine = root / "src" / "higpertext" / "capabilities"
600
+ if engine.exists():
601
+ roots.append(engine)
602
+ external = root / WORKSPACE_DIR_NAME / "capabilities"
603
+ if external.exists():
604
+ roots.append(external)
605
+ if not roots:
606
+ roots.append(root)
607
+ return roots
608
+
609
+
610
+ def _close_session(root: Path) -> None:
611
+ try:
612
+ SessionManager(root, root).clean_session()
613
+ except (ImportError, AttributeError, ValueError):
614
+ pass
615
+
616
+ env_file = root / WORKSPACE_DIR_NAME / "config" / "environment.json"
617
+ if env_file.exists():
618
+ try:
619
+ env_data = _read_json(env_file)
620
+ env_data["active_profile"] = "git"
621
+ env_data["active_profiles"] = ["git"]
622
+ env_file.write_text(
623
+ json.dumps(env_data, indent=4, ensure_ascii=False), encoding="utf-8"
624
+ )
625
+ except (OSError, json.JSONDecodeError):
626
+ pass
627
+ for assistant_dir in [".gemini", ".agents", ".claude", ".opencode"]:
628
+ for subdir in ["skills", "subagents"]:
629
+ d = root / assistant_dir / subdir
630
+ if d.exists():
631
+ try:
632
+ shutil.rmtree(d)
633
+ d.mkdir(parents=True, exist_ok=True)
634
+ except OSError: # nosec B110
635
+ pass