Skip to content

Instantly share code, notes, and snippets.

@johnlindquist
Created March 11, 2026 20:00
Show Gist options
  • Select an option

  • Save johnlindquist/a44037dfe86a46b30f44a902ac015196 to your computer and use it in GitHub Desktop.

Select an option

Save johnlindquist/a44037dfe86a46b30f44a902ac015196 to your computer and use it in GitHub Desktop.
Vercel Plugin: Lexical Search & Intelligent Skill Matching — Executive Summary

Vercel Plugin: Lexical Search & Intelligent Skill Matching

Executive Summary

The Vercel Plugin for Claude Code now ships a hybrid prompt matching engine that combines exact phrase matching with full-text lexical search, synonym expansion, and suffix stemming. The result: skills are injected when the developer means something, not just when they say the exact magic words.


What Problem Does This Solve?

Previously, skill injection relied on exact substring matching against curated phrase lists. If a skill defined "deploy preview" as a trigger phrase, typing "ship the release to preview" would miss entirely. Developers use varied terminology — the plugin now understands that.


Key Features

1. MiniSearch-Powered Full-Text Index

Skills define structured retrieval metadata with four searchable fields:

Field Purpose Example
aliases Synonym names for the skill "chat components", "message UI"
intents What the developer wants to accomplish "build chat UI", "render streaming markdown"
entities Technical terms and APIs "useChat", "MessageResponse"
examples Natural language usage examples "build a chat interface with streaming"

The index uses TF-IDF ranking with prefix matching, 20% fuzzy tolerance, and field-weighted boosting (intents 3x, aliases 2x, entities 1.5x).

2. Suffix Stemmer

A conservative, rule-based stemmer normalizes word forms so morphological variants match:

  • "deploying" / "deployed" / "deployment"deploy
  • "authentication"authenticate
  • "configured"configure

600+ exception words prevent false positives (e.g., "ring" is not stemmed from -ing). Double-consonant correction and post-stem canonical mapping handle edge cases ("cach""cache").

3. Synonym Expansion

37+ semantic groups with bidirectional synonym matching:

deploy  ↔ ship, release, go-live, publish, push
env     ↔ environment, secret, config, variable
database ↔ db, sql, postgres, prisma, drizzle
cache   ↔ cdn, revalidate, isr, edge-cache, stale-while-revalidate

Synonyms are expanded at both index-build time and query time, so skill metadata and user prompts speak the same language.

4. Contraction Normalization

41 English contractions are expanded before matching (both "can't" and "cant""cannot"), shared across all normalization layers.

5. Adaptive Boost Tiers

When exact phrase matching falls short, lexical scores are amplified based on how close the exact score came to the threshold:

Scenario Exact Score Boost Rationale
No signals matched 0 1.5x Lexical is the only signal — trust it more
Weak signals >0, <half threshold 1.35x Some exact signal, moderate trust
Near threshold ≥half threshold 1.1x Exact scoring almost worked — conservative bump

6. Hard Suppression via noneOf

Even the strongest lexical match cannot override a noneOf exclusion. If a skill defines noneOf: ["vue"], no amount of synonym or stemming magic will inject it into a Vue project. Curator intent always wins.


How It Works

User Prompt: "ship the release to preview"
         │
         ▼
┌─────────────────────────┐
│   Normalize              │  lowercase, expand contractions, collapse whitespace
└─────────┬───────────────┘
          ▼
┌─────────────────────────┐
│   Exact Signal Scoring   │  phrases (+6), allOf (+4), anyOf (+1), noneOf (-∞)
│   Score: 0               │  ← no exact phrase matched
└─────────┬───────────────┘
          ▼  (score < minScore → fallback)
┌─────────────────────────┐
│   Lexical Fallback       │
│   ├─ Synonym expansion   │  "ship" → "deploy", "release" → "deploy"
│   ├─ Stemming            │  word normalization
│   ├─ MiniSearch TF-IDF   │  ranked retrieval over skill metadata
│   ├─ Top-K filter (5)    │  prevent broad false positives
│   └─ Adaptive boost      │  1.5x (no exact signals)
│   Final score: 10.5      │  ← exceeds minScore=6 ✓
└─────────┬───────────────┘
          ▼
┌─────────────────────────┐
│   Injection Pipeline     │  rank → dedup → budget (8KB) → inject top 2
└─────────────────────────┘

Integration Points

Hook When Role
UserPromptSubmit Every user message Runs hybrid scoring; injects up to 2 skills (8KB budget)
PreToolUse File read/write/bash Path + import + bash pattern matching (unchanged)
SessionStart Session init Initializes lexical index from skill map

Configuration

Variable Default Description
VERCEL_PLUGIN_LEXICAL_PROMPT on Set to 0 to disable lexical fallback
VERCEL_PLUGIN_LEXICAL_RESULT_MIN_SCORE 4.0 Minimum raw TF-IDF score for lexical hits
VERCEL_PLUGIN_PROMPT_INJECTION_BUDGET 8000 Max bytes injected per prompt hook

Design Principles

  1. Exact-first: Curated phrase matches always take priority over lexical hits
  2. Fallback-only: Lexical search activates only when exact scoring underperforms
  3. Conservative stemming: Better to miss a match than produce a wrong one
  4. Curator control: noneOf hard-suppression is inviolable
  5. Top-K filtering: Only the best 5 lexical results are considered, preventing noise
  6. Zero external services: Everything runs locally in ~2ms with no network calls

Test Coverage

  • lexical-index.test.ts — synonym bidirectionality, contraction expansion, index construction
  • stemmer.test.ts — suffix rules, 600+ exceptions, double-consonant handling
  • prompt-patterns-lexical.test.ts — hybrid scoring, adaptive tiers, fallback logic
  • prompt-lexical-eval.test.ts — end-to-end evaluation against real skill configs
  • user-prompt-submit-skill-inject.test.ts — full hook integration with lexical on/off

Built for the Vercel Plugin for Claude Code — March 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment