context-map-ai 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (195) hide show
  1. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/copilot-instructions.md +1 -1
  2. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/workflows/docs.yml +3 -1
  3. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/workflows/publish.yml +14 -0
  4. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.pre-commit-hooks.yaml +1 -1
  5. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/CHANGELOG.md +38 -0
  6. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/PKG-INFO +6 -3
  7. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/README.md +1 -1
  8. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/README_EN.md +1 -1
  9. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ast_summary.py +0 -1
  10. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_cyclomatic.py +0 -1
  11. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_doctor.py +5 -6
  12. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_domain_extractor.py +1 -3
  13. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ecosistema.py +3 -1
  14. context_map_ai-2.3.0/context_map/__tests__/test_enrich.py +68 -0
  15. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_enterprise_features.py +1 -1
  16. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hardware.py +1 -2
  17. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hooks.py +0 -1
  18. context_map_ai-2.3.0/context_map/__tests__/test_mcp_server_seguridad.py +31 -0
  19. context_map_ai-2.3.0/context_map/__tests__/test_ollama.py +29 -0
  20. context_map_ai-2.3.0/context_map/__tests__/test_pack_unpack.py +135 -0
  21. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_scan_cache.py +1 -1
  22. context_map_ai-2.3.0/context_map/__tests__/test_security.py +65 -0
  23. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_security_scanner.py +1 -0
  24. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_similarity.py +1 -2
  25. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_tokenization.py +0 -1
  26. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_wrap.py +0 -1
  27. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/cli/parser.py +10 -1
  28. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/doctor_cmd.py +3 -6
  29. context_map_ai-2.3.0/context_map/application/commands/enrich.py +162 -0
  30. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/export.py +5 -6
  31. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/hook.py +7 -3
  32. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/personal.py +32 -9
  33. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/refresh.py +1 -0
  34. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/update.py +101 -15
  35. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/watch.py +2 -2
  36. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/__init__.py +0 -1
  37. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/generators/generadores.py +16 -0
  38. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/tokenization.py +2 -5
  39. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/checker.py +20 -0
  40. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/reporting.py +1 -0
  41. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/signals.py +0 -1
  42. context_map_ai-2.3.0/context_map/domain/analyzers/__init__.py +31 -0
  43. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analyzers/ast_audit.py +21 -18
  44. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analyzers/ast_summary.py +7 -8
  45. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analyzers/cyclomatic.py +85 -7
  46. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/adaptador.py +4 -8
  47. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/hooks.py +3 -3
  48. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/health/doctor.py +2 -4
  49. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/health/hardware.py +2 -3
  50. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/normalization/domain_extractor.py +3 -4
  51. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/normalization/similarity.py +6 -5
  52. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/reglas/reglas.py +1 -1
  53. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/cache.py +5 -5
  54. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/security.py +60 -7
  55. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/storage/packer.py +70 -29
  56. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/watcher.py +2 -2
  57. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/content.py +1 -1
  58. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/hermes.py +4 -2
  59. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/ollama.py +27 -7
  60. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/mcp_server.py +85 -16
  61. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/version_check.py +11 -4
  62. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/brief.py +0 -2
  63. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/extractors.py +1 -1
  64. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/sections.py +1 -1
  65. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_historial.py +11 -11
  66. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/pyproject.toml +5 -2
  67. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/uv.lock +3 -3
  68. context_map_ai-2.2.0/context_map/__tests__/test_ollama.py +0 -14
  69. context_map_ai-2.2.0/context_map/__tests__/test_pack_unpack.py +0 -60
  70. context_map_ai-2.2.0/context_map/application/commands/enrich.py +0 -86
  71. context_map_ai-2.2.0/context_map/domain/analysis/complexity.py +0 -134
  72. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  73. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  74. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  75. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.github/workflows/ci.yml +0 -0
  76. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.gitignore +0 -0
  77. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/config.yaml +0 -0
  78. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/shields/pre-commit.md +0 -0
  79. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/triggers/post-commit.md +0 -0
  80. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/.hermes/workflows/dev-loop.md +0 -0
  81. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/AGENTS.md +0 -0
  82. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/CODE_OF_CONDUCT.md +0 -0
  83. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/CONTRIBUTING.md +0 -0
  84. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/LICENSE +0 -0
  85. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/Makefile +0 -0
  86. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/action.yml +0 -0
  87. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__init__.py +0 -0
  88. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/conftest.py +0 -0
  89. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/smoke.py +0 -0
  90. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ast_audit.py +0 -0
  91. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_checker_frescura.py +1 -1
  92. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_cli.py +0 -0
  93. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_conexiones.py +0 -0
  94. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_diario_consolidado.py +0 -0
  95. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_export.py +1 -1
  96. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
  97. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
  98. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_ingest.py +0 -0
  99. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_mermaid.py +0 -0
  100. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
  101. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_parsing.py +0 -0
  102. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_personal.py +0 -0
  103. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_refresh_temporales.py +0 -0
  104. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_reglas_catalogo.py +0 -0
  105. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_scanner.py +0 -0
  106. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_standardize.py +0 -0
  107. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_topologia_arbol.py +0 -0
  108. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_vault_consolidation.py +0 -0
  109. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/__tests__/test_watcher.py +0 -0
  110. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/__init__.py +0 -0
  111. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/cli/__init__.py +0 -0
  112. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/cli/cli.py +3 -3
  113. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/__init__.py +2 -2
  114. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/_helpers.py +0 -0
  115. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/adapt.py +0 -0
  116. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/auto.py +0 -0
  117. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/build.py +0 -0
  118. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/importers.py +0 -0
  119. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/ingest.py +0 -0
  120. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/pack.py +0 -0
  121. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/scan.py +0 -0
  122. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/sync.py +0 -0
  123. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/tools.py +0 -0
  124. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/application/commands/wrap.py +0 -0
  125. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/cli.py +0 -0
  126. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/generators/__init__.py +0 -0
  127. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/generators/narrative_templates.py +0 -0
  128. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/logging_setup.py +0 -0
  129. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/models/__init__.py +0 -0
  130. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/models/config.py +0 -0
  131. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/models/models.py +0 -0
  132. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/__init__.py +0 -0
  133. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/cleaning.py +0 -0
  134. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/inference.py +0 -0
  135. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/mappings.py +0 -0
  136. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/normalization/standardize.py +0 -0
  137. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/__init__.py +0 -0
  138. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/cargadores.py +0 -0
  139. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/clasificacion.py +0 -0
  140. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/dedup.py +0 -0
  141. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/parsing/grafo.py +0 -0
  142. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/personal/__init__.py +0 -0
  143. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/personal/bd.py +0 -0
  144. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/storage/__init__.py +0 -0
  145. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/storage/config_loader.py +0 -0
  146. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/core/storage/store.py +0 -0
  147. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/__init__.py +0 -0
  148. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/__init__.py +0 -0
  149. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/analysis/models.py +0 -0
  150. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/__init__.py +0 -0
  151. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/detector.py +0 -0
  152. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ecosystem/rules_templates.py +0 -0
  153. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/health/__init__.py +0 -0
  154. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ingestion/__init__.py +0 -0
  155. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/ingestion/ingest.py +0 -0
  156. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/reporting/__init__.py +0 -0
  157. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/reporting/reporter.py +0 -0
  158. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/__init__.py +0 -0
  159. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/scanning/scanner.py +0 -0
  160. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/__init__.py +0 -0
  161. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/relaciones.py +0 -0
  162. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/domain/synchronization/sync.py +0 -0
  163. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/__init__.py +0 -0
  164. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/__init__.py +0 -0
  165. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/analyzers/structure.py +0 -0
  166. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/__init__.py +0 -0
  167. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/antigravity.py +0 -0
  168. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/chat_export.py +0 -0
  169. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/infrastructure/integrations/git.py +0 -0
  170. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/__init__.py +0 -0
  171. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/__init__.py +0 -0
  172. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/agents.py +0 -0
  173. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/briefs/skill.py +0 -0
  174. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/__init__.py +0 -0
  175. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/atomic.py +0 -0
  176. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/__init__.py +0 -0
  177. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/canvas.py +0 -0
  178. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/common.py +0 -0
  179. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
  180. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
  181. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
  182. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/rutas.py +0 -0
  183. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
  184. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
  185. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
  186. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
  187. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
  188. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/mermaid.py +0 -0
  189. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/preservar.py +0 -0
  190. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/templates.py +0 -0
  191. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/context_map/presentation/vault/writer.py +0 -0
  192. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/docs/LAUNCH_KIT.md +0 -0
  193. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
  194. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
  195. {context_map_ai-2.2.0 → context_map_ai-2.3.0}/mkdocs.yml +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-19).
