45%

graph-related-nodes-query

Goal

Give the intel graph a query that takes a new bug’s free text and returns ranked related :PlanBugNode, so relatedness rides the graph instead of hand-rolled regex.

Implementation Sketch

Deliver the graph-related-nodes-query slice of the graph-native relatedness + dedup capability per the plan mission; ground every claim against the verified intel-graph facts in the overview.

Work Items

  • VERIFY-FIRST — do not re-implement. intel-query.sh related-nodes --text "<title+repro>" [--kind bug|plan|all] [--repo ori] [--limit N] is already delivered: cmd_related_nodes (intel_repo/neo4j/query_graph.py:2631), help registration (scripts/intel_query/01-bootstrap.sh:154), and dispatch registration alongside symbol-plans/bugs-for (scripts/intel_query/04-gates-routing.sh:77-86). Confirm the subcommand and registration are still present and correctly wired at execution time; if drifted, repair in place. Never author a second dispatch path or duplicate help text — registration lives once.
  • VERIFY-FIRST — do not re-implement. Input-text code-ref extraction already reuses the canonical extractor: _related_input_refs (intel_repo/neo4j/query_graph.py:2558-2571) calls extract_code_refs.extract_from_body (backtick/clause-id/error-code aware) with zero new regex. Confirm this reuse is still intact at execution time; if a new regex or duplicate extractor has crept in, delete it and re-route through extract_from_body — never grow a second extractor.
  • Fix the fulltext-primary gate inversion so the PRIMARY ranking this item requires actually runs: cmd_related_nodes (intel_repo/neo4j/query_graph.py:2631, gate at L2665-2667) MUST run the bug_text FULLTEXT index (Lucene over Bug.title + Bug.subsystem — no new index needed) UNCONDITIONALLY on sanitized non-empty input text; input_refs (from extract_from_body) feeds ONLY the additive shared-MENTIONS_CODE-symbol tier and never gates whether fulltext runs at all. Add an EXPLICIT Bug.subsystem equality scoring tier (today subsystem matches only implicitly inside the fulltext index, not as its own scored contribution). Preserve the versioned envelope {id, kind, title, status, score, shared_symbols} exactly — it is the frozen contract §02 compute_relatedness and §03 compute_bug_dedup bind to; shared_symbols stays the input-intersect-candidate symbol set (empty list until w-e3e502d2’s enrichment bridge lands), score composes the fulltext Lucene score plus the subsystem-equality bonus plus the shared-symbol count.
  • Extend the graceful-degrade contract to the INPUT side, not just the candidate side: ‘graph up but no code ref extractable from the input text’ MUST NOT be treated as ‘no related candidate’ — fulltext ranking runs regardless of whether extract_from_body found any input refs (input refs feed only the additive shared-symbol tier per w-848aa49a’s fix). Flip pinned test (c) (intel_repo/tests/test_related_nodes.py) from asserting the short-circuit to asserting the corrected behavior, paired with a NEW positive pin (prose-only text with zero extractable code refs still returns ranked fulltext hits) and a negative twin (empty/whitespace-only text still returns count: 0 with no driver contact, preserving the genuine no-signal case). Keep the existing candidate-side contract unchanged: graph unavailable -> {"status":"unavailable","reason":...} exit 0 (consumer fails open); rank on the already-live bug_text index — no body_preview index until w-e3e502d2 populates body_preview; surface the fulltext score even when shared_symbols is empty.
  • Populate the candidate-side bug→symbol bridge so the mission’s prescribed ‘shared MENTIONS_CODE symbols + body_preview overlap’ ranking is feasible against the live graph instead of a fulltext-only proxy: wire scripts/plan_corpus/export_json.py (§02.3) to emit each tracker entry’s body_preview, and confirm intel_repo/neo4j/import_plan_bug_graph.py Phase 3 (~L734-1212) MERGEs (:Bug|:PlanBugNode)-[:MENTIONS_CODE]->CodeReference->RESOLVES_TO->Symbol from that body_preview via extract_from_body. The import bridge CODE exists but is INERT — 0 bug MENTIONS_CODE edges and 0 body_preview nodes at HEAD (verified) because the export envelope does not feed it. This prerequisite gives w-48b2ef06’s shared-symbol primary tier real data; absent it, the query runs only on the fulltext substrate.

