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
15 changes: 15 additions & 0 deletions graphify/report.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ def generate(
f"- {detection_result['total_files']} files · ~{detection_result['total_words']:,} words",
"- Verdict: corpus is large enough that graph structure adds value.",
]
# #3511: files detect() saw but could not classify (no supported
# extension/shebang) were counted nowhere -- a corpus that is mostly
# an unsupported language reported the same "well covered" verdict as
# one that was actually extracted. Surface the count and its biggest
# extensions so a near-total miss (e.g. a Lean/Zig/whatever repo with
# no matching extractor) is visible here instead of silent.
unclassified = detection_result.get("unclassified") or []
if unclassified:
from collections import Counter as _Counter
ext_counts = _Counter(Path(p).suffix or "(none)" for p in unclassified)
top = ", ".join(f"{ext} {n}" for ext, n in ext_counts.most_common(3))
lines.append(
f"- Unclassified: {len(unclassified)} file(s) not represented in "
f"the graph (top: {top})"
)

from .analyze import _is_file_node as _ifn

Expand Down
15 changes: 15 additions & 0 deletions graphify/watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -1428,6 +1428,20 @@ def _rebuild_code(
)
code_files = [Path(f) for f in detected['files']['code']]

# #3511: `graphify extract` has surfaced files it saw but could not
# classify since #1692; this update/watch rebuild path never did,
# so a corpus in a language with no extractor (no supported
# extension or shebang) rebuilt "successfully" with those files
# silently absent from the graph. Same wording as the extract path.
_unclassified = detected.get("unclassified", []) if isinstance(detected, dict) else []
if _unclassified:
_names = ", ".join(sorted({Path(p).name for p in _unclassified})[:6])
_more = f" (+{len(_unclassified) - 6} more)" if len(_unclassified) > 6 else ""
print(
f"[graphify watch] {len(_unclassified)} file(s) not classified "
f"(no supported extension or shebang), skipped: {_names}{_more}"
)

# #2495: hand reconcile the same ignore decisions the detect() call
# above made, so a newly-ignored file that still exists on disk is
# purged from the graph instead of preserved forever by the fail-closed
Expand Down Expand Up @@ -1876,6 +1890,7 @@ def _failed(f: str) -> bool:
"files": {"code": [str(f) for f in code_files], "document": [], "paper": [], "image": []},
"total_files": len(code_files),
"total_words": detected.get("total_words", 0),
"unclassified": detected.get("unclassified", []),
}

# Inherit the existing graph's directed flag (#2342) so `graphify
Expand Down
23 changes: 23 additions & 0 deletions tests/test_report.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,29 @@ def test_report_contains_corpus_check():
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project")
assert "## Corpus Check" in report


def test_report_surfaces_unclassified_files():
"""#3511: detect() already tracks files it saw but could not classify
(no supported extension), but nothing surfaced them -- a corpus that is
mostly an unsupported language got the same "well covered" verdict as
one that was actually extracted."""
G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs()
detection = {
**detection,
"unclassified": ["Main.lean", "Util.lean", "a.toml", "b.toml", "c.toml", "readme"],
}
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project")
assert "Unclassified: 6 file(s)" in report
assert ".lean 2" in report
assert ".toml 3" in report


def test_report_omits_unclassified_line_when_none():
"""Backward compatible: no unclassified files, no new line."""
G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs()
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project")
assert "Unclassified:" not in report

def test_report_contains_god_nodes():
G, communities, cohesion, labels, gods, surprises, detection, tokens = make_inputs()
report = generate(G, communities, cohesion, labels, gods, surprises, detection, tokens, "./project")
Expand Down
19 changes: 19 additions & 0 deletions tests/test_watch.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,25 @@ def test_doc_only_deletion_full_rebuild_evicts_md_nodes(tmp_path):
assert "run()" in labels


def test_rebuild_code_reports_unclassified_files(tmp_path, capsys):
"""#3511: `graphify extract` has surfaced files it saw but could not
classify (no supported extension/shebang) since #1692; the update/watch
rebuild path never did, so a corpus mostly in an unsupported language
(e.g. Lean, per the report) rebuilt "successfully" with those files
silently absent and nothing said about it."""
corpus = tmp_path / "corpus"
corpus.mkdir()
(corpus / "app.py").write_text("def run(): pass\n", encoding="utf-8")
(corpus / "Main.lean").write_text("def main := 0\n", encoding="utf-8")
(corpus / "Util.lean").write_text("def util := 1\n", encoding="utf-8")

assert _rebuild_code(corpus, acquire_lock=False) is True
out = capsys.readouterr().out
assert "2 file(s) not classified" in out
assert "Main.lean" in out
assert "Util.lean" in out


# --- watch() import error without watchdog ---

def test_check_update_no_flag_returns_true(tmp_path):
Expand Down
Loading