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.
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.
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).
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").
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.
41 English contractions are expanded before matching (both "can't" and "cant" → "cannot"), shared across all normalization layers.
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 |
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.
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
└─────────────────────────┘
| 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 |
| 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 |
- Exact-first: Curated phrase matches always take priority over lexical hits
- Fallback-only: Lexical search activates only when exact scoring underperforms
- Conservative stemming: Better to miss a match than produce a wrong one
- Curator control:
noneOfhard-suppression is inviolable - Top-K filtering: Only the best 5 lexical results are considered, preventing noise
- Zero external services: Everything runs locally in ~2ms with no network calls
lexical-index.test.ts— synonym bidirectionality, contraction expansion, index constructionstemmer.test.ts— suffix rules, 600+ exceptions, double-consonant handlingprompt-patterns-lexical.test.ts— hybrid scoring, adaptive tiers, fallback logicprompt-lexical-eval.test.ts— end-to-end evaluation against real skill configsuser-prompt-submit-skill-inject.test.ts— full hook integration with lexical on/off
Built for the Vercel Plugin for Claude Code — March 2026