context-map-ai 2.2.1__tar.gz → 2.3.0__tar.gz
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.
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/workflows/docs.yml +3 -1
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/workflows/publish.yml +14 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.pre-commit-hooks.yaml +1 -1
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/CHANGELOG.md +25 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/PKG-INFO +5 -2
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_ecosistema.py +3 -1
- context_map_ai-2.3.0/context_map/__tests__/test_enrich.py +68 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_enterprise_features.py +1 -1
- context_map_ai-2.3.0/context_map/__tests__/test_ollama.py +29 -0
- context_map_ai-2.3.0/context_map/__tests__/test_security.py +65 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/cli/parser.py +1 -0
- context_map_ai-2.3.0/context_map/application/commands/enrich.py +162 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/personal.py +32 -9
- context_map_ai-2.3.0/context_map/domain/analyzers/__init__.py +31 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analyzers/cyclomatic.py +82 -3
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ecosystem/adaptador.py +4 -8
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/normalization/domain_extractor.py +1 -1
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/scanning/security.py +56 -2
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/content.py +1 -1
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/hermes.py +4 -2
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/ollama.py +22 -2
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_historial.py +11 -11
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/pyproject.toml +5 -2
- context_map_ai-2.2.1/context_map/__tests__/test_ollama.py +0 -13
- context_map_ai-2.2.1/context_map/application/commands/enrich.py +0 -84
- context_map_ai-2.2.1/context_map/domain/analysis/complexity.py +0 -134
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/copilot-instructions.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.github/workflows/ci.yml +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.gitignore +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.hermes/config.yaml +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.hermes/shields/pre-commit.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.hermes/triggers/post-commit.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/.hermes/workflows/dev-loop.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/AGENTS.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/CODE_OF_CONDUCT.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/CONTRIBUTING.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/LICENSE +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/Makefile +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/README.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/README_EN.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/action.yml +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/conftest.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/smoke.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_ast_audit.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_ast_summary.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_checker_frescura.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_cli.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_conexiones.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_cyclomatic.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_diario_consolidado.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_doctor.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_domain_extractor.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_export.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_hardware.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_hooks.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_ingest.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_mcp_server_seguridad.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_mermaid.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_pack_unpack.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_parsing.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_personal.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_refresh_temporales.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_reglas_catalogo.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_scan_cache.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_scanner.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_security_scanner.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_similarity.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_standardize.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_tokenization.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_topologia_arbol.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_vault_consolidation.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_watcher.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_wrap.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/cli/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/cli/cli.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/_helpers.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/adapt.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/auto.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/build.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/doctor_cmd.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/export.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/hook.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/importers.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/ingest.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/pack.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/refresh.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/scan.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/sync.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/tools.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/update.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/watch.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/application/commands/wrap.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/cli.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/generators/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/generators/generadores.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/generators/narrative_templates.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/logging_setup.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/models/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/models/config.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/models/models.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/normalization/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/normalization/cleaning.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/normalization/inference.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/normalization/mappings.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/normalization/standardize.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/parsing/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/parsing/cargadores.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/parsing/clasificacion.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/parsing/dedup.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/parsing/grafo.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/personal/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/personal/bd.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/storage/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/storage/config_loader.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/storage/store.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/core/tokenization.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analysis/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analysis/checker.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analysis/models.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analysis/reporting.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analysis/signals.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analyzers/ast_audit.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/analyzers/ast_summary.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ecosystem/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ecosystem/detector.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ecosystem/hooks.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ecosystem/rules_templates.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/health/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/health/doctor.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/health/hardware.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ingestion/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/ingestion/ingest.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/normalization/similarity.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/reglas/reglas.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/reporting/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/reporting/reporter.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/scanning/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/scanning/cache.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/scanning/scanner.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/storage/packer.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/synchronization/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/synchronization/relaciones.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/synchronization/sync.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/domain/synchronization/watcher.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/structure.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/antigravity.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/chat_export.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/git.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/mcp_server.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/infrastructure/version_check.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/briefs/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/briefs/agents.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/briefs/brief.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/briefs/extractors.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/briefs/sections.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/briefs/skill.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/atomic.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/__init__.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/canvas.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/common.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/rutas.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/mermaid.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/preservar.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/templates.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/presentation/vault/writer.py +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/docs/LAUNCH_KIT.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/mkdocs.yml +0 -0
- {context_map_ai-2.2.1 → context_map_ai-2.3.0}/uv.lock +0 -0
|
@@ -40,3 +40,17 @@ jobs:
|
|
|
40
40
|
uses: pypa/gh-action-pypi-publish@release/v1
|
|
41
41
|
with:
|
|
42
42
|
skip-existing: true
|
|
43
|
+
|
|
44
|
+
github-release:
|
|
45
|
+
name: Create GitHub Release
|
|
46
|
+
needs: pypi-publish
|
|
47
|
+
if: startsWith(github.ref, 'refs/tags/v')
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
permissions:
|
|
50
|
+
contents: write
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
- name: Create Release
|
|
54
|
+
uses: softprops/action-gh-release@v2
|
|
55
|
+
with:
|
|
56
|
+
generate_release_notes: true
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
- id: context-map
|
|
2
2
|
name: ContextMap Auto Sync
|
|
3
3
|
description: Escanea y actualiza automáticamente la bóveda de Obsidian y briefs de ContextMap antes de cada commit.
|
|
4
|
-
entry:
|
|
4
|
+
entry: python -m context_map.cli build --clean --brief --quiet
|
|
5
5
|
language: python
|
|
6
6
|
stages: [pre-commit]
|
|
7
7
|
types: [python, markdown, yaml, json]
|
|
@@ -5,6 +5,31 @@ Todas las notas de versión y cambios destacables en este proyecto serán docume
|
|
|
5
5
|
El formato está basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/),
|
|
6
6
|
y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.3.0] — 2026-08-21
|
|
9
|
+
|
|
10
|
+
### 🛡️ Novedades y Mejoras — Escáner de Secretos Avanzado, Entropía Shannon, Opt-Out Ollama & Automatización Release
|
|
11
|
+
|
|
12
|
+
- **feat(`security`)**: Detección ampliada de credenciales con soporte para Slack tokens (`xoxb-`), Stripe keys (`sk_live_`, `sk_test_`), Twilio SIDs (`AC`, `SK`), SendGrid (`SG.`), npm access tokens (`npm_`) y webhooks de Discord.
|
|
13
|
+
- **feat(`security`)**: Análisis matemático de alta entropía de Shannon (`calcular_entropia_shannon`) en asignaciones de variables sensibles para detectar llaves criptográficas no estructuradas.
|
|
14
|
+
- **feat(`ollama`)**: Soporte de opt-out mediante flag `--no-ollama` en `ctxmap enrich` y variable de entorno `CTXMAP_NO_OLLAMA=1` para forzar análisis puramente offline y estático AST.
|
|
15
|
+
- **feat(`ci/cd`)**: Creación automática de GitHub Releases (`softprops/action-gh-release@v2`) con notas generadas al publicar tags en `publish.yml`, y principio de mínimo privilegio en permisos de `docs.yml`.
|
|
16
|
+
- **feat(`personal`)**: Soporte para la variable `CTXMAP_GDRIVE_ROOTS` en `personal sync` y optimización de búsqueda acotada de unidades en Windows.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## [2.2.2] — 2026-08-21
|
|
21
|
+
|
|
22
|
+
### 🔧 Correcciones y Hardening — Saneamiento Técnico y Parche
|
|
23
|
+
|
|
24
|
+
- **fix(`enrich`)**: Persistencia real en disco de los docstrings generados en `ctxmap enrich` vía `filepath.write_text` con formateo adecuado de indentación y soporte completo para flag `--dry-run` de previsualización sin alterar archivos.
|
|
25
|
+
- **refactor(`complexity`/`cyclomatic`)**: Eliminación de la duplicación de código entre `domain/analysis/complexity.py` y `domain/analyzers/cyclomatic.py`. Consolidación unificada de métricas ciclomáticas de McCabe en `domain/analyzers/cyclomatic.py` (con soporte AST completo para `AsyncFor`, `AsyncWith`, `Assert`, `IfExp`, `BoolOp`).
|
|
26
|
+
- **security(`adaptador`)**: Endurecimiento de `_es_generado_ctxmap` para exigir estrictamente el marcador `CONTEXTMAP:BEGIN` evitando falsos positivos con archivos de usuario.
|
|
27
|
+
- **security(`hermes`)**: Parametrización segura de cláusulas `LIMIT ?` en SQLite para defensa en profundidad.
|
|
28
|
+
- **devops(`pre-commit`)**: Actualización de `.pre-commit-hooks.yaml` para invocar el módulo local `python -m context_map.cli` en lugar del binario global.
|
|
29
|
+
- **docs(`domain_extractor`)**: Corrección de docstrings clarificando análisis de frecuencia léxica local en lugar de TF-IDF.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
8
33
|
## [2.2.1] — 2026-08-21
|
|
9
34
|
|
|
10
35
|
### 🔧 Correcciones — Saneamiento de release
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: context-map-ai
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.3.0
|
|
4
4
|
Summary: Mapa mental narrativo de proyectos para agentes de IA
|
|
5
5
|
Project-URL: Homepage, https://github.com/kudawasama/ContextMap
|
|
6
6
|
Project-URL: Repository, https://github.com/kudawasama/ContextMap
|
|
@@ -9,9 +9,12 @@ Author-email: kudawasama <cespedes.jac@gmail.com>
|
|
|
9
9
|
License-Expression: MIT
|
|
10
10
|
License-File: LICENSE
|
|
11
11
|
Keywords: agent,ai,context,mindmap,obsidian
|
|
12
|
-
Classifier: Development Status ::
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: English
|
|
16
|
+
Classifier: Natural Language :: Spanish
|
|
17
|
+
Classifier: Operating System :: OS Independent
|
|
15
18
|
Classifier: Programming Language :: Python :: 3
|
|
16
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
17
20
|
Classifier: Programming Language :: Python :: 3.11
|
|
@@ -328,8 +328,10 @@ def test_adaptar_upgrade_memoria_viva_agents_generado() -> None:
|
|
|
328
328
|
with open(agents_path, "w", encoding="utf-8") as f:
|
|
329
329
|
f.write(
|
|
330
330
|
"# Instrucciones — Bot Demo\n\n"
|
|
331
|
-
"
|
|
331
|
+
"<!-- CONTEXTMAP:BEGIN -->\n"
|
|
332
|
+
"Este proyecto utiliza ContextMap para gobernanza de contexto.\n"
|
|
332
333
|
"## 1. Protocolo de Inicio\n- Leer el brief\n"
|
|
334
|
+
"<!-- CONTEXTMAP:END -->\n"
|
|
333
335
|
)
|
|
334
336
|
|
|
335
337
|
eco = detectar_ecosistema(temp_dir)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Pruebas unitarias para el comando ctxmap enrich y su persistencia en disco."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import ast
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from context_map.application.commands.enrich import _formatear_docstring, cmd_enrich
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_formatear_docstring_simple() -> None:
|
|
12
|
+
"""Verifica el formateo de un docstring simple de una sola línea."""
|
|
13
|
+
res = _formatear_docstring("Calcula el total de la orden.", " ")
|
|
14
|
+
assert len(res) == 1
|
|
15
|
+
assert res[0] == ' """Calcula el total de la orden."""'
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_formatear_docstring_multilinea() -> None:
|
|
19
|
+
"""Verifica el formateo de un docstring con múltiples líneas e indentación."""
|
|
20
|
+
doc = """Calcula el total.
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
subtotal: Monto base.
|
|
24
|
+
"""
|
|
25
|
+
res = _formatear_docstring(doc, " ")
|
|
26
|
+
assert res[0] == ' """Calcula el total.'
|
|
27
|
+
assert " Args:" in res
|
|
28
|
+
assert " subtotal: Monto base." in res
|
|
29
|
+
assert res[-1] == ' """'
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_cmd_enrich_persiste_cambios_en_disco() -> None:
|
|
33
|
+
"""Verifica que cmd_enrich escriba efectivamente el docstring generado en el archivo."""
|
|
34
|
+
codigo_original = "def sumar(a: int, b: int) -> int:\n return a + b\n"
|
|
35
|
+
|
|
36
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
37
|
+
test_file = Path(tmpdir) / "matematica.py"
|
|
38
|
+
test_file.write_text(codigo_original, encoding="utf-8")
|
|
39
|
+
|
|
40
|
+
args = argparse.Namespace(path=str(test_file), model=None, dry_run=False)
|
|
41
|
+
ret = cmd_enrich(args)
|
|
42
|
+
assert ret == 0
|
|
43
|
+
|
|
44
|
+
contenido_nuevo = test_file.read_text(encoding="utf-8")
|
|
45
|
+
assert contenido_nuevo != codigo_original
|
|
46
|
+
|
|
47
|
+
# Validar que sea sintácticamente válido
|
|
48
|
+
tree = ast.parse(contenido_nuevo)
|
|
49
|
+
fn = tree.body[0]
|
|
50
|
+
assert isinstance(fn, ast.FunctionDef)
|
|
51
|
+
assert ast.get_docstring(fn) is not None
|
|
52
|
+
assert "sumar" in ast.get_docstring(fn).lower() or len(ast.get_docstring(fn)) > 0
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_cmd_enrich_dry_run_no_modifica_disco() -> None:
|
|
56
|
+
"""Verifica que en modo --dry-run el archivo en disco permanezca intacto."""
|
|
57
|
+
codigo_original = "def restar(a: int, b: int) -> int:\n return a - b\n"
|
|
58
|
+
|
|
59
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
60
|
+
test_file = Path(tmpdir) / "calculo.py"
|
|
61
|
+
test_file.write_text(codigo_original, encoding="utf-8")
|
|
62
|
+
|
|
63
|
+
args = argparse.Namespace(path=str(test_file), model=None, dry_run=True)
|
|
64
|
+
ret = cmd_enrich(args)
|
|
65
|
+
assert ret == 0
|
|
66
|
+
|
|
67
|
+
contenido_despues = test_file.read_text(encoding="utf-8")
|
|
68
|
+
assert contenido_despues == codigo_original
|
{context_map_ai-2.2.1 → context_map_ai-2.3.0}/context_map/__tests__/test_enterprise_features.py
RENAMED
|
@@ -4,7 +4,7 @@ import tempfile
|
|
|
4
4
|
|
|
5
5
|
from context_map.application.commands.hook import cmd_hook_install
|
|
6
6
|
from context_map.core.storage.config_loader import load_project_config
|
|
7
|
-
from context_map.domain.
|
|
7
|
+
from context_map.domain.analyzers.cyclomatic import calcular_complejidad_archivo
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
def test_hook_install_creates_pre_commit_script() -> None:
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Pruebas unitarias para el módulo context_map.infrastructure.integrations.ollama."""
|
|
2
|
+
|
|
3
|
+
from context_map.infrastructure.integrations.ollama import OllamaLocalClient
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_ollama_client_init() -> None:
|
|
7
|
+
"""Verifica la inicialización del cliente de Ollama local."""
|
|
8
|
+
client = OllamaLocalClient()
|
|
9
|
+
assert client.host == "http://localhost:11434"
|
|
10
|
+
assert client.hardware is not None
|
|
11
|
+
# Si no hay Ollama corriendo en el entorno de test, esta_disponible() debe retornar False sin lanzar excepción
|
|
12
|
+
disponible = client.esta_disponible()
|
|
13
|
+
assert isinstance(disponible, bool)
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_ollama_client_opt_out_parametro() -> None:
|
|
17
|
+
"""Verifica que el parámetro opt_out desactive inmediatamente el cliente."""
|
|
18
|
+
client = OllamaLocalClient(opt_out=True)
|
|
19
|
+
assert client.opt_out is True
|
|
20
|
+
assert client.esta_disponible() is False
|
|
21
|
+
assert client.listar_modelos_instalados() == []
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_ollama_client_opt_out_entorno(monkeypatch) -> None:
|
|
25
|
+
"""Verifica que la variable de entorno CTXMAP_NO_OLLAMA desactive el cliente."""
|
|
26
|
+
monkeypatch.setenv("CTXMAP_NO_OLLAMA", "1")
|
|
27
|
+
client = OllamaLocalClient()
|
|
28
|
+
assert client.opt_out is True
|
|
29
|
+
assert client.esta_disponible() is False
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""Pruebas unitarias para el escáner de secretos y análisis de entropía de Shannon."""
|
|
2
|
+
|
|
3
|
+
from context_map.domain.scanning.security import SecurityScanner, calcular_entropia_shannon
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_calcular_entropia_shannon() -> None:
|
|
7
|
+
"""Verifica el cálculo de entropía de Shannon en cadenas repetitivas y aleatorias."""
|
|
8
|
+
assert calcular_entropia_shannon("") == 0.0
|
|
9
|
+
# Cadena de caracteres repetidos tiene entropía 0
|
|
10
|
+
assert calcular_entropia_shannon("aaaaaaaaaaaaaaaa") == 0.0
|
|
11
|
+
# Cadena pseudoaleatoria criptográfica tiene alta entropía (> 4.0)
|
|
12
|
+
entropia_alta = calcular_entropia_shannon("a8F9z!q2L#m9X$v1K@p8")
|
|
13
|
+
assert entropia_alta > 4.0
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def test_scanner_detecta_slack_y_stripe() -> None:
|
|
17
|
+
"""Verifica la detección de tokens de Slack y Stripe."""
|
|
18
|
+
token_slack = "".join(["xo", "xb-", "123456789012-", "1234567890123-", "abcDEFghiJKLmnoPQR"])
|
|
19
|
+
token_stripe = "".join(["sk_", "live_", "51HzABC1234567890abcdefghijklmnOPQR"])
|
|
20
|
+
codigo = f"""
|
|
21
|
+
SLACK_BOT = "{token_slack}"
|
|
22
|
+
STRIPE_KEY = "{token_stripe}"
|
|
23
|
+
"""
|
|
24
|
+
hallazgos = SecurityScanner.escanear_contenido(codigo, "app/config.py")
|
|
25
|
+
tipos = [h["tipo"] for h in hallazgos]
|
|
26
|
+
assert "Slack Token" in tipos
|
|
27
|
+
assert "Stripe API Key" in tipos
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def test_scanner_detecta_sendgrid_npm_twilio() -> None:
|
|
31
|
+
"""Verifica la detección de SendGrid, npm y Twilio."""
|
|
32
|
+
token_sendgrid = "".join(["SG.", "abcdefghij1234567890_-.", "_-abcdefghijklmnopqrstuvwxyz0123456789012"])
|
|
33
|
+
token_npm = "".join(["npm_", "1234567890abcdefghijklmnopqrstuv"])
|
|
34
|
+
token_twilio = "".join(["AC", "1234567890abcdef1234567890abcdef"])
|
|
35
|
+
codigo = f"""
|
|
36
|
+
SENDGRID_API = "{token_sendgrid}"
|
|
37
|
+
NPM_AUTH = "{token_npm}"
|
|
38
|
+
TWILIO_SID = "{token_twilio}"
|
|
39
|
+
"""
|
|
40
|
+
hallazgos = SecurityScanner.escanear_contenido(codigo, "services/auth.py")
|
|
41
|
+
tipos = [h["tipo"] for h in hallazgos]
|
|
42
|
+
assert "SendGrid API Key" in tipos
|
|
43
|
+
assert "npm Access Token" in tipos
|
|
44
|
+
assert "Twilio API Key or SID" in tipos
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_scanner_detecta_alta_entropia_generica() -> None:
|
|
48
|
+
"""Verifica que asignaciones de alta entropía a variables sensibles sean advertidas."""
|
|
49
|
+
token_entropy = "".join(["9f8a7b6c5d", "4e3f2a1b0c", "9d8e7f6a5b", "4c3d2e1f0a"])
|
|
50
|
+
codigo = f'AUTH_TOKEN = "{token_entropy}"'
|
|
51
|
+
hallazgos = SecurityScanner.escanear_contenido(codigo, "sec/secrets.py")
|
|
52
|
+
tipos = [h["tipo"] for h in hallazgos]
|
|
53
|
+
assert any("Entropy" in t or "Token" in t for t in tipos)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_scanner_ignora_lineas_dummy() -> None:
|
|
57
|
+
"""Verifica que líneas con dummy, placeholder o example no disparen falsos positivos."""
|
|
58
|
+
token_anthropic = "".join(["sk-ant-", "api03-", "1234567890123456789012345678901234567890"])
|
|
59
|
+
token_example = "".join(["xo", "xb-", "123456789012-", "1234567890123-", "placeholder"])
|
|
60
|
+
codigo = f"""
|
|
61
|
+
# dummy_key = {token_anthropic}
|
|
62
|
+
EXAMPLE_TOKEN = "{token_example}"
|
|
63
|
+
"""
|
|
64
|
+
hallazgos = SecurityScanner.escanear_contenido(codigo, "tests/mock.py")
|
|
65
|
+
assert len(hallazgos) == 0
|
|
@@ -60,6 +60,7 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
60
60
|
s_enrich.add_argument("path", nargs="?", default=".", help="Ruta del archivo o directorio a enriquecer")
|
|
61
61
|
s_enrich.add_argument("--model", default=None, help="Modelo de Ollama preferido (ej. qwen2.5-coder:1.5b)")
|
|
62
62
|
s_enrich.add_argument("--dry-run", action="store_true", help="Solo muestra la vista previa sin modificar archivos")
|
|
63
|
+
s_enrich.add_argument("--no-ollama", action="store_true", help="Desactiva Ollama y utiliza exclusivamente inferencia AST offline")
|
|
63
64
|
|
|
64
65
|
sub.add_parser("init", help="Crea estructura .context-map/")
|
|
65
66
|
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
"""Comando `ctxmap enrich` para el enriquecimiento automático de código función por función.
|
|
2
|
+
|
|
3
|
+
Analiza archivos Python mediante AST y genera docstrings formales en Español Técnico
|
|
4
|
+
(Google Style) utilizando Ollama local si está disponible o inferencia AST estática.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import ast
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from context_map.domain.analyzers.ast_summary import ASTSummaryExtractor
|
|
12
|
+
from context_map.domain.health.hardware import evaluar_hardware_pc
|
|
13
|
+
from context_map.infrastructure.integrations.ollama import OllamaLocalClient
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def _formatear_docstring(doc: str, indent: str) -> list[str]:
|
|
17
|
+
"""Formatea un docstring garantizando comillas triples y la indentación adecuada.
|
|
18
|
+
|
|
19
|
+
Args:
|
|
20
|
+
doc: Contenido del docstring (puede o no traer comillas triples).
|
|
21
|
+
indent: Espacios de indentación que corresponden al cuerpo de la función.
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
Lista de líneas listas para ser insertadas.
|
|
25
|
+
"""
|
|
26
|
+
texto = doc.strip()
|
|
27
|
+
if texto.startswith('"""') and texto.endswith('"""'):
|
|
28
|
+
texto = texto[3:-3].strip()
|
|
29
|
+
elif texto.startswith("'''") and texto.endswith("'''"):
|
|
30
|
+
texto = texto[3:-3].strip()
|
|
31
|
+
|
|
32
|
+
lineas_doc = texto.splitlines()
|
|
33
|
+
if not lineas_doc:
|
|
34
|
+
return [f'{indent}""""""']
|
|
35
|
+
|
|
36
|
+
if len(lineas_doc) == 1:
|
|
37
|
+
return [f'{indent}"""{lineas_doc[0]}"""']
|
|
38
|
+
|
|
39
|
+
resultado = [f'{indent}"""{lineas_doc[0]}']
|
|
40
|
+
for linea in lineas_doc[1:]:
|
|
41
|
+
if linea.strip():
|
|
42
|
+
resultado.append(f"{indent}{linea}")
|
|
43
|
+
else:
|
|
44
|
+
resultado.append("")
|
|
45
|
+
resultado.append(f'{indent}"""')
|
|
46
|
+
return resultado
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def cmd_enrich(args: argparse.Namespace) -> int:
|
|
50
|
+
"""Ejecuta el enriquecimiento de código función por función.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
args: Argumentos de CLI (path, model, dry_run).
|
|
54
|
+
|
|
55
|
+
Returns:
|
|
56
|
+
Código de salida 0 (éxito) o 1 (error).
|
|
57
|
+
"""
|
|
58
|
+
ruta_target = Path(args.path).resolve() if hasattr(args, "path") and args.path else Path(".").resolve()
|
|
59
|
+
dry_run = getattr(args, "dry_run", False)
|
|
60
|
+
|
|
61
|
+
modo_str = " (MODO PREVIEW / DRY-RUN)" if dry_run else ""
|
|
62
|
+
print(f"[enrich] Analizando directorio: {ruta_target}{modo_str}")
|
|
63
|
+
|
|
64
|
+
# 1. Diagnóstico de Hardware y RAM
|
|
65
|
+
hw = evaluar_hardware_pc()
|
|
66
|
+
print(f"[enrich] Hardware: {hw.mensaje_diagnostico}")
|
|
67
|
+
|
|
68
|
+
# 2. Inicializar cliente Ollama si es apto y no se solicitó opt-out
|
|
69
|
+
no_ollama = getattr(args, "no_ollama", False)
|
|
70
|
+
ollama_client: OllamaLocalClient | None = None
|
|
71
|
+
if hw.es_apto_para_ollama and not no_ollama:
|
|
72
|
+
client = OllamaLocalClient(
|
|
73
|
+
model_name=getattr(args, "model", None) or hw.modelo_recomendado,
|
|
74
|
+
opt_out=no_ollama,
|
|
75
|
+
)
|
|
76
|
+
if client.esta_disponible():
|
|
77
|
+
ollama_client = client
|
|
78
|
+
print(f"[enrich] Ollama local activo con modelo '{client.model_name}'")
|
|
79
|
+
else:
|
|
80
|
+
print("[enrich] Ollama no activo en localhost:11434 o desactivado. Se utiliza inferencia sintáctica AST offline.")
|
|
81
|
+
elif no_ollama:
|
|
82
|
+
print("[enrich] Ollama desactivado por flag --no-ollama. Se utiliza inferencia sintáctica AST offline.")
|
|
83
|
+
|
|
84
|
+
# 3. Recorrer archivos .py y enriquecer función por función
|
|
85
|
+
archivos_procesados = 0
|
|
86
|
+
funciones_enriquecidas = 0
|
|
87
|
+
|
|
88
|
+
archivos = [ruta_target] if ruta_target.is_file() else list(ruta_target.rglob("*.py"))
|
|
89
|
+
|
|
90
|
+
for filepath in archivos:
|
|
91
|
+
if any(part.startswith(".") or part in ("venv", "build", "dist", "__pycache__") for part in filepath.parts):
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
try:
|
|
95
|
+
contenido = filepath.read_text(encoding="utf-8", errors="ignore")
|
|
96
|
+
tree = ast.parse(contenido)
|
|
97
|
+
except Exception:
|
|
98
|
+
continue
|
|
99
|
+
|
|
100
|
+
lineas = contenido.splitlines()
|
|
101
|
+
|
|
102
|
+
# Recolectar funciones sin docstring
|
|
103
|
+
nodos_funciones: list[ast.FunctionDef | ast.AsyncFunctionDef] = []
|
|
104
|
+
for node in ast.walk(tree):
|
|
105
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
106
|
+
if not ast.get_docstring(node) and node.body:
|
|
107
|
+
nodos_funciones.append(node)
|
|
108
|
+
|
|
109
|
+
if not nodos_funciones:
|
|
110
|
+
archivos_procesados += 1
|
|
111
|
+
continue
|
|
112
|
+
|
|
113
|
+
# Ordenar de abajo hacia arriba por línea de inicio del cuerpo para insertar sin desfasar índices
|
|
114
|
+
nodos_funciones.sort(key=lambda n: n.body[0].lineno, reverse=True)
|
|
115
|
+
archivo_modificado = False
|
|
116
|
+
|
|
117
|
+
for node in nodos_funciones:
|
|
118
|
+
funciones_enriquecidas += 1
|
|
119
|
+
# Extraer código de la función
|
|
120
|
+
fn_code = "\n".join(lineas[node.lineno - 1 : node.end_lineno])
|
|
121
|
+
|
|
122
|
+
# Intentar inferencia con Ollama o Fallback AST
|
|
123
|
+
nuevo_doc = None
|
|
124
|
+
if ollama_client:
|
|
125
|
+
nuevo_doc = ollama_client.generar_docstring_funcion(node.name, fn_code)
|
|
126
|
+
|
|
127
|
+
if not nuevo_doc:
|
|
128
|
+
extractor = ASTSummaryExtractor(fn_code)
|
|
129
|
+
resumen = extractor.inferir_resumen()
|
|
130
|
+
nuevo_doc = resumen
|
|
131
|
+
|
|
132
|
+
# Determinar indentación del cuerpo de la función
|
|
133
|
+
primer_stmt_lineno = node.body[0].lineno # 1-indexed
|
|
134
|
+
linea_cuerpo = lineas[primer_stmt_lineno - 1]
|
|
135
|
+
indent_cuerpo = linea_cuerpo[: len(linea_cuerpo) - len(linea_cuerpo.lstrip())]
|
|
136
|
+
if not indent_cuerpo:
|
|
137
|
+
# Fallback: indentación de la línea def + 4 espacios
|
|
138
|
+
linea_def = lineas[node.lineno - 1]
|
|
139
|
+
indent_def = linea_def[: len(linea_def) - len(linea_def.lstrip())]
|
|
140
|
+
indent_cuerpo = indent_def + " "
|
|
141
|
+
|
|
142
|
+
bloque_docstring = _formatear_docstring(nuevo_doc, indent_cuerpo)
|
|
143
|
+
|
|
144
|
+
# Insertar en las líneas antes de la primera sentencia del cuerpo
|
|
145
|
+
idx_insercion = primer_stmt_lineno - 1
|
|
146
|
+
for offset, l_doc in enumerate(bloque_docstring):
|
|
147
|
+
lineas.insert(idx_insercion + offset, l_doc)
|
|
148
|
+
|
|
149
|
+
archivo_modificado = True
|
|
150
|
+
prefijo = "[dry-run] " if dry_run else ""
|
|
151
|
+
print(f" · {prefijo}{filepath.name} :: {node.name}() -> Enriquecido con docstring")
|
|
152
|
+
|
|
153
|
+
if archivo_modificado and not dry_run:
|
|
154
|
+
nuevo_contenido = "\n".join(lineas)
|
|
155
|
+
if contenido.endswith("\n"):
|
|
156
|
+
nuevo_contenido += "\n"
|
|
157
|
+
filepath.write_text(nuevo_contenido, encoding="utf-8")
|
|
158
|
+
|
|
159
|
+
archivos_procesados += 1
|
|
160
|
+
|
|
161
|
+
print(f"[enrich] [OK] Finalizado: {archivos_procesados} archivos evaluados, {funciones_enriquecidas} funciones identificadas.")
|
|
162
|
+
return 0
|
|
@@ -142,15 +142,38 @@ def _bases_por_defecto() -> list[str]:
|
|
|
142
142
|
os.path.expanduser("~/Documents"),
|
|
143
143
|
os.path.expanduser("~/Desktop"),
|
|
144
144
|
]
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
145
|
+
|
|
146
|
+
# 1. Rutas configuradas explícitamente vía variable de entorno
|
|
147
|
+
gdrive_env = os.environ.get("CTXMAP_GDRIVE_ROOTS", "").strip()
|
|
148
|
+
if gdrive_env:
|
|
149
|
+
for r in gdrive_env.split(";" if ";" in gdrive_env else ","):
|
|
150
|
+
r_limpia = r.strip()
|
|
151
|
+
if r_limpia and os.path.isdir(r_limpia):
|
|
152
|
+
bases.append(r_limpia)
|
|
153
|
+
return bases
|
|
154
|
+
|
|
155
|
+
# 2. Rutas estándar conocidas de Google Drive
|
|
156
|
+
for ruta_estandar in [
|
|
157
|
+
os.path.expanduser("~/Google Drive/Mi unidad"),
|
|
158
|
+
os.path.expanduser("~/GoogleDrive/Mi unidad"),
|
|
159
|
+
"G:\\Mi unidad",
|
|
160
|
+
"H:\\Mi unidad",
|
|
161
|
+
]:
|
|
162
|
+
if os.path.isdir(ruta_estandar):
|
|
163
|
+
bases.append(os.path.join(ruta_estandar, "Desarrollo y Proyectos"))
|
|
164
|
+
bases.append(ruta_estandar)
|
|
165
|
+
|
|
166
|
+
# 3. Búsqueda acotada en letras de unidad montadas en Windows si no se halló en las estándar
|
|
167
|
+
if os.name == "nt" and not any("Mi unidad" in b for b in bases):
|
|
168
|
+
for letra in "CDEFGHIJKLMNOPQRSTUVWXYZ":
|
|
169
|
+
unidad = f"{letra}:\\"
|
|
170
|
+
if not os.path.isdir(unidad):
|
|
171
|
+
continue
|
|
172
|
+
mi_unidad = os.path.join(unidad, "Mi unidad")
|
|
173
|
+
if os.path.isdir(mi_unidad):
|
|
174
|
+
bases.append(os.path.join(mi_unidad, "Desarrollo y Proyectos"))
|
|
175
|
+
bases.append(mi_unidad)
|
|
176
|
+
|
|
154
177
|
return bases
|
|
155
178
|
|
|
156
179
|
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Módulo de analizadores AST y de código estático de ContextMap."""
|
|
2
|
+
|
|
3
|
+
from context_map.domain.analyzers.ast_audit import (
|
|
4
|
+
AlertaAuditoria,
|
|
5
|
+
AuditVisitor,
|
|
6
|
+
auditar_archivo_python,
|
|
7
|
+
auditar_proyecto_python,
|
|
8
|
+
)
|
|
9
|
+
from context_map.domain.analyzers.ast_summary import ASTSummaryExtractor
|
|
10
|
+
from context_map.domain.analyzers.cyclomatic import (
|
|
11
|
+
MetricaComplejidadArchivo,
|
|
12
|
+
MetricaComplejidadFuncion,
|
|
13
|
+
MetricaFuncion,
|
|
14
|
+
analizar_archivo_ciclomatico,
|
|
15
|
+
calcular_complejidad_archivo,
|
|
16
|
+
calcular_complejidad_ciclomatica,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
__all__ = [
|
|
20
|
+
"AlertaAuditoria",
|
|
21
|
+
"AuditVisitor",
|
|
22
|
+
"ASTSummaryExtractor",
|
|
23
|
+
"MetricaComplejidadArchivo",
|
|
24
|
+
"MetricaComplejidadFuncion",
|
|
25
|
+
"MetricaFuncion",
|
|
26
|
+
"analizar_archivo_ciclomatico",
|
|
27
|
+
"auditar_archivo_python",
|
|
28
|
+
"auditar_proyecto_python",
|
|
29
|
+
"calcular_complejidad_archivo",
|
|
30
|
+
"calcular_complejidad_ciclomatica",
|
|
31
|
+
]
|