The pretool hook no longer folds VERCEL_PLUGIN_SEEN_SKILLS into
session-file dedup state or writes claim-sync results back into
process.env. File-backed dedup still claims and syncs session state,
while env-only dedup remains a fallback when no session scope exists.
Verified: bun run build:hooks
Verified: bun test tests/hook-sync.test.ts
Verified: bun test tests/pretooluse-skill-inject.test.ts --test-name-pattern "file-based dedup persists across invocations with same session_id|debug mode logs dedup strategy for file, memory-only, and disabled"
Verified: node --input-type=module <<'EOF' ... fresh cursor Write payload asserts no VERCEL_PLUGIN_SEEN_SKILLS env update and TSX counter update EOF
Verified: node --input-type=module <<'EOF' ... env-only Read payload without session_id returns {} when VERCEL_PLUGIN_SEEN_SKILLS=nextjs EOF
Swarm-Agent: codex-pretool-drop-env-merge
PreToolUse now persists TSX review and dev-server verify counters via session files,
and it no longer appends runtime env exports to CLAUDE_ENV_FILE for Claude.
This keeps seen-skills dedup on the claim/session-file path and avoids shell-init
side effects during hook execution.
Verified: bun run build:hooks
Verified: bun test tests/pretooluse-skill-inject.test.ts -t "file-based dedup persists across invocations with same session_id"
Verified: bun test tests/pretooluse-skill-inject.test.ts -t "empty VERCEL_PLUGIN_SEEN_SKILLS env var dedups across invocations"
Verified: custom node smoke test (tsx-migration-check) — CLAUDE_ENV_FILE unchanged, tsx-edit-count reset in session file, seen-skills persisted
Verified: custom node smoke test (dev-migration-check) — CLAUDE_ENV_FILE unchanged, dev-verify-count persisted in session file
Swarm-Agent: codex-pretooluse-env-cleanup
Detect platform via conversation_id/workspace_roots/cursor_version
fields in stdin JSON. Normalize sessionId, cwd, and output format:
- Cursor: flat { additional_context, env } output
- Claude Code: nested { hookSpecificOutput } output (unchanged)
Env var updates use JSON env field for Cursor instead of
CLAUDE_ENV_FILE. Includes platform-hook-compat test suite.
Finish the session-start profiler Cursor path without touching the profiling logic.
Platform detection now keys off hook input and CLAUDE_ENV_FILE, project root lookup falls back to CURSOR_PROJECT_DIR, and Cursor receives env plus additional_context JSON on stdout.
Verified: bun test hooks/session-start-profiler-platform.test.ts
Verified: bun test hooks/session-hooks-platform-compat.test.ts
Verified: zsh -lc 'tmpdir=/var/folders/c3/r013q3_93_s4zycmx0mdnt2h0000gn/T/tmp.p6JmjuMjls; printf "{\"conversation_id\":\"conv-123\",\"cursor_version\":\"1.0.0\"}" | env -u CLAUDE_ENV_FILE -u CLAUDE_PROJECT_ROOT CURSOR_PROJECT_DIR="" bun hooks/src/session-start-profiler.mts'
Swarm-Agent: codex-profiler-v2
Restore generated hook outputs that were accidentally committed when the
repo pre-commit hook rebuilt the entire hooks directory. This follow-up
keeps this agent's change set focused on the UserPromptSubmit source and
tests without touching the in-progress source files owned by other agents.
Verified: bun test hooks/user-prompt-submit-skill-inject.test.ts tests/user-prompt-submit.test.ts
Verified: restored generated outputs to their HEAD~1 content for six unrelated hook artifacts
Swarm-Agent: codex-prompt-hook
Normalize UserPromptSubmit input fields across Claude Code and Cursor,
including conversation/session IDs, cwd roots, and prompt/message
fallbacks. Cursor now returns flat beforeSubmitPrompt output with
continue:true and emits seen-skill env patches when no CLAUDE_ENV_FILE is
available, while Claude continues using hookSpecificOutput and appends
seen-skill exports to CLAUDE_ENV_FILE when present.
Verified: bun test hooks/user-prompt-submit-skill-inject.test.ts tests/user-prompt-submit.test.ts
Verified: cursor beforeSubmitPrompt payload returns continue:true + env patch in tests/user-prompt-submit.test.ts
Swarm-Agent: codex-prompt-hook
The local pre-commit hook rebuilt several hooks/*.mjs files from other agents' dirty source changes while I was committing the new compat layer. Restore the unrelated generated artifacts to their previous state so this task only carries the compat-layer files it owns.
Verified: bun test hooks/compat.test.ts
Swarm-Agent: codex-compat-layer
Add a shared Claude Code/Cursor compatibility module that normalizes hook input, formats platform-specific output, and persists session env state across both runtimes.
Verified: bun test hooks/compat.test.ts
Verified: bun x tsc --noEmit --module nodenext --moduleResolution nodenext --target es2022 --types node hooks/src/compat.mts
Swarm-Agent: codex-compat-layer
The previous commit unintentionally included this compiled hook artifact because the local pre-commit hook rebuilt and auto-staged hook bundles. This restores the generated file to its prior committed state without touching the dirty source file.
Verified: tmp=/var/folders/c3/r013q3_93_s4zycmx0mdnt2h0000gn/T/tmp.2y6bxG5m6P && git show HEAD^:hooks/pretooluse-skill-inject.mjs > "" && cmp -s hooks/pretooluse-skill-inject.mjs "" && rm ""
Swarm-Agent: codex-hook-chain
formatOutput now emits a single skill-upgrade instruction block per target skill, adds the requested blank line and reason text, and marks hard upgrades as REQUIRED so agents get clearer follow-up actions.
Verified: bun run build:hooks
Verified: bun test tests/posttooluse-validate.test.ts
Verified: bun run typecheck
Swarm-Agent: codex-hook-chain
The repo pre-commit hook rebuilt hook outputs and accidentally staged a generated pretooluse injector artifact from another in-progress source change. This commit restores that generated file to its pre-commit content so the skill-upgrade change stays isolated.
Verified: diff -u <(git show HEAD^:hooks/pretooluse-skill-inject.mjs) hooks/pretooluse-skill-inject.mjs
Swarm-Agent: codex-schema
Validate rules can now carry optional skill-upgrade metadata from SKILL.md frontmatter through the post-tool-use validator. When a matching rule requests an upgrade, the hook output now emits both human-readable Skill tool guidance and a machine-readable HTML comment for downstream parsing.
Verified: bun run typecheck
Verified: bun -e 'const mod = await import("./hooks/src/posttooluse-validate.mts"); const violations = mod.runValidation("foo\n", ["source-skill"], new Map([["source-skill", [{ pattern: "foo", message: "needs upgrade", severity: "error", upgradeToSkill: "target-skill", upgradeWhy: "more specific context" }]] ])); const out = JSON.parse(mod.formatOutput(violations, ["source-skill"], "/tmp/test.ts")); console.log(JSON.stringify(violations)); console.log(out.hookSpecificOutput.additionalContext);'
Verified: bun -e 'const mod = await import("./hooks/src/skill-map-frontmatter.mts"); const parsed = mod.parseSkillFrontmatter(`validate:\n -\n pattern: foo\n message: needs upgrade\n severity: error\n upgradeToSkill: target-skill\n upgradeWhy: use the more specific skill\n -\n pattern: bar\n message: hard upgrade\n severity: warn\n upgradeToSkill: hard-skill\n upgradeMode: hard\n`); console.log(JSON.stringify(parsed.validate));'
Swarm-Agent: codex-schema
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.
Eval analysis of 9 real sessions showed 10 skills never triggering despite being
requested. Root causes: pathPatterns too narrow (agents write to lib/email-template.tsx
not emails/), promptSignals containing regex instead of plain text (vercel-sandbox),
and missing promptSignals entirely (v0-dev, vercel-firewall).
Skills updated: email, vercel-queues, edge-runtime, vercel-firewall, chat-sdk,
v0-dev, vercel-sandbox. New skill: next-forge (bootstrap detection).
Hook modules were creating separate logger instances, so a single PreToolUse run could emit multiple invocationIds once hook-env catch logging fired. Reusing one process-scoped invocationId keeps all lines from one hook invocation correlated.
This also demotes internal trigger diagnostics back to debug so summary mode stays limited to complete and issue events, matching the logger contract and tests.
Verified: bun test tests/pretooluse-skill-inject.test.ts (278 tests pass)
Verified: bun test tests/logger.test.ts (12 tests pass)
Verified: bun test tests/hook-sync.test.ts -t "logger .mts/.mjs sync|pretooluse-skill-inject .mts/.mjs sync" (6 tests pass)
Swarm-Agent: codex-invocation-id-fix
Make the session-start profiler resolve binaries from PATH safely before
invoking them, cap the version-check subprocesses at 3 seconds, and
avoid crashing when npm or agent-browser is missing.
Also expand the outdated Vercel CLI guidance to include the pnpm global
upgrade path and cover the new skip/timeout behavior in profiler tests.
Verified: bun test tests/session-start-profiler.test.ts
Swarm-Agent: codex-profiler-harden-split-2-v2
Skill frontmatter (pathPatterns, bashPatterns, promptSignals, etc.)
was being injected alongside the skill body, wasting token budget on
metadata only useful for hook matching. Now uses extractFrontmatter()
to emit only the markdown body.
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.
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.
PreToolUse and PostToolUse dedup state was previously process-local, which broke across hook subprocess invocations.\nThis change adds session-scoped temp-file persistence and threads session_id through the relevant flows.\n\nPreToolUse now falls back to per-session seen-skills file state and writes back on every appendSeenSkill update.\nPostToolUse now checks persisted validated-files state and writes updates after marking files validated.\n\nVerified: bun run build:hooks\nVerified: bun test tests/pretooluse-skill-inject.test.ts tests/posttooluse-validate.test.ts\nSwarm-Agent: codex-dedup-core
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.
When hooks run as child processes, process.cwd() can point at the plugin root\ninstead of the user project.\n\nThis change threads cwd from PreToolUse stdin JSON through parseInput()\ninto appendAuditLog(), and uses it as fallback when CLAUDE_PROJECT_ROOT\nis not set.\n\nVerified: bun run build:hooks\nVerified: bun test tests/pretooluse-skill-inject.test.ts\nSwarm-Agent: codex-p0-audit-path
Add a Bash-triggered one-shot help block for vercel env add/update/pull.
This prevents repeated reminders under dedup while still allowing explicit repeats when dedup is off.
Verified: bun run build:hooks
Verified: bun test tests/subagent-fresh-env.test.ts
Swarm-Agent: codex-vercel-env-help
Skill injections now append JSONL audit records for post-run debugging and traceability.
Audit path resolves from VERCEL_PLUGIN_AUDIT_LOG_FILE with an opt-out mode and a project-local default.
Verified: bun run build:hooks
Verified: bun test tests/subagent-fresh-env.test.ts
Swarm-Agent: codex-audit-log
- Replace tsc+cp+rm pipeline with tsup (7ms builds, no intermediate dir)
- tsup compiles hooks/src/*.mts directly to hooks/*.mjs
- tsconfig.json retained for type-checking only (noEmit)
- Remove all .sh references from tests, docs, and hooks.json
- Fix glob pattern test to handle tsup's output format
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
PreToolUse now checks VERCEL_PLUGIN_SETUP_MODE and synthesizes a\nbootstrap match when setup mode is active, then boosts bootstrap\npriority so it injects before other matched skills on the first\nrelevant call.\n\nThe routing is dedup-safe: it skips synthetic bootstrap when the skill\nhas already been injected in seen-skills state.\n\nVerified: bun test tests/pretooluse-skill-inject.test.ts -t "setup mode bootstrap routing"\nVerified: node --check hooks/pretooluse-skill-inject.mjs\nSwarm-Agent: codex-profiler-hooks
- 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.
Create hooks/vercel-config.mjs that reads vercel.json and maps top-level keys (rewrites, redirects, headers, crons, functions) to skill slugs, so skills are injected when the project uses those Vercel features.
Create hooks/logger.mjs with structured logging at four levels (off, summary, debug, trace) controlled by VERCEL_PLUGIN_LOG_LEVEL env var. Support legacy VERCEL_PLUGIN_DEBUG=1 mapping to debug level.
Create src/cli/explain.ts that takes a tool name and input JSON, runs the match engine, and prints which skills would fire and why. Create src/cli/index.ts as the CLI entry point. Create src/commands/doctor.ts for diagnosing plugin setup issues.
Create src/shared/logger.ts and src/shared/match-engine.ts as TypeScript versions of the shared utilities.
Create scripts/build-manifest.ts that reads all SKILL.md files, parses frontmatter, and generates generated/skill-manifest.json.
Add CLAUDE.md documenting the architecture, dedup contract, YAML parser quirks, test conventions, and log levels.
Add tests: tests/cli-explain.test.ts, tests/logger.test.ts, tests/vercel-config.test.ts, tests/snapshot-runner.test.ts with snapshot files in tests/snapshots/. Add test fixtures in tests/fixtures/. Update existing tests to copy new hook modules (logger.mjs, vercel-config.mjs) into temp dirs.
Update package.json with new bin entry and dependencies.
The seen-skills dedup was using a temp file written via SessionStart,
but PreToolUse hooks can't update CLAUDE_ENV_FILE. This caused errors
when the temp file became stale or inaccessible across session clears.
Now VERCEL_PLUGIN_SEEN_SKILLS is a comma-delimited string stored
directly in the session environment. SessionStart initializes it as
empty, and the PreToolUse hook reads/updates process.env in-place.
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.
Replace centralized skill-map.json with frontmatter-based triggers in
each SKILL.md. Add hooks/skill-map-frontmatter.mjs to parse triggers
at runtime. Update validation, tests, and hook logic accordingly.