context-map-ai 2.1.0__tar.gz → 2.2.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.1.0 → context_map_ai-2.2.0}/.github/copilot-instructions.md +1 -1
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/CHANGELOG.md +10 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/PKG-INFO +16 -8
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/README.md +15 -7
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/README_EN.md +18 -10
- context_map_ai-2.2.0/context_map/__tests__/test_ast_audit.py +38 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_doctor.py +6 -1
- context_map_ai-2.2.0/context_map/__tests__/test_pack_unpack.py +60 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/cli/cli.py +4 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/cli/parser.py +8 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/__init__.py +3 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/_helpers.py +29 -4
- context_map_ai-2.2.0/context_map/application/commands/pack.py +68 -0
- context_map_ai-2.2.0/context_map/domain/analyzers/ast_audit.py +155 -0
- context_map_ai-2.2.0/context_map/domain/storage/packer.py +147 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/ollama.py +28 -1
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/pyproject.toml +1 -1
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/workflows/ci.yml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/workflows/docs.yml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/workflows/publish.yml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.gitignore +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/config.yaml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/shields/pre-commit.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/triggers/post-commit.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/workflows/dev-loop.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.pre-commit-hooks.yaml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/AGENTS.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/CODE_OF_CONDUCT.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/CONTRIBUTING.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/LICENSE +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/Makefile +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/action.yml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/conftest.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/smoke.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ast_summary.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_checker_frescura.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_cli.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_conexiones.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_cyclomatic.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_diario_consolidado.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_domain_extractor.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ecosistema.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_enterprise_features.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_export.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hardware.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hooks.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ingest.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_mermaid.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ollama.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_parsing.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_personal.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_refresh_temporales.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_reglas_catalogo.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_scan_cache.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_scanner.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_security_scanner.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_similarity.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_standardize.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_tokenization.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_topologia_arbol.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_vault_consolidation.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_watcher.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_wrap.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/cli/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/adapt.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/auto.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/build.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/doctor_cmd.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/enrich.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/export.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/hook.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/importers.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/ingest.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/personal.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/refresh.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/scan.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/sync.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/tools.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/update.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/watch.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/wrap.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/cli.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/generators/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/generators/generadores.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/generators/narrative_templates.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/logging_setup.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/models/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/models/config.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/models/models.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/cleaning.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/inference.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/mappings.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/standardize.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/cargadores.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/clasificacion.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/dedup.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/grafo.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/personal/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/personal/bd.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/storage/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/storage/config_loader.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/storage/store.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/tokenization.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/checker.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/complexity.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/models.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/reporting.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/signals.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analyzers/ast_summary.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analyzers/cyclomatic.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/adaptador.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/detector.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/hooks.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/rules_templates.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/health/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/health/doctor.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/health/hardware.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ingestion/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ingestion/ingest.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/normalization/domain_extractor.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/normalization/similarity.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/reglas/reglas.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/reporting/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/reporting/reporter.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/cache.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/scanner.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/security.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/relaciones.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/sync.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/watcher.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/analyzers/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/analyzers/content.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/analyzers/structure.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/antigravity.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/chat_export.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/git.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/hermes.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/mcp_server.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/version_check.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/agents.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/brief.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/extractors.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/sections.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/skill.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/atomic.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/__init__.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/canvas.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/common.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/rutas.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_historial.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/mermaid.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/preservar.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/templates.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/writer.py +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/docs/LAUNCH_KIT.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/mkdocs.yml +0 -0
- {context_map_ai-2.1.0 → context_map_ai-2.2.0}/uv.lock +0 -0
|
@@ -5,6 +5,16 @@ 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.2.0] — 2026-08-19
|
|
9
|
+
|
|
10
|
+
### 🗜️ Nuevo — Paquetes de Contexto Portátiles Offline, Escáner de Auditoría AST & Fallback de Modelos
|
|
11
|
+
|
|
12
|
+
- **🗜️ Empaquetado y Desempaquetado Portátil (`ctxmap pack` / `ctxmap unpack`)**: Nuevo sistema para comprimir la memoria viva completa de cualquier proyecto en un archivo único binario `.ctxpack` con manifiesto de integridad (`pack_manifest.json`) y restaurarlo 100% offline en cualquier equipo.
|
|
13
|
+
- **🛡️ Escáner Sintáctico AST de Auditoría Local (`domain/analyzers/ast_audit.py`)**: Motor determinístico de análisis estático que detecta ejecuciones dinámicas inseguras (`eval/exec`), concatenaciones de SQL dinámicas, bloques `except Exception: pass` silenciosos, desuso de administradores de contexto en archivos y credenciales hardcodadas.
|
|
14
|
+
- **🦙 Fallback Inteligente de Modelos Ollama (`infrastructure/integrations/ollama.py`)**: Auto-resolución de modelos locales instalados vía API `/api/tags` para seleccionar automáticamente modelos disponibles (`coder`, `qwen`, `llama`, `deepseek`, `phi`) sin fallar por modelos ausentes.
|
|
15
|
+
- **⚙️ Generación Automática de Plantilla `dominios.yaml` (`application/commands/_helpers.py`)**: Creación transparente de la plantilla por defecto de agrupamiento temático para Obsidian.
|
|
16
|
+
- **Suite de Pruebas**: 176/176 tests unitarios pasados al 100% verde.
|
|
17
|
+
|
|
8
18
|
---
|
|
9
19
|
|
|
10
20
|
## [2.1.0] — 2026-08-18
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: context-map-ai
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.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
|
|
@@ -41,7 +41,8 @@ Description-Content-Type: text/markdown
|
|
|
41
41
|
|
|
42
42
|
*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
43
|
|
|
44
|
-
[](https://github.com/kudawasama/ContextMap)
|
|
45
|
+
[](https://pypi.org/project/context-map-ai/)
|
|
45
46
|
[](https://www.python.org/downloads/)
|
|
46
47
|
[](LICENSE)
|
|
47
48
|
[](file:///.context-map/CONTEXT.md)
|
|
@@ -62,7 +63,7 @@ Description-Content-Type: text/markdown
|
|
|
62
63
|
ContextMap creates an interconnected **Obsidian Vault** and **AI Executive Brief (`CONTEXT.md`)** to manage context, multi-IDE rules, living memory, and autonomous self-maintenance for your software projects across agents like `Antigravity`, `Cursor`, `Claude Code`, `Copilot`, `Hermes`, and `Gemini`.
|
|
63
64
|
|
|
64
65
|
- 📖 **Full English Documentation:** See [**README_EN.md**](README_EN.md)
|
|
65
|
-
- ⚡ **
|
|
66
|
+
- ⚡ **PyPI Install:** `pip install context-map-ai` or `uv tool install context-map-ai`
|
|
66
67
|
- 💬 **Initialize:** Tell your AI Agent: *"Initialize ContextMap for this project"* or run `ctxmap auto .`
|
|
67
68
|
</details>
|
|
68
69
|
|
|
@@ -85,14 +86,17 @@ Construye una bóveda interconectada en **Obsidian** ([Graph View en Árbol Estr
|
|
|
85
86
|
|
|
86
87
|
## ⚡ Inicio Rápido en 10 Segundos
|
|
87
88
|
|
|
88
|
-
### 1. Instalación Global (con `uv`)
|
|
89
|
+
### 1. Instalación Global (con `pip` o `uv`)
|
|
89
90
|
|
|
90
91
|
```bash
|
|
91
|
-
#
|
|
92
|
-
|
|
92
|
+
# Opción 1: Desde PyPI oficial (recomendado con pip)
|
|
93
|
+
pip install context-map-ai
|
|
94
|
+
|
|
95
|
+
# Opción 2: Instalación aislada global con uv (ultra-rápido)
|
|
96
|
+
uv tool install context-map-ai
|
|
93
97
|
|
|
94
|
-
#
|
|
95
|
-
|
|
98
|
+
# Opción 3: Desde el repositorio en GitHub
|
|
99
|
+
uv tool install git+https://github.com/kudawasama/ContextMap.git
|
|
96
100
|
```
|
|
97
101
|
|
|
98
102
|
### 2. Poner en Contexto un Proyecto
|
|
@@ -224,6 +228,10 @@ ctxmap hook install # Inyecta pre-commit y post-commit transpa
|
|
|
224
228
|
# 🔄 Cierre de sesión de trabajo
|
|
225
229
|
ctxmap wrap # refresh + resumen de memoria viva registrada
|
|
226
230
|
|
|
231
|
+
# 📦 Empaquetado y Transporte Offline Portátil
|
|
232
|
+
ctxmap pack . --output mi_proyecto.ctxpack # Comprime la memoria viva en un paquete único
|
|
233
|
+
ctxmap unpack mi_proyecto.ctxpack ./destino # Restaura el contexto completo 100% offline
|
|
234
|
+
|
|
227
235
|
# 📦 Exportación de Contexto Portable (Repomix compatible)
|
|
228
236
|
ctxmap export . --format xml # Exporta contexto plano en XML, JSON o Markdown
|
|
229
237
|
|
|
@@ -9,7 +9,8 @@
|
|
|
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
|
+
[](https://pypi.org/project/context-map-ai/)
|
|
13
14
|
[](https://www.python.org/downloads/)
|
|
14
15
|
[](LICENSE)
|
|
15
16
|
[](file:///.context-map/CONTEXT.md)
|
|
@@ -30,7 +31,7 @@
|
|
|
30
31
|
ContextMap creates an interconnected **Obsidian Vault** and **AI Executive Brief (`CONTEXT.md`)** to manage context, multi-IDE rules, living memory, and autonomous self-maintenance for your software projects across agents like `Antigravity`, `Cursor`, `Claude Code`, `Copilot`, `Hermes`, and `Gemini`.
|
|
31
32
|
|
|
32
33
|
- 📖 **Full English Documentation:** See [**README_EN.md**](README_EN.md)
|
|
33
|
-
- ⚡ **
|
|
34
|
+
- ⚡ **PyPI Install:** `pip install context-map-ai` or `uv tool install context-map-ai`
|
|
34
35
|
- 💬 **Initialize:** Tell your AI Agent: *"Initialize ContextMap for this project"* or run `ctxmap auto .`
|
|
35
36
|
</details>
|
|
36
37
|
|
|
@@ -53,14 +54,17 @@ Construye una bóveda interconectada en **Obsidian** ([Graph View en Árbol Estr
|
|
|
53
54
|
|
|
54
55
|
## ⚡ Inicio Rápido en 10 Segundos
|
|
55
56
|
|
|
56
|
-
### 1. Instalación Global (con `uv`)
|
|
57
|
+
### 1. Instalación Global (con `pip` o `uv`)
|
|
57
58
|
|
|
58
59
|
```bash
|
|
59
|
-
#
|
|
60
|
-
|
|
60
|
+
# Opción 1: Desde PyPI oficial (recomendado con pip)
|
|
61
|
+
pip install context-map-ai
|
|
62
|
+
|
|
63
|
+
# Opción 2: Instalación aislada global con uv (ultra-rápido)
|
|
64
|
+
uv tool install context-map-ai
|
|
61
65
|
|
|
62
|
-
#
|
|
63
|
-
|
|
66
|
+
# Opción 3: Desde el repositorio en GitHub
|
|
67
|
+
uv tool install git+https://github.com/kudawasama/ContextMap.git
|
|
64
68
|
```
|
|
65
69
|
|
|
66
70
|
### 2. Poner en Contexto un Proyecto
|
|
@@ -192,6 +196,10 @@ ctxmap hook install # Inyecta pre-commit y post-commit transpa
|
|
|
192
196
|
# 🔄 Cierre de sesión de trabajo
|
|
193
197
|
ctxmap wrap # refresh + resumen de memoria viva registrada
|
|
194
198
|
|
|
199
|
+
# 📦 Empaquetado y Transporte Offline Portátil
|
|
200
|
+
ctxmap pack . --output mi_proyecto.ctxpack # Comprime la memoria viva en un paquete único
|
|
201
|
+
ctxmap unpack mi_proyecto.ctxpack ./destino # Restaura el contexto completo 100% offline
|
|
202
|
+
|
|
195
203
|
# 📦 Exportación de Contexto Portable (Repomix compatible)
|
|
196
204
|
ctxmap export . --format xml # Exporta contexto plano en XML, JSON o Markdown
|
|
197
205
|
|
|
@@ -9,7 +9,8 @@
|
|
|
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
|
+
[](https://pypi.org/project/context-map-ai/)
|
|
13
14
|
[](https://www.python.org/downloads/)
|
|
14
15
|
[](LICENSE)
|
|
15
16
|
[](file:///.context-map/CONTEXT.md)
|
|
@@ -25,14 +26,14 @@
|
|
|
25
26
|
|
|
26
27
|
## 💡 What is ContextMap and why does it exist?
|
|
27
28
|
|
|
28
|
-
When working with AI Agents in your IDE (`Antigravity`, `Cursor`, `Claude Code`, `Copilot`, etc.), AI
|
|
29
|
+
When working with AI Agents in your IDE (`Antigravity`, `Cursor`, `Claude Code`, `Copilot`, etc.), AI often forgets past decisions, ignores strict architecture constraints, or proposes blind refactors that break the system.
|
|
29
30
|
|
|
30
31
|
**ContextMap solves this by creating a living memory for your project:**
|
|
31
32
|
It builds an interconnected **Obsidian Vault** ([Strict Tree Graph View](file:///.context-map/vault-ContextMap/)) and an AI Executive Brief ([`CONTEXT.md`](file:///.context-map/CONTEXT.md)) that teach any AI Agent:
|
|
32
|
-
- **Why does the project exist?** (Purpose, business
|
|
33
|
-
- **What risks does it face?** (Static complexity
|
|
33
|
+
- **Why does the project exist?** (Purpose, business, and identity).
|
|
34
|
+
- **What risks does it face?** (Static complexity, code alerts, and sensitive zones).
|
|
34
35
|
- **What is implemented vs. pending?** (Deduplicated graph of ideas, bases, and changes).
|
|
35
|
-
- **What
|
|
36
|
+
- **What architecture decisions were made?** (Living memory of past conversations and commit histories).
|
|
36
37
|
|
|
37
38
|
> 🚫 **ContextMap is not just a passive doc generator.** It is an active system for **Agentic Governance**, **Permanent Memory**, **Readiness Assessment**, and **Autonomous Self-Maintenance**.
|
|
38
39
|
|
|
@@ -40,14 +41,17 @@ It builds an interconnected **Obsidian Vault** ([Strict Tree Graph View](file://
|
|
|
40
41
|
|
|
41
42
|
## ⚡ 10-Second Quick Start
|
|
42
43
|
|
|
43
|
-
### 1. Global Installation (
|
|
44
|
+
### 1. Global Installation (via `pip` or `uv`)
|
|
44
45
|
|
|
45
46
|
```bash
|
|
46
|
-
#
|
|
47
|
-
|
|
47
|
+
# Option 1: Official PyPI Package (Recommended via pip)
|
|
48
|
+
pip install context-map-ai
|
|
49
|
+
|
|
50
|
+
# Option 2: Global isolated tool via uv
|
|
51
|
+
uv tool install context-map-ai
|
|
48
52
|
|
|
49
|
-
#
|
|
50
|
-
|
|
53
|
+
# Option 3: Direct from GitHub repository
|
|
54
|
+
uv tool install git+https://github.com/kudawasama/ContextMap.git
|
|
51
55
|
```
|
|
52
56
|
|
|
53
57
|
### 2. Contextualize a Project
|
|
@@ -173,6 +177,10 @@ ctxmap hook install # Injects transparent pre-commit & post-co
|
|
|
173
177
|
# 🔄 Work session closing
|
|
174
178
|
ctxmap wrap # refresh + summary of logged living memory
|
|
175
179
|
|
|
180
|
+
# 📦 Offline Portable Context Packaging
|
|
181
|
+
ctxmap pack . --output project.ctxpack # Packs living context into a single archive
|
|
182
|
+
ctxmap unpack project.ctxpack ./target # Restores context 100% offline
|
|
183
|
+
|
|
176
184
|
# 📦 Portable Context Exporter (Repomix compatible)
|
|
177
185
|
ctxmap export . --format xml # Exports flat context XML, JSON, or Markdown
|
|
178
186
|
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Tests para el módulo de auditoría AST local (ast_audit.py)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from context_map.domain.analyzers.ast_audit import auditar_archivo_python, auditar_proyecto_python
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_auditar_archivo_python_detecta_eval_y_pass(tmp_path):
|
|
9
|
+
"""Verifica la detección de eval() y except pass."""
|
|
10
|
+
code_file = tmp_path / "vulnerable.py"
|
|
11
|
+
code_file.write_text(
|
|
12
|
+
"def test():\n"
|
|
13
|
+
" eval('1 + 1')\n"
|
|
14
|
+
" try:\n"
|
|
15
|
+
" pass\n"
|
|
16
|
+
" except Exception:\n"
|
|
17
|
+
" pass\n",
|
|
18
|
+
encoding="utf-8",
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
alertas = auditar_archivo_python(str(code_file))
|
|
22
|
+
assert len(alertas) >= 2
|
|
23
|
+
categorias = [a.categoria for a in alertas]
|
|
24
|
+
assert "SEGURIDAD" in categorias
|
|
25
|
+
assert "ROBUSTEZ" in categorias
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def test_auditar_proyecto_python(tmp_path):
|
|
29
|
+
"""Verifica que el escáner de proyecto audite archivos .py recursivamente."""
|
|
30
|
+
sub_dir = tmp_path / "core"
|
|
31
|
+
sub_dir.mkdir()
|
|
32
|
+
f1 = sub_dir / "db.py"
|
|
33
|
+
f1.write_text("password = 'super_secret_12345'", encoding="utf-8")
|
|
34
|
+
|
|
35
|
+
alertas = auditar_proyecto_python(str(tmp_path))
|
|
36
|
+
assert len(alertas) == 1
|
|
37
|
+
assert alertas[0].categoria == "SEGURIDAD"
|
|
38
|
+
assert "password" in alertas[0].mensaje
|
|
@@ -7,8 +7,13 @@ from context_map.domain.health.doctor import diagnosticar_salud, reparar_salud,
|
|
|
7
7
|
from context_map.presentation.briefs.extractors import vault_nombre
|
|
8
8
|
|
|
9
9
|
|
|
10
|
-
def test_doctor_diagnosticar_salud(tmp_path):
|
|
10
|
+
def test_doctor_diagnosticar_salud(tmp_path, monkeypatch):
|
|
11
11
|
"""Verifica que diagnosticar_salud retorna un reporte OK."""
|
|
12
|
+
# Aísla la verificación de ~/.context-map-update a un directorio limpio
|
|
13
|
+
fake_home = tmp_path / "fake_home"
|
|
14
|
+
fake_home.mkdir()
|
|
15
|
+
monkeypatch.setattr("os.path.expanduser", lambda path: str(fake_home) if "~" in path else os.path.expanduser(path))
|
|
16
|
+
|
|
12
17
|
report = diagnosticar_salud(str(tmp_path))
|
|
13
18
|
assert report.ok
|
|
14
19
|
assert len(report.checks) >= 4
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Tests unitarios para el empaquetado y desempaquetado de contexto (.ctxpack)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from context_map.domain.storage.packer import (
|
|
7
|
+
crear_paquete_contexto,
|
|
8
|
+
desempaquetar_contexto,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def test_pack_y_unpack_flujo_completo(tmp_path):
|
|
13
|
+
"""Verifica la creación y extracción completa de un archivo .ctxpack."""
|
|
14
|
+
# 1. Crear estructura simulada .context-map/
|
|
15
|
+
src_dir = tmp_path / "proyecto_origen"
|
|
16
|
+
ctx_dir = src_dir / ".context-map"
|
|
17
|
+
state_dir = ctx_dir / "state"
|
|
18
|
+
state_dir.mkdir(parents=True)
|
|
19
|
+
|
|
20
|
+
graph_file = state_dir / "graph.jsonl"
|
|
21
|
+
graph_file.write_text(
|
|
22
|
+
'{"id": "idea_1", "type": "IDEA", "title": "Idea 1"}\n'
|
|
23
|
+
'{"id": "base_1", "type": "BASE", "title": "Base 1"}\n',
|
|
24
|
+
encoding="utf-8",
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
brief_file = ctx_dir / "CONTEXT.md"
|
|
28
|
+
brief_file.write_text("# Brief de Contexto", encoding="utf-8")
|
|
29
|
+
|
|
30
|
+
agents_file = src_dir / "AGENTS.md"
|
|
31
|
+
agents_file.write_text("# Reglas Agénticas", encoding="utf-8")
|
|
32
|
+
|
|
33
|
+
# 2. Empaquetar
|
|
34
|
+
out_file = tmp_path / "backup.ctxpack"
|
|
35
|
+
ruta_gen, size, manifest = crear_paquete_contexto(
|
|
36
|
+
target_dir=str(src_dir),
|
|
37
|
+
output_path=str(out_file),
|
|
38
|
+
project_name="ProyectoPrueba",
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
assert os.path.isfile(ruta_gen)
|
|
42
|
+
assert size > 0
|
|
43
|
+
assert manifest["project"] == "ProyectoPrueba"
|
|
44
|
+
assert manifest["node_count"] == 2
|
|
45
|
+
|
|
46
|
+
# 3. Desempaquetar en directorio destino
|
|
47
|
+
dst_dir = tmp_path / "proyecto_destino"
|
|
48
|
+
res_manifest = desempaquetar_contexto(
|
|
49
|
+
archive_path=str(out_file),
|
|
50
|
+
target_dir=str(dst_dir),
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
assert res_manifest["project"] == "ProyectoPrueba"
|
|
54
|
+
assert (dst_dir / ".context-map" / "state" / "graph.jsonl").exists()
|
|
55
|
+
assert (dst_dir / ".context-map" / "CONTEXT.md").exists()
|
|
56
|
+
assert (dst_dir / "AGENTS.md").exists()
|
|
57
|
+
|
|
58
|
+
content = (dst_dir / ".context-map" / "state" / "graph.jsonl").read_text(encoding="utf-8")
|
|
59
|
+
assert "idea_1" in content
|
|
60
|
+
assert "base_1" in content
|
|
@@ -30,6 +30,8 @@ from context_map.application.commands import (
|
|
|
30
30
|
cmd_weekly,
|
|
31
31
|
cmd_wrap,
|
|
32
32
|
cmd_enrich,
|
|
33
|
+
cmd_pack,
|
|
34
|
+
cmd_unpack,
|
|
33
35
|
)
|
|
34
36
|
from context_map.application.commands.adapt import cmd_adapt
|
|
35
37
|
from context_map.application.commands.export import exportar_contexto
|
|
@@ -121,6 +123,8 @@ def main() -> None:
|
|
|
121
123
|
"personal": cmd_personal,
|
|
122
124
|
"export": cmd_export,
|
|
123
125
|
"enrich": cmd_enrich,
|
|
126
|
+
"pack": cmd_pack,
|
|
127
|
+
"unpack": cmd_unpack,
|
|
124
128
|
"mcp": cmd_mcp,
|
|
125
129
|
}
|
|
126
130
|
|
|
@@ -206,4 +206,12 @@ def create_parser() -> argparse.ArgumentParser:
|
|
|
206
206
|
sp_backup.add_argument("--destino", required=True, help="Ruta destino (ej. /run/media/usb/personal.db)")
|
|
207
207
|
sp_backup.add_argument("--db", default=None, help="Ruta a la BD personal (default: la activa)")
|
|
208
208
|
|
|
209
|
+
p_pack = sub.add_parser("pack", help="Empaqueta el contexto en un archivo comprimido .ctxpack portátil")
|
|
210
|
+
p_pack.add_argument("target", nargs="?", default=".", help="Ruta del proyecto (default: .)")
|
|
211
|
+
p_pack.add_argument("--output", "-o", default=None, help="Ruta del archivo de salida .ctxpack")
|
|
212
|
+
|
|
213
|
+
p_unpack = sub.add_parser("unpack", help="Desempaqueta un archivo .ctxpack y restaura el contexto")
|
|
214
|
+
p_unpack.add_argument("archive", help="Ruta del archivo .ctxpack")
|
|
215
|
+
p_unpack.add_argument("target", nargs="?", default=".", help="Directorio destino para la restauración (default: .)")
|
|
216
|
+
|
|
209
217
|
return p
|
|
@@ -33,8 +33,11 @@ from context_map.application.commands.update import cmd_update
|
|
|
33
33
|
from context_map.application.commands.watch import cmd_watch
|
|
34
34
|
from context_map.application.commands.wrap import cmd_wrap
|
|
35
35
|
from context_map.application.commands.enrich import cmd_enrich
|
|
36
|
+
from context_map.application.commands.pack import cmd_pack, cmd_unpack
|
|
36
37
|
|
|
37
38
|
__all__ = [
|
|
39
|
+
"cmd_pack",
|
|
40
|
+
"cmd_unpack",
|
|
38
41
|
"cmd_enrich",
|
|
39
42
|
"cmd_auto",
|
|
40
43
|
"cmd_build",
|
|
@@ -119,13 +119,38 @@ def project_name(args) -> str:
|
|
|
119
119
|
|
|
120
120
|
|
|
121
121
|
def ensure_dirs(_proj: str | None = None) -> None:
|
|
122
|
-
"""Crea el árbol de directorios de .context-map
|
|
123
|
-
|
|
124
|
-
NOTA: el vault se crea bajo demanda en build/sync, no acá.
|
|
125
|
-
"""
|
|
122
|
+
"""Crea el árbol de directorios de .context-map/ y plantilla de dominios.yaml si no existe."""
|
|
126
123
|
for path in [STATE_DIR, MAPS_DIR, HISTORY_DIR, CHATS_DIR, RAW_DIR]:
|
|
127
124
|
os.makedirs(path, exist_ok=True)
|
|
128
125
|
|
|
126
|
+
dominios_file = os.path.join(CONTEXT_DIR, "dominios.yaml")
|
|
127
|
+
if not os.path.exists(dominios_file):
|
|
128
|
+
try:
|
|
129
|
+
default_template = (
|
|
130
|
+
"# Dominios temáticos del proyecto (grupos profesionales del contexto)\n"
|
|
131
|
+
"# Cada nota se etiqueta automáticamente con grupo-<dominio> según las\n"
|
|
132
|
+
"# palabras clave que aparecen en su título o resumen.\n\n"
|
|
133
|
+
"raiz:\n"
|
|
134
|
+
" - proposito\n"
|
|
135
|
+
" - que es\n"
|
|
136
|
+
" - identidad\n"
|
|
137
|
+
" - esencia\n"
|
|
138
|
+
" - objetivo\n\n"
|
|
139
|
+
"gobierno:\n"
|
|
140
|
+
" - gobierno\n"
|
|
141
|
+
" - verificar\n"
|
|
142
|
+
" - corregir\n"
|
|
143
|
+
" - regla\n\n"
|
|
144
|
+
"vault:\n"
|
|
145
|
+
" - vault\n"
|
|
146
|
+
" - obsidian\n"
|
|
147
|
+
" - nota manual\n"
|
|
148
|
+
)
|
|
149
|
+
with open(dominios_file, "w", encoding="utf-8") as f:
|
|
150
|
+
f.write(default_template)
|
|
151
|
+
except Exception as err:
|
|
152
|
+
logger.debug("No se pudo auto-crear dominios.yaml por defecto: %s", err)
|
|
153
|
+
|
|
129
154
|
|
|
130
155
|
def resolve_vault_mode(args) -> str:
|
|
131
156
|
"""Resuelve el modo de generación del vault desde los argumentos del CLI.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Comandos CLI pack y unpack: empaquetado y desempaquetado de contexto portátil.
|
|
2
|
+
|
|
3
|
+
Permite comprimir la estructura .context-map/ en un archivo .ctxpack
|
|
4
|
+
y restaurarlo 100% offline en cualquier equipo.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import os
|
|
10
|
+
|
|
11
|
+
from context_map.domain.storage.packer import (
|
|
12
|
+
crear_paquete_contexto,
|
|
13
|
+
desempaquetar_contexto,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def cmd_pack(args) -> None:
|
|
18
|
+
"""Ejecuta el empaquetado de contexto en un archivo .ctxpack.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
args: Namespace de argparse con atributos ``target`` y ``output``.
|
|
22
|
+
"""
|
|
23
|
+
target = getattr(args, "target", ".") or "."
|
|
24
|
+
output = getattr(args, "output", None)
|
|
25
|
+
quiet = getattr(args, "quiet", False)
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
ruta_salida, tamanio, manifest = crear_paquete_contexto(
|
|
29
|
+
target_dir=target,
|
|
30
|
+
output_path=output,
|
|
31
|
+
)
|
|
32
|
+
if not quiet:
|
|
33
|
+
kb = tamanio / 1024.0
|
|
34
|
+
print(f"🗜️ [pack] Contexto empaquetado exitosamente en: {ruta_salida} ({kb:.1f} KB)")
|
|
35
|
+
print(f" · Proyecto: {manifest.get('project')} | Nodos: {manifest.get('node_count')} | Formato: {manifest.get('format')}")
|
|
36
|
+
except Exception as err:
|
|
37
|
+
if not quiet:
|
|
38
|
+
print(f"❌ [pack] Error al empaquetar contexto: {err}")
|
|
39
|
+
raise
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def cmd_unpack(args) -> None:
|
|
43
|
+
"""Ejecuta el desempaquetado de un archivo .ctxpack.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
args: Namespace de argparse con atributos ``archive`` y ``target``.
|
|
47
|
+
"""
|
|
48
|
+
archive = getattr(args, "archive", None)
|
|
49
|
+
target = getattr(args, "target", ".") or "."
|
|
50
|
+
quiet = getattr(args, "quiet", False)
|
|
51
|
+
|
|
52
|
+
if not archive:
|
|
53
|
+
if not quiet:
|
|
54
|
+
print("❌ [unpack] Debe especificar la ruta del archivo .ctxpack")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
manifest = desempaquetar_contexto(
|
|
59
|
+
archive_path=archive,
|
|
60
|
+
target_dir=target,
|
|
61
|
+
)
|
|
62
|
+
if not quiet:
|
|
63
|
+
print(f"📦 [unpack] Contexto desempaquetado exitosamente en: {os.path.abspath(target)}")
|
|
64
|
+
print(f" · Proyecto: {manifest.get('project', 'Restaurado')} | Nodos: {manifest.get('node_count', 0)} | Generado: {manifest.get('timestamp', 'N/A')}")
|
|
65
|
+
except Exception as err:
|
|
66
|
+
if not quiet:
|
|
67
|
+
print(f"❌ [unpack] Error al desempaquetar contexto: {err}")
|
|
68
|
+
raise
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""Analizador sintáctico AST de seguridad y calidad de código local para ContextMap.
|
|
2
|
+
|
|
3
|
+
Ejecuta verificaciones determinísticas sin internet ni dependencias externas sobre:
|
|
4
|
+
- Uso inseguro de eval() y exec()
|
|
5
|
+
- Supresión silenciosa de excepciones (except Exception: pass)
|
|
6
|
+
- Concatenación de cadenas en consultas SQL (cursor.execute)
|
|
7
|
+
- Apertura de archivos sin administrador de contexto (with open)
|
|
8
|
+
- Claves o contraseñas hardcodadas en código
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import ast
|
|
14
|
+
import os
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class AlertaAuditoria:
|
|
21
|
+
"""Alerta de calidad o seguridad detectada por el analizador AST.
|
|
22
|
+
|
|
23
|
+
Attributes:
|
|
24
|
+
archivo (str): Ruta del archivo inspeccionado.
|
|
25
|
+
linea (int): Número de línea de la anomalía.
|
|
26
|
+
categoria (str): Categoría ('SEGURIDAD', 'ROBUSTEZ', 'RECURSOS').
|
|
27
|
+
mensaje (str): Descripción detallada del hallazgo.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
archivo: str
|
|
31
|
+
linea: int
|
|
32
|
+
categoria: str
|
|
33
|
+
mensaje: str
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class AuditVisitor(ast.NodeVisitor):
|
|
37
|
+
"""Visitor de AST que inspecciona patrones de calidad y seguridad."""
|
|
38
|
+
|
|
39
|
+
def __init__(self, filepath: str) -> None:
|
|
40
|
+
self.filepath = filepath
|
|
41
|
+
self.alertas: List[AlertaAuditoria] = []
|
|
42
|
+
|
|
43
|
+
def visit_Call(self, node: ast.Call) -> None:
|
|
44
|
+
# Detectar eval() y exec()
|
|
45
|
+
if isinstance(node.func, ast.Name) and node.func.id in ("eval", "exec"):
|
|
46
|
+
self.alertas.append(
|
|
47
|
+
AlertaAuditoria(
|
|
48
|
+
archivo=self.filepath,
|
|
49
|
+
linea=node.lineno,
|
|
50
|
+
categoria="SEGURIDAD",
|
|
51
|
+
mensaje=f"Uso inseguro de ejecutor dinámico '{node.func.id}()'.",
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
# Detectar cursor.execute con f-strings o concatenación
|
|
56
|
+
if isinstance(node.func, ast.Attribute) and node.func.attr in ("execute", "executemany"):
|
|
57
|
+
if node.args:
|
|
58
|
+
primer_arg = node.args[0]
|
|
59
|
+
if isinstance(primer_arg, (ast.JoinedStr, ast.BinOp)):
|
|
60
|
+
self.alertas.append(
|
|
61
|
+
AlertaAuditoria(
|
|
62
|
+
archivo=self.filepath,
|
|
63
|
+
linea=node.lineno,
|
|
64
|
+
categoria="SEGURIDAD",
|
|
65
|
+
mensaje="Posible inyección SQL: consulta formateada con cadenas dinámicas en execute().",
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# Detectar open() suelto fuera de with
|
|
70
|
+
if isinstance(node.func, ast.Name) and node.func.id == "open":
|
|
71
|
+
# Si no está dentro de un ast.With, advertir consumo potencial de descriptor
|
|
72
|
+
self.alertas.append(
|
|
73
|
+
AlertaAuditoria(
|
|
74
|
+
archivo=self.filepath,
|
|
75
|
+
linea=node.lineno,
|
|
76
|
+
categoria="RECURSOS",
|
|
77
|
+
mensaje="Uso de open() directo. Se recomienda usar 'with open(...)' para garantizar el cierre del archivo.",
|
|
78
|
+
)
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
self.generic_visit(node)
|
|
82
|
+
|
|
83
|
+
def visit_ExceptHandler(self, node: ast.ExceptHandler) -> None:
|
|
84
|
+
# Detectar except Exception: pass o except: pass
|
|
85
|
+
if len(node.body) == 1 and isinstance(node.body[0], ast.Pass):
|
|
86
|
+
self.alertas.append(
|
|
87
|
+
AlertaAuditoria(
|
|
88
|
+
archivo=self.filepath,
|
|
89
|
+
linea=node.lineno,
|
|
90
|
+
categoria="ROBUSTEZ",
|
|
91
|
+
mensaje="Bloque 'except' que ignora excepciones silenciosamente con 'pass'.",
|
|
92
|
+
)
|
|
93
|
+
)
|
|
94
|
+
self.generic_visit(node)
|
|
95
|
+
|
|
96
|
+
def visit_Assign(self, node: ast.Assign) -> None:
|
|
97
|
+
# Detectar contraseñas hardcodadas
|
|
98
|
+
for target in node.targets:
|
|
99
|
+
if isinstance(target, ast.Name):
|
|
100
|
+
nombre_var = target.id.lower()
|
|
101
|
+
if any(k in nombre_var for k in ["password", "secret_key", "api_key", "auth_token"]):
|
|
102
|
+
if isinstance(node.value, ast.Constant) and isinstance(node.value.value, str):
|
|
103
|
+
val = node.value.value
|
|
104
|
+
if len(val) > 4 and val not in ("placeholder", "change_me", "dummy"):
|
|
105
|
+
self.alertas.append(
|
|
106
|
+
AlertaAuditoria(
|
|
107
|
+
archivo=self.filepath,
|
|
108
|
+
linea=node.lineno,
|
|
109
|
+
categoria="SEGURIDAD",
|
|
110
|
+
mensaje=f"Posible credencial hardcodada en la variable '{target.id}'.",
|
|
111
|
+
)
|
|
112
|
+
)
|
|
113
|
+
self.generic_visit(node)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def auditar_archivo_python(filepath: str) -> List[AlertaAuditoria]:
|
|
117
|
+
"""Audita un archivo Python individual mediante análisis AST.
|
|
118
|
+
|
|
119
|
+
Args:
|
|
120
|
+
filepath: Ruta del archivo Python.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Lista de AlertaAuditoria encontradas.
|
|
124
|
+
"""
|
|
125
|
+
if not os.path.isfile(filepath) or not filepath.endswith(".py"):
|
|
126
|
+
return []
|
|
127
|
+
|
|
128
|
+
try:
|
|
129
|
+
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
|
130
|
+
tree = ast.parse(f.read(), filename=filepath)
|
|
131
|
+
visitor = AuditVisitor(filepath)
|
|
132
|
+
visitor.visit(tree)
|
|
133
|
+
return visitor.alertas
|
|
134
|
+
except Exception:
|
|
135
|
+
return []
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
def auditar_proyecto_python(target_dir: str = ".") -> List[AlertaAuditoria]:
|
|
139
|
+
"""Escanea y audita sintácticamente todos los archivos Python del proyecto.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
target_dir: Directorio raíz del proyecto.
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
Lista consolidada de AlertaAuditoria del proyecto.
|
|
146
|
+
"""
|
|
147
|
+
alertas: List[AlertaAuditoria] = []
|
|
148
|
+
for raiz, _, archivos in os.walk(target_dir):
|
|
149
|
+
if any(ign in raiz for ign in [".git", "venv", ".venv", "__pycache__", ".context-map"]):
|
|
150
|
+
continue
|
|
151
|
+
for f in archivos:
|
|
152
|
+
if f.endswith(".py"):
|
|
153
|
+
r = os.path.join(raiz, f)
|
|
154
|
+
alertas.extend(auditar_archivo_python(r))
|
|
155
|
+
return alertas
|