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,303 @@
1
+ """Context Engine Infrastructure layer — GraphIndex, PackStore, skeletonizer, and TelemetryReader."""
2
+
3
+ from __future__ import annotations
4
+ import ast
5
+ import json
6
+ import re
7
+ from pathlib import Path
8
+ from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
9
+ from higpertext.kernel.domain.context_engine import SymbolRef, ContextPack
10
+
11
+ # Patrones del formato generado por SemanticGraphGenerator.
12
+
13
+
14
+ def _parse_file_line(line: str) -> str | None:
15
+ prefix = "### 📂 ["
16
+ if not line.startswith(prefix):
17
+ return None
18
+ end = line.find("]", len(prefix))
19
+ return line[len(prefix):end] if end != -1 else None
20
+
21
+
22
+ def _parse_doc_suffix(line: str) -> str:
23
+ marker = "— *"
24
+ start = line.find(marker)
25
+ if start == -1:
26
+ return ""
27
+ doc_start = start + len(marker)
28
+ doc_end = line.find("*", doc_start)
29
+ return line[doc_start:doc_end].strip() if doc_end != -1 else ""
30
+
31
+
32
+ def _parse_symbol_line(line: str, prefix: str, kind: str) -> tuple[str, str, str] | None:
33
+ stripped = line.strip()
34
+ if not stripped.startswith(prefix):
35
+ return None
36
+ name_start = len(prefix)
37
+ name_end = stripped.find("`", name_start)
38
+ if name_end == -1:
39
+ return None
40
+ signature = stripped[name_start:name_end]
41
+ name = signature.split("(", 1)[0].strip()
42
+ return (name, kind, _parse_doc_suffix(stripped)) if name.isidentifier() else None
43
+
44
+
45
+ class GraphIndex:
46
+ """Lee el semantic graph y expone búsqueda de símbolos por keyword."""
47
+
48
+ def __init__(self, project_root: Path) -> None:
49
+ self._json_path = project_root / WORKSPACE_DIR_NAME / "state" / "semantic_graph.json"
50
+ self._md_path = project_root / WORKSPACE_DIR_NAME / "state" / "semantic_graph.md"
51
+ self._symbols: list[SymbolRef] = []
52
+ self._parse()
53
+
54
+ def _parse(self) -> None:
55
+ if self._json_path.exists():
56
+ self._parse_json()
57
+ return
58
+ self._parse_markdown()
59
+
60
+ def _parse_json(self) -> None:
61
+ raw = json.loads(self._json_path.read_text(encoding="utf-8"))
62
+ if "symbols" in raw:
63
+ self._parse_symbols_json(raw["symbols"])
64
+ return
65
+ self._parse_files_json(raw.get("files", {}))
66
+
67
+ def _parse_symbols_json(self, symbols: list[dict]) -> None:
68
+ for symbol in symbols:
69
+ self._symbols.append(
70
+ SymbolRef(
71
+ file=symbol.get("file", ""),
72
+ name=symbol.get("name", ""),
73
+ kind=symbol.get("type", "symbol"),
74
+ doc=symbol.get("docstring", ""),
75
+ )
76
+ )
77
+
78
+ def _parse_files_json(self, files: dict) -> None:
79
+ for file_path, data in files.items():
80
+ for cls in data.get("classes", []):
81
+ self._symbols.append(
82
+ SymbolRef(
83
+ file=file_path,
84
+ name=cls.get("name", ""),
85
+ kind="class",
86
+ doc=cls.get("docstring", ""),
87
+ )
88
+ )
89
+ for method in cls.get("methods", []):
90
+ self._symbols.append(
91
+ SymbolRef(
92
+ file=file_path,
93
+ name=method.get("name", ""),
94
+ kind="method",
95
+ doc=method.get("docstring", ""),
96
+ )
97
+ )
98
+ for func in data.get("functions", []):
99
+ self._symbols.append(
100
+ SymbolRef(
101
+ file=file_path,
102
+ name=func.get("name", ""),
103
+ kind="function",
104
+ doc=func.get("docstring", ""),
105
+ )
106
+ )
107
+
108
+ def _parse_markdown(self) -> None:
109
+ if not self._md_path.exists():
110
+ return
111
+ current_file = ""
112
+ for line in self._md_path.read_text(encoding="utf-8").splitlines():
113
+ parsed_file = _parse_file_line(line)
114
+ if parsed_file:
115
+ current_file = parsed_file
116
+ continue
117
+ if not current_file:
118
+ continue
119
+ parsed = _parse_symbol_line(line, "- `def ", "method")
120
+ if parsed and line[:1].isspace():
121
+ name, kind, doc = parsed
122
+ self._symbols.append(SymbolRef(file=current_file, name=name, kind=kind, doc=doc))
123
+ continue
124
+ parsed = _parse_symbol_line(line, "- **`class ", "class")
125
+ if parsed:
126
+ name, kind, doc = parsed
127
+ self._symbols.append(SymbolRef(file=current_file, name=name, kind=kind, doc=doc))
128
+ continue
129
+ parsed = _parse_symbol_line(line, "- `def ", "function")
130
+ if parsed:
131
+ name, kind, doc = parsed
132
+ self._symbols.append(SymbolRef(file=current_file, name=name, kind=kind, doc=doc))
133
+
134
+ def all_symbols(self) -> list[SymbolRef]:
135
+ return list(self._symbols)
136
+
137
+ def search(self, keywords: list[str]) -> list[SymbolRef]:
138
+ """Devuelve símbolos cuyo nombre, archivo o doc contiene algún keyword."""
139
+ if not keywords:
140
+ return []
141
+ kws = [k.lower() for k in keywords]
142
+ hits: list[SymbolRef] = []
143
+ for s in self._symbols:
144
+ haystack = f"{s.name} {s.file} {s.doc}".lower()
145
+ if any(k in haystack for k in kws):
146
+ hits.append(s)
147
+ return hits
148
+
149
+
150
+ class PackStore:
151
+ """Guarda y referencia context packs como artefactos Markdown en disco."""
152
+
153
+ def __init__(self, project_root: Path) -> None:
154
+ self._dir = project_root / WORKSPACE_DIR_NAME / "state" / "context_packs"
155
+
156
+ def save(self, pack: ContextPack) -> Path:
157
+ self._dir.mkdir(parents=True, exist_ok=True)
158
+ task_id = self._slug(pack.intent.goal)
159
+ path = self._dir / f"{task_id}.md"
160
+ path.write_text(pack.to_markdown(), encoding="utf-8")
161
+ return path
162
+
163
+ @staticmethod
164
+ def _slug(goal: str) -> str:
165
+ slug = re.sub(r"[^a-z0-9]+", "-", goal.lower()).strip("-")
166
+ return slug[:50] or "context-pack"
167
+
168
+
169
+ # Umbral a partir del cual un archivo se considera 'grande' y candidato a esqueletización.
170
+ _SKELETON_THRESHOLD_CHARS = 3000
171
+
172
+
173
+ def should_skeletonize(source: str) -> bool:
174
+ """Indica si un archivo supera el umbral para ser esqueletizado."""
175
+ return len(source) > _SKELETON_THRESHOLD_CHARS
176
+
177
+
178
+ def extract_skeleton(source: str) -> str:
179
+ """Extrae firmas de clases y funciones de un fragmento de código Python."""
180
+ try:
181
+ return _extract_via_ast(source)
182
+ except SyntaxError:
183
+ return _extract_via_regex(source)
184
+
185
+
186
+ def _extract_via_ast(source: str) -> str:
187
+ """Extrae firmas usando el módulo ast estándar de Python."""
188
+ tree = ast.parse(source)
189
+ lines: list[str] = []
190
+ for node in ast.walk(tree):
191
+ if isinstance(node, ast.ClassDef):
192
+ bases = ", ".join(ast.unparse(b) for b in node.bases)
193
+ header = f"class {node.name}({bases}):" if bases else f"class {node.name}:"
194
+ lines.append(header)
195
+ elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
196
+ prefix = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def"
197
+ args = ast.unparse(node.args)
198
+ ret = f" -> {ast.unparse(node.returns)}" if node.returns else ""
199
+ lines.append(f" {prefix} {node.name}({args}){ret}: ...")
200
+ return "\n".join(lines)
201
+
202
+
203
+ def _extract_via_regex(source: str) -> str:
204
+ """Fallback lineal: extrae líneas que definen clases o funciones sin regex."""
205
+ signatures: list[str] = []
206
+ for line in source.splitlines():
207
+ stripped = line.strip()
208
+ if not stripped.endswith(":"):
209
+ continue
210
+ if _is_signature_line(stripped):
211
+ signatures.append(f"{stripped} ...")
212
+ return "\n".join(signatures)
213
+
214
+
215
+ def _is_signature_line(line: str) -> bool:
216
+ if line.startswith("class "):
217
+ name = line.removeprefix("class ").split("(", 1)[0].removesuffix(":").strip()
218
+ return name.isidentifier()
219
+ if line.startswith("async def "):
220
+ name = line.removeprefix("async def ").split("(", 1)[0].strip()
221
+ return _has_balanced_signature_tail(line) and name.isidentifier()
222
+ if line.startswith("def "):
223
+ name = line.removeprefix("def ").split("(", 1)[0].strip()
224
+ return _has_balanced_signature_tail(line) and name.isidentifier()
225
+ return False
226
+
227
+
228
+ def _has_balanced_signature_tail(line: str) -> bool:
229
+ open_index = line.find("(")
230
+ close_index = line.rfind(")")
231
+ colon_index = line.rfind(":")
232
+ return open_index != -1 and open_index < close_index < colon_index
233
+
234
+
235
+ class TelemetryReader:
236
+ """Lee telemetry.jsonl y expone queries por sesión."""
237
+
238
+ def __init__(self, project_root: Path) -> None:
239
+ self._path = project_root / WORKSPACE_DIR_NAME / "state" / "telemetry.jsonl"
240
+
241
+ def _all_events(self) -> list[dict]:
242
+ if not self._path.exists():
243
+ return []
244
+ events = []
245
+ for line in self._path.read_text(encoding="utf-8").splitlines():
246
+ line = line.strip()
247
+ if not line:
248
+ continue
249
+ try:
250
+ events.append(json.loads(line))
251
+ except json.JSONDecodeError:
252
+ continue
253
+ return events
254
+
255
+ def events_for_session(self, session_id: str) -> list[dict]:
256
+ return [e for e in self._all_events() if e.get("session_id") == session_id]
257
+
258
+ def total_tokens(self, session_id: str) -> int:
259
+ return sum(
260
+ e.get("estimated_tokens", 0)
261
+ for e in self.events_for_session(session_id)
262
+ if e.get("event") == "tool_call"
263
+ )
264
+
265
+ def total_cost(self, session_id: str) -> float:
266
+ return sum(
267
+ e.get("estimated_cost_usd", 0.0)
268
+ for e in self.events_for_session(session_id)
269
+ if e.get("event") == "tool_call"
270
+ )
271
+
272
+ def exploration_reads(self, session_id: str) -> int:
273
+ """Lecturas de archivo sin higpertext_related=True (exploración ciega)."""
274
+ return sum(
275
+ 1
276
+ for e in self.events_for_session(session_id)
277
+ if e.get("event") == "activity"
278
+ and e.get("op_type") == "file-read"
279
+ and not e.get("higpertext_related", False)
280
+ )
281
+
282
+ def higpertext_reads(self, session_id: str) -> int:
283
+ """Lecturas de archivo con higpertext_related=True (uso correcto)."""
284
+ return sum(
285
+ 1
286
+ for e in self.events_for_session(session_id)
287
+ if e.get("event") == "activity"
288
+ and e.get("op_type") == "file-read"
289
+ and e.get("higpertext_related", False)
290
+ )
291
+
292
+ def hook_intercepts(self, session_id: str) -> int:
293
+ return sum(
294
+ 1 for e in self.events_for_session(session_id) if e.get("event") == "hook_intercept"
295
+ )
296
+
297
+ def files_read(self, session_id: str) -> set[str]:
298
+ """Conjunto de archivos leídos en la sesión (targets de activity)."""
299
+ return {
300
+ e["target"]
301
+ for e in self.events_for_session(session_id)
302
+ if e.get("event") == "activity" and e.get("target")
303
+ }
@@ -0,0 +1,99 @@
1
+ """LocalVectorStore - Base de datos vectorial persistida en archivo JSON."""
2
+
3
+ from __future__ import annotations
4
+ import json
5
+ import math
6
+ from pathlib import Path
7
+ from higpertext.kernel.domain.rag import IVectorStore, VectorEntry, DocumentChunk
8
+
9
+
10
+ class LocalVectorStore(IVectorStore):
11
+ """Almacén vectorial en JSON local con búsqueda por similitud coseno."""
12
+
13
+ def __init__(self, project_root: Path) -> None:
14
+ self.project_root = project_root
15
+ self.store_file = project_root / ".higpertext" / "state" / "vector_store.json"
16
+ self.entries: list[VectorEntry] = []
17
+
18
+ def add_entries(self, entries: list[VectorEntry]) -> None:
19
+ self.entries.extend(entries)
20
+
21
+ def search(
22
+ self, query_vector: list[float], limit: int = 5
23
+ ) -> list[tuple[DocumentChunk, float]]:
24
+ if not self.entries or not query_vector:
25
+ return []
26
+
27
+ results: list[tuple[DocumentChunk, float]] = []
28
+ for entry in self.entries:
29
+ score = self._cosine_similarity(query_vector, entry.embedding)
30
+ results.append((entry.chunk, score))
31
+
32
+ # Ordenar por score de similitud descendente
33
+ results.sort(key=lambda x: x[1], reverse=True)
34
+ return results[:limit]
35
+
36
+ def clear(self) -> None:
37
+ self.entries = []
38
+
39
+ def save(self) -> None:
40
+ self.store_file.parent.mkdir(parents=True, exist_ok=True)
41
+ serializable_data = []
42
+ for entry in self.entries:
43
+ serializable_data.append(
44
+ {
45
+ "chunk": {
46
+ "file": entry.chunk.file,
47
+ "content": entry.chunk.content,
48
+ "chunk_type": entry.chunk.chunk_type,
49
+ "start_line": entry.chunk.start_line,
50
+ "end_line": entry.chunk.end_line,
51
+ "metadata": entry.chunk.metadata,
52
+ },
53
+ "embedding": entry.embedding,
54
+ }
55
+ )
56
+
57
+ tmp_file = self.store_file.with_suffix(".tmp")
58
+ tmp_file.write_text(
59
+ json.dumps(serializable_data, indent=2, ensure_ascii=False),
60
+ encoding="utf-8",
61
+ )
62
+ tmp_file.replace(self.store_file)
63
+
64
+ def load(self) -> None:
65
+ if not self.store_file.exists():
66
+ self.entries = []
67
+ return
68
+
69
+ try:
70
+ data = json.loads(self.store_file.read_text(encoding="utf-8"))
71
+ self.entries = []
72
+ for item in data:
73
+ c = item["chunk"]
74
+ chunk = DocumentChunk(
75
+ file=c["file"],
76
+ content=c["content"],
77
+ chunk_type=c["chunk_type"],
78
+ start_line=c["start_line"],
79
+ end_line=c["end_line"],
80
+ metadata=c.get("metadata", {}),
81
+ )
82
+ self.entries.append(VectorEntry(chunk=chunk, embedding=item["embedding"]))
83
+ except (json.JSONDecodeError, KeyError, OSError):
84
+ self.entries = []
85
+
86
+ @staticmethod
87
+ def _cosine_similarity(v1: list[float], v2: list[float]) -> float:
88
+ """Calcula la similitud coseno entre dos vectores."""
89
+ if len(v1) != len(v2) or not v1:
90
+ return 0.0
91
+
92
+ dot_product = sum(a * b for a, b in zip(v1, v2))
93
+ norm_a = math.sqrt(sum(a * a for a in v1))
94
+ norm_b = math.sqrt(sum(b * b for b in v2))
95
+
96
+ if norm_a == 0.0 or norm_b == 0.0:
97
+ return 0.0
98
+
99
+ return dot_product / (norm_a * norm_b)
@@ -0,0 +1 @@
1
+ # Infrastructure deployment init
@@ -0,0 +1,283 @@
1
+ """ResourceDeployer — despliega y restaura skills, subagentes y playbooks (Infraestructura)."""
2
+
3
+ from __future__ import annotations
4
+ import json
5
+ from pathlib import Path
6
+ from higpertext.kernel.infrastructure.presentation.markdown_renderer import (
7
+ MarkdownRenderer,
8
+ )
9
+ from higpertext.kernel.pkg_resources import resolve_resource
10
+
11
+ from higpertext.kernel.infrastructure.logger import get_logger
12
+ from higpertext.kernel.app_config import (
13
+ PLAYBOOKS as _PLAYBOOKS,
14
+ AGENT_JSON as _AGENT_JSON,
15
+ NO_SKILLS_MSG as _NO_SKILLS_MSG,
16
+ NO_SUBAGENTS_MSG as _NO_SUBAGENTS_MSG,
17
+ ANTIGRAVITY_AGENT_TEMPLATE as _AGT,
18
+ )
19
+ _log = get_logger()
20
+
21
+
22
+ class ResourceDeployer:
23
+ """Responsabilidad única: copiar/eliminar recursos efímeros de sesión."""
24
+
25
+ def __init__(self, project_root: Path, root_dir: Path) -> None:
26
+ self.project_root = project_root
27
+ self.root_dir = root_dir
28
+
29
+ def _resolve_template_dirs(self, subdir: str) -> list[Path]:
30
+ from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
31
+ dirs = []
32
+ agent_templates = self.project_root / WORKSPACE_DIR_NAME / "agent_templates" / subdir
33
+ if agent_templates.exists():
34
+ dirs.append(agent_templates)
35
+ dirs.append(resolve_resource(self.root_dir, "src", "higpertext", "templates", subdir))
36
+ return dirs
37
+
38
+ def deploy_skills(self, skills: list, skills_dest: Path) -> None:
39
+ if not skills:
40
+ return
41
+ skills_dirs = self._resolve_template_dirs("skills")
42
+ skills_dest.mkdir(parents=True, exist_ok=True)
43
+
44
+ # Construir índice de rglob de todos los directorios ordenados por prioridad
45
+ index = {}
46
+ for d in reversed(skills_dirs):
47
+ if d.exists():
48
+ for f in d.rglob("*.json"):
49
+ index[f.parent.name] = f
50
+ # También buscar archivos Markdown directos
51
+ for f in d.rglob("*.md"):
52
+ index[f.stem] = f
53
+
54
+ for name in skills:
55
+ src = index.get(name)
56
+ if not (src and src.exists()):
57
+ continue
58
+ try:
59
+ dest = skills_dest / name
60
+ dest.mkdir(parents=True, exist_ok=True)
61
+ if src.suffix == ".json":
62
+ data = json.loads(src.read_text(encoding="utf-8"))
63
+ (dest / "SKILL.md").write_text(data.get("content", ""), encoding="utf-8")
64
+ else:
65
+ # Si es Markdown directo (.md)
66
+ (dest / "SKILL.md").write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
67
+ except (OSError, json.JSONDecodeError) as e:
68
+ _log.warning(f"[!] Error generando skill '{name}': {e}")
69
+
70
+ def deploy_subagents(self, subagents: list, subagents_dest: Path, skills_str: str = "") -> None:
71
+ if not subagents:
72
+ return
73
+ subagents_dirs = self._resolve_template_dirs("subagents")
74
+ subagents_dest.mkdir(parents=True, exist_ok=True)
75
+
76
+ index = {}
77
+ for d in reversed(subagents_dirs):
78
+ if d.exists():
79
+ for f in d.rglob("*.json"):
80
+ index[f.stem] = f
81
+ for f in d.rglob("*.md"):
82
+ index[f.stem] = f
83
+
84
+ is_antigravity = subagents_dest.name == "agents"
85
+ for name in subagents:
86
+ src = index.get(name)
87
+ if not (src and src.exists()):
88
+ continue
89
+ try:
90
+ if src.suffix == ".json":
91
+ self._process_subagent_file(src, subagents_dest, name, is_antigravity, skills_str)
92
+ else:
93
+ # Si es Markdown directo (.md)
94
+ if is_antigravity:
95
+ # Generar el JSON mock de Antigravity
96
+ mock_data = {
97
+ "description": f"Subagent {name}",
98
+ "system_prompt": src.read_text(encoding="utf-8")
99
+ }
100
+ self._generate_antigravity_agent_json(subagents_dest, name, mock_data)
101
+ else:
102
+ (subagents_dest / f"{name}.md").write_text(src.read_text(encoding="utf-8"), encoding="utf-8")
103
+ except (OSError, json.JSONDecodeError) as e:
104
+ _log.warning(f"[!] Error generando subagente '{name}': {e}")
105
+
106
+ def _process_subagent_file(
107
+ self, src: Path, subagents_dest: Path, name: str, is_antigravity: bool, skills_str: str
108
+ ) -> None:
109
+ data = json.loads(src.read_text(encoding="utf-8"))
110
+ if is_antigravity:
111
+ self._generate_antigravity_agent_json(subagents_dest, name, data)
112
+ else:
113
+ md = MarkdownRenderer.generate_subagent_md(data, skills_str)
114
+ (subagents_dest / f"{name}.md").write_text(md, encoding="utf-8")
115
+
116
+ def _generate_antigravity_agent_json(
117
+ self, subagents_dest: Path, name: str, data: dict
118
+ ) -> None:
119
+ agent_dir = subagents_dest / name
120
+ agent_dir.mkdir(parents=True, exist_ok=True)
121
+ system_prompt = self._build_antigravity_system_prompt(data)
122
+ agent_json = {
123
+ "name": name,
124
+ "description": data.get("description", ""),
125
+ "hidden": _AGT["hidden"],
126
+ "config": {
127
+ "customAgent": {
128
+ "systemPromptSections": [
129
+ {
130
+ "title": _AGT["system_prompt_section_title"],
131
+ "content": system_prompt.strip(),
132
+ }
133
+ ],
134
+ "toolNames": _AGT["tool_names"],
135
+ "systemPromptConfig": _AGT["system_prompt_config"],
136
+ }
137
+ },
138
+ }
139
+ (agent_dir / _AGENT_JSON).write_text(
140
+ json.dumps(agent_json, indent=2, ensure_ascii=False),
141
+ encoding="utf-8",
142
+ )
143
+
144
+ @staticmethod
145
+ def _build_antigravity_system_prompt(data: dict) -> str:
146
+ system_prompt = f"{data.get('description', '')}\n\n"
147
+ for entry in _AGT["system_prompt_keys"]:
148
+ key, title = entry["key"], entry["title"]
149
+ if key in data:
150
+ system_prompt += f"{title}\n" + "\n".join(f"- {x}" for x in data[key]) + "\n\n"
151
+ return system_prompt
152
+
153
+ def deploy_playbooks(
154
+ self,
155
+ workflows_dest: Path,
156
+ skills_str: str,
157
+ subagents_str: str,
158
+ session_id: str,
159
+ assistant: str = "gemini",
160
+ get_model_fn=None,
161
+ get_profile_fn=None,
162
+ ) -> None:
163
+ tmpl_dir = resolve_resource(self.root_dir, "src", "higpertext", "templates", "workflows")
164
+ workflows_dest.mkdir(parents=True, exist_ok=True)
165
+ session_block = (
166
+ f"\n# Session Info\n- **Session ID**: `{session_id}`\n- **Status**: `active`"
167
+ )
168
+ for name in _PLAYBOOKS:
169
+ src = tmpl_dir / f"{name}.json"
170
+ if not src.exists():
171
+ continue
172
+ try:
173
+ data = json.loads(src.read_text(encoding="utf-8"))
174
+ profile_model = self._resolve_model(data, assistant, get_model_fn, get_profile_fn)
175
+ content = MarkdownRenderer.generate_workflow_base_md(data, assistant, profile_model)
176
+ content = content.replace("{required_skills_str}", skills_str)
177
+ content = content.replace("{related_subagents_str}", subagents_str)
178
+ content += session_block
179
+ (workflows_dest / f"{name}.md").write_text(content, encoding="utf-8")
180
+ except (OSError, json.JSONDecodeError) as e:
181
+ _log.warning(f"[!] Error compilando playbook '{name}': {e}")
182
+
183
+ def restore_playbooks(
184
+ self,
185
+ workflows_dest: Path,
186
+ assistant: str = "gemini",
187
+ get_model_fn=None,
188
+ get_profile_fn=None,
189
+ ) -> None:
190
+ tmpl_dir = resolve_resource(self.root_dir, "src", "higpertext", "templates", "workflows")
191
+ if not (tmpl_dir.exists() and workflows_dest.exists()):
192
+ return
193
+ for name in _PLAYBOOKS:
194
+ src = tmpl_dir / f"{name}.json"
195
+ dest = workflows_dest / f"{name}.md"
196
+ if not src.exists():
197
+ continue
198
+ try:
199
+ data = json.loads(src.read_text(encoding="utf-8"))
200
+ profile_model = self._resolve_model(data, assistant, get_model_fn, get_profile_fn)
201
+ content = MarkdownRenderer.generate_workflow_base_md(data, assistant, profile_model)
202
+ content = content.replace("{required_skills_str}", _NO_SKILLS_MSG)
203
+ content = content.replace("{related_subagents_str}", _NO_SUBAGENTS_MSG)
204
+ dest.write_text(content, encoding="utf-8")
205
+ except (OSError, json.JSONDecodeError) as e:
206
+ _log.warning(f"[!] Error restaurando playbook '{name}': {e}")
207
+
208
+ def build_resource_links(
209
+ self,
210
+ skills: list,
211
+ subagents: list,
212
+ skills_dest: Path,
213
+ subagents_dest: Path,
214
+ ) -> tuple[str, str]:
215
+ tmpl_skills = resolve_resource(self.root_dir, "src", "higpertext", "templates", "skills")
216
+ tmpl_subagents = resolve_resource(
217
+ self.root_dir, "src", "higpertext", "templates", "subagents"
218
+ )
219
+ skill_index = (
220
+ {f.parent.name for f in tmpl_skills.rglob("*.json")} if tmpl_skills.exists() else set()
221
+ )
222
+ agent_index = (
223
+ {f.stem for f in tmpl_subagents.rglob("*.json")} if tmpl_subagents.exists() else set()
224
+ )
225
+
226
+ def _skill_link(name: str) -> str:
227
+ d = skills_dest / name
228
+ try:
229
+ rel = d.relative_to(self.project_root).as_posix()
230
+ return f"- [{name}](file:///{rel}/SKILL.md)"
231
+ except ValueError:
232
+ return f"- `{name}`"
233
+
234
+ def _subagent_link(name: str) -> str:
235
+ is_antigravity = subagents_dest.name == "agents"
236
+ f = (
237
+ subagents_dest / name / _AGENT_JSON
238
+ if is_antigravity
239
+ else subagents_dest / f"{name}.md"
240
+ )
241
+ try:
242
+ rel = f.relative_to(self.project_root).as_posix()
243
+ return f"- [{name}](file:///{rel})"
244
+ except ValueError:
245
+ return f"- `{name}`"
246
+
247
+ skills_lines = [
248
+ (_skill_link(s) if (s in skill_index or (skills_dest / s).exists()) else f"- `{s}`")
249
+ for s in sorted(skills)
250
+ ]
251
+ sub_lines = [
252
+ (
253
+ _subagent_link(sa)
254
+ if (
255
+ sa in agent_index
256
+ or (subagents_dest / f"{sa}.md").exists()
257
+ or (subagents_dest / sa / _AGENT_JSON).exists()
258
+ )
259
+ else f"- `{sa}`"
260
+ )
261
+ for sa in sorted(subagents)
262
+ ]
263
+ skills_str = "\n".join(skills_lines) if skills_lines else "- `00-quick-reference`"
264
+ subs_str = "\n".join(sub_lines) if sub_lines else "- `research`"
265
+ return skills_str, subs_str
266
+
267
+ @staticmethod
268
+ def _resolve_model(data: dict, assistant: str, get_model_fn, get_profile_fn) -> str:
269
+ if get_profile_fn:
270
+ try:
271
+ profile_data = get_profile_fn()
272
+ if profile_data and "model" in profile_data:
273
+ return profile_data["model"]
274
+ except Exception: # nosec B110
275
+ pass
276
+ if get_model_fn:
277
+ try:
278
+ model = get_model_fn(assistant)
279
+ if model:
280
+ return model
281
+ except Exception: # nosec B110
282
+ pass
283
+ return data.get("model", "")
@@ -0,0 +1 @@
1
+ # Infrastructure diagnostics init