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,98 @@
1
+ """Python language parser — AST-based extraction."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import ast
6
+ from pathlib import Path
7
+
8
+ from .base import LanguageParser, ParseResult
9
+
10
+ _STDLIB_NOISE = frozenset(
11
+ {
12
+ "sys", "re", "json", "ast", "abc", "io", "copy", "math", "time", "enum",
13
+ "uuid", "glob", "shutil", "fnmatch", "hashlib", "logging", "argparse",
14
+ "pathlib", "datetime", "textwrap", "itertools", "functools", "operator",
15
+ "contextlib", "traceback", "subprocess", "threading", "multiprocessing",
16
+ "tempfile", "collections", "dataclasses", "typing", "types", "inspect",
17
+ "importlib", "warnings", "unittest", "os.path", "pathlib.Path",
18
+ "datetime.datetime", "collections.defaultdict", "typing.Optional",
19
+ "typing.List", "typing.Dict", "typing.Any",
20
+ }
21
+ )
22
+
23
+
24
+ class _ASTVisitor(ast.NodeVisitor):
25
+ def __init__(self) -> None:
26
+ self.classes: list[dict] = []
27
+ self.functions: list[dict] = []
28
+ self.imports: list[str] = []
29
+
30
+ def visit_Import(self, node: ast.Import) -> None:
31
+ for alias in node.names:
32
+ self.imports.append(alias.name)
33
+ self.generic_visit(node)
34
+
35
+ def visit_ImportFrom(self, node: ast.ImportFrom) -> None:
36
+ module = node.module or ""
37
+ for alias in node.names:
38
+ full = f"{module}.{alias.name}" if module else alias.name
39
+ self.imports.append(full)
40
+ self.generic_visit(node)
41
+
42
+ def visit_ClassDef(self, node: ast.ClassDef) -> None:
43
+ methods = [
44
+ {
45
+ "name": n.name,
46
+ "parameters": [a.arg for a in n.args.args],
47
+ "docstring": ast.get_docstring(n) or "",
48
+ }
49
+ for n in node.body
50
+ if isinstance(n, ast.FunctionDef)
51
+ ]
52
+ self.classes.append(
53
+ {
54
+ "name": node.name,
55
+ "docstring": ast.get_docstring(node) or "",
56
+ "methods": methods,
57
+ }
58
+ )
59
+
60
+ def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
61
+ self.functions.append(
62
+ {
63
+ "name": node.name,
64
+ "parameters": [a.arg for a in node.args.args],
65
+ "docstring": ast.get_docstring(node) or "",
66
+ }
67
+ )
68
+ self.generic_visit(node)
69
+
70
+
71
+ def _filter_imports(imports: list[str]) -> list[str]:
72
+ return sorted(
73
+ {imp for imp in imports if imp.split(".")[0] not in _STDLIB_NOISE and imp not in _STDLIB_NOISE}
74
+ )
75
+
76
+
77
+ class PythonParser(LanguageParser):
78
+ """Parses Python files via the stdlib AST."""
79
+
80
+ def parse_file(self, file_path: Path) -> ParseResult:
81
+ try:
82
+ code = file_path.read_text(encoding="utf-8", errors="replace")
83
+ tree = ast.parse(code)
84
+ except (OSError, SyntaxError, ValueError) as exc:
85
+ return self.empty(note=f"Parser warning: {exc}", lang_type="python")
86
+
87
+ visitor = _ASTVisitor()
88
+ visitor.visit(tree)
89
+ is_entry = '__name__ == "__main__"' in code or "__name__ == '__main__'" in code
90
+
91
+ return ParseResult(
92
+ imports=_filter_imports(visitor.imports),
93
+ classes=visitor.classes,
94
+ functions=visitor.functions,
95
+ summary=ast.get_docstring(tree) or "",
96
+ type="python",
97
+ is_entry_point=is_entry,
98
+ )
@@ -0,0 +1,91 @@
1
+ """TypeScript / JavaScript language parser."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from pathlib import Path
7
+
8
+ from .base import LanguageParser, ParseResult
9
+
10
+ _RE_IMPORT_FROM = re.compile(
11
+ r"""import\s+(?:type\s+)?(?:\{[^}]*\}|[\w*]+)\s+from\s+['"]([^'"]+)['"]"""
12
+ )
13
+ _RE_REQUIRE = re.compile(
14
+ r"""(?:const|let|var)\s+([\w]+)\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)"""
15
+ )
16
+ _RE_CLASS = re.compile(r"""(?:export\s+(?:default\s+)?)?class\s+([\w]+)""")
17
+ _RE_FUNC_DECL = re.compile(r"""(?:export\s+)?(?:async\s+)?function\s+([\w]+)\s*\(""")
18
+ _RE_ARROW = re.compile(r"""(?:export\s+)?(?:const|let|var)\s+([\w]+)\s*=\s*(?:async\s*)?\(""")
19
+
20
+
21
+ class TypeScriptParser(LanguageParser):
22
+ """Parses TypeScript/JavaScript files via regex."""
23
+
24
+ def parse_file(self, file_path: Path) -> ParseResult:
25
+ try:
26
+ content = file_path.read_text(encoding="utf-8", errors="replace")
27
+ except OSError:
28
+ return self.empty(lang_type="javascript")
29
+
30
+ imports = list(
31
+ {m.group(1) for m in _RE_IMPORT_FROM.finditer(content)}
32
+ | {m.group(2) for m in _RE_REQUIRE.finditer(content)}
33
+ )
34
+ classes = [
35
+ {"name": m.group(1), "docstring": "", "methods": []}
36
+ for m in _RE_CLASS.finditer(content)
37
+ ]
38
+ functions = [
39
+ {"name": m.group(1), "parameters": [], "docstring": ""}
40
+ for pattern in (_RE_FUNC_DECL, _RE_ARROW)
41
+ for m in pattern.finditer(content)
42
+ ]
43
+ return ParseResult(
44
+ imports=imports,
45
+ classes=classes,
46
+ functions=functions,
47
+ type="javascript",
48
+ )
49
+
50
+ def parse_source(self, source: str, file: str) -> tuple[list, list]:
51
+ """Secondary interface used by semantic_engine Symbol/Relation extraction."""
52
+ from higpertext.kernel.domain.semantic_engine import (
53
+ Relation, RelationType, Symbol, SymbolType,
54
+ )
55
+
56
+ if not source.strip():
57
+ return [], []
58
+
59
+ symbols: list = []
60
+ relations: list = []
61
+ file_sym = Symbol(name=file, type=SymbolType.MODULE, file=file, line=0)
62
+
63
+ for m in _RE_IMPORT_FROM.finditer(source):
64
+ mod = Symbol(name=m.group(1), type=SymbolType.MODULE, file=file,
65
+ line=source[: m.start()].count("\n") + 1)
66
+ if mod not in symbols:
67
+ symbols.append(mod)
68
+ relations.append(Relation(source=file_sym, target=mod, type=RelationType.IMPORTS))
69
+
70
+ for m in _RE_REQUIRE.finditer(source):
71
+ var = m.group(1)
72
+ line = source[: m.start()].count("\n") + 1
73
+ mod = Symbol(name=var, type=SymbolType.MODULE, file=file, line=line)
74
+ if mod not in symbols:
75
+ symbols.append(mod)
76
+ relations.append(Relation(source=file_sym, target=mod, type=RelationType.IMPORTS))
77
+
78
+ for m in _RE_CLASS.finditer(source):
79
+ sym = Symbol(name=m.group(1), type=SymbolType.CLASS, file=file,
80
+ line=source[: m.start()].count("\n") + 1)
81
+ if sym not in symbols:
82
+ symbols.append(sym)
83
+
84
+ for pattern in (_RE_FUNC_DECL, _RE_ARROW):
85
+ for m in pattern.finditer(source):
86
+ sym = Symbol(name=m.group(1), type=SymbolType.FUNCTION, file=file,
87
+ line=source[: m.start()].count("\n") + 1)
88
+ if sym not in symbols:
89
+ symbols.append(sym)
90
+
91
+ return symbols, relations
@@ -0,0 +1,409 @@
1
+ """higpertext Semantic Graph — indexa símbolos y dependencias del proyecto (Infraestructura)."""
2
+
3
+ from __future__ import annotations
4
+ from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME, PROJECT_ROOT
5
+ import os
6
+ import json
7
+ import re
8
+ import fnmatch
9
+ from collections import defaultdict
10
+ from pathlib import Path
11
+ from higpertext.kernel.infrastructure.parser.language import (
12
+ PythonParser,
13
+ TypeScriptParser,
14
+ PowerShellParser,
15
+ ParseResult,
16
+ )
17
+
18
+ # ── Config ────────────────────────────────────────────────────────────────────
19
+ _SG_CONFIG_PATH = PROJECT_ROOT / "src" / "config" / "semantic_graph.json"
20
+ _SG: dict = json.loads(_SG_CONFIG_PATH.read_text(encoding="utf-8"))
21
+ _MD: dict = _SG["markdown"]
22
+
23
+ _IGNORE_FOLDERS = frozenset(_SG["ignore_folders"]) | {WORKSPACE_DIR_NAME}
24
+ _IGNORE_SUFFIXES = frozenset(_SG["ignore_suffixes"])
25
+ _SUPPORTED_SUFFIXES = frozenset(_SG["supported_suffixes"])
26
+
27
+
28
+ class IgnoreChecker:
29
+ """Determina si una ruta debe ser excluida del análisis."""
30
+
31
+ def __init__(self, project_root: Path) -> None:
32
+ self.project_root = project_root
33
+ self.patterns: list[str] = list(_SG["ignore_patterns"])
34
+ for ignore_file in _SG["ignore_files"]:
35
+ self._load_ignore_file(project_root / ignore_file)
36
+
37
+ def _load_ignore_file(self, path: Path) -> None:
38
+ if not path.exists():
39
+ return
40
+ try:
41
+ text = path.read_text(encoding="utf-8", errors="ignore")
42
+ for line in text.splitlines():
43
+ line = line.strip()
44
+ if not line or line.startswith("#"):
45
+ continue
46
+ if line.endswith("/"):
47
+ self.patterns += [line[:-1], line + "*"]
48
+ else:
49
+ self.patterns += [line, line + "/*"]
50
+ except OSError: # nosec B110
51
+ pass
52
+
53
+ def _has_ignored_folder(self, relative_path: str) -> bool:
54
+ return any(part in _IGNORE_FOLDERS for part in Path(relative_path).parts)
55
+
56
+ def _has_ignored_suffix(self, relative_path: str) -> bool:
57
+ name = Path(relative_path).name.lower()
58
+ return any(name.endswith(s) for s in _IGNORE_SUFFIXES)
59
+
60
+ def _has_unsupported_suffix(self, relative_path: str) -> bool:
61
+ suffix = Path(relative_path).suffix.lower()
62
+ return bool(suffix) and suffix not in _SUPPORTED_SUFFIXES
63
+
64
+ def _matches_pattern(self, relative_path: str) -> bool:
65
+ name = Path(relative_path).name
66
+ return any(
67
+ fnmatch.fnmatch(relative_path, p) or fnmatch.fnmatch(name, p) for p in self.patterns
68
+ )
69
+
70
+ def is_ignored(self, relative_path: str) -> bool:
71
+ return (
72
+ self._has_ignored_folder(relative_path)
73
+ or self._has_ignored_suffix(relative_path)
74
+ or self._has_unsupported_suffix(relative_path)
75
+ or self._matches_pattern(relative_path)
76
+ )
77
+
78
+
79
+ _PARSER_CLASSES = {
80
+ "python": PythonParser,
81
+ "powershell": PowerShellParser,
82
+ "typescript": TypeScriptParser,
83
+ }
84
+ _PARSERS: dict[str, object] = {
85
+ ext: _PARSER_CLASSES[name]()
86
+ for ext, name in _SG["parser_map"].items()
87
+ }
88
+
89
+
90
+ class SemanticGraphGenerator:
91
+ """Genera el grafo semántico del proyecto (JSON + Markdown)."""
92
+
93
+ @staticmethod
94
+ def _parse_file(full_path: Path, suffix: str) -> dict:
95
+ parser = _PARSERS.get(suffix)
96
+ if parser is not None:
97
+ result: ParseResult = parser.parse_file(full_path) # type: ignore[union-attr]
98
+ else:
99
+ result = ParseResult(type=suffix.lstrip(".") or "unknown")
100
+ result.size = full_path.stat().st_size
101
+ return result.to_dict()
102
+
103
+ # --- Compatibility shims (delegated to shared parsers) ---
104
+
105
+ @staticmethod
106
+ def parse_python(file_path: Path) -> dict:
107
+ return _PARSERS[".py"].parse_file(file_path).to_dict() # type: ignore[union-attr]
108
+
109
+ @staticmethod
110
+ def parse_powershell(file_path: Path) -> dict:
111
+ return _PARSERS[".ps1"].parse_file(file_path).to_dict() # type: ignore[union-attr]
112
+
113
+ @staticmethod
114
+ def parse_javascript(file_path: Path) -> dict:
115
+ return _PARSERS[".js"].parse_file(file_path).to_dict() # type: ignore[union-attr]
116
+
117
+ @staticmethod
118
+ def resolve_dependencies(files_data: dict) -> list[dict]:
119
+ all_paths = list(files_data.keys())
120
+ deps = []
121
+ for rel_path, data in files_data.items():
122
+ for imp in data.get("imports", []):
123
+ parts = imp.split(".")
124
+ resolved = next(
125
+ (
126
+ f
127
+ for f in all_paths
128
+ if list(Path(f).with_suffix("").parts[-len(parts) :]) == parts
129
+ and f != rel_path
130
+ ),
131
+ None,
132
+ )
133
+ if resolved:
134
+ deps.append({"from": rel_path, "to": resolved})
135
+ return deps
136
+
137
+ @classmethod
138
+ def generate(cls, project_root: Path) -> None:
139
+ project_root = project_root.resolve()
140
+ checker = IgnoreChecker(project_root)
141
+ files_data: dict = {}
142
+
143
+ for root, dirs, files in os.walk(project_root):
144
+ dirs[:] = [d for d in dirs if not checker.is_ignored(str(Path(root) / d))]
145
+ for file in files:
146
+ full_path = Path(root) / file
147
+ rel_path = str(full_path.relative_to(project_root))
148
+ if checker.is_ignored(rel_path):
149
+ continue
150
+ files_data[rel_path] = cls._parse_file(full_path, full_path.suffix.lower())
151
+
152
+ dependencies = cls.resolve_dependencies(files_data)
153
+
154
+ higpertext_dir = project_root / WORKSPACE_DIR_NAME / "state"
155
+ higpertext_dir.mkdir(parents=True, exist_ok=True)
156
+
157
+ json_path = higpertext_dir / "semantic_graph.json"
158
+ graph_data = {
159
+ "generated_at": (json_path.stat().st_mtime if json_path.exists() else None),
160
+ "files": files_data,
161
+ "dependencies": dependencies,
162
+ }
163
+ json_path.write_text(json.dumps(graph_data, indent=4, ensure_ascii=False), encoding="utf-8")
164
+
165
+ cls._write_markdown_graph(higpertext_dir / "semantic_graph.md", files_data, dependencies)
166
+
167
+ # --- Markdown generation helpers ---
168
+
169
+ @staticmethod
170
+ def _layer_map_lines(files_data: dict) -> list[str]:
171
+ layer_counts: dict[str, int] = defaultdict(int)
172
+ for fp, data in files_data.items():
173
+ has_content = data.get("classes") or data.get("functions")
174
+ if not has_content:
175
+ continue
176
+ parts = Path(fp).parts
177
+ layer = parts[0] if len(parts) == 1 else "/".join(parts[:2])
178
+ layer_counts[layer] += 1
179
+
180
+ if not layer_counts:
181
+ return []
182
+
183
+ lines = [_MD["section_layer_map"], ""]
184
+ lines += [_MD["layer_table_header"], _MD["layer_table_sep"]]
185
+ for layer, count in sorted(layer_counts.items(), key=lambda x: -x[1]):
186
+ lines.append(f"| `{layer}` | {count} |")
187
+ lines += ["", "---", ""]
188
+ return lines
189
+
190
+ @staticmethod
191
+ def _entry_points_lines(files_data: dict) -> list[str]:
192
+ entries = [fp for fp, data in sorted(files_data.items()) if data.get("is_entry_point")]
193
+ if not entries:
194
+ return []
195
+ lines = [_MD["section_entry_points"], ""]
196
+ for fp in entries:
197
+ lines.append(f"- [`{fp}`](file:///{fp})")
198
+ lines += ["", "---", ""]
199
+ return lines
200
+
201
+ @staticmethod
202
+ def _mermaid_block(files_data: dict, dependencies: list) -> list[str]:
203
+ layer_deps: set[tuple[str, str]] = set()
204
+ for dep in dependencies:
205
+ from_layer = Path(dep["from"]).parts[0]
206
+ to_layer = Path(dep["to"]).parts[0]
207
+ if from_layer != to_layer:
208
+ layer_deps.add((from_layer, to_layer))
209
+
210
+ if not layer_deps:
211
+ return []
212
+
213
+ lines = [_MD["section_dep_graph"], "```mermaid", "graph TD"]
214
+ seen_nodes: set[str] = set()
215
+ for src, dst in sorted(layer_deps):
216
+ src_id = re.sub(r"\W", "_", src)
217
+ dst_id = re.sub(r"\W", "_", dst)
218
+ if src_id not in seen_nodes:
219
+ lines.append(f' {src_id}["{src}"]')
220
+ seen_nodes.add(src_id)
221
+ if dst_id not in seen_nodes:
222
+ lines.append(f' {dst_id}["{dst}"]')
223
+ seen_nodes.add(dst_id)
224
+ lines.append(f" {src_id} --> {dst_id}")
225
+ lines += ["```", "", "---", ""]
226
+ return lines
227
+
228
+ @staticmethod
229
+ def _render_classes(classes: list) -> list[str]:
230
+ lines = ["#### Classes"]
231
+ for cls in classes:
232
+ doc = f" — *{cls['docstring'].strip()}*" if cls.get("docstring") else ""
233
+ lines.append(f"- **`class {cls['name']}`**{doc}")
234
+ for method in cls.get("methods", []):
235
+ params_str = ", ".join(method["parameters"])
236
+ mdoc = f" — *{method['docstring'].strip()}*" if method.get("docstring") else ""
237
+ lines.append(f" - `def {method['name']}({params_str})`{mdoc}")
238
+ lines.append("")
239
+ return lines
240
+
241
+ @staticmethod
242
+ def _render_functions(functions: list) -> list[str]:
243
+ lines = ["#### Functions"]
244
+ for func in functions:
245
+ params_str = ", ".join(func.get("parameters", []))
246
+ fdoc = f" — *{func['docstring'].strip()}*" if func.get("docstring") else ""
247
+ lines.append(f"- `def {func['name']}({params_str})`{fdoc}")
248
+ lines.append("")
249
+ return lines
250
+
251
+ @staticmethod
252
+ def _file_section(filepath: str, data: dict) -> list[str]:
253
+ has_content = data.get("classes") or data.get("functions") or data.get("summary")
254
+ if not has_content:
255
+ return []
256
+ ftype = data.get("type", "unknown").upper()
257
+ header = f"### 📂 [{filepath}](file:///{filepath}) ({ftype})"
258
+ lines: list[str] = [header]
259
+ if data.get("summary"):
260
+ lines += [f"*{data['summary'].strip()}*", ""]
261
+ if data.get("classes"):
262
+ lines += SemanticGraphGenerator._render_classes(data["classes"])
263
+ if data.get("functions"):
264
+ lines += SemanticGraphGenerator._render_functions(data["functions"])
265
+ if data.get("imports"):
266
+ imports_str = ", ".join(f"`{imp}`" for imp in sorted(data["imports"]))
267
+ lines += [f"**Imports**: {imports_str}", ""]
268
+ lines += ["---", ""]
269
+ return lines
270
+
271
+ @staticmethod
272
+ def _has_symbols(data: dict) -> bool:
273
+ return bool(data.get("classes") or data.get("functions"))
274
+
275
+ @classmethod
276
+ def _write_markdown_graph(cls, md_path: Path, files_data: dict, dependencies: list) -> None:
277
+ symbol_files = {fp: d for fp, d in files_data.items() if cls._has_symbols(d)}
278
+ total = len(files_data)
279
+ with_symbols = len(symbol_files)
280
+
281
+ lines: list[str] = [
282
+ _MD["graph_title"],
283
+ "",
284
+ _MD["graph_description"],
285
+ "",
286
+ _MD["section_overview"],
287
+ f"- **Total indexed files**: {total}",
288
+ f"- **Files with symbols**: {with_symbols}",
289
+ "",
290
+ "---",
291
+ "",
292
+ ]
293
+
294
+ lines += cls._layer_map_lines(files_data)
295
+ lines += cls._entry_points_lines(files_data)
296
+ if dependencies:
297
+ lines += cls._mermaid_block(files_data, dependencies)
298
+
299
+ lines += [_MD["section_files_dir"], ""]
300
+ groups: dict[str, list[str]] = defaultdict(list)
301
+ for fp in sorted(symbol_files):
302
+ parts = Path(fp).parts
303
+ group = "/".join(parts[:2]) if len(parts) > 2 else parts[0]
304
+ groups[group].append(fp)
305
+
306
+ for group in sorted(groups):
307
+ lines += [f"### 📁 `{group}/`", ""]
308
+ is_test_group = any(p in group.lower() for p in _SG["test_group_markers"])
309
+ if is_test_group:
310
+ for filepath in groups[group]:
311
+ lines.append(f"- [`{Path(filepath).name}`](file:///{filepath})")
312
+ lines.append("")
313
+ else:
314
+ for filepath in groups[group]:
315
+ lines += cls._file_section(filepath, symbol_files[filepath])
316
+
317
+ project_root = md_path.parent.parent
318
+ lines += cls._capabilities_index_lines(project_root)
319
+ lines += cls._profiles_index_lines(project_root)
320
+
321
+ md_path.write_text("\n".join(lines), encoding="utf-8")
322
+
323
+ @staticmethod
324
+ def _capabilities_index_lines(project_root: Path) -> list[str]:
325
+ _desc_max = _SG["description_max_length"]
326
+ caps_root = PROJECT_ROOT / _SG["paths"]["capabilities_root"]
327
+ if not caps_root.exists():
328
+ return []
329
+
330
+ rows: list[tuple[str, str, str, str]] = []
331
+ for json_file in sorted(caps_root.rglob("*.json")):
332
+ try:
333
+ data = json.loads(json_file.read_text(encoding="utf-8"))
334
+ except (OSError, json.JSONDecodeError):
335
+ continue
336
+ cap_id = data.get("id", "")
337
+ if not cap_id:
338
+ continue
339
+ description = data.get("description", "")[:_desc_max]
340
+ hook = data.get("hook_task_id", "—")
341
+ entrypoint = data.get("entrypoint", "")
342
+ rows.append((cap_id, description, hook, entrypoint))
343
+
344
+ if not rows:
345
+ return []
346
+
347
+ lines = [
348
+ _MD["section_caps_index"],
349
+ "",
350
+ _MD["caps_description"],
351
+ "",
352
+ _MD["caps_table_header"],
353
+ _MD["caps_table_sep"],
354
+ ]
355
+ for cap_id, desc, hook, entry in rows:
356
+ lines.append(f"| `{cap_id}` | {desc} | `{hook}` | `{entry}` |")
357
+ lines += ["", "---", ""]
358
+ return lines
359
+
360
+ @staticmethod
361
+ def _profiles_index_lines(project_root: Path) -> list[str]:
362
+ profiles_root = PROJECT_ROOT / _SG["paths"]["profiles_root"]
363
+ caps_root = PROJECT_ROOT / _SG["paths"]["capabilities_root"]
364
+ if not profiles_root.exists():
365
+ return []
366
+
367
+ hook_map: dict[str, str] = {}
368
+ for json_file in caps_root.rglob("*.json"):
369
+ try:
370
+ data = json.loads(json_file.read_text(encoding="utf-8"))
371
+ cap_id = data.get("id", "")
372
+ hook = data.get("hook_task_id", "")
373
+ if cap_id and hook:
374
+ hook_map[cap_id] = hook
375
+ except (OSError, json.JSONDecodeError):
376
+ continue
377
+
378
+ lines = [
379
+ _MD["section_profiles"],
380
+ "",
381
+ _MD["profiles_description"],
382
+ "",
383
+ ]
384
+
385
+ for profile_file in sorted(profiles_root.glob("*.json")):
386
+ try:
387
+ profile = json.loads(profile_file.read_text(encoding="utf-8"))
388
+ except (OSError, json.JSONDecodeError):
389
+ continue
390
+
391
+ name = profile.get("name", profile_file.stem)
392
+ desc = profile.get("description", "")
393
+ capabilities = profile.get("capabilities", [])
394
+ active_hooks = sorted({hook_map[c] for c in capabilities if c in hook_map})
395
+
396
+ lines += [
397
+ f"### `{name}`",
398
+ f"*{desc}*",
399
+ "",
400
+ f"**Capabilities** ({len(capabilities)}): "
401
+ + ", ".join(f"`{c}`" for c in capabilities),
402
+ "",
403
+ "**Hooks activos**: "
404
+ + (", ".join(f"`{h}`" for h in active_hooks) if active_hooks else "—"),
405
+ "",
406
+ ]
407
+
408
+ lines += ["---", ""]
409
+ return lines
@@ -0,0 +1 @@
1
+ # Infrastructure presentation init