context-map-ai 2.7.0__tar.gz → 2.7.2__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 (257) hide show
  1. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/copilot-instructions.md +1 -1
  2. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CHANGELOG.md +42 -0
  3. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/PKG-INFO +5 -3
  4. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/README.md +4 -2
  5. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/README_EN.md +2 -2
  6. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/README_TECNICO.md +3 -1
  7. context_map_ai-2.7.2/context_map/__tests__/test_analyzers_content.py +154 -0
  8. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_diario_consolidado.py +51 -0
  9. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_web_clipper.py +25 -1
  10. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_wiki_avanzado.py +44 -0
  11. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/clip.py +20 -2
  12. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/wiki.py +47 -3
  13. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/content.py +47 -2
  14. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/canvas.py +7 -0
  15. context_map_ai-2.7.2/docs/PI_PACKAGE.md +62 -0
  16. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/pyproject.toml +1 -1
  17. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/uv.lock +1 -1
  18. context_map_ai-2.7.0/context_map/__tests__/test_analyzers_content.py +0 -69
  19. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.agents/skills/contextmap/SKILL.md +0 -0
  20. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/workflows/ci.yml +0 -0
  24. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/workflows/docs.yml +0 -0
  25. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/workflows/publish.yml +0 -0
  26. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.gitignore +0 -0
  27. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/config.yaml +0 -0
  28. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/shields/pre-commit.md +0 -0
  29. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/triggers/post-commit.md +0 -0
  30. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/workflows/contextmap.yaml +0 -0
  31. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/workflows/dev-loop.md +0 -0
  32. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.pre-commit-hooks.yaml +0 -0
  33. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/AGENTS.md +0 -0
  34. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CLAUDE.md +0 -0
  35. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CODE_OF_CONDUCT.md +0 -0
  36. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CONTRIBUTING.md +0 -0
  37. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/LICENSE +0 -0
  38. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/Makefile +0 -0
  39. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/action.yml +0 -0
  40. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__init__.py +0 -0
  41. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/conftest.py +0 -0
  42. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/smoke.py +0 -0
  43. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ast_audit.py +0 -0
  44. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ast_summary.py +0 -0
  45. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_brief_identidad.py +0 -0
  46. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_build_sin_eventos.py +0 -0
  47. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_captura.py +0 -0
  48. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_cargadores_chats.py +0 -0
  49. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_checker_frescura.py +0 -0
  50. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_clasificacion_chat.py +0 -0
  51. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_cli.py +0 -0
  52. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_conexiones.py +0 -0
  53. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_conocimiento_brief.py +0 -0
  54. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_cyclomatic.py +0 -0
  55. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_dependencias_mcp.py +0 -0
  56. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_doctor.py +0 -0
  57. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_domain_extractor.py +0 -0
  58. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ecosistema.py +0 -0
  59. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_embeddings_opcionales.py +0 -0
  60. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_enrich.py +0 -0
  61. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_enterprise_features.py +0 -0
  62. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_exclusiones_escaneo.py +0 -0
  63. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_export.py +0 -0
  64. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_fechas_conversaciones.py +0 -0
  65. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hardware.py +0 -0
  66. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
  67. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
  68. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hooks.py +0 -0
  69. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_humanizacion.py +0 -0
  70. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_inbox.py +0 -0
  71. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ingest.py +0 -0
  72. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ingesta_rica.py +0 -0
  73. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_llm_opcional.py +0 -0
  74. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_mcp_server_seguridad.py +0 -0
  75. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_mermaid.py +0 -0
  76. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
  77. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_obsidian_config.py +0 -0
  78. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ollama.py +0 -0
  79. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_pack_unpack.py +0 -0
  80. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_parsing.py +0 -0
  81. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal.py +0 -0
  82. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_identidad.py +0 -0
  83. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_panorama.py +0 -0
  84. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_repair.py +0 -0
  85. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_ruta.py +0 -0
  86. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_sync.py +0 -0
  87. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_prompt_caching.py +0 -0
  88. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_readiness_signals.py +0 -0
  89. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_refresh_multicanal.py +0 -0
  90. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_refresh_temporales.py +0 -0
  91. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_reglas_catalogo.py +0 -0
  92. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_review.py +0 -0
  93. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_scan_cache.py +0 -0
  94. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_scanner.py +0 -0
  95. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_security.py +0 -0
  96. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_security_scanner.py +0 -0
  97. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_similarity.py +0 -0
  98. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_standardize.py +0 -0
  99. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_templates_filenames.py +0 -0
  100. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_tokenization.py +0 -0
  101. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_topologia_arbol.py +0 -0
  102. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_vault_consolidation.py +0 -0
  103. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_watcher.py +0 -0
  104. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_wiki.py +0 -0
  105. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_wrap.py +0 -0
  106. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/__init__.py +0 -0
  107. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/cli/__init__.py +0 -0
  108. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/cli/cli.py +0 -0
  109. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/cli/parser.py +0 -0
  110. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/__init__.py +0 -0
  111. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/_helpers.py +0 -0
  112. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/adapt.py +0 -0
  113. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/auto.py +0 -0
  114. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/build.py +0 -0
  115. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/doctor_cmd.py +0 -0
  116. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/enrich.py +0 -0
  117. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/export.py +0 -0
  118. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/hook.py +0 -0
  119. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/importers.py +0 -0
  120. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/inbox.py +0 -0
  121. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/ingest.py +0 -0
  122. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/pack.py +0 -0
  123. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/__init__.py +0 -0
  124. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/common.py +0 -0
  125. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/export.py +0 -0
  126. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/panorama.py +0 -0
  127. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/query.py +0 -0
  128. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/repair.py +0 -0
  129. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/sync.py +0 -0
  130. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/timeline.py +0 -0
  131. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/refresh.py +0 -0
  132. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/review.py +0 -0
  133. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/scan.py +0 -0
  134. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/sync.py +0 -0
  135. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/tools.py +0 -0
  136. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/update.py +0 -0
  137. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/watch.py +0 -0
  138. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/wiki.py +0 -0
  139. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/wrap.py +0 -0
  140. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/cli.py +0 -0
  141. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/__init__.py +0 -0
  142. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/generators/__init__.py +0 -0
  143. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/generators/generadores.py +0 -0
  144. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/generators/narrative_templates.py +0 -0
  145. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/logging_setup.py +0 -0
  146. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/models/__init__.py +0 -0
  147. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/models/config.py +0 -0
  148. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/models/models.py +0 -0
  149. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/__init__.py +0 -0
  150. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/cleaning.py +0 -0
  151. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/humanizacion.py +0 -0
  152. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/inference.py +0 -0
  153. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/mappings.py +0 -0
  154. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/standardize.py +0 -0
  155. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/__init__.py +0 -0
  156. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/cargadores.py +0 -0
  157. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/clasificacion.py +0 -0
  158. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/dedup.py +0 -0
  159. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/grafo.py +0 -0
  160. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/__init__.py +0 -0
  161. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/bd.py +0 -0
  162. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/panorama.py +0 -0
  163. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/repair.py +0 -0
  164. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/storage/__init__.py +0 -0
  165. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/storage/config_loader.py +0 -0
  166. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/storage/store.py +0 -0
  167. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/tokenization.py +0 -0
  168. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/__init__.py +0 -0
  169. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/__init__.py +0 -0
  170. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/checker.py +0 -0
  171. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/models.py +0 -0
  172. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/reporting.py +0 -0
  173. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/signals.py +0 -0
  174. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/__init__.py +0 -0
  175. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/ast_audit.py +0 -0
  176. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/ast_summary.py +0 -0
  177. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/cyclomatic.py +0 -0
  178. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/__init__.py +0 -0
  179. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/adaptador.py +0 -0
  180. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/detector.py +0 -0
  181. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/hooks.py +0 -0
  182. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/rules_templates.py +0 -0
  183. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/health/__init__.py +0 -0
  184. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/health/doctor.py +0 -0
  185. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/health/hardware.py +0 -0
  186. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ingestion/__init__.py +0 -0
  187. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ingestion/ingest.py +0 -0
  188. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/__init__.py +0 -0
  189. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/captura.py +0 -0
  190. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/embeddings.py +0 -0
  191. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/inbox.py +0 -0
  192. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/indices.py +0 -0
  193. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/llm.py +0 -0
  194. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/review.py +0 -0
  195. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/normalization/domain_extractor.py +0 -0
  196. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/normalization/similarity.py +0 -0
  197. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/reglas/reglas.py +0 -0
  198. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/reporting/__init__.py +0 -0
  199. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/reporting/reporter.py +0 -0
  200. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/__init__.py +0 -0
  201. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/cache.py +0 -0
  202. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/scanner.py +0 -0
  203. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/security.py +0 -0
  204. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/storage/packer.py +0 -0
  205. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/__init__.py +0 -0
  206. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/relaciones.py +0 -0
  207. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/sync.py +0 -0
  208. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/watcher.py +0 -0
  209. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/__init__.py +0 -0
  210. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/__init__.py +0 -0
  211. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/exclusions.py +0 -0
  212. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/structure.py +0 -0
  213. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/__init__.py +0 -0
  214. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/antigravity.py +0 -0
  215. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/chat_export.py +0 -0
  216. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/git.py +0 -0
  217. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/hermes.py +0 -0
  218. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/ollama.py +0 -0
  219. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/mcp_server.py +0 -0
  220. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/version_check.py +0 -0
  221. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/__init__.py +0 -0
  222. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/__init__.py +0 -0
  223. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/agents.py +0 -0
  224. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/brief.py +0 -0
  225. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/extractors.py +0 -0
  226. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/sections.py +0 -0
  227. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/skill.py +0 -0
  228. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/__init__.py +0 -0
  229. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/atomic.py +0 -0
  230. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/__init__.py +0 -0
  231. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/conocimiento.py +0 -0
  232. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
  233. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/dominios.py +0 -0
  234. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/escritura.py +0 -0
  235. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/graph_style.py +0 -0
  236. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
  237. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/nodos.py +0 -0
  238. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
  239. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/obsidian_config.py +0 -0
  240. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/readme_extract.py +0 -0
  241. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/rutas.py +0 -0
  242. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
  243. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
  244. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_historial.py +0 -0
  245. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
  246. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
  247. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
  248. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/mermaid.py +0 -0
  249. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/preservar.py +0 -0
  250. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/templates.py +0 -0
  251. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/writer.py +0 -0
  252. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/LAUNCH_KIT.md +0 -0
  253. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/LLM.md +0 -0
  254. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/WEB_CLIPPER.md +0 -0
  255. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
  256. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
  257. {context_map_ai-2.7.0 → context_map_ai-2.7.2}/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-10-07).
