context-map-ai 2.0.0__py3-none-any.whl

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 (148) hide show
  1. context_map/__init__.py +1 -0
  2. context_map/__tests__/conftest.py +33 -0
  3. context_map/__tests__/smoke.py +23 -0
  4. context_map/__tests__/test_checker_frescura.py +122 -0
  5. context_map/__tests__/test_cli.py +134 -0
  6. context_map/__tests__/test_conexiones.py +675 -0
  7. context_map/__tests__/test_cyclomatic.py +44 -0
  8. context_map/__tests__/test_diario_consolidado.py +76 -0
  9. context_map/__tests__/test_doctor.py +43 -0
  10. context_map/__tests__/test_ecosistema.py +448 -0
  11. context_map/__tests__/test_enterprise_features.py +338 -0
  12. context_map/__tests__/test_export.py +43 -0
  13. context_map/__tests__/test_hermes_clasificacion.py +73 -0
  14. context_map/__tests__/test_hook_memoria_viva.py +45 -0
  15. context_map/__tests__/test_hooks.py +35 -0
  16. context_map/__tests__/test_ingest.py +155 -0
  17. context_map/__tests__/test_mermaid.py +59 -0
  18. context_map/__tests__/test_metrica_memoria_viva.py +87 -0
  19. context_map/__tests__/test_parsing.py +65 -0
  20. context_map/__tests__/test_personal.py +489 -0
  21. context_map/__tests__/test_refresh_temporales.py +34 -0
  22. context_map/__tests__/test_reglas_catalogo.py +146 -0
  23. context_map/__tests__/test_scan_cache.py +30 -0
  24. context_map/__tests__/test_scanner.py +68 -0
  25. context_map/__tests__/test_security_scanner.py +40 -0
  26. context_map/__tests__/test_standardize.py +52 -0
  27. context_map/__tests__/test_tokenization.py +115 -0
  28. context_map/__tests__/test_topologia_arbol.py +307 -0
  29. context_map/__tests__/test_vault_consolidation.py +441 -0
  30. context_map/__tests__/test_watcher.py +29 -0
  31. context_map/__tests__/test_wrap.py +79 -0
  32. context_map/application/__init__.py +10 -0
  33. context_map/application/cli/__init__.py +8 -0
  34. context_map/application/cli/cli.py +139 -0
  35. context_map/application/cli/parser.py +204 -0
  36. context_map/application/commands/__init__.py +60 -0
  37. context_map/application/commands/_helpers.py +348 -0
  38. context_map/application/commands/adapt.py +85 -0
  39. context_map/application/commands/auto.py +56 -0
  40. context_map/application/commands/build.py +166 -0
  41. context_map/application/commands/doctor_cmd.py +56 -0
  42. context_map/application/commands/export.py +136 -0
  43. context_map/application/commands/hook.py +49 -0
  44. context_map/application/commands/importers.py +163 -0
  45. context_map/application/commands/ingest.py +133 -0
  46. context_map/application/commands/personal.py +555 -0
  47. context_map/application/commands/refresh.py +138 -0
  48. context_map/application/commands/scan.py +38 -0
  49. context_map/application/commands/sync.py +179 -0
  50. context_map/application/commands/tools.py +127 -0
  51. context_map/application/commands/update.py +144 -0
  52. context_map/application/commands/watch.py +27 -0
  53. context_map/application/commands/wrap.py +89 -0
  54. context_map/cli.py +26 -0
  55. context_map/core/__init__.py +68 -0
  56. context_map/core/generators/__init__.py +13 -0
  57. context_map/core/generators/generadores.py +186 -0
  58. context_map/core/generators/narrative_templates.py +264 -0
  59. context_map/core/logging_setup.py +71 -0
  60. context_map/core/models/__init__.py +7 -0
  61. context_map/core/models/config.py +17 -0
  62. context_map/core/models/models.py +172 -0
  63. context_map/core/normalization/__init__.py +27 -0
  64. context_map/core/normalization/cleaning.py +135 -0
  65. context_map/core/normalization/inference.py +209 -0
  66. context_map/core/normalization/mappings.py +116 -0
  67. context_map/core/normalization/standardize.py +145 -0
  68. context_map/core/parsing/__init__.py +19 -0
  69. context_map/core/parsing/cargadores.py +108 -0
  70. context_map/core/parsing/clasificacion.py +45 -0
  71. context_map/core/parsing/dedup.py +28 -0
  72. context_map/core/parsing/grafo.py +78 -0
  73. context_map/core/personal/__init__.py +23 -0
  74. context_map/core/personal/bd.py +527 -0
  75. context_map/core/storage/__init__.py +17 -0
  76. context_map/core/storage/config_loader.py +62 -0
  77. context_map/core/storage/store.py +214 -0
  78. context_map/core/tokenization.py +258 -0
  79. context_map/domain/__init__.py +49 -0
  80. context_map/domain/analysis/__init__.py +17 -0
  81. context_map/domain/analysis/checker.py +48 -0
  82. context_map/domain/analysis/complexity.py +134 -0
  83. context_map/domain/analysis/models.py +46 -0
  84. context_map/domain/analysis/reporting.py +95 -0
  85. context_map/domain/analysis/signals.py +411 -0
  86. context_map/domain/analyzers/cyclomatic.py +125 -0
  87. context_map/domain/ecosystem/__init__.py +27 -0
  88. context_map/domain/ecosystem/adaptador.py +229 -0
  89. context_map/domain/ecosystem/detector.py +436 -0
  90. context_map/domain/ecosystem/hooks.py +131 -0
  91. context_map/domain/ecosystem/rules_templates.py +396 -0
  92. context_map/domain/health/__init__.py +24 -0
  93. context_map/domain/health/doctor.py +321 -0
  94. context_map/domain/ingestion/__init__.py +19 -0
  95. context_map/domain/ingestion/ingest.py +292 -0
  96. context_map/domain/reglas/reglas.py +244 -0
  97. context_map/domain/reporting/__init__.py +13 -0
  98. context_map/domain/reporting/reporter.py +189 -0
  99. context_map/domain/scanning/__init__.py +13 -0
  100. context_map/domain/scanning/cache.py +116 -0
  101. context_map/domain/scanning/scanner.py +307 -0
  102. context_map/domain/scanning/security.py +79 -0
  103. context_map/domain/synchronization/__init__.py +7 -0
  104. context_map/domain/synchronization/relaciones.py +67 -0
  105. context_map/domain/synchronization/sync.py +249 -0
  106. context_map/domain/synchronization/watcher.py +142 -0
  107. context_map/infrastructure/__init__.py +4 -0
  108. context_map/infrastructure/analyzers/__init__.py +4 -0
  109. context_map/infrastructure/analyzers/content.py +182 -0
  110. context_map/infrastructure/analyzers/structure.py +194 -0
  111. context_map/infrastructure/integrations/__init__.py +4 -0
  112. context_map/infrastructure/integrations/antigravity.py +398 -0
  113. context_map/infrastructure/integrations/chat_export.py +217 -0
  114. context_map/infrastructure/integrations/git.py +147 -0
  115. context_map/infrastructure/integrations/hermes.py +304 -0
  116. context_map/infrastructure/mcp_server.py +279 -0
  117. context_map/infrastructure/version_check.py +192 -0
  118. context_map/presentation/__init__.py +20 -0
  119. context_map/presentation/briefs/__init__.py +9 -0
  120. context_map/presentation/briefs/agents.py +161 -0
  121. context_map/presentation/briefs/brief.py +107 -0
  122. context_map/presentation/briefs/extractors.py +167 -0
  123. context_map/presentation/briefs/sections.py +183 -0
  124. context_map/presentation/briefs/skill.py +224 -0
  125. context_map/presentation/vault/__init__.py +25 -0
  126. context_map/presentation/vault/atomic.py +420 -0
  127. context_map/presentation/vault/consolidated/__init__.py +22 -0
  128. context_map/presentation/vault/consolidated/canvas.py +281 -0
  129. context_map/presentation/vault/consolidated/common.py +611 -0
  130. context_map/presentation/vault/consolidated/consolidado.py +479 -0
  131. context_map/presentation/vault/consolidated/jerarquico.py +262 -0
  132. context_map/presentation/vault/consolidated/notas_ideas.py +232 -0
  133. context_map/presentation/vault/consolidated/rutas.py +149 -0
  134. context_map/presentation/vault/consolidated/secciones_backlog.py +131 -0
  135. context_map/presentation/vault/consolidated/secciones_estructura.py +248 -0
  136. context_map/presentation/vault/consolidated/secciones_historial.py +252 -0
  137. context_map/presentation/vault/consolidated/secciones_ideas.py +331 -0
  138. context_map/presentation/vault/consolidated/secciones_proposito.py +220 -0
  139. context_map/presentation/vault/consolidated/secciones_riesgos.py +187 -0
  140. context_map/presentation/vault/mermaid.py +150 -0
  141. context_map/presentation/vault/preservar.py +121 -0
  142. context_map/presentation/vault/templates.py +197 -0
  143. context_map/presentation/vault/writer.py +160 -0
  144. context_map_ai-2.0.0.dist-info/METADATA +270 -0
  145. context_map_ai-2.0.0.dist-info/RECORD +148 -0
  146. context_map_ai-2.0.0.dist-info/WHEEL +4 -0
  147. context_map_ai-2.0.0.dist-info/entry_points.txt +2 -0
  148. context_map_ai-2.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1 @@
