Instead of injecting the full SKILL.md body as additionalContext, inject "You must run the Skill(<name>) tool." — a more conventional way of telling the agent to use the Skill tool for context loading.
27 KiB
Architecture Overview
What Is vercel-plugin?
vercel-plugin is a hook-driven context router for Claude Code. It solves a fundamental problem in AI-assisted development: the agent needs domain knowledge to be useful, but has a finite context window.
Two failure modes arise without it:
| Failure Mode | What Happens | Effect |
|---|---|---|
| Too much context | Every skill is injected upfront | The context window fills with irrelevant instructions; the agent loses focus and makes mistakes |
| Too little context | No skills are injected | The agent lacks Vercel-specific knowledge; it hallucinates APIs, skips best practices, and produces broken deployments |
vercel-plugin threads the needle by injecting only the right skills at the right time, driven by what the developer is actually doing — the files they touch, the commands they run, and the questions they ask.
High-Level System Architecture
flowchart TB
subgraph Claude["Claude Code Runtime"]
direction TB
Agent["Agent (LLM)"]
Tools["Tool Calls<br/>(Read, Edit, Write, Bash, Agent)"]
Context["Context Window"]
end
subgraph Plugin["vercel-plugin Hook System"]
direction TB
HooksJSON["hooks.json<br/>(12 hook registrations)"]
Manifest["skill-manifest.json<br/>(pre-compiled patterns)"]
Skills["skills/<name>/SKILL.md<br/>(46 skill definitions)"]
VercelMD["vercel.md<br/>(ecosystem graph, ~52KB)"]
end
subgraph Lifecycle["Hook Lifecycle Phases"]
direction LR
Startup["Startup<br/>3 hooks"]
Runtime["Runtime<br/>3 hooks"]
PostAction["Post-Action<br/>3 hooks"]
Subagent["Subagent<br/>2 hooks"]
Teardown["Teardown<br/>1 hook"]
end
Agent -->|"triggers"| Tools
Tools -->|"fires hooks"| Plugin
Plugin -->|"additionalContext"| Context
Context -->|"informs"| Agent
HooksJSON --> Lifecycle
style Claude fill:#0a0a1a,stroke:#333,color:#e0e0e0
style Plugin fill:#1a1a2e,stroke:#333,color:#e0e0e0
style Lifecycle fill:#16213e,stroke:#333,color:#e0e0e0
How It Works
- Claude Code registers all hooks from
hooks/hooks.jsonat startup - When the agent takes an action (reads a file, runs a command, submits a prompt), Claude Code fires the corresponding hook(s)
- Each hook receives JSON on stdin describing the action, evaluates pattern matches, and decides whether to inject skill content
- Matched skills are returned as
additionalContextin the hook's JSON stdout, which Claude Code appends to the agent's context for the current turn
Hook Registration Flow
All 12 hooks are declared in hooks/hooks.json. Each entry maps a lifecycle event + regex matcher to a Node.js command with an optional timeout.
flowchart TB
subgraph Startup ["SessionStart Phase"]
SS1["session-start-seen-skills.mjs<br/>Initializes dedup state"]
SS2["session-start-profiler.mjs<br/>Scans project → sets LIKELY_SKILLS"]
SS3["inject-claude-md.mjs<br/>Outputs vercel.md ecosystem graph"]
end
subgraph Runtime ["Runtime Phase"]
PTU["pretooluse-skill-inject.mjs<br/>Read | Edit | Write | Bash<br/>Main injection engine"]
PSO["pretooluse-subagent-spawn-observe.mjs<br/>Agent<br/>Records pending subagent launches"]
UPS["user-prompt-submit-skill-inject.mjs<br/>All prompts<br/>Prompt signal scoring"]
end
subgraph PostAction ["Post-Action Phase"]
PTV["posttooluse-validate.mjs<br/>Write | Edit<br/>Skill validation rules"]
PTF["posttooluse-shadcn-font-fix.mjs<br/>Bash<br/>Shadcn font fix"]
PVO["posttooluse-verification-observe.mjs<br/>Bash<br/>Verification boundary observer"]
end
subgraph SubagentLC ["Subagent Lifecycle"]
SAB["subagent-start-bootstrap.mjs<br/>Any subagent<br/>Injects context by agent type budget"]
SAS["subagent-stop-sync.mjs<br/>Any subagent<br/>Writes ledger + syncs dedup state"]
end
subgraph Teardown ["Teardown Phase"]
SEC["session-end-cleanup.mjs<br/>Deletes all temp files"]
end
Startup --> Runtime
Runtime --> PostAction
PostAction --> SubagentLC
SubagentLC --> Teardown
style Startup fill:#1a1a2e,stroke:#16213e,color:#e0e0e0
style Runtime fill:#16213e,stroke:#0f3460,color:#e0e0e0
style PostAction fill:#0f3460,stroke:#533483,color:#e0e0e0
style SubagentLC fill:#533483,stroke:#e94560,color:#e0e0e0
style Teardown fill:#e94560,stroke:#e94560,color:#e0e0e0
Hook Registry Table
| # | Event | Hook File | Matcher | Timeout | Purpose |
|---|---|---|---|---|---|
| 1 | SessionStart | session-start-seen-skills.mjs |
startup|resume|clear|compact |
— | Initialize dedup env var |
| 2 | SessionStart | session-start-profiler.mjs |
startup|resume|clear|compact |
— | Profile project, set LIKELY_SKILLS |
| 3 | SessionStart | inject-claude-md.mjs |
startup|resume|clear|compact |
— | Inject vercel.md ecosystem graph |
| 4 | PreToolUse | pretooluse-skill-inject.mjs |
Read|Edit|Write|Bash |
5s | Main skill injection engine |
| 5 | PreToolUse | pretooluse-subagent-spawn-observe.mjs |
Agent |
5s | Record pending subagent launches |
| 6 | UserPromptSubmit | user-prompt-submit-skill-inject.mjs |
(all prompts) | 5s | Prompt signal scoring + injection |
| 7 | PostToolUse | posttooluse-shadcn-font-fix.mjs |
Bash |
5s | Fix shadcn font loading |
| 8 | PostToolUse | posttooluse-verification-observe.mjs |
Bash |
5s | Observe verification boundaries |
| 9 | PostToolUse | posttooluse-validate.mjs |
Write|Edit |
5s | Run skill validation rules |
| 10 | SubagentStart | subagent-start-bootstrap.mjs |
.+ (any) |
5s | Bootstrap subagent with context |
| 11 | SubagentStop | subagent-stop-sync.mjs |
.+ (any) |
5s | Write ledger, sync dedup |
| 12 | SessionEnd | session-end-cleanup.mjs |
— | — | Delete temp files |
Skill Injection Pipeline
This is the core data flow — how the plugin decides which skills to inject and in what order.
flowchart LR
subgraph Signals ["Signal Collection"]
FP["File path globs"]
BC["Bash command regex"]
IP["Import patterns"]
PS["Prompt phrases"]
VC["vercel.json keys"]
PR["Profiler likely-skills"]
end
subgraph Scoring ["Score & Rank"]
PM["Pattern Match<br/>base priority 4-8"]
VCR["vercel.json routing<br/>+-10 priority"]
PB["Profiler boost<br/>+5 priority"]
SM["+50 setup mode boost"]
TX["+40 TSX review boost"]
RK["Rank by<br/>final priority"]
end
subgraph Filtering ["Filter & Budget"]
DD["Dedup check<br/>(claim dir + env var + session file)"]
BG["Budget gate<br/>PreToolUse: 3 skills / 18KB<br/>PromptSubmit: 2 skills / 8KB"]
SF["Summary fallback<br/>(over budget -> short summary)"]
end
subgraph Output ["Injection"]
AC["additionalContext<br/>-> Claude's context window"]
end
Signals --> Scoring
Scoring --> Filtering
Filtering --> Output
FP --> PM
BC --> PM
IP --> PM
PS --> PM
VC --> VCR
PR --> PB
PM --> RK
VCR --> RK
PB --> RK
SM --> RK
TX --> RK
RK --> DD
DD --> BG
BG --> SF
SF --> AC
BG --> AC
style Signals fill:#1a1a2e,stroke:#333,color:#e0e0e0
style Scoring fill:#16213e,stroke:#333,color:#e0e0e0
style Filtering fill:#0f3460,stroke:#333,color:#e0e0e0
style Output fill:#533483,stroke:#333,color:#e0e0e0
Pipeline Walkthrough
-
Signal Collection — When the agent performs an action (opens a file, runs a command, submits a prompt), the relevant hook extracts signals: file paths are matched against glob patterns, bash commands against regex, imports against package names, and prompt text against phrase/allOf/anyOf scoring.
-
Score & Rank — Each matched skill starts with its base
priority(typically 4-8). Multiple boosters can raise it:Booster Value Source Profiler +5 VERCEL_PLUGIN_LIKELY_SKILLS(detected at session start)vercel.json routing up to +-10 Keys in project's vercel.jsonSetup mode +50 VERCEL_PLUGIN_SETUP_MODE=1(greenfield/bootstrap projects)TSX review +40 After N .tsxedits (default 3)Dev server detect boost When dev server patterns appear in bash -
Filter & Budget — Skills are deduplicated (no skill injects twice per session), then the top candidates are checked against the byte budget. If the full skill body would exceed the budget, the plugin falls back to injecting just the
summaryfield instead. -
Injection — Surviving skills are returned as
additionalContextin the hook's JSON output, which Claude Code appends to the agent's context for the current turn.
Dedup Contract
A skill should never be injected twice in the same session. The dedup system uses three redundant sources of truth, merged on every hook invocation.
flowchart TB
subgraph Sources ["Three Sources of Truth"]
CD["Claim Directory<br/><tmpdir>/vercel-plugin-<sessionId>-seen-skills.d/<br/>One empty file per skill (atomic O_EXCL create)"]
SF["Session File<br/><tmpdir>/vercel-plugin-<sessionId>-seen-skills.txt<br/>Comma-delimited snapshot"]
EV["Env Var<br/>VERCEL_PLUGIN_SEEN_SKILLS<br/>Comma-delimited, set via CLAUDE_ENV_FILE"]
end
MERGE["mergeSeenSkillStates()<br/>Union of all three sources"]
CD --> MERGE
SF --> MERGE
EV --> MERGE
MERGE --> CHECK{"Skill already<br/>in merged set?"}
CHECK -->|Yes| SKIP["Skip injection"]
CHECK -->|No| CLAIM["Atomic claim + inject"]
CLAIM --> UPDATE["Update all three sources"]
Claim Mechanics
- Atomic claims:
openSync(path, "wx")with theO_EXCLflag ensures that if two hooks race to claim the same skill, exactly one succeeds and the other getsEEXIST. - Session file: A comma-delimited text file synced from the claim directory. Acts as a fast-read cache.
- Env var:
VERCEL_PLUGIN_SEEN_SKILLSpersists across hook invocations viaCLAUDE_ENV_FILE. Initialized to""by session-start. - State merge:
mergeSeenSkillStates()unions all three sources on every hook call, tolerating partial failures. - Scoped claims: Subagent dedup claims are scoped by
agentIdto prevent sibling subagents from cross-contaminating each other's state.
Dedup Strategies
The system uses a fallback chain (visible in debug logs):
| Strategy | Mechanism | When Used |
|---|---|---|
file |
Atomic file claims in tmpdir | Default — most reliable |
env-var |
VERCEL_PLUGIN_SEEN_SKILLS only |
Fallback if tmpdir is unavailable |
memory-only |
In-memory set for single invocation | Fallback if env file is unavailable |
disabled |
No dedup | When VERCEL_PLUGIN_HOOK_DEDUP=off |
Cleanup
session-end-cleanup.mjs deletes the claim directory, session files, pending launch dirs, and profile cache when the session ends. If the session crashes, the OS tmpdir cleanup eventually reclaims the files.
Prompt Signal Scoring
The UserPromptSubmit hook uses a scoring system to match user prompts to skills. Each skill's promptSignals frontmatter defines four signal types:
| Signal | Score | Behavior |
|---|---|---|
phrases |
+6 each | Exact substring match (case-insensitive). The primary signal. |
allOf |
+4 per group | All terms in a group must appear. For compound concepts like "deploy" + "preview". |
anyOf |
+1 each, capped at +2 | Optional boosters. Broad terms that add confidence. |
noneOf |
-Infinity | Hard suppress. If any term matches, the skill is excluded entirely. |
A skill is injected only if its total score meets minScore (default: 6). This means a single phrase match is enough, or an allOf group (+4) plus two anyOf matches (+2) = 6.
Additional Prompt Routing
- Troubleshooting intent classification: The prompt hook detects frustration/debug signals and routes to
investigation-mode+ a companion skill (workflow,agent-browser-verify, orvercel-cli). - Test framework suppression: When a prompt mentions test frameworks, verification-family skills are suppressed to avoid conflicting instructions.
- Investigation companion selection: When
investigation-modetriggers, the second slot goes to the best-scoring companion from a priority list.
Subagent Architecture
When the main agent spawns subagents, the plugin manages their skill context independently.
sequenceDiagram
participant Main as Main Agent
participant PreTool as pretooluse-subagent-spawn-observe
participant State as Subagent State (tmpdir)
participant Bootstrap as subagent-start-bootstrap
participant Sub as Subagent
participant Sync as subagent-stop-sync
participant Ledger as Subagent Ledger (JSONL)
Main->>PreTool: Agent tool call (description, prompt, type)
PreTool->>State: appendPendingLaunch(sessionId, payload)
Note over State: Records description, prompt, subagent_type
Main->>Sub: Spawn subagent
Sub->>Bootstrap: SubagentStart event
Bootstrap->>State: claimPendingLaunch(sessionId, agentType)
Bootstrap->>Bootstrap: Resolve budget category
Note over Bootstrap: Explore=1KB, Plan=3KB, general=8KB
Bootstrap->>Sub: additionalContext (profiler + skills)
Sub->>Sub: Works on task...
Sub->>Sync: SubagentStop event
Sync->>Ledger: Append JSONL record
Note over Ledger: timestamp, agent_id, agent_type, transcript_path
Budget Categories
Subagent context is sized by agent type to avoid wasting context on lightweight agents:
| Agent Type | Budget | Content |
|---|---|---|
Explore |
1KB (minimal) | Project profile + skill names only |
Plan |
3KB (light) | Profile + skill summaries + deployment constraints |
general-purpose |
8KB (standard) | Profile + full skill bodies |
| Other/custom | 8KB (standard) | Treated as general-purpose |
Skill Structure
The plugin ships 46 skills in skills/<name>/SKILL.md. Each skill is a self-contained markdown document with YAML frontmatter that declares its triggers and metadata:
---
name: skill-slug
description: "One-line description"
summary: "Brief fallback injected when budget is exceeded"
metadata:
priority: 6 # Base priority (4-8 range)
pathPatterns: ["**/*.prisma"] # File glob triggers
bashPatterns: ["prisma\\s"] # Bash command regex triggers
importPatterns: ["@prisma/client"] # Import/require triggers
promptSignals:
phrases: ["prisma schema"] # +6 each
allOf: [["database", "orm"]] # +4 per group
anyOf: ["migration"] # +1 each (cap +2)
noneOf: ["mongodb"] # Hard exclude
minScore: 6
validate:
- pattern: "executeRaw\\("
message: "Use $queryRaw for type safety"
severity: "warn"
skipIfFileContains: "\\$queryRaw"
---
# Skill Title
Markdown body injected as additionalContext...
Manifest
generated/skill-manifest.json is built by scripts/build-manifest.ts from all SKILL.md frontmatter. It pre-compiles glob patterns to regex at build time so hooks don't parse YAML or convert globs at runtime.
The manifest uses a version 2 paired-array format: pathPatterns[i] corresponds to pathRegexSources[i], ensuring globs and their compiled regex stay aligned.
Hooks prefer the manifest over scanning SKILL.md files directly. Run bun run build:manifest to regenerate after changing any skill frontmatter.
YAML Parser Semantics
The plugin uses a custom inline parseSimpleYaml (in skill-map-frontmatter.mjs), not the js-yaml library. This has intentional behavioral differences:
| Input | js-yaml | vercel-plugin parser | Rationale |
|---|---|---|---|
Bare null |
JavaScript null |
String "null" |
Skill frontmatter values should always be strings for pattern matching |
Bare true / false |
JavaScript boolean | String "true" / "false" |
Same reason — no type coercion |
Unclosed [ |
Parse error | Scalar string (no error) | Graceful degradation for malformed arrays |
| Tab indentation | Allowed | Explicit error | Prevents hard-to-debug YAML whitespace issues |
These choices are deliberate. The parser is optimized for the narrow use case of skill frontmatter where all values are ultimately used as string patterns or display text.
Template Include Engine
Agents and commands derive their instructions from skills via .md.tmpl templates. This keeps skills as the single source of truth — no copy-pasting skill content into agent definitions.
flowchart LR
SKILL["skills/nextjs/SKILL.md<br/>(source of truth)"]
TMPL["agents/ai-architect.md.tmpl<br/>(template with include markers)"]
BUILD["bun run build:from-skills"]
OUTPUT["agents/ai-architect.md<br/>(generated, committed)"]
SKILL --> BUILD
TMPL --> BUILD
BUILD --> OUTPUT
Two include formats:
{{include:skill:<name>:<heading>}} — extracts a section by heading
{{include:skill:<name>:frontmatter:<field>}} — extracts a frontmatter value
Build: bun run build:from-skills resolves includes and writes output files. 8 templates currently exist across agents/ and commands/.
Check: bun run build:from-skills:check verifies outputs are up-to-date (exits non-zero on drift).
Build Pipeline
flowchart LR
SRC["hooks/src/*.mts<br/>(TypeScript source)"]
HOOKS["hooks/*.mjs<br/>(compiled ESM)"]
SKILLS["skills/*/SKILL.md<br/>(skill definitions)"]
MANIFEST["generated/skill-manifest.json"]
TMPLS["*.md.tmpl<br/>(templates)"]
AGENTS["agents/*.md + commands/*.md<br/>(generated)"]
SRC -->|"bun run build:hooks<br/>(tsup)"| HOOKS
SKILLS -->|"bun run build:manifest"| MANIFEST
SKILLS -->|"bun run build:from-skills"| AGENTS
TMPLS -->|"bun run build:from-skills"| AGENTS
All three steps are combined in bun run build. A pre-commit hook auto-compiles .mts files when staged.
Environment Variables
| Variable | Default | Source (Writer) | Reader(s) | Description |
|---|---|---|---|---|
VERCEL_PLUGIN_LOG_LEVEL |
off |
User / shell | logger.mts |
Logging verbosity: off / summary / debug / trace |
VERCEL_PLUGIN_DEBUG |
— | User / shell | logger.mts |
Legacy: 1 maps to debug level |
VERCEL_PLUGIN_HOOK_DEBUG |
— | User / shell | logger.mts |
Legacy: 1 maps to debug level |
VERCEL_PLUGIN_SEEN_SKILLS |
"" |
session-start-seen-skills |
pretooluse-skill-inject, user-prompt-submit-skill-inject |
Comma-delimited list of already-injected skills |
VERCEL_PLUGIN_HOOK_DEDUP |
— | User / shell | pretooluse-skill-inject, user-prompt-submit-skill-inject, prompt-analysis |
Set to off to disable dedup entirely |
VERCEL_PLUGIN_LIKELY_SKILLS |
— | session-start-profiler |
pretooluse-skill-inject, subagent-start-bootstrap |
Comma-delimited profiler-detected skills (+5 boost) |
VERCEL_PLUGIN_GREENFIELD |
— | session-start-profiler |
inject-claude-md |
true when profiler detects an empty project |
VERCEL_PLUGIN_SETUP_MODE |
— | session-start-profiler |
pretooluse-skill-inject |
1 when bootstrap hints >= 3 (+50 priority boost) |
VERCEL_PLUGIN_BOOTSTRAP_HINTS |
— | session-start-profiler |
— | Comma-delimited bootstrap signal names |
VERCEL_PLUGIN_RESOURCE_HINTS |
— | session-start-profiler |
— | Comma-delimited resource category names |
VERCEL_PLUGIN_AGENT_BROWSER_AVAILABLE |
— | session-start-profiler |
pretooluse-skill-inject |
1 if agent-browser CLI is on PATH |
VERCEL_PLUGIN_INJECTION_BUDGET |
18000 |
User / shell | pretooluse-skill-inject |
PreToolUse byte budget |
VERCEL_PLUGIN_PROMPT_INJECTION_BUDGET |
8000 |
User / shell | user-prompt-submit-skill-inject |
UserPromptSubmit byte budget |
VERCEL_PLUGIN_REVIEW_THRESHOLD |
3 |
User / shell | pretooluse-skill-inject |
TSX edits before react-best-practices injection |
VERCEL_PLUGIN_TSX_EDIT_COUNT |
0 |
pretooluse-skill-inject |
pretooluse-skill-inject |
Current .tsx edit count |
VERCEL_PLUGIN_DEV_VERIFY_COUNT |
0 |
pretooluse-skill-inject |
pretooluse-skill-inject |
Dev server verification event count |
VERCEL_PLUGIN_DEV_COMMAND |
— | pretooluse-skill-inject |
pretooluse-skill-inject |
Detected dev server command |
VERCEL_PLUGIN_VALIDATED_FILES |
— | posttooluse-validate |
posttooluse-validate |
Comma-delimited path:hash pairs of validated files |
VERCEL_PLUGIN_RECENT_EDITS |
— | pretooluse-skill-inject |
posttooluse-verification-observe |
Comma-delimited recent file edit paths |
VERCEL_PLUGIN_AUDIT_LOG_FILE |
— | User / shell | hook-env |
Audit log file path, or off to disable |
VERCEL_PLUGIN_LEXICAL_RESULT_MIN_SCORE |
5.0 |
User / shell | lexical-index |
Minimum score for lexical fallback results |
CLAUDE_ENV_FILE |
— | Claude Code | All hooks | Path to env file for persisting vars across hook invocations |
CLAUDE_PLUGIN_ROOT |
— | Claude Code | All hooks | Root directory of the plugin installation |
CLAUDE_PROJECT_ROOT |
— | Claude Code | session-start-profiler |
Root directory of the user's project |
SESSION_ID |
— | Claude Code | Multiple hooks | Fallback session ID from Claude Code |
User Stories
"I'm building a Next.js app with Prisma"
- Session starts — The profiler scans
package.json, findsnextand@prisma/client-> setsVERCEL_PLUGIN_LIKELY_SKILLS=nextjs,vercel-storage. - Developer opens
schema.prisma— PreToolUse matches**/*.prismaglob -> injects thevercel-storageskill with Prisma best practices. - Developer edits
app/page.tsx— PreToolUse matches.tsxpath -> TSX edit counter increments. After 3 edits,react-best-practicesis injected. - Developer writes to
schema.prisma— PostToolUse validate runs rules from the skill, catching unsafeexecuteRawusage. - Developer asks "how do I deploy to preview?" — UserPromptSubmit scores against
promptSignalsand injects thedeployments-cicdskill.
"I'm starting a brand new project"
- Session starts — The profiler finds no
package.json, no config files -> setsVERCEL_PLUGIN_GREENFIELD=trueandVERCEL_PLUGIN_SETUP_MODE=1. - inject-claude-md outputs greenfield execution mode instructions: skip planning, choose defaults immediately, start executing.
- Developer asks "bootstrap a Next.js app with auth" — UserPromptSubmit matches phrases from
bootstrapandauthskills -> both are injected (within the 2-skill / 8KB budget). - Developer runs
npx create-next-app— PreToolUse matches the bash pattern -> injectsnextjsskill with setup mode boost (+50).
"I'm debugging a slow API route"
- Developer opens
app/api/data/route.ts— PreToolUse matches the path -> injectsvercel-functionsskill. - Developer asks "why is my API slow?" — UserPromptSubmit matches
observabilityskill phrases -> injects it alongside function guidance. - Developer runs
vercel logs— PreToolUse matches the bash pattern -> injectsvercel-cliskill (if not already seen, per dedup). - Developer runs
curl localhost:3000/api/data— PostToolUse verification observer classifies this as aclientRequestboundary and emits a structured log event.
"Agent spawns a research subagent"
- Developer triggers a complex task — Main agent decides to spawn an
Exploresubagent. - PreToolUse (Agent matcher) —
pretooluse-subagent-spawn-observerecords the pending launch with description and prompt text. - SubagentStart —
subagent-start-bootstrapreads the pending launch, runs prompt signal matching against the subagent's description, and injects a 1KB minimal context (Explore budget). - Subagent completes —
subagent-stop-syncwrites a JSONL ledger entry with agent metadata and transcript path.
Source Code Map
hooks/
├── hooks.json # Hook registry (lifecycle -> matcher -> command)
├── src/
│ ├── hook-env.mts # Shared runtime helpers (env, paths, file I/O)
│ ├── logger.mts # Structured JSON logging (off/summary/debug/trace)
│ ├── skill-map-frontmatter.mts # Custom YAML parser + buildSkillMap()
│ ├── patterns.mts # Glob->regex, ranking, atomic claims, seen-skills
│ ├── prompt-patterns.mts # Prompt signal compiler + scorer
│ ├── prompt-analysis.mts # Dry-run analysis reports for prompt matching
│ ├── vercel-config.mts # vercel.json key->skill routing (+-10 priority)
│ ├── unified-ranker.mts # Combined ranking across all signal types
│ ├── lexical-index.mts # Lexical fallback scoring for unmatched prompts
│ ├── stemmer.mts # Word stemming for lexical matching
│ ├── shared-contractions.mts # Contraction expansion for text normalization
│ ├── subagent-state.mts # Subagent pending launch state management
│ ├── session-start-seen-skills.mts # Hook: initialize dedup env var
│ ├── session-start-profiler.mts # Hook: profile project -> set LIKELY_SKILLS
│ ├── inject-claude-md.mts # Hook: inject vercel.md ecosystem graph
│ ├── pretooluse-skill-inject.mts # Hook: main injection engine
│ ├── pretooluse-subagent-spawn-observe.mts # Hook: record pending subagent launches
│ ├── user-prompt-submit-skill-inject.mts # Hook: prompt signal scoring + injection
│ ├── posttooluse-validate.mts # Hook: skill validation rules
│ ├── posttooluse-verification-observe.mts # Hook: verification boundary observer
│ ├── subagent-start-bootstrap.mts # Hook: bootstrap subagent context
│ ├── subagent-stop-sync.mts # Hook: write ledger, sync dedup
│ └── session-end-cleanup.mts # Hook: delete temp files
├── posttooluse-shadcn-font-fix.mjs # Standalone hook (no .mts source)
├── *.mjs # Compiled output (committed, ESM)
skills/
├── <name>/SKILL.md # 46 skill definitions with YAML frontmatter
generated/
├── skill-manifest.json # Pre-compiled manifest (globs -> regex)
├── build-from-skills.manifest.json # Template include build manifest
scripts/
├── build-manifest.ts # Manifest builder
├── build-from-skills.ts # Template include engine
src/cli/
├── explain.ts # `vercel-plugin explain` command
├── doctor.ts # `vercel-plugin doctor` command
CLI Tools
vercel-plugin explain <target>
Shows which skills match a file path or bash command, with priority breakdown and budget simulation.
# Explain what fires for a file
vercel-plugin explain app/api/auth/route.ts
# Explain what fires for a bash command
vercel-plugin explain "vercel deploy --prod"
# JSON output with budget simulation
vercel-plugin explain app/page.tsx --json --budget 8000
vercel-plugin doctor
Self-diagnosis: validates manifest parity, checks hook timeout risk, tests dedup correctness, and reports skill map errors.
vercel-plugin doctor