Skip to content

Feature: structural gaps - surface close-but-unconnected community pairs with bridging questions in GRAPH_REPORT.md #1839

Description

@janezhang99

What this adds

The Knowledge Gaps section catches node-level gaps: isolated nodes, thin communities. It says nothing about cluster-level blind spots, meaning two healthy communities that sit close in the graph (several shared 2-hop intermediaries) but have few or no direct topical edges between them. In a documentation corpus that pattern is usually a real editorial finding: the connection you assumed was written down but never was.

I built this as a post-processor for my own corpus and it surfaced a genuine miss on the first run. Sample output from a 148-node, 12-community doc corpus (a civic infrastructure project I lead):

## Structural Gaps
_Pairs of well-formed topic clusters that sit close in the graph but are barely
connected to each other. Each is a candidate blind spot; the bridging question
is what would close it._

- **Layer 3 — Provenance (Chain of Custody)  ⇄  Layer 4 — Federated AI (Collective Learning)**
  Bridging question: **What would directly link `Layer 3 — Provenance (Chain of Custody)`
  and `Layer 4 — Federated AI (Collective Learning)` - for instance, between
  `Layer 2 — The Mesh Protocol (Federated Discovery)` and `Flower (flwr) Federated Learning Runtime`?**
  _These two topics share 5 intermediary concepts but have 1 direct edge between them
  (structural-gap score 0.0694). A likely blind spot: the graph treats them as
  neighbours-of-neighbours, not as directly related._

The architecture in that corpus depends on exactly that coupling, and no document states it. This is the structural-gap reading InfraNodus is known for, done deterministically and offline.

How it works (no LLM calls, sub-second)

  • Gate: a community is well-formed if it has at least 4 real nodes (reusing the _is_file_node / _is_concept_node / _is_json_key_node exclusions) and connectance (2·edges/nodes) of at least 1.2. Connectance rather than cohesion_score because edge density falls mechanically as a community grows, which would drop the largest, most central clusters.
  • Proximity: shared 2-hop neighbours, each weighted 1/sqrt(degree) so one mega-hub cannot inflate every pair, normalized by the smaller community's real-node count.
  • gap_score = proximity / (1 + observed_cross_edges), where imports/contains/method edges do not count as connection (wiring is not topical linkage).
  • Pairs with no shared neighbours are excluded. Unrelated is not a gap.
  • Each gap renders with a deterministic bridging question built from the two clusters' lead concepts.

Where it lives

community_gaps() in analyze.py (same signature shape as suggest_questions), rendered by report.generate(), which computes it by default when the new structural_gaps kwarg is None. So every report surface gets the section (skill pipeline, cluster-only, watch and hook rebuilds) with zero call-site changes, and the skill files do not change at all: tools.skillgen --check passes untouched. The section is omitted when no gaps are found, so well-linked graphs render byte-identical to today. Passing structural_gaps=[] suppresses it.

Honest scope note

On code corpora this mostly stays quiet, and that seems correct: import edges are explicit and 2-hop overlap between gated communities is sparse. I validated on a 370-node Next.js repo and got zero to one weak gap, and only after filtering JSON key nodes out of the shared-neighbour evidence (without _is_json_key_node, dependencies and scripts dominate the results). The feature earns its place on doc and mixed corpora.

Related: #1768 reconciled the two existing gap counts; this extends the same story from nodes to cluster pairs. #1254 and #198 are about generating cross-layer edges; this is the complement, reporting where topical edges are missing.

I have this implemented and tested against 0.9.13: ten tests covering the gate, damping, structural-edge exclusion, JSON-noise exclusion, determinism, and the report rendering paths, with the full suite passing locally (2539 passed; one pre-existing network-dependent security test fails identically on unmodified main in my sandbox) and skillgen untouched. Happy to open the PR (rebased to current main) if this is in scope. Also fine if you would rather keep the report surface small; it works as a standalone post-processor too, which is how I run it today.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions