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,79 @@
|
|
|
1
|
+
"""Regla UserPromptSubmit — inyecta ContextPack ensamblado en cada prompt del usuario.
|
|
2
|
+
|
|
3
|
+
Integra el context_engine con el sistema de hooks para que el modelo reciba
|
|
4
|
+
símbolos relevantes, memorias y esqueletos de archivos en cada turno.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
import os
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
RuleSeverity = Literal["continue", "context", "block"]
|
|
14
|
+
|
|
15
|
+
_DEFAULT_TOKEN_BUDGET = 4_000
|
|
16
|
+
|
|
17
|
+
# Importación al nivel de módulo — permite patch en tests y falla limpio si no existe.
|
|
18
|
+
try:
|
|
19
|
+
from higpertext.kernel.application.context_engine import ContextAssembler
|
|
20
|
+
from higpertext.kernel.domain.context_engine import TaskIntent
|
|
21
|
+
|
|
22
|
+
_ENGINE_AVAILABLE = True
|
|
23
|
+
except ImportError:
|
|
24
|
+
_ENGINE_AVAILABLE = False
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass
|
|
28
|
+
class RuleResult:
|
|
29
|
+
severity: RuleSeverity
|
|
30
|
+
message: str = ""
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def inject_context_pack(root: Path, prompt: str) -> RuleResult | None:
|
|
34
|
+
"""Ensambla un ContextPack a partir del prompt y lo retorna como contexto.
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
RuleResult con el pack en markdown, o None si no hay contenido relevante
|
|
38
|
+
o si el context_engine no está disponible.
|
|
39
|
+
"""
|
|
40
|
+
if not _ENGINE_AVAILABLE:
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
budget = _resolve_budget()
|
|
44
|
+
intent = TaskIntent.from_goal(goal=prompt, task_type="general", token_budget=budget)
|
|
45
|
+
|
|
46
|
+
try:
|
|
47
|
+
assembler = ContextAssembler(project_root=root)
|
|
48
|
+
pack = assembler.assemble(intent)
|
|
49
|
+
except Exception:
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
if not pack.relevant_symbols and not pack.applicable_memories:
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
return RuleResult(severity="context", message=_format_pack(pack))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _resolve_budget() -> int:
|
|
59
|
+
"""Retorna el budget de tokens desde variable de entorno o usa el default."""
|
|
60
|
+
try:
|
|
61
|
+
return int(os.environ.get("HIGPERTEXT_CONTEXT_BUDGET", _DEFAULT_TOKEN_BUDGET))
|
|
62
|
+
except ValueError:
|
|
63
|
+
return _DEFAULT_TOKEN_BUDGET
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _format_pack(pack: object) -> str:
|
|
67
|
+
"""Envuelve el markdown del ContextPack en un banner higpertext."""
|
|
68
|
+
lines = [
|
|
69
|
+
"╔─ HIGPERTEXT · Context Engine — Contexto ensamblado ──────",
|
|
70
|
+
# type: ignore[attr-defined]
|
|
71
|
+
f"│ Tokens estimados : {pack.estimated_tokens:,} / {pack.intent.token_budget:,}",
|
|
72
|
+
f"│ Símbolos : {len(pack.relevant_symbols)}", # type: ignore[attr-defined]
|
|
73
|
+
f"│ Memorias : {len(pack.applicable_memories)}", # type: ignore[attr-defined]
|
|
74
|
+
f"│ Esqueletos : {len(pack.skeletons)}", # type: ignore[attr-defined]
|
|
75
|
+
"╠────────────────────────────────────────────────────────────",
|
|
76
|
+
pack.to_markdown(), # type: ignore[attr-defined]
|
|
77
|
+
"╚───────────────────────────────────────────────────────────",
|
|
78
|
+
]
|
|
79
|
+
return "\n".join(lines)
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"""Reglas de gestión de contexto para PreCompact y PostToolUse.
|
|
2
|
+
|
|
3
|
+
check_context_pressure — preserva estado higpertext crítico antes de compresión.
|
|
4
|
+
check_large_output — avisa al modelo cuando un tool output es demasiado grande.
|
|
5
|
+
check_window_pressure — mide uso acumulado de la ventana de contexto por turno.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Literal
|
|
15
|
+
|
|
16
|
+
RuleSeverity = Literal["continue", "context", "block"]
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _large_output_threshold() -> int:
|
|
20
|
+
"""Umbral de chars para output masivo. Configurable vía HIGPERTEXT_LARGE_OUTPUT_CHARS."""
|
|
21
|
+
try:
|
|
22
|
+
return int(os.environ.get("HIGPERTEXT_LARGE_OUTPUT_CHARS", "5000"))
|
|
23
|
+
except ValueError:
|
|
24
|
+
return 5_000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Mantiene compatibilidad con imports existentes (tests, otros módulos)
|
|
28
|
+
LARGE_OUTPUT_CHARS = _large_output_threshold()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class RuleResult:
|
|
33
|
+
severity: RuleSeverity
|
|
34
|
+
message: str = ""
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
# ── Regla 1: Preservar estado higpertext antes de compresión (PreCompress) ─────────
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def check_context_pressure(root: Path) -> RuleResult:
|
|
41
|
+
"""Emite resumen compacto de sesión para preservarlo tras la compresión."""
|
|
42
|
+
session = _read_json(root / WORKSPACE_DIR_NAME / "state" / "session.json")
|
|
43
|
+
env = _read_json(root / WORKSPACE_DIR_NAME / "config" / "environment.json")
|
|
44
|
+
|
|
45
|
+
sid = session.get("session_id", "—")
|
|
46
|
+
profile = env.get("active_profile", "—")
|
|
47
|
+
asst = env.get("assistant", "claude")
|
|
48
|
+
active = session.get("status") == "active"
|
|
49
|
+
|
|
50
|
+
if not active:
|
|
51
|
+
return RuleResult(severity="continue")
|
|
52
|
+
|
|
53
|
+
skills = _list_dir_names(root / _SKILL_DIRS.get(asst, ".claude/skills"))
|
|
54
|
+
subagents = _list_dir_names(root / _AGENT_DIRS.get(asst, ".claude/subagents"), "*.md")
|
|
55
|
+
|
|
56
|
+
lines = [
|
|
57
|
+
"╔─ HIGPERTEXT · Contexto preservado (pre-compresión) ──────",
|
|
58
|
+
f"│ Sesión : {sid}",
|
|
59
|
+
f"│ Perfil : {profile}",
|
|
60
|
+
f"│ Skills : {', '.join(skills) or '—'}",
|
|
61
|
+
f"│ Agentes : {', '.join(subagents) or '—'}",
|
|
62
|
+
"│ → Continúa usando `htx task <cap>` para",
|
|
63
|
+
"│ invocar capacidades higpertext.",
|
|
64
|
+
"╚───────────────────────────────────────────────────────",
|
|
65
|
+
]
|
|
66
|
+
return RuleResult(severity="context", message="\n".join(lines))
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ── Regla 2: Aviso de output masivo (PostToolUse) ─────────────────────────────
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def check_large_output(payload: dict) -> RuleResult | None:
|
|
73
|
+
"""Avisa si la respuesta de un tool supera el umbral de chars."""
|
|
74
|
+
threshold = _large_output_threshold()
|
|
75
|
+
tool_response = payload.get("tool_response", {})
|
|
76
|
+
output_str = json.dumps(tool_response, ensure_ascii=False)
|
|
77
|
+
if len(output_str) <= threshold:
|
|
78
|
+
return None
|
|
79
|
+
tool = payload.get("tool_name", "unknown")
|
|
80
|
+
n_lines = output_str.count("\n") + 1
|
|
81
|
+
suggestion = _large_output_suggestion(tool)
|
|
82
|
+
return RuleResult(
|
|
83
|
+
severity="context",
|
|
84
|
+
message="\n".join(
|
|
85
|
+
[
|
|
86
|
+
"╔─ HIGPERTEXT · Output masivo detectado ───────────────────",
|
|
87
|
+
f"│ Tool : {tool}",
|
|
88
|
+
f"│ Tamaño : {len(output_str):,} chars"
|
|
89
|
+
f" · ~{n_lines:,} líneas (límite {threshold:,})",
|
|
90
|
+
"│ ⚠ No re-emitas este blob en tu respuesta.",
|
|
91
|
+
"│ Referencia el archivo por path:line. Si necesitas",
|
|
92
|
+
"│ releer, usa offset/limit en vez de leer todo.",
|
|
93
|
+
f"│ → {suggestion}",
|
|
94
|
+
"╚───────────────────────────────────────────────────────",
|
|
95
|
+
]
|
|
96
|
+
),
|
|
97
|
+
)
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def _large_output_suggestion(tool: str) -> str:
|
|
101
|
+
"""Devuelve acción concreta según herramienta que generó output masivo."""
|
|
102
|
+
normalized = tool.lower()
|
|
103
|
+
if normalized == "read":
|
|
104
|
+
return "Usa: htx task common.smart-read --path <archivo> --mode auto"
|
|
105
|
+
if normalized == "bash":
|
|
106
|
+
return "Si fue cat/head/less, usa: htx task common.code-skeletonizer --path <archivo>"
|
|
107
|
+
if "grep" in normalized or normalized == "common.grep-search":
|
|
108
|
+
return "Reduce con --max_results, --max_per_file y --line_limit"
|
|
109
|
+
return "Reduce la salida con filtros, rangos o capacidades smart-read/grep-search"
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
# ── Helpers ───────────────────────────────────────────────────────────────────
|
|
113
|
+
from higpertext.kernel.app_config import SKILL_DIRS as _SKILL_DIRS, AGENT_DIRS as _AGENT_DIRS
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _read_json(path: Path) -> dict:
|
|
117
|
+
try:
|
|
118
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
119
|
+
except (OSError, json.JSONDecodeError):
|
|
120
|
+
return {}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _list_dir_names(path: Path, glob: str = "*") -> list[str]:
|
|
124
|
+
if not path.exists():
|
|
125
|
+
return []
|
|
126
|
+
if glob == "*.md":
|
|
127
|
+
return sorted(f.stem for f in path.glob(glob))
|
|
128
|
+
return sorted(d.name for d in path.iterdir() if d.is_dir())
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# ── Regla 3: Presión de ventana de contexto (PostToolUse) ────────────────────
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def _window_config() -> tuple[int, float, float]:
|
|
135
|
+
"""Retorna (window_limit, warn_pct, critical_pct) desde env vars o defaults."""
|
|
136
|
+
try:
|
|
137
|
+
limit = int(os.environ.get("HIGPERTEXT_WINDOW_LIMIT", "200000"))
|
|
138
|
+
except ValueError:
|
|
139
|
+
limit = 200_000
|
|
140
|
+
try:
|
|
141
|
+
warn = float(os.environ.get("HIGPERTEXT_WARN_PCT", "0.70"))
|
|
142
|
+
except ValueError:
|
|
143
|
+
warn = 0.70
|
|
144
|
+
try:
|
|
145
|
+
critical = float(os.environ.get("HIGPERTEXT_CRITICAL_PCT", "0.90"))
|
|
146
|
+
except ValueError:
|
|
147
|
+
critical = 0.90
|
|
148
|
+
return limit, warn, critical
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def check_window_pressure(payload: dict, root: Path) -> RuleResult | None:
|
|
152
|
+
"""Acumula tokens del tool output y alerta si la ventana supera los umbrales.
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
None si está bajo el umbral de warning.
|
|
156
|
+
RuleResult(context) con banner WARNING entre warn y critical.
|
|
157
|
+
RuleResult(context) con banner CRITICAL si supera el umbral crítico.
|
|
158
|
+
"""
|
|
159
|
+
try:
|
|
160
|
+
from higpertext.kernel.domain.context_engine import (
|
|
161
|
+
load_window_state,
|
|
162
|
+
save_window_state,
|
|
163
|
+
)
|
|
164
|
+
except ImportError:
|
|
165
|
+
return None
|
|
166
|
+
|
|
167
|
+
limit, warn_pct, critical_pct = _window_config()
|
|
168
|
+
output_str = json.dumps(payload.get("tool_response", {}), ensure_ascii=False)
|
|
169
|
+
new_tokens = int(len(output_str) / 3.5)
|
|
170
|
+
|
|
171
|
+
state = load_window_state(root)
|
|
172
|
+
state.add(new_tokens)
|
|
173
|
+
save_window_state(root, state)
|
|
174
|
+
|
|
175
|
+
usage = state.usage_pct(limit)
|
|
176
|
+
if usage < warn_pct:
|
|
177
|
+
return None
|
|
178
|
+
|
|
179
|
+
level = "🔴 CRITICAL" if usage >= critical_pct else "🟡 WARNING"
|
|
180
|
+
action = (
|
|
181
|
+
"Ejecuta /compact para comprimir el historial antes de continuar."
|
|
182
|
+
if usage >= critical_pct
|
|
183
|
+
else "Considera ejecutar /compact pronto."
|
|
184
|
+
)
|
|
185
|
+
return RuleResult(
|
|
186
|
+
severity="context",
|
|
187
|
+
message="\n".join(
|
|
188
|
+
[
|
|
189
|
+
f"╔─ HIGPERTEXT · Ventana de Contexto · {level} ─────────────",
|
|
190
|
+
f"│ Uso estimado : {
|
|
191
|
+
state.accumulated_tokens:,} / {
|
|
192
|
+
limit:,} tokens ({
|
|
193
|
+
usage * 100:.1f}%)",
|
|
194
|
+
f"│ Turno actual : {state.turn}",
|
|
195
|
+
f"│ ⚠ {action}",
|
|
196
|
+
"╚──────────────────────────────────────────────────────────────",
|
|
197
|
+
]
|
|
198
|
+
),
|
|
199
|
+
)
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"""Adaptador de gobernanza — lee contratos de dominio y expone valores para hooks."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import sys
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
DEFAULT_HARD_BLOCKS = [
|
|
8
|
+
(r"\bsudo\b", "sudo no está permitido en este entorno por política de seguridad"),
|
|
9
|
+
(
|
|
10
|
+
r"\bgit\s+push\b",
|
|
11
|
+
"git push es una acción exclusiva del usuario"
|
|
12
|
+
" — el agente no puede publicar cambios al remoto",
|
|
13
|
+
),
|
|
14
|
+
]
|
|
15
|
+
DEFAULT_FUNC_LIMIT = 30
|
|
16
|
+
DEFAULT_CLASS_LIMIT = 200
|
|
17
|
+
DEFAULT_UNCOMMITTED_LIMIT = 5
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _load_domain(root: Path, name: str) -> dict:
|
|
21
|
+
"""Carga un archivo de contrato de dominio via ContractLoader."""
|
|
22
|
+
core_path = str(root / "src")
|
|
23
|
+
if core_path not in sys.path:
|
|
24
|
+
sys.path.insert(0, core_path)
|
|
25
|
+
try:
|
|
26
|
+
from higpertext.kernel.infrastructure import ContractLoader
|
|
27
|
+
|
|
28
|
+
return getattr(ContractLoader(root), f"load_{name}")()
|
|
29
|
+
except Exception:
|
|
30
|
+
return {}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def get_bash_blocks(root: Path) -> list[tuple[str, str]]:
|
|
34
|
+
"""Bloqueos hard de bash: security_guardrails + branching_strategy."""
|
|
35
|
+
blocks: list[tuple[str, str]] = []
|
|
36
|
+
for source in ("security_guardrails", "branching_strategy"):
|
|
37
|
+
data = _load_domain(root, source)
|
|
38
|
+
for entry in data.get("blocked_patterns", []) + data.get("rules", []):
|
|
39
|
+
pattern = entry.get("pattern")
|
|
40
|
+
reason = entry.get("reason", "Comando bloqueado por política de gobernanza")
|
|
41
|
+
if pattern:
|
|
42
|
+
blocks.append((pattern, reason))
|
|
43
|
+
return blocks or DEFAULT_HARD_BLOCKS
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def get_deployment_blocks(root: Path) -> list[tuple[str, str, str]]:
|
|
47
|
+
"""Retorna (pattern, reason, severity) desde deployment_gates.json."""
|
|
48
|
+
data = _load_domain(root, "deployment_gates")
|
|
49
|
+
result = []
|
|
50
|
+
for entry in data.get("blocked_patterns", []):
|
|
51
|
+
pattern = entry.get("pattern")
|
|
52
|
+
reason = entry.get("reason", "Acción de deployment bloqueada por gobernanza")
|
|
53
|
+
severity = entry.get("severity", "warn")
|
|
54
|
+
if pattern:
|
|
55
|
+
result.append((pattern, reason, severity))
|
|
56
|
+
return result
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_code_limits(root: Path) -> tuple[int, int]:
|
|
60
|
+
"""Retorna (max_function_lines, max_class_lines) desde quality_gates.json."""
|
|
61
|
+
data = _load_domain(root, "quality_gates")
|
|
62
|
+
limits = data.get("code_quality_limits", {})
|
|
63
|
+
return (
|
|
64
|
+
limits.get("max_function_lines", DEFAULT_FUNC_LIMIT),
|
|
65
|
+
limits.get("max_class_lines", DEFAULT_CLASS_LIMIT),
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def get_session_limits(root: Path) -> int:
|
|
70
|
+
"""Retorna max_uncommitted_files desde quality_gates.json."""
|
|
71
|
+
data = _load_domain(root, "quality_gates")
|
|
72
|
+
return data.get("gitflow_limits", {}).get("max_uncommitted_files", DEFAULT_UNCOMMITTED_LIMIT)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rules": [
|
|
3
|
+
{
|
|
4
|
+
"pattern": "\\bgit\\s+commit\\b",
|
|
5
|
+
"severity": "block",
|
|
6
|
+
"capability": "git.committer",
|
|
7
|
+
"reason": "Los commits directos omiten el flujo de gobernanza: conventional commits, stage selectivo, validación de cobertura y registro de telemetría. Usa la capacidad git.committer que aplica todas estas reglas automáticamente.",
|
|
8
|
+
"example": "htx task git.committer --message \"feat(scope): description\""
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"pattern": "\\bpip\\s+install\\b|\\bpip3\\s+install\\b",
|
|
12
|
+
"severity": "block",
|
|
13
|
+
"capability": "",
|
|
14
|
+
"reason": "Las instalaciones de paquetes deben hacerse en el entorno controlado .venv. Usa '.venv/bin/pip install <paquete>' o gestiona dependencias desde pyproject.toml.",
|
|
15
|
+
"example": ".venv/bin/pip install <paquete>"
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
"pattern": "\\bpython\\s+(?!htx\\.py)\\S+\\.py\\b|\\bpython3\\s+(?!htx\\.py)\\S+\\.py\\b",
|
|
19
|
+
"severity": "context",
|
|
20
|
+
"capability": "common.higpertext-tester",
|
|
21
|
+
"reason": "Ejecutar scripts Python directamente evita el runtime del motor. Usa 'htx task' para lanzar capacidades o 'htx workflow run' para pipelines.",
|
|
22
|
+
"example": "htx task common.higpertext-tester --test <módulo>"
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
"""Reglas de evaluación para escritura de código (PreToolUse:Write|Edit).
|
|
2
|
+
|
|
3
|
+
Cada función retorna una RuleResult o None si no aplica.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
from .governance_adapter import get_code_limits
|
|
8
|
+
import re
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
RuleSeverity = Literal["continue", "context", "block"]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
_PYTHON_FILE = re.compile(r"\.py$")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class RuleResult:
|
|
21
|
+
severity: RuleSeverity
|
|
22
|
+
message: str = ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def is_python_file(file_path: str) -> bool:
|
|
26
|
+
return bool(_PYTHON_FILE.search(file_path))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
# ── Regla 1: Longitud de funciones y clases ───────────────────────────────────
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def check_code_length(file_path: str, root: Path) -> RuleResult | None:
|
|
33
|
+
func_limit, class_limit = get_code_limits(root)
|
|
34
|
+
violations = _scan_file(file_path, func_limit, class_limit)
|
|
35
|
+
if not violations:
|
|
36
|
+
return None
|
|
37
|
+
msg = "[HIGPERTEXT QUALITY GATE] Violaciones detectadas:\n" + "\n".join(violations)
|
|
38
|
+
return RuleResult(severity="context", message=msg)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _check_func_limit(in_func: bool, current_line: int, func_start: int, func_limit: int, func_name: str, violations: list[str]) -> None:
|
|
42
|
+
if in_func and (current_line - func_start) > func_limit:
|
|
43
|
+
violations.append(
|
|
44
|
+
f" función '{func_name}' excede {func_limit} líneas (línea {func_start})"
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _check_class_limit(in_class: bool, current_line: int, class_start: int, class_limit: int, class_name: str, violations: list[str]) -> None:
|
|
49
|
+
if in_class and (current_line - class_start) > class_limit:
|
|
50
|
+
violations.append(
|
|
51
|
+
f" clase '{class_name}' excede {class_limit} líneas (línea {class_start})"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _scan_file(filepath: str, func_limit: int, class_limit: int) -> list[str]:
|
|
56
|
+
violations: list[str] = []
|
|
57
|
+
try:
|
|
58
|
+
lines = Path(filepath).read_text(encoding="utf-8").splitlines()
|
|
59
|
+
except OSError:
|
|
60
|
+
return violations
|
|
61
|
+
|
|
62
|
+
in_func = False
|
|
63
|
+
in_class = False
|
|
64
|
+
func_start = 0
|
|
65
|
+
class_start = 0
|
|
66
|
+
func_name = ""
|
|
67
|
+
class_name = ""
|
|
68
|
+
|
|
69
|
+
total = len(lines)
|
|
70
|
+
for i, line in enumerate(lines, 1):
|
|
71
|
+
m_func = re.match(r"\s*def\s+(\w+)", line)
|
|
72
|
+
m_class = re.match(r"\s*class\s+(\w+)", line)
|
|
73
|
+
|
|
74
|
+
if m_func:
|
|
75
|
+
_check_func_limit(in_func, i, func_start, func_limit, func_name, violations)
|
|
76
|
+
in_func, func_start, func_name = True, i, m_func.group(1)
|
|
77
|
+
|
|
78
|
+
elif m_class:
|
|
79
|
+
_check_class_limit(in_class, i, class_start, class_limit, class_name, violations)
|
|
80
|
+
in_class, class_start, class_name = True, i, m_class.group(1)
|
|
81
|
+
|
|
82
|
+
# Chequeo final: última función/clase del archivo
|
|
83
|
+
_check_func_limit(in_func, total + 1, func_start, func_limit, func_name, violations)
|
|
84
|
+
_check_class_limit(in_class, total + 1, class_start, class_limit, class_name, violations)
|
|
85
|
+
|
|
86
|
+
return violations
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""Reglas de seguridad comunes para hooks de asistentes higpertext."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import re
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from typing import Any, Literal
|
|
10
|
+
|
|
11
|
+
RuleSeverity = Literal["continue", "context", "warn", "ask", "block"]
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass
|
|
15
|
+
class RuleResult:
|
|
16
|
+
severity: RuleSeverity
|
|
17
|
+
message: str = ""
|
|
18
|
+
replacement_output: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
_SENSITIVE_PATH_PATTERNS = [
|
|
22
|
+
r"(^|/)\.env(\..*)?$",
|
|
23
|
+
r"(^|/)\.npmrc$",
|
|
24
|
+
r"(^|/)\.pypirc$",
|
|
25
|
+
r"(^|/)\.netrc$",
|
|
26
|
+
r"(^|/)id_(rsa|dsa|ecdsa|ed25519)$",
|
|
27
|
+
r"\.(pem|key|p12|pfx|crt)$",
|
|
28
|
+
r"(^|/)(secrets?|credentials?)(/|\.|$)",
|
|
29
|
+
r"(^|/)\.aws/credentials$",
|
|
30
|
+
r"(^|/)\.azure(/|$)",
|
|
31
|
+
r"(^|/)\.kube/config$",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def evaluate_command_guard(command: str, root: Path) -> RuleResult | None:
|
|
36
|
+
"""Evalúa comandos peligrosos definidos por gobernanza."""
|
|
37
|
+
if not command:
|
|
38
|
+
return None
|
|
39
|
+
for pattern, reason, severity in _command_rules(root):
|
|
40
|
+
if re.search(pattern, command, re.IGNORECASE):
|
|
41
|
+
normalized = _normalize_severity(severity)
|
|
42
|
+
return RuleResult(
|
|
43
|
+
severity=normalized,
|
|
44
|
+
message=_format_command_message(command, reason, normalized),
|
|
45
|
+
)
|
|
46
|
+
return None
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def evaluate_path_guard(tool_name: str, tool_input: dict[str, Any]) -> RuleResult | None:
|
|
50
|
+
"""Bloquea acceso a rutas sensibles desde Read/Write/Edit."""
|
|
51
|
+
path = _extract_path(tool_input)
|
|
52
|
+
if not path or not _is_sensitive_path(path):
|
|
53
|
+
return None
|
|
54
|
+
return RuleResult(
|
|
55
|
+
severity="block",
|
|
56
|
+
message="\n".join(
|
|
57
|
+
[
|
|
58
|
+
"╔─ HIGPERTEXT · Security Guard ───────────────────────────",
|
|
59
|
+
f"│ ✗ {tool_name} sobre ruta sensible bloqueado.",
|
|
60
|
+
f"│ Ruta: {path}",
|
|
61
|
+
"│ Usa una capacidad segura o pide aprobación humana explícita.",
|
|
62
|
+
"╚────────────────────────────────────────────────────────────",
|
|
63
|
+
]
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def mask_tool_output(tool_response: Any, root: Path) -> RuleResult | None:
|
|
69
|
+
"""Enmascara secretos presentes en outputs de herramientas."""
|
|
70
|
+
output = _stringify_response(tool_response)
|
|
71
|
+
if not output:
|
|
72
|
+
return None
|
|
73
|
+
masked = output
|
|
74
|
+
for pattern, replacement in _masking_patterns(root):
|
|
75
|
+
masked = re.sub(pattern, replacement, masked, flags=re.IGNORECASE | re.DOTALL)
|
|
76
|
+
if masked == output:
|
|
77
|
+
return None
|
|
78
|
+
return RuleResult(
|
|
79
|
+
severity="context",
|
|
80
|
+
message="[HIGPERTEXT SECURITY] Se enmascararon secretos detectados en el output.",
|
|
81
|
+
replacement_output=masked,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def _command_rules(root: Path) -> list[tuple[str, str, str]]:
|
|
86
|
+
data = _load_security_guardrails(root)
|
|
87
|
+
entries = [
|
|
88
|
+
(
|
|
89
|
+
item.get("pattern", ""),
|
|
90
|
+
item.get("reason", "Comando restringido por seguridad"),
|
|
91
|
+
item.get("severity", "block"),
|
|
92
|
+
)
|
|
93
|
+
for item in data.get("blocked_patterns", [])
|
|
94
|
+
if item.get("pattern")
|
|
95
|
+
]
|
|
96
|
+
entries.extend(
|
|
97
|
+
(
|
|
98
|
+
item.get("pattern", ""),
|
|
99
|
+
item.get("reason", "Acción requiere aprobación humana explícita"),
|
|
100
|
+
"ask",
|
|
101
|
+
)
|
|
102
|
+
for item in data.get("approval_patterns", [])
|
|
103
|
+
if item.get("pattern")
|
|
104
|
+
)
|
|
105
|
+
entries.extend(
|
|
106
|
+
(
|
|
107
|
+
item.get("pattern", ""),
|
|
108
|
+
item.get("reason", "Acción potencialmente riesgosa"),
|
|
109
|
+
item.get("severity", "warn"),
|
|
110
|
+
)
|
|
111
|
+
for item in data.get("warning_patterns", [])
|
|
112
|
+
if item.get("pattern")
|
|
113
|
+
)
|
|
114
|
+
forbidden = data.get("guardrails", {}).get("forbidden_commands", [])
|
|
115
|
+
entries.extend(
|
|
116
|
+
(
|
|
117
|
+
re.escape(command),
|
|
118
|
+
f"'{command}' está prohibido por política de seguridad",
|
|
119
|
+
"block",
|
|
120
|
+
)
|
|
121
|
+
for command in forbidden
|
|
122
|
+
if command
|
|
123
|
+
)
|
|
124
|
+
return entries
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def _normalize_severity(value: str) -> RuleSeverity:
|
|
128
|
+
lowered = str(value).lower().strip()
|
|
129
|
+
if lowered in {"warn", "warning", "context"}:
|
|
130
|
+
return "warn"
|
|
131
|
+
if lowered in {"ask", "approval", "human_approval"}:
|
|
132
|
+
return "ask"
|
|
133
|
+
return "block"
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _format_command_message(command: str, reason: str, severity: RuleSeverity) -> str:
|
|
137
|
+
if severity == "warn":
|
|
138
|
+
marker = "⚠"
|
|
139
|
+
action = "Advertencia: la acción continuará, pero queda registrada."
|
|
140
|
+
elif severity == "ask":
|
|
141
|
+
marker = "?"
|
|
142
|
+
action = "Requiere aprobación humana explícita antes de ejecutar."
|
|
143
|
+
else:
|
|
144
|
+
marker = "✗"
|
|
145
|
+
action = "Comando bloqueado por política de seguridad."
|
|
146
|
+
return "\n".join(
|
|
147
|
+
[
|
|
148
|
+
"╔─ HIGPERTEXT · Security Guard ───────────────────────────",
|
|
149
|
+
f"│ {marker} {reason}",
|
|
150
|
+
f"│ Severidad: {severity}",
|
|
151
|
+
f"│ Acción : {action}",
|
|
152
|
+
f"│ Comando : {command}",
|
|
153
|
+
"╚────────────────────────────────────────────────────────────",
|
|
154
|
+
]
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _masking_patterns(root: Path) -> list[tuple[str, str]]:
|
|
159
|
+
data = _load_security_guardrails(root)
|
|
160
|
+
configured = data.get("data_masking", {}).get("patterns", [])
|
|
161
|
+
patterns = [
|
|
162
|
+
(item.get("regex", ""), item.get("mask", "[MASKED]"))
|
|
163
|
+
for item in configured
|
|
164
|
+
if item.get("regex")
|
|
165
|
+
]
|
|
166
|
+
patterns.extend(
|
|
167
|
+
[
|
|
168
|
+
(
|
|
169
|
+
r"(?i)(token|password|secret|api[_-]?key)\s*[:=]\s*['\"]?[^\s'\"]{8,}",
|
|
170
|
+
r"\1=********[MASKED]",
|
|
171
|
+
),
|
|
172
|
+
(r"(?i)bearer\s+[a-z0-9._\-]{20,}", "Bearer ********[MASKED]"),
|
|
173
|
+
]
|
|
174
|
+
)
|
|
175
|
+
return patterns
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _load_security_guardrails(root: Path) -> dict[str, Any]:
|
|
179
|
+
path = root / "src" / "config" / "governance" / "security_guardrails.json"
|
|
180
|
+
try:
|
|
181
|
+
return json.loads(path.read_text(encoding="utf-8"))
|
|
182
|
+
except (OSError, json.JSONDecodeError):
|
|
183
|
+
return {}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _extract_path(tool_input: dict[str, Any]) -> str:
|
|
187
|
+
return str(
|
|
188
|
+
tool_input.get("filePath")
|
|
189
|
+
or tool_input.get("filepath")
|
|
190
|
+
or tool_input.get("path")
|
|
191
|
+
or tool_input.get("file_path")
|
|
192
|
+
or tool_input.get("file")
|
|
193
|
+
or ""
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
|
|
197
|
+
def _is_sensitive_path(path: str) -> bool:
|
|
198
|
+
normalized = path.replace("\\", "/")
|
|
199
|
+
return any(
|
|
200
|
+
re.search(pattern, normalized, re.IGNORECASE) for pattern in _SENSITIVE_PATH_PATTERNS
|
|
201
|
+
)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def _stringify_response(tool_response: Any) -> str:
|
|
205
|
+
if isinstance(tool_response, str):
|
|
206
|
+
return tool_response
|
|
207
|
+
if isinstance(tool_response, dict):
|
|
208
|
+
for key in ("output", "content", "text", "stdout"):
|
|
209
|
+
if key in tool_response and isinstance(tool_response[key], str):
|
|
210
|
+
return tool_response[key]
|
|
211
|
+
try:
|
|
212
|
+
return json.dumps(tool_response, ensure_ascii=False)
|
|
213
|
+
except TypeError:
|
|
214
|
+
return str(tool_response)
|