Fresh intel (regenerated)

This section is ALREADY SUBSTANTIALLY BUILT but its route-state says otherwise, and the ONE live defect that remains is precisely the gap between the two. cmd_related_nodes (intel_repo/neo4j/query_graph.py:2631), the scripts/intel-query.sh related-nodes registration, and 7 unit tests all exist at HEAD (per the 2026-06-28 commit-deferred implementation) — yet plan.json still shows w-848aa49a / w-d7049000 / w-20fdb9c8 / w-e3e502d2 not-started. Live probing shows the not-started statuses are ACCIDENTALLY HONEST:

[JOIN] The decisive finding — the fulltext-primary contract is inverted by an input-refs gate, and the tests PIN the inversion. cmd_related_nodes early-returns count: 0 whenever extract_from_body finds no code ref in the INPUT text (query_graph.py:2665-2667 “Empty input refs → a real ‘no related candidate’”), so the bug_text fulltext leg — which w-848aa49a names the PRIMARY realizable ranking — NEVER RUNS for a plain-prose bug title. Verified live: related-nodes --text "add-bug relatedness dedup duplicate" returns 0 hits while the raw bug_text index returns BUG-07-190/-254/-170 at Lucene scores 2.3-3.7 for the same words; add one backticked symbol to the text and the same query returns 5 strong hits. intel_repo/tests/test_related_nodes.py test (c) pins the short-circuit as correct (gd.assert_not_called() — no driver contact), and test (a)‘s own comment admits “Text MUST carry a code ref so we pass the empty-refs short-circuit”. This is exactly the dark-signal-vs-no-relation confusion w-d7049000 exists to forbid, relocated from the candidate side to the input side.

Decisions this package forces before any work:

  1. VERIFY-FIRST, do not re-implement — w-201356a2’s subcommand + registration + tests are live at HEAD; the remaining work is the gate inversion (run fulltext unconditionally; input_refs feed ONLY the additive shared-symbol tier) plus the explicit Bug.subsystem equality tier w-848aa49a prescribes (today subsystem matches only implicitly via the index).
  2. Flipping the gate REQUIRES modifying pinned test (c) — that is a legitimate contract correction, not test-weakening, but it must be paired with a NEW positive pin: prose-only text → fulltext hits returned (the negative-space pin the suite lacks).
  3. w-e3e502d2 (export→import enrichment bridge) is genuinely untouched: scripts/plan_corpus/export_json.py contains ZERO body_preview mentions while import_plan_bug_graph.py Phase 3 (~L696-1196) carries the complete inert bridge. Live graph confirms: 984 :Bug nodes, 0 with body_preview, 0 Bug-side MENTIONS_CODE edges (all 7,255 MENTIONS_CODE edges are Issue/Comment-side).
  4. The envelope {id, kind, title, status, score, shared_symbols} is the contract §02 compute_relatedness and §03 compute_bug_dedup bind to — it is currently UNCONSUMED (zero related-nodes calls in scripts/add_bug_runtime/ or scripts/create_plan_runtime/), so shape changes are still cheap NOW and frozen after s-c7999cd5 lands.
  5. :PlanBugNode (the node kind the work items name as ranking candidates) is TEXTLESS on the live graph — its 18,925 nodes carry only {id, key, level, parent_id, status, timestamps}. The rankable candidates today are :Bug (title+subsystem) only; :Plan carries name/full_name NULL on all 122 nodes (plan-kind dark — the sibling plan-graph-substrate section’s scope, not this one’s).

DIG DEEPER

scripts/intel-query.sh related-nodes --text "plain prose with no code refs at all" --kind bug --limit 5
scripts/intel-query.sh related-nodes --text 'same prose plus `a_symbol`' --kind bug --limit 5
python -m scripts.plan_corpus.read --summary plans/graph-native-bug-relatedness

(full dossier: graph-related-nodes-query—s-f7b5e470.intel.md)