fix(analyze): exclude contains-only AST declarations from knowledge gap detection (#4205)

(cherry picked from commit bf55b069f8)
This commit is contained in:
nothariharan authored and safishamsi committed 2026-10-08 14:27:06 +01:00
1 parent b583288e95
commit 4cf3dfba06
3 files changed
+85 -2

No files matched your search

+15
View File
@@ -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
View File
@@ -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
+65
View File
@@ -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