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,191 @@
|
|
|
1
|
+
"""higpertext Health Check — verifica integridad del motor en < 2 s (Infraestructura)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
import importlib.util
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from higpertext.kernel.pkg_resources import resolve_resource, pkg_data_root
|
|
10
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
11
|
+
_log = get_logger()
|
|
12
|
+
|
|
13
|
+
ROOT_DIR = Path(__file__).resolve().parent.parent.parent.parent.parent.parent
|
|
14
|
+
CAPS_DIR = resolve_resource(ROOT_DIR, "src", "higpertext", "capabilities")
|
|
15
|
+
CUSTOM_CAPS_DIR = ROOT_DIR / WORKSPACE_DIR_NAME / "capabilities"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _check_json_valid(path: Path) -> str | None:
|
|
19
|
+
try:
|
|
20
|
+
json.loads(path.read_text(encoding="utf-8"))
|
|
21
|
+
return None
|
|
22
|
+
except (json.JSONDecodeError, OSError) as e:
|
|
23
|
+
return str(e)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _collect_all_caps() -> list[Path]:
|
|
27
|
+
dirs = [CAPS_DIR]
|
|
28
|
+
if CUSTOM_CAPS_DIR.exists():
|
|
29
|
+
dirs.append(CUSTOM_CAPS_DIR)
|
|
30
|
+
result = []
|
|
31
|
+
for d in dirs:
|
|
32
|
+
result.extend(d.rglob("*.json"))
|
|
33
|
+
return result
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _display_path(p: Path) -> str:
|
|
37
|
+
for base in (ROOT_DIR, pkg_data_root()):
|
|
38
|
+
if base is not None:
|
|
39
|
+
try:
|
|
40
|
+
return str(p.relative_to(base))
|
|
41
|
+
except ValueError:
|
|
42
|
+
continue
|
|
43
|
+
return str(p)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _check_entrypoint(cap_json: Path, entrypoint: str) -> dict | None:
|
|
47
|
+
ep_path = entrypoint
|
|
48
|
+
for prefix in ("python3 ", "python "):
|
|
49
|
+
if ep_path.startswith(prefix):
|
|
50
|
+
ep_path = ep_path[len(prefix) :]
|
|
51
|
+
break
|
|
52
|
+
candidates = [ROOT_DIR / "src" / ep_path, ROOT_DIR / ep_path]
|
|
53
|
+
pkg_root = pkg_data_root()
|
|
54
|
+
if pkg_root is not None:
|
|
55
|
+
candidates.append(pkg_root / ep_path)
|
|
56
|
+
if any(c.exists() for c in candidates):
|
|
57
|
+
return None
|
|
58
|
+
return {
|
|
59
|
+
"file": _display_path(cap_json),
|
|
60
|
+
"severity": "critical",
|
|
61
|
+
"issue": f"Entrypoint no encontrado: {entrypoint}",
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _check_capabilities() -> list[dict]:
|
|
66
|
+
issues = []
|
|
67
|
+
for cap_json in _collect_all_caps():
|
|
68
|
+
err = _check_json_valid(cap_json)
|
|
69
|
+
if err:
|
|
70
|
+
issues.append(
|
|
71
|
+
{
|
|
72
|
+
"file": _display_path(cap_json),
|
|
73
|
+
"severity": "critical",
|
|
74
|
+
"issue": f"JSON inválido: {err}",
|
|
75
|
+
}
|
|
76
|
+
)
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
data = json.loads(cap_json.read_text(encoding="utf-8"))
|
|
80
|
+
|
|
81
|
+
if "version" not in data:
|
|
82
|
+
issues.append(
|
|
83
|
+
{
|
|
84
|
+
"file": _display_path(cap_json),
|
|
85
|
+
"severity": "warning",
|
|
86
|
+
"issue": "Falta campo 'version'",
|
|
87
|
+
}
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
entrypoint = data.get("entrypoint")
|
|
91
|
+
if entrypoint:
|
|
92
|
+
issue = _check_entrypoint(cap_json, entrypoint)
|
|
93
|
+
if issue:
|
|
94
|
+
issues.append(issue)
|
|
95
|
+
return issues
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _check_python_deps() -> list[dict]:
|
|
99
|
+
required = ["argparse", "json", "pathlib", "subprocess", "ast"]
|
|
100
|
+
issues = []
|
|
101
|
+
for mod in required:
|
|
102
|
+
if importlib.util.find_spec(mod) is None:
|
|
103
|
+
issues.append(
|
|
104
|
+
{
|
|
105
|
+
"module": mod,
|
|
106
|
+
"severity": "critical",
|
|
107
|
+
"issue": "Módulo requerido no disponible",
|
|
108
|
+
}
|
|
109
|
+
)
|
|
110
|
+
return issues
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _check_venv() -> dict:
|
|
114
|
+
import platform
|
|
115
|
+
venv_name = ".venv"
|
|
116
|
+
try:
|
|
117
|
+
from higpertext.kernel.htx_resolver import _load_config
|
|
118
|
+
cfg = _load_config()
|
|
119
|
+
is_windows = platform.system() == "Windows"
|
|
120
|
+
venv_rel = cfg.get("venv_bin_windows" if is_windows else "venv_bin", ".venv/bin/htx")
|
|
121
|
+
venv_name = str(Path(venv_rel).parent.parent)
|
|
122
|
+
except Exception as exc:
|
|
123
|
+
_log.warning(f"[health] No se pudo leer config de venv: {exc}")
|
|
124
|
+
venv = ROOT_DIR / venv_name
|
|
125
|
+
if venv.exists():
|
|
126
|
+
return {"status": "ok", "path": str(venv.relative_to(ROOT_DIR))}
|
|
127
|
+
return {
|
|
128
|
+
"status": "warning",
|
|
129
|
+
"message": f"{venv_name} no encontrado — usando Python del sistema",
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def run_health_check(verbose: bool = False) -> int:
|
|
134
|
+
t0 = time.time()
|
|
135
|
+
_log.info("=" * 60)
|
|
136
|
+
_log.info("HIGPERTEXT ENGINE — HEALTH CHECK")
|
|
137
|
+
_log.info("=" * 60)
|
|
138
|
+
|
|
139
|
+
cap_issues = _check_capabilities()
|
|
140
|
+
critical_caps = [i for i in cap_issues if i["severity"] == "critical"]
|
|
141
|
+
warning_caps = [i for i in cap_issues if i["severity"] == "warning"]
|
|
142
|
+
|
|
143
|
+
_log.info(f"\n[Capabilities] {len(_collect_all_caps())} archivos JSON escaneados")
|
|
144
|
+
_print_issues(critical_caps, "errores críticos", "✖", verbose=True)
|
|
145
|
+
_print_issues(warning_caps, "advertencias", "⚠", verbose=verbose)
|
|
146
|
+
|
|
147
|
+
dep_issues = _check_python_deps()
|
|
148
|
+
_log.info(f"\n[Python Deps] Módulos core del motor")
|
|
149
|
+
_print_issues(dep_issues, "Módulos faltantes", "✖", key="module")
|
|
150
|
+
|
|
151
|
+
venv_status = _check_venv()
|
|
152
|
+
_log.info(f"\n[Entorno]")
|
|
153
|
+
if venv_status["status"] == "ok":
|
|
154
|
+
_log.info(f" ✔ .venv encontrado: {venv_status['path']}")
|
|
155
|
+
else:
|
|
156
|
+
_log.warning(f" ⚠ {venv_status['message']}")
|
|
157
|
+
|
|
158
|
+
elapsed = time.time() - t0
|
|
159
|
+
ok = len(critical_caps) == 0 and len(dep_issues) == 0
|
|
160
|
+
_log.info(f"\n{'=' * 60}")
|
|
161
|
+
status_label = "OK" if ok else "FAIL"
|
|
162
|
+
_log.info(f"Estado: {status_label} | Tiempo: {elapsed:.2f}s")
|
|
163
|
+
_log.info("=" * 60)
|
|
164
|
+
|
|
165
|
+
return 0 if ok else 1
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _print_issues(
|
|
169
|
+
issues: list[dict],
|
|
170
|
+
label: str,
|
|
171
|
+
symbol: str,
|
|
172
|
+
verbose: bool = False,
|
|
173
|
+
key: str = "issue",
|
|
174
|
+
) -> None:
|
|
175
|
+
if not issues:
|
|
176
|
+
if label in ("errores críticos", "Módulos faltantes"):
|
|
177
|
+
_log.info(f" ✔ Sin {label.lower()}")
|
|
178
|
+
return
|
|
179
|
+
|
|
180
|
+
show_all = verbose or label in ("errores críticos", "Módulos faltantes")
|
|
181
|
+
total = len(issues)
|
|
182
|
+
if not show_all:
|
|
183
|
+
_log.info(f" {symbol} {total} {label} (usa --verbose para ver)")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
_log.info(f" {symbol} {total} {label}:")
|
|
187
|
+
for issue in issues[:10]:
|
|
188
|
+
file_info = f"{issue['file']}: " if 'file' in issue else ""
|
|
189
|
+
_log.info(f" - {file_info}{issue[key]}")
|
|
190
|
+
if total > 10 and verbose:
|
|
191
|
+
_log.info(f" ... y {total - 10} más (usa --verbose para ver todos)")
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Implementación de infraestructura para ejecución de entornos Docker/Podman."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import shutil
|
|
6
|
+
# Ejecución de Docker/Podman con listas y sin shell.
|
|
7
|
+
import subprocess # nosec B404
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Any
|
|
11
|
+
import yaml
|
|
12
|
+
|
|
13
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
14
|
+
from higpertext.kernel.domain.env_runtime import EnvironmentTemplate, RunState, RunSpec
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def run_process(args: list[str], cwd: str, timeout: int = 300) -> tuple[int, str, str]:
|
|
18
|
+
"""Ejecuta proceso sin shell y devuelve rc/stdout/stderr."""
|
|
19
|
+
result = subprocess.run(
|
|
20
|
+
args, cwd=cwd, timeout=timeout, capture_output=True, text=True
|
|
21
|
+
)
|
|
22
|
+
return result.returncode, result.stdout or "", result.stderr or ""
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass(frozen=True)
|
|
26
|
+
class CommandResult:
|
|
27
|
+
rc: int
|
|
28
|
+
stdout: str
|
|
29
|
+
stderr: str
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class ComposeBackend:
|
|
33
|
+
"""Backend común para docker compose y podman compose."""
|
|
34
|
+
|
|
35
|
+
def __init__(self, engine: str) -> None:
|
|
36
|
+
self.engine = engine
|
|
37
|
+
|
|
38
|
+
def available(self) -> bool:
|
|
39
|
+
return shutil.which(self.engine) is not None
|
|
40
|
+
|
|
41
|
+
def compose_base(self, project_name: str, compose_file: Path) -> list[str]:
|
|
42
|
+
return [self.engine, "compose", "-p", project_name, "-f", str(compose_file)]
|
|
43
|
+
|
|
44
|
+
def up(self, project_name: str, compose_file: Path, cwd: Path, timeout: int) -> CommandResult:
|
|
45
|
+
return self._run(self.compose_base(project_name, compose_file) + ["up", "-d"], cwd, timeout)
|
|
46
|
+
|
|
47
|
+
def exec(
|
|
48
|
+
self,
|
|
49
|
+
project_name: str,
|
|
50
|
+
compose_file: Path,
|
|
51
|
+
cwd: Path,
|
|
52
|
+
service: str,
|
|
53
|
+
command: str,
|
|
54
|
+
timeout: int,
|
|
55
|
+
) -> CommandResult:
|
|
56
|
+
args = self.compose_base(project_name, compose_file) + [
|
|
57
|
+
"exec",
|
|
58
|
+
"-T",
|
|
59
|
+
service,
|
|
60
|
+
"sh",
|
|
61
|
+
"-lc",
|
|
62
|
+
command,
|
|
63
|
+
]
|
|
64
|
+
return self._run(args, cwd, timeout)
|
|
65
|
+
|
|
66
|
+
def logs(
|
|
67
|
+
self,
|
|
68
|
+
project_name: str,
|
|
69
|
+
compose_file: Path,
|
|
70
|
+
cwd: Path,
|
|
71
|
+
service: str = "",
|
|
72
|
+
tail: int = 200,
|
|
73
|
+
) -> CommandResult:
|
|
74
|
+
args = self.compose_base(project_name, compose_file) + [
|
|
75
|
+
"logs",
|
|
76
|
+
"--no-color",
|
|
77
|
+
"--tail",
|
|
78
|
+
str(tail),
|
|
79
|
+
]
|
|
80
|
+
if service:
|
|
81
|
+
args.append(service)
|
|
82
|
+
return self._run(args, cwd, 60)
|
|
83
|
+
|
|
84
|
+
def down(
|
|
85
|
+
self, project_name: str, compose_file: Path, cwd: Path, volumes: bool = False
|
|
86
|
+
) -> CommandResult:
|
|
87
|
+
args = self.compose_base(project_name, compose_file) + ["down"]
|
|
88
|
+
if volumes:
|
|
89
|
+
args.append("--volumes")
|
|
90
|
+
return self._run(args, cwd, 120)
|
|
91
|
+
|
|
92
|
+
@staticmethod
|
|
93
|
+
def _run(args: list[str], cwd: Path, timeout: int) -> CommandResult:
|
|
94
|
+
rc, stdout, stderr = run_process(args, str(cwd), timeout)
|
|
95
|
+
return CommandResult(rc, stdout, stderr)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def resolve_backend(engine: str) -> ComposeBackend:
|
|
99
|
+
"""Selecciona Docker/Podman; auto prefiere Docker y luego Podman."""
|
|
100
|
+
candidates = ["docker", "podman"] if engine == "auto" else [engine]
|
|
101
|
+
for candidate in candidates:
|
|
102
|
+
backend = ComposeBackend(candidate)
|
|
103
|
+
if backend.available():
|
|
104
|
+
return backend
|
|
105
|
+
raise RuntimeError(f"No hay motor de contenedores disponible para: {engine}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class RunStore:
|
|
109
|
+
"""Guarda estados bajo .higpertext/env/runs/<run_id>."""
|
|
110
|
+
|
|
111
|
+
def __init__(self, project_root: Path) -> None:
|
|
112
|
+
self.root = project_root / WORKSPACE_DIR_NAME / "env" / "runs"
|
|
113
|
+
self.root.mkdir(parents=True, exist_ok=True)
|
|
114
|
+
|
|
115
|
+
def run_dir(self, run_id: str) -> Path:
|
|
116
|
+
path = self.root / run_id
|
|
117
|
+
path.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
(path / "logs").mkdir(exist_ok=True)
|
|
119
|
+
return path
|
|
120
|
+
|
|
121
|
+
def save(self, state: RunState) -> None:
|
|
122
|
+
path = Path(state.run_dir) / "state.json"
|
|
123
|
+
path.write_text(json.dumps(state.to_dict(), ensure_ascii=False, indent=2), encoding="utf-8")
|
|
124
|
+
|
|
125
|
+
def load(self, run_id: str) -> RunState:
|
|
126
|
+
path = self.root / run_id / "state.json"
|
|
127
|
+
if not path.exists():
|
|
128
|
+
raise FileNotFoundError(f"Run no encontrado: {run_id}")
|
|
129
|
+
return RunState.from_dict(json.loads(path.read_text(encoding="utf-8")))
|
|
130
|
+
|
|
131
|
+
def list(self) -> list[RunState]:
|
|
132
|
+
states = []
|
|
133
|
+
for path in sorted(self.root.glob("*/state.json")):
|
|
134
|
+
try:
|
|
135
|
+
states.append(RunState.from_dict(json.loads(path.read_text(encoding="utf-8"))))
|
|
136
|
+
except (OSError, json.JSONDecodeError, TypeError):
|
|
137
|
+
continue
|
|
138
|
+
return states
|
|
139
|
+
|
|
140
|
+
def delete(self, run_id: str) -> None:
|
|
141
|
+
shutil.rmtree(self.root / run_id, ignore_errors=True)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
class ComposeRenderer:
|
|
145
|
+
"""Convierte una plantilla a Docker/Podman Compose."""
|
|
146
|
+
|
|
147
|
+
def render(self, template: EnvironmentTemplate, spec: RunSpec, run_dir: Path) -> Path:
|
|
148
|
+
services = {
|
|
149
|
+
name: self._service_payload(payload, template, spec)
|
|
150
|
+
for name, payload in template.services.items()
|
|
151
|
+
}
|
|
152
|
+
compose = {"services": services}
|
|
153
|
+
if template.security.get("network", "isolated") == "isolated":
|
|
154
|
+
compose["networks"] = {"default": {"name": f"{run_dir.name}_net"}}
|
|
155
|
+
path = run_dir / "compose.yaml"
|
|
156
|
+
path.write_text(yaml.safe_dump(compose, sort_keys=False), encoding="utf-8")
|
|
157
|
+
return path
|
|
158
|
+
|
|
159
|
+
def _service_payload(
|
|
160
|
+
self, payload: dict[str, Any], template: EnvironmentTemplate, spec: RunSpec
|
|
161
|
+
) -> dict[str, Any]:
|
|
162
|
+
result = {k: v for k, v in payload.items() if k not in {"tasks", "install", "env"}}
|
|
163
|
+
limits = template.limits
|
|
164
|
+
if limits.get("memory"):
|
|
165
|
+
result["mem_limit"] = str(limits["memory"])
|
|
166
|
+
if limits.get("cpus"):
|
|
167
|
+
result["cpus"] = str(limits["cpus"])
|
|
168
|
+
env = dict(result.get("environment", {}) or {})
|
|
169
|
+
env.update(payload.get("env", {}) or {})
|
|
170
|
+
env.update(spec.env)
|
|
171
|
+
if env:
|
|
172
|
+
result["environment"] = env
|
|
173
|
+
result["privileged"] = bool(template.security.get("privileged", False))
|
|
174
|
+
return result
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
class TemplateLoader:
|
|
178
|
+
"""Resuelve templates locales primero y luego templates del motor."""
|
|
179
|
+
|
|
180
|
+
def __init__(self, project_root: Path) -> None:
|
|
181
|
+
self.project_root = project_root
|
|
182
|
+
self.local_dir = project_root / WORKSPACE_DIR_NAME / "env" / "templates"
|
|
183
|
+
self.engine_dir = Path(__file__).resolve().parents[2] / "templates" / "env"
|
|
184
|
+
|
|
185
|
+
def list_templates(self) -> list[EnvironmentTemplate]:
|
|
186
|
+
templates: dict[str, EnvironmentTemplate] = {}
|
|
187
|
+
for directory in (self.engine_dir, self.local_dir):
|
|
188
|
+
if not directory.exists():
|
|
189
|
+
continue
|
|
190
|
+
for path in sorted(
|
|
191
|
+
[
|
|
192
|
+
*directory.glob("*.yaml"),
|
|
193
|
+
*directory.glob("*.yml"),
|
|
194
|
+
*directory.glob("*.json"),
|
|
195
|
+
]
|
|
196
|
+
):
|
|
197
|
+
template = self.load_file(path)
|
|
198
|
+
templates[template.id] = template
|
|
199
|
+
return sorted(templates.values(), key=lambda item: item.id)
|
|
200
|
+
|
|
201
|
+
def load(self, template_id: str) -> EnvironmentTemplate:
|
|
202
|
+
for directory in (self.local_dir, self.engine_dir):
|
|
203
|
+
for suffix in (".yaml", ".yml", ".json"):
|
|
204
|
+
path = directory / f"{template_id}{suffix}"
|
|
205
|
+
if path.exists():
|
|
206
|
+
return self.load_file(path)
|
|
207
|
+
raise FileNotFoundError(f"Template no encontrado: {template_id}")
|
|
208
|
+
|
|
209
|
+
@staticmethod
|
|
210
|
+
def load_file(path: Path) -> EnvironmentTemplate:
|
|
211
|
+
data: dict[str, Any]
|
|
212
|
+
if path.suffix == ".json":
|
|
213
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
214
|
+
else:
|
|
215
|
+
data = yaml.safe_load(path.read_text(encoding="utf-8")) or {}
|
|
216
|
+
if not data.get("id") or not isinstance(data.get("services"), dict):
|
|
217
|
+
raise ValueError(f"Template inválido: {path}")
|
|
218
|
+
return EnvironmentTemplate(
|
|
219
|
+
id=data["id"],
|
|
220
|
+
description=data.get("description", ""),
|
|
221
|
+
services=data["services"],
|
|
222
|
+
defaults=data.get("defaults", {}),
|
|
223
|
+
tasks=data.get("tasks", data.get("checks", {})),
|
|
224
|
+
limits=data.get("limits", {}),
|
|
225
|
+
security=data.get("security", {}),
|
|
226
|
+
source=path,
|
|
227
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Infrastructure execution init
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""higpertext Parallel Executor — ejecución concurrente de pasos independientes en workflows
|
|
2
|
+
(Infraestructura)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
import threading
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Callable
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
10
|
+
_log = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class StepResult:
|
|
15
|
+
step: int
|
|
16
|
+
task: str
|
|
17
|
+
success: bool
|
|
18
|
+
error: str = ""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class StepGroup:
|
|
23
|
+
"""Grupo de pasos que pueden ejecutarse en paralelo."""
|
|
24
|
+
|
|
25
|
+
steps: list[dict] = field(default_factory=list)
|
|
26
|
+
|
|
27
|
+
def is_parallel(self) -> bool:
|
|
28
|
+
return len(self.steps) > 1
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def build_step_groups(chain: list[dict]) -> list[StepGroup]:
|
|
32
|
+
"""Agrupa pasos del workflow por nivel de paralelismo."""
|
|
33
|
+
sorted_chain = sorted(chain, key=lambda x: x.get("step", 0))
|
|
34
|
+
groups: list[StepGroup] = []
|
|
35
|
+
current_group: StepGroup | None = None
|
|
36
|
+
current_pg: str | None = None
|
|
37
|
+
|
|
38
|
+
for step in sorted_chain:
|
|
39
|
+
is_parallel = step.get("parallel", False)
|
|
40
|
+
pg = step.get("parallel_group", None)
|
|
41
|
+
|
|
42
|
+
if is_parallel:
|
|
43
|
+
effective_pg = pg or "__auto__"
|
|
44
|
+
if current_group is None or current_pg != effective_pg:
|
|
45
|
+
current_group = StepGroup()
|
|
46
|
+
groups.append(current_group)
|
|
47
|
+
current_pg = effective_pg
|
|
48
|
+
current_group.steps.append(step)
|
|
49
|
+
else:
|
|
50
|
+
current_group = StepGroup(steps=[step])
|
|
51
|
+
groups.append(current_group)
|
|
52
|
+
current_pg = None
|
|
53
|
+
|
|
54
|
+
return groups
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class ParallelExecutor:
|
|
58
|
+
"""Ejecuta grupos de pasos de workflow en paralelo usando threads."""
|
|
59
|
+
|
|
60
|
+
def __init__(self, run_task_fn: Callable[[str, dict], bool]) -> None:
|
|
61
|
+
self._run_task = run_task_fn
|
|
62
|
+
|
|
63
|
+
def execute_group(self, group: StepGroup, params: dict) -> list[StepResult]:
|
|
64
|
+
if not group.is_parallel():
|
|
65
|
+
return [self._execute_step(group.steps[0], params)]
|
|
66
|
+
|
|
67
|
+
results: list[StepResult] = []
|
|
68
|
+
lock = threading.Lock()
|
|
69
|
+
threads: list[threading.Thread] = []
|
|
70
|
+
|
|
71
|
+
def worker(step: dict) -> None:
|
|
72
|
+
res = self._execute_step(step, params)
|
|
73
|
+
with lock:
|
|
74
|
+
results.append(res)
|
|
75
|
+
|
|
76
|
+
step_nums = [s.get("step", "?") for s in group.steps]
|
|
77
|
+
_log.info(f"[Parallel] Ejecutando pasos {step_nums} concurrentemente...")
|
|
78
|
+
|
|
79
|
+
for step in group.steps:
|
|
80
|
+
t = threading.Thread(target=worker, args=(step,), daemon=True)
|
|
81
|
+
threads.append(t)
|
|
82
|
+
t.start()
|
|
83
|
+
|
|
84
|
+
for t in threads:
|
|
85
|
+
t.join()
|
|
86
|
+
|
|
87
|
+
results.sort(key=lambda r: r.step)
|
|
88
|
+
return results
|
|
89
|
+
|
|
90
|
+
def _execute_step(self, step: dict, params: dict) -> StepResult:
|
|
91
|
+
step_type = step.get("type", "task")
|
|
92
|
+
step_num = step.get("step", 0)
|
|
93
|
+
merged = {**params, **step.get("params", {})}
|
|
94
|
+
|
|
95
|
+
if step_type == "llm":
|
|
96
|
+
return self._execute_llm_step(step, step_num, merged)
|
|
97
|
+
|
|
98
|
+
task_name = step.get("task", "")
|
|
99
|
+
_log.info(f"[Step {step_num}] Iniciando tarea: {task_name}")
|
|
100
|
+
try:
|
|
101
|
+
success = self._run_task(task_name, merged)
|
|
102
|
+
return StepResult(step=step_num, task=task_name, success=success)
|
|
103
|
+
except Exception as e: # noqa: BLE001
|
|
104
|
+
return StepResult(step=step_num, task=task_name, success=False, error=str(e))
|
|
105
|
+
|
|
106
|
+
def _execute_llm_step(self, step: dict, step_num: int, params: dict) -> StepResult:
|
|
107
|
+
"""Ejecuta un step de tipo 'llm' via common.llm-invoke."""
|
|
108
|
+
llm_params = {
|
|
109
|
+
"prompt": step.get("prompt", params.get("prompt", "")),
|
|
110
|
+
"provider": step.get("provider", params.get("provider", "")),
|
|
111
|
+
"model": step.get("model", params.get("model", "")),
|
|
112
|
+
"system": step.get("system", params.get("system", "")),
|
|
113
|
+
"max_tokens": step.get("max_tokens", params.get("max_tokens", 1024)),
|
|
114
|
+
"temperature": step.get("temperature", params.get("temperature", 0.7)),
|
|
115
|
+
"stream": step.get("stream", params.get("stream", "false")),
|
|
116
|
+
"output_file": step.get("output_file", params.get("output_file", "")),
|
|
117
|
+
}
|
|
118
|
+
label = f"llm:{step.get('provider', 'default')}"
|
|
119
|
+
_log.info(f"[Step {step_num}] Iniciando step LLM: {label}")
|
|
120
|
+
try:
|
|
121
|
+
success = self._run_task("common.llm-invoke", llm_params)
|
|
122
|
+
return StepResult(step=step_num, task=label, success=success)
|
|
123
|
+
except Exception as e: # noqa: BLE001
|
|
124
|
+
return StepResult(step=step_num, task=label, success=False, error=str(e))
|
|
125
|
+
|
|
126
|
+
def _resolve_step_params(
|
|
127
|
+
self,
|
|
128
|
+
group: StepGroup,
|
|
129
|
+
params: dict,
|
|
130
|
+
interpolate_fn: Callable[[dict, dict], dict] | None,
|
|
131
|
+
) -> list[dict]:
|
|
132
|
+
step_params_list: list[dict] = []
|
|
133
|
+
for step in group.steps:
|
|
134
|
+
raw = step.get("params", {})
|
|
135
|
+
resolved = interpolate_fn(raw, params) if interpolate_fn else raw
|
|
136
|
+
step_params_list.append(resolved)
|
|
137
|
+
return step_params_list
|
|
138
|
+
|
|
139
|
+
def _execute_group_with_params(
|
|
140
|
+
self,
|
|
141
|
+
group: StepGroup,
|
|
142
|
+
step_params_list: list[dict],
|
|
143
|
+
params: dict,
|
|
144
|
+
) -> list[StepResult]:
|
|
145
|
+
if group.is_parallel():
|
|
146
|
+
enriched_steps = [
|
|
147
|
+
{**step, "params": sp}
|
|
148
|
+
for step, sp in zip(group.steps, step_params_list)
|
|
149
|
+
]
|
|
150
|
+
group_with_params = StepGroup(steps=enriched_steps)
|
|
151
|
+
return self.execute_group(group_with_params, params)
|
|
152
|
+
|
|
153
|
+
step = {**group.steps[0], "params": step_params_list[0]}
|
|
154
|
+
return [self._execute_step(step, params)]
|
|
155
|
+
|
|
156
|
+
def _handle_results(self, group: StepGroup, results: list[StepResult]) -> bool:
|
|
157
|
+
for res in results:
|
|
158
|
+
if res.success:
|
|
159
|
+
continue
|
|
160
|
+
|
|
161
|
+
step_def = next((s for s in group.steps if s.get("step") == res.step), {})
|
|
162
|
+
on_failure = step_def.get("on_failure", "abort")
|
|
163
|
+
if res.error:
|
|
164
|
+
_log.warning(f"[!] Paso {res.step} ({res.task}) — excepción: {res.error}")
|
|
165
|
+
else:
|
|
166
|
+
_log.warning(f"[!] Paso {res.step} ({res.task}) falló. Acción: {on_failure}")
|
|
167
|
+
|
|
168
|
+
if on_failure == "abort":
|
|
169
|
+
_log.error("[ERROR] Flujo de trabajo abortado.")
|
|
170
|
+
return False
|
|
171
|
+
return True
|
|
172
|
+
|
|
173
|
+
def execute_chain(
|
|
174
|
+
self,
|
|
175
|
+
chain: list[dict],
|
|
176
|
+
params: dict,
|
|
177
|
+
interpolate_fn: Callable[[dict, dict], dict] | None = None,
|
|
178
|
+
) -> bool:
|
|
179
|
+
groups = build_step_groups(chain)
|
|
180
|
+
|
|
181
|
+
for group in groups:
|
|
182
|
+
step_params_list = self._resolve_step_params(group, params, interpolate_fn)
|
|
183
|
+
results = self._execute_group_with_params(group, step_params_list, params)
|
|
184
|
+
if not self._handle_results(group, results):
|
|
185
|
+
return False
|
|
186
|
+
|
|
187
|
+
_log.ok("\n[SUCCESS] Flujo de trabajo completado con éxito.")
|
|
188
|
+
return True
|