klapcontext 0.8.2__tar.gz → 0.9.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {klapcontext-0.8.2/src/klapcontext.egg-info → klapcontext-0.9.0}/PKG-INFO +19 -15
- {klapcontext-0.8.2 → klapcontext-0.9.0}/README.md +18 -14
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/__init__.py +1 -1
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/cli.py +36 -7
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/git.py +4 -1
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/graphify.py +24 -6
- klapcontext-0.9.0/src/klapcontext/portal.py +109 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/graphify.py +1 -1
- klapcontext-0.9.0/src/klapcontext/providers/lifecycle.py +251 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/optional.py +3 -1
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/serena/client.py +12 -1
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/serena/config.py +3 -1
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/services.py +4 -4
- {klapcontext-0.8.2 → klapcontext-0.9.0/src/klapcontext.egg-info}/PKG-INFO +19 -15
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext.egg-info/SOURCES.txt +2 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/tests/test_klapcontext.py +9 -6
- klapcontext-0.9.0/tests/test_provider_lifecycle.py +89 -0
- klapcontext-0.8.2/src/klapcontext/portal.py +0 -99
- {klapcontext-0.8.2 → klapcontext-0.9.0}/LICENSE +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/pyproject.toml +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/setup.cfg +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/__init__.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/capabilities.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/compiler.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/impact.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/intents.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/models.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/planner.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/verification.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent/workflows.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent_context.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/agent_setup.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/context_builder.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/detector.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/evidence.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/exploration.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/frameworks/__init__.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/frameworks/base.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/frameworks/fastapi.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/frameworks/generic_php.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/frameworks/laravel.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/frameworks/node.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/freshness.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/__init__.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/base.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/code_intelligence.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/contracts.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/javascript_intelligence.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/models.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/native.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/python_intelligence.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/registry.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/resolver.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/serena/__init__.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/providers/serena/adapter.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/scope.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/semantic.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext/system.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext.egg-info/dependency_links.txt +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext.egg-info/entry_points.txt +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext.egg-info/requires.txt +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/src/klapcontext.egg-info/top_level.txt +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/tests/test_agent_setup.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/tests/test_exploration.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/tests/test_provider_architecture.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/tests/test_reliability.py +0 -0
- {klapcontext-0.8.2 → klapcontext-0.9.0}/tests/test_serena_provider.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: klapcontext
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.9.0
|
|
4
4
|
Summary: Make your repository understandable to humans and AI.
|
|
5
5
|
Author: Willy Cordon
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -41,7 +41,7 @@ Dynamic: license-file
|
|
|
41
41
|
|
|
42
42
|
KlapContext convierte evidencia de código en un modelo del sistema: puntos de
|
|
43
43
|
entrada, flujos, automatizaciones, integraciones y sus incertidumbres.
|
|
44
|
-
Graphify puede funcionar como proveedor
|
|
44
|
+
Graphify puede funcionar como proveedor estándar de análisis técnico;
|
|
45
45
|
Serena puede sumar navegación semántica on demand por MCP; KlapContext conserva
|
|
46
46
|
analizadores locales y transforma toda evidencia mediante contratos
|
|
47
47
|
desacoplados para personas y agentes de IA.
|
|
@@ -69,12 +69,11 @@ Repositorio → KlapContext → Contexto de ingeniería
|
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
71
|
pip install klapcontext
|
|
72
|
-
# opcional, para sumar el grafo Graphify
|
|
73
|
-
pip install "klapcontext[graphify]"
|
|
74
72
|
```
|
|
75
73
|
|
|
76
|
-
La instalación base no
|
|
77
|
-
|
|
74
|
+
La instalación base no incorpora proveedores en el intérprete de KlapContext.
|
|
75
|
+
El primer `klap init` los prepara en entornos aislados bajo
|
|
76
|
+
`.klap/provider-runtime/`.
|
|
78
77
|
|
|
79
78
|
## Primer uso
|
|
80
79
|
|
|
@@ -89,22 +88,24 @@ bajo `.klap/` y prepara un `AGENTS.md` privado para que los agentes descubran el
|
|
|
89
88
|
contexto progresivamente. Ambos se agregan a `.git/info/exclude`; KlapContext
|
|
90
89
|
nunca modifica tu `.gitignore`.
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
91
|
+
`klap init` prepara Graphify y Serena con versiones fijadas si no encuentra una
|
|
92
|
+
instalación compatible. Si una preparación falla, los analizadores nativos
|
|
93
|
+
siguen generando contexto y el resumen registra el motivo. Graphify ejecuta una extracción real y usa
|
|
94
94
|
`.klap/graphify-out/graph.json` en `context.json`, el portal y la configuración
|
|
95
95
|
MCP. Si no está instalado o falla, los analizadores nativos siguen generando un
|
|
96
96
|
contexto útil y el reporte queda `PARTIAL`, con proveedores detectados,
|
|
97
97
|
ejecutados y omitidos (incluido el motivo). `context.json.analysis` conserva la
|
|
98
|
-
cobertura, trazas y contribuciones combinadas con procedencia.
|
|
99
|
-
|
|
100
|
-
durante `init`. Se puede deshabilitar Graphify explícitamente con
|
|
98
|
+
cobertura, trazas y contribuciones combinadas con procedencia. Se puede
|
|
99
|
+
deshabilitar Graphify explícitamente con
|
|
101
100
|
`.klap/providers.json` y `{"providers":{"graphify":{"enabled":false}}}`.
|
|
102
101
|
|
|
103
102
|
[Serena](https://github.com/oraios/serena) se integra de forma independiente y
|
|
104
|
-
|
|
103
|
+
intercambiable. Si falta una instalación compatible, Serena 1.7.0 se prepara
|
|
104
|
+
en el runtime local; KlapContext descubre sus
|
|
105
105
|
tools reales por MCP y ofrece símbolos, definiciones, referencias,
|
|
106
|
-
implementaciones e impacto por consulta.
|
|
107
|
-
|
|
106
|
+
implementaciones e impacto por consulta. Durante `klap init` sólo ejecuta una
|
|
107
|
+
comprobación acotada de overview sobre un archivo elegible para verificar MCP;
|
|
108
|
+
no realiza búsquedas semánticas amplias, y nunca llama sus tools de escritura, shell,
|
|
108
109
|
onboarding o memorias. Ver [instalación, configuración, seguridad,
|
|
109
110
|
compatibilidad y límites](docs/SERENA.md).
|
|
110
111
|
|
|
@@ -142,12 +143,15 @@ klap scope --json
|
|
|
142
143
|
| Comando | Descripción |
|
|
143
144
|
| --- | --- |
|
|
144
145
|
| `klap init [ruta]` | Genera el contexto inicial. |
|
|
145
|
-
| `klap
|
|
146
|
+
| `klap init --no-serena [ruta]` | Omite instalación, conexión y uso de Serena en esta ejecución. |
|
|
147
|
+
| `klap init --no-graphify [ruta]` | Omite instalación y ejecución de Graphify en esta ejecución. |
|
|
148
|
+
| `klap update [ruta]` | Reutiliza proveedores compatibles y actualiza el contexto. |
|
|
146
149
|
| `klap status [ruta]` | Indica si el contexto está actualizado respecto a Git. |
|
|
147
150
|
| `klap open [ruta]` | Abre el portal humano estático. |
|
|
148
151
|
| `klap agent [ruta]` | Muestra la configuración MCP local de Graphify. |
|
|
149
152
|
| `klap context "tarea" [ruta]` | Compila un contexto focalizado reutilizando `.klap/context.json` si existe. |
|
|
150
153
|
| `klap doctor --agent [ruta]` | Comprueba AGENTS.md, freshness, Graphify y configuración MCP. |
|
|
154
|
+
| `klap doctor --agent --repair [ruta]` | Repara proveedores estándar en runtimes locales. |
|
|
151
155
|
| `klap scope [ruta]` | Explica archivos incluidos/excluidos y sus motivos. |
|
|
152
156
|
| `klap migrate-graph [ruta]` | Planea la migración de grafos legacy; `--apply` preserva respaldos. |
|
|
153
157
|
| `klap --version` | Muestra la versión instalada. |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
KlapContext convierte evidencia de código en un modelo del sistema: puntos de
|
|
6
6
|
entrada, flujos, automatizaciones, integraciones y sus incertidumbres.
|
|
7
|
-
Graphify puede funcionar como proveedor
|
|
7
|
+
Graphify puede funcionar como proveedor estándar de análisis técnico;
|
|
8
8
|
Serena puede sumar navegación semántica on demand por MCP; KlapContext conserva
|
|
9
9
|
analizadores locales y transforma toda evidencia mediante contratos
|
|
10
10
|
desacoplados para personas y agentes de IA.
|
|
@@ -32,12 +32,11 @@ Repositorio → KlapContext → Contexto de ingeniería
|
|
|
32
32
|
|
|
33
33
|
```bash
|
|
34
34
|
pip install klapcontext
|
|
35
|
-
# opcional, para sumar el grafo Graphify
|
|
36
|
-
pip install "klapcontext[graphify]"
|
|
37
35
|
```
|
|
38
36
|
|
|
39
|
-
La instalación base no
|
|
40
|
-
|
|
37
|
+
La instalación base no incorpora proveedores en el intérprete de KlapContext.
|
|
38
|
+
El primer `klap init` los prepara en entornos aislados bajo
|
|
39
|
+
`.klap/provider-runtime/`.
|
|
41
40
|
|
|
42
41
|
## Primer uso
|
|
43
42
|
|
|
@@ -52,22 +51,24 @@ bajo `.klap/` y prepara un `AGENTS.md` privado para que los agentes descubran el
|
|
|
52
51
|
contexto progresivamente. Ambos se agregan a `.git/info/exclude`; KlapContext
|
|
53
52
|
nunca modifica tu `.gitignore`.
|
|
54
53
|
|
|
55
|
-
|
|
56
|
-
|
|
54
|
+
`klap init` prepara Graphify y Serena con versiones fijadas si no encuentra una
|
|
55
|
+
instalación compatible. Si una preparación falla, los analizadores nativos
|
|
56
|
+
siguen generando contexto y el resumen registra el motivo. Graphify ejecuta una extracción real y usa
|
|
57
57
|
`.klap/graphify-out/graph.json` en `context.json`, el portal y la configuración
|
|
58
58
|
MCP. Si no está instalado o falla, los analizadores nativos siguen generando un
|
|
59
59
|
contexto útil y el reporte queda `PARTIAL`, con proveedores detectados,
|
|
60
60
|
ejecutados y omitidos (incluido el motivo). `context.json.analysis` conserva la
|
|
61
|
-
cobertura, trazas y contribuciones combinadas con procedencia.
|
|
62
|
-
|
|
63
|
-
durante `init`. Se puede deshabilitar Graphify explícitamente con
|
|
61
|
+
cobertura, trazas y contribuciones combinadas con procedencia. Se puede
|
|
62
|
+
deshabilitar Graphify explícitamente con
|
|
64
63
|
`.klap/providers.json` y `{"providers":{"graphify":{"enabled":false}}}`.
|
|
65
64
|
|
|
66
65
|
[Serena](https://github.com/oraios/serena) se integra de forma independiente y
|
|
67
|
-
|
|
66
|
+
intercambiable. Si falta una instalación compatible, Serena 1.7.0 se prepara
|
|
67
|
+
en el runtime local; KlapContext descubre sus
|
|
68
68
|
tools reales por MCP y ofrece símbolos, definiciones, referencias,
|
|
69
|
-
implementaciones e impacto por consulta.
|
|
70
|
-
|
|
69
|
+
implementaciones e impacto por consulta. Durante `klap init` sólo ejecuta una
|
|
70
|
+
comprobación acotada de overview sobre un archivo elegible para verificar MCP;
|
|
71
|
+
no realiza búsquedas semánticas amplias, y nunca llama sus tools de escritura, shell,
|
|
71
72
|
onboarding o memorias. Ver [instalación, configuración, seguridad,
|
|
72
73
|
compatibilidad y límites](docs/SERENA.md).
|
|
73
74
|
|
|
@@ -105,12 +106,15 @@ klap scope --json
|
|
|
105
106
|
| Comando | Descripción |
|
|
106
107
|
| --- | --- |
|
|
107
108
|
| `klap init [ruta]` | Genera el contexto inicial. |
|
|
108
|
-
| `klap
|
|
109
|
+
| `klap init --no-serena [ruta]` | Omite instalación, conexión y uso de Serena en esta ejecución. |
|
|
110
|
+
| `klap init --no-graphify [ruta]` | Omite instalación y ejecución de Graphify en esta ejecución. |
|
|
111
|
+
| `klap update [ruta]` | Reutiliza proveedores compatibles y actualiza el contexto. |
|
|
109
112
|
| `klap status [ruta]` | Indica si el contexto está actualizado respecto a Git. |
|
|
110
113
|
| `klap open [ruta]` | Abre el portal humano estático. |
|
|
111
114
|
| `klap agent [ruta]` | Muestra la configuración MCP local de Graphify. |
|
|
112
115
|
| `klap context "tarea" [ruta]` | Compila un contexto focalizado reutilizando `.klap/context.json` si existe. |
|
|
113
116
|
| `klap doctor --agent [ruta]` | Comprueba AGENTS.md, freshness, Graphify y configuración MCP. |
|
|
117
|
+
| `klap doctor --agent --repair [ruta]` | Repara proveedores estándar en runtimes locales. |
|
|
114
118
|
| `klap scope [ruta]` | Explica archivos incluidos/excluidos y sus motivos. |
|
|
115
119
|
| `klap migrate-graph [ruta]` | Planea la migración de grafos legacy; `--apply` preserva respaldos. |
|
|
116
120
|
| `klap --version` | Muestra la versión instalada. |
|
|
@@ -10,6 +10,7 @@ from .git import commit, exclude_klap, is_repository
|
|
|
10
10
|
from .freshness import evaluate_freshness
|
|
11
11
|
from .portal import write as write_portal
|
|
12
12
|
from .providers import AnalysisServices, ResultState, migrate_legacy_graphs
|
|
13
|
+
from .providers.lifecycle import prepare_standard_providers, write_provider_state
|
|
13
14
|
from .scope import IndexScope
|
|
14
15
|
|
|
15
16
|
|
|
@@ -17,20 +18,36 @@ def root_path(value: str | None) -> Path:
|
|
|
17
18
|
return Path(value or os.getcwd()).resolve()
|
|
18
19
|
|
|
19
20
|
|
|
20
|
-
def generate_all(root: Path, update: bool = False, *, shared: bool = False, allow_tracked_agents: bool = False, services: AnalysisServices | None = None) -> dict:
|
|
21
|
+
def generate_all(root: Path, update: bool = False, *, shared: bool = False, allow_tracked_agents: bool = False, services: AnalysisServices | None = None, no_graphify: bool = False, no_serena: bool = False) -> dict:
|
|
21
22
|
if not is_repository(root):
|
|
22
23
|
raise RuntimeError(f"Not a Git repository: {root}")
|
|
23
24
|
exclude_klap(root)
|
|
24
25
|
klap = root / ".klap"
|
|
25
26
|
klap.mkdir(exist_ok=True)
|
|
26
27
|
scope = IndexScope.load(root)
|
|
27
|
-
|
|
28
|
-
|
|
28
|
+
lifecycle = {} if services is not None else prepare_standard_providers(root, no_graphify=no_graphify, no_serena=no_serena)
|
|
29
|
+
services = services or AnalysisServices(root=root, graphify=not no_graphify, serena=not no_serena)
|
|
30
|
+
try:
|
|
31
|
+
context, graph_result = services.build_context(root, scope=scope, update=update)
|
|
32
|
+
finally:
|
|
33
|
+
services.close()
|
|
29
34
|
files = next((item.get("files", []) for item in graph_result.trace if item.get("files") is not None), [])
|
|
35
|
+
graphify_ready = graph_result.state == ResultState.OK and graph_result.provider is not None and graph_result.provider.name == "graphify"
|
|
36
|
+
if lifecycle and "graphify" in lifecycle and lifecycle["graphify"].get("state") == "READY":
|
|
37
|
+
graph_runtime = lifecycle["graphify"]
|
|
38
|
+
if graphify_ready:
|
|
39
|
+
graph_runtime["connection"] = "VERIFIED"
|
|
40
|
+
else:
|
|
41
|
+
graph_runtime.update({
|
|
42
|
+
"state": "PARTIAL",
|
|
43
|
+
"connection": "FAILED",
|
|
44
|
+
"error": graph_result.error or "Graphify extraction did not produce a valid canonical graph",
|
|
45
|
+
})
|
|
46
|
+
write_provider_state(root, lifecycle)
|
|
30
47
|
context["freshness"] = {"status": "CURRENT", "reason": "Generation completed from the recorded scope snapshot."}
|
|
48
|
+
context["analysis"]["provider_lifecycle"] = lifecycle
|
|
31
49
|
(klap / "context.json").write_text(json.dumps(context, indent=2) + "\n", encoding="utf-8")
|
|
32
50
|
agent = write_agent(context, klap / "agent-context.md")
|
|
33
|
-
graphify_ready = graph_result.state == ResultState.OK and graph_result.provider is not None and graph_result.provider.name == "graphify"
|
|
34
51
|
mcp = generate_mcp_configs(root) if graphify_ready else {"status": "unavailable", "reason": "Graphify graph unavailable; native context remains usable."}
|
|
35
52
|
setup = AgentInstructionsManager(root).apply(context, shared=shared, allow_tracked=allow_tracked_agents)
|
|
36
53
|
context["agent_onboarding"] = {**setup.as_dict(), "mcp": mcp}
|
|
@@ -42,6 +59,7 @@ def generate_all(root: Path, update: bool = False, *, shared: bool = False, allo
|
|
|
42
59
|
"git_commit": commit(root),
|
|
43
60
|
"provider": {"name": graph_result.provider.name if graph_result.provider else None, "version": graph_result.provider.version if graph_result.provider else None, "state": graph_result.state.value, "warnings": graph_result.warnings, "trace": graph_result.trace},
|
|
44
61
|
"analysis": context["analysis"],
|
|
62
|
+
"provider_lifecycle": lifecycle,
|
|
45
63
|
"klap_version": __version__,
|
|
46
64
|
"agent_onboarding": setup.as_dict(),
|
|
47
65
|
"scope": {"rules_version": context["system_model"]["index_scope"]["rules_version"], "hash": scope.snapshot_hash(), "configuration": ".klap/scope.json"},
|
|
@@ -63,10 +81,13 @@ def cmd_init(args):
|
|
|
63
81
|
print("KlapContext\n")
|
|
64
82
|
shared, allow = _permission(args, root, "Agregar o actualizar")
|
|
65
83
|
try:
|
|
66
|
-
|
|
84
|
+
no_graphify, no_serena = getattr(args, "no_graphify", False), getattr(args, "no_serena", False)
|
|
85
|
+
context = generate_all(root, shared=shared, allow_tracked_agents=allow, no_graphify=no_graphify, no_serena=no_serena) if shared or allow or no_graphify or no_serena else generate_all(root)
|
|
67
86
|
except RuntimeError as error:
|
|
68
87
|
print(f"✗ {error}", file=sys.stderr)
|
|
69
88
|
return 1
|
|
89
|
+
for name, details in context.get("analysis", {}).get("provider_lifecycle", {}).items():
|
|
90
|
+
print(f"Provider {name}: {details.get('state')} · {details.get('connection')}" + (f" ({details['error']})" if details.get("error") else ""))
|
|
70
91
|
graph_provider = context.get("providers", {}).get("dependency_graph", {})
|
|
71
92
|
graph_line = "✓ Dependency graph generated" if graph_provider.get("state") == "OK" else "! External graph unavailable; native analyzers used"
|
|
72
93
|
print("✓ Git repository detected\n" + graph_line + "\n✓ Engineering context generated\n✓ Agent context generated\n✓ Human portal generated")
|
|
@@ -100,7 +121,8 @@ def cmd_update(args):
|
|
|
100
121
|
root = root_path(args.path)
|
|
101
122
|
shared, allow = _permission(args, root, "Actualizar")
|
|
102
123
|
try:
|
|
103
|
-
|
|
124
|
+
no_graphify, no_serena = getattr(args, "no_graphify", False), getattr(args, "no_serena", False)
|
|
125
|
+
context = generate_all(root, update=True, shared=shared, allow_tracked_agents=allow, no_graphify=no_graphify, no_serena=no_serena) if shared or allow or no_graphify or no_serena else generate_all(root, update=True)
|
|
104
126
|
except RuntimeError as error:
|
|
105
127
|
print(f"✗ {error}", file=sys.stderr)
|
|
106
128
|
return 1
|
|
@@ -120,8 +142,12 @@ def cmd_status(args):
|
|
|
120
142
|
|
|
121
143
|
|
|
122
144
|
def cmd_doctor(args):
|
|
123
|
-
|
|
145
|
+
root = root_path(args.path)
|
|
146
|
+
repaired = prepare_standard_providers(root, repair=True) if getattr(args, "repair", False) else {}
|
|
147
|
+
result = doctor_agent(root, probe_mcp=getattr(args, "probe_mcp", False))
|
|
124
148
|
print("KlapContext — Agent Doctor\n")
|
|
149
|
+
for name, details in repaired.items():
|
|
150
|
+
print(f"Repair {name}: {details.get('state')} · {details.get('connection')}" + (f" ({details['error']})" if details.get("error") else ""))
|
|
125
151
|
checks = [
|
|
126
152
|
(result["agents"]["valid"], "AGENTS.md configured"),
|
|
127
153
|
(result["context"]["available"], "Engineering context available"),
|
|
@@ -225,11 +251,14 @@ def main(argv=None):
|
|
|
225
251
|
if name in {"init", "update"}:
|
|
226
252
|
command.add_argument("--shared", action="store_true", help="Create versionable AGENTS.md instructions")
|
|
227
253
|
command.add_argument("--allow-tracked-agents", action="store_true", help="Authorize updating a tracked AGENTS.md")
|
|
254
|
+
command.add_argument("--no-serena", action="store_true", help="Do not install, connect or use Serena for this run")
|
|
255
|
+
command.add_argument("--no-graphify", action="store_true", help="Do not install, run or use Graphify for this run")
|
|
228
256
|
command.set_defaults(func=func)
|
|
229
257
|
command = subs.add_parser("doctor", help="Diagnose project and agent readiness")
|
|
230
258
|
command.add_argument("path", nargs="?", default=".")
|
|
231
259
|
command.add_argument("--agent", action="store_true", help="Check AGENTS.md and generated context")
|
|
232
260
|
command.add_argument("--probe-mcp", action="store_true", help="Start Graphify MCP briefly and stop it")
|
|
261
|
+
command.add_argument("--repair", action="store_true", help="Install or repair standard providers in isolated project runtimes")
|
|
233
262
|
command.set_defaults(func=cmd_doctor)
|
|
234
263
|
command = subs.add_parser("migrate-graph", help="Safely plan or apply legacy graph storage migration")
|
|
235
264
|
command.add_argument("path", nargs="?", default=".")
|
|
@@ -55,6 +55,9 @@ def exclude_klap(root: Path) -> None:
|
|
|
55
55
|
exclude = (root / git_dir / "info" / "exclude").resolve()
|
|
56
56
|
exclude.parent.mkdir(parents=True, exist_ok=True)
|
|
57
57
|
existing = exclude.read_text(encoding="utf-8") if exclude.exists() else ""
|
|
58
|
-
|
|
58
|
+
# Serena manages project-local language-server metadata in `.serena/`.
|
|
59
|
+
# Keep it private like `.klap/` without modifying the repository's shared
|
|
60
|
+
# .gitignore or hiding an explicitly tracked file.
|
|
61
|
+
additions = [entry for entry in (".klap/", "graphify-out/", ".serena/") if entry not in {line.strip() for line in existing.splitlines()}]
|
|
59
62
|
if additions:
|
|
60
63
|
exclude.write_text(existing.rstrip() + ("\n" if existing.strip() else "") + "\n".join(additions) + "\n", encoding="utf-8")
|
|
@@ -42,14 +42,19 @@ def _scoped_graphifyignore(root: Path, scope: IndexScope):
|
|
|
42
42
|
path.unlink(missing_ok=True)
|
|
43
43
|
|
|
44
44
|
|
|
45
|
-
def executable() -> str | None:
|
|
45
|
+
def executable(root: Path | None = None) -> str | None:
|
|
46
|
+
if root is not None:
|
|
47
|
+
from .providers.lifecycle import managed_command
|
|
48
|
+
managed = managed_command(Path(root), "graphify")
|
|
49
|
+
if managed:
|
|
50
|
+
return managed
|
|
46
51
|
return shutil.which("graphify")
|
|
47
52
|
|
|
48
53
|
|
|
49
|
-
def command_prefix() -> list[str] | None:
|
|
54
|
+
def command_prefix(root: Path | None = None) -> list[str] | None:
|
|
50
55
|
"""Use the console script when present; PyPI also supports python -m graphify."""
|
|
51
|
-
if executable():
|
|
52
|
-
return [executable()]
|
|
56
|
+
if executable(root):
|
|
57
|
+
return [executable(root)]
|
|
53
58
|
try:
|
|
54
59
|
import graphify # noqa: F401
|
|
55
60
|
return [sys.executable, "-m", "graphify"]
|
|
@@ -57,7 +62,16 @@ def command_prefix() -> list[str] | None:
|
|
|
57
62
|
return None
|
|
58
63
|
|
|
59
64
|
|
|
60
|
-
def version() -> str | None:
|
|
65
|
+
def version(root: Path | None = None) -> str | None:
|
|
66
|
+
if root is not None:
|
|
67
|
+
command = executable(root)
|
|
68
|
+
if command:
|
|
69
|
+
try:
|
|
70
|
+
output = subprocess.run([command, "--version"], text=True, capture_output=True, timeout=5, check=False)
|
|
71
|
+
if output.returncode == 0:
|
|
72
|
+
return (output.stdout or output.stderr).strip().rsplit(" ", 1)[-1]
|
|
73
|
+
except (OSError, subprocess.SubprocessError):
|
|
74
|
+
pass
|
|
61
75
|
try:
|
|
62
76
|
return importlib.metadata.version("graphifyy")
|
|
63
77
|
except importlib.metadata.PackageNotFoundError:
|
|
@@ -117,7 +131,11 @@ def migrate_legacy_graphs(root: Path, *, apply: bool = False) -> dict:
|
|
|
117
131
|
|
|
118
132
|
|
|
119
133
|
def generate(root: Path, update: bool = False, scope: IndexScope | None = None) -> Path:
|
|
120
|
-
|
|
134
|
+
# Preserve the public no-argument command resolver for existing callers;
|
|
135
|
+
# only a project-managed runtime needs an explicit root-aware prefix.
|
|
136
|
+
from .providers.lifecycle import managed_command
|
|
137
|
+
managed = managed_command(root, "graphify")
|
|
138
|
+
prefix = [managed] if managed else command_prefix()
|
|
121
139
|
if not prefix:
|
|
122
140
|
raise GraphifyError("Graphify is required. Install it with: pipx install graphifyy")
|
|
123
141
|
scope = scope or IndexScope.load(root)
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"""Offline, evidence-backed HTML portal."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import html
|
|
5
|
+
import json
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def e(value: object) -> str:
|
|
10
|
+
return html.escape(str(value))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def empty(message: str) -> str:
|
|
14
|
+
return f"<p class='empty'>{e(message)}</p>"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def items(rows: list[dict], key: str = "name") -> str:
|
|
18
|
+
if not rows:
|
|
19
|
+
return empty("Sin evidencia suficiente todavía.")
|
|
20
|
+
return "".join(
|
|
21
|
+
f"<li><i></i><div><b>{e(row.get(key) or row.get('name') or row.get('path') or row.get('intent') or 'Sin nombre')}</b><small>{e(row.get('type') or row.get('role') or row.get('reason') or row.get('description') or '')}</small></div></li>"
|
|
22
|
+
for row in rows
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def map_html(interactions: dict) -> str:
|
|
27
|
+
nodes = interactions.get("nodes", [])[:8]
|
|
28
|
+
if not nodes:
|
|
29
|
+
return "<p class='map-empty'>El mapa aparecerá cuando haya interacciones verificables.</p>"
|
|
30
|
+
positions = [(75, 75), (220, 42), (220, 130), (220, 214), (400, 75), (400, 174), (565, 130), (75, 208)]
|
|
31
|
+
names = [node.get("name", f"node-{index}") for index, node in enumerate(nodes)]
|
|
32
|
+
points = dict(zip(names, positions))
|
|
33
|
+
edges = "".join(
|
|
34
|
+
f"<path d='M {points[edge['source']][0]} {points[edge['source']][1]} C {points[edge['source']][0]+55} {points[edge['source']][1]}, {points[edge['target']][0]-55} {points[edge['target']][1]}, {points[edge['target']][0]} {points[edge['target']][1]}'/>"
|
|
35
|
+
for edge in interactions.get("edges", [])[:14]
|
|
36
|
+
if edge.get("source") in points and edge.get("target") in points
|
|
37
|
+
)
|
|
38
|
+
boxes = "".join(
|
|
39
|
+
f"<g class='node' tabindex='0' role='link' data-evidence-link><rect x='{points[name][0]-50}' y='{points[name][1]-18}' width='100' height='36' rx='6'/><text x='{points[name][0]}' y='{points[name][1]-1}'>{e(node.get('name', 'componente'))[:14]}</text><text class='kind' x='{points[name][0]}' y='{points[name][1]+11}'>{e(node.get('type', 'componente'))[:14]}</text></g>"
|
|
40
|
+
for name, node in zip(names, nodes)
|
|
41
|
+
)
|
|
42
|
+
return f"<div class='map'><svg viewBox='0 0 640 260' role='img' aria-label='Mapa de relaciones; cada componente lleva a las evidencias'><g class='edges'>{edges}</g>{boxes}</svg></div>"
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def view(title: str, name: str, body: str, icon: str) -> str:
|
|
46
|
+
return f"<section class='view' id='{name}' aria-labelledby='{name}-title'><header class='view-head'><span aria-hidden='true'>{icon}</span><div><h2 id='{name}-title'>{e(title)}</h2><p>Datos extraídos del repositorio y respaldados por evidencia.</p></div></header>{body}</section>"
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def tree(files: list[dict]) -> str:
|
|
50
|
+
if not files:
|
|
51
|
+
return empty("No se encontraron archivos.")
|
|
52
|
+
rows = []
|
|
53
|
+
for file in files:
|
|
54
|
+
symbols = "".join(f"<li>{e(x.get('name', 'símbolo'))} <small>{e(x.get('kind', ''))}</small></li>" for x in file.get("symbols", [])[:100])
|
|
55
|
+
rows.append(f"<details class='source-file'><summary>{e(file.get('path', 'archivo'))} <small>{len(file.get('symbols', []))} símbolos</small></summary><ul>{symbols or '<li>Sin símbolos detectados.</li>'}</ul></details>")
|
|
56
|
+
return "".join(rows)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
CSS = """
|
|
60
|
+
:root{--bg:#101820;--surface:#1B2630;--up:#263440;--cyan:#00E5F0;--cyan2:#40F5FF;--ink:#F3F6F8;--muted:#AAB9C5;--line:#354650;--ok:#39DFA6;--warn:#FFBF69;--bad:#FF657A}*{box-sizing:border-box}html{scroll-behavior:smooth}body{margin:0;min-width:320px;color:var(--ink);font:15px/1.5 Inter,ui-sans-serif,system-ui,sans-serif;background:var(--bg);background-image:linear-gradient(#fff2 1px,transparent 1px),linear-gradient(90deg,#fff2 1px,transparent 1px);background-size:28px 28px}button,input,select{font:inherit}button,a,summary{cursor:pointer}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,summary:focus-visible{outline:3px solid var(--cyan);outline-offset:3px}.portal{max-width:1400px;margin:auto;padding:16px}.frame{overflow:hidden;border:1px solid var(--line);border-radius:17px;background:#111c24e8;box-shadow:0 22px 65px #0009}.mast{display:flex;align-items:center;gap:12px;padding:12px 20px;border-bottom:1px solid var(--line)}.mark{display:grid;place-items:center;width:35px;height:35px;border-radius:9px;background:#d4dde1;color:#101820;font-size:22px;font-weight:900}.mast b{font-size:14px}.mast small,.view-head p,small,.empty{display:block;color:var(--muted);font-size:11px}.state{margin-left:auto;color:var(--ok);font:11px ui-monospace,monospace}.nav{position:sticky;top:0;z-index:10;display:flex;gap:7px;overflow-x:auto;padding:12px 20px;border-bottom:1px solid var(--line);background:#111c24f5}.nav a{flex:none;border:1px solid var(--line);border-radius:7px;padding:6px 11px;background:#14212a;color:var(--ink);font-size:11px;font-weight:800;text-decoration:none}.nav a:hover,.nav a.active{border-color:var(--cyan);background:var(--cyan);color:#071217}.hero{display:grid;grid-template-columns:minmax(0,1fr) minmax(370px,.95fr);gap:28px;padding:28px;border-bottom:1px solid var(--line)}.eyebrow{color:var(--cyan);font:700 10px ui-monospace,monospace;letter-spacing:.16em}.identity h1{margin:7px 0 10px;font-family:Impact,Haettenschweiler,'Arial Narrow Bold',sans-serif;font-size:clamp(42px,7vw,76px);line-height:.85;letter-spacing:.04em;overflow-wrap:anywhere;text-shadow:3px 3px var(--cyan)}.identity p{max-width:450px;font-size:16px;font-weight:650}.tags{display:flex;flex-wrap:wrap;gap:7px}.tags span{padding:3px 8px;border:1px solid var(--cyan);border-radius:5px;font:10px ui-monospace,monospace}.copy{margin-top:18px;border:0;border-radius:7px;padding:12px 17px;background:var(--cyan);color:#071217;font-weight:950;letter-spacing:.04em;box-shadow:0 4px #008c96}.copy:hover{background:var(--cyan2)}.intro{color:var(--muted);font-size:12px!important;font-weight:400!important}.checks{display:grid;grid-template-columns:1fr 1fr;gap:7px;margin:18px 0 0;padding:14px 0 0;border-top:2px solid var(--ink)}.checks li{list-style:none;color:var(--muted);font-size:10px}.console-wrap{position:relative;padding-top:25px}.label{position:absolute;z-index:2;top:0;left:50%;transform:translateX(-50%);min-width:180px;border:4px solid #38505c;border-radius:8px 8px 0 0;padding:5px 10px;background:var(--cyan);color:#061519;text-align:center;font:900 10px ui-monospace,monospace}.console{border:4px solid #596872;border-radius:25px;padding:21px 22px 28px;background:linear-gradient(135deg,#80909a,#3d4a52 55%,#667680);box-shadow:inset 2px 2px #aeb9be,inset -4px -5px #263038,0 16px 30px #0008}.screen{min-height:310px;border:8px solid #121b20;border-radius:13px;padding:13px;background:#061d26;box-shadow:inset 0 0 0 2px #274552,inset 0 0 24px #00e5f033;color:var(--cyan)}.screen-top{display:flex;justify-content:space-between;gap:8px;border-bottom:1px solid var(--cyan);padding-bottom:6px;font:800 10px ui-monospace,monospace}.screen h2{font:900 14px ui-monospace,monospace}.map{min-height:130px;overflow:auto;border:1px dashed #00e5f099}.map svg{display:block;min-width:440px;width:100%;height:130px}.edges path{fill:none;stroke:#00e5f0aa;stroke-width:1.5}.node{cursor:pointer}.node rect{fill:#0b3542;stroke:var(--cyan)}.node text{fill:var(--ink);font:10px ui-monospace,monospace;text-anchor:middle}.node .kind{fill:#9eeff3;font-size:8px}.stats{display:grid;grid-template-columns:repeat(4,1fr);gap:4px;margin-top:9px;padding-top:8px;border-top:1px solid #00e5f066}.stats b{display:block;font:900 19px ui-monospace,monospace}.stats span{font-size:8px;color:#b0eef2}.note{font:9px/1.35 ui-monospace,monospace}.controls{display:grid;grid-template-columns:1fr 72px;gap:14px;align-items:center;margin-top:15px}.mini{display:flex;gap:8px;justify-content:center}.mini button,.mini a{border:1px solid #1c3f49;border-radius:999px;padding:5px 9px;background:#17323c;color:var(--cyan);font:900 8px ui-monospace,monospace;text-decoration:none}.buttons{display:flex;justify-content:space-between}.buttons button,.buttons a{display:grid;place-items:center;width:32px;height:32px;border:2px solid #087b84;border-radius:50%;background:var(--cyan);color:#071217;font-weight:950;box-shadow:0 3px #075960;text-decoration:none}.view{scroll-margin-top:60px;padding:32px 28px;border-bottom:1px solid var(--line)}.view-head{display:flex;gap:9px;align-items:center;margin-bottom:16px}.view-head>span{display:grid;place-items:center;width:31px;height:31px;border:1px solid var(--cyan);border-radius:6px;color:var(--cyan)}.view-head h2{margin:0;font-size:20px}.view-head p{margin:1px 0}.grid,.two,.columns{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.card,.flow,.trace,.tech{padding:16px;border:1px solid var(--line);border-radius:9px;background:var(--surface)}.list{margin:0;padding:0;list-style:none}.list li{display:flex;gap:8px;padding:9px 0;border-bottom:1px solid var(--line)}.list li:last-child{border:0}.list i{flex:none;width:7px;height:7px;margin-top:7px;border-radius:50%;background:var(--cyan)}.evidence mark{margin-left:auto;height:max-content;background:#16453a;color:var(--ok);font-size:9px}.unknown{margin-top:12px}.links{display:flex;flex-wrap:wrap;gap:9px;margin:12px 0}.links a{flex:1;border:1px solid var(--cyan);border-radius:6px;padding:9px;background:#122b35;color:var(--cyan);font-weight:800;text-decoration:none}.files{max-height:70vh;overflow:auto;margin-top:12px;padding:14px;border:1px solid var(--line);border-radius:9px;background:var(--surface)}.files details{padding:5px;border-bottom:1px solid var(--line)}.files summary{cursor:pointer}.files ul{padding-left:20px}#search,select{width:100%;padding:10px;border:1px solid var(--line);border-radius:6px;background:var(--surface);color:var(--ink)}#trace{margin-top:10px;color:var(--cyan);font-family:ui-monospace,monospace}.help{max-width:440px;border:1px solid var(--cyan);border-radius:10px;background:var(--up);color:var(--ink);box-shadow:0 20px 60px #000b}.help::backdrop{background:#000b}.help button{float:right;border:0;border-radius:5px;padding:6px 9px;background:var(--cyan);color:#071217;font-weight:800}@media(max-width:900px){.hero{grid-template-columns:1fr;max-width:680px;margin:auto}.console-wrap{order:-1}}@media(max-width:620px){.portal{padding:0}.frame{border-radius:0;border-left:0;border-right:0}.mast{padding:10px 13px}.mast small{display:none}.nav{padding:9px 12px}.hero,.view{padding:20px 14px}.identity h1{font-size:51px}.console{padding:17px 13px 24px}.screen{padding:10px}.grid,.two,.columns,.checks{grid-template-columns:1fr}}
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def render(context: dict, agent_text: str, graphify_files: list[str]) -> str:
|
|
65
|
+
project, system = context["project"], context.get("system_model", {})
|
|
66
|
+
stack = [item for values in context.get("stack", {}).values() if isinstance(values, list) for item in values]
|
|
67
|
+
points, flows, evidence = system.get("entry_points", []), system.get("main_flows", []), context.get("evidence", [])
|
|
68
|
+
unknowns, scope, explore = system.get("unknowns", []), system.get("index_scope", {}), system.get("exploration", {})
|
|
69
|
+
framework = system.get("framework") or {}
|
|
70
|
+
state = context.get("freshness", {}).get("status") or ("CURRENT" if project.get("git_commit") else "UNINITIALIZED")
|
|
71
|
+
project_name = project.get("name") or "Proyecto sin nombre"
|
|
72
|
+
tags = "".join(f"<span>{e(x)}</span>" for x in stack[:6]) or "<span>Stack no identificado</span>"
|
|
73
|
+
if framework.get("version"):
|
|
74
|
+
tags += f"<span>{e(framework.get('name', 'Framework'))} {e(framework['version'])}</span>"
|
|
75
|
+
if framework.get("php_version"):
|
|
76
|
+
tags += f"<span>PHP {e(framework['php_version'])}</span>"
|
|
77
|
+
purpose = system.get("purpose", {}).get("text") or "No pudimos determinar el propósito con suficiente confianza."
|
|
78
|
+
flow_cards = "".join(f"<article class='flow'><b>{e(flow.get('name', 'Flujo detectado'))}</b><p>{' <em>→</em> '.join(e(step.get('name', 'paso')) for step in flow.get('steps', []))}</p></article>" for flow in flows) or empty("No se detectaron flujos principales con confianza.")
|
|
79
|
+
evidence_rows = "".join(f"<li><i></i><div><b>{e(x.get('path', 'archivo'))}</b><small>{e(x.get('reason', 'Evidencia detectada'))}</small></div><mark>{e(x.get('status', 'CONFIRMED'))}</mark></li>" for x in evidence) or empty("No se registró evidencia aún.")
|
|
80
|
+
options = "".join(f"<option value='{i}'>{e(x.get('name', 'Flujo'))}</option>" for i, x in enumerate(flows))
|
|
81
|
+
traces = json.dumps([[step.get("name", "paso") for step in x.get("steps", [])] for x in flows]).replace("</", "<\\/")
|
|
82
|
+
graph_links = "".join(f"<a href='graphify-out/{name}' target='_blank' rel='noreferrer'>{label} ↗</a>" for name, label in [("graph.html", "Abrir grafo técnico"), ("GRAPH_REPORT.md", "Abrir informe técnico")] if name in graphify_files) or empty("No hay artefactos de Graphify disponibles.")
|
|
83
|
+
providers = [{"name": x.get("name"), "description": f"{x.get('status', 'UNKNOWN')} · {'ejecutado' if x.get('executed') else x.get('reason', 'no ejecutado')}"} for x in context.get("analysis", {}).get("providers", [])]
|
|
84
|
+
roots = [{"name": x.get("path"), "description": f"{x.get('files', 0)} archivos incluidos"} for x in scope.get("included_roots", [])]
|
|
85
|
+
excluded = [{"name": x, "description": f"{n} archivos"} for x, n in scope.get("excluded_reasons", {}).items()]
|
|
86
|
+
call_cards = "".join(f"<article class='flow'><b>{e(x.get('name', 'Recorrido'))}</b><p>{' <em>→</em> '.join(e(s.get('name', 'paso')) for s in x.get('steps', []))}</p></article>" for x in explore.get("call_paths", [])) or empty("No hay recorridos de llamadas verificables en el grafo.")
|
|
87
|
+
recent = "".join(f"<li><i></i><div><b>{e(x.get('subject', 'Cambio'))}</b><small>{e(x.get('date', ''))} · {e(x.get('commit', '')[:10])}</small></div></li>" for x in system.get("recent_changes", [])) or empty("No hay historial disponible.")
|
|
88
|
+
views = [
|
|
89
|
+
view("Sistema", "sistema", f"<div class='tech'><h3>Resumen de {e(project_name)}</h3><p>{e(system.get('project_story', {}).get('text') or purpose)}</p></div>", "⌘"),
|
|
90
|
+
view("Flujos principales", "flujos", f"<div class='grid'>{flow_cards}</div>" + (f"<div class='trace'><label for='flow-select'>TRAZAR UN FLUJO</label><select id='flow-select'>{options}</select><div id='trace'></div></div>" if flows else ""), "⌁"),
|
|
91
|
+
view("Entradas", "entradas", f"<div class='two'><article class='card'><h3>Puntos de entrada</h3><ul class='list'>{items(points, 'intent')}</ul></article><article class='card'><h3>Entradas y salidas</h3><div class='columns'><div><label>ENTRADAS</label><ul class='list'>{items(system.get('inputs', []), 'description')}</ul></div><div><label>SALIDAS</label><ul class='list'>{items(system.get('outputs', []), 'description')}</ul></div></div></article></div>", "⇢"),
|
|
92
|
+
view("Evidencias", "evidencia", f"<article class='card'><h3>Hechos que sostienen el mapa</h3><ul class='list evidence'>{evidence_rows}</ul></article><article class='card unknown'><h3>Lo que todavía no sabemos</h3><ul class='list'>{items([{'name': x, 'type': 'requiere validación'} for x in unknowns])}</ul></article>", "◈"),
|
|
93
|
+
view("Index Scope", "scope", f"<div class='tech'><h3>Qué entró al índice</h3><p>{scope.get('included_code_files', 0)} archivos de código incluidos; {scope.get('excluded_files', 0)} recursos excluidos; {scope.get('graph_nodes_outside_scope', 0)} nodos fuera del alcance. Configuración: <code>.klap/scope.json</code>.</p></div><div class='two'><article class='card'><h3>Directorios incluidos</h3><ul class='list'>{items(roots)}</ul></article><article class='card'><h3>Exclusiones por motivo</h3><ul class='list'>{items(excluded)}</ul></article></div>", "◎"),
|
|
94
|
+
view("Archivos y símbolos", "archivos", f"<input id='search' type='search' placeholder='Buscar archivo o símbolo' aria-label='Buscar archivo o símbolo'><div class='files'>{tree(explore.get('files', []))}</div>", "▤"),
|
|
95
|
+
view("Recorridos del grafo", "recorridos", f"<p class='empty'>Estas rutas muestran llamadas conectadas en Graphify; no implican todas las ramas.</p><div class='grid'>{call_cards}</div>", "⌁"),
|
|
96
|
+
view("Historial Git", "historial", f"<article class='card'><h3>Cambios recientes</h3><ul class='list'>{recent}</ul></article>", "◷"),
|
|
97
|
+
view("Graphify y proveedores", "tecnico", f"<div class='tech'><h3>Profundizá en el análisis del código</h3><p>{e(system.get('project_story', {}).get('text') or 'Todavía no hay una narrativa técnica verificable.')}</p></div><div class='links'>{graph_links}</div><div class='two'><article class='card'><h3>Dependencias</h3><ul class='list'>{items(system.get('dependencies', []), 'package')}</ul></article><article class='card'><h3>Proveedores precompilados</h3><ul class='list'>{items(providers)}</ul></article></div>", "⌬"),
|
|
98
|
+
]
|
|
99
|
+
onboarding = context.get("agent_onboarding", {})
|
|
100
|
+
checks = "".join(["<li>Reporte generado desde evidencia local</li>", f"<li>{'✓' if onboarding.get('status') in {'configured', 'unchanged'} else '○'} AGENTS.md</li>", "<li>✓ Contexto en .klap/</li>", f"<li>{'✓' if graphify_files else '○'} Graphify</li>", f"<li>{'✓' if onboarding.get('mcp', {}).get('available', False) else '○'} MCP</li>"])
|
|
101
|
+
console = f"<div class='console-wrap'><div class='label'>ANÁLISIS LOCAL<br><small>{len(evidence)} evidencias</small></div><section class='console' aria-label='Consola de análisis de {e(project_name)}'><div class='screen'><div class='screen-top'><span>{e(project_name)}</span><span>{e(state)}</span></div><h2>Mapa del sistema</h2>{map_html(system.get('system_interactions', {}))}<div class='stats'><div><span>Entradas</span><b>{len(points)}</b></div><div><span>Flujos</span><b>{len(flows)}</b></div><div><span>Confirmados</span><b>{len(evidence)}</b></div><div><span>Pendientes</span><b>{len(unknowns)}</b></div></div><p class='note'>Abrí una sección para profundizar en el análisis y su evidencia.</p></div><div class='controls'><div class='mini'><button data-help>Select · ayuda</button><a href='#sistema'>Start · explorar</a></div><div class='buttons'><button data-copy aria-label='Copiar contexto de agente'>A</button><a href='#evidencia' aria-label='Ir a evidencias'>B</a></div></div></section></div>"
|
|
102
|
+
script = """<script>const ctx=__CTX__,paths=__PATHS__,help=document.querySelector('#help'),links=[...document.querySelectorAll('.nav a')],sections=[...document.querySelectorAll('.view')];document.querySelectorAll('[data-copy]').forEach(b=>b.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(ctx);const t=b.textContent;b.textContent='✓';setTimeout(()=>b.textContent=t,1500)}catch{b.textContent='!'}}));document.querySelectorAll('[data-help]').forEach(b=>b.addEventListener('click',()=>help.showModal()));document.querySelector('[data-close]').addEventListener('click',()=>help.close());document.querySelectorAll('[data-evidence-link]').forEach(n=>{const open=()=>document.querySelector('#evidencia').scrollIntoView({behavior:'smooth'});n.addEventListener('click',open);n.addEventListener('keydown',event=>{if(event.key==='Enter'||event.key===' '){event.preventDefault();open()}})});const q=document.querySelector('#search');if(q)q.addEventListener('input',()=>document.querySelectorAll('.source-file').forEach(f=>f.hidden=!f.textContent.toLowerCase().includes(q.value.toLowerCase())));const s=document.querySelector('#flow-select');if(s){const out=document.querySelector('#trace'),show=()=>out.textContent=(paths[s.value]||[]).join(' → ');s.addEventListener('change',show);show()}const observer=new IntersectionObserver(rows=>rows.forEach(row=>{if(row.isIntersecting)links.forEach(link=>link.classList.toggle('active',link.getAttribute('href')==='#'+row.target.id))}),{rootMargin:'-25% 0px -65% 0px'});sections.forEach(section=>observer.observe(section));</script>""".replace("__CTX__", json.dumps(agent_text).replace("</", "<\\/")).replace("__PATHS__", traces)
|
|
103
|
+
navigation = [("sistema", "Sistema"), ("flujos", "Flujos"), ("entradas", "Entradas"), ("evidencia", "Evidencias"), ("scope", "Index Scope"), ("archivos", "Archivos"), ("recorridos", "Recorridos"), ("historial", "Git"), ("tecnico", "Graphify")]
|
|
104
|
+
top = "<nav class='nav' aria-label='Navegación del reporte'>" + "".join(f"<a href='#{key}'>{label}</a>" for key, label in navigation) + "</nav>"
|
|
105
|
+
return f"<!doctype html><html lang='es'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>{e(project_name)} · KlapContext</title><style>{CSS}</style></head><body><div class='portal'><div class='frame'><header class='mast'><span class='mark'>K</span><div><b>KlapContext</b><small>Reporte de ingeniería del repositorio</small></div><span class='state'>● {e(state)}</span></header>{top}<section class='hero' aria-labelledby='project-title'><div class='identity'><span class='eyebrow'>REPORTE DE INGENIERÍA</span><h1 id='project-title'>{e(project_name)}</h1><p>{e(purpose)}</p><div class='tags'>{tags}</div><button class='copy' data-copy>COPIAR CONTEXTO →</button><p class='intro'>Briefing listo para usar con un agente y navegar la evidencia técnica del proyecto.</p><ul class='checks'>{checks}</ul></div>{console}</section>{''.join(views)}</div></div><dialog class='help' id='help'><div><button data-close>Cerrar</button><h2>Confianza y navegación</h2><p>Los hechos confirmados se extraen durante <code>klap init</code>. Cada sección conserva los datos precompilados y su trazabilidad; las ausencias se muestran explícitamente.</p></div></dialog>{script}</body></html>"
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def write(context: dict, agent_text: str, graphify_files: list[str], output: Path) -> None:
|
|
109
|
+
output.write_text(render(context, agent_text, graphify_files), encoding="utf-8")
|
|
@@ -18,7 +18,7 @@ class GraphifyProvider:
|
|
|
18
18
|
request = request or {}
|
|
19
19
|
if request.get("existing_only"):
|
|
20
20
|
return graphify.canonical_graph(Path(request.get("root", "."))).exists()
|
|
21
|
-
return graphify.command_prefix() is not None
|
|
21
|
+
return graphify.command_prefix(Path(request.get("root", "."))) is not None
|
|
22
22
|
|
|
23
23
|
def dependency_graph(self, request: Any) -> ProviderResult[dict]:
|
|
24
24
|
request = request or {}
|