21
+ > Generado automáticamente por **ContextMap** (2026-08-21).
22
22
 
23
23
  ## Antes de sugerir código
24
24
 
@@ -7,11 +7,13 @@ on:
7
7
  - main
8
8
 
9
9
  permissions:
10
- contents: write
10
+ contents: read
11
11
 
12
12
  jobs:
13
13
  deploy:
14
14
  runs-on: ubuntu-latest
15
+ permissions:
16
+ contents: write
15
17
  steps:
16
18
  - uses: actions/checkout@v4
17
19
 
@@ -40,3 +40,17 @@ jobs:
40
40
  uses: pypa/gh-action-pypi-publish@release/v1
41
41
  with:
42
42
  skip-existing: true
43
+
44
+ github-release:
45
+ name: Create GitHub Release
46
+ needs: pypi-publish
47
+ if: startsWith(github.ref, 'refs/tags/v')
48
+ runs-on: ubuntu-latest
49
+ permissions:
50
+ contents: write
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - name: Create Release
54
+ uses: softprops/action-gh-release@v2
55
+ with:
56
+ generate_release_notes: true
@@ -1,7 +1,7 @@
1
1
  - id: context-map
2
2
  name: ContextMap Auto Sync
3
3
  description: Escanea y actualiza automáticamente la bóveda de Obsidian y briefs de ContextMap antes de cada commit.
4
- entry: ctxmap build --clean --brief --quiet
4
+ entry: python -m context_map.cli build --clean --brief --quiet
5
5
  language: python
