fix(prompt-hook): close the segment-vocab integrity gaps (#1141, #1142, #1144, #1145, #1146) (#1150)

Five hardening fixes to the #1136 MEDIUM (graph-derived) tier:

- #1141: updateNode() now writes the segment vocabulary like insertNode()
  does — framework post-extract renames (NestJS route prefixing) left the
  new name permanently unsearchable (the old rows orphaned, the backfill
  gated on an EMPTY vocab, so even a full re-index re-created the drift).
- #1142: new CodeGraph.healSegmentVocabIfEmpty() — the hook opens the
  graph without sync, so a database migrated from pre-vocab schema kept
  the MEDIUM tier dormant until some unrelated sync ran. The hook heals
  on first use (one SELECT when populated; lock-aware, defers to a
  running sync) and records noop-vocab-empty when it can't.
- #1144: a name whose only nodes are file/import kind is skipped instead
  of falling back to surfacing an import statement as a matched symbol;
  import specifiers no longer enter the vocab at all (shared
  isSegmentableKind gate across insertNode/updateNode/rebuild page query)
  since they can never be surfaced and only inflate rarity statistics.
- #1145: plural variant folding is keyed on English plural spelling —
  bare-s plurals no longer mint a bogus -es sibling (services→servic),
  unambiguous sibilant-es plurals no longer mint a bogus -s sibling
  (classes→classe), trailing -ss singulars no longer strip (class→clas);
  genuinely ambiguous endings (caches/databases) still emit both keys.
- #1146: getSegmentCoOccurrence folds variants to their original word
  inside the SQL (CASE mapping + COUNT(DISTINCT word)) so a plural pair
  of ONE word can't tie with a genuine two-word match and crowd it past
  the pre-fold ORDER BY/LIMIT; the JS re-check stays as the honesty layer.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Colby Mchenry
2026-07-02 17:23:54 -05:00
committed by GitHub
co-authored by Claude Fable 5
parent be55b93d02
commit 35611b92bb
7 changed files with 233 additions and 20 deletions
+10
View File
@@ -1156,6 +1156,16 @@ program
// scored, each hit re-verified to exist (see getSegmentMatches). The
// payload names the symbols but does NOT run explore — the agent owns
// the query where the hook's confidence is only "these are related".
//
// A database indexed before the vocab table existed starts with it
// EMPTY, and only sync() backfills it — which this hook never runs
// (#1142). Heal it here: on a populated vocab this is one SELECT;
// the actual backfill is a one-time batched pass whose cost the MCP
// server's own catch-up sync usually pays first (it runs at every
// session start). A distinct noop outcome keeps a dormant vocab
// from polluting the noop-unverified recall signal.
const vocabReady = await cg.healSegmentVocabIfEmpty().catch(() => false);
if (!vocabReady) { gate('noop-vocab-empty'); return; }
const related = cg.getSegmentMatches(proseWords);
if (related.length === 0) { gate('noop-unverified'); return; }
const lines = related