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,111 @@
|
|
|
1
|
+
"""common.search-router — recomienda la capacidad óptima para buscar/leer contexto."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import json
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
10
|
+
_log = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def route(query: str, intent: str, scope: str, preset: str, budget: int) -> list[dict]:
|
|
14
|
+
"""Construye plan de búsqueda/lectura según intención."""
|
|
15
|
+
steps: list[dict] = []
|
|
16
|
+
if intent == "error" or any(
|
|
17
|
+
token in query.lower() for token in ("traceback", "error", "exception", "failed")
|
|
18
|
+
):
|
|
19
|
+
steps.append(
|
|
20
|
+
{
|
|
21
|
+
"capability": "common.error-context-locator",
|
|
22
|
+
"params": {"error": query, "max_context": 5},
|
|
23
|
+
}
|
|
24
|
+
)
|
|
25
|
+
if _looks_like_path(query):
|
|
26
|
+
steps.append(
|
|
27
|
+
{
|
|
28
|
+
"capability": "common.smart-read",
|
|
29
|
+
"params": {"path": query, "mode": "auto", "max_tokens": budget},
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
if intent in {"feature", "refactor", "symbol"}:
|
|
33
|
+
steps.append(
|
|
34
|
+
{
|
|
35
|
+
"capability": "common.graph-query",
|
|
36
|
+
"params": {"symbol": query, "depth": 2, "budget": budget},
|
|
37
|
+
}
|
|
38
|
+
)
|
|
39
|
+
# RAG Semántico como primer fallback general
|
|
40
|
+
steps.append(
|
|
41
|
+
{
|
|
42
|
+
"capability": "common.semantic-search",
|
|
43
|
+
"params": {"query": query, "limit": 5, "root": scope or "."},
|
|
44
|
+
}
|
|
45
|
+
)
|
|
46
|
+
steps.append(
|
|
47
|
+
{
|
|
48
|
+
"capability": "common.grep-search",
|
|
49
|
+
"params": {
|
|
50
|
+
"pattern": query,
|
|
51
|
+
"path": scope or ".",
|
|
52
|
+
"preset": preset,
|
|
53
|
+
"max_results": 50,
|
|
54
|
+
"max_per_file": 5,
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
return _dedupe(steps)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _dedupe(steps: list[dict]) -> list[dict]:
|
|
62
|
+
seen = set()
|
|
63
|
+
result = []
|
|
64
|
+
for step in steps:
|
|
65
|
+
key = (step["capability"], json.dumps(step["params"], sort_keys=True))
|
|
66
|
+
if key not in seen:
|
|
67
|
+
seen.add(key)
|
|
68
|
+
result.append(step)
|
|
69
|
+
return result
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _looks_like_path(query: str) -> bool:
|
|
73
|
+
value = query.strip().strip("\"'")
|
|
74
|
+
if any(space in value for space in (" ", "\n", "\t")):
|
|
75
|
+
return False
|
|
76
|
+
return "/" in value or Path(value).suffix != ""
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def _format_plan(steps: list[dict]) -> str:
|
|
80
|
+
lines = ["╔─ HIGPERTEXT · Search Router ───────────────────────────────"]
|
|
81
|
+
for index, step in enumerate(steps, 1):
|
|
82
|
+
params = " ".join(
|
|
83
|
+
f"--{k} {json.dumps(v, ensure_ascii=False)}" for k, v in step["params"].items()
|
|
84
|
+
)
|
|
85
|
+
lines.append(f"│ {index}. htx task {step['capability']} {params}")
|
|
86
|
+
lines.append("╚────────────────────────────────────────────────────────────")
|
|
87
|
+
return "\n".join(lines)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def main() -> None:
|
|
91
|
+
parser = argparse.ArgumentParser(description="Orquesta búsqueda inteligente")
|
|
92
|
+
parser.add_argument("--query", required=True)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--intent",
|
|
95
|
+
default="general",
|
|
96
|
+
choices=["error", "feature", "refactor", "docs", "symbol", "general"],
|
|
97
|
+
)
|
|
98
|
+
parser.add_argument("--scope", default=".")
|
|
99
|
+
parser.add_argument("--preset", default="code")
|
|
100
|
+
parser.add_argument("--budget", type=int, default=4000)
|
|
101
|
+
parser.add_argument("--json", default="false")
|
|
102
|
+
args = parser.parse_args()
|
|
103
|
+
steps = route(args.query, args.intent, args.scope, args.preset, args.budget)
|
|
104
|
+
if args.json.lower() in {"true", "1", "yes"}:
|
|
105
|
+
print(json.dumps({"steps": steps}, ensure_ascii=False, indent=2))
|
|
106
|
+
return
|
|
107
|
+
_log.info(_format_plan(steps))
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
if __name__ == "__main__":
|
|
111
|
+
main()
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""higpertext Semantic Diff — detecta funciones/clases cambiadas entre commits via AST."""
|
|
2
|
+
|
|
3
|
+
import ast
|
|
4
|
+
import sys
|
|
5
|
+
import json
|
|
6
|
+
import argparse
|
|
7
|
+
import subprocess # nosec B404
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
10
|
+
_log = get_logger()
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def run_cmd(cmd: list[str]) -> tuple[int, str, str]:
|
|
14
|
+
res = subprocess.run(cmd, capture_output=True, text=True) # nosec B603
|
|
15
|
+
return res.returncode, res.stdout, res.stderr
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _extract_symbols(source: str) -> dict[str, set[str]]:
|
|
19
|
+
"""Extrae nombres de funciones y clases de código fuente Python via AST."""
|
|
20
|
+
symbols: dict[str, set[str]] = {
|
|
21
|
+
"functions": set(),
|
|
22
|
+
"classes": set(),
|
|
23
|
+
"methods": set(),
|
|
24
|
+
}
|
|
25
|
+
try:
|
|
26
|
+
tree = ast.parse(source)
|
|
27
|
+
except SyntaxError:
|
|
28
|
+
return symbols
|
|
29
|
+
for node in ast.walk(tree):
|
|
30
|
+
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
31
|
+
symbols["functions"].add(node.name)
|
|
32
|
+
elif isinstance(node, ast.ClassDef):
|
|
33
|
+
symbols["classes"].add(node.name)
|
|
34
|
+
for item in node.body:
|
|
35
|
+
if isinstance(item, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
36
|
+
symbols["methods"].add(f"{node.name}.{item.name}")
|
|
37
|
+
return symbols
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _get_file_content_at(ref: str, filepath: str) -> str | None:
|
|
41
|
+
"""Obtiene el contenido de un archivo en un commit dado."""
|
|
42
|
+
ret, out, _ = run_cmd(["git", "show", f"{ref}:{filepath}"])
|
|
43
|
+
return out if ret == 0 else None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _get_changed_files(base: str, head: str, files_filter: list[str] | None) -> list[str]:
|
|
47
|
+
"""Lista archivos .py que cambiaron entre base y head."""
|
|
48
|
+
ret, out, _ = run_cmd(["git", "diff", "--name-only", base, head])
|
|
49
|
+
if ret != 0:
|
|
50
|
+
return []
|
|
51
|
+
changed = [f for f in out.splitlines() if f.endswith(".py")]
|
|
52
|
+
if files_filter:
|
|
53
|
+
changed = [f for f in changed if any(f.endswith(ff) or ff in f for ff in files_filter)]
|
|
54
|
+
return changed
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _diff_symbols(base: str, head: str, filepath: str) -> dict:
|
|
58
|
+
"""Compara símbolos AST entre dos versiones de un archivo."""
|
|
59
|
+
content_base = _get_file_content_at(base, filepath)
|
|
60
|
+
content_head = _get_file_content_at(head, filepath)
|
|
61
|
+
|
|
62
|
+
if content_base is None and content_head is None:
|
|
63
|
+
return {}
|
|
64
|
+
|
|
65
|
+
sym_base = _extract_symbols(content_base or "")
|
|
66
|
+
sym_head = _extract_symbols(content_head or "")
|
|
67
|
+
|
|
68
|
+
result: dict[str, list] = {}
|
|
69
|
+
for kind in ("functions", "classes", "methods"):
|
|
70
|
+
b = sym_base[kind]
|
|
71
|
+
h = sym_head[kind]
|
|
72
|
+
added = sorted(h - b)
|
|
73
|
+
removed = sorted(b - h)
|
|
74
|
+
# Modified = same name but content changed (line-level heuristic per symbol)
|
|
75
|
+
common = b & h
|
|
76
|
+
modified = _detect_modified(content_base or "", content_head or "", common, kind)
|
|
77
|
+
|
|
78
|
+
if added or removed or modified:
|
|
79
|
+
result[kind] = {
|
|
80
|
+
"added": added,
|
|
81
|
+
"removed": removed,
|
|
82
|
+
"modified": sorted(modified),
|
|
83
|
+
}
|
|
84
|
+
return result
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _detect_modified(src_base: str, src_head: str, names: set[str], kind: str) -> set[str]:
|
|
88
|
+
"""Heurística: extrae el bloque de cada símbolo y compara su hash."""
|
|
89
|
+
modified = set()
|
|
90
|
+
for name in names:
|
|
91
|
+
block_b = _extract_block(src_base, name, kind)
|
|
92
|
+
block_h = _extract_block(src_head, name, kind)
|
|
93
|
+
if block_b != block_h:
|
|
94
|
+
modified.add(name)
|
|
95
|
+
return modified
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def _extract_block(source: str, name: str, kind: str) -> str:
|
|
99
|
+
"""Extrae el cuerpo de una función/clase por nombre."""
|
|
100
|
+
try:
|
|
101
|
+
tree = ast.parse(source)
|
|
102
|
+
except SyntaxError:
|
|
103
|
+
return ""
|
|
104
|
+
lines = source.splitlines()
|
|
105
|
+
for node in ast.walk(tree):
|
|
106
|
+
node_name = getattr(node, "name", None)
|
|
107
|
+
if node_name != name.split(".")[-1]:
|
|
108
|
+
continue
|
|
109
|
+
if kind == "functions" and not isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef):
|
|
110
|
+
continue
|
|
111
|
+
if kind == "classes" and not isinstance(node, ast.ClassDef):
|
|
112
|
+
continue
|
|
113
|
+
start = node.lineno - 1
|
|
114
|
+
end = node.end_lineno if hasattr(node, "end_lineno") else start + 1
|
|
115
|
+
return "\n".join(lines[start:end])
|
|
116
|
+
return ""
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _print_text(results: dict[str, dict]) -> None:
|
|
120
|
+
if not results:
|
|
121
|
+
_log.info("[*] No se detectaron cambios semánticos en símbolos Python.")
|
|
122
|
+
return
|
|
123
|
+
_log.info("=" * 60)
|
|
124
|
+
_log.info("DIFF SEMÁNTICO — Símbolos modificados")
|
|
125
|
+
_log.info("=" * 60)
|
|
126
|
+
for filepath, kinds in results.items():
|
|
127
|
+
_log.info(f"\n📄 {filepath}")
|
|
128
|
+
for kind, changes in kinds.items():
|
|
129
|
+
for action, names in changes.items():
|
|
130
|
+
if names:
|
|
131
|
+
icon = {"added": "✚", "removed": "✖", "modified": "~"}.get(action, "?")
|
|
132
|
+
label = f" [{icon} {action.upper()} {kind}]"
|
|
133
|
+
for n in names:
|
|
134
|
+
_log.info(f"{label}: {n}")
|
|
135
|
+
_log.info("\n" + "=" * 60)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def main() -> None:
|
|
139
|
+
parser = argparse.ArgumentParser(description="higpertext Semantic Diff")
|
|
140
|
+
parser.add_argument("--base", default="HEAD~1")
|
|
141
|
+
parser.add_argument("--head", default="HEAD")
|
|
142
|
+
parser.add_argument("--files", default=None)
|
|
143
|
+
parser.add_argument("--format", default="text", choices=["text", "json"])
|
|
144
|
+
args = parser.parse_args()
|
|
145
|
+
|
|
146
|
+
files_filter = [f.strip() for f in args.files.split(",") if f.strip()] if args.files else None
|
|
147
|
+
changed_files = _get_changed_files(args.base, args.head, files_filter)
|
|
148
|
+
|
|
149
|
+
if not changed_files:
|
|
150
|
+
_log.info("[*] No hay archivos Python modificados entre los commits especificados.")
|
|
151
|
+
sys.exit(0)
|
|
152
|
+
|
|
153
|
+
results: dict[str, dict] = {}
|
|
154
|
+
for fp in changed_files:
|
|
155
|
+
diff = _diff_symbols(args.base, args.head, fp)
|
|
156
|
+
if diff:
|
|
157
|
+
results[fp] = diff
|
|
158
|
+
|
|
159
|
+
if args.format == "json":
|
|
160
|
+
print(json.dumps(results, indent=2))
|
|
161
|
+
else:
|
|
162
|
+
_print_text(results)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Capacidad para buscar fragmentos de código semánticamente mediante RAG."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
# Configurar path para importar higpertext
|
|
10
|
+
_ROOT = Path(__file__).resolve().parents[5]
|
|
11
|
+
sys.path.insert(0, str(_ROOT / "src"))
|
|
12
|
+
|
|
13
|
+
from higpertext.kernel.infrastructure.llm.providers.gemini_embeddings import GeminiEmbeddingProvider
|
|
14
|
+
from higpertext.kernel.infrastructure.database.local_vector_store import LocalVectorStore
|
|
15
|
+
from higpertext.kernel.application.rag_service import RAGService
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def main() -> None:
|
|
19
|
+
parser = argparse.ArgumentParser(description="Busca código semánticamente mediante RAG.")
|
|
20
|
+
parser.add_argument("--query", type=str, required=True, help="Texto o concepto a buscar.")
|
|
21
|
+
parser.add_argument("--limit", type=int, default=5, help="Límite de resultados.")
|
|
22
|
+
parser.add_argument("--root", type=str, default=".", help="Ruta al proyecto.")
|
|
23
|
+
args = parser.parse_args()
|
|
24
|
+
|
|
25
|
+
project_root = Path(args.root).resolve()
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
embedder = GeminiEmbeddingProvider(project_root)
|
|
29
|
+
store = LocalVectorStore(project_root)
|
|
30
|
+
service = RAGService(embedder, store)
|
|
31
|
+
|
|
32
|
+
hits = service.search_context(args.query, limit=args.limit)
|
|
33
|
+
|
|
34
|
+
# Output en JSON estructurado para el Framework
|
|
35
|
+
print(json.dumps({"hits": hits}, indent=2, ensure_ascii=False))
|
|
36
|
+
sys.exit(0)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
print(json.dumps({"error": str(e)}), file=sys.stderr)
|
|
39
|
+
sys.exit(1)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
if __name__ == "__main__":
|
|
43
|
+
main()
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"""higpertext Session Control — inicia y limpia sesiones de desarrollo del agente."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import json
|
|
5
|
+
import argparse
|
|
6
|
+
from higpertext.kernel.session_manager import SessionManager # noqa: E402
|
|
7
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
10
|
+
_log = get_logger()
|
|
11
|
+
|
|
12
|
+
ROOT_DIR = Path(__file__).parents[4]
|
|
13
|
+
if str(ROOT_DIR / "src" / "core") not in sys.path:
|
|
14
|
+
sys.path.append(str(ROOT_DIR / "src" / "core"))
|
|
15
|
+
|
|
16
|
+
def _load_roadmap_resources(project_root: Path) -> tuple:
|
|
17
|
+
"""Lee session_resources mergeando roadmaps activos en roadmaps/active.json.
|
|
18
|
+
|
|
19
|
+
Fallback a roadmap.json único para compatibilidad hacia atrás.
|
|
20
|
+
"""
|
|
21
|
+
roadmaps_dir = project_root / WORKSPACE_DIR_NAME / "config" / "roadmaps"
|
|
22
|
+
active_index = roadmaps_dir / "active.json"
|
|
23
|
+
|
|
24
|
+
if active_index.exists():
|
|
25
|
+
try:
|
|
26
|
+
active_ids: list = json.loads(active_index.read_text(encoding="utf-8")).get(
|
|
27
|
+
"active", []
|
|
28
|
+
)
|
|
29
|
+
except (OSError, json.JSONDecodeError):
|
|
30
|
+
return None, None
|
|
31
|
+
|
|
32
|
+
if not active_ids:
|
|
33
|
+
return None, None
|
|
34
|
+
|
|
35
|
+
merged_skills: list = []
|
|
36
|
+
merged_subagents: list = []
|
|
37
|
+
loaded: list = []
|
|
38
|
+
|
|
39
|
+
for rid in active_ids:
|
|
40
|
+
rf = roadmaps_dir / f"{rid}.json"
|
|
41
|
+
if not rf.exists():
|
|
42
|
+
continue
|
|
43
|
+
try:
|
|
44
|
+
data = json.loads(rf.read_text(encoding="utf-8"))
|
|
45
|
+
res = data.get("session_resources", {})
|
|
46
|
+
for s in res.get("skills", []):
|
|
47
|
+
if s not in merged_skills:
|
|
48
|
+
merged_skills.append(s)
|
|
49
|
+
for sa in res.get("subagents", []):
|
|
50
|
+
if sa not in merged_subagents:
|
|
51
|
+
merged_subagents.append(sa)
|
|
52
|
+
loaded.append(data.get("project", rid))
|
|
53
|
+
except (OSError, json.JSONDecodeError):
|
|
54
|
+
continue
|
|
55
|
+
|
|
56
|
+
if not loaded:
|
|
57
|
+
return None, None
|
|
58
|
+
|
|
59
|
+
projects_str = ", ".join(f"'{p}'" for p in loaded)
|
|
60
|
+
_log.info(
|
|
61
|
+
f"[*] Roadmaps activos: {projects_str}"
|
|
62
|
+
f" — montando {len(merged_skills)} skill(s) y {len(merged_subagents)} subagente(s)."
|
|
63
|
+
)
|
|
64
|
+
return merged_skills or None, merged_subagents or None
|
|
65
|
+
|
|
66
|
+
# Fallback: roadmap.json único
|
|
67
|
+
roadmap_file = project_root / WORKSPACE_DIR_NAME / "config" / "roadmap.json"
|
|
68
|
+
if not roadmap_file.exists():
|
|
69
|
+
return None, None
|
|
70
|
+
try:
|
|
71
|
+
data = json.loads(roadmap_file.read_text(encoding="utf-8"))
|
|
72
|
+
resources = data.get("session_resources", {})
|
|
73
|
+
skills = resources.get("skills", [])
|
|
74
|
+
subagents = resources.get("subagents", [])
|
|
75
|
+
if skills or subagents:
|
|
76
|
+
_log.info(
|
|
77
|
+
f"[*] Roadmap detectado: '{data.get('project', '')}' "
|
|
78
|
+
f"— montando {len(skills)} skill(s) y {len(subagents)} subagente(s)."
|
|
79
|
+
)
|
|
80
|
+
return skills, subagents
|
|
81
|
+
return None, None
|
|
82
|
+
except (OSError, json.JSONDecodeError):
|
|
83
|
+
return None, None
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _load_profile_session_resources(profile_name: str, root_dir: Path) -> tuple:
|
|
87
|
+
"""Fallback: lee session_skills y session_subagents del perfil JSON."""
|
|
88
|
+
profile_file = root_dir / "src" / "config" / "profiles" / f"{profile_name}.json"
|
|
89
|
+
if not profile_file.exists():
|
|
90
|
+
return [], []
|
|
91
|
+
try:
|
|
92
|
+
data = json.loads(profile_file.read_text(encoding="utf-8"))
|
|
93
|
+
return data.get("session_skills", []), data.get("session_subagents", [])
|
|
94
|
+
except (OSError, json.JSONDecodeError):
|
|
95
|
+
return [], []
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def main() -> None:
|
|
99
|
+
"""Punto de entrada de session-control."""
|
|
100
|
+
parser = argparse.ArgumentParser(description="higpertext Session Control Capability")
|
|
101
|
+
parser.add_argument("--action", required=True, choices=["start", "clean"])
|
|
102
|
+
parser.add_argument("--profile", help="Active profile name")
|
|
103
|
+
parser.add_argument("--assistant", help="Assistant name (claude, gemini, antigravity, copilot)")
|
|
104
|
+
parser.add_argument("--skills", help="Comma-separated skills (overrides roadmap and profile)")
|
|
105
|
+
parser.add_argument(
|
|
106
|
+
"--subagents", help="Comma-separated subagents (overrides roadmap and profile)"
|
|
107
|
+
)
|
|
108
|
+
args = parser.parse_args()
|
|
109
|
+
|
|
110
|
+
project_root = Path.cwd()
|
|
111
|
+
session_mgr = SessionManager(project_root, ROOT_DIR)
|
|
112
|
+
|
|
113
|
+
if args.action == "start":
|
|
114
|
+
env_assistant, active_profile = session_mgr.get_assistant_and_profile()
|
|
115
|
+
assistant = args.assistant or env_assistant
|
|
116
|
+
profile = args.profile or active_profile
|
|
117
|
+
|
|
118
|
+
# Prioridad: args explícitos, de lo contrario SessionManager resolverá
|
|
119
|
+
# roadmap/perfil/fallback
|
|
120
|
+
skills_list = (
|
|
121
|
+
[s.strip() for s in args.skills.split(",") if s.strip()] if args.skills else None
|
|
122
|
+
)
|
|
123
|
+
subagents_list = (
|
|
124
|
+
[sa.strip() for sa in args.subagents.split(",") if sa.strip()]
|
|
125
|
+
if args.subagents
|
|
126
|
+
else None
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
session_mgr.start_session(profile, assistant, skills_list, subagents_list)
|
|
130
|
+
|
|
131
|
+
elif args.action == "clean":
|
|
132
|
+
session_mgr.clean_session()
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
if __name__ == "__main__":
|
|
136
|
+
main()
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
"""common.smart-read — lectura segura y compacta para LLM."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import ast
|
|
7
|
+
import json
|
|
8
|
+
import sys
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
try:
|
|
13
|
+
from higpertext.capabilities.common.scripts.code_skeletonizer import (
|
|
14
|
+
skeletonize_file,
|
|
15
|
+
)
|
|
16
|
+
except ImportError: # pragma: no cover - fallback para ejecución directa
|
|
17
|
+
from code_skeletonizer import skeletonize_file # type: ignore
|
|
18
|
+
|
|
19
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
20
|
+
_log = get_logger()
|
|
21
|
+
|
|
22
|
+
_CODE_SUFFIXES = {".py", ".js", ".jsx", ".ts", ".tsx", ".go", ".java", ".cs", ".rs"}
|
|
23
|
+
_DEFAULT_MAX_BYTES = 100 * 1024
|
|
24
|
+
_DEFAULT_LIMIT = 120
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@dataclass(frozen=True)
|
|
28
|
+
class SymbolSpan:
|
|
29
|
+
"""Rango aproximado de un símbolo dentro de un archivo."""
|
|
30
|
+
|
|
31
|
+
name: str
|
|
32
|
+
kind: str
|
|
33
|
+
start: int
|
|
34
|
+
end: int
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _read_lines(path: Path) -> list[str]:
|
|
38
|
+
return path.read_text(encoding="utf-8", errors="ignore").splitlines()
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _estimate_tokens(text: str) -> int:
|
|
42
|
+
return max(1, len(text) // 4)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _is_large(path: Path, max_bytes: int) -> bool:
|
|
46
|
+
return max_bytes > 0 and path.stat().st_size > max_bytes
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _line_map(path: Path) -> list[SymbolSpan]:
|
|
50
|
+
if path.suffix != ".py":
|
|
51
|
+
return _regex_line_map(_read_lines(path))
|
|
52
|
+
try:
|
|
53
|
+
tree = ast.parse(path.read_text(encoding="utf-8", errors="ignore"))
|
|
54
|
+
except SyntaxError:
|
|
55
|
+
return _regex_line_map(_read_lines(path))
|
|
56
|
+
spans: list[SymbolSpan] = []
|
|
57
|
+
for node in ast.walk(tree):
|
|
58
|
+
if isinstance(node, ast.ClassDef):
|
|
59
|
+
spans.append(
|
|
60
|
+
SymbolSpan(
|
|
61
|
+
node.name,
|
|
62
|
+
"class",
|
|
63
|
+
node.lineno,
|
|
64
|
+
getattr(node, "end_lineno", node.lineno),
|
|
65
|
+
)
|
|
66
|
+
)
|
|
67
|
+
elif isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
68
|
+
kind = "async def" if isinstance(node, ast.AsyncFunctionDef) else "def"
|
|
69
|
+
spans.append(
|
|
70
|
+
SymbolSpan(
|
|
71
|
+
node.name,
|
|
72
|
+
kind,
|
|
73
|
+
node.lineno,
|
|
74
|
+
getattr(node, "end_lineno", node.lineno),
|
|
75
|
+
)
|
|
76
|
+
)
|
|
77
|
+
return sorted(spans, key=lambda item: item.start)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _regex_line_map(lines: list[str]) -> list[SymbolSpan]:
|
|
81
|
+
spans: list[SymbolSpan] = []
|
|
82
|
+
for index, line in enumerate(lines, 1):
|
|
83
|
+
stripped = line.strip()
|
|
84
|
+
if stripped.startswith("class "):
|
|
85
|
+
spans.append(
|
|
86
|
+
SymbolSpan(stripped.split()[1].split("(")[0].rstrip(":"), "class", index, index)
|
|
87
|
+
)
|
|
88
|
+
elif stripped.startswith(("def ", "async def ", "function ")):
|
|
89
|
+
name = stripped.replace("async def ", "def ", 1).split()[1].split("(")[0]
|
|
90
|
+
spans.append(SymbolSpan(name, "def", index, index))
|
|
91
|
+
return spans
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def _format_range(lines: list[str], start: int, end: int, line_limit: int = 240) -> str:
|
|
95
|
+
start = max(1, start)
|
|
96
|
+
end = min(len(lines), end)
|
|
97
|
+
rendered = []
|
|
98
|
+
for lineno in range(start, end + 1):
|
|
99
|
+
text = lines[lineno - 1]
|
|
100
|
+
if line_limit and len(text) > line_limit:
|
|
101
|
+
text = text[: line_limit - 3] + "..."
|
|
102
|
+
rendered.append(f"{lineno:>5}: {text}")
|
|
103
|
+
return "\n".join(rendered)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _symbol_range(path: Path, symbol: str, around: int) -> tuple[int, int] | None:
|
|
107
|
+
for span in _line_map(path):
|
|
108
|
+
if span.name == symbol or symbol.lower() in span.name.lower():
|
|
109
|
+
return max(1, span.start - around), span.end + around
|
|
110
|
+
return None
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def _summary(path: Path, max_bytes: int) -> str:
|
|
114
|
+
spans = _line_map(path)
|
|
115
|
+
lines = [
|
|
116
|
+
"╔─ HIGPERTEXT · Smart Read Summary ─────────────────────────",
|
|
117
|
+
f"│ Archivo : {path}",
|
|
118
|
+
f"│ Tamaño : {path.stat().st_size} bytes",
|
|
119
|
+
f"│ Grande : {'sí' if _is_large(path, max_bytes) else 'no'}",
|
|
120
|
+
f"│ Símbolos: {len(spans)}",
|
|
121
|
+
"╠────────────────────────────────────────────────────────────",
|
|
122
|
+
]
|
|
123
|
+
for span in spans[:80]:
|
|
124
|
+
lines.append(f"│ L{span.start}-{span.end}: {span.kind} {span.name}")
|
|
125
|
+
if len(spans) > 80:
|
|
126
|
+
lines.append(f"│ ... {len(spans) - 80} símbolo(s) omitidos")
|
|
127
|
+
lines.append("╚────────────────────────────────────────────────────────────")
|
|
128
|
+
return "\n".join(lines)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def smart_read(
|
|
132
|
+
path: Path,
|
|
133
|
+
mode: str,
|
|
134
|
+
symbol: str = "",
|
|
135
|
+
offset: int = 1,
|
|
136
|
+
limit: int = _DEFAULT_LIMIT,
|
|
137
|
+
around_line: int = 0,
|
|
138
|
+
max_bytes: int = _DEFAULT_MAX_BYTES,
|
|
139
|
+
) -> str:
|
|
140
|
+
"""Devuelve contenido curado según modo solicitado."""
|
|
141
|
+
if not path.exists() or not path.is_file():
|
|
142
|
+
raise FileNotFoundError(str(path))
|
|
143
|
+
resolved_mode = mode
|
|
144
|
+
if mode == "auto":
|
|
145
|
+
resolved_mode = (
|
|
146
|
+
"skeleton" if path.suffix in _CODE_SUFFIXES and _is_large(path, max_bytes) else "range"
|
|
147
|
+
)
|
|
148
|
+
if resolved_mode == "summary":
|
|
149
|
+
return _summary(path, max_bytes)
|
|
150
|
+
if resolved_mode == "skeleton":
|
|
151
|
+
return skeletonize_file(path)
|
|
152
|
+
|
|
153
|
+
lines = _read_lines(path)
|
|
154
|
+
if resolved_mode == "symbol":
|
|
155
|
+
if not symbol:
|
|
156
|
+
raise ValueError("--symbol es requerido para mode=symbol")
|
|
157
|
+
span = _symbol_range(path, symbol, around_line or 3)
|
|
158
|
+
if not span:
|
|
159
|
+
return f"[NOT FOUND] Símbolo no encontrado: {symbol}\n\n{_summary(path, max_bytes)}"
|
|
160
|
+
return _format_range(lines, span[0], span[1])
|
|
161
|
+
if resolved_mode == "full":
|
|
162
|
+
if _is_large(path, max_bytes):
|
|
163
|
+
return "\n".join(
|
|
164
|
+
[
|
|
165
|
+
"[BLOCKED] Archivo grande; usa --mode skeleton, summary, symbol o range.",
|
|
166
|
+
f"Archivo: {path}",
|
|
167
|
+
f"Tamaño: {path.stat().st_size} bytes > {max_bytes} bytes",
|
|
168
|
+
]
|
|
169
|
+
)
|
|
170
|
+
return _format_range(lines, 1, len(lines))
|
|
171
|
+
if around_line > 0:
|
|
172
|
+
return _format_range(lines, around_line - limit, around_line + limit)
|
|
173
|
+
return _format_range(lines, offset, offset + limit - 1)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def _json_payload(path: Path, content: str, mode: str) -> dict:
|
|
177
|
+
return {
|
|
178
|
+
"path": str(path),
|
|
179
|
+
"mode": mode,
|
|
180
|
+
"bytes": path.stat().st_size,
|
|
181
|
+
"estimated_tokens": _estimate_tokens(content),
|
|
182
|
+
"content": content,
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def main() -> None:
|
|
187
|
+
parser = argparse.ArgumentParser(description="Lectura segura y optimizada para LLM")
|
|
188
|
+
parser.add_argument("--path", required=True)
|
|
189
|
+
parser.add_argument(
|
|
190
|
+
"--mode",
|
|
191
|
+
default="auto",
|
|
192
|
+
choices=["auto", "skeleton", "range", "symbol", "full", "summary"],
|
|
193
|
+
)
|
|
194
|
+
parser.add_argument("--symbol", default="")
|
|
195
|
+
parser.add_argument("--offset", type=int, default=1)
|
|
196
|
+
parser.add_argument("--limit", type=int, default=_DEFAULT_LIMIT)
|
|
197
|
+
parser.add_argument("--around_line", type=int, default=0)
|
|
198
|
+
parser.add_argument("--max_bytes", type=int, default=_DEFAULT_MAX_BYTES)
|
|
199
|
+
parser.add_argument("--max_tokens", type=int, default=0)
|
|
200
|
+
parser.add_argument("--json", default="false")
|
|
201
|
+
args = parser.parse_args()
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
path = Path(args.path)
|
|
205
|
+
content = smart_read(
|
|
206
|
+
path,
|
|
207
|
+
args.mode,
|
|
208
|
+
args.symbol,
|
|
209
|
+
args.offset,
|
|
210
|
+
args.limit,
|
|
211
|
+
args.around_line,
|
|
212
|
+
args.max_bytes,
|
|
213
|
+
)
|
|
214
|
+
if args.max_tokens > 0 and _estimate_tokens(content) > args.max_tokens:
|
|
215
|
+
content = smart_read(path, "summary", max_bytes=args.max_bytes)
|
|
216
|
+
if args.json.lower() in {"true", "1", "yes"}:
|
|
217
|
+
_log.info(
|
|
218
|
+
json.dumps(
|
|
219
|
+
_json_payload(path, content, args.mode),
|
|
220
|
+
ensure_ascii=False,
|
|
221
|
+
indent=2,
|
|
222
|
+
)
|
|
223
|
+
)
|
|
224
|
+
else:
|
|
225
|
+
_log.info(content)
|
|
226
|
+
except Exception as exc:
|
|
227
|
+
_log.error(f"[ERROR] {exc}")
|
|
228
|
+
sys.exit(1)
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
if __name__ == "__main__":
|
|
232
|
+
main()
|