context-map-ai 2.2.0__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.0 → context_map_ai-2.3.0}/.github/copilot-instructions.md +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/workflows/docs.yml +3 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/workflows/publish.yml +14 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.pre-commit-hooks.yaml +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/CHANGELOG.md +38 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/PKG-INFO +6 -3
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/README.md +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/README_EN.md +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ast_summary.py +0 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_cyclomatic.py +0 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_doctor.py +5 -6
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_domain_extractor.py +1 -3
- {context_map_ai-2.2.0 → 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.0 → context_map_ai-2.3.0}/context_map/__tests__/test_enterprise_features.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hardware.py +1 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hooks.py +0 -1
- context_map_ai-2.3.0/context_map/__tests__/test_mcp_server_seguridad.py +31 -0
- context_map_ai-2.3.0/context_map/__tests__/test_ollama.py +29 -0
- context_map_ai-2.3.0/context_map/__tests__/test_pack_unpack.py +135 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_scan_cache.py +1 -1
- context_map_ai-2.3.0/context_map/__tests__/test_security.py +65 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_security_scanner.py +1 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_similarity.py +1 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_tokenization.py +0 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_wrap.py +0 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/cli/parser.py +10 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/doctor_cmd.py +3 -6
- context_map_ai-2.3.0/context_map/application/commands/enrich.py +162 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/export.py +5 -6
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/hook.py +7 -3
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/personal.py +32 -9
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/refresh.py +1 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/update.py +101 -15
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/watch.py +2 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/__init__.py +0 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/generators/generadores.py +16 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/tokenization.py +2 -5
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/checker.py +20 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/reporting.py +1 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/signals.py +0 -1
- context_map_ai-2.3.0/context_map/domain/analyzers/__init__.py +31 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analyzers/ast_audit.py +21 -18
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analyzers/ast_summary.py +7 -8
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analyzers/cyclomatic.py +85 -7
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/adaptador.py +4 -8
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/hooks.py +3 -3
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/health/doctor.py +2 -4
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/health/hardware.py +2 -3
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/normalization/domain_extractor.py +3 -4
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/normalization/similarity.py +6 -5
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/reglas/reglas.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/cache.py +5 -5
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/security.py +60 -7
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/storage/packer.py +70 -29
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/watcher.py +2 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/content.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/hermes.py +4 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/ollama.py +27 -7
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/mcp_server.py +85 -16
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/version_check.py +11 -4
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/brief.py +0 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/extractors.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/sections.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_historial.py +11 -11
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/pyproject.toml +5 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/uv.lock +3 -3
- context_map_ai-2.2.0/context_map/__tests__/test_ollama.py +0 -14
- context_map_ai-2.2.0/context_map/__tests__/test_pack_unpack.py +0 -60
- context_map_ai-2.2.0/context_map/application/commands/enrich.py +0 -86
- context_map_ai-2.2.0/context_map/domain/analysis/complexity.py +0 -134
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/workflows/ci.yml +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.gitignore +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/config.yaml +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/shields/pre-commit.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/triggers/post-commit.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/workflows/dev-loop.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/AGENTS.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/CODE_OF_CONDUCT.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/CONTRIBUTING.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/LICENSE +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/Makefile +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/action.yml +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/conftest.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/smoke.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ast_audit.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_checker_frescura.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_cli.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_conexiones.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_diario_consolidado.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_export.py +1 -1
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ingest.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_mermaid.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_parsing.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_personal.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_refresh_temporales.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_reglas_catalogo.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_scanner.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_standardize.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_topologia_arbol.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_vault_consolidation.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_watcher.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/cli/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/cli/cli.py +3 -3
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/__init__.py +2 -2
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/_helpers.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/adapt.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/auto.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/build.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/importers.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/ingest.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/pack.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/scan.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/sync.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/tools.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/wrap.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/cli.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/generators/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/generators/narrative_templates.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/logging_setup.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/models/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/models/config.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/models/models.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/cleaning.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/inference.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/mappings.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/standardize.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/cargadores.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/clasificacion.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/dedup.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/grafo.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/personal/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/personal/bd.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/storage/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/storage/config_loader.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/storage/store.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/models.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/detector.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/rules_templates.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/health/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ingestion/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ingestion/ingest.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/reporting/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/reporting/reporter.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/scanner.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/relaciones.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/sync.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/structure.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/antigravity.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/chat_export.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/git.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/agents.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/skill.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/atomic.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/__init__.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/canvas.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/common.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/rutas.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/mermaid.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/preservar.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/templates.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/writer.py +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/docs/LAUNCH_KIT.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
- {context_map_ai-2.2.0 → context_map_ai-2.3.0}/mkdocs.yml +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,44 @@ 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
|
+
|
|
33
|
+
## [2.2.1] — 2026-08-21
|
|
34
|
+
|
|
35
|
+
### 🔧 Correcciones — Saneamiento de release
|
|
36
|
+
|
|
37
|
+
- **fix: flag `--version` real**: `ctxmap --version` ahora imprime la versión instalada (antes no existía y `update` mostraba un mensaje hardcodeado).
|
|
38
|
+
- **fix: detección de versión con el paquete renombrado**: `version_check` prioriza `context-map-ai` (nombre canónico actual) sobre el heredado `context-map`, eliminando avisos falsos de actualización.
|
|
39
|
+
- **fix: lint (ruff) 100% limpio**: corregidos 11 errores (`F811` redefinición en `brief.py`, `F841` variables sin usar, `SIM102/105/108`).
|
|
40
|
+
- **style: ruff --fix e isort** aplicados en todo el paquete (anotaciones modernas `dict`/`| None` y orden de imports).
|
|
41
|
+
|
|
42
|
+
Suite: 176/176 tests verdes · ruff limpio.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
8
46
|
## [2.2.0] — 2026-08-19
|
|
9
47
|
|
|
10
48
|
### 🗜️ Nuevo — Paquetes de Contexto Portátiles Offline, Escáner de Auditoría AST & Fallback de Modelos
|
|
@@ -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
|
|
@@ -41,7 +44,7 @@ Description-Content-Type: text/markdown
|
|
|
41
44
|
|
|
42
45
|
*Captura el alma de tu proyecto, establece gobernanza automática y mantiene vivo el contexto para cualquier Agente de IA (`Antigravity`, `Cursor`, `Claude`, `Hermes`, `Copilot`, `Windsurf`, `Gemini`).*
|
|
43
46
|
|
|
44
|
-
[](https://github.com/kudawasama/ContextMap)
|
|
45
48
|
[](https://pypi.org/project/context-map-ai/)
|
|
46
49
|
[](https://www.python.org/downloads/)
|
|
47
50
|
[](LICENSE)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
*Captura el alma de tu proyecto, establece gobernanza automática y mantiene vivo el contexto para cualquier Agente de IA (`Antigravity`, `Cursor`, `Claude`, `Hermes`, `Copilot`, `Windsurf`, `Gemini`).*
|
|
11
11
|
|
|
12
|
-
[](https://github.com/kudawasama/ContextMap)
|
|
13
13
|
[](https://pypi.org/project/context-map-ai/)
|
|
14
14
|
[](https://www.python.org/downloads/)
|
|
15
15
|
[](LICENSE)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
*Capture the soul of your project, establish automated governance, and keep living context available for any AI Agent (`Antigravity`, `Cursor`, `Claude Code`, `Hermes`, `Copilot`, `Windsurf`, `Gemini`).*
|
|
11
11
|
|
|
12
|
-
[](https://github.com/kudawasama/ContextMap)
|
|
13
13
|
[](https://pypi.org/project/context-map-ai/)
|
|
14
14
|
[](https://www.python.org/downloads/)
|
|
15
15
|
[](LICENSE)
|
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import os
|
|
6
|
-
|
|
6
|
+
|
|
7
|
+
from context_map.domain.health.doctor import diagnosticar_salud, reparar_salud
|
|
7
8
|
from context_map.presentation.briefs.extractors import vault_nombre
|
|
8
9
|
|
|
9
10
|
|
|
@@ -13,7 +14,7 @@ def test_doctor_diagnosticar_salud(tmp_path, monkeypatch):
|
|
|
13
14
|
fake_home = tmp_path / "fake_home"
|
|
14
15
|
fake_home.mkdir()
|
|
15
16
|
monkeypatch.setattr("os.path.expanduser", lambda path: str(fake_home) if "~" in path else os.path.expanduser(path))
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
report = diagnosticar_salud(str(tmp_path))
|
|
18
19
|
assert report.ok
|
|
19
20
|
assert len(report.checks) >= 4
|
|
@@ -41,8 +42,6 @@ def test_doctor_reparar_vaults_desalineados(tmp_path):
|
|
|
41
42
|
(ctx_dir / "vault-Viejo").mkdir(parents=True)
|
|
42
43
|
(ctx_dir / "vault-Viejo" / "nota.md").write_text("# Nota", encoding="utf-8")
|
|
43
44
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
report = reparar_salud(str(tmp_path))
|
|
45
|
+
# El vault esperado para tmp_path será vault-<nombre del directorio>
|
|
46
|
+
reparar_salud(str(tmp_path))
|
|
48
47
|
assert os.path.isdir(ctx_dir / "_legacy" / "vault-Viejo")
|
{context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_domain_extractor.py
RENAMED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"""Pruebas unitarias para el módulo context_map.domain.normalization.domain_extractor."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
from context_map.domain.normalization.domain_extractor import DomainConceptExtractor, extraer_conceptos_proyecto
|
|
3
|
+
from context_map.domain.normalization.domain_extractor import DomainConceptExtractor
|
|
6
4
|
|
|
7
5
|
|
|
8
6
|
def test_domain_concept_extractor() -> None:
|
|
@@ -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.0 → 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:
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
"""Pruebas unitarias para el módulo context_map.domain.health.hardware."""
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from context_map.domain.health.hardware import evaluar_hardware_pc, EspecificacionesHardware
|
|
3
|
+
from context_map.domain.health.hardware import EspecificacionesHardware, evaluar_hardware_pc
|
|
5
4
|
|
|
6
5
|
|
|
7
6
|
def test_evaluar_hardware_pc() -> None:
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""Tests de seguridad del servidor MCP: confirmación destructiva y validación de rutas."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from context_map.infrastructure import mcp_server
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_mcp_build_clean_sin_confirmacion_es_rechazado():
|
|
9
|
+
"""Verifica que build(clean=True) exige confirm=True."""
|
|
10
|
+
res = mcp_server.build(target=".", clean=True)
|
|
11
|
+
assert "rechazada por seguridad" in res
|
|
12
|
+
assert "confirm=True" in res
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def test_mcp_doctor_fix_sin_confirmacion_es_rechazado():
|
|
16
|
+
"""Verifica que doctor(fix=True) exige confirm=True."""
|
|
17
|
+
res = mcp_server.doctor(target=".", fix=True)
|
|
18
|
+
assert "rechazada por seguridad" in res
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def test_mcp_install_hooks_sin_confirmacion_es_rechazado():
|
|
22
|
+
"""Verifica que install_hooks exige confirm=True."""
|
|
23
|
+
res = mcp_server.install_hooks(target=".", force=True)
|
|
24
|
+
assert "rechazada por seguridad" in res
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_mcp_target_invalido_devuelve_error():
|
|
28
|
+
"""Verifica que un target inexistente no se procesa."""
|
|
29
|
+
res = mcp_server.check(target="/ruta/inexistente/xyz-123")
|
|
30
|
+
assert "ERROR" in res
|
|
31
|
+
assert "no es un directorio" in res
|
|
@@ -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,135 @@
|
|
|
1
|
+
"""Tests unitarios para el empaquetado y desempaquetado de contexto (.ctxpack)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
import os
|
|
7
|
+
import tarfile
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from context_map.domain.storage.packer import (
|
|
12
|
+
_sha256_archivo,
|
|
13
|
+
crear_paquete_contexto,
|
|
14
|
+
desempaquetar_contexto,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_pack_y_unpack_flujo_completo(tmp_path):
|
|
19
|
+
"""Verifica la creación y extracción completa de un archivo .ctxpack."""
|
|
20
|
+
# 1. Crear estructura simulada .context-map/
|
|
21
|
+
src_dir = tmp_path / "proyecto_origen"
|
|
22
|
+
ctx_dir = src_dir / ".context-map"
|
|
23
|
+
state_dir = ctx_dir / "state"
|
|
24
|
+
state_dir.mkdir(parents=True)
|
|
25
|
+
|
|
26
|
+
graph_file = state_dir / "graph.jsonl"
|
|
27
|
+
graph_file.write_text(
|
|
28
|
+
'{"id": "idea_1", "type": "IDEA", "title": "Idea 1"}\n'
|
|
29
|
+
'{"id": "base_1", "type": "BASE", "title": "Base 1"}\n',
|
|
30
|
+
encoding="utf-8",
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
brief_file = ctx_dir / "CONTEXT.md"
|
|
34
|
+
brief_file.write_text("# Brief de Contexto", encoding="utf-8")
|
|
35
|
+
|
|
36
|
+
agents_file = src_dir / "AGENTS.md"
|
|
37
|
+
agents_file.write_text("# Reglas Agénticas", encoding="utf-8")
|
|
38
|
+
|
|
39
|
+
# 2. Empaquetar
|
|
40
|
+
out_file = tmp_path / "backup.ctxpack"
|
|
41
|
+
ruta_gen, size, manifest = crear_paquete_contexto(
|
|
42
|
+
target_dir=str(src_dir),
|
|
43
|
+
output_path=str(out_file),
|
|
44
|
+
project_name="ProyectoPrueba",
|
|
45
|
+
)
|
|
46
|
+
|
|
47
|
+
assert os.path.isfile(ruta_gen)
|
|
48
|
+
assert size > 0
|
|
49
|
+
assert manifest["project"] == "ProyectoPrueba"
|
|
50
|
+
assert manifest["node_count"] == 2
|
|
51
|
+
|
|
52
|
+
# 3. Desempaquetar en directorio destino
|
|
53
|
+
dst_dir = tmp_path / "proyecto_destino"
|
|
54
|
+
res_manifest = desempaquetar_contexto(
|
|
55
|
+
archive_path=str(out_file),
|
|
56
|
+
target_dir=str(dst_dir),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
assert res_manifest["project"] == "ProyectoPrueba"
|
|
60
|
+
assert (dst_dir / ".context-map" / "state" / "graph.jsonl").exists()
|
|
61
|
+
assert (dst_dir / ".context-map" / "CONTEXT.md").exists()
|
|
62
|
+
assert (dst_dir / "AGENTS.md").exists()
|
|
63
|
+
|
|
64
|
+
content = (dst_dir / ".context-map" / "state" / "graph.jsonl").read_text(encoding="utf-8")
|
|
65
|
+
assert "idea_1" in content
|
|
66
|
+
assert "base_1" in content
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_manifest_incluye_checksums_sha256(tmp_path):
|
|
70
|
+
"""Verifica que el manifiesto registra un checksum SHA-256 por archivo."""
|
|
71
|
+
src_dir = tmp_path / "origen"
|
|
72
|
+
state_dir = src_dir / ".context-map" / "state"
|
|
73
|
+
state_dir.mkdir(parents=True)
|
|
74
|
+
(state_dir / "graph.jsonl").write_text("hola\n", encoding="utf-8")
|
|
75
|
+
|
|
76
|
+
out_file = tmp_path / "backup.ctxpack"
|
|
77
|
+
_, _, manifest = crear_paquete_contexto(str(src_dir), str(out_file), project_name="Checksums")
|
|
78
|
+
|
|
79
|
+
ruta_rel = os.path.join(".context-map", "state", "graph.jsonl")
|
|
80
|
+
assert ruta_rel in manifest["files"]
|
|
81
|
+
assert manifest["files"][ruta_rel] == _sha256_archivo(str(state_dir / "graph.jsonl"))
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_unpack_detecta_integridad_comprometida(tmp_path):
|
|
85
|
+
"""Verifica que un paquete con contenido alterado se rechaza por checksum."""
|
|
86
|
+
src_dir = tmp_path / "origen"
|
|
87
|
+
state_dir = src_dir / ".context-map" / "state"
|
|
88
|
+
state_dir.mkdir(parents=True)
|
|
89
|
+
(state_dir / "graph.jsonl").write_text('{"id": "a", "type": "IDEA"}\n', encoding="utf-8")
|
|
90
|
+
|
|
91
|
+
out_file = tmp_path / "backup.ctxpack"
|
|
92
|
+
crear_paquete_contexto(str(src_dir), str(out_file), project_name="Integridad")
|
|
93
|
+
|
|
94
|
+
# Reescribir el tar alterando graph.jsonl sin actualizar el manifiesto
|
|
95
|
+
tampered = tmp_path / "tampered.ctxpack"
|
|
96
|
+
with tarfile.open(str(out_file), "r:gz") as tar, tarfile.open(str(tampered), "w:gz") as out:
|
|
97
|
+
for m in tar.getmembers():
|
|
98
|
+
f = tar.extractfile(m)
|
|
99
|
+
contenido = f.read() if f else b""
|
|
100
|
+
if m.name == ".context-map/state/graph.jsonl":
|
|
101
|
+
contenido = b'{"id": "ALTERADO_TAMPERED"}\n'
|
|
102
|
+
ti = tarfile.TarInfo(m.name)
|
|
103
|
+
ti.size = len(contenido)
|
|
104
|
+
ti.mode = m.mode
|
|
105
|
+
out.addfile(ti, io.BytesIO(contenido))
|
|
106
|
+
|
|
107
|
+
with pytest.raises(ValueError, match="Integridad"):
|
|
108
|
+
desempaquetar_contexto(str(tampered), str(tmp_path / "destino"))
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _crear_tar_con_symlink(path: str) -> None:
|
|
112
|
+
"""Crea un .ctxpack malicioso con un symlink apuntando fuera del destino."""
|
|
113
|
+
with tarfile.open(path, "w:gz") as tar:
|
|
114
|
+
data = b"contenido"
|
|
115
|
+
ti = tarfile.TarInfo(".context-map/state/graph.jsonl")
|
|
116
|
+
ti.size = len(data)
|
|
117
|
+
tar.addfile(ti, io.BytesIO(data))
|
|
118
|
+
|
|
119
|
+
# Symlink malicioso que apunta fuera del directorio destino
|
|
120
|
+
sl = tarfile.TarInfo("escape")
|
|
121
|
+
sl.type = tarfile.SYMTYPE
|
|
122
|
+
sl.linkname = "/tmp/ctxmap_escape_target"
|
|
123
|
+
tar.addfile(sl)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_unpack_rechaza_symlinks(tmp_path):
|
|
127
|
+
"""Verifica que un .ctxpack con symlinks no los extrae (hardening)."""
|
|
128
|
+
archivo = tmp_path / "malicioso.ctxpack"
|
|
129
|
+
_crear_tar_con_symlink(str(archivo))
|
|
130
|
+
|
|
131
|
+
destino = tmp_path / "destino"
|
|
132
|
+
desempaquetar_contexto(str(archivo), str(destino))
|
|
133
|
+
|
|
134
|
+
assert (destino / ".context-map" / "state" / "graph.jsonl").exists()
|
|
135
|
+
assert not (destino / "escape").exists() # el symlink no se extrae
|
|
@@ -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
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
"""Pruebas unitarias para el módulo context_map.domain.normalization.similarity."""
|
|
2
2
|
|
|
3
|
-
import pytest
|
|
4
3
|
from context_map.domain.normalization.similarity import (
|
|
4
|
+
deduplicar_elementos_similares,
|
|
5
5
|
distancia_levenshtein,
|
|
6
6
|
similitud_jaccard_ngramas,
|
|
7
7
|
son_textos_similares,
|
|
8
|
-
deduplicar_elementos_similares,
|
|
9
8
|
)
|
|
10
9
|
|
|
11
10
|
|