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

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