21
+ > Generado automáticamente por **ContextMap** (2026-10-08).
22
22
 
23
23
  ## Antes de sugerir código
24
24
 
@@ -7,6 +7,48 @@ y este proyecto adhiere a [Semantic Versioning](https://semver.org/spec/v2.0.0.h
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ### 🧩 Paquete de pi: `@kudawa/pi-contextmap`
11
+
12
+ - Publicado en npm el paquete que trae ContextMap a [pi](https://pi.dev):
13
+ extensión que **registra el servidor MCP** (avisa si falta el CLI), skill
14
+ `contextmap` y los prompts `/contextmap-contexto`, `/contextmap-cierre` y
15
+ `/contextmap-preguntar`.
16
+ - Install: `pi install npm:@kudawa/pi-contextmap`. Repo propio con publicación
17
+ automática en npm por **Trusted Publishing (OIDC, sin tokens)**:
18
+ https://github.com/kudawasama/pi-contextmap
19
+ - Guía: `docs/PI_PACKAGE.md`. (El nombre `pi-contextmap` fue rechazado por npm
20
+ por parecido a `pi-context-map`; se publicó con ámbito `@kudawa`.)
21
+
22
+ ## [2.7.2] — 2026-10-08
23
+
24
+ ### 🐛 Arreglado
25
+
26
+ - **Ruido de TODO en el Diario**: `render_nota_dia` volcaba los TODO del código
27
+ (`TODO (ruta.py:Ln): …`) en la nota del día. Ahora aplica el mismo filtro
28
+ `_es_todo_codigo` que ya usaban backlog, historial e ideas: la deuda técnica
29
+ vive en `5.0-BACKLOG/5.1-Tareas`, no en el diario.
30
+ - **Falsos positivos del extractor de TODO**: en archivos `.py` se analizan
31
+ **solo comentarios reales** con `tokenize` (un docstring o un string que
32
+ *menciona* el marcador ya no cuenta) y `**TODO**` en negrita Markdown dejó de
33
+ parecer un comentario. Descubierto al documentar el propio filtro, que generó
34
+ nodos fantasma.
35
+ - Tests: `test_analyzers_content.py` (8) y `test_diario_consolidado.py` (6);
36
+ suite **362/362**.
37
+
38
+ ## [2.7.1] — 2026-10-08
39
+
40
+ ### 🐛 Arreglado
41
+
42
+ - **Acentos en el portapapeles (Windows)**: `ctxmap inbox add --clipboard` leía la
43
+ salida de PowerShell con la codificación de la consola (cp850) y corrompía el
44
+ texto (`Recuperación` → `Recuperaci¢n`). PowerShell ahora fuerza salida UTF-8 y
45
+ la lectura se decodifica **siempre** como UTF-8. Detectado al estrenar el
46
+ Second Brain con una captura real (`·`, tildes y eñes).
47
+ - **Respuestas extractivas con ruido**: `wiki ask` citaba títulos Markdown,
48
+ avisos de redirección y bloques de fórmulas (`\displaystyle`) en lugar de la
49
+ definición. `wiki._frases` descarta estructura y ruido matemático y devuelve
50
+ prosa limpia (sin `**`, sin `[texto](url)`, sin viñetas).
51
+
10
52
  ## [2.7.0] — 2026-10-07
11
53
 
12
54
  ### 🗺️ G8 y G9 evaluados: BM25 ya estaba · MOC en una sola pasada
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: context-map-ai
3
- Version: 2.7.0
3
+ Version: 2.7.2
4
4
  Summary: ContextMap IA — Memoria permanente y mapa mental narrativo para agentes de IA
5
5
  Project-URL: Homepage, https://github.com/kudawasama/ContextMap
6
6
  Project-URL: Repository, https://github.com/kudawasama/ContextMap
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
42
42
 
43
43
  ### *Evita que tu IA olvide tus decisiones, gaste dinero en tokens y rompa tu código.*
44
44
 
45
- [![Release](https://img.shields.io/badge/version-v2.7.0-blue.svg?style=for-the-badge)](CHANGELOG.md)
45
+ [![Release](https://img.shields.io/badge/version-v2.7.2-blue.svg?style=for-the-badge)](CHANGELOG.md)
46
46
  [![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/)
47
47
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](pyproject.toml)
48
48
  [![Tests: 314 Passing](https://img.shields.io/badge/tests-314%2F314%20passing-brightgreen.svg?style=for-the-badge)](context_map/__tests__/)
@@ -144,6 +144,8 @@ Bot-Automatizacion 🟡 Tibio 21d 32 2/1 0
144
144
  ### 🔌 3. Control Directo para Agentes de IA (Servidor MCP Nativo & Skills)
145
145
  ContextMap incluye un servidor **MCP nativo (29 herramientas)** y generador de **Skills para Antigravity**. Asistentes como **Antigravity, Hermes Agent, Claude Desktop, Cursor o Windsurf** pueden sincronizar el mapa, consultar lecciones y registrar decisiones de forma completamente autónoma sin que toques la terminal.
146
146
 
147
+ > 🧩 También disponible como **paquete de [pi](https://pi.dev)**: `pi install npm:@kudawa/pi-contextmap` (trae el MCP, una skill y 3 prompts). Ver [docs/PI_PACKAGE.md](docs/PI_PACKAGE.md).
148
+
147
149
  ### 🛡️ 4. Reglas y Skills Universales para 10+ Editores e IAs
148
150
  Escribe tus normas una sola vez y ContextMap las inyecta en el formato nativo de cada herramienta:
149
151
  * **Google Antigravity**: `.agents/skills/contextmap/SKILL.md`
@@ -159,7 +161,7 @@ Escribe tus normas una sola vez y ContextMap las inyecta en el formato nativo de
159
161
 
160
162
  ## ⚖️ Comparativa: ContextMap vs. Otras Soluciones
161
163
 
162
- | ¿Qué necesitas? | Copiar y Pegar Todo<br>*(Repomix / Gitingest)* | Indexadores de Editor<br>*(Cursor / Windsurf)* | **ContextMap v2.7.0** |
164
+ | ¿Qué necesitas? | Copiar y Pegar Todo<br>*(Repomix / Gitingest)* | Indexadores de Editor<br>*(Cursor / Windsurf)* | **ContextMap v2.7.2** |
163
165
  | :--- | :---: | :---: | :---: |
164
166
  | **Gasto de Tokens** | 🔴 Altísimo (quema tu dinero) | 🟡 Medio | 🟢 **Mínimo (>99% de ahorro)** |
165
167
  | **Prompt Caching Determinista** | ❌ Incompatible (cambia el hash) | 🟡 Parcial | **✅ Prefijo congelado (>90% Hit Rate)** |
@@ -6,7 +6,7 @@
6
6
 
7
7
  ### *Evita que tu IA olvide tus decisiones, gaste dinero en tokens y rompa tu código.*
8
8
 
9
- [![Release](https://img.shields.io/badge/version-v2.7.0-blue.svg?style=for-the-badge)](CHANGELOG.md)
9
+ [![Release](https://img.shields.io/badge/version-v2.7.2-blue.svg?style=for-the-badge)](CHANGELOG.md)
10
10
  [![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/)
11
11
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](pyproject.toml)
12
12
  [![Tests: 314 Passing](https://img.shields.io/badge/tests-314%2F314%20passing-brightgreen.svg?style=for-the-badge)](context_map/__tests__/)
@@ -108,6 +108,8 @@ Bot-Automatizacion 🟡 Tibio 21d 32 2/1 0
108
108
  ### 🔌 3. Control Directo para Agentes de IA (Servidor MCP Nativo & Skills)
109
109
  ContextMap incluye un servidor **MCP nativo (29 herramientas)** y generador de **Skills para Antigravity**. Asistentes como **Antigravity, Hermes Agent, Claude Desktop, Cursor o Windsurf** pueden sincronizar el mapa, consultar lecciones y registrar decisiones de forma completamente autónoma sin que toques la terminal.
110
110
 
111
+ > 🧩 También disponible como **paquete de [pi](https://pi.dev)**: `pi install npm:@kudawa/pi-contextmap` (trae el MCP, una skill y 3 prompts). Ver [docs/PI_PACKAGE.md](docs/PI_PACKAGE.md).
112
+
111
113
  ### 🛡️ 4. Reglas y Skills Universales para 10+ Editores e IAs
112
114
  Escribe tus normas una sola vez y ContextMap las inyecta en el formato nativo de cada herramienta:
113
115
  * **Google Antigravity**: `.agents/skills/contextmap/SKILL.md`
@@ -123,7 +125,7 @@ Escribe tus normas una sola vez y ContextMap las inyecta en el formato nativo de
123
125
 
124
126
  ## ⚖️ Comparativa: ContextMap vs. Otras Soluciones
125
127
 
126
- | ¿Qué necesitas? | Copiar y Pegar Todo<br>*(Repomix / Gitingest)* | Indexadores de Editor<br>*(Cursor / Windsurf)* | **ContextMap v2.7.0** |
128
+ | ¿Qué necesitas? | Copiar y Pegar Todo<br>*(Repomix / Gitingest)* | Indexadores de Editor<br>*(Cursor / Windsurf)* | **ContextMap v2.7.2** |
127
129
  | :--- | :---: | :---: | :---: |
128
130
  | **Gasto de Tokens** | 🔴 Altísimo (quema tu dinero) | 🟡 Medio | 🟢 **Mínimo (>99% de ahorro)** |
129
131
  | **Prompt Caching Determinista** | ❌ Incompatible (cambia el hash) | 🟡 Parcial | **✅ Prefijo congelado (>90% Hit Rate)** |
@@ -6,7 +6,7 @@
6
6
 
7
7
  ### *Stop your AI from forgetting architectural decisions, burning tokens, and breaking working code.*
8
8
 
9
- [![Release](https://img.shields.io/badge/version-v2.7.0-blue.svg?style=for-the-badge)](CHANGELOG.md)
9
+ [![Release](https://img.shields.io/badge/version-v2.7.2-blue.svg?style=for-the-badge)](CHANGELOG.md)
10
10
  [![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/)
11
11
  [![Python](https://img.shields.io/badge/python-3.10+-3776AB.svg?style=for-the-badge&logo=python&logoColor=white)](pyproject.toml)
12
12
  [![Tests: 314 Passing](https://img.shields.io/badge/tests-314%2F314%20passing-brightgreen.svg?style=for-the-badge)](context_map/__tests__/)
@@ -123,7 +123,7 @@ Write project rules once, and ContextMap auto-syncs them to every tool's native
123
123
 
124
124
  ## ⚖️ Comparison: ContextMap vs. Alternatives
125
125
 
126
- | What do you need? | Raw Dumps<br>*(Repomix / Gitingest)* | IDE-Locked Indexers<br>*(Cursor / Windsurf)* | **ContextMap v2.7.0** |
126
+ | What do you need? | Raw Dumps<br>*(Repomix / Gitingest)* | IDE-Locked Indexers<br>*(Cursor / Windsurf)* | **ContextMap v2.7.2** |
127
127
  | :--- | :---: | :---: | :---: |
128
128
  | **Token Consumption** | 🔴 Massive (expensive & slow) | 🟡 Medium | 🟢 **Ultra-efficient (>99% savings)** |
129
129
  | **Deterministic Prompt Caching** | ❌ Incompatible (breaks hash) | 🟡 Partial | **✅ Frozen Prefix (>90% Hit Rate)** |
@@ -4,7 +4,7 @@
4
4
 
5
5
  **Documentación de Ingeniería de Software, Patrones de Diseño, AST y Protocolos**
6
6
 
7
- [![Version: v2.7.0](https://img.shields.io/badge/version-v2.7.0-blue.svg?style=for-the-badge)](CHANGELOG.md)
7
+ [![Version: v2.7.2](https://img.shields.io/badge/version-v2.7.2-blue.svg?style=for-the-badge)](CHANGELOG.md)
8
8
  [![Tests: 314 Passing](https://img.shields.io/badge/tests-314%2F314%20passing-brightgreen.svg?style=for-the-badge)](context_map/__tests__/)
9
9
  [![Type Check: Strict MyPy](https://img.shields.io/badge/mypy-100%25%20strict%20blocking-00599C.svg?style=for-the-badge&logo=python&logoColor=white)](.github/workflows/ci.yml)
10
10
  [![Prompt Cache: Optimized](https://img.shields.io/badge/Prompt%20Cache->90%25%20Hit%20Rate-orange.svg?style=for-the-badge)](context_map/presentation/briefs/)
@@ -84,6 +84,8 @@ Para garantizar un Graph View de Obsidian legible, limpio y sin colisiones:
84
84
 
85
85
  El módulo `infrastructure/mcp_server.py` implementa el protocolo **MCP sobre transporte `stdio`**, permitiendo que agentes como **Hermes Agent**, **Cursor**, **Claude Desktop** y **Windsurf** ejecuten 29 herramientas nativas:
86
86
 
87
+ > 🧩 Para **pi** hay un paquete que registra este MCP, añade la skill `contextmap` y 3 prompts: `pi install npm:@kudawa/pi-contextmap` (ver [docs/PI_PACKAGE.md](../docs/PI_PACKAGE.md)).
88
+
87
89
  | Tool MCP | Descripción |
88
90
  | :--- | :--- |
89
91
  | `refresh` | Flujo completo de sincronización (scan + build con preservación + check). |
@@ -0,0 +1,154 @@
1
+ """Pruebas de la extracción de marcadores TODO/FIXME/HACK de comentarios.
2
+
3
+ Caso real de la auditoría 2026-09-21: la heurística buscaba la subcadena
4
+ ``todo``/``bug`` en cualquier línea, así que 647 de los 685 eventos ``TODO``
5
+ de la BD personal eran prosa en español («todo el rango», «todos los módulos»),
6
+ llamadas ``logger.debug`` o líneas de docstring.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from context_map.infrastructure.analyzers.content import extraer_todos
12
+
13
+ CODIGO_CON_FALSOS_POSITIVOS = '''"""Carpetas base que se escanean en ``sync --todos``."""
14
+
15
+ import logging
16
+
17
+ logger = logging.getLogger(__name__)
18
+
19
+
20
+ def cargar():
21
+ """Carga y re-estandariza todo el grafo (por si quedaron viejos)."""
22
+ for item in todos_los_items:
23
+ # Cubre todo el rango y todo dia del periodo
24
+ logger.debug("Encoding detection on empty bytes, %s", item)
25
+ # Avoid unoptimized RSS usage.
26
+ yield item
27
+ '''
28
+
29
+ CODIGO_CON_MARCADORES_REALES = """# TODO: unificar el descubrimiento de proyectos
30
+ import os # FIXME: ruta hardcodeada a F:
31
+ # HACK: parche temporal hasta migrar la BD
32
+ # XXX revisar antes del release
33
+ valor = 1 # TODO(c) pendiente de tipar
34
+ """
35
+
36
+
37
+ def test_no_marca_prosa_en_espanol_ni_debug_ni_docstrings(tmp_path) -> None:
38
+ """La prosa con «todo», `logger.debug` y los docstrings NO son pendientes."""
39
+ archivo = tmp_path / "modulo.py"
40
+ archivo.write_text(CODIGO_CON_FALSOS_POSITIVOS, encoding="utf-8")
41
+
42
+ assert extraer_todos(str(archivo)) == []
43
+
44
+
45
+ def test_detecta_marcadores_reales_en_comentarios(tmp_path) -> None:
46
+ """Los marcadores en MAYÚSCULAS dentro de comentarios sí se detectan."""
47
+ archivo = tmp_path / "modulo.py"
48
+ archivo.write_text(CODIGO_CON_MARCADORES_REALES, encoding="utf-8")
49
+
50
+ todos = extraer_todos(str(archivo))
51
+
52
+ assert len(todos) == 5, todos
53
+ assert todos[0].startswith("L1: # TODO: unificar")
54
+ assert any("FIXME" in t for t in todos)
55
+ assert any("HACK" in t for t in todos)
56
+ assert any("XXX" in t for t in todos)
57
+
58
+
59
+ def test_todo_minuscula_no_es_marcador(tmp_path) -> None:
60
+ """`# todo` en un comentario en español es prosa, no un marcador."""
61
+ archivo = tmp_path / "modulo.py"
62
+ archivo.write_text("# todo esto hay que revisarlo luego\n", encoding="utf-8")
63
+
64
+ assert extraer_todos(str(archivo)) == []
65
+
66
+
67
+ def test_archivo_inexistente_no_falla() -> None:
68
+ """Un archivo ilegible devuelve una lista vacía, sin propagar la excepción."""
69
+ assert extraer_todos("ruta/que/no/existe.py") == []
70
+
71
+
72
+ CODIGO_CON_NEGRITA_MARKDOWN = '''"""Docstring que explica el filtro: los **TODO** crudos del código se excluyen."""
73
+
74
+
75
+
76
+ def f():
77
+ """Ojo con **TODO** en negrita y con *TODO suelto en prosa."""
78
+ return 1
79
+ '''
80
+
81
+
82
+ CODIGO_CON_COMENTARIOS_DE_BLOQUE = '''/* TODO: refactorizar el parser */
83
+ * FIXME: ojo con el encoding
84
+ /** HACK: puente temporal */
85
+ <!-- TODO: documentar la API -->
86
+ '''
87
+
88
+
89
+ def test_negrita_markdown_no_es_marcador(tmp_path) -> None:
90
+ """Regresión 2026-10-08: ``**TODO**`` (negrita Markdown) no es un comentario.
91
+
92
+ Dogfooding: al documentar el filtro del diario, el propio docstring generó un
93
+ nodo ``TODO (…/canvas.py:L203)`` porque ``*`` contaba como signo de comentario.
94
+ """
95
+ archivo = tmp_path / "modulo.py"
96
+ archivo.write_text(CODIGO_CON_NEGRITA_MARKDOWN, encoding="utf-8")
97
+
98
+ assert extraer_todos(str(archivo)) == []
99
+
100
+
101
+ def test_marcadores_en_comentarios_de_bloque_siguen_detectandose(tmp_path) -> None:
102
+ """``/* TODO */``, `` * TODO`` y ``<!-- TODO -->`` siguen siendo marcadores.
103
+
104
+ Se prueba en un ``.ts``: los comentarios de bloque no existen en Python, así
105
+ que ahí manda la heurística por líneas (no ``tokenize``).
106
+ """
107
+ archivo = tmp_path / "modulo.ts"
108
+ archivo.write_text(CODIGO_CON_COMENTARIOS_DE_BLOQUE, encoding="utf-8")
109
+
110
+ todos = extraer_todos(str(archivo))
111
+
112
+ assert len(todos) == 4, todos
113
+ assert any("refactorizar el parser" in t for t in todos)
114
+ assert any("encoding" in t for t in todos)
115
+ assert any("puente temporal" in t for t in todos)
116
+ assert any("documentar la API" in t for t in todos)
117
+
118
+
119
+ def test_python_solo_mira_comentarios_reales(tmp_path) -> None:
120
+ """En ``.py`` un marcador dentro de un string o docstring NO cuenta.
121
+
122
+ Regresión 2026-10-08: documentar el filtro generó nodos TODO fantasma porque
123
+ se miraban líneas sueltas. Con ``tokenize`` solo cuentan los comentarios.
124
+ """
125
+ archivo = tmp_path / "doc.py"
126
+ archivo.write_text(
127
+ '"""Docstring que cita el marcador # TODO: no es un pendiente."""\n'
128
+ '\n'
129
+ 'MENSAJE = "# FIXME: tampoco, es un string"\n'
130
+ 'RE = r"# TODO"\n'
131
+ '\n'
132
+ 'x = 1 # TODO: este sí es un pendiente real\n',
133
+ encoding="utf-8",
134
+ )
135
+
136
+ todos = extraer_todos(str(archivo))
137
+
138
+ assert len(todos) == 1, todos
139
+ assert "pendiente real" in todos[0]
140
+
141
+
142
+ def test_javascript_usa_la_heuristica_por_lineas(tmp_path) -> None:
143
+ """En lenguajes sin ``tokenize`` se mantiene la heurística por líneas."""
144
+ archivo = tmp_path / "app.ts"
145
+ archivo.write_text(
146
+ '// TODO: tipar el retorno\n'
147
+ 'const x = 1;\n',
148
+ encoding="utf-8",
149
+ )
150
+
151
+ todos = extraer_todos(str(archivo))
152
+
153
+ assert len(todos) == 1, todos
154
+ assert "tipar el retorno" in todos[0]
@@ -101,3 +101,54 @@ def test_preserva_contenido_del_agente_despues_del_scanner(tmp_path):
101
101
  assert "Nodo B" in final
102
102
  assert final.count("🤖 Ingresados por el scanner") == 1
103
103
 
104
+
105
+ def test_excluye_todos_de_codigo_y_conserva_los_conversados(tmp_path):
106
+ """Los TODO crudos del código NO se vuelcan al diario; los conversados sí.
107
+
108
+ Dogfooding 2026-10-08: el diario acumulaba volcados de código (p. ej.
109
+ ``TODO (context_map/x.py:L10): return valor``), que son deuda técnica y
110
+ viven en ``5.0-BACKLOG/5.1-Tareas``. El filtro ``_es_todo_codigo`` que ya
111
+ usaban backlog/historial/ideas faltaba en el generador del diario.
112
+ """
113
+ out = str(tmp_path)
114
+ hoy = date.today().isoformat()
115
+ codigo = Node(
116
+ id="t1",
117
+ type="FUTURO",
118
+ title="TODO (context_map/app.py:L10): return valor # pendiente de tipar",
119
+ created_at=f"{hoy}T10:00:00",
120
+ )
121
+ prueba = Node(
122
+ id="t2",
123
+ type="FUTURO",
124
+ title="TODO (context_map/__tests__/test_x.py:L5): assert algo",
125
+ created_at=f"{hoy}T10:30:00",
126
+ )
127
+ conversado = Node(
128
+ id="t3",
129
+ type="FUTURO",
130
+ title="Revisar el flujo de captura móvil con el usuario",
131
+ created_at=f"{hoy}T11:00:00",
132
+ )
133
+
134
+ render_nota_dia(out, "MiProyecto", [codigo, prueba, conversado])
135
+ contenido = _ruta_diario(tmp_path, hoy).read_text(encoding="utf-8")
136
+
137
+ assert "Revisar el flujo de captura móvil" in contenido
138
+ assert "return valor" not in contenido
139
+ assert "assert algo" not in contenido
140
+
141
+
142
+ def test_sin_nodos_utiles_no_crea_diario(tmp_path):
143
+ """Un día con solo TODO de código no genera diario (sin ruido)."""
144
+ out = str(tmp_path)
145
+ hoy = date.today().isoformat()
146
+ solo_codigo = Node(
147
+ id="t1",
148
+ type="FUTURO",
149
+ title="TODO (context_map/app.py:L10): def foo(): pass",
150
+ created_at=f"{hoy}T10:00:00",
151
+ )
152
+ assert render_nota_dia(out, "MiProyecto", [solo_codigo]) is None
153
+ assert not _ruta_diario(tmp_path, hoy).exists()
154
+
@@ -82,7 +82,31 @@ def test_comandos_de_portapapeles_por_plataforma(monkeypatch) -> None:
82
82
 
83
83
  monkeypatch.setattr(clip.sys, "platform", "win32")
84
84
  monkeypatch.setattr(clip.os, "name", "nt")
85
- assert clip._comandos_portapapeles()[0][0] == "powershell"
85
+ comando = clip._comandos_portapapeles()[0]
86
+ assert comando[0] == "powershell"
87
+ assert "UTF8" in comando[-1], "PowerShell debe forzar salida UTF-8"
88
+
89
+
90
+ def test_lectura_decodifica_siempre_como_utf8(monkeypatch) -> None:
91
+ """Regresión (2026-10-08): los acentos se corrompían ("Recuperación" →
92
+ "Recuperaci¢n") porque se leía con la codificación local de la consola.
93
+
94
+ Ahora la lectura pasa ``encoding="utf-8"`` y NUNCA ``text=True``.
95
+ """
96
+ capturado: dict[str, object] = {}
97
+
98
+ def _run(_comando, **kwargs):
99
+ capturado.update(kwargs)
100
+ return NS(returncode=0, stdout="Recuperación · ñ á é — BM25")
101
+
102
+ monkeypatch.setattr(clip.shutil, "which", lambda _cmd: "/usr/bin/falso")
103
+ monkeypatch.setattr(clip.subprocess, "run", _run)
104
+
105
+ texto = clip.leer_portapapeles([["falso"]])
106
+
107
+ assert texto == "Recuperación · ñ á é — BM25", "Los acentos deben sobrevivir"
108
+ assert capturado.get("encoding") == "utf-8"
109
+ assert "text" not in capturado, "No debe usarse la codificación local (text=True)"
86
110
 
87
111
 
88
112
  def test_desglosar_markdown_extrae_titulo_y_url() -> None:
@@ -79,6 +79,50 @@ def test_bm25_prefiere_mayor_frecuencia(tmp_path) -> None:
79
79
  assert resultados[0]["titulo"] == "Mucho vector"
80
80
 
81
81
 
82
+ def test_frases_descarta_titulos_y_ruido_matematico() -> None:
83
+ """Dogfooding 2026-10-08: la síntesis citaba títulos y fórmulas en vez de prosa.
84
+
85
+ Con la página real de Wikipedia de BM25, la respuesta extractiva devolvía
86
+ "# BM25 — ranking de relevancia" y "(Redirigido desde …)" en lugar de la
87
+ definición. ``_frases`` debe quedarse solo con prosa informativa.
88
+ """
89
+ texto = (
90
+ "# BM25 — ranking de relevancia\n"
91
+ "[Ir al contenido](#bodyContent)\n"
92
+ "(Redirigido desde «[BM25](/w/index.php?title=BM25&redirect=no)»)\n"
93
+ "**Okapi BM25** es una función de ranking utilizada en recuperación de "
94
+ "información para asignar relevancia a los documentos de un buscador.\n"
95
+ "s c o r e ( D , Q ) = \u2211 i = 1 n I D F ( q i ) \u22c5 f ( q i , D ) "
96
+ "{\\displaystyle score(D,Q)}\n"
97
+ "- [Modelo de espacio vectorial](https://es.wikipedia.org/wiki/Modelo)\n"
98
+ )
99
+ frases = kb._frases(texto)
100
+
101
+ assert any("es una función de ranking" in f for f in frases), frases
102
+ assert all(not f.startswith("#") for f in frases)
103
+ assert not any("w/index.php" in f for f in frases)
104
+ assert not any("displaystyle" in f for f in frases)
105
+
106
+
107
+ def test_sintetizar_devuelve_la_definicion_no_el_titulo(tmp_path) -> None:
108
+ """La respuesta extractiva cita la definición y no la cabecera de la página."""
109
+ vdir = _vault(tmp_path)
110
+ kb.ingresar(
111
+ vdir,
112
+ "BM25 — ranking de relevancia",
113
+ "# BM25 — ranking de relevancia\n\n"
114
+ "(Redirigido desde «BM25»)\n\n"
115
+ "Okapi BM25 es una función de ranking utilizada en recuperación de "
116
+ "información para asignar relevancia a los documentos de un buscador.",
117
+ entidades="BM25",
118
+ )
119
+
120
+ resultado = kb.sintetizar(vdir, "¿para qué sirve BM25?")
121
+
122
+ assert "función de ranking" in resultado["respuesta"]
123
+ assert "Redirigido" not in resultado["respuesta"]
124
+
125
+
82
126
  def test_moc_agrupa_conceptos_con_sus_fuentes(tmp_path) -> None:
83
127
  """G9: el MOC se genera en una pasada y agrupa cada concepto con sus fuentes."""
84
128
  vdir = _vault(tmp_path)
@@ -71,6 +71,12 @@ def desglosar_markdown(texto: str) -> tuple[str, str]:
71
71
  return coincidencia.group(1).strip(), coincidencia.group(2).strip()
72
72
 
73
73
 
74
+ _PS_GET_CLIPBOARD_UTF8 = (
75
+ "[Console]::OutputEncoding=[System.Text.Encoding]::UTF8; Get-Clipboard"
76
+ )
77
+ """Lectura del portapapeles en Windows forzando salida UTF-8 (ver abajo)."""
78
+
79
+
74
80
  def _comandos_portapapeles() -> list[list[str]]:
75
81
  """Comandos nativos para leer el portapapeles, en orden de preferencia.
76
82
 
@@ -81,7 +87,10 @@ def _comandos_portapapeles() -> list[list[str]]:
81
87
  if sys.platform == "darwin":
82
88
  return [["pbpaste"]]
83
89
  if os.name == "nt":
84
- return [["powershell", "-NoProfile", "-Command", "Get-Clipboard"]]
90
+ # ``[Console]::OutputEncoding=...UTF8`` es OBLIGATORIO: sin ello
91
+ # PowerShell escribe en la página de códigos de la consola (cp850/cp1252)
92
+ # y los acentos se corrompen ("Recuperación" → "Recuperaci¢n").
93
+ return [["powershell", "-NoProfile", "-Command", _PS_GET_CLIPBOARD_UTF8]]
85
94
  return [
86
95
  ["wl-paste", "-n"],
87
96
  ["xclip", "-selection", "clipboard", "-o"],
@@ -92,6 +101,10 @@ def _comandos_portapapeles() -> list[list[str]]:
92
101
  def leer_portapapeles(comandos: list[list[str]] | None = None, timeout: int = 10) -> str:
93
102
  """Lee el portapapeles del sistema con herramientas nativas (sin dependencias).
94
103
 
104
+ El resultado se decodifica **siempre como UTF-8** (no con la codificación
105
+ local): así los acentos, la eñe y los símbolos sobreviven en cualquier
106
+ idioma.
107
+
95
108
  Args:
96
109
  comandos (list[list[str]] | None): Comandos a probar (inyectable en
97
110
  tests). Si es None, se autodetectan por plataforma.
@@ -106,7 +119,12 @@ def leer_portapapeles(comandos: list[list[str]] | None = None, timeout: int = 10
106
119
  continue
107
120
  try:
108
121
  resultado = subprocess.run(
109
- comando, capture_output=True, text=True, timeout=timeout, check=False,
122
+ comando,
123
+ capture_output=True,
124
+ timeout=timeout,
125
+ check=False,
126
+ encoding="utf-8",
127
+ errors="replace",
110
128
  )
111
129
  except Exception: # noqa: BLE001 — portapapeles es best-effort
112
130
  continue
@@ -302,19 +302,63 @@ _NEGACIONES = re.compile(
302
302
  )
303
303
 
304
304
 
305
+ def _limpiar_frase(frase: str) -> str:
306
+ """Quita marcadores Markdown de una frase (énfasis, enlaces, listas).
307
+
308
+ Args:
309
+ frase (str): Fragmento crudo de Markdown.
310
+
311
+ Returns:
312
+ str: Texto en prosa, con espacios normalizados.
313
+ """
314
+ sin_enfasis = re.sub(r"\*\*([^*]+)\*\*", r"\1", frase)
315
+ sin_enlaces = re.sub(r"\[([^\]]+)\]\([^)]*\)", r"\1", sin_enfasis)
316
+ sin_marcadores = re.sub(r"^\s*(?:[-*>]\s+)+\s*", "", sin_enlaces)
317
+ return re.sub(r"\s+", " ", sin_marcadores).strip()
318
+
319
+
320
+ def _es_frase_util(frase: str) -> bool:
321
+ """Heurística: ¿la frase es prosa informativa y no ruido estructural?
322
+
323
+ Descarta títulos, ítems de índice, referencias a URLs y bloques de fórmulas
324
+ (muy simbólicos para el poco texto que aportan), que ensuciaban las
325
+ respuestas extractivas.
326
+
327
+ Args:
328
+ frase (str): Frase ya limpiada.
329
+
330
+ Returns:
331
+ bool: True si sirve como fuente de una respuesta.
332
+ """
333
+ if len(frase) < 40:
334
+ return False
335
+ if "\\displaystyle" in frase or "w/index.php" in frase:
336
+ return False
337
+ letras_y_espacios = sum(c.isalpha() or c.isspace() for c in frase)
338
+ return letras_y_espacios / len(frase) >= 0.7
339
+
340
+
305
341
  def _frases(texto: str) -> list[str]:
306
- """Divide un texto en frases útiles (sin frontmatter ni wikilinks).
342
+ """Divide un texto en frases de prosa útiles (sin frontmatter ni wikilinks).
307
343
 
308
344
  Args:
309
345
  texto (str): Contenido Markdown de una página.
310
346
 
311
347
  Returns:
312
- list[str]: Frases de al menos 30 caracteres.
348
+ list[str]: Frases informativas, limpias de marcadores Markdown.
313
349
  """
314
350
  plano = re.sub(r"^---\n.*?\n---\n", "", texto, flags=re.DOTALL)
315
351
  plano = re.sub(r"\[\[([^|\]]+\|)?([^\]]+)\]\]", r"\2", plano)
316
352
  partes = re.split(r"(?<=[.!?])\s+|\n+", plano)
317
- return [p.strip() for p in partes if len(p.strip()) >= 30]
353
+ frases: list[str] = []
354
+ for parte in partes:
355
+ bruta = parte.strip()
356
+ if not bruta or bruta.startswith("#"):
357
+ continue # Los títulos ya los aporta el título de la página.
358
+ limpia = _limpiar_frase(bruta)
359
+ if _es_frase_util(limpia):
360
+ frases.append(limpia)
361
+ return frases
318
362
 
319
363
 
320
364
  def _mejores_frases(ruta: str, q_tokens: set[str], max_frases: int = 2) -> list[str]:
@@ -97,7 +97,8 @@ def extraer_funciones(ruta: str) -> list[str]:
97
97
 
98
98
 
99
99
  _RE_MARCADOR_TODO = re.compile(
100
- r"(?:#|//|/\*|\*|<!--|--)\s*(?:TODO|FIXME|HACK|BUG|OPTIMIZE|XXX)[Ss]?\b:?"
100
+ r"(?:#|//|/\*+|^\s*\*(?!\*)|<!--|--)\s*(?:TODO|FIXME|HACK|BUG|OPTIMIZE|XXX)[Ss]?\b:?",
101
+ re.MULTILINE,
101
102
  )
102
103
  """Marcador de tarea pendiente: requiere signo de comentario y palabra en MAYÚSCULAS.
103
104
 
@@ -107,11 +108,50 @@ módulos»), las llamadas ``logger.debug(...)`` y los docstrings. En la BD perso
107
108
  eso produjo 647 de 685 eventos ``TODO`` falsos (94%). La coincidencia en
108
109
  mayúsculas es deliberada: ``# todo`` en un comentario en español es prosa, no un
109
110
  marcador; ``# TODO`` sí lo es.
111
+
112
+ Ajuste 2026-10-08: el ``*`` suelto solo cuenta como comentario de bloque al
113
+ inicio de línea y si no es el primero de una negrita (``**TODO**`` en Markdown
114
+ no es un pendiente). ``/*`` y ``/**`` siguen valiendo dentro de la línea.
110
115
  """
111
116
 
112
117
 
118
+ def _todos_de_python(ruta: str) -> list[str] | None:
119
+ """Marcadores hallados en COMENTARIOS reales de un ``.py`` (con ``tokenize``).
120
+
121
+ Evita la clase de falsos positivos más terca: un docstring o un string que
122
+ *menciona* un marcador (p. ej. documentar ``# TODO`` o escribir ``**TODO**``
123
+ en negrita) no es un pendiente del código. Devuelve None si el archivo no es
124
+ un Python válido, para que el llamador use la heurística por líneas.
125
+
126
+ Args:
127
+ ruta (str): Ruta del archivo Python.
128
+
129
+ Returns:
130
+ list[str] | None: Líneas ``L<n>: <comentario>`` (máx. 10), o None si no
131
+ se pudo tokenizar.
132
+ """
133
+ import tokenize
134
+
135
+ encontrados: list[str] = []
136
+ try:
137
+ with open(ruta, "rb") as f:
138
+ for token in tokenize.tokenize(f.readline):
139
+ if token.type == tokenize.COMMENT and _RE_MARCADOR_TODO.search(token.string):
140
+ encontrados.append(f"L{token.start[0]}: {token.string.strip()[:100]}")
141
+ if len(encontrados) >= 10:
142
+ break
143
+ except Exception as err: # noqa: BLE001 — sintaxis inválida: fallback por líneas
144
+ logger.debug("No se pudo tokenizar %s: %s", ruta, err)
145
+ return None
146
+ return encontrados
147
+
148
+
113
149
  def extraer_todos(ruta: str) -> list[str]:
114
- """Extrae los marcadores TODO/FIXME/HACK/BUG/OPTIMIZE/XXX escritos en comentarios.
150
+ """Extrae los marcadores TODO/FIXME/HACK/BUG/OPTIMIZE/XXX de comentarios.
151
+
152
+ En archivos ``.py`` analiza **solo comentarios** (``tokenize``), de modo que
153
+ docstrings y strings que mencionan un marcador no cuentan. En el resto de
154
+ lenguajes (JS/TS/C/HTML/SQL…) usa la heurística por líneas.
115
155
 
116
156
  Args:
117
157
  ruta (str): Ruta del archivo a analizar.
@@ -120,6 +160,11 @@ def extraer_todos(ruta: str) -> list[str]:
120
160
  list[str]: Líneas con marcador, con el formato ``L<n>: <texto>``
121
161
  (máximo 10 por archivo).
122
162
  """
163
+ if ruta.endswith(".py"):
164
+ comentarios = _todos_de_python(ruta)
165
+ if comentarios is not None:
166
+ return comentarios
167
+
123
168
  todos = []
124
169
  try:
125
170
  with open(ruta, encoding="utf-8", errors="ignore") as f: