klapcontext 0.7.1__tar.gz → 0.8.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.
- {klapcontext-0.7.1/src/klapcontext.egg-info → klapcontext-0.8.2}/PKG-INFO +1 -1
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/__init__.py +2 -1
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent_context.py +14 -1
- klapcontext-0.8.2/src/klapcontext/portal.py +99 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2/src/klapcontext.egg-info}/PKG-INFO +1 -1
- {klapcontext-0.7.1 → klapcontext-0.8.2}/tests/test_klapcontext.py +9 -0
- klapcontext-0.7.1/src/klapcontext/portal.py +0 -187
- {klapcontext-0.7.1 → klapcontext-0.8.2}/LICENSE +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/README.md +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/pyproject.toml +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/setup.cfg +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/__init__.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/capabilities.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/compiler.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/impact.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/intents.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/models.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/planner.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/verification.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent/workflows.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/agent_setup.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/cli.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/context_builder.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/detector.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/evidence.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/exploration.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/frameworks/__init__.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/frameworks/base.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/frameworks/fastapi.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/frameworks/generic_php.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/frameworks/laravel.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/frameworks/node.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/freshness.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/git.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/graphify.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/__init__.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/base.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/code_intelligence.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/contracts.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/graphify.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/javascript_intelligence.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/models.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/native.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/optional.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/python_intelligence.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/registry.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/resolver.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/serena/__init__.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/serena/adapter.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/serena/client.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/serena/config.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/services.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/scope.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/semantic.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/system.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext.egg-info/SOURCES.txt +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext.egg-info/dependency_links.txt +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext.egg-info/entry_points.txt +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext.egg-info/requires.txt +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext.egg-info/top_level.txt +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/tests/test_agent_setup.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/tests/test_exploration.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/tests/test_provider_architecture.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/tests/test_reliability.py +0 -0
- {klapcontext-0.7.1 → klapcontext-0.8.2}/tests/test_serena_provider.py +0 -0
|
@@ -3,10 +3,23 @@ from pathlib import Path
|
|
|
3
3
|
|
|
4
4
|
def items(values, key="name"):
|
|
5
5
|
return "\n".join(f"- {value.get(key) or value.get('path')}" for value in values) if values else "Sin evidencia disponible."
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def dependency_items(values) -> str:
|
|
9
|
+
"""Render both framework-manifest and normalized semantic dependencies."""
|
|
10
|
+
rows = []
|
|
11
|
+
for value in values[:12]:
|
|
12
|
+
name = value.get("package") or value.get("name") or value.get("path") or "Dependencia sin nombre"
|
|
13
|
+
version = value.get("version") or "versión no declarada"
|
|
14
|
+
scope = value.get("scope") or value.get("category") or value.get("status") or "sin clasificación"
|
|
15
|
+
rows.append(f"- {name} {version} ({scope})")
|
|
16
|
+
return "\n".join(rows) or "Sin dependencias específicas detectadas."
|
|
17
|
+
|
|
18
|
+
|
|
6
19
|
def render(context: dict) -> str:
|
|
7
20
|
s=context.get("system_model",{}); purpose=s.get("purpose",{}).get("text") or "No fue posible determinar el propósito con confianza."
|
|
8
21
|
exploration=s.get("exploration",{}); coverage=exploration.get("coverage",{}); scope=s.get("index_scope",{})
|
|
9
|
-
sections=[("Propósito del Proyecto",purpose),("Cómo Funciona el Sistema",s.get("project_story",{}).get("text") or "No fue posible determinarlo con confianza."),("Superficies de Ejecución","\n".join(f"- {x['label']}: {x['count']}" for x in s.get("runtime_surfaces",[])) or "Sin superficies específicas detectadas."),("Rutas",items(s.get("routes",[]))),("Procesos Programados",items(s.get("scheduled_processes",[]))),("Jobs de Cola",items(s.get("queue_jobs",[]))),("Flujos Principales","\n".join(f"- **{x['name']}**: " + " → ".join(step["name"] for step in x["steps"]) for x in s.get("main_flows",[])) or "No se detectaron flujos principales con confianza."),("Dependencias Importantes",
|
|
22
|
+
sections=[("Propósito del Proyecto",purpose),("Cómo Funciona el Sistema",s.get("project_story",{}).get("text") or "No fue posible determinarlo con confianza."),("Superficies de Ejecución","\n".join(f"- {x['label']}: {x['count']}" for x in s.get("runtime_surfaces",[])) or "Sin superficies específicas detectadas."),("Rutas",items(s.get("routes",[]))),("Procesos Programados",items(s.get("scheduled_processes",[]))),("Jobs de Cola",items(s.get("queue_jobs",[]))),("Flujos Principales","\n".join(f"- **{x['name']}**: " + " → ".join(step["name"] for step in x["steps"]) for x in s.get("main_flows",[])) or "No se detectaron flujos principales con confianza."),("Dependencias Importantes",dependency_items(s.get("dependencies",[]))),("Sistemas Externos",items(s.get("external_systems",[]))),("Almacenes de Datos",items(s.get("datastores",[]))),("Por Dónde Empezar","\n".join(f"- **{x['intent']}**: {', '.join(x['paths'])}" for x in s.get("start_here",[])) or "Sin recomendaciones detectadas."),("Incertidumbres","\n".join(f"- {x}" for x in s.get("unknowns",[])) or "Sin incertidumbres relevantes registradas.")]
|
|
10
23
|
lines=["# KlapContext — Contexto del Proyecto"]
|
|
11
24
|
for title,body in sections: lines.extend(["",f"## {title}",body])
|
|
12
25
|
lines.extend(["", "## Alcance del índice", f"- Código incluido: {scope.get('included_code_files', 0)} archivos; excluidos: {scope.get('excluded_files', 0)}; reglas: {scope.get('rules_version', 'UNKNOWN')}.", "- Consulta `.klap/context.json` → `system_model.index_scope` y editá `.klap/scope.json` para revisar excepciones.", "- El índice filtrado es una guía verificable, no autoridad final; revisá código fuente e incertidumbres antes de modificar.", "", "## Cobertura de la exploración", f"- Archivos de código con nodos: {coverage.get('code_files_with_graph_nodes', 0)}/{coverage.get('eligible_code_files', 0)}; estado: {coverage.get('status', 'UNKNOWN')}.", "- Un archivo representado en el grafo no implica que todas sus funciones estén resueltas.", "", "## Estructura y recorridos de llamadas", "- Consulta `.klap/context.json` → `system_model.exploration.files` para localizar archivos y símbolos por ruta."])
|
|
@@ -0,0 +1,99 @@
|
|
|
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(f"<li><i></i><div><b>{e(row.get(key) 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>" for row in rows)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def map_html(interactions: dict) -> str:
|
|
24
|
+
nodes = interactions.get("nodes", [])[:8]
|
|
25
|
+
if not nodes:
|
|
26
|
+
return "<p class='map-empty'>El mapa aparecerá cuando haya interacciones verificables.</p>"
|
|
27
|
+
pos = [(75, 75), (220, 42), (220, 130), (220, 214), (400, 75), (400, 174), (565, 130), (75, 208)]
|
|
28
|
+
points = {node["name"]: pos[index] for index, node in enumerate(nodes)}
|
|
29
|
+
edges = "".join(f"<path d='M {points[x['source']][0]} {points[x['source']][1]} C {points[x['source']][0]+55} {points[x['source']][1]}, {points[x['target']][0]-55} {points[x['target']][1]}, {points[x['target']][0]} {points[x['target']][1]}'/>" for x in interactions.get("edges", [])[:14] if x.get("source") in points and x.get("target") in points)
|
|
30
|
+
boxes = "".join(f"<g class='node' tabindex='0' role='button' data-node='{e(x['name'])}'><rect x='{points[x['name']][0]-50}' y='{points[x['name']][1]-18}' width='100' height='36' rx='6'/><text x='{points[x['name']][0]}' y='{points[x['name']][1]-1}'>{e(x['name'])[:14]}</text><text class='kind' x='{points[x['name']][0]}' y='{points[x['name']][1]+11}'>{e(x.get('type','componente'))[:14]}</text></g>" for x in nodes)
|
|
31
|
+
return f"<div class='map'><svg viewBox='0 0 640 260' role='img' aria-label='Mapa de relaciones'><g class='edges'>{edges}</g>{boxes}</svg></div>"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def view(title: str, name: str, body: str, icon: str) -> str:
|
|
35
|
+
return f"<section class='view' data-view='{name}'><header class='view-head'><span>{icon}</span><div><h2>{e(title)}</h2><p>Datos extraídos del repositorio y respaldados por evidencia.</p></div></header>{body}</section>"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def tree(files: list[dict]) -> str:
|
|
39
|
+
if not files:
|
|
40
|
+
return empty("No se encontraron archivos.")
|
|
41
|
+
rows = []
|
|
42
|
+
for file in files:
|
|
43
|
+
symbols = "".join(f"<li>{e(x.get('name', 'símbolo'))} <small>{e(x.get('kind', ''))}</small></li>" for x in file.get("symbols", [])[:100])
|
|
44
|
+
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>")
|
|
45
|
+
return "".join(rows)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
CSS = """
|
|
49
|
+
: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{display:flex;gap:7px;overflow-x:auto;padding:12px 20px;border-bottom:1px solid var(--line)}.nav button{flex:none;border:1px solid var(--line);border-radius:7px;padding:6px 11px;background:#14212a;color:var(--ink);font-size:11px;font-weight:800}.nav button:hover,.nav button.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}.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:.8;letter-spacing:.04em;text-shadow:3px 3px var(--cyan)}.identity h1 span{display:block}.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;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{border:1px solid #1c3f49;border-radius:999px;padding:5px 9px;background:#17323c;color:var(--cyan);font:900 8px ui-monospace,monospace}.buttons{display:flex;justify-content:space-between}.buttons button{width:32px;height:32px;border:2px solid #087b84;border-radius:50%;background:var(--cyan);color:#071217;font-weight:950;box-shadow:0 3px #075960}.view{display:none;padding:28px}.view.active{display:block;animation:in .2s ease}@keyframes in{from{opacity:.25;transform:translateY(7px)}to{opacity:1;transform:none}}.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}}
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def render(context: dict, agent_text: str, graphify_files: list[str]) -> str:
|
|
54
|
+
project, system = context["project"], context.get("system_model", {})
|
|
55
|
+
stack = [item for values in context.get("stack", {}).values() if isinstance(values, list) for item in values]
|
|
56
|
+
points, flows, evidence = system.get("entry_points", []), system.get("main_flows", []), context.get("evidence", [])
|
|
57
|
+
unknowns, scope, explore = system.get("unknowns", []), system.get("index_scope", {}), system.get("exploration", {})
|
|
58
|
+
framework = system.get("framework") or {}
|
|
59
|
+
state = context.get("freshness", {}).get("status") or ("CURRENT" if project.get("git_commit") else "UNINITIALIZED")
|
|
60
|
+
tags = "".join(f"<span>{e(x)}</span>" for x in stack[:6]) or "<span>Stack no identificado</span>"
|
|
61
|
+
if framework.get("version"): tags += f"<span>{e(framework.get('name','Framework'))} {e(framework['version'])}</span>"
|
|
62
|
+
if framework.get("php_version"): tags += f"<span>PHP {e(framework['php_version'])}</span>"
|
|
63
|
+
purpose = system.get("purpose", {}).get("text") or "No pudimos determinar el propósito con suficiente confianza."
|
|
64
|
+
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.")
|
|
65
|
+
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.")
|
|
66
|
+
options = "".join(f"<option value='{i}'>{e(x.get('name','Flujo'))}</option>" for i,x in enumerate(flows))
|
|
67
|
+
traces = json.dumps([[step.get("name","paso") for step in x.get("steps",[])] for x in flows]).replace("</","<\\/")
|
|
68
|
+
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.")
|
|
69
|
+
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",[])]
|
|
70
|
+
roots = [{"name": x.get("path"),"description":f"{x.get('files',0)} archivos incluidos"} for x in scope.get("included_roots",[])]
|
|
71
|
+
excluded = [{"name":x,"description":f"{n} archivos"} for x,n in scope.get("excluded_reasons",{}).items()]
|
|
72
|
+
call_cards = "".join(f"<article class='flow'><b>{e(x['name'])}</b><p>{' <em>→</em> '.join(e(s['name']) for s in x['steps'])}</p></article>" for x in explore.get("call_paths",[])) or empty("No hay recorridos de llamadas verificables en el grafo.")
|
|
73
|
+
recent = "".join(f"<li><i></i><div><b>{e(x['subject'])}</b><small>{e(x['date'])} · {e(x['commit'][:10])}</small></div></li>" for x in system.get("recent_changes",[])) or empty("No hay historial disponible.")
|
|
74
|
+
views = [
|
|
75
|
+
view("Sistema","sistema","<p class='empty'>Usá la consola y la navegación superior para recorrer el análisis.</p>","⌘"),
|
|
76
|
+
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 ""),"⌁"),
|
|
77
|
+
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>","⇢"),
|
|
78
|
+
view("Evidencia","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>","◈"),
|
|
79
|
+
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>","◎"),
|
|
80
|
+
view("Exploración técnica","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>","⌬"),
|
|
81
|
+
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>","▤"),
|
|
82
|
+
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>","⌁"),
|
|
83
|
+
view("Historial Git","historial",f"<article class='card'><h3>Cambios recientes</h3><ul class='list'>{recent}</ul></article>","◷"),
|
|
84
|
+
]
|
|
85
|
+
onboarding = context.get("agent_onboarding",{})
|
|
86
|
+
checks = f"<li>Use this project with your AI agent</li><li>{'✓' if onboarding.get('status') in {'configured','unchanged'} else '○'} AGENTS.md</li><li>✓ Contexto en .klap/</li><li>{'✓' if graphify_files else '○'} Graphify</li><li>{'✓' if onboarding.get('mcp',{}).get('available',False) else '○'} MCP</li>"
|
|
87
|
+
console = f"<div class='console-wrap'><div class='label'>KLAP_CONTEXT<br><small>{len(evidence)} evidencias</small></div><section class='console' aria-label='Consola de análisis'><div class='screen'><div class='screen-top'><span>{e(project['name'])}</span><span>SISTEMA</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'>Siguiente paso: abrí Evidencias para investigar un área, flujo o archivo.</p></div><div class='controls'><div class='mini'><button data-help>Select · ayuda</button><button data-go='sistema'>Start · explorar</button></div><div class='buttons'><button data-copy aria-label='Copiar contexto'>A</button><button data-go='evidencia' aria-label='Ver evidencias'>B</button></div></div></section></div>"
|
|
88
|
+
script = """<script>const ctx=__CTX__,paths=__PATHS__,views=[...document.querySelectorAll('.view')],nav=[...document.querySelectorAll('[data-go]')],help=document.querySelector('#help');function go(n){if(!views.some(v=>v.dataset.view===n))n='sistema';views.forEach(v=>v.classList.toggle('active',v.dataset.view===n));nav.forEach(b=>b.classList.toggle('active',b.dataset.go===n));history.replaceState(null,'','#'+n);window.scrollTo({top:0,behavior:'smooth'})}nav.forEach(b=>b.addEventListener('click',()=>go(b.dataset.go)));document.querySelectorAll('[data-copy]').forEach(b=>b.addEventListener('click',async()=>{try{await navigator.clipboard.writeText(ctx);const t=b.textContent;b.textContent='✓ COPIADO';setTimeout(()=>b.textContent=t,1500)}catch{b.textContent='No se pudo copiar'}}));document.querySelectorAll('[data-help]').forEach(b=>b.addEventListener('click',()=>help.showModal()));document.querySelector('[data-close]').addEventListener('click',()=>help.close());document.querySelectorAll('.node').forEach(n=>n.addEventListener('click',()=>go('evidencia')));go(location.hash.slice(1)||'sistema');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()}</script>""".replace("__CTX__",json.dumps(agent_text).replace("</","<\\/")).replace("__PATHS__",traces)
|
|
89
|
+
navigation = [("sistema", "Sistema"), ("flujos", "Flujos"), ("entradas", "Entradas"), ("evidencia", "Evidencias"), ("scope", "Index Scope"), ("archivos", "Archivos"), ("recorridos", "Recorridos"), ("historial", "Git"), ("tecnico", "Graphify")]
|
|
90
|
+
buttons = []
|
|
91
|
+
for key, label in navigation:
|
|
92
|
+
active = " class='active'" if key == "sistema" else ""
|
|
93
|
+
buttons.append(f"<button{active} data-go='{key}'>{label}</button>")
|
|
94
|
+
top = "<nav class='nav' aria-label='Navegación del reporte'>" + "".join(buttons) + "</nav>"
|
|
95
|
+
return f"<!doctype html><html lang='es'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>KlapContext · {e(project['name'])}</title><style>{CSS}</style></head><body><div class='portal'><div class='frame'><header class='mast'><span class='mark'>C</span><div><b>Charcoal + Cyan + Soft Gray</b><small>Contexto de ingeniería autónomo y offline.</small></div><span class='state'>● {e(state)}</span></header>{top}<section class='hero'><div class='identity'><span class='eyebrow'>PANORAMA DEL SISTEMA · REPORTE DE INGENIERÍA</span><h1><span>KLAP_</span><span>CONTEXT</span></h1><p>Hace que tu repositorio sea comprensible para humanos e IA.</p><div class='tags'>{tags}</div><button class='copy' data-copy>COPIAR CONTEXTO →</button><p class='intro'>{e(purpose)}</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>. La consola resume datos reales; las vistas técnicas preservan detalle y trazabilidad.</p></div></dialog>{script}</body></html>"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def write(context: dict, agent_text: str, graphify_files: list[str], output: Path) -> None:
|
|
99
|
+
output.write_text(render(context, agent_text, graphify_files), encoding="utf-8")
|
|
@@ -74,6 +74,15 @@ def test_portal_has_dashboard_and_real_views(tmp_path):
|
|
|
74
74
|
assert "data-go='entradas'" in page
|
|
75
75
|
assert "data-node=" in page or "map-empty" in page
|
|
76
76
|
assert "Use this project with your AI agent" in page
|
|
77
|
+
assert "KLAP_CONTEXT" in page and "COPIAR CONTEXTO" in page
|
|
78
|
+
assert "--cyan:#00E5F0" in page and "data-help" in page
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def test_agent_context_accepts_normalized_dependency_names(tmp_path):
|
|
82
|
+
context = build(fixture_repo(tmp_path), {"nodes": []})
|
|
83
|
+
context["system_model"]["dependencies"] = [{"name": "express", "version": "^5", "category": "framework"}]
|
|
84
|
+
text = render_agent(context)
|
|
85
|
+
assert "express ^5 (framework)" in text
|
|
77
86
|
|
|
78
87
|
|
|
79
88
|
def test_cli_and_portal_ignore_scalar_stack_metadata(tmp_path, monkeypatch, capsys):
|
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import html
|
|
4
|
-
import json
|
|
5
|
-
from pathlib import Path
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def e(value: object) -> str:
|
|
9
|
-
return html.escape(str(value))
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def empty(message: str) -> str:
|
|
13
|
-
return f"<p class='empty'>{e(message)}</p>"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def item_rows(items: list[dict], key: str = "name") -> str:
|
|
17
|
-
if not items:
|
|
18
|
-
return empty("Sin evidencia suficiente todavía.")
|
|
19
|
-
return "".join(
|
|
20
|
-
"<li><span class='dot'></span><div><strong>"
|
|
21
|
-
f"{e(item.get(key) or item.get('path') or item.get('intent') or 'Sin nombre')}</strong>"
|
|
22
|
-
f"<small>{e(item.get('type') or item.get('role') or item.get('description') or '')}</small>"
|
|
23
|
-
"</div></li>"
|
|
24
|
-
for item in items
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def system_map(interactions: dict) -> str:
|
|
29
|
-
"""Render a compact, intentionally abstract map for the dashboard overview."""
|
|
30
|
-
nodes = interactions.get("nodes", [])[:9]
|
|
31
|
-
edges = interactions.get("edges", [])[:14]
|
|
32
|
-
if not nodes:
|
|
33
|
-
return "<div class='map-empty'>El mapa aparecerá cuando haya interacciones verificables.</div>"
|
|
34
|
-
|
|
35
|
-
positions = [(105, 92), (270, 50), (270, 142), (270, 234), (460, 92), (460, 190), (620, 140), (105, 220), (620, 260)]
|
|
36
|
-
points = {node["name"]: positions[index] for index, node in enumerate(nodes)}
|
|
37
|
-
links = "".join(
|
|
38
|
-
f"<path d='M {points[edge['source']][0]} {points[edge['source']][1]} "
|
|
39
|
-
f"C {points[edge['source']][0] + 70} {points[edge['source']][1]}, "
|
|
40
|
-
f"{points[edge['target']][0] - 70} {points[edge['target']][1]}, "
|
|
41
|
-
f"{points[edge['target']][0]} {points[edge['target']][1]}'/>"
|
|
42
|
-
for edge in edges
|
|
43
|
-
if edge.get("source") in points and edge.get("target") in points
|
|
44
|
-
)
|
|
45
|
-
boxes = "".join(
|
|
46
|
-
f"<g class='map-node' tabindex='0' role='button' data-node='{e(node['name'])}' data-kind='{e(node.get('type', 'componente'))}'><rect x='{points[node['name']][0] - 65}' y='{points[node['name']][1] - 24}' width='130' height='48' rx='8'/>"
|
|
47
|
-
f"<text x='{points[node['name']][0]}' y='{points[node['name']][1] - 2}'>{e(node['name'])[:18]}</text>"
|
|
48
|
-
f"<text class='map-kind' x='{points[node['name']][0]}' y='{points[node['name']][1] + 13}'>{e(node.get('type', 'componente'))[:18]}</text></g>"
|
|
49
|
-
for node in nodes
|
|
50
|
-
)
|
|
51
|
-
return (
|
|
52
|
-
"<div class='map-shell'><div class='map-legend'><span><i></i> interacción detectada</span>"
|
|
53
|
-
"<span>vista conceptual</span></div><svg class='system-map' viewBox='0 0 720 300' role='img' "
|
|
54
|
-
"aria-label='Mapa conceptual del sistema'><g class='map-links'>"
|
|
55
|
-
f"{links}</g>{boxes}</svg></div>"
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
def section(title: str, content: str, view: str, icon: str) -> str:
|
|
60
|
-
return f"<section class='view' data-view='{view}' aria-label='{e(title)}'><div class='view-title'><span>{icon}</span><div><h2>{e(title)}</h2><p>Información extraída del repositorio y respaldada por evidencia.</p></div></div>{content}</section>"
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def file_tree(files: list[dict]) -> str:
|
|
64
|
-
tree: dict = {}
|
|
65
|
-
for item in files:
|
|
66
|
-
cursor = tree
|
|
67
|
-
parts = item["path"].split("/")
|
|
68
|
-
for part in parts[:-1]:
|
|
69
|
-
cursor = cursor.setdefault(part, {})
|
|
70
|
-
cursor[parts[-1]] = item
|
|
71
|
-
|
|
72
|
-
def branch(directory: dict) -> str:
|
|
73
|
-
rows = []
|
|
74
|
-
for name, value in sorted(directory.items(), key=lambda pair: ("path" in pair[1], pair[0].lower())):
|
|
75
|
-
if "path" not in value:
|
|
76
|
-
rows.append(f"<details class='folder'><summary>▸ {e(name)}</summary>{branch(value)}</details>")
|
|
77
|
-
continue
|
|
78
|
-
symbols = value.get("symbols", [])
|
|
79
|
-
details = "".join(f"<li><b>{e(symbol['name'])}</b> <small>{e(symbol['kind'])} · {e(symbol['source'])}</small>" +
|
|
80
|
-
("<ul>" + "".join(f"<li>{e(member['name'])}</li>" for member in symbol.get("members", [])) + "</ul>" if symbol.get("members") else "") + "</li>" for symbol in symbols[:100])
|
|
81
|
-
rows.append(f"<details class='source-file'><summary>{e(name)} <small>{len(symbols)} símbolos · {'grafo' if value['analyzed'] else 'sin nodos del grafo'}</small></summary><p>{e(value['path'])}</p><ul>{details or '<li>Sin símbolos detectados.</li>'}</ul></details>")
|
|
82
|
-
return "".join(rows)
|
|
83
|
-
return branch(tree) or empty("No se encontraron archivos.")
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
def render(context: dict, agent_text: str, graphify_files: list[str]) -> str:
|
|
87
|
-
project = context["project"]
|
|
88
|
-
system = context.get("system_model", {})
|
|
89
|
-
stack = [item for values in context.get("stack", {}).values() if isinstance(values, list) for item in values]
|
|
90
|
-
points = system.get("entry_points", [])
|
|
91
|
-
flows = system.get("main_flows", [])
|
|
92
|
-
evidence = context.get("evidence", [])
|
|
93
|
-
surfaces = system.get("runtime_surfaces", [])
|
|
94
|
-
unknowns = system.get("unknowns", [])
|
|
95
|
-
scope = system.get("index_scope", {})
|
|
96
|
-
framework = system.get("framework") or {}
|
|
97
|
-
onboarding = context.get("agent_onboarding", {})
|
|
98
|
-
provider_rows = context.get("analysis", {}).get("providers", [])
|
|
99
|
-
provider_summary = [{"name": item.get("name"), "description": f"{item.get('status', 'UNKNOWN')} · {'ejecutado' if item.get('executed') else item.get('reason', 'no ejecutado')}"} for item in provider_rows]
|
|
100
|
-
onboarding_ready = onboarding.get("status") in {"configured", "unchanged"}
|
|
101
|
-
mcp_ready = onboarding.get("mcp", {}).get("available", False)
|
|
102
|
-
purpose = system.get("purpose", {}).get("text") or "No pudimos determinar el propósito con suficiente confianza."
|
|
103
|
-
story = system.get("project_story", {}).get("text") or system.get("runtime", {}).get("description") or "Todavía no hay una narrativa técnica verificable."
|
|
104
|
-
exploration = system.get("exploration", {})
|
|
105
|
-
coverage = exploration.get("coverage", {})
|
|
106
|
-
graph_paths = exploration.get("call_paths", [])
|
|
107
|
-
call_cards = "".join(f"<article class='flow-card'><b>{e(path['name'])}</b><p>{' <em>→</em> '.join(e(step['name']) for step in path['steps'])}</p><small>{e(path['note'])} Ramas adicionales: {path['branches']}.</small></article>" for path in graph_paths)
|
|
108
|
-
recent = "".join(f"<li><strong>{e(change['subject'])}</strong><small>{e(change['date'])} · {e(change['commit'][:10])}</small></li>" for change in system.get("recent_changes", []))
|
|
109
|
-
freshness = context.get("freshness", {})
|
|
110
|
-
state = freshness.get("status") or ("CURRENT" if project.get("git_commit") else "UNINITIALIZED")
|
|
111
|
-
tags = "".join(f"<span>{e(item)}</span>" for item in stack[:6])
|
|
112
|
-
if framework.get("version"):
|
|
113
|
-
tags += f"<span>{e(framework.get('name', 'Framework'))} {e(framework['version'])}</span>"
|
|
114
|
-
if framework.get("php_version"):
|
|
115
|
-
tags += f"<span>PHP {e(framework['php_version'])}</span>"
|
|
116
|
-
tags = tags or "<span>Stack no identificado</span>"
|
|
117
|
-
surface_cards = "".join(
|
|
118
|
-
f"<article class='surface'><b>{e(item.get('count', 0))}</b><span>{e(item.get('label', 'superficies'))}</span></article>"
|
|
119
|
-
for item in surfaces
|
|
120
|
-
) or "<article class='surface'><b>—</b><span>superficies detectadas</span></article>"
|
|
121
|
-
flow_cards = "".join(
|
|
122
|
-
f"<article class='flow-card'><b>{e(flow.get('name', 'Flujo detectado'))}</b><p>"
|
|
123
|
-
f"{' <em>→</em> '.join(e(step.get('name', 'paso')) for step in flow.get('steps', []))}</p></article>"
|
|
124
|
-
for flow in flows
|
|
125
|
-
) or empty("No se detectaron flujos principales con confianza.")
|
|
126
|
-
evidence_rows = "".join(
|
|
127
|
-
f"<li><span class='dot'></span><div><strong>{e(item.get('path', 'archivo'))}</strong>"
|
|
128
|
-
f"<small>{e(item.get('reason', 'Evidencia detectada'))}</small></div><mark>{e(item.get('status', 'CONFIRMED'))}</mark></li>"
|
|
129
|
-
for item in evidence
|
|
130
|
-
) or empty("No se registró evidencia aún.")
|
|
131
|
-
callflow = next((name for name in graphify_files if name == "callflow.html" or name.endswith("-callflow.html")), None)
|
|
132
|
-
graph_specs = [("graph.html", "Abrir grafo técnico"), (callflow, "Abrir call flows"), ("GRAPH_REPORT.md", "Abrir informe técnico")]
|
|
133
|
-
graph_links = "".join(
|
|
134
|
-
f"<a href='graphify-out/{name}' target='_blank' rel='noreferrer'>{label}<span>↗</span></a>"
|
|
135
|
-
for name, label in graph_specs if name and name in graphify_files
|
|
136
|
-
) or empty("No hay artefactos de Graphify disponibles.")
|
|
137
|
-
start_rows = "".join(
|
|
138
|
-
f"<li><b>{e(item.get('intent', 'Revisar el sistema'))}</b><small>{e(' · '.join(item.get('paths', [])))}</small></li>"
|
|
139
|
-
for item in system.get("start_here", [])
|
|
140
|
-
) or empty("No hay recomendaciones de onboarding todavía.")
|
|
141
|
-
flow_options = "".join(f"<option value='{index}'>{e(flow.get('name', 'Flujo'))}</option>" for index, flow in enumerate(flows))
|
|
142
|
-
traces = json.dumps([[step.get("name", "paso") for step in flow.get("steps", [])] for flow in flows]).replace("</", "<\\/")
|
|
143
|
-
scope_roots = [{"name": item.get("path"), "description": f"{item.get('files', 0)} archivos incluidos"} for item in scope.get("included_roots", [])]
|
|
144
|
-
scope_exclusions = [{"name": reason, "description": f"{count} archivos"} for reason, count in scope.get("excluded_reasons", {}).items()]
|
|
145
|
-
scope_warnings = [{"name": warning, "description": "revisar alcance"} for warning in scope.get("warnings", [])]
|
|
146
|
-
if system.get("disabled_declarations"):
|
|
147
|
-
scope_warnings.append({"name": f"{len(system['disabled_declarations'])} declaración(es) comentadas o deshabilitadas no cuentan como operación activa", "description": "detección de código activo"})
|
|
148
|
-
if context.get("freshness", {}).get("status") not in {None, "CURRENT"}:
|
|
149
|
-
scope_warnings.append({"name": context["freshness"].get("reason", "El índice está desactualizado"), "description": "freshness"})
|
|
150
|
-
|
|
151
|
-
overview = f"""
|
|
152
|
-
<section class='overview-head'>
|
|
153
|
-
<div><span class='eyebrow'>PANORAMA DEL SISTEMA</span><h1>{e(project['name'])}</h1>
|
|
154
|
-
<p>{e(purpose)}</p><div class='tags'>{tags}</div></div>
|
|
155
|
-
<div class='overview-state'><span class='live'>● {state}</span><small>análisis basado en {len(evidence)} evidencias</small></div>
|
|
156
|
-
</section>
|
|
157
|
-
<section class='map-card' aria-label='Mapa de interacción del sistema'><header><div><span class='icon'>⌘</span><div><h2>Mapa del sistema</h2><p>Componentes, relaciones y puntos de entrada en una sola lectura.</p></div></div><button class='outline' data-go='tecnico'>Explorar evidencia <span>→</span></button></header>{system_map(system.get('system_interactions', {}))}</section>
|
|
158
|
-
<section class='quick-grid'>
|
|
159
|
-
<article data-go='entradas'><b>{len(points)}</b><span>Puntos de entrada</span><small>CLI, HTTP, archivos o scheduler</small></article>
|
|
160
|
-
<article data-go='flujos'><b>{len(flows)}</b><span>Flujos principales</span><small>Procesos y secuencias detectadas</small></article>
|
|
161
|
-
<article data-go='evidencia'><b>{len(evidence)}</b><span>Hechos confirmados</span><small>Con respaldo en el código</small></article>
|
|
162
|
-
<article class='attention' data-go='evidencia'><b>{len(unknowns)}</b><span>Pendientes de validar</span><small>Aspectos con evidencia insuficiente</small></article>
|
|
163
|
-
</section>
|
|
164
|
-
<section class='dashboard-bottom'><div class='compact-panel'><header><h2>Cómo se ejecuta</h2><button class='text-button' data-go='entradas'>Ver entradas →</button></header><div class='surfaces'>{surface_cards}</div></div>
|
|
165
|
-
<div class='compact-panel'><header><h2>Lectura recomendada</h2><button class='text-button' data-go='flujos'>Ver flujos →</button></header><ul class='start-list'>{start_rows}</ul></div></section>
|
|
166
|
-
"""
|
|
167
|
-
views = [
|
|
168
|
-
section("Sistema", overview, "sistema", "⌘"),
|
|
169
|
-
section("Flujos principales", f"<div class='flow-grid'>{flow_cards}</div>" + (f"<div class='trace-box'><label for='flow-select'>TRAZAR UN FLUJO</label><select id='flow-select'>{flow_options}</select><div id='flow-trace'></div></div>" if flows else ""), "flujos", "⌁"),
|
|
170
|
-
section("Entradas", f"<div class='detail-grid'><article class='detail-card'><h3>Puntos de entrada</h3><ul class='detail-list'>{item_rows(points, 'intent')}</ul></article><article class='detail-card'><h3>Entradas y salidas</h3><div class='columns'><div><label>ENTRADAS</label><ul class='detail-list'>{item_rows(system.get('inputs', []), 'description')}</ul></div><div><label>SALIDAS</label><ul class='detail-list'>{item_rows(system.get('outputs', []), 'description')}</ul></div></div></article></div>", "entradas", "⇢"),
|
|
171
|
-
section("Evidencia", f"<article class='detail-card evidence-card'><h3>Hechos que sostienen el mapa</h3><ul class='detail-list evidence-list'>{evidence_rows}</ul></article><article class='detail-card unknown-card'><h3>Lo que todavía no sabemos</h3><ul class='detail-list'>{item_rows([{'name': item, 'type': 'requiere validación'} for item in unknowns])}</ul></article>", "evidencia", "◈"),
|
|
172
|
-
section("Index Scope", f"<div class='tech-hero'><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='detail-grid'><article class='detail-card'><h3>Directorios incluidos</h3><ul class='detail-list'>{item_rows(scope_roots)}</ul></article><article class='detail-card'><h3>Exclusiones por motivo</h3><ul class='detail-list'>{item_rows(scope_exclusions)}</ul></article></div><article class='detail-card unknown-card'><h3>Advertencias concretas</h3><ul class='detail-list'>{item_rows(scope_warnings)}</ul><p>El portal es estático: editá <code>.klap/scope.json</code> y ejecutá <code>klap update</code> para cambiar el alcance.</p></article>", "scope", "◎"),
|
|
173
|
-
section("Exploración técnica", f"<div class='tech-hero'><h3>Profundizá en el análisis del código</h3><p>{e(story)}</p></div><div class='graph-links'>{graph_links}</div><div class='detail-grid'><article class='detail-card'><h3>Dependencias</h3><ul class='detail-list'>{item_rows(system.get('dependencies', []), 'package')}</ul></article><article class='detail-card'><h3>Procesos en segundo plano</h3><ul class='detail-list'>{item_rows(system.get('background_processes', []))}</ul></article></div><article class='detail-card unknown-card'><h3>Proveedores precompilados</h3><ul class='detail-list'>{item_rows(provider_summary)}</ul><p>Este portal es HTML estático: muestra únicamente datos producidos durante <code>klap init/update</code>. Las consultas semánticas dinámicas se hacen por CLI/MCP y no se simulan en el navegador.</p></article>", "tecnico", "⌬"),
|
|
174
|
-
section("Archivos y símbolos", f"<div class='tech-hero'><h3>Explorá el proyecto</h3><p>{coverage.get('code_files_with_graph_nodes', 0)} de {coverage.get('eligible_code_files', 0)} archivos de código tienen nodos en el grafo. Estado: {e(coverage.get('status', 'UNKNOWN'))}. Un nodo no garantiza cobertura completa.</p></div><input id='file-search' type='search' placeholder='Buscar archivo o símbolo' aria-label='Buscar archivo o símbolo'><div class='file-tree'>{file_tree(exploration.get('files', []))}</div>", "archivos", "▤"),
|
|
175
|
-
section("Recorridos del grafo", f"<p class='empty'>Estas rutas muestran llamadas conectadas en Graphify. No implican que sean entradas HTTP ni describen todas las ramas.</p><div class='flow-grid'>{call_cards or empty('No hay recorridos de llamadas verificables en el grafo.')}</div>", "recorridos", "⌁"),
|
|
176
|
-
section("Historial Git", f"<article class='detail-card'><h3>Cambios recientes</h3><p>Los mensajes describen cambios registrados; el código actual sigue siendo la fuente para comprender el comportamiento.</p><ul class='detail-list'>{recent or empty('No hay historial disponible.')}</ul></article>", "historial", "◷"),
|
|
177
|
-
]
|
|
178
|
-
agent = json.dumps(agent_text).replace("</", "<\\/")
|
|
179
|
-
return f"""<!doctype html><html lang='es'><head><meta charset='utf-8'><meta name='viewport' content='width=device-width,initial-scale=1'><title>KlapContext · {e(project['name'])}</title>
|
|
180
|
-
<style>
|
|
181
|
-
:root{{--bg:#07111f;--panel:#0c1a2d;--panel-2:#0e2036;--line:#1b3650;--ink:#edf7ff;--muted:#92a7be;--cyan:#16e0c4;--blue:#3d9cff;--violet:#976dff;--amber:#ffb648}}*{{box-sizing:border-box}}body{{margin:0;min-height:100vh;color:var(--ink);font:14px/1.45 Inter,ui-sans-serif,system-ui,sans-serif;background:radial-gradient(900px 520px at 52% -130px,#153d5c70,transparent 70%),var(--bg)}}button,select{{font:inherit}}.top{{height:56px;display:flex;align-items:center;gap:14px;padding:0 18px;border-bottom:1px solid var(--line);background:#081422e8;backdrop-filter:blur(16px)}}.brand{{display:flex;align-items:center;gap:9px;font-weight:800;font-size:13px}}.logo{{display:grid;place-items:center;width:28px;height:28px;border-radius:7px;color:#051525;background:linear-gradient(135deg,var(--cyan),var(--violet));box-shadow:0 0 18px #16e0c455}}.top-title{{display:flex;flex-direction:column;border-left:1px solid var(--line);padding-left:14px}}.top-title b{{font-size:12px}}.top-title small{{color:var(--muted);font-size:10px}}.top-state{{margin-left:auto;color:var(--cyan);font:10px ui-monospace,monospace}}.repo{{padding:6px 9px;border:1px solid var(--line);border-radius:6px;color:#c5d8ed;font:10px ui-monospace,monospace}}.shell{{display:grid;grid-template-columns:174px minmax(0,1fr) 260px;min-height:calc(100vh - 56px)}}.side{{padding:14px 10px;border-right:1px solid var(--line);background:#081422aa}}.side button{{display:flex;width:100%;align-items:center;gap:9px;padding:10px;border:0;border-radius:6px;background:transparent;color:var(--muted);text-align:left;cursor:pointer;font-size:12px}}.side button:hover,.side button.active{{background:linear-gradient(90deg,#0c514d88,#0c243b);color:var(--ink);box-shadow:inset 2px 0 var(--cyan)}}.side .nav-icon{{color:var(--cyan);font-size:16px;width:19px;text-align:center}}.side-footer{{margin-top:28px;padding-top:16px;border-top:1px solid var(--line)}}main{{padding:16px;min-width:0;max-width:1220px;width:100%;margin:auto}}.view{{display:none;animation:in .28s ease}}.view.active{{display:block}}@keyframes in{{from{{opacity:.35;transform:translateY(6px)}}to{{opacity:1;transform:none}}}}.view-title{{display:none}}.overview-head{{display:flex;justify-content:space-between;gap:24px;padding:5px 4px 15px}}.eyebrow,label{{color:var(--cyan);font:10px ui-monospace,monospace;letter-spacing:.13em}}h1{{margin:3px 0 4px;font-size:25px;letter-spacing:-.045em}}h2,h3,p{{margin-top:0}}.overview-head p{{max-width:760px;margin-bottom:9px;color:#c6d7e7;font-size:12px}}.overview-state{{display:flex;align-items:flex-end;flex-direction:column;gap:5px;white-space:nowrap}}.live{{color:var(--cyan);font:10px ui-monospace,monospace}}.overview-state small{{color:var(--muted);font-size:10px}}.tags{{display:flex;gap:5px;flex-wrap:wrap}}.tags span{{padding:3px 6px;border:1px solid #29506b;border-radius:4px;color:#aac0d8;font:9px ui-monospace,monospace}}.map-card,.compact-panel,.detail-card,.flow-card,.trace-box{{border:1px solid var(--line);border-radius:9px;background:linear-gradient(135deg,#0d2037e8,#091727e8);box-shadow:0 18px 35px #00000018}}header{{display:flex;justify-content:space-between;align-items:center;gap:12px;padding:13px 14px;border-bottom:1px solid #1a334c}}header>div{{display:flex;align-items:center;gap:9px}}header h2{{margin:0;font-size:13px}}header p{{margin:2px 0 0;color:var(--muted);font-size:10px}}.icon{{display:grid;place-items:center;width:28px;height:28px;border-radius:6px;color:var(--cyan);background:#0a3b4277;font-size:17px}}.outline,.text-button{{border:1px solid #24658a;border-radius:6px;background:#0a1a2d;color:var(--cyan);padding:7px 9px;font-size:10px;font-weight:700;cursor:pointer}}.text-button{{border:0;background:transparent;padding:0}}.map-shell{{padding:9px 12px 10px;background:radial-gradient(circle at 50% 42%,#123e5570,transparent 60%)}}.map-legend{{display:flex;justify-content:space-between;color:var(--muted);font:9px ui-monospace,monospace}}.map-legend i{{display:inline-block;width:6px;height:6px;border-radius:50%;background:var(--cyan);margin-right:4px}}.system-map{{display:block;width:100%;height:min(33vh,280px)}}.map-links path{{fill:none;stroke:#22e2cf9c;stroke-width:1.5;filter:drop-shadow(0 0 3px #20e3d055)}}.map-node rect{{fill:#102b49;stroke:#2f83b4;stroke-width:1}}.map-node text{{fill:#e7f5ff;font:10px ui-monospace,monospace;text-anchor:middle}}.map-node .map-kind{{fill:#84a3bd;font-size:8px}}.map-empty{{padding:58px;text-align:center;color:var(--muted)}}.quick-grid{{display:grid;grid-template-columns:repeat(4,1fr);gap:9px;margin:10px 0}}.quick-grid article{{min-height:85px;padding:11px;border:1px solid var(--line);border-radius:7px;background:#0b192b}}.quick-grid b{{display:block;color:#f2f8ff;font-size:24px;letter-spacing:-.06em}}.quick-grid span{{display:block;font-size:10px;font-weight:700}}.quick-grid small{{display:block;margin-top:4px;color:var(--muted);font-size:9px}}.quick-grid .attention b{{color:var(--amber)}}.dashboard-bottom,.detail-grid{{display:grid;grid-template-columns:1fr 1fr;gap:10px}}.compact-panel header{{padding:11px 13px}}.compact-panel h2{{font-size:12px}}.surfaces{{display:grid;grid-template-columns:repeat(4,1fr);gap:7px;padding:10px}}.surface{{padding:8px;border-radius:5px;background:#091628;border:1px solid #1c3a54}}.surface b{{display:block;color:var(--cyan);font-size:18px}}.surface span{{color:var(--muted);font-size:9px}}.start-list,.detail-list{{list-style:none;margin:0;padding:8px 13px}}.start-list li{{padding:5px 0;border-bottom:1px solid #1a324a}}.start-list li:last-child,.detail-list li:last-child{{border:0}}.start-list b,.detail-list strong{{display:block;font-size:11px}}.start-list small,.detail-list small{{display:block;color:var(--muted);font-size:9px}}.empty{{padding:8px 13px;color:var(--muted);font-size:11px}}.right{{padding:16px 11px;border-left:1px solid var(--line);background:#081422aa}}.agent-card,.insight-card{{padding:14px;margin-bottom:10px;border:1px solid var(--line);border-radius:8px;background:linear-gradient(145deg,#122a47,#0b182b)}}.agent-card{{border-color:#317b8d}}.agent-card h3,.insight-card h3{{margin-bottom:7px;font-size:12px}}.agent-card p,.insight-card p{{color:#b4c5d7;font-size:10px}}.copy{{width:100%;border:0;border-radius:5px;padding:9px;background:linear-gradient(90deg,var(--cyan),#46d1e7);color:#03202a;font-weight:800;cursor:pointer;font-size:11px}}.insight-card ul{{list-style:none;margin:0;padding:0}}.insight-card li{{padding:7px 0;border-bottom:1px solid #22405a;color:#c0d2e4;font-size:10px}}.insight-card li:last-child{{border:0}}.view-title{{margin:0 0 13px;align-items:center;gap:9px}}.view-title>span{{display:grid;place-items:center;width:31px;height:31px;border-radius:7px;background:#0a3b4277;color:var(--cyan);font-size:19px}}.view-title h2{{margin:0;font-size:18px}}.view-title p{{margin:2px 0 0;color:var(--muted);font-size:10px}}.flow-grid{{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}}.flow-card{{padding:15px}}.flow-card b{{font-size:13px}}.flow-card p{{margin:8px 0 0;color:#b9cce0;font:10px/1.8 ui-monospace,monospace}}em{{color:var(--cyan);font-style:normal}}.trace-box{{margin-top:10px;padding:14px}}select{{display:block;margin:7px 0;padding:8px;border:1px solid var(--line);border-radius:5px;background:#081727;color:var(--ink);font-size:11px}}#flow-trace{{color:var(--cyan);font:11px/1.7 ui-monospace,monospace}}.detail-card{{padding:15px}}.detail-card h3{{font-size:13px}}.detail-list{{padding:0}}.detail-list li{{display:flex;gap:8px;align-items:flex-start;padding:8px 0;border-bottom:1px solid #1a324a}}.dot{{display:block;flex:none;width:6px;height:6px;margin-top:5px;border-radius:50%;background:var(--violet);box-shadow:0 0 7px var(--violet)}}.evidence-list mark{{margin-left:auto;border-radius:3px;padding:2px 4px;background:#10493f;color:var(--cyan);font:8px ui-monospace,monospace}}.unknown-card{{margin-top:10px}}.columns{{display:grid;grid-template-columns:1fr 1fr;gap:18px}}.tech-hero{{padding:19px;border:1px solid #2f4968;border-radius:9px;background:linear-gradient(120deg,#172d50,#10192f)}}.tech-hero h3{{margin-bottom:6px;font-size:16px}}.tech-hero p{{margin:0;color:#b4c7dc;font-size:12px}}.graph-links{{display:flex;gap:10px;margin:10px 0}}.graph-links a{{flex:1;padding:13px;border:1px solid #2c5875;border-radius:7px;background:#0b1a2d;color:#d9efff;text-decoration:none;font-size:11px;font-weight:700}}.graph-links a span{{float:right;color:var(--cyan)}}@media(max-width:1050px){{.shell{{grid-template-columns:160px minmax(0,1fr)}}.right{{display:none}}}}@media(max-width:720px){{.shell{{display:block}}.side{{display:flex;overflow:auto;padding:8px;border-bottom:1px solid var(--line)}}.side button{{min-width:max-content}}.side-footer{{display:none}}.top-title{{display:none}}.repo{{display:none}}main{{padding:10px}}.overview-head{{display:block}}.overview-state{{margin-top:10px;align-items:flex-start}}.quick-grid{{grid-template-columns:repeat(2,1fr)}}.dashboard-bottom,.detail-grid,.flow-grid,.columns{{grid-template-columns:1fr}}.system-map{{width:680px;max-width:none}}.map-shell{{overflow:auto}}.surfaces{{grid-template-columns:repeat(2,1fr)}}}}
|
|
182
|
-
.file-tree{{max-height:70vh;overflow:auto;margin-top:12px;padding:14px;border:1px solid var(--line);border-radius:9px;background:var(--panel)}}.file-tree details{{padding:3px 4px 3px 14px;border-left:1px solid var(--line)}}.file-tree summary{{cursor:pointer;padding:5px;color:var(--ink)}}.file-tree small{{color:var(--muted)}}.source-file ul{{padding-left:22px}}.source-file li{{padding:3px}}#file-search{{width:100%;padding:11px;margin-top:12px;background:var(--panel);border:1px solid var(--line);border-radius:7px;color:var(--ink)}}
|
|
183
|
-
</style></head><body><nav class='top'><div class='brand'><span class='logo'>K</span>klap_context</div><div class='top-title'><b>Comprender el sistema</b><small>Explorá componentes, flujos y evidencia de tu repositorio.</small></div><span class='top-state'>● {state}</span><span class='repo'>{e(project['name'])}</span></nav><div class='shell'><aside class='side'><button class='active' data-go='sistema'><span class='nav-icon'>⌘</span>Sistema</button><button data-go='flujos'><span class='nav-icon'>⌁</span>Flujos</button><button data-go='entradas'><span class='nav-icon'>⇢</span>Entradas</button><button data-go='evidencia'><span class='nav-icon'>◈</span>Evidencias</button><button data-go='scope'><span class='nav-icon'>◎</span>Index Scope</button><button data-go='archivos'><span class='nav-icon'>▤</span>Archivos</button><button data-go='recorridos'><span class='nav-icon'>⌁</span>Recorridos</button><button data-go='historial'><span class='nav-icon'>◷</span>Git</button><button data-go='tecnico'><span class='nav-icon'>⌬</span>Graphify</button><div class='side-footer'><button data-go='evidencia'><span class='nav-icon'>?</span>Ayuda y confianza</button></div></aside><main>{''.join(views)}</main><aside class='right'><section class='agent-card'><h3>✦ Acciones para agente</h3><p>Generá un briefing listo para usar y profundizá luego en el sistema.</p><button class='copy' onclick='copyContext(this)'>▣ Copiar contexto</button></section><section class='insight-card'><h3>Use this project with your AI agent</h3><ul><li>{'✓' if onboarding_ready else '!'} AGENTS.md {'preparado' if onboarding_ready else 'requiere configuración'}</li><li>✓ Contexto generado en .klap/</li><li>{'✓' if graphify_files else '!'} Grafo Graphify {'disponible' if graphify_files else 'no disponible'}</li><li>{'✓' if mcp_ready else '!'} Configuración MCP {'generada' if mcp_ready else 'pendiente'}</li></ul><p>Comprobá vigencia con <code>klap status</code> y actualizá con <code>klap update</code>.</p></section><section class='insight-card'><h3>💡 Hallazgos principales <span>{min(len(evidence), 9)}</span></h3><ul><li>{len(points)} punto(s) de entrada detectado(s).</li><li>{len(flows)} flujo(s) principal(es) disponible(s).</li><li>{len(evidence)} hecho(s) con respaldo en el código.</li></ul></section><section class='insight-card'><h3>⚡ Siguiente paso</h3><p>Usá las vistas laterales para investigar un área, sin perder el panorama inicial.</p></section></aside></div><script>const agentContext={agent};const views=[...document.querySelectorAll('.view')];const nav=[...document.querySelectorAll('[data-go]')];function go(name){{views.forEach(view=>view.classList.toggle('active',view.dataset.view===name));nav.forEach(button=>button.classList.toggle('active',button.dataset.go===name));history.replaceState(null,'','#'+name);window.scrollTo({{top:0,behavior:'smooth'}})}}nav.forEach(button=>button.addEventListener('click',()=>go(button.dataset.go)));go(location.hash.slice(1)||'sistema');function copyContext(button){{navigator.clipboard.writeText(agentContext).then(()=>{{const old=button.textContent;button.textContent='✓ Contexto copiado';setTimeout(()=>button.textContent=old,1500)}})}}const paths={traces};const search=document.querySelector('#file-search');if(search)search.addEventListener('input',()=>{{const term=search.value.toLocaleLowerCase();document.querySelectorAll('.source-file').forEach(file=>{{file.hidden=!file.textContent.toLocaleLowerCase().includes(term)}});document.querySelectorAll('.folder').forEach(folder=>{{folder.open=!!term}})}});const selector=document.querySelector('#flow-select');if(selector){{const trace=document.querySelector('#flow-trace');const show=()=>trace.textContent=paths[selector.value].join(' → ');selector.addEventListener('change',show);show();}}</script></body></html>"""
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
def write(context: dict, agent_text: str, graphify_files: list[str], output: Path) -> None:
|
|
187
|
-
output.write_text(render(context, agent_text, graphify_files), encoding="utf-8")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{klapcontext-0.7.1 → klapcontext-0.8.2}/src/klapcontext/providers/javascript_intelligence.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|