mirror of
https://github.com/safishamsi/graphify.git
synced 2026-10-16 08:59:26 +00:00
fix(analyze): exclude contains-only AST declarations from knowledge gap detection (#4205)
(cherry picked from commit bf55b069f8)
This commit is contained in:
1 parent
b583288e95
commit
4cf3dfba06
3 files changed
+85
-2
No files matched your search
@@ -199,6 +199,20 @@ def _is_concept_node(G: nx.Graph, node_id: str) -> bool:
|
||||
return False
|
||||
|
||||
|
||||
def _is_contains_only_ast_node(G: nx.Graph, node_id: str) -> bool:
|
||||
"""AST declarations (type aliases, enum members, local consts, JSON keys)
|
||||
whose only edge is the structural `contains` from their declaring file are
|
||||
working as designed, not documentation gaps (#4205)."""
|
||||
from graphify.build import _is_ast_tier
|
||||
attrs = G.nodes[node_id]
|
||||
if not _is_ast_tier(attrs):
|
||||
return False
|
||||
if G.degree(node_id) != 1:
|
||||
return False
|
||||
edges = list(G.edges(node_id, data=True))
|
||||
return len(edges) == 1 and edges[0][2].get("relation") == "contains"
|
||||
|
||||
|
||||
from graphify.detect import CODE_EXTENSIONS, DOC_EXTENSIONS, PAPER_EXTENSIONS, IMAGE_EXTENSIONS
|
||||
|
||||
|
||||
@@ -552,6 +566,7 @@ def suggest_questions(
|
||||
if G.degree(n) <= 1
|
||||
and not _is_file_node(G, n)
|
||||
and not _is_concept_node(G, n)
|
||||
and not _is_contains_only_ast_node(G, n)
|
||||
and G.nodes[n].get("file_type") != "rationale"
|
||||
]
|
||||
if isolated:
|
||||
|
||||
+5
-2
@@ -153,7 +153,7 @@ def generate(
|
||||
f"the graph (top: {top})"
|
||||
)
|
||||
|
||||
from .analyze import _is_file_node, _is_concept_node
|
||||
from .analyze import _is_file_node, _is_concept_node, _is_contains_only_ast_node
|
||||
|
||||
def _real_node(n) -> bool:
|
||||
# The one definition of "real" (structural, not file/concept/
|
||||
@@ -324,7 +324,10 @@ def generate(
|
||||
]
|
||||
|
||||
# --- Gaps section ---
|
||||
isolated = [n for n in G.nodes() if G.degree(n) <= 1 and _real_node(n)]
|
||||
isolated = [
|
||||
n for n in G.nodes()
|
||||
if G.degree(n) <= 1 and _real_node(n) and not _is_contains_only_ast_node(G, n)
|
||||
]
|
||||
# Same threshold the Summary and Communities headers used (#3148): this
|
||||
# was a hardcoded 3, so with --min-community-size anything else the count
|
||||
# here disagreed with the label text beside it, which already printed
|
||||
|
||||
@@ -168,3 +168,68 @@ def test_community_listing_excludes_rationale_and_concept_nodes():
|
||||
assert "Nodes (3):" in text, text
|
||||
assert "Names exist largely" not in text
|
||||
assert "Some Concept" not in text
|
||||
|
||||
|
||||
def test_contains_only_ast_declarations_are_not_knowledge_gaps():
|
||||
"""#4205: TS type aliases / enum members / local consts whose only edge
|
||||
is the structural contains edge from their own file are working as
|
||||
designed, so they must not be reported as weakly-connected gaps."""
|
||||
G = nx.Graph()
|
||||
G.add_node("tabs", label="tabs.ts", file_type="code", source_file="src/tabs.ts")
|
||||
for name in ("TabRow", "ContributionRow", "TabRecognition"):
|
||||
G.add_node(name, label=name, file_type="code",
|
||||
source_file="src/tabs.ts", source_location="L1",
|
||||
_origin="ast")
|
||||
G.add_edge("tabs", name, relation="contains", confidence="EXTRACTED")
|
||||
communities = {0: ["tabs", "TabRow", "ContributionRow", "TabRecognition"]}
|
||||
|
||||
text = generate(
|
||||
G, communities, {}, {}, [], [],
|
||||
{"total_files": 1, "total_words": 10}, {},
|
||||
root="proj", min_community_size=1,
|
||||
)
|
||||
gaps = text.split("## Knowledge Gaps")[-1].split("## ")[0] if "## Knowledge Gaps" in text else ""
|
||||
assert "TabRow" not in gaps
|
||||
assert "ContributionRow" not in gaps
|
||||
|
||||
from graphify.analyze import suggest_questions
|
||||
qs = suggest_questions(G, communities, {})
|
||||
assert all(q["type"] != "isolated_nodes" for q in qs)
|
||||
|
||||
|
||||
def test_semantic_contains_only_node_still_flags_as_gap():
|
||||
"""The #4205 fix must not hide the semantic side of gap detection: a
|
||||
non-AST node whose only edge is contains stays isolated."""
|
||||
G = nx.Graph()
|
||||
G.add_node("doc", label="guide.md", file_type="document", source_file="docs/guide.md")
|
||||
G.add_node("concept", label="OnboardingChecklist", file_type="document",
|
||||
source_file="docs/guide.md", source_location=None, _origin="semantic")
|
||||
G.add_edge("doc", "concept", relation="contains", confidence="EXTRACTED")
|
||||
communities = {0: ["doc", "concept"]}
|
||||
|
||||
text = generate(
|
||||
G, communities, {}, {}, [], [],
|
||||
{"total_files": 1, "total_words": 10}, {},
|
||||
root="proj", min_community_size=1,
|
||||
)
|
||||
gaps = text.split("## Knowledge Gaps")[-1].split("## ")[0] if "## Knowledge Gaps" in text else ""
|
||||
assert "OnboardingChecklist" in gaps
|
||||
|
||||
|
||||
def test_ast_node_with_non_contains_single_edge_still_flags():
|
||||
"""The exclusion is specifically for contains-only AST nodes: an AST
|
||||
node whose one edge is a different relation is still a gap."""
|
||||
G = nx.Graph()
|
||||
G.add_node("a", label="a.ts", file_type="code", source_file="src/a.ts")
|
||||
G.add_node("loose", label="LooseHelper", file_type="code",
|
||||
source_file="src/a.ts", source_location="L1", _origin="ast")
|
||||
G.add_edge("a", "loose", relation="calls", confidence="AMBIGUOUS")
|
||||
communities = {0: ["a", "loose"]}
|
||||
|
||||
text = generate(
|
||||
G, communities, {}, {}, [], [],
|
||||
{"total_files": 1, "total_words": 10}, {},
|
||||
root="proj", min_community_size=1,
|
||||
)
|
||||
gaps = text.split("## Knowledge Gaps")[-1].split("## ")[0] if "## Knowledge Gaps" in text else ""
|
||||
assert "LooseHelper" in gaps
|
||||
Reference in new issue
Block a user