klapcontext 0.3.3__tar.gz → 0.5.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.3.3/src/klapcontext.egg-info → klapcontext-0.5.0}/PKG-INFO +31 -4
- {klapcontext-0.3.3 → klapcontext-0.5.0}/README.md +30 -3
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/__init__.py +1 -1
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/compiler.py +5 -2
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent_context.py +6 -0
- klapcontext-0.5.0/src/klapcontext/agent_setup.py +264 -0
- klapcontext-0.5.0/src/klapcontext/cli.py +190 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/context_builder.py +8 -2
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/detector.py +26 -16
- klapcontext-0.5.0/src/klapcontext/exploration.py +118 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/frameworks/laravel.py +4 -1
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/portal.py +36 -1
- {klapcontext-0.3.3 → klapcontext-0.5.0/src/klapcontext.egg-info}/PKG-INFO +31 -4
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext.egg-info/SOURCES.txt +4 -0
- klapcontext-0.5.0/tests/test_agent_setup.py +122 -0
- klapcontext-0.5.0/tests/test_exploration.py +68 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/tests/test_klapcontext.py +1 -0
- klapcontext-0.3.3/src/klapcontext/cli.py +0 -82
- {klapcontext-0.3.3 → klapcontext-0.5.0}/LICENSE +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/pyproject.toml +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/setup.cfg +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/__init__.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/capabilities.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/impact.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/intents.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/models.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/planner.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/verification.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/agent/workflows.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/evidence.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/frameworks/__init__.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/frameworks/base.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/frameworks/fastapi.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/frameworks/generic_php.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/frameworks/node.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/git.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/graphify.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/providers/__init__.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/providers/base.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/providers/code_intelligence.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/providers/graphify.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/providers/javascript_intelligence.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/providers/python_intelligence.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/semantic.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext/system.py +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext.egg-info/dependency_links.txt +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext.egg-info/entry_points.txt +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext.egg-info/requires.txt +0 -0
- {klapcontext-0.3.3 → klapcontext-0.5.0}/src/klapcontext.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: klapcontext
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Make your repository understandable to humans and AI.
|
|
5
5
|
Author: Willy Cordon
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -78,9 +78,29 @@ klap init
|
|
|
78
78
|
klap open
|
|
79
79
|
```
|
|
80
80
|
|
|
81
|
-
`klap init` genera el análisis Graphify
|
|
82
|
-
`.klap
|
|
83
|
-
|
|
81
|
+
`klap init` genera el análisis Graphify, escribe artefactos locales bajo
|
|
82
|
+
`.klap/` y prepara un `AGENTS.md` privado para que los agentes descubran el
|
|
83
|
+
contexto progresivamente. Ambos se agregan a `.git/info/exclude`; KlapContext
|
|
84
|
+
nunca modifica tu `.gitignore`.
|
|
85
|
+
|
|
86
|
+
Para compartir las instrucciones de onboarding con el equipo:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
klap init --shared
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
El modo compartido genera un `AGENTS.md` versionable, sin rutas personales ni
|
|
93
|
+
secretos. Si el archivo ya existe, KlapContext preserva su contenido y sólo
|
|
94
|
+
administra el bloque entre `KLAPCONTEXT:START` y `KLAPCONTEXT:END`. Un archivo
|
|
95
|
+
versionado nunca se modifica silenciosamente en modo privado.
|
|
96
|
+
|
|
97
|
+
El portal incluye un árbol navegable de archivos y símbolos extraídos del grafo,
|
|
98
|
+
recorridos de llamadas y cambios recientes de Git. El contexto del agente y
|
|
99
|
+
`klap context` usan las mismas rutas y símbolos. El indicador de cobertura cuenta
|
|
100
|
+
archivos de código con al menos un nodo en Graphify: **no mide exhaustividad de
|
|
101
|
+
funciones ni garantiza que se hayan resuelto rutas dinámicas**. Los recorridos
|
|
102
|
+
son caminos del grafo, no afirmaciones de que una ruta HTTP los ejecuta; sus
|
|
103
|
+
ramas pueden requerir inspección del código original.
|
|
84
104
|
|
|
85
105
|
## Comandos
|
|
86
106
|
|
|
@@ -92,6 +112,7 @@ tu `.gitignore`.
|
|
|
92
112
|
| `klap open [ruta]` | Abre el portal humano estático. |
|
|
93
113
|
| `klap agent [ruta]` | Muestra la configuración MCP local de Graphify. |
|
|
94
114
|
| `klap context "tarea" [ruta]` | Compila un contexto focalizado reutilizando `.klap/context.json` si existe. |
|
|
115
|
+
| `klap doctor --agent [ruta]` | Comprueba AGENTS.md, freshness, Graphify y configuración MCP. |
|
|
95
116
|
| `klap --version` | Muestra la versión instalada. |
|
|
96
117
|
|
|
97
118
|
## Salidas
|
|
@@ -102,9 +123,15 @@ tu `.gitignore`.
|
|
|
102
123
|
├── agent-context.md # briefing para agentes de IA
|
|
103
124
|
├── index.html # portal humano, sin servidor
|
|
104
125
|
├── state.json # metadatos de freshness
|
|
126
|
+
├── mcp/ # configuración local reutilizable para clientes MCP
|
|
105
127
|
└── graphify/ # grafo, informe y visualizaciones de Graphify
|
|
106
128
|
```
|
|
107
129
|
|
|
130
|
+
`klap agent` genera ejemplos locales para Codex, Claude Code, Cursor y clientes
|
|
131
|
+
MCP genéricos. No instala agentes ni modifica configuraciones globales. Usá
|
|
132
|
+
`klap doctor --agent --probe-mcp` para comprobar que Graphify MCP puede iniciar;
|
|
133
|
+
eso no equivale a verificar una conexión real de un cliente.
|
|
134
|
+
|
|
108
135
|
Las afirmaciones incluyen evidencia y estados `CONFIRMED`, `INFERRED` o
|
|
109
136
|
`UNKNOWN`. Si KlapContext no puede determinar algo con confianza, lo expone en
|
|
110
137
|
lugar de inventarlo.
|
|
@@ -44,9 +44,29 @@ klap init
|
|
|
44
44
|
klap open
|
|
45
45
|
```
|
|
46
46
|
|
|
47
|
-
`klap init` genera el análisis Graphify
|
|
48
|
-
`.klap
|
|
49
|
-
|
|
47
|
+
`klap init` genera el análisis Graphify, escribe artefactos locales bajo
|
|
48
|
+
`.klap/` y prepara un `AGENTS.md` privado para que los agentes descubran el
|
|
49
|
+
contexto progresivamente. Ambos se agregan a `.git/info/exclude`; KlapContext
|
|
50
|
+
nunca modifica tu `.gitignore`.
|
|
51
|
+
|
|
52
|
+
Para compartir las instrucciones de onboarding con el equipo:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
klap init --shared
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
El modo compartido genera un `AGENTS.md` versionable, sin rutas personales ni
|
|
59
|
+
secretos. Si el archivo ya existe, KlapContext preserva su contenido y sólo
|
|
60
|
+
administra el bloque entre `KLAPCONTEXT:START` y `KLAPCONTEXT:END`. Un archivo
|
|
61
|
+
versionado nunca se modifica silenciosamente en modo privado.
|
|
62
|
+
|
|
63
|
+
El portal incluye un árbol navegable de archivos y símbolos extraídos del grafo,
|
|
64
|
+
recorridos de llamadas y cambios recientes de Git. El contexto del agente y
|
|
65
|
+
`klap context` usan las mismas rutas y símbolos. El indicador de cobertura cuenta
|
|
66
|
+
archivos de código con al menos un nodo en Graphify: **no mide exhaustividad de
|
|
67
|
+
funciones ni garantiza que se hayan resuelto rutas dinámicas**. Los recorridos
|
|
68
|
+
son caminos del grafo, no afirmaciones de que una ruta HTTP los ejecuta; sus
|
|
69
|
+
ramas pueden requerir inspección del código original.
|
|
50
70
|
|
|
51
71
|
## Comandos
|
|
52
72
|
|
|
@@ -58,6 +78,7 @@ tu `.gitignore`.
|
|
|
58
78
|
| `klap open [ruta]` | Abre el portal humano estático. |
|
|
59
79
|
| `klap agent [ruta]` | Muestra la configuración MCP local de Graphify. |
|
|
60
80
|
| `klap context "tarea" [ruta]` | Compila un contexto focalizado reutilizando `.klap/context.json` si existe. |
|
|
81
|
+
| `klap doctor --agent [ruta]` | Comprueba AGENTS.md, freshness, Graphify y configuración MCP. |
|
|
61
82
|
| `klap --version` | Muestra la versión instalada. |
|
|
62
83
|
|
|
63
84
|
## Salidas
|
|
@@ -68,9 +89,15 @@ tu `.gitignore`.
|
|
|
68
89
|
├── agent-context.md # briefing para agentes de IA
|
|
69
90
|
├── index.html # portal humano, sin servidor
|
|
70
91
|
├── state.json # metadatos de freshness
|
|
92
|
+
├── mcp/ # configuración local reutilizable para clientes MCP
|
|
71
93
|
└── graphify/ # grafo, informe y visualizaciones de Graphify
|
|
72
94
|
```
|
|
73
95
|
|
|
96
|
+
`klap agent` genera ejemplos locales para Codex, Claude Code, Cursor y clientes
|
|
97
|
+
MCP genéricos. No instala agentes ni modifica configuraciones globales. Usá
|
|
98
|
+
`klap doctor --agent --probe-mcp` para comprobar que Graphify MCP puede iniciar;
|
|
99
|
+
eso no equivale a verificar una conexión real de un cliente.
|
|
100
|
+
|
|
74
101
|
Las afirmaciones incluyen evidencia y estados `CONFIRMED`, `INFERRED` o
|
|
75
102
|
`UNKNOWN`. Si KlapContext no puede determinar algo con confianza, lo expone en
|
|
76
103
|
lugar de inventarlo.
|
|
@@ -34,8 +34,11 @@ def compile_context(root: Path, query: str, *, detail: str = "standard", max_tok
|
|
|
34
34
|
context = build(root, {"nodes": []})
|
|
35
35
|
model = context["system_model"]; limit = min(DETAIL_LIMITS[detail], max(1, max_tokens // 350)); terms = _terms(query, plan.area)
|
|
36
36
|
entries = _rank(model["entry_points"], terms, limit); flows = _rank(model["main_flows"], terms, limit); files = _rank(model["important_files"], terms, limit)
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
exploration = model.get("exploration", {})
|
|
38
|
+
graph_files = _rank(exploration.get("files", []), terms, limit)
|
|
39
|
+
graph_paths = _rank(exploration.get("call_paths", []), terms, limit)
|
|
40
|
+
read_first = ([{"path": item.get("file"), "reason": "Punto de entrada relacionado", "relevance": item["relevance"]} for item in entries if item.get("file")] + [{"path": item["path"], "reason": "Archivo o símbolo relacionado en el grafo", "relevance": item["relevance"]} for item in graph_files] + files)[:limit]
|
|
41
|
+
result = {"intent": plan.intent, "confidence": plan.confidence, "query": query, "area": plan.area, "detail": detail, "max_tokens": max_tokens, "entry_points": entries, "flows": flows, "graph_call_paths": graph_paths, "symbols": _rank(model["semantic_model"]["components"], terms, limit), "files": files + graph_files, "coverage": exploration.get("coverage", {}), "recent_changes": _rank(model.get("recent_changes", []), terms, min(limit, 3)), "dependencies": _rank(model["dependencies"], terms, limit), "tests": _rank(context["tests"], terms, limit), "read_first": read_first, "evidence": [evidence for group in entries + flows for evidence in group.get("evidence", [])][:limit], "uncertainties": model["unknowns"], "strategy": plan.strategy}
|
|
39
42
|
result["verification"] = reconcile(result["evidence"])
|
|
40
43
|
result = sanitize(result)
|
|
41
44
|
result["estimated_tokens"] = min(max_tokens, len(json.dumps(result, ensure_ascii=False)) // 4)
|
|
@@ -5,9 +5,15 @@ 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
6
|
def render(context: dict) -> str:
|
|
7
7
|
s=context.get("system_model",{}); purpose=s.get("purpose",{}).get("text") or "No fue posible determinar el propósito con confianza."
|
|
8
|
+
exploration=s.get("exploration",{}); coverage=exploration.get("coverage",{})
|
|
8
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","\n".join(f"- {x['package']} {x['version']} ({x['scope']})" for x in s.get("dependencies",[])[:12]) or "Sin dependencias específicas detectadas."),("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.")]
|
|
9
10
|
lines=["# KlapContext — Contexto del Proyecto"]
|
|
10
11
|
for title,body in sections: lines.extend(["",f"## {title}",body])
|
|
12
|
+
lines.extend(["", "## 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."])
|
|
13
|
+
for path in exploration.get("call_paths", [])[:8]:
|
|
14
|
+
lines.append("- " + " → ".join(f"{step['name']} ({step['file']})" for step in path["steps"]) + " [recorrido del grafo; entrada no confirmada]")
|
|
15
|
+
lines.extend(["", "## Cambios recientes en Git"])
|
|
16
|
+
lines.extend(f"- {change['date']} {change['commit'][:10]}: {change['subject']}" for change in s.get("recent_changes", [])[:6])
|
|
11
17
|
lines.extend(["","## Instrucciones para el Agente","1. Lee este contexto antes de explorar ampliamente el repositorio.","2. Usa Graphify MCP para dependencias y rutas de llamadas.","3. Consulta paths o vecinos relevantes antes de abrir muchos archivos.","4. Lee el código real antes de modificarlo.","5. Trata el contexto inferido como guía, no como verdad absoluta."])
|
|
12
18
|
text="\n".join(lines)+"\n"; return text+f"\nTokens estimados: {len(text.split()) * 4 // 3}\n"
|
|
13
19
|
def write(context: dict, output: Path) -> str:
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"""Safe, idempotent onboarding for AGENTS.md-compatible coding agents."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import os
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
import tempfile
|
|
9
|
+
from dataclasses import asdict, dataclass
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from .git import commit, dirty_files, is_repository, run_git
|
|
13
|
+
from .graphify import command_prefix
|
|
14
|
+
|
|
15
|
+
START = "<!-- KLAPCONTEXT:START -->"
|
|
16
|
+
END = "<!-- KLAPCONTEXT:END -->"
|
|
17
|
+
AGENT_FILE = "AGENTS.md"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class AgentSetupResult:
|
|
22
|
+
status: str
|
|
23
|
+
path: str = AGENT_FILE
|
|
24
|
+
mode: str = "private"
|
|
25
|
+
created: bool = False
|
|
26
|
+
updated: bool = False
|
|
27
|
+
tracked: bool = False
|
|
28
|
+
mcp_configured: bool = False
|
|
29
|
+
message: str = ""
|
|
30
|
+
|
|
31
|
+
def as_dict(self) -> dict:
|
|
32
|
+
return asdict(self)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def is_tracked(root: Path, relative: str = AGENT_FILE) -> bool:
|
|
36
|
+
return is_repository(root) and run_git(root, "ls-files", "--error-unmatch", relative) is not None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def marker_state(text: str) -> str:
|
|
40
|
+
starts, ends = text.count(START), text.count(END)
|
|
41
|
+
if starts == ends == 0:
|
|
42
|
+
return "absent"
|
|
43
|
+
if starts == ends == 1 and text.index(START) < text.index(END):
|
|
44
|
+
return "valid"
|
|
45
|
+
return "malformed"
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def context_freshness(root: Path) -> dict:
|
|
49
|
+
state_file = root / ".klap" / "state.json"
|
|
50
|
+
if not state_file.exists():
|
|
51
|
+
return {"status": "MISSING", "reason": "No existe .klap/state.json"}
|
|
52
|
+
try:
|
|
53
|
+
state = json.loads(state_file.read_text(encoding="utf-8"))
|
|
54
|
+
except (OSError, json.JSONDecodeError):
|
|
55
|
+
return {"status": "INVALID", "reason": "state.json no es válido"}
|
|
56
|
+
current = commit(root)
|
|
57
|
+
ignored = (".klap/", "graphify-out/", AGENT_FILE)
|
|
58
|
+
relevant = [path for path in dirty_files(root) if not path.replace("\\", "/").startswith(ignored)]
|
|
59
|
+
if state.get("git_commit") != current:
|
|
60
|
+
return {"status": "STALE", "reason": "El commit analizado no coincide con HEAD", "changed_files": relevant}
|
|
61
|
+
if relevant:
|
|
62
|
+
return {"status": "STALE", "reason": "Hay cambios relevantes sin confirmar", "changed_files": relevant}
|
|
63
|
+
return {"status": "CURRENT", "reason": "El contexto coincide con el código confirmado", "changed_files": []}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def generate_mcp_configs(root: Path) -> dict:
|
|
67
|
+
"""Write local examples only; never mutate global agent configuration."""
|
|
68
|
+
graph = (root / ".klap" / "graphify" / "graph.json").resolve()
|
|
69
|
+
directory = root / ".klap" / "mcp"
|
|
70
|
+
directory.mkdir(parents=True, exist_ok=True)
|
|
71
|
+
prefix = command_prefix()
|
|
72
|
+
available = bool(prefix and graph.exists())
|
|
73
|
+
command = sys.executable
|
|
74
|
+
args = ["-m", "graphify.serve", str(graph)]
|
|
75
|
+
generic = {"mcpServers": {"graphify": {"command": command, "args": args}}}
|
|
76
|
+
(directory / "generic.json").write_text(json.dumps(generic, indent=2) + "\n", encoding="utf-8")
|
|
77
|
+
codex = "[mcp_servers.graphify]\n" + f'command = {json.dumps(command)}\nargs = {json.dumps(args)}\n'
|
|
78
|
+
(directory / "codex.toml").write_text(codex, encoding="utf-8")
|
|
79
|
+
instructions = f"""# Graphify MCP setup
|
|
80
|
+
|
|
81
|
+
Generated local configuration examples. They contain no credentials.
|
|
82
|
+
|
|
83
|
+
- Generic / Claude Code project config: `.klap/mcp/generic.json`
|
|
84
|
+
- Cursor project config: copy the same JSON to `.cursor/mcp.json` only when you explicitly want project configuration.
|
|
85
|
+
- Codex project config snippet: `.klap/mcp/codex.toml`
|
|
86
|
+
|
|
87
|
+
The configured stdio command is:
|
|
88
|
+
|
|
89
|
+
`{command} {' '.join(args)}`
|
|
90
|
+
|
|
91
|
+
Run `klap doctor --agent --probe-mcp` to verify that the server can start. A configuration file alone does not prove a client connection.
|
|
92
|
+
"""
|
|
93
|
+
(directory / "README.md").write_text(instructions, encoding="utf-8")
|
|
94
|
+
return {"available": available, "directory": ".klap/mcp", "generic": ".klap/mcp/generic.json", "codex": ".klap/mcp/codex.toml"}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def _overview(context: dict) -> str:
|
|
98
|
+
project = context.get("project", {})
|
|
99
|
+
system = context.get("system_model", {})
|
|
100
|
+
stack = context.get("stack", {})
|
|
101
|
+
purpose = system.get("purpose", {}).get("text")
|
|
102
|
+
languages = ", ".join(stack.get("languages", []))
|
|
103
|
+
frameworks = ", ".join(stack.get("frameworks", []))
|
|
104
|
+
facts = [f"Repository `{project.get('name', 'project')}`"]
|
|
105
|
+
if languages:
|
|
106
|
+
facts.append(f"uses {languages}")
|
|
107
|
+
if frameworks:
|
|
108
|
+
facts.append(f"with {frameworks}")
|
|
109
|
+
summary = " ".join(facts) + "."
|
|
110
|
+
if purpose:
|
|
111
|
+
summary += " " + purpose.strip()
|
|
112
|
+
flows = system.get("main_flows", [])
|
|
113
|
+
if flows:
|
|
114
|
+
summary += f" KlapContext detected {len(flows)} main execution flow(s)."
|
|
115
|
+
return summary
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def render_agent_block(context: dict, *, shared: bool) -> str:
|
|
119
|
+
shared_note = "\nIf `.klap/` is missing, run `klap init` before relying on generated context.\n" if shared else ""
|
|
120
|
+
return f"""{START}
|
|
121
|
+
|
|
122
|
+
## KlapContext — Engineering Context
|
|
123
|
+
|
|
124
|
+
This repository has been analyzed by KlapContext.
|
|
125
|
+
|
|
126
|
+
### Project overview
|
|
127
|
+
|
|
128
|
+
{_overview(context)}
|
|
129
|
+
{shared_note}
|
|
130
|
+
### Before starting a task
|
|
131
|
+
|
|
132
|
+
1. Run `klap status`; if relevant context is stale, run `klap update`.
|
|
133
|
+
2. Read `.klap/agent-context.md` progressively; do not load the entire graph.
|
|
134
|
+
3. Use Graphify MCP for dependencies, call paths, callers and impact analysis.
|
|
135
|
+
4. Identify the relevant execution flow before modifying code.
|
|
136
|
+
5. Read the actual source code and related tests before making changes.
|
|
137
|
+
6. Treat inferred relationships as hypotheses until verified.
|
|
138
|
+
|
|
139
|
+
### Context resources
|
|
140
|
+
|
|
141
|
+
- `.klap/agent-context.md`
|
|
142
|
+
- `.klap/context.json`
|
|
143
|
+
- `.klap/index.html`
|
|
144
|
+
- `.klap/graphify/graph.json`
|
|
145
|
+
- `.klap/mcp/README.md`
|
|
146
|
+
|
|
147
|
+
### Working rules
|
|
148
|
+
|
|
149
|
+
- Respect existing architecture, conventions and contracts.
|
|
150
|
+
- Prefer focused Graphify queries over broad repository searches.
|
|
151
|
+
- Never modify code based only on generated context; verify the source first.
|
|
152
|
+
- Preserve existing contracts unless the task explicitly requires changing them.
|
|
153
|
+
|
|
154
|
+
{END}"""
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class AgentInstructionsManager:
|
|
158
|
+
def __init__(self, root: Path):
|
|
159
|
+
self.root = root.resolve()
|
|
160
|
+
self.path = self.root / AGENT_FILE
|
|
161
|
+
|
|
162
|
+
def apply(self, context: dict, *, shared: bool = False, allow_tracked: bool = False) -> AgentSetupResult:
|
|
163
|
+
mode = "shared" if shared else "private"
|
|
164
|
+
tracked = is_tracked(self.root)
|
|
165
|
+
if tracked and not (shared or allow_tracked):
|
|
166
|
+
return AgentSetupResult("authorization_required", mode=mode, tracked=True, message="AGENTS.md está versionado; se requiere autorización explícita.")
|
|
167
|
+
existing = self.path.read_text(encoding="utf-8") if self.path.exists() else ""
|
|
168
|
+
state = marker_state(existing)
|
|
169
|
+
if state == "malformed":
|
|
170
|
+
return AgentSetupResult("conflict", mode=mode, tracked=tracked, message="Marcadores KLAPCONTEXT incompletos o duplicados; no se modificó AGENTS.md.")
|
|
171
|
+
block = render_agent_block(context, shared=shared)
|
|
172
|
+
if state == "valid":
|
|
173
|
+
start = existing.index(START)
|
|
174
|
+
end = existing.index(END, start) + len(END)
|
|
175
|
+
updated = existing[:start] + block + existing[end:]
|
|
176
|
+
elif existing.strip():
|
|
177
|
+
updated = existing.rstrip() + "\n\n" + block + "\n"
|
|
178
|
+
else:
|
|
179
|
+
updated = block + "\n"
|
|
180
|
+
if updated == existing:
|
|
181
|
+
if shared:
|
|
182
|
+
self._include_shared_agents()
|
|
183
|
+
return AgentSetupResult("unchanged", mode=mode, tracked=tracked, mcp_configured=True, message="AGENTS.md ya está actualizado.")
|
|
184
|
+
self._atomic_write(updated, existing if self.path.exists() else None)
|
|
185
|
+
if shared:
|
|
186
|
+
self._include_shared_agents()
|
|
187
|
+
elif not tracked and is_repository(self.root):
|
|
188
|
+
self._exclude_private_agents()
|
|
189
|
+
return AgentSetupResult("configured", mode=mode, created=not bool(existing), updated=bool(existing), tracked=tracked, mcp_configured=True, message="AGENTS.md configurado de forma segura.")
|
|
190
|
+
|
|
191
|
+
def _atomic_write(self, content: str, previous: str | None) -> None:
|
|
192
|
+
if previous is not None:
|
|
193
|
+
backup = self.root / ".klap" / "backups" / AGENT_FILE
|
|
194
|
+
backup.parent.mkdir(parents=True, exist_ok=True)
|
|
195
|
+
backup.write_text(previous, encoding="utf-8")
|
|
196
|
+
fd, name = tempfile.mkstemp(prefix=".AGENTS.", suffix=".tmp", dir=self.root)
|
|
197
|
+
try:
|
|
198
|
+
with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle:
|
|
199
|
+
handle.write(content)
|
|
200
|
+
handle.flush()
|
|
201
|
+
os.fsync(handle.fileno())
|
|
202
|
+
os.replace(name, self.path)
|
|
203
|
+
finally:
|
|
204
|
+
if os.path.exists(name):
|
|
205
|
+
os.unlink(name)
|
|
206
|
+
|
|
207
|
+
def _exclude_private_agents(self) -> None:
|
|
208
|
+
git_dir = run_git(self.root, "rev-parse", "--git-dir")
|
|
209
|
+
if not git_dir:
|
|
210
|
+
return
|
|
211
|
+
exclude = (self.root / git_dir / "info" / "exclude").resolve()
|
|
212
|
+
existing = exclude.read_text(encoding="utf-8") if exclude.exists() else ""
|
|
213
|
+
if AGENT_FILE not in {line.strip() for line in existing.splitlines()}:
|
|
214
|
+
exclude.write_text(existing.rstrip() + ("\n" if existing.strip() else "") + AGENT_FILE + "\n", encoding="utf-8")
|
|
215
|
+
|
|
216
|
+
def _include_shared_agents(self) -> None:
|
|
217
|
+
git_dir = run_git(self.root, "rev-parse", "--git-dir")
|
|
218
|
+
if not git_dir:
|
|
219
|
+
return
|
|
220
|
+
exclude = (self.root / git_dir / "info" / "exclude").resolve()
|
|
221
|
+
if not exclude.exists():
|
|
222
|
+
return
|
|
223
|
+
lines = exclude.read_text(encoding="utf-8").splitlines()
|
|
224
|
+
filtered = [line for line in lines if line.strip() != AGENT_FILE]
|
|
225
|
+
if filtered != lines:
|
|
226
|
+
exclude.write_text("\n".join(filtered).rstrip() + ("\n" if filtered else ""), encoding="utf-8")
|
|
227
|
+
|
|
228
|
+
def validate(self) -> dict:
|
|
229
|
+
if not self.path.exists():
|
|
230
|
+
return {"status": "missing", "valid": False, "message": "AGENTS.md no existe"}
|
|
231
|
+
text = self.path.read_text(encoding="utf-8")
|
|
232
|
+
state = marker_state(text)
|
|
233
|
+
paths = [".klap/agent-context.md", ".klap/context.json", ".klap/index.html", ".klap/graphify/graph.json"]
|
|
234
|
+
missing = [path for path in paths if not (self.root / path).exists()]
|
|
235
|
+
return {"status": state, "valid": state == "valid" and not missing, "missing_paths": missing, "message": "Configuración válida" if state == "valid" and not missing else "Configuración incompleta"}
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
def probe_graphify_mcp(root: Path, timeout: float = 2.0) -> dict:
|
|
239
|
+
graph = root / ".klap" / "graphify" / "graph.json"
|
|
240
|
+
prefix = command_prefix()
|
|
241
|
+
if not prefix or not graph.exists():
|
|
242
|
+
return {"startable": False, "connection_verified": False, "reason": "Graphify o graph.json no disponible"}
|
|
243
|
+
command = [sys.executable, "-m", "graphify.serve", str(graph)]
|
|
244
|
+
process = subprocess.Popen(command, cwd=root, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)
|
|
245
|
+
try:
|
|
246
|
+
process.wait(timeout=timeout)
|
|
247
|
+
return {"startable": process.returncode == 0, "connection_verified": False, "reason": "El proceso terminó sin una sesión MCP"}
|
|
248
|
+
except subprocess.TimeoutExpired:
|
|
249
|
+
process.terminate()
|
|
250
|
+
try:
|
|
251
|
+
process.wait(timeout=1)
|
|
252
|
+
except subprocess.TimeoutExpired:
|
|
253
|
+
process.kill()
|
|
254
|
+
return {"startable": True, "connection_verified": False, "reason": "El servidor inició; no se verificó conexión de un cliente"}
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def doctor_agent(root: Path, *, probe_mcp: bool = False) -> dict:
|
|
258
|
+
manager = AgentInstructionsManager(root)
|
|
259
|
+
agents = manager.validate()
|
|
260
|
+
freshness = context_freshness(root)
|
|
261
|
+
graph = root / ".klap" / "graphify" / "graph.json"
|
|
262
|
+
mcp_files = [root / ".klap" / "mcp" / "generic.json", root / ".klap" / "mcp" / "codex.toml"]
|
|
263
|
+
mcp_probe = probe_graphify_mcp(root) if probe_mcp else {"startable": bool(command_prefix() and graph.exists()), "connection_verified": False, "reason": "Use --probe-mcp para comprobar el arranque"}
|
|
264
|
+
return {"agents": agents, "context": {"available": (root / ".klap" / "agent-context.md").exists() and (root / ".klap" / "context.json").exists()}, "freshness": freshness, "graph": {"available": graph.exists()}, "mcp": {"configured": all(path.exists() for path in mcp_files), **mcp_probe}}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import argparse, json, os, sys, webbrowser
|
|
3
|
+
from datetime import datetime, timezone
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from . import __version__
|
|
7
|
+
from .agent_context import write as write_agent
|
|
8
|
+
from .agent_setup import AgentInstructionsManager, context_freshness, doctor_agent, generate_mcp_configs, is_tracked
|
|
9
|
+
from .context_builder import build
|
|
10
|
+
from .git import commit, dirty_files, exclude_klap, is_repository
|
|
11
|
+
from .graphify import GraphifyError
|
|
12
|
+
from .portal import write as write_portal
|
|
13
|
+
from .providers import default_provider
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def root_path(value: str | None) -> Path:
|
|
17
|
+
return Path(value or os.getcwd()).resolve()
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def generate_all(root: Path, update: bool = False, *, shared: bool = False, allow_tracked_agents: bool = False) -> dict:
|
|
21
|
+
if not is_repository(root):
|
|
22
|
+
raise RuntimeError(f"Not a Git repository: {root}")
|
|
23
|
+
exclude_klap(root)
|
|
24
|
+
klap = root / ".klap"
|
|
25
|
+
klap.mkdir(exist_ok=True)
|
|
26
|
+
provider = default_provider()
|
|
27
|
+
graph_path = provider.generate(root, update)
|
|
28
|
+
files = provider.copy_outputs(root, klap / "graphify")
|
|
29
|
+
context = build(root, provider.load_graph(graph_path))
|
|
30
|
+
(klap / "context.json").write_text(json.dumps(context, indent=2) + "\n", encoding="utf-8")
|
|
31
|
+
agent = write_agent(context, klap / "agent-context.md")
|
|
32
|
+
mcp = generate_mcp_configs(root)
|
|
33
|
+
setup = AgentInstructionsManager(root).apply(context, shared=shared, allow_tracked=allow_tracked_agents)
|
|
34
|
+
context["agent_onboarding"] = {**setup.as_dict(), "mcp": mcp}
|
|
35
|
+
(klap / "context.json").write_text(json.dumps(context, indent=2) + "\n", encoding="utf-8")
|
|
36
|
+
write_portal(context, agent, files, klap / "index.html")
|
|
37
|
+
state = {
|
|
38
|
+
"schema_version": "0.3",
|
|
39
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
40
|
+
"git_commit": commit(root),
|
|
41
|
+
"provider": {"name": provider.name, "version": provider.version()},
|
|
42
|
+
"klap_version": __version__,
|
|
43
|
+
"agent_onboarding": setup.as_dict(),
|
|
44
|
+
}
|
|
45
|
+
(klap / "state.json").write_text(json.dumps(state, indent=2) + "\n", encoding="utf-8")
|
|
46
|
+
return context
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def _permission(args, root: Path, action: str) -> tuple[bool, bool]:
|
|
50
|
+
shared = getattr(args, "shared", False)
|
|
51
|
+
allow = getattr(args, "allow_tracked_agents", False)
|
|
52
|
+
if not shared and is_tracked(root) and not allow and sys.stdin.isatty():
|
|
53
|
+
allow = input(f"AGENTS.md está versionado. ¿{action} la sección KlapContext? [y/N] ").strip().lower() in {"y", "yes", "s", "si", "sí"}
|
|
54
|
+
return shared, allow
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def cmd_init(args):
|
|
58
|
+
root = root_path(args.path)
|
|
59
|
+
print("KlapContext\n")
|
|
60
|
+
shared, allow = _permission(args, root, "Agregar o actualizar")
|
|
61
|
+
try:
|
|
62
|
+
context = generate_all(root, shared=shared, allow_tracked_agents=allow) if shared or allow else generate_all(root)
|
|
63
|
+
except (RuntimeError, GraphifyError) as error:
|
|
64
|
+
print(f"✗ {error}", file=sys.stderr)
|
|
65
|
+
return 1
|
|
66
|
+
print("✓ Git repository detected\n✓ Graphify available\n✓ Graph generated\n✓ Engineering context generated\n✓ Agent context generated\n✓ Human portal generated")
|
|
67
|
+
setup = context.get("agent_onboarding", {})
|
|
68
|
+
print(("✓ " if setup.get("status") in {"configured", "unchanged"} else "! ") + setup.get("message", "Agent onboarding not configured"))
|
|
69
|
+
coverage = context["system_model"]["exploration"]["coverage"]
|
|
70
|
+
print(f"Coverage: {coverage['code_files_with_graph_nodes']}/{coverage['eligible_code_files']} code files represented in graph ({coverage['status']}); graph nodes do not guarantee complete symbol analysis")
|
|
71
|
+
stack_items = [item for values in context["stack"].values() if isinstance(values, list) for item in values]
|
|
72
|
+
print(f"\nProject: {context['project']['name']}\nStack: {' / '.join(stack_items) or 'unknown'}\nContext: CURRENT\n\nHuman portal:\n.klap/index.html\n\nAgent context:\n.klap/agent-context.md")
|
|
73
|
+
return 0
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def cmd_update(args):
|
|
77
|
+
root = root_path(args.path)
|
|
78
|
+
shared, allow = _permission(args, root, "Actualizar")
|
|
79
|
+
try:
|
|
80
|
+
context = generate_all(root, update=True, shared=shared, allow_tracked_agents=allow) if shared or allow else generate_all(root, update=True)
|
|
81
|
+
except (RuntimeError, GraphifyError) as error:
|
|
82
|
+
print(f"✗ {error}", file=sys.stderr)
|
|
83
|
+
return 1
|
|
84
|
+
coverage = context["system_model"]["exploration"]["coverage"]
|
|
85
|
+
print(f"✓ KlapContext updated; graph coverage: {coverage['code_files_with_graph_nodes']}/{coverage['eligible_code_files']} code files ({coverage['status']})")
|
|
86
|
+
return 0
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def cmd_status(args):
|
|
90
|
+
root = root_path(args.path)
|
|
91
|
+
state_file = root / ".klap" / "state.json"
|
|
92
|
+
if not state_file.exists():
|
|
93
|
+
print("! No KlapContext found. Run: klap init")
|
|
94
|
+
return 1
|
|
95
|
+
try:
|
|
96
|
+
state = json.loads(state_file.read_text(encoding="utf-8"))
|
|
97
|
+
except (OSError, json.JSONDecodeError):
|
|
98
|
+
print("Status: INVALID\nstate.json no es válido")
|
|
99
|
+
return 2
|
|
100
|
+
current = commit(root)
|
|
101
|
+
ignored = (".klap/", "graphify-out/", "AGENTS.md")
|
|
102
|
+
relevant = [path for path in dirty_files(root) if not path.replace("\\", "/").startswith(ignored)]
|
|
103
|
+
fresh = state.get("git_commit") == current and not relevant
|
|
104
|
+
reason = "El contexto coincide con el código confirmado" if fresh else "El commit analizado o archivos relevantes cambiaron"
|
|
105
|
+
print(f"Status: {'CURRENT' if fresh else 'STALE'}\n{reason}")
|
|
106
|
+
return 0 if fresh else 2
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def cmd_doctor(args):
|
|
110
|
+
result = doctor_agent(root_path(args.path), probe_mcp=getattr(args, "probe_mcp", False))
|
|
111
|
+
print("KlapContext — Agent Doctor\n")
|
|
112
|
+
checks = [
|
|
113
|
+
(result["agents"]["valid"], "AGENTS.md configured"),
|
|
114
|
+
(result["context"]["available"], "Engineering context available"),
|
|
115
|
+
(result["freshness"]["status"] == "CURRENT", "Context is current"),
|
|
116
|
+
(result["graph"]["available"], "Graphify graph available"),
|
|
117
|
+
(result["mcp"]["configured"], "MCP configuration generated"),
|
|
118
|
+
]
|
|
119
|
+
for ok, label in checks:
|
|
120
|
+
print(("✓" if ok else "!") + " " + label)
|
|
121
|
+
print(("✓" if result["mcp"]["connection_verified"] else "!") + " MCP connection " + ("verified" if result["mcp"]["connection_verified"] else "not yet verified"))
|
|
122
|
+
if result["agents"].get("missing_paths"):
|
|
123
|
+
print("Missing paths: " + ", ".join(result["agents"]["missing_paths"]))
|
|
124
|
+
return 0 if all(ok for ok, _ in checks) else 2
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def cmd_open(args):
|
|
128
|
+
page = root_path(args.path) / ".klap" / "index.html"
|
|
129
|
+
if not page.exists():
|
|
130
|
+
print("⚠ No human portal found. Run: klap init", file=sys.stderr)
|
|
131
|
+
return 1
|
|
132
|
+
webbrowser.open(page.as_uri())
|
|
133
|
+
print(f"Opened {page}")
|
|
134
|
+
return 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def cmd_agent(args):
|
|
138
|
+
root = root_path(args.path)
|
|
139
|
+
graph = root / ".klap" / "graphify" / "graph.json"
|
|
140
|
+
if not graph.exists():
|
|
141
|
+
print("⚠ No Graphify graph found. Run: klap init", file=sys.stderr)
|
|
142
|
+
return 1
|
|
143
|
+
config = generate_mcp_configs(root)
|
|
144
|
+
print(f"Graphify MCP setup generated in {config['directory']}\nGeneric: {config['generic']}\nCodex: {config['codex']}\nRun: klap doctor --agent --probe-mcp")
|
|
145
|
+
return 0
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def cmd_context(args):
|
|
149
|
+
from .agent.compiler import compile_context
|
|
150
|
+
result = compile_context(root_path(args.path), args.query, detail=args.detail, max_tokens=args.max_tokens)
|
|
151
|
+
if args.json:
|
|
152
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
153
|
+
else:
|
|
154
|
+
print(f"Intent: {result['intent']}\nÁrea: {result['area']}\nRead first:\n" + "\n".join(f"- {item.get('path', item.get('name'))}: {item['reason']}" for item in result["read_first"]))
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def main(argv=None):
|
|
159
|
+
if hasattr(sys.stdout, "reconfigure"):
|
|
160
|
+
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
161
|
+
if hasattr(sys.stderr, "reconfigure"):
|
|
162
|
+
sys.stderr.reconfigure(encoding="utf-8", errors="replace")
|
|
163
|
+
parser = argparse.ArgumentParser(prog="klap", description="Make your repository understandable to humans and AI.")
|
|
164
|
+
parser.add_argument("--version", action="version", version=__version__)
|
|
165
|
+
subs = parser.add_subparsers(dest="command", required=True)
|
|
166
|
+
for name, func, help_text in (("init", cmd_init, "Generate engineering context"), ("update", cmd_update, "Refresh engineering context"), ("status", cmd_status, "Show context freshness"), ("open", cmd_open, "Open the human portal"), ("agent", cmd_agent, "Show Graphify MCP setup")):
|
|
167
|
+
command = subs.add_parser(name, help=help_text)
|
|
168
|
+
command.add_argument("path", nargs="?", help="Repository root (defaults to current directory)")
|
|
169
|
+
if name in {"init", "update"}:
|
|
170
|
+
command.add_argument("--shared", action="store_true", help="Create versionable AGENTS.md instructions")
|
|
171
|
+
command.add_argument("--allow-tracked-agents", action="store_true", help="Authorize updating a tracked AGENTS.md")
|
|
172
|
+
command.set_defaults(func=func)
|
|
173
|
+
command = subs.add_parser("doctor", help="Diagnose project and agent readiness")
|
|
174
|
+
command.add_argument("path", nargs="?", default=".")
|
|
175
|
+
command.add_argument("--agent", action="store_true", help="Check AGENTS.md and generated context")
|
|
176
|
+
command.add_argument("--probe-mcp", action="store_true", help="Start Graphify MCP briefly and stop it")
|
|
177
|
+
command.set_defaults(func=cmd_doctor)
|
|
178
|
+
command = subs.add_parser("context", help="Compile compact context for an agent task")
|
|
179
|
+
command.add_argument("query")
|
|
180
|
+
command.add_argument("path", nargs="?", default=".")
|
|
181
|
+
command.add_argument("--detail", choices=("minimal", "standard", "deep"), default="standard")
|
|
182
|
+
command.add_argument("--max-tokens", type=int, default=5000)
|
|
183
|
+
command.add_argument("--json", action="store_true")
|
|
184
|
+
command.set_defaults(func=cmd_context)
|
|
185
|
+
args = parser.parse_args(argv)
|
|
186
|
+
return args.func(args)
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
if __name__ == "__main__":
|
|
190
|
+
raise SystemExit(main())
|