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