6
6
  stages: [pre-commit]
7
7
  types: [python, markdown, yaml, json]
@@ -5,6 +5,44 @@ Todas las notas de versión y cambios destacables en este proyecto serán docume
5
5
  El formato está basado en [Keep a Changelog](https://keepachangelog.com/es-ES/1.0.0/),
6
6
  y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.3.0] — 2026-08-21
9
+
10
+ ### 🛡️ Novedades y Mejoras — Escáner de Secretos Avanzado, Entropía Shannon, Opt-Out Ollama & Automatización Release
11
+
12
+ - **feat(`security`)**: Detección ampliada de credenciales con soporte para Slack tokens (`xoxb-`), Stripe keys (`sk_live_`, `sk_test_`), Twilio SIDs (`AC`, `SK`), SendGrid (`SG.`), npm access tokens (`npm_`) y webhooks de Discord.
13
+ - **feat(`security`)**: Análisis matemático de alta entropía de Shannon (`calcular_entropia_shannon`) en asignaciones de variables sensibles para detectar llaves criptográficas no estructuradas.
14
+ - **feat(`ollama`)**: Soporte de opt-out mediante flag `--no-ollama` en `ctxmap enrich` y variable de entorno `CTXMAP_NO_OLLAMA=1` para forzar análisis puramente offline y estático AST.
15
+ - **feat(`ci/cd`)**: Creación automática de GitHub Releases (`softprops/action-gh-release@v2`) con notas generadas al publicar tags en `publish.yml`, y principio de mínimo privilegio en permisos de `docs.yml`.
16
+ - **feat(`personal`)**: Soporte para la variable `CTXMAP_GDRIVE_ROOTS` en `personal sync` y optimización de búsqueda acotada de unidades en Windows.
17
+
18
+ ---
19
+
20
+ ## [2.2.2] — 2026-08-21
21
+
22
+ ### 🔧 Correcciones y Hardening — Saneamiento Técnico y Parche
23
+
24
+ - **fix(`enrich`)**: Persistencia real en disco de los docstrings generados en `ctxmap enrich` vía `filepath.write_text` con formateo adecuado de indentación y soporte completo para flag `--dry-run` de previsualización sin alterar archivos.
25
+ - **refactor(`complexity`/`cyclomatic`)**: Eliminación de la duplicación de código entre `domain/analysis/complexity.py` y `domain/analyzers/cyclomatic.py`. Consolidación unificada de métricas ciclomáticas de McCabe en `domain/analyzers/cyclomatic.py` (con soporte AST completo para `AsyncFor`, `AsyncWith`, `Assert`, `IfExp`, `BoolOp`).
26
+ - **security(`adaptador`)**: Endurecimiento de `_es_generado_ctxmap` para exigir estrictamente el marcador `CONTEXTMAP:BEGIN` evitando falsos positivos con archivos de usuario.
27
+ - **security(`hermes`)**: Parametrización segura de cláusulas `LIMIT ?` en SQLite para defensa en profundidad.
28
+ - **devops(`pre-commit`)**: Actualización de `.pre-commit-hooks.yaml` para invocar el módulo local `python -m context_map.cli` en lugar del binario global.
29
+ - **docs(`domain_extractor`)**: Corrección de docstrings clarificando análisis de frecuencia léxica local en lugar de TF-IDF.
30
+
31
+ ---
32
+
33
+ ## [2.2.1] — 2026-08-21
34
+
35
+ ### 🔧 Correcciones — Saneamiento de release
36
+
37
+ - **fix: flag `--version` real**: `ctxmap --version` ahora imprime la versión instalada (antes no existía y `update` mostraba un mensaje hardcodeado).
38
+ - **fix: detección de versión con el paquete renombrado**: `version_check` prioriza `context-map-ai` (nombre canónico actual) sobre el heredado `context-map`, eliminando avisos falsos de actualización.
39
+ - **fix: lint (ruff) 100% limpio**: corregidos 11 errores (`F811` redefinición en `brief.py`, `F841` variables sin usar, `SIM102/105/108`).
40
+ - **style: ruff --fix e isort** aplicados en todo el paquete (anotaciones modernas `dict`/`| None` y orden de imports).
41
+
42
+ Suite: 176/176 tests verdes · ruff limpio.
43
+
44
+ ---
45
+
8
46
  ## [2.2.0] — 2026-08-19
9
47
 
10
48
  ### 🗜️ Nuevo — Paquetes de Contexto Portátiles Offline, Escáner de Auditoría AST & Fallback de Modelos
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: context-map-ai
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Mapa mental narrativo de proyectos para agentes de IA
5
5
  Project-URL: Homepage, https://github.com/kudawasama/ContextMap
6
6
  Project-URL: Repository, https://github.com/kudawasama/ContextMap
@@ -9,9 +9,12 @@ Author-email: kudawasama <cespedes.jac@gmail.com>
9
9
  License-Expression: MIT
10
10
  License-File: LICENSE
11
11
  Keywords: agent,ai,context,mindmap,obsidian
12
- Classifier: Development Status :: 4 - Beta
12
+ Classifier: Development Status :: 5 - Production/Stable
13
13
  Classifier: Intended Audience :: Developers
