Files
vercel__vercel-plugin/docs/developer-guide.md
John Lindquist c6e4beacf2 refactor: complete zero-bundle orchestrator
Align the orchestrator branch with the engine-first runtime so skill
resolution, registry metadata, and operator docs all describe the
same source of truth.

Make first-session registry installs resilient in restricted
environments by treating install-plan persistence as best-effort,
which prevents hook execution from failing when the home-state cache
cannot be written.

Ploop-Iter: 2
2026-04-02 05:27:10 -06:00

13 KiB

Developer Workflows & CLI Reference

This guide covers every build command, CLI tool, testing workflow, and development process in vercel-plugin.


Table of Contents


Build Pipeline

The project has two independent build stages that combine into a single bun run build:

graph TD
    subgraph "Stage 1 — Hook Compilation"
        MTS["hooks/src/*.mts<br/>(TypeScript sources)"]
        MTS -->|"tsup<br/>hooks/tsup.config.ts"| MJS["hooks/*.mjs<br/>(ESM, committed)"]
    end

    subgraph "Stage 2 — Manifest Generation"
        RULES["engine/*.md<br/>(40+ rule files, YAML frontmatter)"]
        RULES -->|"build-manifest.ts<br/>glob→regex pre-compile"| MANIFEST["generated/skill-rules.json<br/>(version 2, paired arrays)"]
    end

    MJS --> RUNTIME["Runtime: Claude Code<br/>hook execution"]
    MANIFEST --> RUNTIME

