From ccad2977ea104727f950b5cf1bd3c377f2ecc418 Mon Sep 17 00:00:00 2001 From: xiaolai Date: Thu, 9 Apr 2026 10:05:42 +0800 Subject: [PATCH] fix: extension set drift in analyze.py and watch.py; remove dead code in cluster.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit analyze.py and watch.py maintained their own hardcoded extension sets that diverged from the canonical set in detect.py. 15 of 30 supported extensions (swift, lua, zig, ps1, ex, jsx, jl, m, etc.) were missing from analyze.py, causing _file_category() to misclassify those files as "doc" instead of "code" — inflating cross-file-type surprise scores. watch.py had a similar gap, silently ignoring file changes in newer languages during --watch mode. Both modules now import from detect.py. Also removed cluster.build_graph(), a 16-line function that duplicated build.build_from_json() and was never imported anywhere. Co-Authored-By: Claude Opus 4.6 (1M context) --- graphify/analyze.py | 15 +++++---------- graphify/cluster.py | 17 ----------------- graphify/watch.py | 15 +++------------ tests/test_analyze.py | 9 +++++++++ 4 files changed, 17 insertions(+), 39 deletions(-) diff --git a/graphify/analyze.py b/graphify/analyze.py index 8ca7ddac2..b6c0e4c28 100644 --- a/graphify/analyze.py +++ b/graphify/analyze.py @@ -1,6 +1,7 @@ """Graph analysis: god nodes (most connected), surprising connections (cross-community), suggested questions.""" from __future__ import annotations import networkx as nx +from .detect import CODE_EXTENSIONS, DOC_EXTENSIONS, PAPER_EXTENSIONS, IMAGE_EXTENSIONS def _node_community_map(communities: dict[int, list[str]]) -> dict[str, int]: @@ -109,19 +110,13 @@ def _is_concept_node(G: nx.Graph, node_id: str) -> bool: return False -_CODE_EXTENSIONS = {"py", "ts", "tsx", "js", "go", "rs", "java", "rb", "cpp", "c", "h", "cs", "kt", "scala", "php"} -_DOC_EXTENSIONS = {"md", "txt", "rst"} -_PAPER_EXTENSIONS = {"pdf"} -_IMAGE_EXTENSIONS = {"png", "jpg", "jpeg", "webp", "gif", "svg"} - - def _file_category(path: str) -> str: - ext = path.rsplit(".", 1)[-1].lower() if "." in path else "" - if ext in _CODE_EXTENSIONS: + ext = ("." + path.rsplit(".", 1)[-1].lower()) if "." in path else "" + if ext in CODE_EXTENSIONS: return "code" - if ext in _PAPER_EXTENSIONS: + if ext in PAPER_EXTENSIONS: return "paper" - if ext in _IMAGE_EXTENSIONS: + if ext in IMAGE_EXTENSIONS: return "image" return "doc" diff --git a/graphify/cluster.py b/graphify/cluster.py index 09d070405..363d555c8 100644 --- a/graphify/cluster.py +++ b/graphify/cluster.py @@ -52,23 +52,6 @@ def _partition(G: nx.Graph) -> dict[str, int]: return {node: cid for cid, nodes in enumerate(communities) for node in nodes} -def build_graph(nodes: list[dict], edges: list[dict]) -> nx.Graph: - """Build a NetworkX graph from graphify node/edge dicts. - - Preserves original edge direction as _src/_tgt attributes so that - display functions can show relationships in the correct direction, - even though the graph is undirected for structural analysis. - """ - G = nx.Graph() - for n in nodes: - G.add_node(n["id"], **{k: v for k, v in n.items() if k != "id"}) - for e in edges: - attrs = {k: v for k, v in e.items() if k not in ("source", "target")} - attrs["_src"] = e["source"] - attrs["_tgt"] = e["target"] - G.add_edge(e["source"], e["target"], **attrs) - return G - _MAX_COMMUNITY_FRACTION = 0.25 # communities larger than 25% of graph get split _MIN_SPLIT_SIZE = 10 # only split if community has at least this many nodes diff --git a/graphify/watch.py b/graphify/watch.py index c0cabdde8..a1556c94b 100644 --- a/graphify/watch.py +++ b/graphify/watch.py @@ -3,19 +3,10 @@ import json import time from pathlib import Path +from .detect import CODE_EXTENSIONS, DOC_EXTENSIONS, PAPER_EXTENSIONS, IMAGE_EXTENSIONS - -_WATCHED_EXTENSIONS = { - ".py", ".ts", ".js", ".go", ".rs", ".java", ".cpp", ".c", ".rb", ".swift", ".kt", - ".cs", ".scala", ".php", ".cc", ".cxx", ".hpp", ".h", ".kts", - ".md", ".txt", ".rst", ".pdf", - ".png", ".jpg", ".jpeg", ".webp", ".gif", ".svg", -} - -_CODE_EXTENSIONS = { - ".py", ".ts", ".js", ".go", ".rs", ".java", ".cpp", ".c", ".rb", ".swift", ".kt", - ".cs", ".scala", ".php", ".cc", ".cxx", ".hpp", ".h", ".kts", -} +_WATCHED_EXTENSIONS = CODE_EXTENSIONS | DOC_EXTENSIONS | PAPER_EXTENSIONS | IMAGE_EXTENSIONS +_CODE_EXTENSIONS = CODE_EXTENSIONS def _rebuild_code(watch_path: Path, *, follow_symlinks: bool = False) -> bool: diff --git a/tests/test_analyze.py b/tests/test_analyze.py index 0ae2ede54..c6dc82866 100644 --- a/tests/test_analyze.py +++ b/tests/test_analyze.py @@ -137,6 +137,15 @@ def test_file_category(): assert _file_category("flash.pdf") == "paper" assert _file_category("diagram.png") == "image" assert _file_category("notes.md") == "doc" + # Languages that were previously misclassified as "doc" + assert _file_category("main.swift") == "code" + assert _file_category("lib.lua") == "code" + assert _file_category("build.zig") == "code" + assert _file_category("script.ps1") == "code" + assert _file_category("app.ex") == "code" + assert _file_category("view.jsx") == "code" + assert _file_category("solver.jl") == "code" + assert _file_category("bridge.m") == "code" def test_is_concept_node_empty_source():