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,86 @@
|
|
|
1
|
+
"""Construcción de comandos de subproceso para capacidades."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import shutil
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def extra_args(params: dict, prefix: str = "--") -> list[str]:
|
|
13
|
+
return [arg for key, value in params.items() for arg in [f"{prefix}{key}", str(value)]]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def resolve_entrypoint(
|
|
17
|
+
entrypoint: str,
|
|
18
|
+
pkg_data_dir: Path,
|
|
19
|
+
workspace_root: Path,
|
|
20
|
+
base_dir: str = "",
|
|
21
|
+
) -> Path:
|
|
22
|
+
"""Resuelve el path del entrypoint manteniendo prioridad legacy."""
|
|
23
|
+
ep = Path(entrypoint)
|
|
24
|
+
if ep.is_absolute() and ep.exists():
|
|
25
|
+
return ep
|
|
26
|
+
if base_dir:
|
|
27
|
+
candidate = Path(base_dir).parent.parent / entrypoint
|
|
28
|
+
if candidate.exists():
|
|
29
|
+
return candidate
|
|
30
|
+
higpertext_relative = workspace_root / WORKSPACE_DIR_NAME / entrypoint
|
|
31
|
+
if higpertext_relative.exists():
|
|
32
|
+
return higpertext_relative
|
|
33
|
+
cwd_relative = workspace_root / entrypoint
|
|
34
|
+
if cwd_relative.exists():
|
|
35
|
+
return cwd_relative
|
|
36
|
+
return pkg_data_dir / entrypoint
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def build_command(
|
|
40
|
+
entrypoint: str,
|
|
41
|
+
language: str,
|
|
42
|
+
params: dict,
|
|
43
|
+
root_dir: Path,
|
|
44
|
+
pkg_data_dir: Path,
|
|
45
|
+
workspace_root: Path,
|
|
46
|
+
base_dir: str = "",
|
|
47
|
+
cap_id: str = "",
|
|
48
|
+
) -> list[str]:
|
|
49
|
+
"""Construye la lista de argumentos del subproceso."""
|
|
50
|
+
args = extra_args(params)
|
|
51
|
+
if language == "powershell":
|
|
52
|
+
path_to_script = resolve_entrypoint(entrypoint, pkg_data_dir, workspace_root, base_dir)
|
|
53
|
+
return build_powershell_command(path_to_script, params)
|
|
54
|
+
if language == "cli":
|
|
55
|
+
return entrypoint.split() + args
|
|
56
|
+
if language == "bash":
|
|
57
|
+
path_to_script = resolve_entrypoint(entrypoint, pkg_data_dir, workspace_root, base_dir)
|
|
58
|
+
script = entrypoint if Path(entrypoint).is_absolute() else str(path_to_script)
|
|
59
|
+
return ["bash", script] + args
|
|
60
|
+
|
|
61
|
+
htx_bin = _resolve_htx_bin(root_dir, pkg_data_dir)
|
|
62
|
+
runner = pkg_data_dir / "higpertext" / "capabilities" / "capabilities_runner.py"
|
|
63
|
+
if cap_id and runner.exists() and entrypoint.startswith("capabilities/"):
|
|
64
|
+
if Path(htx_bin).exists():
|
|
65
|
+
return [htx_bin, "task", cap_id] + args
|
|
66
|
+
return [sys.executable, str(runner), cap_id] + args
|
|
67
|
+
path_to_script = resolve_entrypoint(entrypoint, pkg_data_dir, workspace_root, base_dir)
|
|
68
|
+
return [sys.executable, str(path_to_script)] + args
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def build_powershell_command(path_to_script: Path, params: dict) -> list[str]:
|
|
72
|
+
pwsh = "powershell" if shutil.which("powershell") else "pwsh"
|
|
73
|
+
return [
|
|
74
|
+
pwsh,
|
|
75
|
+
"-ExecutionPolicy",
|
|
76
|
+
"Bypass",
|
|
77
|
+
"-File",
|
|
78
|
+
str(path_to_script),
|
|
79
|
+
] + extra_args(params, prefix="-")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _resolve_htx_bin(root_dir: Path, pkg_data_dir: Path) -> str:
|
|
83
|
+
_ = root_dir
|
|
84
|
+
venv_htx = pkg_data_dir.parents[1] / ".venv" / "bin" / "htx"
|
|
85
|
+
|
|
86
|
+
return str(venv_htx) if venv_htx.exists() else (shutil.which("htx") or str(venv_htx))
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
|
|
2
|
+
"""Servicio de infraestructura para ejecutar capacidades desde el CLI."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
# Runner central usa listas de argumentos y shell=False.
|
|
9
|
+
import subprocess # nosec B404
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from higpertext.kernel.infrastructure.cli.capability_command_builder import build_command
|
|
13
|
+
from higpertext.kernel.infrastructure.cli.task_result_reporter import (
|
|
14
|
+
build_memory_notes,
|
|
15
|
+
report_task_result,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CapabilityTaskService:
|
|
20
|
+
"""Resuelve, ejecuta y reporta capacidades técnicas."""
|
|
21
|
+
|
|
22
|
+
def __init__(self, engine, root_dir: Path, pkg_data_dir: Path, logger, dep) -> None:
|
|
23
|
+
self.engine = engine
|
|
24
|
+
self.root_dir = root_dir
|
|
25
|
+
self.pkg_data_dir = pkg_data_dir
|
|
26
|
+
self.logger = logger
|
|
27
|
+
self.dep = dep
|
|
28
|
+
|
|
29
|
+
def resolve_from_profiles(self, task_name: str, profiles: list[str]) -> tuple[dict | None, str | None]:
|
|
30
|
+
for profile_name in profiles:
|
|
31
|
+
ctx = self.engine.get_agent_context(profile_name)
|
|
32
|
+
cap = next(
|
|
33
|
+
(c for c in ctx["active_capabilities"] if c["id"].endswith(task_name)),
|
|
34
|
+
None,
|
|
35
|
+
)
|
|
36
|
+
if cap:
|
|
37
|
+
return cap, profile_name
|
|
38
|
+
return None, None
|
|
39
|
+
|
|
40
|
+
def resolve_custom_capability(self, task_name: str) -> tuple[dict | None, str | None]:
|
|
41
|
+
try:
|
|
42
|
+
cap = self.engine.capabilities.load_capability(task_name)
|
|
43
|
+
custom_dir = self.engine.capabilities._get_custom_capabilities_dir()
|
|
44
|
+
cap_file = next(
|
|
45
|
+
(
|
|
46
|
+
f
|
|
47
|
+
for f in self.engine.capabilities._get_all_capability_files()
|
|
48
|
+
if f.stem.endswith(task_name)
|
|
49
|
+
),
|
|
50
|
+
None,
|
|
51
|
+
)
|
|
52
|
+
if cap_file and custom_dir in cap_file.parents:
|
|
53
|
+
return cap, "custom"
|
|
54
|
+
except (OSError, StopIteration, AttributeError): # nosec B110
|
|
55
|
+
pass
|
|
56
|
+
return None, None
|
|
57
|
+
|
|
58
|
+
def resolve_capability(self, task_name: str, active_profile: str) -> tuple[dict | None, str | None]:
|
|
59
|
+
profiles = [active_profile, "global"] if active_profile != "global" else ["global"]
|
|
60
|
+
cap, profile_name = self.resolve_from_profiles(task_name, profiles)
|
|
61
|
+
if cap:
|
|
62
|
+
return cap, profile_name
|
|
63
|
+
return self.resolve_custom_capability(task_name)
|
|
64
|
+
|
|
65
|
+
def build_cmd(
|
|
66
|
+
self,
|
|
67
|
+
entrypoint: str,
|
|
68
|
+
language: str,
|
|
69
|
+
params: dict,
|
|
70
|
+
base_dir: str = "",
|
|
71
|
+
cap_id: str = "",
|
|
72
|
+
) -> list[str]:
|
|
73
|
+
return build_command(
|
|
74
|
+
entrypoint,
|
|
75
|
+
language,
|
|
76
|
+
params,
|
|
77
|
+
self.root_dir,
|
|
78
|
+
self.pkg_data_dir,
|
|
79
|
+
Path(os.getcwd()),
|
|
80
|
+
base_dir=base_dir,
|
|
81
|
+
cap_id=cap_id,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
def get_active_profile(self) -> str:
|
|
85
|
+
try:
|
|
86
|
+
env_mgr = self.dep("EnvironmentManager")(Path.cwd())
|
|
87
|
+
if env_mgr.is_initialized():
|
|
88
|
+
return env_mgr.load_environment().get("active_profile", "global")
|
|
89
|
+
except (OSError, json.JSONDecodeError): # nosec B110
|
|
90
|
+
pass
|
|
91
|
+
return "global"
|
|
92
|
+
|
|
93
|
+
def execute_task_cmd(
|
|
94
|
+
self, task_name: str, target_cap: dict, params: dict, stream_runner, stream: bool = False
|
|
95
|
+
) -> subprocess.CompletedProcess:
|
|
96
|
+
cap_id = target_cap.get("id", task_name)
|
|
97
|
+
cmd = self.build_cmd(
|
|
98
|
+
target_cap["entrypoint"],
|
|
99
|
+
target_cap.get("language", "python"),
|
|
100
|
+
params,
|
|
101
|
+
base_dir=target_cap.get("_base_dir", ""),
|
|
102
|
+
cap_id=cap_id,
|
|
103
|
+
)
|
|
104
|
+
entrypoint = target_cap.get("entrypoint", "")
|
|
105
|
+
|
|
106
|
+
cache = self.dep("get_capability_cache")()
|
|
107
|
+
cached = cache.get(cap_id, params, entrypoint)
|
|
108
|
+
if cached is not None:
|
|
109
|
+
self.logger.info(f"[Cache] HIT para '{cap_id}' — usando resultado previo.")
|
|
110
|
+
return subprocess.CompletedProcess(
|
|
111
|
+
args=cmd,
|
|
112
|
+
returncode=cached.returncode,
|
|
113
|
+
stdout=cached.stdout,
|
|
114
|
+
stderr=cached.stderr,
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
self.logger.info(f"[>] Ejecutando: {' '.join(cmd)}")
|
|
118
|
+
resilience = self.dep("get_resilience_manager")()
|
|
119
|
+
max_retries = target_cap.get("retries", 1)
|
|
120
|
+
|
|
121
|
+
def _run_once() -> subprocess.CompletedProcess:
|
|
122
|
+
if stream:
|
|
123
|
+
return stream_runner(cmd)
|
|
124
|
+
return subprocess.run( # nosec B603 B607
|
|
125
|
+
cmd, cwd=os.getcwd(), capture_output=True, text=True, check=False
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
result = resilience.execute_with_resilience(
|
|
129
|
+
capability_id=cap_id,
|
|
130
|
+
fn=_run_once,
|
|
131
|
+
max_retries=max_retries,
|
|
132
|
+
)
|
|
133
|
+
if result is None:
|
|
134
|
+
return subprocess.CompletedProcess(
|
|
135
|
+
args=cmd,
|
|
136
|
+
returncode=1,
|
|
137
|
+
stdout="",
|
|
138
|
+
stderr=f"[Circuit] '{cap_id}' bloqueado por circuit breaker.",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
cache.set(
|
|
142
|
+
cap_id,
|
|
143
|
+
params,
|
|
144
|
+
stdout=result.stdout,
|
|
145
|
+
stderr=result.stderr,
|
|
146
|
+
returncode=result.returncode,
|
|
147
|
+
entrypoint=entrypoint,
|
|
148
|
+
)
|
|
149
|
+
return result
|
|
150
|
+
|
|
151
|
+
def validate_contract(
|
|
152
|
+
self, target_cap: dict, params: dict, result: subprocess.CompletedProcess
|
|
153
|
+
) -> tuple[bool, list]:
|
|
154
|
+
if result.returncode != 0:
|
|
155
|
+
return True, []
|
|
156
|
+
return self.dep("ContractValidator").validate(
|
|
157
|
+
params, result.stdout, result.stderr, result.returncode, target_cap
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
def save_memory(
|
|
161
|
+
self,
|
|
162
|
+
task_name: str,
|
|
163
|
+
params: dict,
|
|
164
|
+
result: subprocess.CompletedProcess,
|
|
165
|
+
contract_success: bool,
|
|
166
|
+
contract_errors: list,
|
|
167
|
+
) -> None:
|
|
168
|
+
try:
|
|
169
|
+
mem_script = (
|
|
170
|
+
self.pkg_data_dir
|
|
171
|
+
/ "higpertext"
|
|
172
|
+
/ "capabilities"
|
|
173
|
+
/ "common"
|
|
174
|
+
/ "scripts"
|
|
175
|
+
/ "memory_manager.py"
|
|
176
|
+
)
|
|
177
|
+
status = "success" if result.returncode == 0 and contract_success else "failure"
|
|
178
|
+
notes = build_memory_notes(task_name, params, result, contract_success, contract_errors)
|
|
179
|
+
subprocess.run( # nosec B603 B607
|
|
180
|
+
[
|
|
181
|
+
"python",
|
|
182
|
+
str(mem_script),
|
|
183
|
+
"--action",
|
|
184
|
+
f"Auto-run: {task_name}",
|
|
185
|
+
"--status",
|
|
186
|
+
status,
|
|
187
|
+
"--notes",
|
|
188
|
+
notes,
|
|
189
|
+
],
|
|
190
|
+
capture_output=True,
|
|
191
|
+
check=False,
|
|
192
|
+
)
|
|
193
|
+
except OSError as mem_e:
|
|
194
|
+
self.logger.warning(f"[WARNING] Falló guardado de memoria: {mem_e}")
|
|
195
|
+
|
|
196
|
+
def warn_missing_pat(self, task_name: str) -> None:
|
|
197
|
+
if "ado" in task_name or "sre" in task_name and not os.getenv("ADO_PAT"):
|
|
198
|
+
self.logger.warning("[WARNING] No se detectó ADO_PAT en .env.")
|
|
199
|
+
|
|
200
|
+
def run_task(self, task_name: str, params: dict, stream_runner, stream: bool = False, no_cache: bool = False) -> bool:
|
|
201
|
+
try:
|
|
202
|
+
active_profile = self.get_active_profile()
|
|
203
|
+
target_cap, found_profile = self.resolve_capability(task_name, active_profile)
|
|
204
|
+
if not target_cap:
|
|
205
|
+
self.logger.warning(
|
|
206
|
+
f"[!] Capacidad '{task_name}' no disponible para el perfil '{active_profile.upper()}'."
|
|
207
|
+
)
|
|
208
|
+
return False
|
|
209
|
+
self.logger.info(f"[*] Capacidad '{task_name}' resuelta: {found_profile.upper()}")
|
|
210
|
+
validation = self.dep("normalize_and_validate_params")(target_cap, params)
|
|
211
|
+
if not validation.ok:
|
|
212
|
+
self.logger.error(f"[ERROR] Parámetros inválidos para '{task_name}':")
|
|
213
|
+
for error in validation.errors:
|
|
214
|
+
self.logger.info(f" - {error}")
|
|
215
|
+
return False
|
|
216
|
+
for warning in validation.warnings:
|
|
217
|
+
self.logger.warning(f"[PARAM] {warning}")
|
|
218
|
+
params = validation.params
|
|
219
|
+
self.warn_missing_pat(task_name)
|
|
220
|
+
self.logger.info(f"[*] Ejecutando tarea: {task_name}")
|
|
221
|
+
|
|
222
|
+
if no_cache:
|
|
223
|
+
cache = self.dep("get_capability_cache")()
|
|
224
|
+
cache.invalidate(target_cap.get("id", task_name))
|
|
225
|
+
|
|
226
|
+
result = self.execute_task_cmd(task_name, target_cap, params, stream_runner, stream=stream)
|
|
227
|
+
ok, errors = self.validate_contract(target_cap, params, result)
|
|
228
|
+
status = report_task_result(task_name, result, ok, errors, self.logger)
|
|
229
|
+
if task_name != "memory-manager":
|
|
230
|
+
self.save_memory(task_name, params, result, ok, errors)
|
|
231
|
+
return status
|
|
232
|
+
except (OSError, ValueError) as exc:
|
|
233
|
+
self.logger.warning(f"[!] Error inesperado: {exc}")
|
|
234
|
+
return False
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
"""higpertext CLI — Knowledge search mixin (Infraestructura)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
from collections.abc import Iterable
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
10
|
+
_log = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class HigpertextSearchMixin:
|
|
14
|
+
"""Provides knowledge-base search capabilities to HigpertextHub."""
|
|
15
|
+
|
|
16
|
+
def _search_guidelines_contract(self, full_path: Path, query_terms: Iterable[str]) -> list:
|
|
17
|
+
matches = []
|
|
18
|
+
try:
|
|
19
|
+
data = json.loads(full_path.read_text(encoding="utf-8"))
|
|
20
|
+
for section, rules in data.get("guidelines", {}).items():
|
|
21
|
+
for rule in rules:
|
|
22
|
+
if any(t in rule.lower() or t in section.lower() for t in query_terms):
|
|
23
|
+
label = section.upper().replace("_", " ")
|
|
24
|
+
card = f"⚖️ [CONTRATO: {label}]\n" f" Regla: {rule}"
|
|
25
|
+
matches.append((0, card))
|
|
26
|
+
except (OSError, json.JSONDecodeError): # nosec B110
|
|
27
|
+
pass
|
|
28
|
+
return matches
|
|
29
|
+
|
|
30
|
+
def _search_learnings_json(self, full_path: Path, query_terms: Iterable[str]) -> list:
|
|
31
|
+
matches = []
|
|
32
|
+
try:
|
|
33
|
+
data = json.loads(full_path.read_text(encoding="utf-8"))
|
|
34
|
+
if not isinstance(data, dict):
|
|
35
|
+
return matches
|
|
36
|
+
for tag, entries in data.items():
|
|
37
|
+
for entry in entries:
|
|
38
|
+
blob = self._entry_blob(entry, [tag])
|
|
39
|
+
if any(t in blob for t in query_terms):
|
|
40
|
+
learned = ", ".join(entry.get("learned", []))
|
|
41
|
+
card = (
|
|
42
|
+
f"💡 [MEMORIA: APRENDIZAJE] (Tag: {tag})\n"
|
|
43
|
+
f" Acción: {entry.get('action')} "
|
|
44
|
+
f"({entry.get('action_id')})\n"
|
|
45
|
+
f" Aprendido: {learned or 'N/A'}\n"
|
|
46
|
+
f" Notas: {entry.get('notes')}"
|
|
47
|
+
)
|
|
48
|
+
matches.append((0, card))
|
|
49
|
+
except (OSError, json.JSONDecodeError): # nosec B110
|
|
50
|
+
pass
|
|
51
|
+
return matches
|
|
52
|
+
|
|
53
|
+
def _search_journal_json(self, full_path: Path, query_terms: Iterable[str]) -> list:
|
|
54
|
+
matches = []
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(full_path.read_text(encoding="utf-8"))
|
|
57
|
+
if not isinstance(data, list):
|
|
58
|
+
return matches
|
|
59
|
+
for entry in data:
|
|
60
|
+
blob = self._entry_blob(entry, [])
|
|
61
|
+
if any(t in blob for t in query_terms):
|
|
62
|
+
matches.append((0, self._journal_card(entry)))
|
|
63
|
+
except (OSError, json.JSONDecodeError): # nosec B110
|
|
64
|
+
pass
|
|
65
|
+
return matches
|
|
66
|
+
|
|
67
|
+
@staticmethod
|
|
68
|
+
def _entry_blob(entry: dict, extra: list) -> str:
|
|
69
|
+
parts = (
|
|
70
|
+
extra
|
|
71
|
+
+ [entry.get("action", ""), entry.get("notes", "")]
|
|
72
|
+
+ entry.get("tags", [])
|
|
73
|
+
+ entry.get("learned", [])
|
|
74
|
+
)
|
|
75
|
+
if entry.get("failure_root_cause"):
|
|
76
|
+
parts.append(entry["failure_root_cause"])
|
|
77
|
+
return " ".join(str(v).lower() for v in parts)
|
|
78
|
+
|
|
79
|
+
@staticmethod
|
|
80
|
+
def _journal_card(entry: dict) -> str:
|
|
81
|
+
tags_str = ", ".join(entry.get("tags", []))
|
|
82
|
+
status = "🟢 SUCCESS" if entry.get("status") == "success" else "🔴 FAILURE"
|
|
83
|
+
ts = (entry.get("timestamp") or "")[:19]
|
|
84
|
+
card = (
|
|
85
|
+
f"🪵 [MEMORIA: JOURNAL] ({ts})\n"
|
|
86
|
+
f" Acción: {entry.get('action')} "
|
|
87
|
+
f"({entry.get('action_id')}) -> {status}\n"
|
|
88
|
+
f" Tags: {tags_str}\n"
|
|
89
|
+
f" Notas: {entry.get('notes')}"
|
|
90
|
+
)
|
|
91
|
+
if entry.get("failure_root_cause"):
|
|
92
|
+
card += f"\n Causa Raíz: {entry['failure_root_cause']}"
|
|
93
|
+
return card
|
|
94
|
+
|
|
95
|
+
def _search_text_file(self, full_path: Path, query_terms: list[str]) -> list:
|
|
96
|
+
matches = []
|
|
97
|
+
try:
|
|
98
|
+
lines = full_path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
99
|
+
for idx, line in enumerate(lines, 1):
|
|
100
|
+
if any(t in line.lower() for t in query_terms):
|
|
101
|
+
matches.append((idx, line.strip()))
|
|
102
|
+
except OSError: # nosec B110
|
|
103
|
+
pass
|
|
104
|
+
return matches
|
|
105
|
+
|
|
106
|
+
def _search_file(self, full_path: Path, query_terms: list[str]) -> list:
|
|
107
|
+
name = full_path.name
|
|
108
|
+
if name == "guidelines_contract.json":
|
|
109
|
+
return self._search_guidelines_contract(full_path, query_terms)
|
|
110
|
+
if name == "learnings.json":
|
|
111
|
+
return self._search_learnings_json(full_path, query_terms)
|
|
112
|
+
if name == "journal.json":
|
|
113
|
+
return self._search_journal_json(full_path, query_terms)
|
|
114
|
+
if name.endswith((".md", ".txt")):
|
|
115
|
+
return self._search_text_file(full_path, query_terms)
|
|
116
|
+
return []
|
|
117
|
+
|
|
118
|
+
def _search_semantic_graph(
|
|
119
|
+
self, graph_path: Path, query_terms: list[str], root_dir: Path
|
|
120
|
+
) -> dict:
|
|
121
|
+
try:
|
|
122
|
+
data = json.loads(graph_path.read_text(encoding="utf-8"))
|
|
123
|
+
except (OSError, json.JSONDecodeError):
|
|
124
|
+
return {}
|
|
125
|
+
|
|
126
|
+
matches: dict = {}
|
|
127
|
+
files = data.get("files", {})
|
|
128
|
+
|
|
129
|
+
for rel_path, file_data in files.items():
|
|
130
|
+
hits: list[tuple[int, str]] = []
|
|
131
|
+
|
|
132
|
+
summary = file_data.get("summary", "").lower()
|
|
133
|
+
if summary and any(t in summary for t in query_terms):
|
|
134
|
+
hits.append((0, f"📝 módulo: {file_data['summary'].strip()[:120]}"))
|
|
135
|
+
|
|
136
|
+
# Buscar en clases y sus métodos
|
|
137
|
+
hits.extend(self._search_code_elements(file_data.get("classes", []), query_terms, "🔷 class", "methods"))
|
|
138
|
+
|
|
139
|
+
# Buscar en funciones
|
|
140
|
+
hits.extend(self._search_code_elements(file_data.get("functions", []), query_terms, "🔹 def"))
|
|
141
|
+
|
|
142
|
+
for imp in file_data.get("imports", []):
|
|
143
|
+
if any(t in imp.lower() for t in query_terms):
|
|
144
|
+
hits.append((0, f"📦 import {imp}"))
|
|
145
|
+
|
|
146
|
+
if hits:
|
|
147
|
+
try:
|
|
148
|
+
key = f"[grafo] ./{Path(rel_path).as_posix()}"
|
|
149
|
+
except ValueError:
|
|
150
|
+
key = f"[grafo] {rel_path}"
|
|
151
|
+
matches[key] = hits
|
|
152
|
+
|
|
153
|
+
return matches
|
|
154
|
+
|
|
155
|
+
def _walk_search(
|
|
156
|
+
self, search_paths: list[Path], query_terms: list[str], root_dir: Path
|
|
157
|
+
) -> dict:
|
|
158
|
+
matches: dict = {}
|
|
159
|
+
query_terms_set = set(query_terms)
|
|
160
|
+
|
|
161
|
+
for path in search_paths:
|
|
162
|
+
if not path.exists():
|
|
163
|
+
continue
|
|
164
|
+
for full_path in path.rglob("*"): # Use rglob for recursive search
|
|
165
|
+
if not self._is_searchable_file(full_path):
|
|
166
|
+
continue
|
|
167
|
+
try:
|
|
168
|
+
found = self._search_file(full_path, query_terms_set)
|
|
169
|
+
if not found:
|
|
170
|
+
continue
|
|
171
|
+
key = self._make_search_key(full_path, root_dir)
|
|
172
|
+
matches[key] = found
|
|
173
|
+
except OSError: # nosec B110
|
|
174
|
+
pass
|
|
175
|
+
return matches
|
|
176
|
+
|
|
177
|
+
@staticmethod
|
|
178
|
+
def _is_searchable_file(full_path: Path) -> bool:
|
|
179
|
+
if not full_path.is_file() or full_path.suffix not in (".md", ".txt", ".json"):
|
|
180
|
+
return False
|
|
181
|
+
try:
|
|
182
|
+
return full_path.stat().st_size <= 1024 * 1024
|
|
183
|
+
except OSError:
|
|
184
|
+
return False
|
|
185
|
+
|
|
186
|
+
@staticmethod
|
|
187
|
+
def _make_search_key(full_path: Path, root_dir: Path) -> str:
|
|
188
|
+
return f"./{full_path.relative_to(root_dir)}" if root_dir in full_path.parents else str(full_path)
|
|
189
|
+
|
|
190
|
+
@staticmethod
|
|
191
|
+
def _print_search_results(matches: dict) -> None:
|
|
192
|
+
if not matches:
|
|
193
|
+
_log.warning("[!] No se encontró información relevante.")
|
|
194
|
+
_log.info(
|
|
195
|
+
"[TIP] Consulta al administrador de gobernanza si buscas " "una norma no listada."
|
|
196
|
+
)
|
|
197
|
+
return
|
|
198
|
+
_log.ok("[SUCCESS] Se encontraron coincidencias:")
|
|
199
|
+
for filepath, snippets in list(matches.items())[:5]:
|
|
200
|
+
_log.info(f"\n📄 Coincidencias en {filepath}:")
|
|
201
|
+
for line_num, snippet in snippets[:3]:
|
|
202
|
+
if line_num == 0:
|
|
203
|
+
_log.info(f"{snippet}")
|
|
204
|
+
else:
|
|
205
|
+
if len(snippet) > 120:
|
|
206
|
+
snippet = snippet[:117] + "..."
|
|
207
|
+
_log.info(f" L{line_num}: {snippet}")
|
|
208
|
+
if len(snippets) > 3:
|
|
209
|
+
_log.info(f" ... y {len(snippets) - 3} coincidencias más.")
|
|
210
|
+
|
|
211
|
+
@staticmethod
|
|
212
|
+
def _search_code_elements(
|
|
213
|
+
elements: list[dict],
|
|
214
|
+
query_terms: list[str],
|
|
215
|
+
prefix: str,
|
|
216
|
+
nested_key: str | None = None,
|
|
217
|
+
) -> list[tuple[int, str]]:
|
|
218
|
+
hits = []
|
|
219
|
+
for elem in elements:
|
|
220
|
+
name = elem.get("name", "")
|
|
221
|
+
doc = elem.get("docstring", "")
|
|
222
|
+
if any(t in name.lower() or t in doc.lower() for t in query_terms):
|
|
223
|
+
doc_snippet = f" — *{doc.strip()[:60]}*" if doc else ""
|
|
224
|
+
hits.append((0, f"{prefix} {name}{doc_snippet}"))
|
|
225
|
+
|
|
226
|
+
if nested_key:
|
|
227
|
+
nested_elements = elem.get(nested_key, [])
|
|
228
|
+
for ne in nested_elements:
|
|
229
|
+
ne_name = ne.get("name", "")
|
|
230
|
+
ne_doc = ne.get("docstring", "")
|
|
231
|
+
if any(t in ne_name.lower() or t in ne_doc.lower() for t in query_terms):
|
|
232
|
+
ne_doc_snippet = f" — *{ne_doc.strip()[:60]}*" if ne_doc else ""
|
|
233
|
+
hits.append((0, f" └─ def {ne_name}{ne_doc_snippet}"))
|
|
234
|
+
return hits
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Contratos y aliases de parámetros para capabilities higpertext."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
_ALIASES: dict[str, tuple[str, ...]] = {
|
|
9
|
+
"path": (
|
|
10
|
+
"file",
|
|
11
|
+
"file_path",
|
|
12
|
+
"filePath",
|
|
13
|
+
"source",
|
|
14
|
+
"source_path",
|
|
15
|
+
"tests_path",
|
|
16
|
+
"target",
|
|
17
|
+
),
|
|
18
|
+
"tests_path": ("path", "test_path", "test", "target"),
|
|
19
|
+
"source_path": ("path", "source", "target"),
|
|
20
|
+
"target": ("path", "source_path", "target_dir"),
|
|
21
|
+
"output": ("output_report", "report_path", "out"),
|
|
22
|
+
"json": ("as_json", "format_json"),
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass(frozen=True)
|
|
27
|
+
class ParameterValidation:
|
|
28
|
+
params: dict[str, Any]
|
|
29
|
+
errors: list[str]
|
|
30
|
+
warnings: list[str]
|
|
31
|
+
|
|
32
|
+
@property
|
|
33
|
+
def ok(self) -> bool:
|
|
34
|
+
return not self.errors
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def normalize_and_validate_params(capability: dict, params: dict[str, Any]) -> ParameterValidation:
|
|
38
|
+
"""Normaliza aliases declarados y valida parámetros requeridos."""
|
|
39
|
+
declared = _declared_parameters(capability)
|
|
40
|
+
normalized = dict(params)
|
|
41
|
+
warnings: list[str] = []
|
|
42
|
+
|
|
43
|
+
for canonical in declared:
|
|
44
|
+
if canonical in normalized:
|
|
45
|
+
continue
|
|
46
|
+
alias = _first_present_alias(canonical, normalized)
|
|
47
|
+
if alias:
|
|
48
|
+
normalized[canonical] = normalized[alias]
|
|
49
|
+
normalized.pop(alias, None)
|
|
50
|
+
warnings.append(f"Alias --{alias} normalizado a --{canonical}")
|
|
51
|
+
|
|
52
|
+
errors = _missing_required_errors(capability, normalized)
|
|
53
|
+
return ParameterValidation(params=normalized, errors=errors, warnings=warnings)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _declared_parameters(capability: dict) -> set[str]:
|
|
57
|
+
return {item.get("name", "") for item in capability.get("parameters", []) if item.get("name")}
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _first_present_alias(canonical: str, params: dict[str, Any]) -> str:
|
|
61
|
+
for alias in _ALIASES.get(canonical, ()):
|
|
62
|
+
if alias in params:
|
|
63
|
+
return alias
|
|
64
|
+
return ""
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _missing_required_errors(capability: dict, params: dict[str, Any]) -> list[str]:
|
|
68
|
+
errors: list[str] = []
|
|
69
|
+
for item in capability.get("parameters", []):
|
|
70
|
+
name = item.get("name", "")
|
|
71
|
+
if not name or not item.get("required", False):
|
|
72
|
+
continue
|
|
73
|
+
if params.get(name) not in (None, ""):
|
|
74
|
+
continue
|
|
75
|
+
errors.append(_format_missing_required(name, item))
|
|
76
|
+
return errors
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _format_missing_required(name: str, spec: dict) -> str:
|
|
80
|
+
description = spec.get("description", "sin descripción")
|
|
81
|
+
aliases = ", ".join(f"--{alias}" for alias in _ALIASES.get(name, ()))
|
|
82
|
+
alias_hint = f" Alias aceptados: {aliases}." if aliases else ""
|
|
83
|
+
return f"Falta parámetro requerido --{name}: {description}.{alias_hint}"
|