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) callsextract_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 throughextract_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 thebug_textFULLTEXT index (Lucene over Bug.title + Bug.subsystem — no new index needed) UNCONDITIONALLY on sanitized non-empty input text;input_refs(fromextract_from_body) feeds ONLY the additive shared-MENTIONS_CODE-symbol tier and never gates whether fulltext runs at all. Add an EXPLICITBug.subsystemequality 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 §02compute_relatednessand §03compute_bug_dedupbind to;shared_symbolsstays the input-intersect-candidate symbol set (empty list until w-e3e502d2’s enrichment bridge lands),scorecomposes 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_bodyfound 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 returnscount: 0with 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-livebug_textindex — no body_preview index until w-e3e502d2 populates body_preview; surface the fulltext score even whenshared_symbolsis 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:
- 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_refsfeed ONLY the additive shared-symbol tier) plus the explicitBug.subsystemequality tier w-848aa49a prescribes (today subsystem matches only implicitly via the index). - 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).
- w-e3e502d2 (export→import enrichment bridge) is genuinely untouched:
scripts/plan_corpus/export_json.pycontains ZERObody_previewmentions whileimport_plan_bug_graph.pyPhase 3 (~L696-1196) carries the complete inert bridge. Live graph confirms: 984 :Bug nodes, 0 withbody_preview, 0 Bug-sideMENTIONS_CODEedges (all 7,255 MENTIONS_CODE edges are Issue/Comment-side). - The envelope
{id, kind, title, status, score, shared_symbols}is the contract §02compute_relatednessand §03compute_bug_dedupbind to — it is currently UNCONSUMED (zerorelated-nodescalls inscripts/add_bug_runtime/orscripts/create_plan_runtime/), so shape changes are still cheap NOW and frozen after s-c7999cd5 lands. :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;:Plancarriesname/full_nameNULL on all 122 nodes (plan-kind dark — the siblingplan-graph-substratesection’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)