mirror of
https://github.com/vercel/vercel-plugin.git
synced 2026-09-14 15:39:47 +08:00
8e24d0694a
Add a chainTo frontmatter field to skills that triggers follow-up skill injection when PostToolUse file contents match regex patterns. Add upgradeToSkill/upgradeWhy fields to validation rules so validate errors can recommend loading a specific skill. Register posttooluse-bash-chain.mjs in hooks.json. Add ChainToRule interface to patterns.mts and skill-map-frontmatter.mts, with parseChainToRules() parser. Add duplicate-key detection to the YAML parser. Reset dedup claim dir and session file on clear/compact events in session-start-seen-skills so skills re-inject into fresh context windows. Add chainTo rules to: agent-browser-verify, agent-browser, ai-elements, ai-gateway, ai-generation-persistence, ai-sdk, auth, bootstrap, chat-sdk, cms, cron-jobs, deployments-cicd, email, env-vars, geist, investigation-mode, json-render, marketplace, micro, ncc, next-forge, nextjs, observability, payments, react-best-practices, routing-middleware, runtime-cache, satori, shadcn, sign-in-with-vercel, swr, turbopack, turborepo, v0-dev, vercel-agent, vercel-api, vercel-cli, vercel-firewall, vercel-flags, vercel-functions, vercel-queues, vercel-sandbox, vercel-storage, verification, workflow. Add upgradeToSkill to ai-elements and ai-sdk validate rules. Expand ai-sdk validate messages with Run Skill() hints. Update nextjs, vercel-storage, runtime-cache, workflow, turborepo skill bodies. Add new skills: geistdocs (Geist design system docs), zzz-test-meta-name-mask (test fixture). Add skills/_chain-audit.md chain coverage audit doc. Delete .claude-plugin/marketplace.json, .claude-plugin/plugin.json (deprecated), skills/edge-runtime/SKILL.md (consolidated into vercel-functions). Add tests: posttooluse-chain.test.ts (4699 lines, chain injection e2e), ai-sdk-companion.test.ts (181 lines). Expand build-skill-map.test.ts (+335 lines), validate-rules.test.ts (+936 lines), session-start-seen-skills.test.ts (+74 lines), skill-map-frontmatter.test.ts (+50 lines), verification-skill.test.ts (+20 lines). Update build-manifest.ts to emit chainTo rules and upgradeToSkill fields. Rebuild generated/skill-manifest.json, generated/skill-catalog.md, generated/build-from-skills.manifest.json. Rebuild all compiled hooks/*.mjs. Update CLAUDE.md lexical prompt default to on. Update vercel.md ecosystem graph, docs, and cli-reference.
125 lines
3.7 KiB
JavaScript
125 lines
3.7 KiB
JavaScript
// hooks/src/compat.mts
|
|
import { appendFileSync } from "fs";
|
|
var cursorSessionEnv = /* @__PURE__ */ new Map();
|
|
var currentHookEventName;
|
|
function isRecord(value) {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
}
|
|
function readString(value) {
|
|
return typeof value === "string" ? value : void 0;
|
|
}
|
|
function readRecord(value) {
|
|
return isRecord(value) ? value : void 0;
|
|
}
|
|
function readWorkspaceRoot(raw) {
|
|
if (!Array.isArray(raw.workspace_roots)) return void 0;
|
|
const firstRoot = raw.workspace_roots[0];
|
|
return typeof firstRoot === "string" ? firstRoot : void 0;
|
|
}
|
|
function normalizeToolOutputValue(value) {
|
|
if (typeof value === "undefined") return void 0;
|
|
if (typeof value === "string") return value;
|
|
try {
|
|
return JSON.stringify(value);
|
|
} catch {
|
|
return String(value);
|
|
}
|
|
}
|
|
function escapeShellEnvValue(value) {
|
|
return value.replace(/(["\\$`])/g, "\\$1");
|
|
}
|
|
function drainCursorSessionEnv() {
|
|
if (cursorSessionEnv.size === 0) return void 0;
|
|
const env = Object.fromEntries(cursorSessionEnv);
|
|
cursorSessionEnv.clear();
|
|
return env;
|
|
}
|
|
function detectPlatform(raw) {
|
|
if ("conversation_id" in raw || "workspace_roots" in raw || "cursor_version" in raw) {
|
|
return "cursor";
|
|
}
|
|
return "claude-code";
|
|
}
|
|
function normalizeInput(raw) {
|
|
const platform = detectPlatform(raw);
|
|
const sessionId = readString(raw.session_id ?? raw.conversation_id) ?? "";
|
|
const cwd = readString(raw.cwd) ?? readWorkspaceRoot(raw) ?? process.env.CURSOR_PROJECT_DIR ?? process.env.CLAUDE_PROJECT_DIR ?? process.cwd();
|
|
const hookEvent = readString(raw.hook_event_name) ?? "";
|
|
const toolOutput = normalizeToolOutputValue(raw.tool_output ?? raw.tool_response);
|
|
currentHookEventName = hookEvent || void 0;
|
|
return {
|
|
platform,
|
|
sessionId,
|
|
cwd,
|
|
hookEvent,
|
|
toolName: readString(raw.tool_name),
|
|
toolInput: readRecord(raw.tool_input),
|
|
toolOutput,
|
|
prompt: readString(raw.prompt),
|
|
raw
|
|
};
|
|
}
|
|
function formatOutput(platform, internal) {
|
|
if (platform === "cursor") {
|
|
const env = {
|
|
...drainCursorSessionEnv() ?? {},
|
|
...internal.env ?? {}
|
|
};
|
|
const output = {};
|
|
if (typeof internal.additionalContext !== "undefined") {
|
|
output.additional_context = internal.additionalContext;
|
|
}
|
|
if (typeof internal.permission !== "undefined") {
|
|
output.permission = internal.permission;
|
|
}
|
|
if (Object.keys(env).length > 0) {
|
|
output.env = env;
|
|
}
|
|
if (typeof internal.userMessage !== "undefined") {
|
|
output.user_message = internal.userMessage;
|
|
}
|
|
return output;
|
|
}
|
|
const hookSpecificOutput = {};
|
|
if (typeof internal.additionalContext !== "undefined") {
|
|
if (currentHookEventName) {
|
|
hookSpecificOutput.hookEventName = currentHookEventName;
|
|
}
|
|
hookSpecificOutput.additionalContext = internal.additionalContext;
|
|
}
|
|
if (typeof internal.permission !== "undefined") {
|
|
if (currentHookEventName) {
|
|
hookSpecificOutput.hookEventName = currentHookEventName;
|
|
}
|
|
hookSpecificOutput.permissionDecision = internal.permission;
|
|
}
|
|
if (Object.keys(hookSpecificOutput).length === 0) {
|
|
return {};
|
|
}
|
|
return { hookSpecificOutput };
|
|
}
|
|
function getEnvFilePath() {
|
|
return process.env.CLAUDE_ENV_FILE || null;
|
|
}
|
|
function setSessionEnv(platform, key, value) {
|
|
if (platform === "cursor") {
|
|
cursorSessionEnv.set(key, value);
|
|
return;
|
|
}
|
|
const envFile = getEnvFilePath();
|
|
if (!envFile) return;
|
|
appendFileSync(envFile, `export ${key}="${escapeShellEnvValue(value)}"
|
|
`);
|
|
}
|
|
function getProjectRoot() {
|
|
return process.env.CLAUDE_PROJECT_ROOT ?? process.env.CURSOR_PROJECT_DIR ?? process.cwd();
|
|
}
|
|
export {
|
|
detectPlatform,
|
|
formatOutput,
|
|
getEnvFilePath,
|
|
getProjectRoot,
|
|
normalizeInput,
|
|
setSessionEnv
|
|
};
|