Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions graphify/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2329,6 +2329,7 @@ def main() -> None:
print(" --half-life-days N signal weight halves every N days (default 30)")
print(" --min-corroboration N distinct useful results to prefer a node (default 2)")
print(" check-update <path> check needs_update flag and notify if semantic re-extraction is pending (cron-safe)")
print(" inspect <path> list corpus files by category (no LLM or graph build)")
print(" tree emit a D3 v7 collapsible-tree HTML for graph.json")
print(" --graph PATH path to graph.json (default graphify-out/graph.json)")
print(" --output HTML output path (default graphify-out/GRAPH_TREE.html)")
Expand Down Expand Up @@ -3758,6 +3759,18 @@ def main() -> None:

check_update(Path(sys.argv[2]).resolve())
sys.exit(0)
elif cmd == "inspect":
if len(sys.argv) < 3:
print("Usage: graphify inspect <path>", file=sys.stderr)
sys.exit(1)
target = Path(sys.argv[2]).resolve()
if not target.exists():
print(f"error: path not found: {target}", file=sys.stderr)
sys.exit(1)
from graphify.detect import detect as _detect, format_detect_summary

detection = _detect(target)
print(format_detect_summary(detection, root=target))
elif cmd == "tree":
# Emit a D3 v7 collapsible-tree HTML view of graph.json:
# expand-all / collapse-all / reset-view buttons, multi-line
Expand Down
51 changes: 51 additions & 0 deletions graphify/detect.py
Original file line number Diff line number Diff line change
Expand Up @@ -1167,6 +1167,57 @@ def detect(root: Path, *, follow_symlinks: bool | None = None, google_workspace:
}


def format_detect_summary(detection: dict, root: Path | None = None) -> str:
"""Human-readable corpus summary for ``graphify inspect``."""
files = detection.get("files", {})
if not any(files.get(key, []) for key in ("code", "document", "paper", "image", "video")):
return "No supported files found."

scan_root = Path(detection.get("scan_root") or root or ".").resolve()

lines: list[str] = []
for key, label in (
("code", "code"),
("document", "docs"),
("paper", "papers"),
("image", "images"),
("video", "video"),
):
count = len(files.get(key, []))
if count:
lines.append(f" {label}:{' ' * max(1, 9 - len(label))}{count} files")

semantic_groups = (
("document", "Documents"),
("paper", "Papers"),
("image", "Images"),
)
has_semantic = False
for key, heading in semantic_groups:
paths = files.get(key, [])
if not paths:
continue
has_semantic = True
lines.append("")
lines.append(f"{heading}:")
for path_str in sorted(paths):
p = Path(path_str)
try:
rel = str(p.relative_to(scan_root))
except ValueError:
rel = path_str
lines.append(f" {rel}")

if has_semantic:
lines.append("")
lines.append(
"Note: documents, papers, and images require an LLM API key during "
"`graphify extract` (code-only corpora do not)."
)

return "\n".join(lines)


def _md5_file(path: Path) -> str:
"""MD5 of file contents streamed in 64KB chunks — for change detection only."""
import hashlib as _hl
Expand Down
115 changes: 115 additions & 0 deletions tests/test_inspect_cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
"""Tests for `graphify inspect` CLI."""
from __future__ import annotations

import pytest

import graphify.__main__ as mainmod


def _mixed_corpus(tmp_path):
(tmp_path / "main.go").write_text("package main\nfunc main() {}\n")
(tmp_path / "README.md").write_text("# Notes\nEntry point.\n")
(tmp_path / "diagram.png").write_bytes(b"\x89PNG\r\n\x1a\n")
return tmp_path


def _code_only_corpus(tmp_path):
(tmp_path / "auth.py").write_text("def login():\n return True\n")
return tmp_path


def test_inspect_lists_counts_and_semantic_paths(monkeypatch, tmp_path, capsys):
corpus = _mixed_corpus(tmp_path)
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys, "argv", ["graphify", "inspect", str(corpus)],
)

mainmod.main()

out = capsys.readouterr().out
assert "code:" in out
assert "docs:" in out
assert "images:" in out
assert "Documents:" in out
assert "README.md" in out
assert "Images:" in out
assert "diagram.png" in out


def test_inspect_code_only_exits_zero_without_llm_note(monkeypatch, tmp_path, capsys):
corpus = _code_only_corpus(tmp_path)
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys, "argv", ["graphify", "inspect", str(corpus)],
)

mainmod.main()

out = capsys.readouterr().out
assert "code:" in out
assert "LLM" not in out
assert "semantic" not in out.lower()


def test_inspect_semantic_corpus_mentions_llm_note(monkeypatch, tmp_path, capsys):
corpus = _mixed_corpus(tmp_path)
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys, "argv", ["graphify", "inspect", str(corpus)],
)

mainmod.main()

out = capsys.readouterr().out
assert "LLM API key" in out
assert "graphify extract" in out


def test_inspect_missing_path_exits_nonzero(monkeypatch, tmp_path, capsys):
missing = tmp_path / "nope"
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys, "argv", ["graphify", "inspect", str(missing)],
)

with pytest.raises(SystemExit) as exc_info:
mainmod.main()
assert exc_info.value.code == 1
assert "path not found" in capsys.readouterr().err


def test_inspect_empty_dir_prints_message_and_exits_zero(monkeypatch, tmp_path, capsys):
empty = tmp_path / "empty"
empty.mkdir()
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys, "argv", ["graphify", "inspect", str(empty)],
)

mainmod.main()

out = capsys.readouterr().out
assert out.strip() == "No supported files found."


def test_inspect_without_path_prints_usage_and_exits_nonzero(monkeypatch, capsys):
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(mainmod.sys, "argv", ["graphify", "inspect"])

with pytest.raises(SystemExit) as exc_info:
mainmod.main()
assert exc_info.value.code == 1
assert "Usage: graphify inspect <path>" in capsys.readouterr().err


def test_inspect_does_not_create_graphify_out(monkeypatch, tmp_path):
corpus = _mixed_corpus(tmp_path)
monkeypatch.setattr(mainmod, "_check_skill_version", lambda _: None)
monkeypatch.setattr(
mainmod.sys, "argv", ["graphify", "inspect", str(corpus)],
)

mainmod.main()

assert not (corpus / "graphify-out").exists()