fix(analyze): diversify suggested questions across signal types

Round-robin the existing question categories before truncating the result. Preserve within-category order, existing small-limit priority, candidate schema, and no-signal behavior. Fixes #3849.

Co-Authored-By: OpenAI Codex <noreply@openai.com>
(cherry picked from commit 560b88d86f)
This commit is contained in:
azizur100389 authored and safishamsi committed 2026-10-04 20:40:34 +01:00
1 parent b8fcd29cb1
commit bb20ef7234
3 files changed
+73 -1

No files matched your search

+6
View File
@@ -93,6 +93,12 @@ See `SECURITY.md` for the full threat model.
## Testing
`analyze.suggest_questions()` interleaves candidates across question types before
applying its result limit (seven by default). This keeps ambiguous relationships
from hiding bridge, inferred-relationship, isolation, and low-cohesion signals.
The existing candidate order within each type is preserved; if the limit is smaller
than the number of available types, their generation order determines priority.
One test file per module under `tests/`. Run with:
```bash
+13 -1
View File
@@ -468,6 +468,9 @@ def suggest_questions(
Generate questions the graph is uniquely positioned to answer.
Based on: AMBIGUOUS edges, bridge nodes, underexplored god nodes, isolated nodes.
Each question has a 'type', 'question', and 'why' field.
Interleave question types before applying top_n so an abundant early type
cannot crowd out later signals. Preserve the existing order within each type
and category generation order when the limit is smaller than the type count.
"""
if community_labels:
community_labels = {int(k) if isinstance(k, str) else k: v for k, v in community_labels.items()}
@@ -583,7 +586,16 @@ def suggest_questions(
),
}]
return questions[:top_n]
from itertools import zip_longest
by_type: dict[str, list[dict]] = {}
for question in questions:
by_type.setdefault(question["type"], []).append(question)
diversified = [
question for row in zip_longest(*by_type.values())
for question in row if question is not None
]
return diversified[:top_n]
def graph_diff(G_old: nx.Graph, G_new: nx.Graph) -> dict:
+54
View File
@@ -603,6 +603,60 @@ def test_god_nodes_filter_is_case_insensitive():
assert variant not in labels, f"`{variant}` should be filtered as JSON-key noise"
def _question_diversity_graph():
G = nx.Graph()
ambiguous = [f"uncertain_{i}" for i in range(10)]
inferred = ["inferred_a", "inferred_b"]
disconnected = [f"disconnected_{i}" for i in range(5)]
for node in ["hub", *ambiguous, *inferred, *disconnected]:
G.add_node(node, label=node, source_file="src/example.py", file_type="code")
for node in ambiguous:
G.add_edge("hub", node, confidence="AMBIGUOUS", relation="uses")
for node in inferred:
G.add_edge("hub", node, confidence="INFERRED", relation="uses")
communities = {0: ["hub"], 1: ambiguous + inferred, 2: disconnected}
return G, communities
def test_suggest_questions_does_not_starve_later_categories():
"""#3849: ten ambiguous edges must not hide all other question types."""
G, communities = _question_diversity_graph()
questions = suggest_questions(G, communities, {})
assert len(questions) == 7
assert [q["type"] for q in questions[:5]] == [
"ambiguous_edge", "bridge_node", "verify_inferred", "isolated_nodes", "low_cohesion",
]
assert questions == suggest_questions(G, communities, {})
@pytest.mark.parametrize("top_n", [1, 3, 7, 100])
def test_suggest_questions_diversity_preserves_limit_and_candidates(top_n):
G, communities = _question_diversity_graph()
all_questions = suggest_questions(G, communities, {}, top_n=100)
limited = suggest_questions(G, communities, {}, top_n=top_n)
assert limited == all_questions[:top_n]
assert sum(q["type"] == "ambiguous_edge" for q in all_questions) == 10
assert all(set(q) == {"type", "question", "why"} for q in all_questions)
def test_suggest_questions_single_category_uses_available_slots():
G = nx.complete_graph(5)
for node in G:
G.nodes[node].update(label=str(node), source_file="example.py", file_type="code")
for u, v in G.edges:
G.edges[u, v].update(confidence="AMBIGUOUS", relation="uses")
questions = suggest_questions(G, {0: list(G)}, {})
assert len(questions) == 7
assert all(q["type"] == "ambiguous_edge" for q in questions)
def test_suggest_questions_empty_graph_keeps_no_signal():
questions = suggest_questions(nx.Graph(), {}, {})
assert len(questions) == 1
assert questions[0]["type"] == "no_signal"
assert questions[0]["question"] is None
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")