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,1110 @@
|
|
|
1
|
+
"""higpertext Engine CLI — orquestador de capacidades, perfiles y workflows."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import os
|
|
5
|
+
import json
|
|
6
|
+
import argparse
|
|
7
|
+
import subprocess # nosec B404
|
|
8
|
+
import shutil
|
|
9
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
10
|
+
from higpertext.kernel.infrastructure.cli.arguments import parse_kv_args, pop_bool_flag
|
|
11
|
+
from higpertext.kernel.infrastructure.cli.capability_command_builder import (
|
|
12
|
+
build_command,
|
|
13
|
+
build_powershell_command,
|
|
14
|
+
extra_args,
|
|
15
|
+
resolve_entrypoint,
|
|
16
|
+
)
|
|
17
|
+
from higpertext.kernel.infrastructure.cli.capability_task_service import CapabilityTaskService
|
|
18
|
+
from higpertext.kernel.infrastructure.cli.parser_builder import (
|
|
19
|
+
add_agent_subparser,
|
|
20
|
+
add_health_subparser,
|
|
21
|
+
add_profile_init_subparsers,
|
|
22
|
+
add_roadmap_subparser,
|
|
23
|
+
add_session_workflow_subparsers,
|
|
24
|
+
add_task_ask_subparsers,
|
|
25
|
+
build_parser,
|
|
26
|
+
)
|
|
27
|
+
from higpertext.kernel.infrastructure.cli.agent_commands import (
|
|
28
|
+
dispatch_agent,
|
|
29
|
+
dispatch_agent_list,
|
|
30
|
+
dispatch_agent_register,
|
|
31
|
+
dispatch_agent_sync,
|
|
32
|
+
)
|
|
33
|
+
from higpertext.kernel.infrastructure.cli.profile_commands import (
|
|
34
|
+
dispatch_profile,
|
|
35
|
+
dispatch_profile_learn,
|
|
36
|
+
dispatch_profile_validate,
|
|
37
|
+
)
|
|
38
|
+
from higpertext.kernel.infrastructure.cli.roadmap_commands import dispatch_roadmap
|
|
39
|
+
from higpertext.kernel.infrastructure.cli.session_commands import (
|
|
40
|
+
parse_session_resources,
|
|
41
|
+
start_session,
|
|
42
|
+
)
|
|
43
|
+
from higpertext.kernel.infrastructure.cli.task_commands import dispatch_task
|
|
44
|
+
from higpertext.kernel.infrastructure.cli.task_result_reporter import (
|
|
45
|
+
build_memory_notes,
|
|
46
|
+
print_task_failure,
|
|
47
|
+
report_task_result,
|
|
48
|
+
)
|
|
49
|
+
from higpertext.kernel.infrastructure.cli.workflow_commands import (
|
|
50
|
+
dispatch_workflow,
|
|
51
|
+
resolve_workflow_params,
|
|
52
|
+
)
|
|
53
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
54
|
+
from higpertext.kernel.app_config import ASSISTANTS as _ASSISTANTS, RESERVED_PROFILES as _RESERVED_PROFILES_CFG
|
|
55
|
+
from pathlib import Path
|
|
56
|
+
|
|
57
|
+
_log = get_logger()
|
|
58
|
+
|
|
59
|
+
# ROOT_DIR: project root for the user's workspace (CWD when installed as CLI).
|
|
60
|
+
# PKG_DATA_DIR: where shipped static data lives (site-packages when installed, src/ in dev).
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _resolve_root_dir() -> Path:
|
|
64
|
+
env_override = os.environ.get("HIGPERTEXT_ENGINE_ROOT")
|
|
65
|
+
if env_override:
|
|
66
|
+
return Path(env_override).resolve()
|
|
67
|
+
# router.py is nested 6 levels deep from the repo root in development.
|
|
68
|
+
this_file = Path(__file__).resolve()
|
|
69
|
+
if "site-packages" in this_file.parts:
|
|
70
|
+
return Path.cwd()
|
|
71
|
+
return this_file.parent.parent.parent.parent.parent.parent
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
ROOT_DIR = _resolve_root_dir()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _resolve_pkg_data_dir() -> Path:
|
|
78
|
+
try:
|
|
79
|
+
import higpertext_data
|
|
80
|
+
|
|
81
|
+
return Path(higpertext_data.__path__[0]).resolve()
|
|
82
|
+
except (ImportError, AttributeError, IndexError):
|
|
83
|
+
return ROOT_DIR / "src"
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
PKG_DATA_DIR = _resolve_pkg_data_dir()
|
|
87
|
+
|
|
88
|
+
_router_file = Path(__file__).resolve()
|
|
89
|
+
_kernel_core = str(_router_file.parent.parent.parent.parent) # src/core when installed
|
|
90
|
+
if _kernel_core not in sys.path:
|
|
91
|
+
sys.path.insert(0, _kernel_core)
|
|
92
|
+
# In development, also add the repo's src/core
|
|
93
|
+
_dev_candidate = _router_file.parent.parent.parent.parent.parent.parent
|
|
94
|
+
if "site-packages" not in str(_dev_candidate):
|
|
95
|
+
_dev_core = str(_dev_candidate / "src" / "core")
|
|
96
|
+
if _dev_core not in sys.path:
|
|
97
|
+
sys.path.insert(0, _dev_core)
|
|
98
|
+
|
|
99
|
+
# Helper for resolving mockable classes/functions — always via
|
|
100
|
+
# kernel._compat so tests patch one place
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _dep(name):
|
|
104
|
+
import higpertext.kernel._compat as _compat
|
|
105
|
+
|
|
106
|
+
if hasattr(_compat, name):
|
|
107
|
+
return getattr(_compat, name)
|
|
108
|
+
if name == "_start_session":
|
|
109
|
+
return _start_session
|
|
110
|
+
if name == "_parse_session_resources":
|
|
111
|
+
return _parse_session_resources
|
|
112
|
+
raise AttributeError(name)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
try:
|
|
116
|
+
if "htx" in sys.modules and hasattr(sys.modules["htx"], "HigpertextEngine"):
|
|
117
|
+
HigpertextEngine = sys.modules["htx"].HigpertextEngine # noqa: F841
|
|
118
|
+
else:
|
|
119
|
+
from higpertext.kernel.engine import HigpertextEngine # noqa: F401
|
|
120
|
+
from higpertext.kernel.infrastructure.cli.cli_search import HigpertextSearchMixin
|
|
121
|
+
from higpertext.adapters.gemini_adapter.gemini_adapter import GeminiAdapter
|
|
122
|
+
from higpertext.adapters.claude_adapter.claude_adapter import ClaudeAdapter
|
|
123
|
+
from higpertext.adapters.copilot_adapter.copilot_adapter import CopilotAdapter
|
|
124
|
+
from higpertext.adapters.open_code_adapter.open_code_adapter import OpenCodeAdapter
|
|
125
|
+
except ImportError as e:
|
|
126
|
+
_log.warning(f"[!] Error cargando higpertext Engine en CLI: {e}")
|
|
127
|
+
sys.exit(1)
|
|
128
|
+
|
|
129
|
+
POSTMORTEM_DIR = ROOT_DIR.parent.parent / "Test" / "postmortem"
|
|
130
|
+
GOVERNANCE_DIR = ROOT_DIR.parent.parent / "Gobernanza"
|
|
131
|
+
DOCS_DIR = ROOT_DIR / "docs"
|
|
132
|
+
|
|
133
|
+
from higpertext.kernel.app_config import (
|
|
134
|
+
IGNORE_PATTERNS as _IGNORE_PATTERNS,
|
|
135
|
+
ASSISTANT_IGNORE_FILES as _ASSISTANT_IGNORE_FILES,
|
|
136
|
+
GITIGNORE_SECTION as _GITIGNORE_SECTION,
|
|
137
|
+
GUIDE_SPECS as _GUIDE_SPECS_RAW,
|
|
138
|
+
PROFILES_SECTION as _PROFILES_SECTION,
|
|
139
|
+
RULES_SECTION as _RULES_SECTION,
|
|
140
|
+
)
|
|
141
|
+
|
|
142
|
+
_IGNORE_CONTENT = "\n".join(_IGNORE_PATTERNS)
|
|
143
|
+
_ASSISTANT_IGNORE: dict[str, tuple[str, ...]] = {
|
|
144
|
+
k: tuple(v) for k, v in _ASSISTANT_IGNORE_FILES.items()
|
|
145
|
+
}
|
|
146
|
+
_GUIDE_SPECS: dict[str, tuple[str, str, list[str]]] = {
|
|
147
|
+
k: (v["filename"], v["title"], v["intro"]) for k, v in _GUIDE_SPECS_RAW.items()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
# --- Maps assistant id to its rule-generation adapter function ---
|
|
151
|
+
_ADAPTER_MAP = {
|
|
152
|
+
"gemini": lambda ctx, root: GeminiAdapter.generate_rules(ctx, root),
|
|
153
|
+
"antigravity": lambda ctx, root: GeminiAdapter.generate_rules(ctx, root),
|
|
154
|
+
"claude": lambda ctx, root: ClaudeAdapter.generate_rules(ctx, root),
|
|
155
|
+
"copilot": lambda ctx, root: CopilotAdapter.generate_rules(ctx, root),
|
|
156
|
+
"github-copilot": lambda ctx, root: CopilotAdapter.generate_rules(ctx, root),
|
|
157
|
+
"opencode": lambda ctx, root: OpenCodeAdapter.generate_rules(ctx, root),
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# --- CLI argument parsing helpers ---
|
|
164
|
+
def _parse_kv_args(raw_args: list[str]) -> dict:
|
|
165
|
+
"""Parsea una lista ['--key', 'val', ...] en un dict."""
|
|
166
|
+
return parse_kv_args(raw_args)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class HigpertextHub(HigpertextSearchMixin):
|
|
170
|
+
"""Orquestador principal del CLI de higpertext Engine."""
|
|
171
|
+
|
|
172
|
+
def __init__(self) -> None:
|
|
173
|
+
self.version = "5.0.0"
|
|
174
|
+
engine_cls = self._load_engine_cls()
|
|
175
|
+
self.engine = engine_cls(ROOT_DIR)
|
|
176
|
+
|
|
177
|
+
# Prefers already-loaded HigpertextEngine from htx.py module to avoid
|
|
178
|
+
# a double-import cycle when CLI is invoked via the launcher shim.
|
|
179
|
+
@staticmethod
|
|
180
|
+
def _load_engine_cls():
|
|
181
|
+
try:
|
|
182
|
+
if "htx" in sys.modules and hasattr(sys.modules["htx"], "HigpertextEngine"):
|
|
183
|
+
return sys.modules["htx"].HigpertextEngine
|
|
184
|
+
from higpertext.kernel.engine import HigpertextEngine as _NE # noqa: F811
|
|
185
|
+
|
|
186
|
+
return _NE
|
|
187
|
+
except (ImportError, AttributeError):
|
|
188
|
+
from higpertext.kernel.engine import HigpertextEngine as _NE2 # noqa: F811
|
|
189
|
+
|
|
190
|
+
return _NE2
|
|
191
|
+
|
|
192
|
+
# --- Project initialization ---
|
|
193
|
+
# Creates htx.py launcher, writes ignore files, injects initial profile.
|
|
194
|
+
|
|
195
|
+
def init_project(
|
|
196
|
+
self, profile_name: str | None, assistant: str, target_dir: str | None = None
|
|
197
|
+
) -> None:
|
|
198
|
+
"""Inicializa la estructura del asistente en el proyecto destino."""
|
|
199
|
+
project_root = self._resolve_project_root(target_dir)
|
|
200
|
+
self._update_gitignore(project_root)
|
|
201
|
+
|
|
202
|
+
_log.info(f"[*] Inicializando entorno vacío para '{assistant}' en: " f"{project_root}")
|
|
203
|
+
if profile_name:
|
|
204
|
+
_log.warning(
|
|
205
|
+
f" [!] 'init' inicializa el workspace en blanco. Para "
|
|
206
|
+
f"inyectar el rol '{profile_name}' ejecuta: htx"
|
|
207
|
+
f" profile load {profile_name} --assistant {assistant}"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
self.generate_user_guide(project_root, assistant)
|
|
211
|
+
|
|
212
|
+
env_mgr = _dep("EnvironmentManager")(project_root)
|
|
213
|
+
env_data = env_mgr.initialize_environment(profile_name or "global", assistant)
|
|
214
|
+
initial_profile = profile_name or "global"
|
|
215
|
+
from higpertext.adapters.adapter_utils import get_adapter_class
|
|
216
|
+
|
|
217
|
+
adapter_cls = get_adapter_class(assistant)
|
|
218
|
+
try:
|
|
219
|
+
context = self.engine.get_agent_context(initial_profile, env_data=env_data)
|
|
220
|
+
except FileNotFoundError as e:
|
|
221
|
+
_log.warning(
|
|
222
|
+
f"[!] Perfil '{initial_profile}' no disponible en este entorno,"
|
|
223
|
+
f" omitiendo contexto: {e}"
|
|
224
|
+
)
|
|
225
|
+
context = None
|
|
226
|
+
if adapter_cls and hasattr(adapter_cls, "on_init") and context:
|
|
227
|
+
adapter_cls.on_init(context, project_root)
|
|
228
|
+
self._generate_dynamic_playbooks(project_root, assistant, copy_resources=False)
|
|
229
|
+
if context:
|
|
230
|
+
self._render_native_hooks(project_root, assistant, initial_profile, context)
|
|
231
|
+
self._refresh_semantic_graph(project_root)
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def _update_gitignore(project_root: Path) -> None:
|
|
235
|
+
"""Añade los patrones de higpertext al .gitignore del proyecto destino.
|
|
236
|
+
|
|
237
|
+
Si el bloque ya existe (detectado por el encabezado), no lo duplica.
|
|
238
|
+
"""
|
|
239
|
+
gitignore_path = project_root / ".gitignore"
|
|
240
|
+
marker = "# higpertext Engine - archivos generados (no versionar)"
|
|
241
|
+
existing = ""
|
|
242
|
+
if gitignore_path.exists():
|
|
243
|
+
try:
|
|
244
|
+
existing = gitignore_path.read_text(encoding="utf-8")
|
|
245
|
+
except OSError: # nosec B110
|
|
246
|
+
pass
|
|
247
|
+
if marker in existing:
|
|
248
|
+
return
|
|
249
|
+
with gitignore_path.open("a", encoding="utf-8") as f:
|
|
250
|
+
f.write(_GITIGNORE_SECTION)
|
|
251
|
+
_log.ok(f"[*] .gitignore actualizado en: {gitignore_path}")
|
|
252
|
+
|
|
253
|
+
@staticmethod
|
|
254
|
+
def _resolve_project_root(target_dir: str | None) -> Path:
|
|
255
|
+
if target_dir:
|
|
256
|
+
root = Path(target_dir).expanduser().resolve()
|
|
257
|
+
root.mkdir(parents=True, exist_ok=True)
|
|
258
|
+
return root
|
|
259
|
+
return Path.cwd()
|
|
260
|
+
|
|
261
|
+
@staticmethod
|
|
262
|
+
def _refresh_semantic_graph(project_root: Path) -> None:
|
|
263
|
+
try:
|
|
264
|
+
from higpertext.kernel.infrastructure.parser.semantic_graph import (
|
|
265
|
+
SemanticGraphGenerator,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
_log.info("[*] Generando grafo semántico inicial...")
|
|
269
|
+
SemanticGraphGenerator.generate(project_root)
|
|
270
|
+
_log.ok("[SUCCESS] Grafo semántico generado en " ".higpertext/state/semantic_graph.md")
|
|
271
|
+
except (ImportError, OSError) as e:
|
|
272
|
+
_log.warning(f"[WARNING] No se pudo generar el grafo semántico: {e}")
|
|
273
|
+
|
|
274
|
+
# --- Dynamic playbooks ---
|
|
275
|
+
# Reads session state and rebuilds playbook .md files with active
|
|
276
|
+
# skill/subagent links injected. Called after session-start and
|
|
277
|
+
# profile load to keep playbooks in sync with the active session.
|
|
278
|
+
|
|
279
|
+
def _generate_dynamic_playbooks(
|
|
280
|
+
self, project_root: Path, assistant: str, copy_resources: bool = True
|
|
281
|
+
) -> None:
|
|
282
|
+
"""Genera los playbooks primarios adaptados al asistente."""
|
|
283
|
+
from higpertext.kernel.infrastructure.compilers import CompilerFactory
|
|
284
|
+
CompilerFactory.create("playbook", project_root, engine_root=ROOT_DIR, pkg_data_dir=PKG_DATA_DIR, engine=self.engine).compile(assistant, copy_resources)
|
|
285
|
+
|
|
286
|
+
# --- User guide generation ---
|
|
287
|
+
# Generates a Markdown guide file for the selected assistant and writes
|
|
288
|
+
# it to the project root. Content is assembled from _GUIDE_SPECS and
|
|
289
|
+
# module-level section constants (_PROFILES_SECTION, _RULES_SECTION).
|
|
290
|
+
|
|
291
|
+
def generate_user_guide(self, target_path: Path, assistant: str) -> None:
|
|
292
|
+
"""Genera la guía del asistente con capacidades globales."""
|
|
293
|
+
from higpertext.kernel.infrastructure.compilers import CompilerFactory
|
|
294
|
+
CompilerFactory.create("guide", target_path, engine=self.engine).compile(assistant)
|
|
295
|
+
|
|
296
|
+
# --- Profile loading ---
|
|
297
|
+
# Validates the environment is initialized, then delegates rule generation
|
|
298
|
+
# to the assistant-specific adapter from _ADAPTER_MAP.
|
|
299
|
+
|
|
300
|
+
def _check_assistant_initialized(
|
|
301
|
+
self, env_mgr: "EnvironmentManager", assistant: str, project_root: Path
|
|
302
|
+
) -> "dict | None":
|
|
303
|
+
"""Verifica inicialización y retorna env_data o None."""
|
|
304
|
+
if not env_mgr.is_initialized():
|
|
305
|
+
_log.warning(
|
|
306
|
+
f"[!] Proyecto en {project_root} no inicializado. "
|
|
307
|
+
f"Ejecuta: htx init --assistant {assistant}"
|
|
308
|
+
)
|
|
309
|
+
return None
|
|
310
|
+
env_data = env_mgr.load_environment()
|
|
311
|
+
if assistant not in env_data.get("assistants", []):
|
|
312
|
+
_log.warning(
|
|
313
|
+
f"[!] '{assistant}' no está inicializado. Ejecuta: "
|
|
314
|
+
f"htx init --assistant {assistant}"
|
|
315
|
+
)
|
|
316
|
+
return None
|
|
317
|
+
return env_data
|
|
318
|
+
|
|
319
|
+
def _inject_profile_rules(
|
|
320
|
+
self, context: dict, assistant: str, profile_name: str, project_root: Path
|
|
321
|
+
) -> None:
|
|
322
|
+
_log.info(
|
|
323
|
+
f"\n[*] Inyectando perfil '{profile_name.upper()}' para "
|
|
324
|
+
f"'{assistant}' en: {project_root}"
|
|
325
|
+
)
|
|
326
|
+
_log.info(f" {context['profile']['description']}")
|
|
327
|
+
generator = _ADAPTER_MAP.get(assistant)
|
|
328
|
+
if generator:
|
|
329
|
+
rule_file = generator(context, project_root)
|
|
330
|
+
_log.ok(f"[SUCCESS] Reglas inyectadas en: {rule_file}")
|
|
331
|
+
_log.info("\n Capacidades activas del perfil:")
|
|
332
|
+
for cap in context["active_capabilities"]:
|
|
333
|
+
_log.info(f" - {cap['id']}: {cap['description']}")
|
|
334
|
+
|
|
335
|
+
def _resolve_assistant_for_profile(self, env_mgr: "EnvironmentManager", assistant: str | None) -> str:
|
|
336
|
+
if assistant:
|
|
337
|
+
return assistant
|
|
338
|
+
env_data = env_mgr.load_environment() if env_mgr.is_initialized() else {}
|
|
339
|
+
return env_data.get("assistant") or "gemini"
|
|
340
|
+
|
|
341
|
+
def _print_profile_validation(self, profile_name: str) -> None:
|
|
342
|
+
success, errors, warnings = self.engine.validate_profile(profile_name)
|
|
343
|
+
if not success or warnings:
|
|
344
|
+
if errors:
|
|
345
|
+
_log.warning(
|
|
346
|
+
f"[!] ADVERTENCIA: El perfil '{profile_name}'"
|
|
347
|
+
" tiene inconsistencias o errores:"
|
|
348
|
+
)
|
|
349
|
+
for err in errors:
|
|
350
|
+
_log.info(f" - {err}")
|
|
351
|
+
if warnings:
|
|
352
|
+
_log.info(f"[*] ALERTA: Optimización del perfil '{profile_name}':")
|
|
353
|
+
for warn in warnings:
|
|
354
|
+
_log.info(f" - {warn}")
|
|
355
|
+
_log.warning("[!] Cargando el perfil de todos modos...\n")
|
|
356
|
+
|
|
357
|
+
def _get_assistants_to_render(self, assistant: str, env_data: dict) -> list[str]:
|
|
358
|
+
assistants_to_render = [assistant]
|
|
359
|
+
for a in env_data.get("assistants", []):
|
|
360
|
+
if a not in assistants_to_render:
|
|
361
|
+
assistants_to_render.append(a)
|
|
362
|
+
return assistants_to_render
|
|
363
|
+
|
|
364
|
+
def load_agent_profile(
|
|
365
|
+
self,
|
|
366
|
+
profile_name: str,
|
|
367
|
+
assistant: str | None = None,
|
|
368
|
+
target_dir: str | None = None,
|
|
369
|
+
) -> None:
|
|
370
|
+
"""Carga un perfil e inyecta sus reglas en el proyecto."""
|
|
371
|
+
try:
|
|
372
|
+
project_root = self._resolve_project_root(target_dir)
|
|
373
|
+
env_mgr = _dep("EnvironmentManager")(project_root)
|
|
374
|
+
assistant = self._resolve_assistant_for_profile(env_mgr, assistant)
|
|
375
|
+
|
|
376
|
+
env_data = self._check_assistant_initialized(env_mgr, assistant, project_root)
|
|
377
|
+
if env_data is None:
|
|
378
|
+
return
|
|
379
|
+
|
|
380
|
+
self._print_profile_validation(profile_name)
|
|
381
|
+
|
|
382
|
+
context = self.engine.get_agent_context(profile_name, env_data=env_data)
|
|
383
|
+
if not context:
|
|
384
|
+
return
|
|
385
|
+
|
|
386
|
+
self._inject_profile_rules(context, assistant, profile_name, project_root)
|
|
387
|
+
env_mgr.add_active_profile(profile_name)
|
|
388
|
+
self._generate_dynamic_playbooks(project_root, assistant, copy_resources=False)
|
|
389
|
+
|
|
390
|
+
for ast in self._get_assistants_to_render(assistant, env_data):
|
|
391
|
+
self._render_native_hooks(project_root, ast, profile_name, context)
|
|
392
|
+
|
|
393
|
+
self._auto_start_session(project_root, profile_name, assistant)
|
|
394
|
+
except (OSError, KeyError, ValueError) as e:
|
|
395
|
+
_log.warning(f"[!] Error gestionando perfil: {e}")
|
|
396
|
+
|
|
397
|
+
def _render_native_hooks(
|
|
398
|
+
self, project_root: Path, assistant: str, profile_name: str, context: dict
|
|
399
|
+
) -> None:
|
|
400
|
+
"""Compila hooks del perfil y regenera config nativa del asistente."""
|
|
401
|
+
from higpertext.kernel.infrastructure.compilers import CompilerFactory
|
|
402
|
+
compiler = CompilerFactory.create("hook", project_root, pkg_data_dir=PKG_DATA_DIR)
|
|
403
|
+
compiler.compile(profile_name, assistant, context)
|
|
404
|
+
|
|
405
|
+
def _auto_start_session(self, project_root: Path, profile_name: str, assistant: str) -> None:
|
|
406
|
+
"""Inicia sesión automáticamente al cargar perfil si no hay una activa."""
|
|
407
|
+
try:
|
|
408
|
+
session_file = project_root / WORKSPACE_DIR_NAME / "state" / "session.json"
|
|
409
|
+
if session_file.exists():
|
|
410
|
+
data = json.loads(session_file.read_text(encoding="utf-8"))
|
|
411
|
+
# Si ya hay sesión activa para el mismo perfil y asistente, no relanzar
|
|
412
|
+
if (
|
|
413
|
+
data.get("status") == "active"
|
|
414
|
+
and data.get("profile") == profile_name
|
|
415
|
+
and data.get("assistant") == assistant
|
|
416
|
+
):
|
|
417
|
+
return
|
|
418
|
+
session_mgr = _dep("SessionManager")(project_root, ROOT_DIR)
|
|
419
|
+
session_mgr.start_session(profile_name, assistant, None, None)
|
|
420
|
+
except Exception as e:
|
|
421
|
+
_log.warning(f"[WARNING] No se pudo iniciar sesión automáticamente: {e}")
|
|
422
|
+
|
|
423
|
+
# --- Agent lifecycle (no assistant) ---
|
|
424
|
+
# Initializes .higpertext/ from src/config + src/hooks without generating
|
|
425
|
+
# any assistant-specific files (.claude/, .opencode/, etc.)
|
|
426
|
+
|
|
427
|
+
_RESERVED_PROFILES = _RESERVED_PROFILES_CFG
|
|
428
|
+
|
|
429
|
+
def agent_init(self, profile_name: str, target_dir: str | None = None) -> None:
|
|
430
|
+
"""Crea la estructura base del agente e inicializa su ambiente."""
|
|
431
|
+
if profile_name in self._RESERVED_PROFILES:
|
|
432
|
+
_log.error(f"[ERROR] '{profile_name}' es un perfil reservado del motor higpertext.")
|
|
433
|
+
_log.info(f" Nombres reservados: {', '.join(sorted(self._RESERVED_PROFILES))}")
|
|
434
|
+
return None
|
|
435
|
+
|
|
436
|
+
project_root = self._resolve_project_root(target_dir)
|
|
437
|
+
_log.info(f"[*] Creando agente '{profile_name}' en: {project_root}")
|
|
438
|
+
|
|
439
|
+
self._scaffold_agent_structure(project_root)
|
|
440
|
+
|
|
441
|
+
env_mgr = _dep("EnvironmentManager")(project_root)
|
|
442
|
+
env_data = env_mgr.initialize_environment(profile_name, "agent")
|
|
443
|
+
|
|
444
|
+
self._compile_hook_layers(project_root, profile_name)
|
|
445
|
+
self._refresh_semantic_graph(project_root)
|
|
446
|
+
|
|
447
|
+
_log.ok(f"[SUCCESS] Agente listo en: {project_root}")
|
|
448
|
+
_log.info(f" Perfil : {profile_name}")
|
|
449
|
+
_log.info(f" Ejecuta : htx agent status")
|
|
450
|
+
return env_data
|
|
451
|
+
|
|
452
|
+
def _scaffold_agent_structure(self, project_root: Path) -> None:
|
|
453
|
+
"""Crea la estructura de carpetas base e inyecta perfiles y hooks globales."""
|
|
454
|
+
dirs = [
|
|
455
|
+
"src/higpertext/capabilities",
|
|
456
|
+
"src/config/profiles",
|
|
457
|
+
"src/config/governance",
|
|
458
|
+
"src/config/environments",
|
|
459
|
+
"src/config/templates",
|
|
460
|
+
"src/config/workflows",
|
|
461
|
+
"src/config/hooks/profiles",
|
|
462
|
+
"src/config/hooks/custom",
|
|
463
|
+
"content",
|
|
464
|
+
]
|
|
465
|
+
for d in dirs:
|
|
466
|
+
(project_root / d).mkdir(parents=True, exist_ok=True)
|
|
467
|
+
|
|
468
|
+
self._copy_base_profiles(project_root)
|
|
469
|
+
self._copy_base_governance(project_root)
|
|
470
|
+
_log.info(f"[*] Estructura base creada en: {project_root / 'src'}")
|
|
471
|
+
|
|
472
|
+
def _copy_base_governance(self, project_root: Path) -> None:
|
|
473
|
+
"""Copia guidelines_contract.json y security_guardrails.json al agente."""
|
|
474
|
+
import shutil as _shutil
|
|
475
|
+
|
|
476
|
+
engine_gov = PKG_DATA_DIR / "config" / "governance"
|
|
477
|
+
dest_gov = project_root / "src" / "config" / "governance"
|
|
478
|
+
copied = []
|
|
479
|
+
for gov_file in ("guidelines_contract.json", "security_guardrails.json"):
|
|
480
|
+
src = engine_gov / gov_file
|
|
481
|
+
dst = dest_gov / gov_file
|
|
482
|
+
if src.exists() and not dst.exists():
|
|
483
|
+
_shutil.copy2(src, dst)
|
|
484
|
+
copied.append(gov_file)
|
|
485
|
+
if copied:
|
|
486
|
+
_log.info(f"[*] Gobernanza base copiada: {', '.join(copied)}")
|
|
487
|
+
|
|
488
|
+
def _copy_base_profiles(self, project_root: Path) -> None:
|
|
489
|
+
"""Copia agent_designer.json al nuevo agente. base_agent es interno del motor."""
|
|
490
|
+
engine_profiles = PKG_DATA_DIR / "config" / "profiles"
|
|
491
|
+
dest_profiles = project_root / "src" / "config" / "profiles"
|
|
492
|
+
src = engine_profiles / "agent_designer.json"
|
|
493
|
+
dst = dest_profiles / "agent_designer.json"
|
|
494
|
+
if src.exists() and not dst.exists():
|
|
495
|
+
import shutil as _shutil
|
|
496
|
+
|
|
497
|
+
_shutil.copy2(src, dst)
|
|
498
|
+
_log.info(f"[*] Perfil base copiado: agent_designer")
|
|
499
|
+
|
|
500
|
+
@staticmethod
|
|
501
|
+
def _copy_resource_files(sub_src: Path, sub_dest: Path, label: str) -> None:
|
|
502
|
+
import shutil as _shutil
|
|
503
|
+
|
|
504
|
+
for f in sub_src.rglob("*"):
|
|
505
|
+
if not f.is_file():
|
|
506
|
+
continue
|
|
507
|
+
if f.suffix not in (".json", ".py"):
|
|
508
|
+
continue
|
|
509
|
+
rel = f.relative_to(sub_src)
|
|
510
|
+
dst = sub_dest / rel
|
|
511
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
512
|
+
_shutil.copy2(f, dst)
|
|
513
|
+
if f.suffix == ".json":
|
|
514
|
+
_log.info(f"[*] {label} instalada: {rel}")
|
|
515
|
+
|
|
516
|
+
def _install_source_resources(self, project_root: Path, source_path: Path) -> None:
|
|
517
|
+
src_root = source_path / "src"
|
|
518
|
+
for sub_src, sub_dest, label in [
|
|
519
|
+
(
|
|
520
|
+
src_root / "capabilities",
|
|
521
|
+
project_root / WORKSPACE_DIR_NAME / "capabilities",
|
|
522
|
+
"Capability",
|
|
523
|
+
),
|
|
524
|
+
(
|
|
525
|
+
src_root / "config" / "hooks",
|
|
526
|
+
project_root / "src" / "config" / "hooks",
|
|
527
|
+
"Hook",
|
|
528
|
+
),
|
|
529
|
+
(
|
|
530
|
+
src_root / "config" / "governance",
|
|
531
|
+
project_root / "src" / "config" / "governance",
|
|
532
|
+
"Governance",
|
|
533
|
+
),
|
|
534
|
+
]:
|
|
535
|
+
if sub_src.exists():
|
|
536
|
+
self._copy_resource_files(sub_src, sub_dest, label)
|
|
537
|
+
|
|
538
|
+
@staticmethod
|
|
539
|
+
def _install_profile_rules(project_root: Path, src_root: Path, profile_name: str) -> None:
|
|
540
|
+
import shutil as _shutil
|
|
541
|
+
|
|
542
|
+
profile_rules_src = (
|
|
543
|
+
src_root / "config" / "hooks" / "profiles" / profile_name / "profile_rules.json"
|
|
544
|
+
)
|
|
545
|
+
if not profile_rules_src.exists():
|
|
546
|
+
return
|
|
547
|
+
for assistant_hooks in (
|
|
548
|
+
project_root / ".claude" / "hooks" / "_rules",
|
|
549
|
+
project_root / ".gemini" / "hooks" / "_rules",
|
|
550
|
+
):
|
|
551
|
+
if assistant_hooks.parent.parent.exists():
|
|
552
|
+
assistant_hooks.mkdir(parents=True, exist_ok=True)
|
|
553
|
+
_shutil.copy2(profile_rules_src, assistant_hooks / "profile_rules.json")
|
|
554
|
+
_log.info(f"[*] Profile rules instaladas en: {assistant_hooks}")
|
|
555
|
+
|
|
556
|
+
def install_profile(
|
|
557
|
+
self,
|
|
558
|
+
profile_name: str,
|
|
559
|
+
target_dir: str | None = None,
|
|
560
|
+
source: str | None = None,
|
|
561
|
+
) -> None:
|
|
562
|
+
"""Copia un perfil a .higpertext/profiles/ del proyecto activo."""
|
|
563
|
+
import shutil as _shutil
|
|
564
|
+
|
|
565
|
+
project_root = self._resolve_project_root(target_dir)
|
|
566
|
+
sub_path = "src/config/profiles" if source else "src/higpertext/profiles"
|
|
567
|
+
src = Path(source or ROOT_DIR) / sub_path / f"{profile_name}.json"
|
|
568
|
+
if not src.exists():
|
|
569
|
+
_log.error(f"[ERROR] Perfil '{profile_name}' no encontrado en: {src}")
|
|
570
|
+
return
|
|
571
|
+
dest = project_root / WORKSPACE_DIR_NAME / "profiles" / f"{profile_name}.json"
|
|
572
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
573
|
+
_shutil.copy2(src, dest)
|
|
574
|
+
_log.ok(f"[SUCCESS] Perfil '{profile_name}' instalado en: {dest}")
|
|
575
|
+
|
|
576
|
+
if source:
|
|
577
|
+
source_path = Path(source)
|
|
578
|
+
self._install_source_resources(project_root, source_path)
|
|
579
|
+
self._install_profile_rules(project_root, source_path / "src", profile_name)
|
|
580
|
+
self._register_agent_dir(project_root, source_path)
|
|
581
|
+
self._copy_templates(source_path / "src", project_root)
|
|
582
|
+
|
|
583
|
+
def _copy_templates(self, src_root: Path, project_root: Path) -> None:
|
|
584
|
+
"""Copia src/templates/ del agente externo al motor para resolución de skills."""
|
|
585
|
+
import shutil as _shutil
|
|
586
|
+
|
|
587
|
+
templates_src = src_root / "templates"
|
|
588
|
+
if not templates_src.exists():
|
|
589
|
+
return
|
|
590
|
+
templates_dst = project_root / WORKSPACE_DIR_NAME / "agent_templates"
|
|
591
|
+
templates_dst.mkdir(parents=True, exist_ok=True)
|
|
592
|
+
for f in templates_src.rglob("*"):
|
|
593
|
+
if f.is_file():
|
|
594
|
+
dst = templates_dst / f.relative_to(templates_src)
|
|
595
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
596
|
+
_shutil.copy2(f, dst)
|
|
597
|
+
_log.info(f"[*] Templates del agente copiadas en: {templates_dst}")
|
|
598
|
+
|
|
599
|
+
def _register_agent_dir(self, project_root: Path, agent_dir: Path) -> None:
|
|
600
|
+
"""Persiste agent_dir en environment.json para resolución de recursos externos."""
|
|
601
|
+
env_file = project_root / WORKSPACE_DIR_NAME / "config" / "environment.json"
|
|
602
|
+
try:
|
|
603
|
+
data = json.loads(env_file.read_text(encoding="utf-8"))
|
|
604
|
+
data["agent_dir"] = str(agent_dir.resolve())
|
|
605
|
+
env_file.write_text(json.dumps(data, indent=4, ensure_ascii=False), encoding="utf-8")
|
|
606
|
+
_log.info(f"[*] agent_dir registrado: {agent_dir.resolve()}")
|
|
607
|
+
except Exception as exc:
|
|
608
|
+
_log.warning(f"[!] No se pudo registrar agent_dir: {exc}")
|
|
609
|
+
|
|
610
|
+
def agent_status(self, target_dir: str | None = None) -> None:
|
|
611
|
+
"""Muestra el estado del ambiente del agente."""
|
|
612
|
+
project_root = self._resolve_project_root(target_dir)
|
|
613
|
+
env_mgr = _dep("EnvironmentManager")(project_root)
|
|
614
|
+
if not env_mgr.is_initialized():
|
|
615
|
+
_log.warning("[!] Ambiente no inicializado. Ejecuta: htx agent init --profile <nombre>")
|
|
616
|
+
return
|
|
617
|
+
env_data = env_mgr.load_environment()
|
|
618
|
+
profiles = env_data.get("active_profiles", env_data.get("active_profile", "—"))
|
|
619
|
+
_log.info(f"\n── Agent Status ───────────────────────────")
|
|
620
|
+
_log.info(f" Perfil(es) : {profiles}")
|
|
621
|
+
_log.info(f" Entorno : {project_root / WORKSPACE_DIR_NAME / 'config' / 'environment.json'}")
|
|
622
|
+
hooks_cfg = project_root / WORKSPACE_DIR_NAME / "config" / "hooks_config.json"
|
|
623
|
+
hooks_status = "compilados" if hooks_cfg.exists() else "no compilados"
|
|
624
|
+
_log.info(f" Hooks : {hooks_status}")
|
|
625
|
+
_log.info("")
|
|
626
|
+
def agent_clean(self, target_dir: str | None = None) -> None:
|
|
627
|
+
"""Limpia el estado efímero del agente sin borrar configuración base."""
|
|
628
|
+
project_root = self._resolve_project_root(target_dir)
|
|
629
|
+
session_file = project_root / WORKSPACE_DIR_NAME / "state" / "session.json"
|
|
630
|
+
if session_file.exists():
|
|
631
|
+
session_file.unlink()
|
|
632
|
+
_log.ok("[SUCCESS] Sesión efímera eliminada.")
|
|
633
|
+
else:
|
|
634
|
+
_log.info("[*] No hay sesión activa que limpiar.")
|
|
635
|
+
|
|
636
|
+
@staticmethod
|
|
637
|
+
def _compile_hook_layers(project_root: Path, profile_name: str) -> None:
|
|
638
|
+
"""Compila las capas de hooks (global + perfil + custom) en hooks_config.json."""
|
|
639
|
+
from higpertext.kernel.infrastructure.compilers import CompilerFactory
|
|
640
|
+
CompilerFactory.create("hook", project_root, pkg_data_dir=PKG_DATA_DIR).compile(profile_name)
|
|
641
|
+
|
|
642
|
+
# --- Knowledge search ---
|
|
643
|
+
# Delegates to HigpertextSearchMixin._walk_search which traverses governance,
|
|
644
|
+
# docs, .memory and src/config/governance paths. Results are capped at
|
|
645
|
+
# 5 files and 3 snippets per file to avoid context overflow.
|
|
646
|
+
|
|
647
|
+
def ask_knowledge(self, query: str) -> None:
|
|
648
|
+
"""Busca respuestas en gobernanza, docs, memoria y grafo semántico."""
|
|
649
|
+
_log.info(f"[*] Buscando información sobre: '{query}'...")
|
|
650
|
+
query_terms = [t.lower() for t in query.split() if t]
|
|
651
|
+
if not query_terms:
|
|
652
|
+
_log.warning("[!] La consulta de búsqueda está vacía.")
|
|
653
|
+
return
|
|
654
|
+
project_dir = Path(os.getcwd()).resolve()
|
|
655
|
+
search_paths = [
|
|
656
|
+
GOVERNANCE_DIR,
|
|
657
|
+
DOCS_DIR,
|
|
658
|
+
ROOT_DIR / ".memory",
|
|
659
|
+
PKG_DATA_DIR / "config" / "governance",
|
|
660
|
+
project_dir / "src" / "config" / "governance",
|
|
661
|
+
project_dir / "docs",
|
|
662
|
+
project_dir / ".memory",
|
|
663
|
+
]
|
|
664
|
+
search_paths = [p for p in search_paths if p.exists()]
|
|
665
|
+
matches = self._walk_search(search_paths, query_terms, project_dir)
|
|
666
|
+
|
|
667
|
+
# Búsqueda en el grafo semántico — proyecto primero, luego Engine
|
|
668
|
+
graph_path = project_dir / WORKSPACE_DIR_NAME / "state" / "semantic_graph.json"
|
|
669
|
+
if not graph_path.exists():
|
|
670
|
+
graph_path = ROOT_DIR / WORKSPACE_DIR_NAME / "state" / "semantic_graph.json"
|
|
671
|
+
graph_matches = self._search_semantic_graph(graph_path, query_terms, project_dir)
|
|
672
|
+
if graph_matches:
|
|
673
|
+
_log.info("\n🔍 Símbolos encontrados en el grafo semántico:")
|
|
674
|
+
self._print_search_results(graph_matches)
|
|
675
|
+
|
|
676
|
+
self._print_search_results(matches)
|
|
677
|
+
|
|
678
|
+
# --- Task runner ---
|
|
679
|
+
# Resolves capability → builds subprocess command → validates contract
|
|
680
|
+
# → persists result in .memory/ via memory-manager (skipped for itself).
|
|
681
|
+
|
|
682
|
+
@staticmethod
|
|
683
|
+
def _read_session_state(project_root: Path) -> tuple[bool, str | None]:
|
|
684
|
+
from higpertext.kernel.infrastructure.compilers.playbook_compiler import PlaybookCompiler
|
|
685
|
+
compiler = PlaybookCompiler(project_root, None, None, None)
|
|
686
|
+
return compiler._read_session_state()
|
|
687
|
+
|
|
688
|
+
@staticmethod
|
|
689
|
+
def _collect_skill_links(skills_dest: Path, project_root: Path) -> list[str]:
|
|
690
|
+
from higpertext.kernel.infrastructure.compilers.playbook_compiler import PlaybookCompiler
|
|
691
|
+
compiler = PlaybookCompiler(project_root, None, None, None)
|
|
692
|
+
return compiler._collect_skill_links(skills_dest)
|
|
693
|
+
|
|
694
|
+
@staticmethod
|
|
695
|
+
def _collect_subagent_links(subagents_dest: Path, project_root: Path) -> list[str]:
|
|
696
|
+
from higpertext.kernel.infrastructure.compilers.playbook_compiler import PlaybookCompiler
|
|
697
|
+
compiler = PlaybookCompiler(project_root, None, None, None)
|
|
698
|
+
return compiler._collect_subagent_links(subagents_dest)
|
|
699
|
+
|
|
700
|
+
def _build_playbook_strings(
|
|
701
|
+
self,
|
|
702
|
+
has_session: bool,
|
|
703
|
+
session_id: str | None,
|
|
704
|
+
skills_dest: Path,
|
|
705
|
+
subagents_dest: Path,
|
|
706
|
+
project_root: Path,
|
|
707
|
+
) -> tuple[str, str]:
|
|
708
|
+
from higpertext.kernel.infrastructure.compilers.playbook_compiler import PlaybookCompiler
|
|
709
|
+
compiler = PlaybookCompiler(project_root, None, None, None)
|
|
710
|
+
return compiler._build_playbook_strings(has_session, session_id, skills_dest, subagents_dest)
|
|
711
|
+
|
|
712
|
+
def _task_service(self) -> CapabilityTaskService:
|
|
713
|
+
return CapabilityTaskService(self.engine, ROOT_DIR, PKG_DATA_DIR, _log, _dep)
|
|
714
|
+
|
|
715
|
+
# Capability resolution: profile search first, then custom fallback.
|
|
716
|
+
def _resolve_from_profiles(
|
|
717
|
+
self, task_name: str, profiles: list[str]
|
|
718
|
+
) -> tuple[dict | None, str | None]:
|
|
719
|
+
return self._task_service().resolve_from_profiles(task_name, profiles)
|
|
720
|
+
|
|
721
|
+
def _resolve_custom_capability(self, task_name: str) -> tuple[dict | None, str | None]:
|
|
722
|
+
return self._task_service().resolve_custom_capability(task_name)
|
|
723
|
+
|
|
724
|
+
def _resolve_capability(
|
|
725
|
+
self, task_name: str, active_profile: str
|
|
726
|
+
) -> tuple[dict | None, str | None]:
|
|
727
|
+
"""Busca la capacidad en perfil activo, global y custom."""
|
|
728
|
+
return self._task_service().resolve_capability(task_name, active_profile)
|
|
729
|
+
|
|
730
|
+
@staticmethod
|
|
731
|
+
def _extra_args(params: dict, prefix: str = "--") -> list[str]:
|
|
732
|
+
return extra_args(params, prefix)
|
|
733
|
+
|
|
734
|
+
def _resolve_entrypoint(self, entrypoint: str, base_dir: str = "") -> Path:
|
|
735
|
+
"""Resuelve el path del entrypoint con prioridad:
|
|
736
|
+
1. Absoluto y existente → se usa tal cual
|
|
737
|
+
2. Relativo a base_dir (capabilities custom con _base_dir anotado)
|
|
738
|
+
3. Relativo a cwd/{WORKSPACE_DIR_NAME}/capabilities/ (extensiones instaladas)
|
|
739
|
+
4. Relativo a ROOT_DIR/src (capabilities built-in)
|
|
740
|
+
"""
|
|
741
|
+
return resolve_entrypoint(entrypoint, PKG_DATA_DIR, Path(os.getcwd()), base_dir)
|
|
742
|
+
|
|
743
|
+
def _build_cmd(
|
|
744
|
+
self,
|
|
745
|
+
entrypoint: str,
|
|
746
|
+
language: str,
|
|
747
|
+
params: dict,
|
|
748
|
+
base_dir: str = "",
|
|
749
|
+
cap_id: str = "",
|
|
750
|
+
) -> list[str]:
|
|
751
|
+
return build_command(
|
|
752
|
+
entrypoint,
|
|
753
|
+
language,
|
|
754
|
+
params,
|
|
755
|
+
ROOT_DIR,
|
|
756
|
+
PKG_DATA_DIR,
|
|
757
|
+
Path(os.getcwd()),
|
|
758
|
+
base_dir=base_dir,
|
|
759
|
+
cap_id=cap_id,
|
|
760
|
+
)
|
|
761
|
+
|
|
762
|
+
def _build_ps_cmd(self, path_to_script: Path, params: dict) -> list[str]:
|
|
763
|
+
return build_powershell_command(path_to_script, params)
|
|
764
|
+
|
|
765
|
+
@staticmethod
|
|
766
|
+
def _build_memory_notes(
|
|
767
|
+
task_name: str,
|
|
768
|
+
params: dict,
|
|
769
|
+
result: subprocess.CompletedProcess,
|
|
770
|
+
contract_success: bool,
|
|
771
|
+
contract_errors: list,
|
|
772
|
+
) -> str:
|
|
773
|
+
return build_memory_notes(task_name, params, result, contract_success, contract_errors)
|
|
774
|
+
|
|
775
|
+
def _save_memory(
|
|
776
|
+
self,
|
|
777
|
+
task_name: str,
|
|
778
|
+
params: dict,
|
|
779
|
+
result: subprocess.CompletedProcess,
|
|
780
|
+
contract_success: bool,
|
|
781
|
+
contract_errors: list,
|
|
782
|
+
) -> None:
|
|
783
|
+
self._task_service().save_memory(
|
|
784
|
+
task_name, params, result, contract_success, contract_errors
|
|
785
|
+
)
|
|
786
|
+
|
|
787
|
+
def _get_active_profile(self) -> str:
|
|
788
|
+
return self._task_service().get_active_profile()
|
|
789
|
+
|
|
790
|
+
@staticmethod
|
|
791
|
+
def _stream_subprocess(cmd: list[str]) -> subprocess.CompletedProcess:
|
|
792
|
+
"""Ejecuta un subproceso con streaming línea a línea hacia stdout."""
|
|
793
|
+
import io
|
|
794
|
+
|
|
795
|
+
stdout_lines: list[str] = []
|
|
796
|
+
stderr_lines: list[str] = []
|
|
797
|
+
try:
|
|
798
|
+
with subprocess.Popen( # nosec B603 B607
|
|
799
|
+
cmd,
|
|
800
|
+
cwd=os.getcwd(),
|
|
801
|
+
stdout=subprocess.PIPE,
|
|
802
|
+
stderr=subprocess.PIPE,
|
|
803
|
+
text=True,
|
|
804
|
+
bufsize=1,
|
|
805
|
+
) as proc:
|
|
806
|
+
# Stream stdout en tiempo real
|
|
807
|
+
for line in proc.stdout or io.StringIO():
|
|
808
|
+
_log.info(line, end="", flush=True)
|
|
809
|
+
stdout_lines.append(line)
|
|
810
|
+
# Captura stderr al cerrar
|
|
811
|
+
stderr_data = proc.stderr.read() if proc.stderr else ""
|
|
812
|
+
if stderr_data:
|
|
813
|
+
stderr_lines.append(stderr_data)
|
|
814
|
+
proc.wait()
|
|
815
|
+
returncode = proc.returncode
|
|
816
|
+
except OSError as e:
|
|
817
|
+
return subprocess.CompletedProcess(args=cmd, returncode=1, stdout="", stderr=str(e))
|
|
818
|
+
return subprocess.CompletedProcess(
|
|
819
|
+
args=cmd,
|
|
820
|
+
returncode=returncode,
|
|
821
|
+
stdout="".join(stdout_lines),
|
|
822
|
+
stderr="".join(stderr_lines),
|
|
823
|
+
)
|
|
824
|
+
|
|
825
|
+
def _execute_task_cmd(
|
|
826
|
+
self, task_name: str, target_cap: dict, params: dict, stream: bool = False
|
|
827
|
+
) -> subprocess.CompletedProcess:
|
|
828
|
+
"""Construye y ejecuta el comando de la tarea con cache + retry + streaming."""
|
|
829
|
+
return self._task_service().execute_task_cmd(
|
|
830
|
+
task_name, target_cap, params, self._stream_subprocess, stream=stream
|
|
831
|
+
)
|
|
832
|
+
|
|
833
|
+
def _validate_contract(
|
|
834
|
+
self, target_cap: dict, params: dict, result: subprocess.CompletedProcess
|
|
835
|
+
) -> tuple[bool, list]:
|
|
836
|
+
if result.returncode != 0:
|
|
837
|
+
return True, []
|
|
838
|
+
return self._task_service().validate_contract(target_cap, params, result)
|
|
839
|
+
|
|
840
|
+
@staticmethod
|
|
841
|
+
def _warn_missing_pat(task_name: str) -> None:
|
|
842
|
+
CapabilityTaskService(None, ROOT_DIR, PKG_DATA_DIR, _log, _dep).warn_missing_pat(task_name)
|
|
843
|
+
|
|
844
|
+
def run_task(
|
|
845
|
+
self, task_name: str, params: dict, stream: bool = False, no_cache: bool = False
|
|
846
|
+
) -> bool:
|
|
847
|
+
"""Ejecuta una capacidad técnica validando el perfil activo."""
|
|
848
|
+
try:
|
|
849
|
+
active_profile = self._get_active_profile()
|
|
850
|
+
target_cap, found_profile = self._resolve_capability(task_name, active_profile)
|
|
851
|
+
if not target_cap:
|
|
852
|
+
_log.warning(
|
|
853
|
+
f"[!] Capacidad '{task_name}' no disponible para el "
|
|
854
|
+
f"perfil '{active_profile.upper()}'."
|
|
855
|
+
)
|
|
856
|
+
return False
|
|
857
|
+
_log.info(f"[*] Capacidad '{task_name}' resuelta: {found_profile.upper()}")
|
|
858
|
+
validation = _dep("normalize_and_validate_params")(target_cap, params)
|
|
859
|
+
if not validation.ok:
|
|
860
|
+
_log.error(f"[ERROR] Parámetros inválidos para '{task_name}':")
|
|
861
|
+
for err in validation.errors:
|
|
862
|
+
_log.info(f" - {err}")
|
|
863
|
+
return False
|
|
864
|
+
for warning in validation.warnings:
|
|
865
|
+
_log.warning(f"[PARAM] {warning}")
|
|
866
|
+
params = validation.params
|
|
867
|
+
self._warn_missing_pat(task_name)
|
|
868
|
+
_log.info(f"[*] Ejecutando tarea: {task_name}")
|
|
869
|
+
|
|
870
|
+
if no_cache:
|
|
871
|
+
cache = _dep("get_capability_cache")()
|
|
872
|
+
cache.invalidate(target_cap.get("id", task_name))
|
|
873
|
+
|
|
874
|
+
result = self._execute_task_cmd(task_name, target_cap, params, stream=stream)
|
|
875
|
+
ok, errs = self._validate_contract(target_cap, params, result)
|
|
876
|
+
status = self._report_task_result(task_name, result, ok, errs)
|
|
877
|
+
if task_name != "memory-manager":
|
|
878
|
+
self._save_memory(task_name, params, result, ok, errs)
|
|
879
|
+
return status
|
|
880
|
+
except (OSError, ValueError) as e:
|
|
881
|
+
_log.warning(f"[!] Error inesperado: {e}")
|
|
882
|
+
return False
|
|
883
|
+
|
|
884
|
+
@staticmethod
|
|
885
|
+
def _print_task_failure(
|
|
886
|
+
task_name: str,
|
|
887
|
+
result: subprocess.CompletedProcess,
|
|
888
|
+
contract_success: bool,
|
|
889
|
+
contract_errors: list,
|
|
890
|
+
) -> None:
|
|
891
|
+
print_task_failure(task_name, result, contract_success, contract_errors, _log)
|
|
892
|
+
|
|
893
|
+
@staticmethod
|
|
894
|
+
def _report_task_result(
|
|
895
|
+
task_name: str,
|
|
896
|
+
result: subprocess.CompletedProcess,
|
|
897
|
+
contract_success: bool,
|
|
898
|
+
contract_errors: list,
|
|
899
|
+
) -> bool:
|
|
900
|
+
return report_task_result(task_name, result, contract_success, contract_errors, _log)
|
|
901
|
+
|
|
902
|
+
# --- Workflow runner ---
|
|
903
|
+
# Loads a workflow definition, resolves its parameters from CLI args,
|
|
904
|
+
# and executes each step in the chain sequentially, aborting on failure
|
|
905
|
+
# unless the step specifies on_failure != "abort".
|
|
906
|
+
|
|
907
|
+
def run_workflow(self, workflow_id: str, cli_params: dict) -> bool:
|
|
908
|
+
"""Ejecuta un flujo de trabajo cargándolo y ejecutando su cadena."""
|
|
909
|
+
try:
|
|
910
|
+
workflow = self.engine.workflows.load_workflow(workflow_id)
|
|
911
|
+
except FileNotFoundError as e:
|
|
912
|
+
_log.warning(f"[!] {e}")
|
|
913
|
+
return False
|
|
914
|
+
|
|
915
|
+
_log.info(f"[*] Ejecutando flujo: {workflow.get('name', workflow_id)}")
|
|
916
|
+
params = self._resolve_workflow_params(workflow, cli_params)
|
|
917
|
+
return self._execute_workflow_chain(workflow.get("execution_chain", []), params)
|
|
918
|
+
|
|
919
|
+
def list_workflows_cli(self) -> None:
|
|
920
|
+
_log.info("[*] Flujos de trabajo disponibles:")
|
|
921
|
+
for wf in self.engine.workflows.list_workflows():
|
|
922
|
+
_log.info(f" - {wf['id']}: {wf['name']} ({wf['description']})")
|
|
923
|
+
|
|
924
|
+
@staticmethod
|
|
925
|
+
def _resolve_workflow_params(workflow: dict, cli_params: dict) -> dict:
|
|
926
|
+
return resolve_workflow_params(workflow, cli_params)
|
|
927
|
+
|
|
928
|
+
def _execute_workflow_chain(self, chain: list, params: dict) -> bool:
|
|
929
|
+
# --- Feature 2: Parallel Executor ---
|
|
930
|
+
executor = _dep("ParallelExecutor")(run_task_fn=self.run_task)
|
|
931
|
+
return executor.execute_chain(
|
|
932
|
+
chain=chain,
|
|
933
|
+
params=params,
|
|
934
|
+
interpolate_fn=self._interpolate_params,
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
def _interpolate_str(self, val: str, resolved_params: dict) -> str:
|
|
938
|
+
try:
|
|
939
|
+
return val.format(**resolved_params)
|
|
940
|
+
except KeyError as e:
|
|
941
|
+
_log.warning(f"[Warning] Placeholder {e} no provisto: {val}")
|
|
942
|
+
return val
|
|
943
|
+
|
|
944
|
+
def _interpolate_val(self, val: object, resolved_params: dict) -> object:
|
|
945
|
+
if isinstance(val, str):
|
|
946
|
+
return self._interpolate_str(val, resolved_params)
|
|
947
|
+
if isinstance(val, dict):
|
|
948
|
+
return {k: self._interpolate_val(v, resolved_params) for k, v in val.items()}
|
|
949
|
+
if isinstance(val, list):
|
|
950
|
+
return [self._interpolate_val(i, resolved_params) for i in val]
|
|
951
|
+
return val
|
|
952
|
+
|
|
953
|
+
def _interpolate_params(self, step_params: dict, resolved_params: dict) -> dict:
|
|
954
|
+
return {k: self._interpolate_val(v, resolved_params) for k, v in step_params.items()}
|
|
955
|
+
|
|
956
|
+
|
|
957
|
+
# --- CLI subcommand builder helpers ---
|
|
958
|
+
# Each helper registers one logical group of subcommands on the subparser.
|
|
959
|
+
# Split to keep _build_parser() under 30 lines.
|
|
960
|
+
def _add_task_ask_subparsers(subs: argparse.Action) -> None:
|
|
961
|
+
add_task_ask_subparsers(subs)
|
|
962
|
+
|
|
963
|
+
|
|
964
|
+
def _add_profile_init_subparsers(subs: argparse.Action) -> None:
|
|
965
|
+
add_profile_init_subparsers(subs, _ASSISTANTS)
|
|
966
|
+
|
|
967
|
+
|
|
968
|
+
def _add_session_workflow_subparsers(subs: argparse.Action) -> None:
|
|
969
|
+
add_session_workflow_subparsers(subs, _ASSISTANTS)
|
|
970
|
+
|
|
971
|
+
|
|
972
|
+
def _add_health_subparser(subs: argparse.Action) -> None:
|
|
973
|
+
add_health_subparser(subs)
|
|
974
|
+
|
|
975
|
+
|
|
976
|
+
def _add_agent_subparser(subs: argparse.Action) -> None:
|
|
977
|
+
add_agent_subparser(subs)
|
|
978
|
+
|
|
979
|
+
|
|
980
|
+
def _add_roadmap_subparser(subs: argparse.Action) -> None:
|
|
981
|
+
add_roadmap_subparser(subs)
|
|
982
|
+
|
|
983
|
+
|
|
984
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
985
|
+
return build_parser(_ASSISTANTS)
|
|
986
|
+
|
|
987
|
+
|
|
988
|
+
# --- Session lifecycle dispatchers ---
|
|
989
|
+
# Decoupled from HigpertextHub to keep the class focused on orchestration logic.
|
|
990
|
+
def _parse_session_resources(
|
|
991
|
+
args: argparse.Namespace,
|
|
992
|
+
) -> tuple[list | None, list | None]:
|
|
993
|
+
return parse_session_resources(args)
|
|
994
|
+
|
|
995
|
+
|
|
996
|
+
def _start_session(
|
|
997
|
+
nexus: "HigpertextHub",
|
|
998
|
+
session_mgr: "SessionManager",
|
|
999
|
+
args: argparse.Namespace,
|
|
1000
|
+
default_profile: str,
|
|
1001
|
+
default_assistant: str,
|
|
1002
|
+
) -> None:
|
|
1003
|
+
start_session(nexus, session_mgr, args, default_profile, default_assistant)
|
|
1004
|
+
|
|
1005
|
+
|
|
1006
|
+
def _dispatch_session(nexus: "HigpertextHub", args: argparse.Namespace) -> None:
|
|
1007
|
+
session_mgr = _dep("SessionManager")(Path.cwd(), ROOT_DIR)
|
|
1008
|
+
assistant, profile = session_mgr.get_assistant_and_profile()
|
|
1009
|
+
if args.action == "start":
|
|
1010
|
+
_dep("_start_session")(nexus, session_mgr, args, profile, assistant)
|
|
1011
|
+
elif args.action == "clean":
|
|
1012
|
+
session_mgr.clean_session()
|
|
1013
|
+
nexus.load_agent_profile(profile, assistant)
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def _dispatch_workflow(nexus: "HigpertextHub", args: argparse.Namespace) -> None:
|
|
1017
|
+
dispatch_workflow(nexus, args)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def _pop_bool_flag(task_args: list[str], flag: str, default: bool) -> tuple[bool, list[str]]:
|
|
1021
|
+
return pop_bool_flag(task_args, flag, default)
|
|
1022
|
+
|
|
1023
|
+
|
|
1024
|
+
def _dispatch_task(nexus: "HigpertextHub", args: argparse.Namespace) -> None:
|
|
1025
|
+
dispatch_task(nexus, args)
|
|
1026
|
+
|
|
1027
|
+
|
|
1028
|
+
def _dispatch_health(args: argparse.Namespace) -> None:
|
|
1029
|
+
from higpertext.kernel.infrastructure.diagnostics.health import run_health_check
|
|
1030
|
+
|
|
1031
|
+
sys.exit(run_health_check(verbose=getattr(args, "verbose", False)))
|
|
1032
|
+
|
|
1033
|
+
|
|
1034
|
+
def _dispatch_doctor(args: argparse.Namespace) -> None:
|
|
1035
|
+
from higpertext.capabilities.common.scripts.doctor import main as doctor_main
|
|
1036
|
+
|
|
1037
|
+
doctor_main(["--json", "true"] if getattr(args, "json", False) else [])
|
|
1038
|
+
|
|
1039
|
+
|
|
1040
|
+
def _dispatch_command(
|
|
1041
|
+
nexus: "HigpertextHub", args: argparse.Namespace, parser: argparse.ArgumentParser
|
|
1042
|
+
) -> None:
|
|
1043
|
+
"""Despacha el subcomando al handler correspondiente."""
|
|
1044
|
+
handlers = {
|
|
1045
|
+
"task": lambda: _dispatch_task(nexus, args),
|
|
1046
|
+
"health": lambda: _dispatch_health(args),
|
|
1047
|
+
"doctor": lambda: _dispatch_doctor(args),
|
|
1048
|
+
"workflow": lambda: _dispatch_non_task(nexus, args),
|
|
1049
|
+
"ask": lambda: _dispatch_non_task(nexus, args),
|
|
1050
|
+
"profile": lambda: _dispatch_non_task(nexus, args),
|
|
1051
|
+
"init": lambda: _dispatch_non_task(nexus, args),
|
|
1052
|
+
"session": lambda: _dispatch_non_task(nexus, args),
|
|
1053
|
+
"roadmap": lambda: _dispatch_non_task(nexus, args),
|
|
1054
|
+
"agent": lambda: _dispatch_non_task(nexus, args),
|
|
1055
|
+
}
|
|
1056
|
+
handler = handlers.get(args.command)
|
|
1057
|
+
if handler is None:
|
|
1058
|
+
parser.print_help()
|
|
1059
|
+
return
|
|
1060
|
+
handler()
|
|
1061
|
+
|
|
1062
|
+
|
|
1063
|
+
def _dispatch_roadmap(args: argparse.Namespace) -> None:
|
|
1064
|
+
"""Gestiona roadmaps en .higpertext/config/roadmaps/."""
|
|
1065
|
+
dispatch_roadmap(args, _log)
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def _dispatch_profile_learn(args: argparse.Namespace) -> None:
|
|
1069
|
+
"""Extrae patrones, calibra pesos y actualiza learned_profile.json."""
|
|
1070
|
+
dispatch_profile_learn(args, ROOT_DIR)
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
def _dispatch_profile_validate(nexus: "HigpertextHub", args: argparse.Namespace) -> None:
|
|
1074
|
+
dispatch_profile_validate(nexus, args, _log)
|
|
1075
|
+
|
|
1076
|
+
|
|
1077
|
+
def _dispatch_agent_register(args: argparse.Namespace) -> None:
|
|
1078
|
+
dispatch_agent_register(args, _log, ROOT_DIR)
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def _dispatch_agent_sync(args: argparse.Namespace) -> None:
|
|
1082
|
+
dispatch_agent_sync(args, _log, ROOT_DIR)
|
|
1083
|
+
|
|
1084
|
+
|
|
1085
|
+
def _dispatch_agent_list() -> None:
|
|
1086
|
+
dispatch_agent_list(_log, ROOT_DIR)
|
|
1087
|
+
|
|
1088
|
+
|
|
1089
|
+
def _dispatch_agent(nexus: "HigpertextHub", args: argparse.Namespace) -> None:
|
|
1090
|
+
dispatch_agent(nexus, args, _log, ROOT_DIR)
|
|
1091
|
+
|
|
1092
|
+
|
|
1093
|
+
def _dispatch_non_task(nexus: "HigpertextHub", args: argparse.Namespace) -> None:
|
|
1094
|
+
handlers = {
|
|
1095
|
+
"workflow": lambda: _dispatch_workflow(nexus, args),
|
|
1096
|
+
"ask": lambda: nexus.ask_knowledge(args.query),
|
|
1097
|
+
"profile": lambda: dispatch_profile(nexus, args, _log, ROOT_DIR),
|
|
1098
|
+
"init": lambda: nexus.init_project(args.profile, args.assistant, args.target),
|
|
1099
|
+
"agent": lambda: _dispatch_agent(nexus, args),
|
|
1100
|
+
"roadmap": lambda: _dispatch_roadmap(args),
|
|
1101
|
+
"session": lambda: _dispatch_session(nexus, args),
|
|
1102
|
+
}
|
|
1103
|
+
handlers[args.command]()
|
|
1104
|
+
|
|
1105
|
+
|
|
1106
|
+
def main() -> None:
|
|
1107
|
+
"""Punto de entrada del CLI de higpertext Engine."""
|
|
1108
|
+
parser = _build_parser()
|
|
1109
|
+
args = parser.parse_args()
|
|
1110
|
+
_dispatch_command(HigpertextHub(), args, parser)
|