Data flow summary

  1. TypeScript hooks (hooks/src/*.mts) compile via tsup to ESM modules (hooks/*.mjs). Target: node20, no bundling, no sourcemaps. The compiled .mjs files are committed to the repo so the Claude Agent SDK can execute them directly.
  2. Engine rule frontmatter from 40+ engine/*.md files gets pre-compiled into generated/skill-rules.json with glob-to-regex conversion for fast runtime matching. The manifest uses a version 2 format with paired arrays (pathPatternspathRegexSources, bashPatternsbashRegexSources).

Agents and commands (agents/*.md, commands/*.md) are standalone files that do not require a build step.

Stage execution order

bun run build
  ├── bun run build:hooks        # Stage 1: .mts → .mjs
  └── bun run build:manifest     # Stage 2: engine/*.md → skill-rules.json

Both stages are independent and can run in any order, but build runs them sequentially for simplicity.


Build Commands

bun run build:hooks

Compiles all TypeScript hook sources to ESM.

Detail Value
Source hooks/src/*.mts
Output hooks/*.mjs
Tool tsup with hooks/tsup.config.ts
Target node20, no bundling, no sourcemaps

Run this after editing any .mts file. The pre-commit hook runs it automatically when .mts files are staged.

bun run build:manifest

Generates the skill rules manifest from engine rule frontmatter.

Detail Value
Script scripts/build-manifest.ts
Input engine/*.md (40+ rule files)
Output generated/skill-rules.json

The manifest pre-compiles glob patterns to regex at build time so runtime hooks avoid expensive parsing. Version 2 format with paired arrays (pathPatternspathRegexSources).

bun run build

Runs both stages sequentially:

bun run build:hooks && bun run build:manifest

bun run typecheck

Runs TypeScript type checking on hook sources without emitting files:

tsc -p hooks/tsconfig.json --noEmit

bun run doctor

Runs vercel-plugin doctor (see docs/cli-reference.md for full details). Self-diagnosis for the plugin setup.


Testing Architecture

Running tests

bun test                                    # Typecheck + all test files
bun test tests/<file>.test.ts               # Single test file
bun run test:update-snapshots               # Regenerate golden snapshots

bun test runs typecheck first (tsc -p hooks/tsconfig.json --noEmit), then all test files.

Test categories

The test suite is organized into functional categories:

graph LR
    subgraph "Hook Integration"
        A1["pretooluse-skill-inject"]
        A2["user-prompt-submit"]
        A3["posttooluse-validate"]
        A4["session-start-profiler"]
        A5["session-start-seen-skills"]
        A6["session-end-cleanup"]
    end

    subgraph "Subagent Lifecycle"
        B1["subagent-fresh-env"]
        B2["subagent-lifecycle-integration"]
        B3["subagent-start-context"]
        B4["subagent-start-bootstrap-routing"]
        B5["subagent-scope-dedup"]
        B6["subagent-state"]
        B7["session-timeline-subagent"]
    end

    subgraph "Pattern Matching"
        C1["patterns"]
        C2["fuzz-glob"]
        C3["fuzz-yaml"]
        C4["prompt-signals"]
        C5["prompt-analysis"]
        C6["prompt-patterns-lexical"]
        C7["unified-ranker"]
    end

    subgraph "Snapshots & Golden Files"
        D1["snapshot-runner"]
        D2["snapshots"]
    end

    subgraph "Validation & Skill Map"
        E1["validate"]
        E2["validate-rules"]
        E3["build-skill-map"]
        E4["skill-map-frontmatter"]
        E5["external-skill-resolution"]
    end

    subgraph "CLI & Infrastructure"
        G1["cli-explain"]
        G2["hooks-json-structural"]
        G3["doctor-subagent-hooks"]
        G4["hook-sync"]
        G5["logger"]
        G6["vercel-config"]
    end

    subgraph "Scenario & Specialized"
        H1["notion-clone-patterns"]
        H2["slack-clone-patterns"]
        H3["tsx-review-trigger"]
        H4["dev-server-verify"]
        H5["verification-skill"]
        H6["verification-logging"]
        H7["verification-intent-routing"]
        H8["redaction"]
        H9["stemmer"]
        H10["lexical-index"]
    end

    subgraph "Benchmark"
        I1["benchmark-pipeline"]
        I2["benchmark-analyze"]
    end

Hook integration tests

End-to-end tests for each hook entry point. They simulate Claude Agent SDK hook invocations with realistic tool input and verify the correct skills are injected, dedup state is maintained, and output conforms to SyncHookJSONOutput.

Test file Hook under test Key assertions
pretooluse-skill-inject PreToolUse Path/bash/import matching, priority ranking, budget enforcement, dedup
user-prompt-submit UserPromptSubmit Prompt signal scoring (phrases/allOf/anyOf/noneOf), 2-skill cap, 8KB budget
posttooluse-validate PostToolUse Validation rule matching, severity levels, skipIfFileContains
session-start-profiler SessionStart Config file scanning, dependency detection, greenfield mode
session-start-seen-skills SessionStart Env var initialization, claim dir creation
session-end-cleanup SessionEnd Temp file deletion, claim dir cleanup

Pattern matching tests

Unit tests for the matching and compilation layer. Cover glob-to-regex conversion, bash command regex, import pattern detection, YAML parsing edge cases, and prompt signal scoring.

Snapshot tests

Golden-file regression tests. snapshot-runner generates skill injection metadata for each vercel.json fixture and compares against committed baselines. Update with bun run test:update-snapshots.

Validation tests

Test the YAML frontmatter parser, skill map construction, structural validation rules, and external skill resolution. Exercises the custom parseSimpleYaml parser's intentional differences from js-yaml (bare null → string "null", bare booleans → strings, unclosed [ → scalar).

Build & template tests

Test the template include engine: marker regex matching, section extraction with nested headings, frontmatter field resolution, code block fence skipping, and full compilation pipeline.

Benchmark tests

Performance regression tests for the injection pipeline. benchmark-pipeline measures pattern compilation and matching latency; benchmark-analyze validates that results stay within acceptable bounds.

CLI tests

Tests for vercel-plugin explain covering target type detection (file vs bash), pattern matching output, priority calculations with profiler/vercel.json boosts, budget simulation, and collision detection.

Scenario tests

Real-world regression tests that simulate specific project types (Notion clone, Slack clone) to verify correct skill injection for realistic file and dependency combinations.


Pre-Commit Hook

The .git/hooks/pre-commit script automates hook compilation.

When any hooks/src/*.mts file is staged:

1. Typecheck: bun run typecheck
2. Compile:   bun run build:hooks
3. Stage:     git add hooks/*.mjs

Playground System

The playground generates static skill files for external AI coding tools. Lives in .playground/.

Structure

.playground/
├── generate-all.ts          # Unified CLI entry point
├── _shared/
│   ├── emitter.ts           # Context creation + skill flattening
│   ├── plugin-discovery.ts  # Discovers skills from plugin root
│   ├── skill-discovery.ts   # Skill data extraction
│   ├── types.ts             # Shared types (DiscoveredSkill, PluginManifest, etc.)
│   └── marker-patch.ts      # {{include:…}} marker resolution for external tools
├── codex-cli/generate.ts    # → .codex/ directory structure
├── cursor/generate.ts       # → .cursor/rules/
├── vscode-copilot/generate.ts   # → .github/copilot-instructions.md
├── opencode/generate.ts     # → .opencode/
├── gemini-cli/generate.ts   # → .gemini/commands/
├── gemini-code-assist/generate.ts  # → .gemini/skills/
├── _fixtures/               # Test plugins (full, minimal, collision, oversized, etc.)
└── _snapshots/              # Golden output snapshots

Running the generator

bun run playground:generate

Options:

Flag Description
--plugins <dir> Plugin root to discover skills from (default: .playground/_fixtures)
--out <dir> Output directory (default: .playground/_output)
--dry-run Preview without writing files
--target <name> Comma-separated generator names (e.g., cursor,codex-cli)

Supported generators: codex-cli, cursor, vscode-copilot, opencode, gemini-cli, gemini-code-assist

Workflow

sequenceDiagram
    participant Dev as Developer
    participant Gen as generate-all.ts
    participant Disc as plugin-discovery.ts
    participant Emit as Per-tool generator

    Dev->>Gen: bun run playground:generate
    Gen->>Disc: Discover plugins (skills, commands, agents, hooks)
    Disc-->>Gen: DiscoveredPlugin[]
    Gen->>Emit: Spawn generators in parallel (one per target tool)
    Emit-->>Gen: Generated files + stats
    Gen-->>Dev: JSON report (file counts, durations, skill counts)

Output is a JSON report with file counts, durations, and per-generator statistics.


Environment Variables

These variables control runtime behavior. Set them before running Claude Code or in tests.

Variable Default Description
VERCEL_PLUGIN_LOG_LEVEL off Logging verbosity: off, summary, debug, trace
VERCEL_PLUGIN_DEBUG Legacy: 1 maps to debug level
VERCEL_PLUGIN_SEEN_SKILLS "" Comma-delimited already-injected skill slugs
VERCEL_PLUGIN_HOOK_DEDUP Set to off to disable deduplication
VERCEL_PLUGIN_LIKELY_SKILLS Profiler-detected skills (comma-delimited, +5 boost)
VERCEL_PLUGIN_GREENFIELD true when project is empty (set by profiler)
VERCEL_PLUGIN_INJECTION_BUDGET 18000 PreToolUse byte budget
VERCEL_PLUGIN_PROMPT_INJECTION_BUDGET 8000 UserPromptSubmit byte budget
VERCEL_PLUGIN_REVIEW_THRESHOLD 3 TSX edits before react-best-practices injection
VERCEL_PLUGIN_TSX_EDIT_COUNT 0 Current .tsx edit count
VERCEL_PLUGIN_AUDIT_LOG_FILE Audit log path or off

Troubleshooting

Manifest parity errors from doctor

The generated/skill-rules.json is out of sync with live engine/*.md files.

Fix:

bun run build:manifest

Typecheck failures

Hook source uses TypeScript features that need compilation. The tsc target is hooks/tsconfig.json.

Fix:

bun run typecheck  # See errors
# Fix the .mts files, then:
bun run build:hooks

Hook timeout (5-second limit)

All PreToolUse, UserPromptSubmit, PostToolUse, SubagentStart, and SubagentStop hooks have a 5-second timeout. If you add many skills or patterns, doctor will warn you.

Diagnose:

bun run doctor

Mitigations:

  • Use the pre-built manifest (build:manifest) to avoid live YAML scanning
  • Consolidate low-priority skills
  • Increase pattern specificity to reduce false-positive matching

Dedup not working (skills injected twice)

Check: Is session-start-seen-skills.mjs running on SessionStart? Run doctor to verify.

Debug: Set VERCEL_PLUGIN_LOG_LEVEL=debug to see dedup strategy selection and claim attempts in stderr.

Pre-commit hook not running

Verify the hook exists and is executable:

ls -la .git/hooks/pre-commit
chmod +x .git/hooks/pre-commit

Playground generator fails

Ensure the plugin root has an engine/ directory with valid rule files:

bun run playground:generate --dry-run

The --dry-run flag previews without writing, showing discovery errors on stderr.

Tests fail after adding a new skill

After adding a new engine/<name>.md:

bun run build:manifest          # Update manifest
bun run test:update-snapshots   # Update golden snapshots
bun test                        # Verify everything passes