1
+ __all__ = ["build_map"]
@@ -0,0 +1,33 @@
1
+ """Configuración compartida de pytest.
2
+
3
+ Aísla la base de datos personal de ContextMap durante los tests: cualquier
4
+ ``do_sync`` (build/scan/refresh) que se ejecute en la suite consolida en una
5
+ BD temporal, jamás en la BD personal real del usuario (F: / ~/.context-map).
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import contextlib
11
+ import os
12
+ import tempfile
13
+ from collections.abc import Generator
14
+
15
+ import pytest
16
+
17
+
18
+ @pytest.fixture(autouse=True)
19
+ def _aislar_bd_personal(monkeypatch: pytest.MonkeyPatch) -> Generator[None, None, None]:
20
+ """Redirige CTXMAP_PERSONAL_DB a una ruta temporal por test.
21
+
22
+ Args:
23
+ monkeypatch: Fixture de pytest para parchear variables de entorno.
24
+
25
+ Yields:
26
+ None: cede el control al test con la variable ya parcheada.
27
+ """
28
+ fd, ruta_tmp = tempfile.mkstemp(prefix="ctxmap_test_personal_", suffix=".db")
29
+ os.close(fd)
30
+ monkeypatch.setenv("CTXMAP_PERSONAL_DB", ruta_tmp)
31
+ yield
32
+ with contextlib.suppress(OSError):
33
+ os.remove(ruta_tmp)
@@ -0,0 +1,23 @@
1
+ from __future__ import annotations
2
+
3
+ from context_map.core.models import Edge, Node
4
+ from context_map.presentation.vault import render_active_map
5
+
6
+
7
+ def test_smoke_render() -> None:
8
+ """
9
+ Verifica que el writer genera el mapa esperado.
10
+ """
11
+ nodes: list[Node] = [
12
+ Node(id="BASE-01", type="BASE", title="Demo", summary="demo", source="test")
13
+ ]
14
+ edges: list[Edge] = [Edge(source="BASE-01", target="IDEA-01", kind="depends_on")]
15
+ md = render_active_map("Demo", nodes, edges)
16
+ assert "# Demo — Context Map" in md
17
+ assert "## Mapa mental de contexto" in md
18
+ assert "## Conexiones" in md
19
+
20
+
21
+ if __name__ == "__main__":
22
+ test_smoke_render()
23
+ print("smoke:ok")
@@ -0,0 +1,122 @@
1
+ """Tests de la señal de frescura del contexto (R1, auditoría 2026-08-14).
2
+
3
+ Verifica que ``check`` (y por tanto ``refresh``) detecta actividad posterior
4
+ al último build: commits git y sesiones de Hermes sin importar.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import json
10
+ from types import SimpleNamespace
11
+
12
+ from context_map.domain.analysis.checker import (
13
+ analizar_readiness,
14
+ _sesiones_posteriores,
15
+ _ultima_actividad,
16
+ )
17
+
18
+
19
+ def _hacer_repo(tmp_path, con_build: bool = True, con_git: bool = True) -> str:
20
+ """Crea un mini proyecto con ``.context-map/state/last_build.json``.
21
+
22
+ Args:
23
+ tmp_path: Fixture de pytest con directorio temporal.
24
+ con_build (bool): Si se crea el last_build.json.
25
+ con_git (bool): Si se crea la carpeta .git.
26
+
27
+ Returns:
28
+ str: Ruta del mini proyecto.
29
+ """
30
+ ctx = tmp_path / ".context-map" / "state"
31
+ ctx.mkdir(parents=True)
32
+ if con_build:
33
+ (ctx / "last_build.json").write_text(
34
+ json.dumps({"clean": False, "manuales_preservadas": 0,
35
+ "timestamp": "2000-01-01T00:00:00"}),
36
+ encoding="utf-8",
37
+ )
38
+ if con_git:
39
+ (tmp_path / ".git").mkdir(exist_ok=True)
40
+ return str(tmp_path)
41
+
42
+
43
+ def _sesion(fecha_inicio: str) -> SimpleNamespace:
44
+ """Crea una sesión fake de Hermes con la fecha de inicio dada.
45
+
46
+ Args:
47
+ fecha_inicio (str): Timestamp (epoch o ISO) de inicio de la sesión.
48
+
49
+ Returns:
50
+ SimpleNamespace: Sesión mínima compatible con el modelo.
51
+ """
52
+ return SimpleNamespace(fecha_inicio=fecha_inicio)
53
+
54
+
55
+ def test_ultima_actividad_detecta_commit_posterior_al_build(tmp_path, monkeypatch):
56
+ """Si hay un commit posterior al last_build, la señal avisa."""
57
+ ruta = _hacer_repo(tmp_path)
58
+
59
+ def _fake_git(_ruta, args): # noqa: ANN001
60
+ if args and args[0] == "log":
61
+ return "1800000000" # epoch posterior al build (2000-01-01)
62
+ return ""
63
+
64
+ monkeypatch.setattr("context_map.domain.analysis.checker._ejecutar_git", _fake_git)
65
+ monkeypatch.setattr("context_map.domain.analysis.checker.leer_sesiones", lambda *_: [])
66
+ actividad = _ultima_actividad(ruta)
67
+ assert actividad["commits_posteriores"] > 0
68
+ assert "commit" in actividad["aviso"].lower()
69
+
70
+
71
+ def test_check_avisa_con_sesiones_sin_importar(tmp_path, monkeypatch):
72
+ """Si hay sesiones de Hermes posteriores al build, la señal avisa."""
73
+ ruta = _hacer_repo(tmp_path)
74
+
75
+ def _fake_git(_ruta, args): # noqa: ANN001
76
+ return ""
77
+
78
+ def _fake_leer(db_path=None, limite=None): # noqa: ANN001
79
+ return [_sesion("1800000000"), _sesion("1800000000"), _sesion("1800000000")]
80
+
81
+ monkeypatch.setattr("context_map.domain.analysis.checker._ejecutar_git", _fake_git)
82
+ monkeypatch.setattr("context_map.domain.analysis.checker.leer_sesiones", _fake_leer)
83
+ resultado = analizar_readiness(ruta)
84
+ assert any("sin importar" in s for s in resultado.sugerencias)
85
+
86
+
87
+ def test_check_sin_actividad_no_avisa(tmp_path, monkeypatch):
88
+ """Sin commits ni sesiones posteriores, no hay falso aviso."""
89
+ ruta = _hacer_repo(tmp_path)
90
+
91
+ def _fake_git(_ruta, args): # noqa: ANN001
92
+ return ""
93
+
94
+ def _fake_leer(db_path=None, limite=None): # noqa: ANN001
95
+ return [_sesion("100")] # anterior al build
96
+
97
+ monkeypatch.setattr("context_map.domain.analysis.checker._ejecutar_git", _fake_git)
98
+ monkeypatch.setattr("context_map.domain.analysis.checker.leer_sesiones", _fake_leer)
99
+ resultado = analizar_readiness(ruta)
100
+ assert not any("sin importar" in s for s in resultado.sugerencias)
101
+
102
+
103
+ def test_sesiones_posteriores_cuenta_solo_las_recientes(tmp_path, monkeypatch):
104
+ """Cuenta sesiones cuya fecha de inicio supera el timestamp del build."""
105
+ ruta = _hacer_repo(tmp_path)
106
+
107
+ def _fake_leer(db_path=None, limite=None): # noqa: ANN001
108
+ return [
109
+ _sesion("1800000000"), # posterior a 2000-01-01
110
+ _sesion("1800000000"),
111
+ _sesion("100"), # anterior al build
112
+ ]
113
+
114
+ monkeypatch.setattr("context_map.domain.analysis.checker.leer_sesiones", _fake_leer)
115
+ assert _sesiones_posteriores(ruta) == 2
116
+
117
+
118
+ def test_sin_build_previo_avisa_nunca_build(tmp_path):
119
+ """Si no existe last_build.json, el aviso indica que nunca se ha construido."""
120
+ ruta = _hacer_repo(tmp_path, con_build=False)
121
+ actividad = _ultima_actividad(ruta)
122
+ assert "nunca" in actividad["aviso"].lower()
@@ -0,0 +1,134 @@
1
+ """Pruebas unitarias para el parser principal del CLI y despacho de comandos."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+
7
+ from context_map.application.cli.parser import create_parser
8
+
9
+
10
+ def test_cli_parser_defaults() -> None:
11
+ """Verifica que el parser principal cree los subcomandos esperados."""
12
+ parser = create_parser()
13
+ assert isinstance(parser, argparse.ArgumentParser)
14
+
15
+ # Test parser para comando 'check'
16
+ args = parser.parse_args(["check", "."])
17
+ assert args.cmd == "check"
18
+ assert args.target == "."
19
+
20
+ # Test parser para comando 'build'
21
+ args_build = parser.parse_args(["build", "--clean", "--brief"])
22
+ assert args_build.cmd == "build"
23
+ assert args_build.clean is True
24
+ assert args_build.brief is True
25
+
26
+ # Test parser para comando 'scan'
27
+ args_scan = parser.parse_args(["scan", "."])
28
+ assert args_scan.cmd == "scan"
29
+ assert args_scan.target == "."
30
+
31
+
32
+ def test_cli_parser_auto_mode() -> None:
33
+ """Verifica los flags por defecto del comando auto."""
34
+ parser = create_parser()
35
+ args_auto = parser.parse_args(["auto", ".", "--project", "TestApp"])
36
+ assert args_auto.cmd == "auto"
37
+ assert args_auto.target == "."
38
+ assert args_auto.project == "TestApp"
39
+
40
+
41
+ def test_aviso_pre_actualizacion_contiene_comando(monkeypatch) -> None:
42
+ """El aviso pre-refresh informa de la versión nueva y el comando de actualización."""
43
+ import context_map.infrastructure.version_check as vc
44
+ from context_map.infrastructure.version_check import aviso_pre_actualizacion
45
+
46
+ monkeypatch.setattr(vc, "hay_actualizacion", lambda force=False: (True, "1.4.0", "1.5.0"))
47
+ msg = aviso_pre_actualizacion()
48
+ assert "DESACTUALIZADO" in msg
49
+ assert "1.5.0" in msg
50
+ assert "uv tool install" in msg
51
+ assert "--with" in msg # incluye el SDK de MCP
52
+
53
+ monkeypatch.setattr(vc, "hay_actualizacion", lambda force=False: (False, "1.5.0", "1.5.0"))
54
+ assert aviso_pre_actualizacion() == ""
55
+
56
+
57
+ def test_refresh_imprime_aviso_pre_actualizacion(monkeypatch, capsys) -> None:
58
+ """El refresh verifica la versión del PROGRAMA antes de actualizar el CONTEXTO."""
59
+ import context_map.infrastructure.version_check as vc
60
+ from context_map.application.commands import refresh as refresh_mod
61
+ from context_map.application.commands.refresh import cmd_refresh
62
+
63
+ # No ejecutar scan/build/check reales en el test
64
+ monkeypatch.setattr(refresh_mod, "cmd_scan", lambda args: None)
65
+ monkeypatch.setattr(refresh_mod, "cmd_build", lambda args: None)
66
+ monkeypatch.setattr(refresh_mod, "cmd_check", lambda args: None)
67
+ # Hay una actualización pendiente → debe avisarse al inicio
68
+ monkeypatch.setattr(vc, "hay_actualizacion", lambda force=False: (True, "1.4.0", "1.5.0"))
69
+
70
+ args = argparse.Namespace(target=".", project="Demo", quiet=False, mode="hierarchical")
71
+ cmd_refresh(args)
72
+ out = capsys.readouterr().out
73
+
74
+ assert "DESACTUALIZADO" in out, "El refresh no avisó de la actualización pendiente"
75
+ assert "1.5.0" in out
76
+ # El aviso aparece ANTES del resultado del refresh
77
+ assert out.index("DESACTUALIZADO") < out.index("[refresh] [OK]")
78
+
79
+
80
+ def test_refresh_importa_sesiones_automatico(monkeypatch, capsys) -> None:
81
+ """El refresh importa las sesiones recientes de Hermes (memoria viva automática)."""
82
+ import argparse
83
+
84
+ from context_map.application.commands import refresh as refresh_mod
85
+ from context_map.application.commands.refresh import cmd_refresh
86
+
87
+ monkeypatch.setattr(refresh_mod, "cmd_scan", lambda args: None)
88
+ monkeypatch.setattr(refresh_mod, "cmd_build", lambda args: None)
89
+ monkeypatch.setattr(refresh_mod, "cmd_check", lambda args: None)
90
+
91
+ llamado: dict = {}
92
+
93
+ def _fake_importar_sesiones(**kwargs):
94
+ llamado.update(kwargs)
95
+ return 3
96
+
97
+ monkeypatch.setattr(
98
+ "context_map.infrastructure.integrations.hermes.importar_sesiones",
99
+ _fake_importar_sesiones,
100
+ )
101
+
102
+ args = argparse.Namespace(target=".", project="Demo", quiet=False, mode="hierarchical")
103
+ cmd_refresh(args)
104
+ out = capsys.readouterr().out
105
+
106
+ # Se importan las 5 sesiones más recientes y los eventos van al raw del proyecto
107
+ assert llamado.get("limite") == 5
108
+ assert "events.jsonl" in llamado.get("output_path", "")
109
+ assert "3 evento(s) de sesiones importados" in out
110
+
111
+
112
+ def test_refresh_importar_sesiones_tolerante(monkeypatch, capsys) -> None:
113
+ """Si la BD de sesiones no existe, el refresh NO se rompe (aviso y sigue)."""
114
+ import argparse
115
+
116
+ from context_map.application.commands import refresh as refresh_mod
117
+ from context_map.application.commands.refresh import cmd_refresh
118
+
119
+ monkeypatch.setattr(refresh_mod, "cmd_scan", lambda args: None)
120
+ monkeypatch.setattr(refresh_mod, "cmd_build", lambda args: None)
121
+ monkeypatch.setattr(refresh_mod, "cmd_check", lambda args: None)
122
+
123
+ def _fake_importar_sesiones(**kwargs):
124
+ raise FileNotFoundError("sin BD de sesiones")
125
+
126
+ monkeypatch.setattr(
127
+ "context_map.infrastructure.integrations.hermes.importar_sesiones",
128
+ _fake_importar_sesiones,
129
+ )
130
+
131
+ args = argparse.Namespace(target=".", project="Demo", quiet=False, mode="hierarchical")
132
+ cmd_refresh(args) # no debe lanzar excepción
133
+ out = capsys.readouterr().out
134
+ assert "[refresh] [OK]" in out, "El refresh se rompió por la importación de sesiones"