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,303 @@
|
|
|
1
|
+
"""Higpertext CLI Logger — infraestructura de logging para la CLI.
|
|
2
|
+
|
|
3
|
+
Provee un logger estructurado con:
|
|
4
|
+
- Salida a consola con colores ANSI (nivel configurable).
|
|
5
|
+
- Rotación de archivo en .higpertext/logs/htx.log (sin dependencias externas).
|
|
6
|
+
- Niveles semánticos: info, ok, warn, error, debug alineados con el estilo de la CLI.
|
|
7
|
+
- `event_block()` — emite un bloque estructurado AI-legible en un solo call.
|
|
8
|
+
- Factory `get_logger` para obtener la instancia singleton configurada.
|
|
9
|
+
|
|
10
|
+
Formato de bloque (AI-legible):
|
|
11
|
+
╔─ htx · <event> ─────────────────────────────────────────
|
|
12
|
+
│ status : ok
|
|
13
|
+
│ sessions_analyzed : 12
|
|
14
|
+
│ ...
|
|
15
|
+
╚─────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
Uso:
|
|
18
|
+
from higpertext.kernel.infrastructure.logger import get_logger
|
|
19
|
+
log = get_logger(project_root=root)
|
|
20
|
+
log.event_block("profile-learn", status="ok", fields={
|
|
21
|
+
"sessions_analyzed": 12,
|
|
22
|
+
"calibration_mode": "z-score",
|
|
23
|
+
})
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import logging
|
|
29
|
+
import logging.handlers
|
|
30
|
+
import os
|
|
31
|
+
import sys
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
from typing import Any
|
|
34
|
+
|
|
35
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
36
|
+
|
|
37
|
+
# ── Constantes ────────────────────────────────────────────────────────────────
|
|
38
|
+
|
|
39
|
+
from higpertext.kernel.app_config import LOGGER_NAME, LOG_FILE as _LOG_FILE
|
|
40
|
+
_MAX_BYTES = 1_048_576 # 1 MB
|
|
41
|
+
_BACKUP_COUNT = 3
|
|
42
|
+
_DEFAULT_LEVEL = logging.INFO
|
|
43
|
+
|
|
44
|
+
# Nivel personalizado OK (entre INFO y WARNING)
|
|
45
|
+
_OK_LEVEL = 25
|
|
46
|
+
logging.addLevelName(_OK_LEVEL, "OK")
|
|
47
|
+
|
|
48
|
+
_BOX_WIDTH = 57 # ancho interior del bloque ╔─…╚─
|
|
49
|
+
|
|
50
|
+
# ── Colores ANSI ──────────────────────────────────────────────────────────────
|
|
51
|
+
|
|
52
|
+
_RESET = "\033[0m"
|
|
53
|
+
_BOLD = "\033[1m"
|
|
54
|
+
|
|
55
|
+
_COLORS: dict[int, str] = {
|
|
56
|
+
logging.DEBUG: "\033[36m", # cyan
|
|
57
|
+
logging.INFO: "\033[34m", # blue
|
|
58
|
+
_OK_LEVEL: "\033[32m", # green
|
|
59
|
+
logging.WARNING: "\033[33m", # yellow
|
|
60
|
+
logging.ERROR: "\033[31m", # red
|
|
61
|
+
logging.CRITICAL: "\033[35m", # magenta
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
_PREFIX: dict[int, str] = {
|
|
65
|
+
logging.DEBUG: "[DBG]",
|
|
66
|
+
logging.INFO: "[*] ",
|
|
67
|
+
_OK_LEVEL: "[OK] ",
|
|
68
|
+
logging.WARNING: "[!] ",
|
|
69
|
+
logging.ERROR: "[ERR]",
|
|
70
|
+
logging.CRITICAL: "[!!!]",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
_STATUS_ICON: dict[str, str] = {
|
|
74
|
+
"ok": "✓",
|
|
75
|
+
"error": "✗",
|
|
76
|
+
"warn": "⚠",
|
|
77
|
+
"skip": "—",
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# ── Formatters ────────────────────────────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
class _ColorFormatter(logging.Formatter):
|
|
84
|
+
"""Formatter con colores ANSI para salida en consola."""
|
|
85
|
+
|
|
86
|
+
_use_color: bool = sys.stdout.isatty() and os.getenv("NO_COLOR") is None
|
|
87
|
+
|
|
88
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
89
|
+
level = record.levelno
|
|
90
|
+
prefix = _PREFIX.get(level, "[?] ")
|
|
91
|
+
msg = super().format(record)
|
|
92
|
+
|
|
93
|
+
if self._use_color:
|
|
94
|
+
color = _COLORS.get(level, "")
|
|
95
|
+
return f"{color}{_BOLD}{prefix}{_RESET} {msg}"
|
|
96
|
+
return f"{prefix} {msg}"
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
class _PlainFormatter(logging.Formatter):
|
|
100
|
+
"""Formatter sin colores para archivos de log."""
|
|
101
|
+
|
|
102
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
103
|
+
prefix = _PREFIX.get(record.levelno, "[?] ")
|
|
104
|
+
return f"{prefix} {super().format(record)}"
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# ── Bloque estructurado ───────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
def _build_event_block(
|
|
110
|
+
event: str,
|
|
111
|
+
status: str,
|
|
112
|
+
fields: dict[str, Any],
|
|
113
|
+
) -> str:
|
|
114
|
+
"""Construye un bloque de texto estructurado AI-legible.
|
|
115
|
+
|
|
116
|
+
El formato es intencionalmente consistente con el banner de session_stop
|
|
117
|
+
para que el asistente pueda parsearlo mediante texto plano.
|
|
118
|
+
|
|
119
|
+
╔─ htx · profile-learn ───────────────────────────────────
|
|
120
|
+
│ status : ✓ ok
|
|
121
|
+
│ sessions_analyzed : 12
|
|
122
|
+
│ calibration_mode : z-score
|
|
123
|
+
│ adoption_pct : 78%
|
|
124
|
+
╚─────────────────────────────────────────────────────────
|
|
125
|
+
"""
|
|
126
|
+
title = f"htx · {event}"
|
|
127
|
+
header_fill = "─" * max(0, _BOX_WIDTH - len(title) - 1)
|
|
128
|
+
header = f"╔─ {title} {header_fill}"
|
|
129
|
+
|
|
130
|
+
icon = _STATUS_ICON.get(status, status)
|
|
131
|
+
rows: list[str] = [f"│ {'status':<20}: {icon} {status}"]
|
|
132
|
+
|
|
133
|
+
# Calcula el ancho de la columna de claves
|
|
134
|
+
max_key = max((len(k) for k in fields), default=6)
|
|
135
|
+
col = max(max_key, 20)
|
|
136
|
+
|
|
137
|
+
for key, value in fields.items():
|
|
138
|
+
val_str = str(value) if value not in (None, "", []) else "—"
|
|
139
|
+
rows.append(f"│ {key:<{col}}: {val_str}")
|
|
140
|
+
|
|
141
|
+
footer = "╚" + "─" * (_BOX_WIDTH + 2)
|
|
142
|
+
|
|
143
|
+
return "\n".join([header, *rows, footer])
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# ── Caja genérica ─────────────────────────────────────────────────────────────
|
|
147
|
+
|
|
148
|
+
def render_box(title: str, lines: list[str]) -> str:
|
|
149
|
+
"""Construye un bloque de caja ╔─ … ╚─ como string.
|
|
150
|
+
|
|
151
|
+
Uso desde hooks/RuleResult (canal LLM) o cualquier código que necesite
|
|
152
|
+
el string antes de decidir cómo emitirlo.
|
|
153
|
+
|
|
154
|
+
render_box("HIGPERTEXT · Loop Guard · BLOQUEADO", [
|
|
155
|
+
f"│ Herramienta : {tool}",
|
|
156
|
+
"│ Acción : cambia el enfoque",
|
|
157
|
+
])
|
|
158
|
+
"""
|
|
159
|
+
header_fill = "─" * max(0, _BOX_WIDTH - len(title) - 1)
|
|
160
|
+
header = f"╔─ {title} {header_fill}"
|
|
161
|
+
footer = "╚" + "─" * (_BOX_WIDTH + 2)
|
|
162
|
+
return "\n".join([header, *lines, footer])
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ── Logger extendido ──────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
class HtxLogger(logging.Logger):
|
|
168
|
+
"""Logger con métodos semánticos y emisión de bloques estructurados."""
|
|
169
|
+
|
|
170
|
+
def ok(self, msg: str, *args: object, **kwargs: object) -> None:
|
|
171
|
+
"""Emite un mensaje de nivel OK (verde)."""
|
|
172
|
+
if self.isEnabledFor(_OK_LEVEL):
|
|
173
|
+
self._log(_OK_LEVEL, msg, args, **kwargs)
|
|
174
|
+
|
|
175
|
+
def section(self, title: str) -> None:
|
|
176
|
+
"""Imprime un separador de sección visible en consola."""
|
|
177
|
+
self.info("─" * 3 + f" {title} " + "─" * max(0, 48 - len(title)))
|
|
178
|
+
|
|
179
|
+
def box(self, title: str, lines: list[str], level: int = logging.INFO) -> None:
|
|
180
|
+
"""Emite un bloque de caja ╔─ … ╚─ via el logger.
|
|
181
|
+
|
|
182
|
+
Equivalente a llamar render_box() y luego _log(level, ...).
|
|
183
|
+
Usar cuando el destinatario es el log interno (consola/archivo),
|
|
184
|
+
no el LLM (para eso usar render_box() + emit_block/RuleResult).
|
|
185
|
+
"""
|
|
186
|
+
if self.isEnabledFor(level):
|
|
187
|
+
self._log(level, render_box(title, lines), ())
|
|
188
|
+
|
|
189
|
+
def event_block(
|
|
190
|
+
self,
|
|
191
|
+
event: str,
|
|
192
|
+
*,
|
|
193
|
+
status: str = "ok",
|
|
194
|
+
fields: dict[str, Any] | None = None,
|
|
195
|
+
) -> None:
|
|
196
|
+
"""Emite un bloque estructurado AI-legible en una sola llamada.
|
|
197
|
+
|
|
198
|
+
Elimina la repetición de múltiples `log.info()` para campos
|
|
199
|
+
relacionados. El bloque se emite completo en un solo mensaje,
|
|
200
|
+
lo que facilita el parseo por parte del asistente.
|
|
201
|
+
|
|
202
|
+
Args:
|
|
203
|
+
event: Identificador del evento (ej: "profile-learn").
|
|
204
|
+
status: Estado del resultado: "ok" | "error" | "warn" | "skip".
|
|
205
|
+
fields: Diccionario ordenado de campo → valor a mostrar.
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
log.event_block("profile-learn", status="ok", fields={
|
|
209
|
+
"sessions_analyzed": 12,
|
|
210
|
+
"calibration_mode": "z-score",
|
|
211
|
+
"adoption_pct": "78%",
|
|
212
|
+
"strong_patterns": "hooks frecuentes, commits convencionales",
|
|
213
|
+
"weak_patterns": None,
|
|
214
|
+
"commit_topics": "refactor, api, test",
|
|
215
|
+
})
|
|
216
|
+
"""
|
|
217
|
+
block = _build_event_block(event, status, fields or {})
|
|
218
|
+
level = _OK_LEVEL if status == "ok" else (
|
|
219
|
+
logging.WARNING if status == "warn" else
|
|
220
|
+
logging.ERROR if status == "error" else
|
|
221
|
+
logging.INFO
|
|
222
|
+
)
|
|
223
|
+
if self.isEnabledFor(level):
|
|
224
|
+
self._log(level, block, ())
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# ── Factory ───────────────────────────────────────────────────────────────────
|
|
228
|
+
|
|
229
|
+
logging.setLoggerClass(HtxLogger)
|
|
230
|
+
|
|
231
|
+
_instance: HtxLogger | None = None
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
def get_logger(
|
|
235
|
+
project_root: Path | None = None,
|
|
236
|
+
*,
|
|
237
|
+
level: int = _DEFAULT_LEVEL,
|
|
238
|
+
file_logging: bool = True,
|
|
239
|
+
) -> HtxLogger:
|
|
240
|
+
"""Retorna la instancia singleton del logger higpertext.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
project_root: Raíz del proyecto para calcular la ruta del archivo de log.
|
|
244
|
+
Si es None, solo se emite a consola.
|
|
245
|
+
level: Nivel mínimo de logging (default: INFO).
|
|
246
|
+
file_logging: Si True, agrega un handler de archivo rotativo.
|
|
247
|
+
"""
|
|
248
|
+
global _instance
|
|
249
|
+
if _instance is not None:
|
|
250
|
+
return _instance
|
|
251
|
+
|
|
252
|
+
logger: HtxLogger = logging.getLogger(LOGGER_NAME) # type: ignore[assignment]
|
|
253
|
+
logger.setLevel(level)
|
|
254
|
+
logger.propagate = False
|
|
255
|
+
|
|
256
|
+
# ── Handler consola ───────────────────────────────────────────────────────
|
|
257
|
+
# Use a subclass that resolves sys.stdout dynamically so pytest capsys works.
|
|
258
|
+
class _DynamicStdoutHandler(logging.StreamHandler):
|
|
259
|
+
@property
|
|
260
|
+
def stream(self): # type: ignore[override]
|
|
261
|
+
return sys.stdout
|
|
262
|
+
|
|
263
|
+
@stream.setter
|
|
264
|
+
def stream(self, value): # type: ignore[override]
|
|
265
|
+
pass # ignore the value set by StreamHandler.__init__
|
|
266
|
+
|
|
267
|
+
console = _DynamicStdoutHandler()
|
|
268
|
+
console.setLevel(level)
|
|
269
|
+
console.setFormatter(_ColorFormatter("%(message)s"))
|
|
270
|
+
logger.addHandler(console)
|
|
271
|
+
|
|
272
|
+
# ── Handler archivo (opcional) ────────────────────────────────────────────
|
|
273
|
+
if file_logging and project_root is not None:
|
|
274
|
+
log_dir = project_root / WORKSPACE_DIR_NAME / "logs"
|
|
275
|
+
try:
|
|
276
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
277
|
+
file_handler = logging.handlers.RotatingFileHandler(
|
|
278
|
+
log_dir / _LOG_FILE,
|
|
279
|
+
maxBytes=_MAX_BYTES,
|
|
280
|
+
backupCount=_BACKUP_COUNT,
|
|
281
|
+
encoding="utf-8",
|
|
282
|
+
)
|
|
283
|
+
file_handler.setLevel(logging.DEBUG)
|
|
284
|
+
file_handler.setFormatter(
|
|
285
|
+
_PlainFormatter(
|
|
286
|
+
"%(asctime)s %(levelname)-8s %(message)s",
|
|
287
|
+
datefmt="%Y-%m-%dT%H:%M:%S",
|
|
288
|
+
)
|
|
289
|
+
)
|
|
290
|
+
logger.addHandler(file_handler)
|
|
291
|
+
except OSError:
|
|
292
|
+
pass # nosec — no bloquear si no se puede escribir el log
|
|
293
|
+
|
|
294
|
+
_instance = logger
|
|
295
|
+
return _instance
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def reset_logger() -> None:
|
|
299
|
+
"""Limpia la instancia singleton (útil en tests)."""
|
|
300
|
+
global _instance
|
|
301
|
+
if _instance is not None:
|
|
302
|
+
_instance.handlers.clear()
|
|
303
|
+
_instance = None
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""OutputStore — persiste outputs de capabilities en .higpertext/reports/ con índice."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
from datetime import datetime, timezone
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from higpertext.kernel.config_paths import WORKSPACE_DIR_NAME
|
|
10
|
+
from higpertext.kernel.app_config import REPORTS_DIR as _REPORTS_DIR, INDEX_FILE as _INDEX_FILE
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _canonical_name(capability_id: str) -> str:
|
|
14
|
+
"""Convierte 'common.telemetry-report' → 'common_telemetry_report'."""
|
|
15
|
+
return re.sub(r"[.\-\s]+", "_", capability_id)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class OutputStore:
|
|
19
|
+
"""Persiste y recupera outputs de capabilities sin modificar su stdout.
|
|
20
|
+
|
|
21
|
+
El stdout al agente no se toca. Esta clase es una capa secundaria
|
|
22
|
+
de persistencia para que otras tools puedan leer outputs anteriores.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self, project_root: Path) -> None:
|
|
26
|
+
self._reports_dir = project_root / WORKSPACE_DIR_NAME / _REPORTS_DIR
|
|
27
|
+
self._index_path = self._reports_dir / _INDEX_FILE
|
|
28
|
+
|
|
29
|
+
def write(self, capability_id: str, content: str, fmt: str = "md") -> Path:
|
|
30
|
+
"""Persiste el output de una capability y actualiza el índice."""
|
|
31
|
+
self._reports_dir.mkdir(parents=True, exist_ok=True)
|
|
32
|
+
filename = f"{_canonical_name(capability_id)}.{fmt}"
|
|
33
|
+
out_path = self._reports_dir / filename
|
|
34
|
+
out_path.write_text(content, encoding="utf-8")
|
|
35
|
+
self._update_index(capability_id, out_path, len(content))
|
|
36
|
+
return out_path
|
|
37
|
+
|
|
38
|
+
def read(self, capability_id: str) -> str | None:
|
|
39
|
+
"""Retorna el último output persistido de una capability, o None."""
|
|
40
|
+
index = self._load_index()
|
|
41
|
+
entry = index.get(capability_id)
|
|
42
|
+
if not entry:
|
|
43
|
+
return None
|
|
44
|
+
path = Path(entry["path"])
|
|
45
|
+
if not path.exists():
|
|
46
|
+
return None
|
|
47
|
+
return path.read_text(encoding="utf-8")
|
|
48
|
+
|
|
49
|
+
def list(self) -> dict[str, dict]:
|
|
50
|
+
"""Retorna el índice completo: {capability_id: {path, ts, size_bytes}}."""
|
|
51
|
+
return self._load_index()
|
|
52
|
+
|
|
53
|
+
def _update_index(self, capability_id: str, path: Path, size: int) -> None:
|
|
54
|
+
index = self._load_index()
|
|
55
|
+
index[capability_id] = {
|
|
56
|
+
"path": str(path),
|
|
57
|
+
"ts": datetime.now(timezone.utc).isoformat(timespec="seconds"),
|
|
58
|
+
"size_bytes": size,
|
|
59
|
+
}
|
|
60
|
+
self._index_path.write_text(
|
|
61
|
+
json.dumps(index, indent=2, ensure_ascii=False), encoding="utf-8"
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
def _load_index(self) -> dict:
|
|
65
|
+
if not self._index_path.exists():
|
|
66
|
+
return {}
|
|
67
|
+
try:
|
|
68
|
+
return json.loads(self._index_path.read_text(encoding="utf-8"))
|
|
69
|
+
except (OSError, json.JSONDecodeError):
|
|
70
|
+
return {}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Infrastructure parser init
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
"""CodeChunker - Divide archivos Python y Markdown en fragmentos semánticos."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
import ast
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from higpertext.kernel.domain.rag import DocumentChunk
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class CodeChunker:
|
|
10
|
+
"""Divide archivos de código y documentación en bloques individuales."""
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def chunk_file(file_path: Path, max_chars: int = 1500) -> list[DocumentChunk]:
|
|
14
|
+
"""Detecta la extensión e invoca al chunker especializado."""
|
|
15
|
+
if not file_path.exists() or file_path.is_dir():
|
|
16
|
+
return []
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
content = file_path.read_text(encoding="utf-8", errors="replace")
|
|
20
|
+
except OSError:
|
|
21
|
+
return []
|
|
22
|
+
|
|
23
|
+
if file_path.suffix == ".py":
|
|
24
|
+
return CodeChunker._chunk_python(file_path, content)
|
|
25
|
+
elif file_path.suffix == ".md":
|
|
26
|
+
return CodeChunker._chunk_markdown(file_path, content)
|
|
27
|
+
else:
|
|
28
|
+
return CodeChunker._chunk_generic(file_path, content, max_chars)
|
|
29
|
+
|
|
30
|
+
@staticmethod
|
|
31
|
+
def _chunk_python(file_path: Path, content: str) -> list[DocumentChunk]:
|
|
32
|
+
chunks: list[DocumentChunk] = []
|
|
33
|
+
lines = content.splitlines()
|
|
34
|
+
|
|
35
|
+
try:
|
|
36
|
+
tree = ast.parse(content)
|
|
37
|
+
except SyntaxError:
|
|
38
|
+
# Fallback a segmentación genérica si hay error sintáctico
|
|
39
|
+
return CodeChunker._chunk_generic(file_path, content)
|
|
40
|
+
|
|
41
|
+
# Extraer módulos/clases/funciones
|
|
42
|
+
for node in ast.iter_child_nodes(tree):
|
|
43
|
+
if isinstance(node, (ast.ClassDef, ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
44
|
+
start = node.lineno
|
|
45
|
+
# Intentar calcular línea final
|
|
46
|
+
end = getattr(node, "end_lineno", len(lines))
|
|
47
|
+
block_content = "\n".join(lines[start - 1 : end])
|
|
48
|
+
chunk_type = "class" if isinstance(node, ast.ClassDef) else "function"
|
|
49
|
+
|
|
50
|
+
chunks.append(
|
|
51
|
+
DocumentChunk(
|
|
52
|
+
file=str(file_path.resolve()),
|
|
53
|
+
content=block_content,
|
|
54
|
+
chunk_type=chunk_type,
|
|
55
|
+
start_line=start,
|
|
56
|
+
end_line=end,
|
|
57
|
+
metadata={"name": node.name},
|
|
58
|
+
)
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Si no se extrajeron bloques estructurales, o el archivo es pequeño, agregar todo el archivo
|
|
62
|
+
if not chunks:
|
|
63
|
+
chunks.append(
|
|
64
|
+
DocumentChunk(
|
|
65
|
+
file=str(file_path.resolve()),
|
|
66
|
+
content=content,
|
|
67
|
+
chunk_type="general",
|
|
68
|
+
start_line=1,
|
|
69
|
+
end_line=len(lines),
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return chunks
|
|
73
|
+
|
|
74
|
+
@staticmethod
|
|
75
|
+
def _chunk_markdown(file_path: Path, content: str) -> list[DocumentChunk]:
|
|
76
|
+
chunks: list[DocumentChunk] = []
|
|
77
|
+
lines = content.splitlines()
|
|
78
|
+
current_chunk_lines: list[str] = []
|
|
79
|
+
start_line = 1
|
|
80
|
+
|
|
81
|
+
for idx, line in enumerate(lines, 1):
|
|
82
|
+
if line.startswith("## ") or line.startswith("# "):
|
|
83
|
+
if current_chunk_lines:
|
|
84
|
+
chunks.append(
|
|
85
|
+
DocumentChunk(
|
|
86
|
+
file=str(file_path.resolve()),
|
|
87
|
+
content="\n".join(current_chunk_lines),
|
|
88
|
+
chunk_type="markdown",
|
|
89
|
+
start_line=start_line,
|
|
90
|
+
end_line=idx - 1,
|
|
91
|
+
)
|
|
92
|
+
)
|
|
93
|
+
current_chunk_lines = []
|
|
94
|
+
start_line = idx
|
|
95
|
+
current_chunk_lines.append(line)
|
|
96
|
+
|
|
97
|
+
if current_chunk_lines:
|
|
98
|
+
chunks.append(
|
|
99
|
+
DocumentChunk(
|
|
100
|
+
file=str(file_path.resolve()),
|
|
101
|
+
content="\n".join(current_chunk_lines),
|
|
102
|
+
chunk_type="markdown",
|
|
103
|
+
start_line=start_line,
|
|
104
|
+
end_line=len(lines),
|
|
105
|
+
)
|
|
106
|
+
)
|
|
107
|
+
return chunks
|
|
108
|
+
|
|
109
|
+
@staticmethod
|
|
110
|
+
def _chunk_generic(file_path: Path, content: str, max_chars: int = 1500) -> list[DocumentChunk]:
|
|
111
|
+
chunks: list[DocumentChunk] = []
|
|
112
|
+
lines = content.splitlines()
|
|
113
|
+
current_lines: list[str] = []
|
|
114
|
+
current_len = 0
|
|
115
|
+
start_line = 1
|
|
116
|
+
|
|
117
|
+
for idx, line in enumerate(lines, 1):
|
|
118
|
+
current_lines.append(line)
|
|
119
|
+
current_len += len(line) + 1
|
|
120
|
+
if current_len >= max_chars:
|
|
121
|
+
chunks.append(
|
|
122
|
+
DocumentChunk(
|
|
123
|
+
file=str(file_path.resolve()),
|
|
124
|
+
content="\n".join(current_lines),
|
|
125
|
+
chunk_type="general",
|
|
126
|
+
start_line=start_line,
|
|
127
|
+
end_line=idx,
|
|
128
|
+
)
|
|
129
|
+
)
|
|
130
|
+
current_lines = []
|
|
131
|
+
current_len = 0
|
|
132
|
+
start_line = idx + 1
|
|
133
|
+
|
|
134
|
+
if current_lines:
|
|
135
|
+
chunks.append(
|
|
136
|
+
DocumentChunk(
|
|
137
|
+
file=str(file_path.resolve()),
|
|
138
|
+
content="\n".join(current_lines),
|
|
139
|
+
chunk_type="general",
|
|
140
|
+
start_line=start_line,
|
|
141
|
+
end_line=len(lines),
|
|
142
|
+
)
|
|
143
|
+
)
|
|
144
|
+
return chunks
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"""Shared language parsers — single source of truth for all file-type parsing."""
|
|
2
|
+
|
|
3
|
+
from .base import LanguageParser, ParseResult
|
|
4
|
+
from .python_parser import PythonParser
|
|
5
|
+
from .typescript_parser import TypeScriptParser
|
|
6
|
+
from .powershell_parser import PowerShellParser
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"LanguageParser",
|
|
10
|
+
"ParseResult",
|
|
11
|
+
"PythonParser",
|
|
12
|
+
"TypeScriptParser",
|
|
13
|
+
"PowerShellParser",
|
|
14
|
+
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Base contract for all language parsers."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class ParseResult:
|
|
12
|
+
"""Canonical output of every LanguageParser."""
|
|
13
|
+
|
|
14
|
+
imports: list[str] = field(default_factory=list)
|
|
15
|
+
classes: list[dict] = field(default_factory=list)
|
|
16
|
+
functions: list[dict] = field(default_factory=list)
|
|
17
|
+
summary: str = ""
|
|
18
|
+
type: str = "unknown"
|
|
19
|
+
is_entry_point: bool = False
|
|
20
|
+
size: int = 0
|
|
21
|
+
|
|
22
|
+
def to_dict(self) -> dict:
|
|
23
|
+
return {
|
|
24
|
+
"imports": self.imports,
|
|
25
|
+
"classes": self.classes,
|
|
26
|
+
"functions": self.functions,
|
|
27
|
+
"summary": self.summary,
|
|
28
|
+
"type": self.type,
|
|
29
|
+
"is_entry_point": self.is_entry_point,
|
|
30
|
+
"size": self.size,
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class LanguageParser(ABC):
|
|
35
|
+
"""Contract: every language parser reads a file and returns a ParseResult."""
|
|
36
|
+
|
|
37
|
+
@abstractmethod
|
|
38
|
+
def parse_file(self, file_path: Path) -> ParseResult: ...
|
|
39
|
+
|
|
40
|
+
def empty(self, note: str = "", lang_type: str = "unknown") -> ParseResult:
|
|
41
|
+
return ParseResult(summary=note, type=lang_type)
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""PowerShell language parser."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import re
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .base import LanguageParser, ParseResult
|
|
9
|
+
|
|
10
|
+
_PS_FUNC = re.compile(r"(?i)^\s*function\s+([a-zA-Z0-9_-]+)")
|
|
11
|
+
_PS_USING = re.compile(r"(?i)^\s*using\s+module\s+([a-zA-Z0-9_\-\.]+)")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class PowerShellParser(LanguageParser):
|
|
15
|
+
"""Parses PowerShell files via regex line scanning."""
|
|
16
|
+
|
|
17
|
+
def parse_file(self, file_path: Path) -> ParseResult:
|
|
18
|
+
try:
|
|
19
|
+
lines = file_path.read_text(encoding="utf-8", errors="replace").splitlines()
|
|
20
|
+
except OSError:
|
|
21
|
+
return self.empty(lang_type="powershell")
|
|
22
|
+
|
|
23
|
+
functions = [
|
|
24
|
+
{"name": m.group(1), "parameters": [], "docstring": ""}
|
|
25
|
+
for line in lines
|
|
26
|
+
for m in [_PS_FUNC.match(line)]
|
|
27
|
+
if m
|
|
28
|
+
]
|
|
29
|
+
imports = [
|
|
30
|
+
m.group(1)
|
|
31
|
+
for line in lines
|
|
32
|
+
for m in [_PS_USING.match(line)]
|
|
33
|
+
if m
|
|
34
|
+
]
|
|
35
|
+
return ParseResult(imports=imports, functions=functions, type="powershell")
|