14
14
  Classifier: License :: OSI Approved :: MIT License
15
+ Classifier: Natural Language :: English
16
+ Classifier: Natural Language :: Spanish
17
+ Classifier: Operating System :: OS Independent
15
18
  Classifier: Programming Language :: Python :: 3
16
19
  Classifier: Programming Language :: Python :: 3.10
17
20
  Classifier: Programming Language :: Python :: 3.11
@@ -41,7 +44,7 @@ Description-Content-Type: text/markdown
41
44
 
42
45
  *Captura el alma de tu proyecto, establece gobernanza automática y mantiene vivo el contexto para cualquier Agente de IA (`Antigravity`, `Cursor`, `Claude`, `Hermes`, `Copilot`, `Windsurf`, `Gemini`).*
43
46
 
44
- [![Release](https://img.shields.io/badge/version-v2.2.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
47
+ [![Release](https://img.shields.io/badge/version-v2.2.1-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
45
48
  [![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/)
46
49
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](https://www.python.org/downloads/)
47
50
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE)
@@ -9,7 +9,7 @@
9
9
 
10
10
  *Captura el alma de tu proyecto, establece gobernanza automática y mantiene vivo el contexto para cualquier Agente de IA (`Antigravity`, `Cursor`, `Claude`, `Hermes`, `Copilot`, `Windsurf`, `Gemini`).*
11
11
 
12
- [![Release](https://img.shields.io/badge/version-v2.2.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
12
+ [![Release](https://img.shields.io/badge/version-v2.2.1-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
13
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/)
14
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/)
15
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE)
@@ -9,7 +9,7 @@
9
9
 
10
10
  *Capture the soul of your project, establish automated governance, and keep living context available for any AI Agent (`Antigravity`, `Cursor`, `Claude Code`, `Hermes`, `Copilot`, `Windsurf`, `Gemini`).*
11
11
 
12
- [![Release](https://img.shields.io/badge/version-v2.2.0-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
12
+ [![Release](https://img.shields.io/badge/version-v2.2.1-blue.svg?style=for-the-badge)](https://github.com/kudawasama/ContextMap)
13
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/)
14
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/)
15
15
  [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?style=for-the-badge)](LICENSE)
@@ -1,6 +1,5 @@
1
1
  """Pruebas unitarias para el módulo context_map.domain.analyzers.ast_summary."""
2
2
 
3
- import pytest
4
3
  from context_map.domain.analyzers.ast_summary import ASTSummaryExtractor
5
4
 
6
5
 
@@ -1,6 +1,5 @@
1
1
  """Pruebas unitarias para el módulo context_map.domain.analyzers.cyclomatic."""
2
2
 
3
- import pytest
4
3
  from context_map.domain.analyzers.cyclomatic import calcular_complejidad_ciclomatica
5
4
 
6
5
 
@@ -3,7 +3,8 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import os
6
- from context_map.domain.health.doctor import diagnosticar_salud, reparar_salud, run
6
+
7
+ from context_map.domain.health.doctor import diagnosticar_salud, reparar_salud
7
8
  from context_map.presentation.briefs.extractors import vault_nombre
8
9
 
9
10
 
@@ -13,7 +14,7 @@ def test_doctor_diagnosticar_salud(tmp_path, monkeypatch):
13
14
  fake_home = tmp_path / "fake_home"
14
15
  fake_home.mkdir()
15
16
  monkeypatch.setattr("os.path.expanduser", lambda path: str(fake_home) if "~" in path else os.path.expanduser(path))
16
-
17
+
17
18
  report = diagnosticar_salud(str(tmp_path))
18
19
  assert report.ok
19
20
  assert len(report.checks) >= 4
@@ -41,8 +42,6 @@ def test_doctor_reparar_vaults_desalineados(tmp_path):
41
42
  (ctx_dir / "vault-Viejo").mkdir(parents=True)
42
43
  (ctx_dir / "vault-Viejo" / "nota.md").write_text("# Nota", encoding="utf-8")
43
44
 
44
- repo_name = os.path.basename(os.path.abspath(str(tmp_path)))
45
- # El vault esperado para tmp_path será vault-<repo_name>
46
-
47
- report = reparar_salud(str(tmp_path))
45
+ # El vault esperado para tmp_path será vault-<nombre del directorio>
46
+ reparar_salud(str(tmp_path))
48
47
  assert os.path.isdir(ctx_dir / "_legacy" / "vault-Viejo")
@@ -1,8 +1,6 @@
1
1
  """Pruebas unitarias para el módulo context_map.domain.normalization.domain_extractor."""
2
2
 
3
- import pytest
4
- from pathlib import Path
5
- from context_map.domain.normalization.domain_extractor import DomainConceptExtractor, extraer_conceptos_proyecto
3
+ from context_map.domain.normalization.domain_extractor import DomainConceptExtractor
6
4
 
7
5
 
8
6
  def test_domain_concept_extractor() -> None:
@@ -328,8 +328,10 @@ def test_adaptar_upgrade_memoria_viva_agents_generado() -> None:
328
328
  with open(agents_path, "w", encoding="utf-8") as f:
329
329
  f.write(
330
330
  "# Instrucciones — Bot Demo\n\n"
331
- "Este proyecto utiliza **ContextMap** para gobernanza de contexto.\n"
331
+ "<!-- CONTEXTMAP:BEGIN -->\n"
332
+ "Este proyecto utiliza ContextMap para gobernanza de contexto.\n"
332
333
  "## 1. Protocolo de Inicio\n- Leer el brief\n"
334
+ "<!-- CONTEXTMAP:END -->\n"
333
335
  )
334
336
 
335
337
  eco = detectar_ecosistema(temp_dir)
@@ -0,0 +1,68 @@
1
+ """Pruebas unitarias para el comando ctxmap enrich y su persistencia en disco."""
2
+
3
+ import argparse
4
+ import ast
5
+ import tempfile
6
+ from pathlib import Path
7
+
8
+ from context_map.application.commands.enrich import _formatear_docstring, cmd_enrich
9
+
10
+
11
+ def test_formatear_docstring_simple() -> None:
12
+ """Verifica el formateo de un docstring simple de una sola línea."""
13
+ res = _formatear_docstring("Calcula el total de la orden.", " ")
14
+ assert len(res) == 1
15
+ assert res[0] == ' """Calcula el total de la orden."""'
16
+
17
+
18
+ def test_formatear_docstring_multilinea() -> None:
19
+ """Verifica el formateo de un docstring con múltiples líneas e indentación."""
20
+ doc = """Calcula el total.
21
+
22
+ Args:
23
+ subtotal: Monto base.
24
+ """
25
+ res = _formatear_docstring(doc, " ")
26
+ assert res[0] == ' """Calcula el total.'
27
+ assert " Args:" in res
28
+ assert " subtotal: Monto base." in res
29
+ assert res[-1] == ' """'
30
+
31
+
32
+ def test_cmd_enrich_persiste_cambios_en_disco() -> None:
33
+ """Verifica que cmd_enrich escriba efectivamente el docstring generado en el archivo."""
34
+ codigo_original = "def sumar(a: int, b: int) -> int:\n return a + b\n"
35
+
36
+ with tempfile.TemporaryDirectory() as tmpdir:
37
+ test_file = Path(tmpdir) / "matematica.py"
38
+ test_file.write_text(codigo_original, encoding="utf-8")
39
+
40
+ args = argparse.Namespace(path=str(test_file), model=None, dry_run=False)
41
+ ret = cmd_enrich(args)
42
+ assert ret == 0
43
+
44
+ contenido_nuevo = test_file.read_text(encoding="utf-8")
45
+ assert contenido_nuevo != codigo_original
46
+
47
+ # Validar que sea sintácticamente válido
48
+ tree = ast.parse(contenido_nuevo)
49
+ fn = tree.body[0]
50
+ assert isinstance(fn, ast.FunctionDef)
51
+ assert ast.get_docstring(fn) is not None
52
+ assert "sumar" in ast.get_docstring(fn).lower() or len(ast.get_docstring(fn)) > 0
53
+
54
+
55
+ def test_cmd_enrich_dry_run_no_modifica_disco() -> None:
56
+ """Verifica que en modo --dry-run el archivo en disco permanezca intacto."""
57
+ codigo_original = "def restar(a: int, b: int) -> int:\n return a - b\n"
58
+
59
+ with tempfile.TemporaryDirectory() as tmpdir:
60
+ test_file = Path(tmpdir) / "calculo.py"
61
+ test_file.write_text(codigo_original, encoding="utf-8")
62
+
63
+ args = argparse.Namespace(path=str(test_file), model=None, dry_run=True)
64
+ ret = cmd_enrich(args)
65
+ assert ret == 0
66
+
67
+ contenido_despues = test_file.read_text(encoding="utf-8")
68
+ assert contenido_despues == codigo_original
@@ -4,7 +4,7 @@ import tempfile
4
4
 
5
5
  from context_map.application.commands.hook import cmd_hook_install
6
6
  from context_map.core.storage.config_loader import load_project_config
7
- from context_map.domain.analysis.complexity import calcular_complejidad_archivo
7
+ from context_map.domain.analyzers.cyclomatic import calcular_complejidad_archivo
8
8
 
9
9
 
10
10
  def test_hook_install_creates_pre_commit_script() -> None:
@@ -1,7 +1,6 @@
1
1
  """Pruebas unitarias para el módulo context_map.domain.health.hardware."""
2
2
 
3
- import pytest
4
- from context_map.domain.health.hardware import evaluar_hardware_pc, EspecificacionesHardware
3
+ from context_map.domain.health.hardware import EspecificacionesHardware, evaluar_hardware_pc
5
4
 
6
5
 
7
6
  def test_evaluar_hardware_pc() -> None:
@@ -2,7 +2,6 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import os
6
5
  from context_map.domain.ecosystem.hooks import desinstalar_git_hooks, instalar_git_hooks
7
6
 
8
7
 
@@ -0,0 +1,31 @@
1
+ """Tests de seguridad del servidor MCP: confirmación destructiva y validación de rutas."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from context_map.infrastructure import mcp_server
6
+
7
+
8
+ def test_mcp_build_clean_sin_confirmacion_es_rechazado():
9
+ """Verifica que build(clean=True) exige confirm=True."""
10
+ res = mcp_server.build(target=".", clean=True)
11
+ assert "rechazada por seguridad" in res
12
+ assert "confirm=True" in res
13
+
14
+
15
+ def test_mcp_doctor_fix_sin_confirmacion_es_rechazado():
16
+ """Verifica que doctor(fix=True) exige confirm=True."""
17
+ res = mcp_server.doctor(target=".", fix=True)
18
+ assert "rechazada por seguridad" in res
19
+
20
+
21
+ def test_mcp_install_hooks_sin_confirmacion_es_rechazado():
22
+ """Verifica que install_hooks exige confirm=True."""
23
+ res = mcp_server.install_hooks(target=".", force=True)
24
+ assert "rechazada por seguridad" in res
25
+
26
+
27
+ def test_mcp_target_invalido_devuelve_error():
28
+ """Verifica que un target inexistente no se procesa."""
29
+ res = mcp_server.check(target="/ruta/inexistente/xyz-123")
30
+ assert "ERROR" in res
31
+ assert "no es un directorio" in res
@@ -0,0 +1,29 @@
1
+ """Pruebas unitarias para el módulo context_map.infrastructure.integrations.ollama."""
2
+
3
+ from context_map.infrastructure.integrations.ollama import OllamaLocalClient
4
+
5
+
6
+ def test_ollama_client_init() -> None:
7
+ """Verifica la inicialización del cliente de Ollama local."""
8
+ client = OllamaLocalClient()
9
+ assert client.host == "http://localhost:11434"
10
+ assert client.hardware is not None
11
+ # Si no hay Ollama corriendo en el entorno de test, esta_disponible() debe retornar False sin lanzar excepción
12
+ disponible = client.esta_disponible()
13
+ assert isinstance(disponible, bool)
14
+
15
+
16
+ def test_ollama_client_opt_out_parametro() -> None:
17
+ """Verifica que el parámetro opt_out desactive inmediatamente el cliente."""
18
+ client = OllamaLocalClient(opt_out=True)
19
+ assert client.opt_out is True
20
+ assert client.esta_disponible() is False
21
+ assert client.listar_modelos_instalados() == []
22
+
23
+
24
+ def test_ollama_client_opt_out_entorno(monkeypatch) -> None:
25
+ """Verifica que la variable de entorno CTXMAP_NO_OLLAMA desactive el cliente."""
26
+ monkeypatch.setenv("CTXMAP_NO_OLLAMA", "1")
27
+ client = OllamaLocalClient()
28
+ assert client.opt_out is True
29
+ assert client.esta_disponible() is False
@@ -0,0 +1,135 @@
1
+ """Tests unitarios para el empaquetado y desempaquetado de contexto (.ctxpack)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import io
6
+ import os
7
+ import tarfile
8
+
9
+ import pytest
10
+
11
+ from context_map.domain.storage.packer import (
12
+ _sha256_archivo,
13
+ crear_paquete_contexto,
14
+ desempaquetar_contexto,
15
+ )
16
+
17
+
18
+ def test_pack_y_unpack_flujo_completo(tmp_path):
19
+ """Verifica la creación y extracción completa de un archivo .ctxpack."""
20
+ # 1. Crear estructura simulada .context-map/
21
+ src_dir = tmp_path / "proyecto_origen"
22
+ ctx_dir = src_dir / ".context-map"
23
+ state_dir = ctx_dir / "state"
24
+ state_dir.mkdir(parents=True)
25
+
26
+ graph_file = state_dir / "graph.jsonl"
27
+ graph_file.write_text(
28
+ '{"id": "idea_1", "type": "IDEA", "title": "Idea 1"}\n'
29
+ '{"id": "base_1", "type": "BASE", "title": "Base 1"}\n',
30
+ encoding="utf-8",
31
+ )
32
+
33
+ brief_file = ctx_dir / "CONTEXT.md"
34
+ brief_file.write_text("# Brief de Contexto", encoding="utf-8")
35
+
36
+ agents_file = src_dir / "AGENTS.md"
37
+ agents_file.write_text("# Reglas Agénticas", encoding="utf-8")
38
+
39
+ # 2. Empaquetar
40
+ out_file = tmp_path / "backup.ctxpack"
41
+ ruta_gen, size, manifest = crear_paquete_contexto(
42
+ target_dir=str(src_dir),
43
+ output_path=str(out_file),
44
+ project_name="ProyectoPrueba",
45
+ )
46
+
47
+ assert os.path.isfile(ruta_gen)
48
+ assert size > 0
49
+ assert manifest["project"] == "ProyectoPrueba"
50
+ assert manifest["node_count"] == 2
51
+
52
+ # 3. Desempaquetar en directorio destino
53
+ dst_dir = tmp_path / "proyecto_destino"
54
+ res_manifest = desempaquetar_contexto(
55
+ archive_path=str(out_file),
56
+ target_dir=str(dst_dir),
57
+ )
58
+
59
+ assert res_manifest["project"] == "ProyectoPrueba"
60
+ assert (dst_dir / ".context-map" / "state" / "graph.jsonl").exists()
61
+ assert (dst_dir / ".context-map" / "CONTEXT.md").exists()
62
+ assert (dst_dir / "AGENTS.md").exists()
63
+
64
+ content = (dst_dir / ".context-map" / "state" / "graph.jsonl").read_text(encoding="utf-8")
65
+ assert "idea_1" in content
66
+ assert "base_1" in content
67
+
68
+
69
+ def test_manifest_incluye_checksums_sha256(tmp_path):
70
+ """Verifica que el manifiesto registra un checksum SHA-256 por archivo."""
71
+ src_dir = tmp_path / "origen"
72
+ state_dir = src_dir / ".context-map" / "state"
73
+ state_dir.mkdir(parents=True)
74
+ (state_dir / "graph.jsonl").write_text("hola\n", encoding="utf-8")
75
+
76
+ out_file = tmp_path / "backup.ctxpack"
77
+ _, _, manifest = crear_paquete_contexto(str(src_dir), str(out_file), project_name="Checksums")
78
+
79
+ ruta_rel = os.path.join(".context-map", "state", "graph.jsonl")
80
+ assert ruta_rel in manifest["files"]
81
+ assert manifest["files"][ruta_rel] == _sha256_archivo(str(state_dir / "graph.jsonl"))
82
+
83
+
84
+ def test_unpack_detecta_integridad_comprometida(tmp_path):
85
+ """Verifica que un paquete con contenido alterado se rechaza por checksum."""
86
+ src_dir = tmp_path / "origen"
87
+ state_dir = src_dir / ".context-map" / "state"
88
+ state_dir.mkdir(parents=True)
89
+ (state_dir / "graph.jsonl").write_text('{"id": "a", "type": "IDEA"}\n', encoding="utf-8")
90
+
91
+ out_file = tmp_path / "backup.ctxpack"
92
+ crear_paquete_contexto(str(src_dir), str(out_file), project_name="Integridad")
93
+
94
+ # Reescribir el tar alterando graph.jsonl sin actualizar el manifiesto
95
+ tampered = tmp_path / "tampered.ctxpack"
96
+ with tarfile.open(str(out_file), "r:gz") as tar, tarfile.open(str(tampered), "w:gz") as out:
97
+ for m in tar.getmembers():
98
+ f = tar.extractfile(m)
99
+ contenido = f.read() if f else b""
100
+ if m.name == ".context-map/state/graph.jsonl":
101
+ contenido = b'{"id": "ALTERADO_TAMPERED"}\n'
102
+ ti = tarfile.TarInfo(m.name)
103
+ ti.size = len(contenido)
104
+ ti.mode = m.mode
105
+ out.addfile(ti, io.BytesIO(contenido))
106
+
107
+ with pytest.raises(ValueError, match="Integridad"):
108
+ desempaquetar_contexto(str(tampered), str(tmp_path / "destino"))
109
+
110
+
111
+ def _crear_tar_con_symlink(path: str) -> None:
112
+ """Crea un .ctxpack malicioso con un symlink apuntando fuera del destino."""
113
+ with tarfile.open(path, "w:gz") as tar:
114
+ data = b"contenido"
115
+ ti = tarfile.TarInfo(".context-map/state/graph.jsonl")
116
+ ti.size = len(data)
117
+ tar.addfile(ti, io.BytesIO(data))
118
+
119
+ # Symlink malicioso que apunta fuera del directorio destino
120
+ sl = tarfile.TarInfo("escape")
121
+ sl.type = tarfile.SYMTYPE
122
+ sl.linkname = "/tmp/ctxmap_escape_target"
123
+ tar.addfile(sl)
124
+
125
+
126
+ def test_unpack_rechaza_symlinks(tmp_path):
127
+ """Verifica que un .ctxpack con symlinks no los extrae (hardening)."""
128
+ archivo = tmp_path / "malicioso.ctxpack"
129
+ _crear_tar_con_symlink(str(archivo))
130
+
131
+ destino = tmp_path / "destino"
132
+ desempaquetar_contexto(str(archivo), str(destino))
133
+
134
+ assert (destino / ".context-map" / "state" / "graph.jsonl").exists()
135
+ assert not (destino / "escape").exists() # el symlink no se extrae
@@ -1,7 +1,7 @@
1
1
  """Pruebas unitarias para el módulo context_map.domain.scanning.cache."""
2
2
 
3
- import pytest
4
3
  from pathlib import Path
4
+
5
5
  from context_map.domain.scanning.cache import ScanCacheManager
6
6
 
7
7
 
@@ -0,0 +1,65 @@
1
+ """Pruebas unitarias para el escáner de secretos y análisis de entropía de Shannon."""
2
+
3
+ from context_map.domain.scanning.security import SecurityScanner, calcular_entropia_shannon
4
+
5
+
6
+ def test_calcular_entropia_shannon() -> None:
7
+ """Verifica el cálculo de entropía de Shannon en cadenas repetitivas y aleatorias."""
8
+ assert calcular_entropia_shannon("") == 0.0
9
+ # Cadena de caracteres repetidos tiene entropía 0
10
+ assert calcular_entropia_shannon("aaaaaaaaaaaaaaaa") == 0.0
11
+ # Cadena pseudoaleatoria criptográfica tiene alta entropía (> 4.0)
12
+ entropia_alta = calcular_entropia_shannon("a8F9z!q2L#m9X$v1K@p8")
13
+ assert entropia_alta > 4.0
14
+
15
+
16
+ def test_scanner_detecta_slack_y_stripe() -> None:
17
+ """Verifica la detección de tokens de Slack y Stripe."""
18
+ token_slack = "".join(["xo", "xb-", "123456789012-", "1234567890123-", "abcDEFghiJKLmnoPQR"])
19
+ token_stripe = "".join(["sk_", "live_", "51HzABC1234567890abcdefghijklmnOPQR"])
20
+ codigo = f"""
21
+ SLACK_BOT = "{token_slack}"
22
+ STRIPE_KEY = "{token_stripe}"
23
+ """
24
+ hallazgos = SecurityScanner.escanear_contenido(codigo, "app/config.py")
25
+ tipos = [h["tipo"] for h in hallazgos]
26
+ assert "Slack Token" in tipos
27
+ assert "Stripe API Key" in tipos
28
+
29
+
30
+ def test_scanner_detecta_sendgrid_npm_twilio() -> None:
31
+ """Verifica la detección de SendGrid, npm y Twilio."""
32
+ token_sendgrid = "".join(["SG.", "abcdefghij1234567890_-.", "_-abcdefghijklmnopqrstuvwxyz0123456789012"])
33
+ token_npm = "".join(["npm_", "1234567890abcdefghijklmnopqrstuv"])
34
+ token_twilio = "".join(["AC", "1234567890abcdef1234567890abcdef"])
35
+ codigo = f"""
36
+ SENDGRID_API = "{token_sendgrid}"
37
+ NPM_AUTH = "{token_npm}"
38
+ TWILIO_SID = "{token_twilio}"
39
+ """
40
+ hallazgos = SecurityScanner.escanear_contenido(codigo, "services/auth.py")
41
+ tipos = [h["tipo"] for h in hallazgos]
42
+ assert "SendGrid API Key" in tipos
43
+ assert "npm Access Token" in tipos
44
+ assert "Twilio API Key or SID" in tipos
45
+
46
+
47
+ def test_scanner_detecta_alta_entropia_generica() -> None:
48
+ """Verifica que asignaciones de alta entropía a variables sensibles sean advertidas."""
49
+ token_entropy = "".join(["9f8a7b6c5d", "4e3f2a1b0c", "9d8e7f6a5b", "4c3d2e1f0a"])
50
+ codigo = f'AUTH_TOKEN = "{token_entropy}"'
51
+ hallazgos = SecurityScanner.escanear_contenido(codigo, "sec/secrets.py")
52
+ tipos = [h["tipo"] for h in hallazgos]
53
+ assert any("Entropy" in t or "Token" in t for t in tipos)
54
+
55
+
56
+ def test_scanner_ignora_lineas_dummy() -> None:
57
+ """Verifica que líneas con dummy, placeholder o example no disparen falsos positivos."""
58
+ token_anthropic = "".join(["sk-ant-", "api03-", "1234567890123456789012345678901234567890"])
59
+ token_example = "".join(["xo", "xb-", "123456789012-", "1234567890123-", "placeholder"])
60
+ codigo = f"""
61
+ # dummy_key = {token_anthropic}
62
+ EXAMPLE_TOKEN = "{token_example}"
63
+ """
64
+ hallazgos = SecurityScanner.escanear_contenido(codigo, "tests/mock.py")
65
+ assert len(hallazgos) == 0
@@ -1,6 +1,7 @@
1
1
  """Pruebas unitarias para el detector preventivo de secretos en el código fuente."""
2
2
 
3
3
  from pathlib import Path
4
+
4
5
  from context_map.domain.scanning.security import SecurityScanner, escanear_secretos_archivo
5
6
 
6
7
 
@@ -1,11 +1,10 @@
1
1
  """Pruebas unitarias para el módulo context_map.domain.normalization.similarity."""
2
2
 
3
- import pytest
4
3
  from context_map.domain.normalization.similarity import (
4
+ deduplicar_elementos_similares,
5
5
  distancia_levenshtein,
6
6
  similitud_jaccard_ngramas,
7
7
  son_textos_similares,
8
- deduplicar_elementos_similares,
9
8
  )
10
9
 
11
10
 
@@ -1,6 +1,5 @@
1
1
  """Pruebas unitarias para el módulo de tokenización context_map.core.tokenization."""
2
2
 
3
- import pytest
4
3
  from context_map.core.tokenization import TokenCounter, contar_tokens_texto, normalize_model_name
5
4
 
6
5
 
@@ -8,7 +8,6 @@ lo registrado (eventos en events.jsonl) vs lo que queda sin importar
8
8
  from __future__ import annotations
9
9
 
10
10
  import json
11
- import os
12
11
 
13
12
  from context_map.application.commands.wrap import cmd_wrap
14
13