fix(analyze): exclude rationale nodes from suggested-question gap count (#1768)

suggest_questions()'s "isolated/weakly-connected nodes" filter was missing the
`file_type != "rationale"` exclusion that report.py's Knowledge Gaps section
already applies, so the same GRAPH_REPORT.md reported two different counts for
the same concept (757 vs 245 on a real graph) — an internal inconsistency that
made a healthy graph look like a documentation problem. Add the same filter so
both computations agree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
balloon72
2026-07-11 11:52:12 +01:00
committed by safishamsi
co-authored by Claude Opus 4.8
parent a646d66a67
commit 15a86536c4
3 changed files with 20 additions and 2 deletions
+2
View File
@@ -4,6 +4,8 @@ Full release notes with details on each version: [GitHub Releases](https://githu
## 0.9.13 (unreleased)
- Fix: the report's "Suggested Questions" weakly-connected-node count now matches its "Knowledge Gaps" count (#1768, thanks @balloon72). `suggest_questions()` omitted the `file_type != "rationale"` filter that `report.py`'s Knowledge Gaps section applies, so the same `GRAPH_REPORT.md` showed two different numbers for the same concept (e.g. 757 vs 245), making a healthy graph look like it had a major documentation gap. Both computations now use the same filter.
- Fix: Bash scripts that run each other by execution now get a cross-file edge (#1756, thanks @balloon72). `extract_bash` only linked `source x.sh` / `. x.sh`; the two most common forms — `bash x.sh` and `./x.sh` — produced no edge, so execution topology was missing. They now emit a `calls` edge (context `script_invocation`) to the invoked script's entry node when the target resolves to a real file on disk (script runners `bash`/`sh`/`zsh`/`ksh`/`dash` and bare `./x.sh`), skipping missing or shadowed targets.
- Fix: Ruby `.rake` files are now extracted and participate in Ruby cross-file resolution like `.rb` (#1784, thanks @krishnateja7). `.rake` is plain Ruby but the extension was gated out of seven places (classification, extractor dispatch, the language-name/family maps, the `ruby_member_calls` resolver's suffix set, both `.rb`-suffix filters in `ruby_resolution.py`, and the build repo-tag map), so every rake task was skipped and its calls were invisible. All seven now include `.rake`; `Widget.tally` from a `.rake` task resolves to its `.rb` definition.
+4 -1
View File
@@ -504,7 +504,10 @@ def suggest_questions(
# 4. Isolated or weakly-connected nodes → exploration questions
isolated = [
n for n in G.nodes()
if G.degree(n) <= 1 and not _is_file_node(G, n) and not _is_concept_node(G, n)
if G.degree(n) <= 1
and not _is_file_node(G, n)
and not _is_concept_node(G, n)
and G.nodes[n].get("file_type") != "rationale"
]
if isolated:
labels = [G.nodes[n].get("label", n) for n in isolated[:3]]
+14 -1
View File
@@ -5,7 +5,7 @@ import pytest
from pathlib import Path
from graphify.build import build_from_json
from graphify.cluster import cluster
from graphify.analyze import god_nodes, surprising_connections, _is_concept_node, graph_diff, _surprise_score, _file_category, _is_json_key_node, find_import_cycles
from graphify.analyze import god_nodes, surprising_connections, _is_concept_node, graph_diff, _surprise_score, _file_category, _is_json_key_node, find_import_cycles, suggest_questions
from graphify.extract import _make_id
FIXTURES = Path(__file__).parent / "fixtures"
@@ -603,6 +603,19 @@ def test_god_nodes_filter_is_case_insensitive():
assert variant not in labels, f"`{variant}` should be filtered as JSON-key noise"
def test_suggest_questions_excludes_rationale_nodes_from_isolated_count():
G = nx.Graph()
G.add_node("service", label="Service", file_type="code", source_file="service.py")
G.add_node("reason", label="Explains service", file_type="rationale", source_file="service.py")
questions = suggest_questions(G, communities={}, community_labels={}, top_n=10)
isolated = next(question for question in questions if question["type"] == "isolated_nodes")
assert isolated["why"].startswith("1 weakly-connected node")
assert "`Service`" in isolated["question"]
assert "Explains service" not in isolated["question"]
# ── find_import_cycles tests ──────────────────────────────────────────────────