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.
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/copilot-instructions.md +1 -1
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CHANGELOG.md +42 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/PKG-INFO +5 -3
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/README.md +4 -2
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/README_EN.md +2 -2
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/README_TECNICO.md +3 -1
- context_map_ai-2.7.2/context_map/__tests__/test_analyzers_content.py +154 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_diario_consolidado.py +51 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_web_clipper.py +25 -1
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_wiki_avanzado.py +44 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/clip.py +20 -2
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/wiki.py +47 -3
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/content.py +47 -2
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/canvas.py +7 -0
- context_map_ai-2.7.2/docs/PI_PACKAGE.md +62 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/pyproject.toml +1 -1
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/uv.lock +1 -1
- context_map_ai-2.7.0/context_map/__tests__/test_analyzers_content.py +0 -69
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.agents/skills/contextmap/SKILL.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/workflows/ci.yml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/workflows/docs.yml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.github/workflows/publish.yml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.gitignore +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/config.yaml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/shields/pre-commit.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/triggers/post-commit.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/workflows/contextmap.yaml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.hermes/workflows/dev-loop.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/.pre-commit-hooks.yaml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/AGENTS.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CLAUDE.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CODE_OF_CONDUCT.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/CONTRIBUTING.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/LICENSE +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/Makefile +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/action.yml +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/conftest.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/smoke.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ast_audit.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ast_summary.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_brief_identidad.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_build_sin_eventos.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_captura.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_cargadores_chats.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_checker_frescura.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_clasificacion_chat.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_cli.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_conexiones.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_conocimiento_brief.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_cyclomatic.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_dependencias_mcp.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_doctor.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_domain_extractor.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ecosistema.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_embeddings_opcionales.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_enrich.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_enterprise_features.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_exclusiones_escaneo.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_export.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_fechas_conversaciones.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hardware.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hermes_clasificacion.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hook_memoria_viva.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_hooks.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_humanizacion.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_inbox.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ingest.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ingesta_rica.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_llm_opcional.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_mcp_server_seguridad.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_mermaid.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_metrica_memoria_viva.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_obsidian_config.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_ollama.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_pack_unpack.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_parsing.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_identidad.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_panorama.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_repair.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_ruta.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_personal_sync.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_prompt_caching.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_readiness_signals.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_refresh_multicanal.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_refresh_temporales.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_reglas_catalogo.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_review.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_scan_cache.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_scanner.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_security.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_security_scanner.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_similarity.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_standardize.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_templates_filenames.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_tokenization.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_topologia_arbol.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_vault_consolidation.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_watcher.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_wiki.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_wrap.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/cli/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/cli/cli.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/cli/parser.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/_helpers.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/adapt.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/auto.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/build.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/doctor_cmd.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/enrich.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/export.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/hook.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/importers.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/inbox.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/ingest.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/pack.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/common.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/export.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/panorama.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/query.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/repair.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/sync.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/personal/timeline.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/refresh.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/review.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/scan.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/sync.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/tools.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/update.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/watch.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/wiki.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/application/commands/wrap.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/cli.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/generators/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/generators/generadores.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/generators/narrative_templates.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/logging_setup.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/models/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/models/config.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/models/models.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/cleaning.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/humanizacion.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/inference.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/mappings.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/normalization/standardize.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/cargadores.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/clasificacion.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/dedup.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/parsing/grafo.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/bd.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/panorama.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/personal/repair.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/storage/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/storage/config_loader.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/storage/store.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/core/tokenization.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/checker.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/models.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/reporting.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analysis/signals.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/ast_audit.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/ast_summary.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/analyzers/cyclomatic.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/adaptador.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/detector.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/hooks.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ecosystem/rules_templates.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/health/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/health/doctor.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/health/hardware.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ingestion/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/ingestion/ingest.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/captura.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/embeddings.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/inbox.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/indices.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/llm.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/knowledge/review.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/normalization/domain_extractor.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/normalization/similarity.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/reglas/reglas.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/reporting/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/reporting/reporter.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/cache.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/scanner.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/scanning/security.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/storage/packer.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/relaciones.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/sync.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/domain/synchronization/watcher.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/exclusions.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/structure.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/antigravity.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/chat_export.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/git.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/hermes.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/integrations/ollama.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/mcp_server.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/version_check.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/agents.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/brief.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/extractors.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/sections.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/briefs/skill.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/atomic.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/__init__.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/conocimiento.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/consolidado.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/dominios.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/escritura.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/graph_style.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/jerarquico.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/nodos.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/notas_ideas.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/obsidian_config.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/readme_extract.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/rutas.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_backlog.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_estructura.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_historial.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_ideas.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_proposito.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/consolidated/secciones_riesgos.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/mermaid.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/preservar.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/templates.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/presentation/vault/writer.py +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/LAUNCH_KIT.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/LLM.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/WEB_CLIPPER.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/plans/2026-08-10-mapa-mental-conectado.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/docs/plans/PLAN-MEJORAS-CONTEXTO-VIVO-2026-08-14.md +0 -0
- {context_map_ai-2.7.0 → context_map_ai-2.7.2}/mkdocs.yml +0 -0
|
@@ -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.
|
|
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
|
-
[](CHANGELOG.md)
|
|
46
46
|
[](https://pypi.org/project/context-map-ai/)
|
|
47
47
|
[](pyproject.toml)
|
|
48
48
|
[](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.
|
|
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
|
-
[](CHANGELOG.md)
|
|
10
10
|
[](https://pypi.org/project/context-map-ai/)
|
|
11
11
|
[](pyproject.toml)
|
|
12
12
|
[](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.
|
|
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
|
-
[](CHANGELOG.md)
|
|
10
10
|
[](https://pypi.org/project/context-map-ai/)
|
|
11
11
|
[](pyproject.toml)
|
|
12
12
|
[](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.
|
|
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
|
-
[](CHANGELOG.md)
|
|
8
8
|
[](context_map/__tests__/)
|
|
9
9
|
[](.github/workflows/ci.yml)
|
|
10
10
|
[](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]
|
{context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/__tests__/test_diario_consolidado.py
RENAMED
|
@@ -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
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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
|
-
|
|
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]:
|
{context_map_ai-2.7.0 → context_map_ai-2.7.2}/context_map/infrastructure/analyzers/content.py
RENAMED
|
@@ -97,7 +97,8 @@ def extraer_funciones(ruta: str) -> list[str]:
|
|
|
97
97
|
|
|
98
98
|
|
|
99
99
|
_RE_MARCADOR_TODO = re.compile(
|
|
100
|
-
r"(
|
|
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
|
|
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:
|