
Plugin Audit
- 1.1k installs
- 229 repo stars
- Updated July 27, 2026
- vercel-labs/vercel-plugin
plugin-audit provides documented workflows for Audit vercel-plugin performance on real-world projects. Extracts tool calls from Claude Code conversation logs, tests hook matching against actual inputs, ident
About
The plugin-audit skill audit vercel-plugin performance on real-world projects. Extracts tool calls from Claude Code conversation logs, tests hook matching against actual inputs, identifies pattern coverage gaps, and checks plugin cache staleness. Use when asked to audit, test, or investigate plugin skill injection on a real project. # Plugin Audit Audit how well vercel-plugin skill injection performs on real-world Claude Code sessions. Locate conversation logs Find JSONL conversation logs for a target project: ```bash ls -lt ~/.claude/projects/-Users-*-<project-name>/*.jsonl ``` The path uses the project's absolute path with slashes replaced by hyphens and a leading hyphen. Extract tool calls Parse the JSONL log to extract all tool_use entries. Each line is a JSON object with `message.content[]` containing `type: "tool_use"` blocks. Extract `name` and `input` fields. Group by tool type (Bash, Read, Write, Edit). Test hook matching Use the exported pipeline functions directly - do NOT shell out to the hook script for each test.
- **Path pattern gaps**: Files that should trigger a skill but don't (e.g., `src/db/schema.ts` not matching `vercel-storag
- **Bash pattern gaps**: Commands that should trigger but don't (e.g., missing package manager variants)
- **Dedup masking**: Skills that matched but were deduped before injection
- **Budget/cap drops**: Skills matched but dropped by the 12KB budget or 3-skill ceiling
- **Session summary**: Project, date, tool call count, model
Plugin Audit by the numbers
- 1,116 all-time installs (skills.sh)
- Ranked #940 of 16,659 AI & Agent Building skills by installs in the Skillselion catalog
- Security screen: HIGH risk (skills.sh audit)
- Data as of Jul 28, 2026 (Skillselion catalog sync)
plugin-audit capabilities & compatibility
- Capabilities
- **path pattern gaps**: files that should trigger · **bash pattern gaps**: commands that should trig · **dedup masking**: skills that matched but were · **budget/cap drops**: skills matched but dropped · **session summary**: project, date, tool call co
- Use cases
- documentation
What plugin-audit says it does
# Plugin Audit Audit how well vercel-plugin skill injection performs on real-world Claude Code sessions.
Extract tool calls Parse the JSONL log to extract all tool_use entries.
npx skills add https://github.com/vercel-labs/vercel-plugin --skill plugin-auditAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 1.1k |
|---|---|
| repo stars | ★ 229 |
| Security audit | 2 / 3 scanners passed |
| Last updated | July 27, 2026 |
| Repository | vercel-labs/vercel-plugin ↗ |
How do I use plugin-audit for the task described in its SKILL.md triggers?
Audit vercel-plugin performance on real-world projects. Extracts tool calls from Claude Code conversation logs, tests hook matching against actual inputs, identifies pattern coverage gaps, and checks.
Who is it for?
Teams invoking plugin-audit when the user request matches documented triggers and prerequisites.
Skip if: Skip when cached docs are missing, the request is a negative trigger, or another sibling skill owns the workflow.
When should I use this skill?
Audit vercel-plugin performance on real-world projects. Extracts tool calls from Claude Code conversation logs, tests hook matching against actual inputs, identifies pattern coverage gaps, and checks plugin cache stalene
What you get
Step-by-step guidance grounded in plugin-audit documentation and reference files.
- Hook coverage gap report
- Tool-call extraction summary
- Cache staleness findings
By the numbers
- Workflow steps: locate JSONL logs, extract tool calls, replay hook matcher, report gaps
Files
Plugin Audit
Audit how well vercel-plugin skill injection performs on real-world Claude Code sessions.
Workflow
1. Locate conversation logs
Find JSONL conversation logs for a target project:
ls -lt ~/.claude/projects/-Users-*-<project-name>/*.jsonlThe path uses the project's absolute path with slashes replaced by hyphens and a leading hyphen.
2. Extract tool calls
Parse the JSONL log to extract all tool_use entries. Each line is a JSON object with message.content[] containing type: "tool_use" blocks. Extract name and input fields. Group by tool type (Bash, Read, Write, Edit).
3. Test hook matching
Use the exported pipeline functions directly — do NOT shell out to the hook script for each test. Import from the hooks directory:
import { loadSkills, matchSkills } from "./hooks/pretooluse-skill-inject.mjs";
import { createLogger } from "./hooks/logger.mjs";Call loadSkills() once, then matchSkills(toolName, toolInput, compiledSkills) for each tool call. This is fast and gives exact match results.
4. Identify gaps
Compare matched skills against what SHOULD have matched based on the project's technology stack. Common gap categories:
- Path pattern gaps: Files that should trigger a skill but don't (e.g.,
src/db/schema.tsnot matchingvercel-storage) - Bash pattern gaps: Commands that should trigger but don't (e.g., missing package manager variants)
- Dedup masking: Skills that matched but were deduped before injection
- Budget/cap drops: Skills matched but dropped by the 12KB budget or 3-skill ceiling
5. Check plugin cache staleness
Compare the installed plugin cache against the dev version:
# Cache location
~/.claude/plugins/cache/vercel-labs-vercel-plugin/vercel-plugin/<version>/
# Compare skill content
diff <(grep 'pattern' skills/<skill>/SKILL.md) <(grep 'pattern' ~/.claude/plugins/cache/.../skills/<skill>/SKILL.md)Check ~/.claude/plugins/installed_plugins.json for version and git SHA.
Report Format
Produce a structured report with:
1. Session summary: Project, date, tool call count, model 2. Match matrix: Table of tool calls × matched skills (with match type) 3. Coverage gaps: Unmatched tool calls that should have matched, with suggested pattern additions 4. Dedup timeline: Order of skill injections and what got deduped 5. Cache status: Whether installed version matches dev, with specific diffs
References
- Log format details
- Test script for batch matching
Conversation Log Format
Claude Code stores conversation logs as JSONL files at:
~/.claude/projects/<encoded-project-path>/<session-id>.jsonlThe encoded path replaces / with - and prepends a -. Example: /Users/john/dev/my-app → -Users-john-dev-my-app
JSONL structure
Each line is a JSON object with:
{
"type": "assistant" | "user" | "system",
"message": {
"role": "assistant",
"content": [
{ "type": "tool_use", "name": "Bash", "input": { "command": "..." } },
{ "type": "tool_use", "name": "Read", "input": { "file_path": "..." } },
{ "type": "tool_use", "name": "Write", "input": { "file_path": "..." } },
{ "type": "tool_use", "name": "Edit", "input": { "file_path": "..." } }
]
},
"timestamp": "ISO-8601"
}What's NOT in the log
- Hook return payloads (
hookSpecificOutput) are NOT recorded in JSONL - Only
hook_progressevents appear, showing that a hook was invoked - To verify skill injection, test the hook directly against extracted tool inputs
Extracting tool calls
Parse each line, check message.content for arrays containing type: "tool_use", extract name and input. Filter for supported tools: Read, Edit, Write, Bash.
Hook progress events
Look for lines containing hook_progress to see which hooks fired:
{
"hookEvent": "PreToolUse",
"hookName": "PreToolUse:Bash",
"command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/pretooluse-skill-inject.mjs\""
}Subagent tool calls
Sessions using TeamCreate spawn subagents that run in worktree isolation. Their tool calls appear in the same log but may have different cwd values. Track cwd to distinguish main agent from subagents.
#!/usr/bin/env node
/**
* batch-match.mjs — Test vercel-plugin hook matching against a JSONL conversation log.
*
* Usage:
* node .claude/skills/plugin-audit/scripts/batch-match.mjs <path-to-jsonl> [--cache]
*
* Options:
* --cache Use the installed plugin cache instead of the dev hooks
*
* Output: Structured report of matches, gaps, and dedup timeline.
*/
import { readFileSync, existsSync } from "node:fs";
import { resolve, join } from "node:path";
// Resolve plugin root (dev or cache)
const useCache = process.argv.includes("--cache");
// Script lives at .claude/skills/plugin-audit/scripts/ — 4 levels up to project root
const DEV_ROOT = resolve(import.meta.dirname, "../../../..");
const CACHE_ROOT = (() => {
try {
const installed = JSON.parse(
readFileSync(resolve(process.env.HOME, ".claude/plugins/installed_plugins.json"), "utf-8")
);
const entries = installed?.plugins?.["vercel-plugin@vercel-labs-vercel-plugin"] || [];
// Find the highest-version install
const sorted = entries.slice().sort((a, b) => (b.version || "").localeCompare(a.version || ""));
if (sorted.length > 0) return sorted[0].installPath;
} catch {}
return null;
})();
const PLUGIN_ROOT = useCache && CACHE_ROOT ? CACHE_ROOT : DEV_ROOT;
// Dynamic import from the resolved plugin root
const { loadSkills, matchSkills } = await import(join(PLUGIN_ROOT, "hooks/pretooluse-skill-inject.mjs"));
const { createLogger } = await import(join(PLUGIN_ROOT, "hooks/logger.mjs"));
const log = createLogger();
// ---- Parse arguments ----
const jsonlPath = process.argv.find((a) => a.endsWith(".jsonl"));
if (!jsonlPath) {
console.error("Usage: batch-match.mjs <path-to-jsonl> [--cache]");
process.exit(1);
}
if (!existsSync(jsonlPath)) {
console.error(`File not found: ${jsonlPath}`);
process.exit(1);
}
// ---- Extract tool calls from JSONL ----
const SUPPORTED_TOOLS = new Set(["Read", "Edit", "Write", "Bash"]);
function extractToolCalls(jsonlPath) {
const lines = readFileSync(jsonlPath, "utf-8").split("\n").filter(Boolean);
const toolCalls = [];
for (const line of lines) {
try {
const entry = JSON.parse(line);
const content = entry?.message?.content;
if (!Array.isArray(content)) continue;
for (const block of content) {
if (block.type === "tool_use" && SUPPORTED_TOOLS.has(block.name)) {
toolCalls.push({
tool_name: block.name,
tool_input: block.input || {},
timestamp: entry.timestamp,
cwd: entry.cwd,
});
}
}
} catch {
// Skip unparseable lines
}
}
return toolCalls;
}
// ---- Run matching ----
const toolCalls = extractToolCalls(jsonlPath);
console.log(`\n📋 Extracted ${toolCalls.length} tool calls from log\n`);
const skills = loadSkills(PLUGIN_ROOT, log);
if (!skills) {
console.error("FATAL: Could not load skills from", PLUGIN_ROOT);
process.exit(1);
}
console.log(`🔧 Loaded ${Object.keys(skills.skillMap).length} skills from ${useCache ? "CACHE" : "DEV"}\n`);
// Track dedup timeline
const seenSkills = new Set();
const dedupTimeline = [];
const matchMatrix = [];
let matchedCount = 0;
let unmatchedCount = 0;
for (const tc of toolCalls) {
const result = matchSkills(tc.tool_name, tc.tool_input, skills.compiledSkills, log);
const matched = result ? [...result.matched] : [];
const target =
tc.tool_name === "Bash"
? (tc.tool_input.command || "").slice(0, 100)
: tc.tool_input.file_path || "";
const newSkills = matched.filter((s) => !seenSkills.has(s));
const dedupedSkills = matched.filter((s) => seenSkills.has(s));
for (const s of newSkills) seenSkills.add(s);
if (newSkills.length > 0) {
dedupTimeline.push({
tool: tc.tool_name,
target: target.slice(0, 80),
injected: newSkills,
deduped: dedupedSkills,
});
}
matchMatrix.push({
tool: tc.tool_name,
target,
matched,
reasons: result?.matchReasons || {},
});
if (matched.length > 0) matchedCount++;
else unmatchedCount++;
}
// ---- Output Report ----
console.log("═══════════════════════════════════════════════════════");
console.log(" MATCH MATRIX");
console.log("═══════════════════════════════════════════════════════\n");
for (const entry of matchMatrix) {
const status = entry.matched.length > 0 ? "✅" : " ";
const skills = entry.matched.length > 0 ? entry.matched.join(", ") : "—";
const shortTarget = entry.target.length > 70 ? entry.target.slice(0, 70) + "…" : entry.target;
console.log(`${status} ${entry.tool.padEnd(5)} │ ${shortTarget}`);
if (entry.matched.length > 0) {
console.log(` → ${skills}`);
}
}
console.log(`\n📊 ${matchedCount} matched, ${unmatchedCount} unmatched out of ${toolCalls.length} total\n`);
console.log("═══════════════════════════════════════════════════════");
console.log(" DEDUP TIMELINE (injection order)");
console.log("═══════════════════════════════════════════════════════\n");
for (let i = 0; i < dedupTimeline.length; i++) {
const d = dedupTimeline[i];
console.log(`${i + 1}. ${d.tool} │ ${d.target}`);
console.log(` injected: ${d.injected.join(", ")}`);
if (d.deduped.length > 0) {
console.log(` deduped: ${d.deduped.join(", ")}`);
}
}
console.log(`\n🔑 ${seenSkills.size} unique skills would be injected: ${[...seenSkills].join(", ")}\n`);
// ---- Unmatched analysis ----
console.log("═══════════════════════════════════════════════════════");
console.log(" UNMATCHED TOOL CALLS");
console.log("═══════════════════════════════════════════════════════\n");
const unmatched = matchMatrix.filter((e) => e.matched.length === 0);
for (const entry of unmatched) {
const shortTarget = entry.target.length > 80 ? entry.target.slice(0, 80) + "…" : entry.target;
console.log(` ${entry.tool.padEnd(5)} │ ${shortTarget}`);
}
console.log(`\n💡 Review unmatched calls above for potential pattern additions.\n`);
Related skills
How it compares
Pick plugin-audit for retrospective JSONL coverage analysis; pick benchmark-agents for forward interactive eval scenarios on new Vercel features.
FAQ
What does plugin-audit do?
Audit vercel-plugin performance on real-world projects. Extracts tool calls from Claude Code conversation logs, tests hook matching against actual inputs, identifies pattern coverage gaps, and checks plugin cache stalene
When should I use plugin-audit?
Audit vercel-plugin performance on real-world projects. Extracts tool calls from Claude Code conversation logs, tests hook matching against actual inputs, identifies pattern coverage gaps, and checks plugin cache stalene
What are common prerequisites?
--- name: plugin-audit description: Audit vercel-plugin performance on real-world projects.
Is Plugin Audit safe to install?
skills.sh reports 2 of 3 security scanners passed. Review the Security Audits panel on this page before installing in production.