graphsnip 0.2.0__tar.gz → 0.2.1__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.
- {graphsnip-0.2.0/src/graphsnip.egg-info → graphsnip-0.2.1}/PKG-INFO +1 -1
- {graphsnip-0.2.0 → graphsnip-0.2.1}/pyproject.toml +1 -1
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/__init__.py +1 -1
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/cli.py +21 -1
- graphsnip-0.2.1/src/graphsnip/probe.py +83 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1/src/graphsnip.egg-info}/PKG-INFO +1 -1
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip.egg-info/SOURCES.txt +1 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/tests/test_integrations.py +30 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/LICENSE +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/NOTICE +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/README.md +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/setup.cfg +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/__main__.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/ab.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/baseline.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/bench.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/dashboard.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/engine.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/hooks.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/installer.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/ledger.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/mcp_server.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/project.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/py.typed +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/retrieval.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/router.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/semantic.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/shellfilter.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/slicer.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/tokens.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip/usage.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip.egg-info/dependency_links.txt +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip.egg-info/entry_points.txt +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip.egg-info/requires.txt +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/src/graphsnip.egg-info/top_level.txt +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/tests/test_core.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/tests/test_refresh.py +0 -0
- {graphsnip-0.2.0 → graphsnip-0.2.1}/tests/test_semantic.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphsnip
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Token-saving layer on top of Graphify: symbol-level code slicing, context packs, a savings ledger with an honest baseline, hooks and MCP for Claude Code, Copilot and Codex.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/naubadnithin7/codegraf
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "graphsnip"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.1"
|
|
8
8
|
description = "Token-saving layer on top of Graphify: symbol-level code slicing, context packs, a savings ledger with an honest baseline, hooks and MCP for Claude Code, Copilot and Codex."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -444,6 +444,25 @@ def cmd_doctor(a) -> int:
|
|
|
444
444
|
f"Re-run `graphsnip install` (without --portable) to write the full path.")
|
|
445
445
|
elif not os.path.exists(c):
|
|
446
446
|
print(f"! {label}: server command {c} does not exist; re-run `graphsnip install`.")
|
|
447
|
+
if not getattr(a, "no_probe", False):
|
|
448
|
+
from graphsnip.probe import probe_mcp
|
|
449
|
+
|
|
450
|
+
for label, f, key in (("claude", mcp_claude, "mcpServers"), ("copilot", p.root / ".vscode" / "mcp.json", "servers")):
|
|
451
|
+
try:
|
|
452
|
+
ent = json.loads(f.read_text(encoding="utf-8")).get(key, {}).get("graphsnip")
|
|
453
|
+
except Exception:
|
|
454
|
+
ent = None
|
|
455
|
+
if not ent:
|
|
456
|
+
continue
|
|
457
|
+
r = probe_mcp(str(ent.get("command", "")), [str(x) for x in ent.get("args", [])], str(p.root))
|
|
458
|
+
if r["ok"]:
|
|
459
|
+
line(True, f"{label}: the MCP server starts and lists {len(r['tools'])} tools ({r['seconds']}s)")
|
|
460
|
+
else:
|
|
461
|
+
line(False, f"{label}: the MCP server does NOT work: {r['error']}", "fix the command in the config, then reload the app")
|
|
462
|
+
if r["stderr"]:
|
|
463
|
+
print(" server said: " + r["stderr"].replace("\n", "\n "))
|
|
464
|
+
print(" If every server above starts but the assistant still has no graphsnip tools, the app has not started or trusted it:")
|
|
465
|
+
print(" Claude Code: run /mcp and approve it. VS Code: Command Palette > 'MCP: List Servers' > start/trust graphsnip, then 'Configure Tools'.")
|
|
447
466
|
hook_on = st.exists() and HOOK_MARK in st.read_text(encoding="utf-8", errors="ignore")
|
|
448
467
|
print(f"{'✓' if hook_on else '·'} claude hook: {'installed' if hook_on else 'not installed'} (mode: {p.config()['mode']})")
|
|
449
468
|
print("\nAll good." if ok else "\nSome checks failed; see arrows above.")
|
|
@@ -550,7 +569,8 @@ def main(argv=None) -> int:
|
|
|
550
569
|
sp = add("uninstall", cmd_uninstall, "remove everything install added")
|
|
551
570
|
sp.add_argument("--clients", default="all")
|
|
552
571
|
sp.add_argument("--codex-scope", choices=["project", "user"], default="project")
|
|
553
|
-
add("doctor", cmd_doctor, "check the setup")
|
|
572
|
+
sp = add("doctor", cmd_doctor, "check the setup (also starts the configured MCP servers to prove they work)")
|
|
573
|
+
sp.add_argument("--no-probe", action="store_true", help="skip starting the MCP servers")
|
|
554
574
|
|
|
555
575
|
args = ap.parse_args(argv)
|
|
556
576
|
if not getattr(args, "fn", None):
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Start an MCP server exactly as an assistant would and check that it answers, so `doctor` can say why tools are missing."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import queue
|
|
6
|
+
import subprocess
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def probe_mcp(command: str, args: list[str], cwd: str, timeout: float = 25.0) -> dict:
|
|
12
|
+
t0 = time.time()
|
|
13
|
+
try:
|
|
14
|
+
proc = subprocess.Popen([command, *args], cwd=cwd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
15
|
+
text=True, bufsize=1)
|
|
16
|
+
except FileNotFoundError:
|
|
17
|
+
return {"ok": False, "error": f"command not found: {command}", "stderr": "", "tools": [], "seconds": 0.0}
|
|
18
|
+
except OSError as e:
|
|
19
|
+
return {"ok": False, "error": f"could not start {command}: {e}", "stderr": "", "tools": [], "seconds": 0.0}
|
|
20
|
+
|
|
21
|
+
out: queue.Queue = queue.Queue()
|
|
22
|
+
err_lines: list[str] = []
|
|
23
|
+
|
|
24
|
+
def pump(stream, sink):
|
|
25
|
+
for ln in stream:
|
|
26
|
+
sink(ln)
|
|
27
|
+
|
|
28
|
+
threading.Thread(target=pump, args=(proc.stdout, out.put), daemon=True).start()
|
|
29
|
+
threading.Thread(target=pump, args=(proc.stderr, err_lines.append), daemon=True).start()
|
|
30
|
+
|
|
31
|
+
def send(obj):
|
|
32
|
+
try:
|
|
33
|
+
proc.stdin.write(json.dumps(obj) + "\n")
|
|
34
|
+
proc.stdin.flush()
|
|
35
|
+
except OSError:
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
def wait_for(mid):
|
|
39
|
+
end = t0 + timeout
|
|
40
|
+
while time.time() < end:
|
|
41
|
+
try:
|
|
42
|
+
ln = out.get(timeout=0.2)
|
|
43
|
+
except queue.Empty:
|
|
44
|
+
if proc.poll() is not None and out.empty():
|
|
45
|
+
return None
|
|
46
|
+
continue
|
|
47
|
+
try:
|
|
48
|
+
msg = json.loads(ln)
|
|
49
|
+
except Exception:
|
|
50
|
+
continue
|
|
51
|
+
if isinstance(msg, dict) and msg.get("id") == mid:
|
|
52
|
+
return msg
|
|
53
|
+
return None
|
|
54
|
+
|
|
55
|
+
res = {"ok": False, "error": "", "stderr": "", "tools": [], "seconds": 0.0}
|
|
56
|
+
try:
|
|
57
|
+
send({"jsonrpc": "2.0", "id": 1, "method": "initialize",
|
|
58
|
+
"params": {"protocolVersion": "2025-03-26", "capabilities": {}, "clientInfo": {"name": "graphsnip-doctor", "version": "0"}}})
|
|
59
|
+
init = wait_for(1)
|
|
60
|
+
if init is None:
|
|
61
|
+
res["error"] = ("the server exited before answering" if proc.poll() is not None else f"no answer within {timeout:.0f}s")
|
|
62
|
+
else:
|
|
63
|
+
send({"jsonrpc": "2.0", "method": "notifications/initialized"})
|
|
64
|
+
send({"jsonrpc": "2.0", "id": 2, "method": "tools/list"})
|
|
65
|
+
tl = wait_for(2)
|
|
66
|
+
names = [t.get("name") for t in ((tl or {}).get("result") or {}).get("tools", [])]
|
|
67
|
+
if names:
|
|
68
|
+
res.update(ok=True, tools=names)
|
|
69
|
+
else:
|
|
70
|
+
res["error"] = "the server started but listed no tools"
|
|
71
|
+
finally:
|
|
72
|
+
try:
|
|
73
|
+
proc.stdin.close()
|
|
74
|
+
except OSError:
|
|
75
|
+
pass
|
|
76
|
+
try:
|
|
77
|
+
proc.terminate()
|
|
78
|
+
proc.wait(timeout=3)
|
|
79
|
+
except Exception:
|
|
80
|
+
proc.kill()
|
|
81
|
+
res["seconds"] = round(time.time() - t0, 1)
|
|
82
|
+
res["stderr"] = "".join(err_lines)[-800:].strip()
|
|
83
|
+
return res
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graphsnip
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.1
|
|
4
4
|
Summary: Token-saving layer on top of Graphify: symbol-level code slicing, context packs, a savings ledger with an honest baseline, hooks and MCP for Claude Code, Copilot and Codex.
|
|
5
5
|
License-Expression: Apache-2.0
|
|
6
6
|
Project-URL: Repository, https://github.com/naubadnithin7/codegraf
|
|
@@ -331,3 +331,33 @@ def test_copilot_config_uses_absolute_path_even_when_portable(tmp_path, monkeypa
|
|
|
331
331
|
cl = json.loads((tmp_path / ".mcp.json").read_text())["mcpServers"]["graphsnip"]
|
|
332
332
|
assert vs["command"] == "/opt/bin/graphsnip"
|
|
333
333
|
assert cl["command"] == "graphsnip"
|
|
334
|
+
|
|
335
|
+
|
|
336
|
+
def test_probe_reports_a_working_and_a_broken_server(tmp_path):
|
|
337
|
+
import sys
|
|
338
|
+
|
|
339
|
+
from graphsnip.probe import probe_mcp
|
|
340
|
+
|
|
341
|
+
(tmp_path / "a.py").write_text("def f():\n return 1\n")
|
|
342
|
+
ok = probe_mcp(sys.executable, ["-m", "graphsnip", "serve", "--root", "."], str(tmp_path))
|
|
343
|
+
assert ok["ok"] and "gs_pack" in ok["tools"] and len(ok["tools"]) == 7
|
|
344
|
+
bad = probe_mcp("/definitely/not/here/graphsnip", ["serve"], str(tmp_path))
|
|
345
|
+
assert not bad["ok"] and "not found" in bad["error"]
|
|
346
|
+
crash = probe_mcp(sys.executable, ["-c", "import sys; sys.stderr.write('boom: ImportError x'); sys.exit(1)"], str(tmp_path), timeout=5)
|
|
347
|
+
assert not crash["ok"] and "boom" in crash["stderr"]
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def test_doctor_probes_configured_servers(tmp_path, capsys):
|
|
351
|
+
import json
|
|
352
|
+
import sys
|
|
353
|
+
|
|
354
|
+
from graphsnip import cli
|
|
355
|
+
|
|
356
|
+
(tmp_path / "a.py").write_text("def f():\n return 1\n")
|
|
357
|
+
(tmp_path / ".mcp.json").write_text(json.dumps({"mcpServers": {"graphsnip": {"command": sys.executable, "args": ["-m", "graphsnip", "serve", "--root", "."]}}}))
|
|
358
|
+
(tmp_path / ".vscode").mkdir()
|
|
359
|
+
(tmp_path / ".vscode" / "mcp.json").write_text(json.dumps({"servers": {"graphsnip": {"type": "stdio", "command": "/nope/graphsnip", "args": ["serve"]}}}))
|
|
360
|
+
cli.main(["doctor", "--root", str(tmp_path)])
|
|
361
|
+
out = capsys.readouterr().out
|
|
362
|
+
assert "claude: the MCP server starts and lists 7 tools" in out
|
|
363
|
+
assert "copilot: the MCP server does NOT work" in out and "command not found" in out
|
|
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
|