AMBIGUOUS-tagged edges (confidence ≈ 0.35, often `conceptually_related_to`
from LLM semantic extraction) could glue otherwise-independent clusters
together through Leiden/Louvain modularity, and the resulting bridge
questions crowded `Suggested Questions` with low-signal entries. The
`AMBIGUOUS` flag was already correct — only downstream consumers
treated it identically to EXTRACTED/INFERRED.
Changes:
- `cluster()` gains `ambiguous_scale: float = 0.5` (overridable via
`GRAPHIFY_AMBIGUOUS_SCALE`). Multiplier is applied to AMBIGUOUS edge
`weight` in a graph copy before partitioning so the raw graph (and
`/graphify path`/`explain` output) stays untouched. 1.0 restores
legacy behavior.
- `suggest_questions()` tags each ambiguous_edge entry with
`low_confidence=True`. Interleave + top_n contract from #3849 is
preserved; the tag is purely metadata.
- `report.py` splits tagged entries into a new "Low-confidence Hints"
section below the main Suggested Questions.
Tests:
- `test_cluster_ambiguous_scale.py` (new, 7 tests): default scale
demotes AMBIGUOUS weight, scale=0.0 drops it entirely, scale=1.0
is no-op, env override + malformed env fail-soft, raw graph not
mutated, no copy when no AMBIGUOUS edges present.
- `test_suggest_questions_ambiguous.py` (new, 4 tests): tag present
on every ambiguous_edge entry, never on other types.
- `test_analyze.py::test_suggest_questions_diversity_preserves_limit_and_candidates`
relaxed to allow the optional `low_confidence` key.
Fixes#4199
---
한국어 요약: AMBIGUOUS edge (LLM 추론, 신뢰도 ~0.35) 가 Leiden 모듈러리티로 독립
클러스터를 묶거나 Suggested Questions top-N 을 잠식하던 문제 수정. cluster() 에
ambiguous_scale 파라미터 (default 0.5, env override) 추가, 원본 그래프는 복사 후
수정하여 /graphify path/explain 영향 없음. suggest_questions 는 low_confidence
플래그만 추가하고 interleave contract (#3849) 유지. report.py 가 분리 렌더링.
regression test 11 개 신규 + 기존 1 개 relax.
(cherry picked from commit ff52a29266)
graph_diff keyed undirected edges by their sorted endpoints, so a call
that reversed (a->b became b->a) dropped out of the --update summary.
When both snapshots carry _src/_tgt and they differ, the edge is now
reported as one removed and one new edge, with source/target taken from
the markers. Graphs without the markers diff as before.
The skill's diff step loaded the old graph.json with a plain
node_link_graph call, which drops the stored direction, so it now uses
paths.load_node_link_graph (#4066/#4088). Skills regenerated with
skillgen, and the aider/devin monolith round-trip allows the changed
lines.
Closes#4067
Co-Authored-By: Grok Bot <noreply@x.ai>
(cherry picked from commit 917fab9fbb)
_top_level_dir() returned the whole path when it had no "/", so two
files at the scan root (a flat folder of notes, or README.md and
setup.py) got different "top-level dirs". Every such cross-file edge
(other than suppressed INFERRED calls/uses) received +2 and the reason
"connects across different repos/directories" in surprising_connections,
which is false.
- A path without "/" now maps to the scan root ("."). "." rather than ""
keeps root files distinct from an absolute path outside the scan root,
whose first component is "".
- Backslashes and a leading "./" are normalized first: build_from_json
converts backslashes but keeps "./", and serve.py's _load_graph() and
API callers can skip build_from_json altogether.
- The comparison key is (node "repo", top-level dir). merge-graphs and
global add keep source_file repo-relative and tag nodes with "repo",
so two repos can share directory names; single-repo graphs have no
"repo" and are unaffected. On merged graphs a cross-repo edge now gets
the bonus whenever the repos differ; before, it depended on whether
the top-level directory names happened to differ.
Tests: the flat case (fails on d6eaa8a), merged graphs, backslash and
"./" paths, plus pins for what must not change (root vs subdirectory,
root vs out-of-root absolute path).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 05d6410535)
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>
* feat: detect circular import dependencies at file level
- Add find_import_cycles() to analyze.py
- Collapses symbol nodes to parent files, builds directed file graph
- Uses nx.simple_cycles() bounded by max_cycle_length (default 5)
- Deduplicates rotations, returns shortest cycles first
- Considers both imports_from and re_exports edges
Tested on a 976-file Next.js codebase: found 4 cycles including
a known utils↔barrel circular dependency and a 4-file API cycle.
* fix: resolve import-cycle merge blockers
- use source_file-only endpoint resolution (no label fallback)
- support Graph/DiGraph orientation via edge source_file
- return structured cycle records and include self-loops
- integrate Import Cycles section into GRAPH_REPORT.md
- expand cycle tests for real-schema IDs, undirected input,
missing source_file nodes, and non-import relations
* fix(analyze): exclude npm dep-block keys from god-node selection
Extends _JSON_NOISE_LABELS in graphify/analyze.py with the six npm
package.json dependency-block keys (dependencies, devDependencies,
peerDependencies, optionalDependencies, bundledDependencies,
bundleDependencies — lowercased to match the existing .strip().lower()
comparison in _is_json_key_node).
On JS/TS corpora with non-trivial dependency counts, the dep-block key
node accumulates contains+imports edges to every package entry and was
surfacing as the top god-node in the report. The fix is a one-line
extension of the existing frozenset; no new helpers or code paths.
Includes a parametrized regression test in tests/test_analyze.py
covering all five npm keys: dependencies, devDependencies,
peerDependencies, optionalDependencies, bundledDependencies.
Validated live against rsl-siege-manager @ 6085fd66 — zero npm dep-block
keys in the top-10 god-nodes after the fix. Pre-existing failures
(Windows symlink + SQL tree-sitter): 17, unchanged.
Closes#2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* style: drop fork-local issue refs from analyze comments
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
In Python+TypeScript monorepos, the call and import resolvers match by
label across language boundaries (AuthError -> Member), producing false
positives that dominate 'Surprising Connections' due to cross-dir (+2)
and cross-community (+1) bonuses.
Expand the existing calls guard to also cover uses edges, and zero the
cross-dir and cross-community bonuses for these pairs — not just conf_bonus.
Leaves semantically_similar_to, EXTRACTED, and AMBIGUOUS edges unaffected.
5 new tests: calls suppressed, uses suppressed, semantically_similar_to
preserved, same-language INFERRED preserved, cross-language EXTRACTED preserved.