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.
Files changed (188) hide show
  1. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/copilot-instructions.md +1 -1
  2. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/CHANGELOG.md +10 -0
  3. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/PKG-INFO +16 -8
  4. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/README.md +15 -7
  5. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/README_EN.md +18 -10
  6. context_map_ai-2.2.0/context_map/__tests__/test_ast_audit.py +38 -0
  7. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_doctor.py +6 -1
  8. context_map_ai-2.2.0/context_map/__tests__/test_pack_unpack.py +60 -0
  9. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/cli/cli.py +4 -0
  10. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/cli/parser.py +8 -0
  11. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/__init__.py +3 -0
  12. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/_helpers.py +29 -4
  13. context_map_ai-2.2.0/context_map/application/commands/pack.py +68 -0
  14. context_map_ai-2.2.0/context_map/domain/analyzers/ast_audit.py +155 -0
  15. context_map_ai-2.2.0/context_map/domain/storage/packer.py +147 -0
  16. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/ollama.py +28 -1
  17. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/pyproject.toml +1 -1
  18. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  19. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  20. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  21. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/workflows/ci.yml +0 -0
  22. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/workflows/docs.yml +0 -0
  23. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.github/workflows/publish.yml +0 -0
  24. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.gitignore +0 -0
  25. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/config.yaml +0 -0
  26. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/shields/pre-commit.md +0 -0
  27. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/triggers/post-commit.md +0 -0
  28. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.hermes/workflows/dev-loop.md +0 -0
  29. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/.pre-commit-hooks.yaml +0 -0
  30. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/AGENTS.md +0 -0
  31. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/CODE_OF_CONDUCT.md +0 -0
  32. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/CONTRIBUTING.md +0 -0
  33. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/LICENSE +0 -0
  34. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/Makefile +0 -0
  35. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/action.yml +0 -0
  36. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__init__.py +0 -0
  37. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/conftest.py +0 -0
  38. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/smoke.py +0 -0
  39. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ast_summary.py +0 -0
  40. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_checker_frescura.py +0 -0
  41. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_cli.py +0 -0
  42. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_conexiones.py +0 -0
  43. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_cyclomatic.py +0 -0
  44. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_diario_consolidado.py +0 -0
  45. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_domain_extractor.py +0 -0
  46. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ecosistema.py +0 -0
  47. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_enterprise_features.py +0 -0
  48. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_export.py +0 -0
  49. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hardware.py +0 -0
  50. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
  51. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
  52. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_hooks.py +0 -0
  53. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ingest.py +0 -0
  54. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_mermaid.py +0 -0
  55. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
  56. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_ollama.py +0 -0
  57. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_parsing.py +0 -0
  58. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_personal.py +0 -0
  59. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_refresh_temporales.py +0 -0
  60. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_reglas_catalogo.py +0 -0
  61. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_scan_cache.py +0 -0
  62. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_scanner.py +0 -0
  63. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_security_scanner.py +0 -0
  64. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_similarity.py +0 -0
  65. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_standardize.py +0 -0
  66. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_tokenization.py +0 -0
  67. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_topologia_arbol.py +0 -0
  68. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_vault_consolidation.py +0 -0
  69. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_watcher.py +0 -0
  70. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/__tests__/test_wrap.py +0 -0
  71. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/__init__.py +0 -0
  72. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/cli/__init__.py +0 -0
  73. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/adapt.py +0 -0
  74. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/auto.py +0 -0
  75. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/build.py +0 -0
  76. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/doctor_cmd.py +0 -0
  77. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/enrich.py +0 -0
  78. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/export.py +0 -0
  79. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/hook.py +0 -0
  80. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/importers.py +0 -0
  81. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/ingest.py +0 -0
  82. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/personal.py +0 -0
  83. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/refresh.py +0 -0
  84. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/scan.py +0 -0
  85. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/sync.py +0 -0
  86. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/tools.py +0 -0
  87. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/update.py +0 -0
  88. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/watch.py +0 -0
  89. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/application/commands/wrap.py +0 -0
  90. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/cli.py +0 -0
  91. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/__init__.py +0 -0
  92. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/generators/__init__.py +0 -0
  93. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/generators/generadores.py +0 -0
  94. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/generators/narrative_templates.py +0 -0
  95. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/logging_setup.py +0 -0
  96. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/models/__init__.py +0 -0
  97. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/models/config.py +0 -0
  98. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/models/models.py +0 -0
  99. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/__init__.py +0 -0
  100. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/cleaning.py +0 -0
  101. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/inference.py +0 -0
  102. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/mappings.py +0 -0
  103. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/normalization/standardize.py +0 -0
  104. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/__init__.py +0 -0
  105. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/cargadores.py +0 -0
  106. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/clasificacion.py +0 -0
  107. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/dedup.py +0 -0
  108. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/parsing/grafo.py +0 -0
  109. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/personal/__init__.py +0 -0
  110. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/personal/bd.py +0 -0
  111. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/storage/__init__.py +0 -0
  112. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/storage/config_loader.py +0 -0
  113. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/storage/store.py +0 -0
  114. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/core/tokenization.py +0 -0
  115. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/__init__.py +0 -0
  116. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/__init__.py +0 -0
  117. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/checker.py +0 -0
  118. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/complexity.py +0 -0
  119. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/models.py +0 -0
  120. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/reporting.py +0 -0
  121. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analysis/signals.py +0 -0
  122. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analyzers/ast_summary.py +0 -0
  123. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/analyzers/cyclomatic.py +0 -0
  124. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/__init__.py +0 -0
  125. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/adaptador.py +0 -0
  126. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/detector.py +0 -0
  127. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/hooks.py +0 -0
  128. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ecosystem/rules_templates.py +0 -0
  129. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/health/__init__.py +0 -0
  130. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/health/doctor.py +0 -0
  131. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/health/hardware.py +0 -0
  132. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ingestion/__init__.py +0 -0
  133. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/ingestion/ingest.py +0 -0
  134. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/normalization/domain_extractor.py +0 -0
  135. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/normalization/similarity.py +0 -0
  136. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/reglas/reglas.py +0 -0
  137. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/reporting/__init__.py +0 -0
  138. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/reporting/reporter.py +0 -0
  139. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/__init__.py +0 -0
  140. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/cache.py +0 -0
  141. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/scanner.py +0 -0
  142. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/scanning/security.py +0 -0
  143. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/__init__.py +0 -0
  144. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/relaciones.py +0 -0
  145. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/sync.py +0 -0
  146. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/domain/synchronization/watcher.py +0 -0
  147. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/__init__.py +0 -0
  148. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/analyzers/__init__.py +0 -0
  149. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/analyzers/content.py +0 -0
  150. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/analyzers/structure.py +0 -0
  151. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/__init__.py +0 -0
  152. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/antigravity.py +0 -0
  153. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/chat_export.py +0 -0
  154. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/git.py +0 -0
  155. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/integrations/hermes.py +0 -0
  156. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/mcp_server.py +0 -0
  157. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/infrastructure/version_check.py +0 -0
  158. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/__init__.py +0 -0
  159. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/__init__.py +0 -0
  160. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/agents.py +0 -0
  161. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/brief.py +0 -0
  162. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/extractors.py +0 -0
  163. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/sections.py +0 -0
  164. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/briefs/skill.py +0 -0
  165. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/__init__.py +0 -0
  166. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/atomic.py +0 -0
  167. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/__init__.py +0 -0
  168. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/canvas.py +0 -0
  169. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/common.py +0 -0
  170. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
  171. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
  172. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
  173. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/rutas.py +0 -0
  174. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
  175. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
  176. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_historial.py +0 -0
  177. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
  178. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
  179. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
  180. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/mermaid.py +0 -0
  181. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/preservar.py +0 -0
  182. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/templates.py +0 -0
  183. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/context_map/presentation/vault/writer.py +0 -0
  184. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/docs/LAUNCH_KIT.md +0 -0
  185. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
  186. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
  187. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/mkdocs.yml +0 -0
  188. {context_map_ai-2.1.0 → context_map_ai-2.2.0}/uv.lock +0 -0
