New ai-generation-persistence skill (priority 6) injects guidance for treating
AI generations as first-class persistent resources — unique IDs, addressable
URLs, database/Blob storage, cost tracking, and generate-then-redirect UX
patterns. Triggers on AI SDK imports and broad prompt signals.
New verification skill added. Updated ecosystem graph, catalog, manifest,
fixtures, and snapshots.
Refine SKILL.md content across 29 skills based on live eval findings from 16
benchmark sessions. Add .claude/skills/vercel-plugin-eval/ for running real-world
plugin eval sessions with debug log monitoring. Update posttooluse-validate and
validate-rules tests to match refined skill content.
Parallel PreToolUse hooks were racing on the same seen-skills file, causing 2-6x
duplicate injections. UserPromptSubmit hook didn't share dedup state at all.
Fix by implementing atomic per-skill claim files (Oracle session: fix-dedup-race-condition-plan):
1. In hooks/src/hook-env.mts, add tryClaimSessionKey(sessionId, kind, key) using
openSync(path, 'wx') for atomic exclusive create. Add listSessionKeys() to read
claim dir, syncSessionFileFromClaims() to derive the txt snapshot, and
removeSessionClaimDir() for cleanup. Claim files live in
<tmpdir>/vercel-plugin-<sessionId>-seen-skills.d/<skill-name>.
2. In hooks/src/patterns.mts, add mergeSeenSkillStates(...values) to union env var,
session file, and claim dir state. Add serializeSeenSkills(set) helper.
3. In hooks/src/pretooluse-skill-inject.mts, load merged state from env+file+claims.
Before injecting each skill, call tryClaimSessionKey — first process wins, others
get EEXIST and skip. Use same claim path for once-only keys (agent-browser warning,
vercel-env-help). Pass sessionId through InjectOptions.
4. In hooks/src/user-prompt-submit-skill-inject.mts, preserve sessionId from parsed
input, load merged seen state, pass sessionId to injection flow so it uses the
shared claim backend. Skills injected via promptSignals now visible to PreToolUse.
5. In hooks/src/session-end-cleanup.mts, add rmSync deletion of claim directories.
6. Update CLAUDE.md dedup contract to document atomic claim mechanism.
7. Bump .plugin/plugin.json to 0.6.0, rebuild.
The 0.5.4 release added session-scoped temp files for dedup persistence, but this
was wrong — CLAUDE_ENV_FILE env vars ARE propagated to hook subprocesses by Claude
Code. The temp file approach littered tmpdir with hundreds of small files.
Revert to the original env-var-only mechanism:
1. Remove readSessionFile/writeSessionFile/dedupFilePath from hooks/src/hook-env.mts
2. Remove all writeSessionFile calls from pretooluse-skill-inject.mts — revert to
pure process.env.VERCEL_PLUGIN_SEEN_SKILLS = appendSeenSkill(...) pattern
3. Remove sessionId from InjectOptions and the injectSkills call site
4. Remove sessionId from posttooluse-validate.mts ParsedInput — revert
isAlreadyValidated/markValidated to pure env-var signatures
5. Revert all test expectations back to "env-var" and "memory-only" strategies
6. Restore CLAUDE.md dedup contract to env-var-only documentation
Skill dedup is completely broken because CLAUDE_ENV_FILE vars are only available to Bash
tool commands, not hook subprocesses. Each PreToolUse invocation starts a fresh Node.js
process that never sees VERCEL_PLUGIN_SEEN_SKILLS.
To fix this:
1. In hooks/src/hook-env.mts, add readSessionFile(sessionId, kind) and
writeSessionFile(sessionId, kind, value) helpers that read/write
/tmp/vercel-plugin-<sessionId>-<kind>.txt for persistent dedup state.
2. In hooks/src/pretooluse-skill-inject.mts, read seen skills from the session file
at startup using readSessionFile(sessionId, "seen-skills"). Add a persistSeen()
closure inside injectSkills() that calls both appendSeenSkill and writeSessionFile.
Replace all process.env.VERCEL_PLUGIN_SEEN_SKILLS = appendSeenSkill(...) calls with
persistSeen() or inline writeSessionFile calls. Add "file" as the primary dedup
strategy when session_id is present, falling back to "env-var" then "memory-only".
Pass sessionId through InjectOptions to injectSkills().
3. In hooks/src/posttooluse-validate.mts, add sessionId to ParsedInput and extract it
from input.session_id in parseInput(). Pass sessionId to isAlreadyValidated() and
markValidated(), which should read/write via readSessionFile/writeSessionFile for
the "validated-files" kind.
4. Update tests: pretooluse-skill-inject.test.ts — rename "no env var" test to expect
file-based dedup persistence, update strategy assertions from "env-var" to "file",
use omitSessionId for memory-only test. subagent-fresh-env.test.ts — use a fresh
session ID for subagent calls, update strategy expectations to "file".
session-timeline-subagent.test.ts — add sessionId option to runHookEnv, use a
different session ID for the subagent call.
5. Update CLAUDE.md dedup contract section to document file-based mechanism.
6. Bump .plugin/plugin.json version to 0.5.4, run bun run build.
Hook source now lives in hooks/src/*.mts and compiles to hooks/*.mjs via tsc.
The formatOutput() return type is checked against SyncHookJSONOutput from
@anthropic-ai/claude-agent-sdk, preventing unknown fields in hookSpecificOutput
that Claude Code's strict Zod validation would silently reject.
Root cause: skillInjection metadata was a sibling key in hookSpecificOutput,
which Claude Code rejected as "Hook JSON output validation failed: Invalid input".
All skill injections were silently discarded. Now skillInjection metadata is
embedded as an HTML comment inside additionalContext.
- Add hooks/src/*.mts (TypeScript source for all 6 hook modules)
- Add hooks/tsconfig.json (strict, NodeNext, compiles .mts → .mjs)
- Add build:hooks script and pre-commit hook for auto-compilation
- Install @anthropic-ai/claude-agent-sdk as devDependency for types
- Bump plugin version to 0.4.0
- Add greenfield project detection to session-start profiler
- Add profiler boost (+5 priority) for likely skills in PreToolUse
- Remove zzz-test-redos-pattern test skill
- Update golden fixtures and snapshots
- Add agent-browser to ecosystem graph
- Add new golden fixture tests
Update build-manifest.ts to include bodyPath in each skill manifest entry pointing to the SKILL.md file. Update all SKILL.md frontmatter across all skills to ensure consistent formatting. Add vercel-config.mjs improvements for key-aware skill routing.
Add tests/build-skill-map.test.ts for skill map construction. Add golden fixture JSON files (golden-bash-cap-collision, golden-bash-next-dev, golden-edit-middleware, golden-read-env-local, golden-read-vercel-json, golden-vercel-json-crons, golden-vercel-json-rewrites) for deterministic match engine validation. Add tests/fuzz-glob.test.ts and tests/fuzz-yaml.test.ts for property-based testing of the glob-to-regex and YAML parsers.
Update snapshot files for injection output. Update validate.ts and validate.test.ts with command convention checks. Update HOOKS.md documentation. Update pretooluse-skill-inject.mjs, skill-map-frontmatter.mjs, and vercel-config.mjs hook modules. Bump plugin.json version from 0.2.0 to 0.3.0.
Extract glob-to-regex and seen-skills file utilities into hooks/patterns.mjs
for reuse across the PreToolUse hook and validation script. Add
validateSkillMap to skill-map-frontmatter.mjs. Expand plugin tags for
chat-sdk/bot variants. Comprehensive tests for all new modules.
These fields are for path overrides per the open-plugin spec, not
inventory lists. Components are discovered from the filesystem
automatically. Removing them fixes Claude Code validation errors.
Rules (.mdc files) aren't supported by Claude Code's plugin system — they
were silently ignored. Move the ecosystem knowledge graph and all three
convention sets (Next.js 16, AI SDK v6, Vercel platform) into CLAUDE.md
so they're always loaded into context.
Also updates repo references from anthropics/open-plugin to
vercel-labs/vercel-plugin.