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