@@ -18,7 +18,7 @@
18
18
 
19
19
  # GitHub Copilot — Instrucciones para ContextMap
20
20
 
21
- > Generado automáticamente por **ContextMap** (2026-08-18).
21
+ > Generado automáticamente por **ContextMap** (2026-08-19).
22
22
 
23
23
  ## Antes de sugerir código
24
24
 
@@ -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.1.0
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
- [![Release](https://img.shields.io/badge/version-v2.1.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
44
+ [![Release](https://img.shields.io/badge/version-v2.2.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
45
+ [![PyPI](https://img.shields.io/pypi/v/context-map-ai.svg?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/context-map-ai/)
45
46
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
46
47
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE)
47
48
  [![Readiness](https://img.shields.io/badge/Readiness-100%2F100-brightgreen.svg?style=for-the-badge)](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
- - ⚡ **Quick Install:** `uv tool install git+https://github.com/kudawasama/ContextMap.git`
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
- # Recomendado: instalación directa en 1 comando (requiere https://docs.astral.sh/uv/)
92
- uv tool install git+https://github.com/kudawasama/ContextMap.git
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
- # O con pip desde un clon local:
95
- # git clone https://github.com/kudawasama/ContextMap.git && cd ContextMap && pip install -e .
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
- [![Release](https://img.shields.io/badge/version-v2.1.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
12
+ [![Release](https://img.shields.io/badge/version-v2.2.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
13
+ [![PyPI](https://img.shields.io/pypi/v/context-map-ai.svg?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/context-map-ai/)
13
14
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
14
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE)
15
16
  [![Readiness](https://img.shields.io/badge/Readiness-100%2F100-brightgreen.svg?style=for-the-badge)](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
- - ⚡ **Quick Install:** `uv tool install git+https://github.com/kudawasama/ContextMap.git`
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
- # Recomendado: instalación directa en 1 comando (requiere https://docs.astral.sh/uv/)
60
- uv tool install git+https://github.com/kudawasama/ContextMap.git
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
- # O con pip desde un clon local:
63
- # git clone https://github.com/kudawasama/ContextMap.git && cd ContextMap && pip install -e .
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
- [![Release](https://img.shields.io/badge/version-v2.1.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
12
+ [![Release](https://img.shields.io/badge/version-v2.2.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
13
+ [![PyPI](https://img.shields.io/pypi/v/context-map-ai.svg?style=for-the-badge&logo=pypi&logoColor=white)](https://pypi.org/project/context-map-ai/)
13
14
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
14
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE)
15
16
  [![Readiness](https://img.shields.io/badge/Readiness-100%2F100-brightgreen.svg?style=for-the-badge)](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 models frequently forget past architectural decisions, miss core project rules, or propose blind refactorings that break your codebase.
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 domain, and scope).
33
- - **What risks does it face?** (Static complexity hotspots, warnings, and fragile areas).
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 architectural decisions were made?** (Living memory of past sessions and commit history).
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 (with `uv`)
44
+ ### 1. Global Installation (via `pip` or `uv`)
44
45
 
45
46
  ```bash
46
- # Recommended: 1-command direct install (requires https://docs.astral.sh/uv/)
47
- uv tool install git+https://github.com/kudawasama/ContextMap.git
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
- # Or with pip from local clone:
50
- # git clone https://github.com/kudawasama/ContextMap.git && cd ContextMap && pip install -e .
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