Files
vercel__vercel-plugin/tests/posttooluse-chain.test.ts
John Lindquist 8e24d0694a feat: add chainTo skill chaining, prompt signals, validation upgrades, and dedup reset on context clear
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.
2026-03-11 15:18:35 -06:00

4700 lines
193 KiB
TypeScript

/**
* Tests for PostToolUse chain injection (chainTo rules).
*
* Covers:
* - chainTo match triggers skill injection in additionalContext
* - already-seen skill is NOT re-injected via chain
* - chain depth limit (single hop — no recursive chaining)
* - chainTo with no matches produces no additionalContext
* - multiple chainTo matches inject only highest-priority target (first match per target)
* - byte budget is respected for chained content
*/
import { describe, test, expect, beforeEach } from "bun:test";
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { join, resolve } from "node:path";
import { tmpdir } from "node:os";
const ROOT = resolve(import.meta.dirname, "..");
const HOOK_SCRIPT = join(ROOT, "hooks", "posttooluse-validate.mjs");
// Unique session ID per test run
let testSession: string;
beforeEach(() => {
testSession = `chain-test-${Date.now()}-${Math.random().toString(36).slice(2)}`;
});
/**
* Extract postValidation metadata from additionalContext.
*/
function extractPostValidation(hookSpecificOutput: any): any {
const ctx = hookSpecificOutput?.additionalContext || "";
const match = ctx.match(/<!-- postValidation: ({.*?}) -->/);
if (!match) return undefined;
try { return JSON.parse(match[1]); } catch { return undefined; }
}
async function runHook(
input: object,
extraEnv?: Record<string, string>,
): Promise<{ code: number; stdout: string; stderr: string; parsed: any; ctx: string }> {
const payload = JSON.stringify({ ...input, session_id: testSession });
const proc = Bun.spawn(["node", HOOK_SCRIPT], {
stdin: "pipe",
stdout: "pipe",
stderr: "pipe",
env: {
...process.env,
VERCEL_PLUGIN_VALIDATED_FILES: "",
VERCEL_PLUGIN_SEEN_SKILLS: "",
...extraEnv,
},
});
proc.stdin.write(payload);
proc.stdin.end();
const code = await proc.exited;
const stdout = await new Response(proc.stdout).text();
const stderr = await new Response(proc.stderr).text();
let parsed: any = {};
let ctx = "";
try {
parsed = JSON.parse(stdout);
ctx = parsed?.hookSpecificOutput?.additionalContext || "";
} catch {}
return { code, stdout, stderr, parsed, ctx };
}
// ---------------------------------------------------------------------------
// Unit tests for runChainInjection (imported from compiled module)
// ---------------------------------------------------------------------------
describe("runChainInjection unit tests", () => {
let runChainInjection: typeof import("../hooks/src/posttooluse-validate.mts").runChainInjection;
let formatOutput: typeof import("../hooks/src/posttooluse-validate.mts").formatOutput;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-validate.mjs");
runChainInjection = mod.runChainInjection;
formatOutput = mod.formatOutput;
});
test("chainTo match triggers skill injection", () => {
// Use a small target skill (micro ~3KB) to stay within 18KB budget
const chainMap = new Map([
["test-source", [
{
pattern: "SOME_PATTERN",
targetSkill: "micro",
message: "Loading micro guidance.",
},
]],
]);
const fileContent = `const x = SOME_PATTERN;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null, // no session (skip dedup file ops)
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].sourceSkill).toBe("test-source");
expect(result.injected[0].targetSkill).toBe("micro");
expect(result.injected[0].message).toBe("Loading micro guidance.");
expect(result.injected[0].content.length).toBeGreaterThan(0);
expect(result.totalBytes).toBeGreaterThan(0);
});
test("already-seen skill is NOT re-injected via chain", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "TRIGGER_PATTERN",
targetSkill: "micro",
},
]],
]);
const fileContent = `const x = TRIGGER_PATTERN;\n`;
const fakeEnv: any = {
VERCEL_PLUGIN_SEEN_SKILLS: "micro",
};
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
fakeEnv,
);
expect(result.injected.length).toBe(0);
expect(result.totalBytes).toBe(0);
});
test("chain depth is limited to 1 hop (no recursive chaining)", () => {
// Simulate: source-a chains to micro, which itself has chainTo rules.
// runChainInjection only processes the matchedSkills passed in — it does NOT
// recursively process chainTo rules of injected targets. This is the "single hop" guarantee.
const chainMap = new Map([
["source-a", [
{
pattern: "TRIGGER",
targetSkill: "micro", // ~3KB, fits in budget
},
]],
// micro also has a chain rule, but it should NOT fire
// because micro is not in matchedSkills
["micro", [
{
pattern: ".*",
targetSkill: "cron-jobs",
},
]],
]);
const fileContent = `const x = TRIGGER;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["source-a"], // only source-a is matched — micro is NOT in matchedSkills
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
// Only micro should be injected (from source-a chain), NOT cron-jobs
expect(result.injected.length).toBe(1);
expect(result.injected[0].targetSkill).toBe("micro");
// Confirm cron-jobs was NOT injected
expect(result.injected.every((i) => i.targetSkill !== "cron-jobs")).toBe(true);
});
test("chainTo with no matches produces no injections", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "SOMETHING_THAT_WONT_MATCH",
targetSkill: "micro",
},
]],
]);
const fileContent = `import { generateText } from 'ai';\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(0);
expect(result.totalBytes).toBe(0);
});
test("multiple chainTo matches inject only one entry per target skill (first wins)", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "FIRST_MATCH",
targetSkill: "micro",
message: "First chain rule",
},
{
pattern: "SECOND_MATCH",
targetSkill: "micro",
message: "Second chain rule — same target, should be deduped",
},
]],
]);
const fileContent = `const a = FIRST_MATCH;\nconst b = SECOND_MATCH;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
// Only one injection for micro (first match wins)
expect(result.injected.length).toBe(1);
expect(result.injected[0].targetSkill).toBe("micro");
expect(result.injected[0].message).toBe("First chain rule");
});
test("byte budget is respected for chained content", () => {
// micro (~3KB) + swr (~5KB) + cron-jobs (~2KB) = ~10KB — all fit within 18KB budget
// Raise cap to 10 so budget is the limiting factor, not the cap
const chainMap = new Map([
["source-a", [
{
pattern: "PATTERN_A",
targetSkill: "micro", // ~3KB
},
]],
["source-b", [
{
pattern: "PATTERN_B",
targetSkill: "swr", // ~5KB
},
]],
["source-c", [
{
pattern: "PATTERN_C",
targetSkill: "cron-jobs", // ~2KB
},
]],
]);
const fileContent = `PATTERN_A;\nPATTERN_B;\nPATTERN_C;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "", VERCEL_PLUGIN_CHAIN_CAP: "10" };
const result = runChainInjection(
fileContent,
["source-a", "source-b", "source-c"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.totalBytes).toBeLessThanOrEqual(18_000);
expect(result.totalBytes).toBeGreaterThan(0);
// All three should fit within budget (cap raised to 10)
expect(result.injected.length).toBe(3);
});
test("chainTo with nonexistent target skill is skipped gracefully", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "TRIGGER",
targetSkill: "nonexistent-skill-that-does-not-exist",
},
]],
]);
const fileContent = `const x = TRIGGER;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(0);
});
test("chain injection is capped at VERCEL_PLUGIN_CHAIN_CAP (default 2)", () => {
// Three distinct targets, but cap is 2
const chainMap = new Map([
["source-a", [
{ pattern: "PAT_A", targetSkill: "micro" },
]],
["source-b", [
{ pattern: "PAT_B", targetSkill: "swr" },
]],
["source-c", [
{ pattern: "PAT_C", targetSkill: "cron-jobs" },
]],
]);
const fileContent = `PAT_A;\nPAT_B;\nPAT_C;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["source-a", "source-b", "source-c"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
// Default cap is 2
expect(result.injected.length).toBe(2);
});
test("chain cap is configurable via VERCEL_PLUGIN_CHAIN_CAP env var", () => {
const chainMap = new Map([
["source-a", [
{ pattern: "PAT_A", targetSkill: "micro" },
]],
["source-b", [
{ pattern: "PAT_B", targetSkill: "swr" },
]],
["source-c", [
{ pattern: "PAT_C", targetSkill: "cron-jobs" },
]],
]);
const fileContent = `PAT_A;\nPAT_B;\nPAT_C;\n`;
// Cap set to 1
const envCap1: any = { VERCEL_PLUGIN_SEEN_SKILLS: "", VERCEL_PLUGIN_CHAIN_CAP: "1" };
const result1 = runChainInjection(
fileContent,
["source-a", "source-b", "source-c"],
chainMap,
null,
ROOT,
undefined,
envCap1,
);
expect(result1.injected.length).toBe(1);
// Cap set to 10 — all 3 should be injected
const envCap10: any = { VERCEL_PLUGIN_SEEN_SKILLS: "", VERCEL_PLUGIN_CHAIN_CAP: "10" };
const result10 = runChainInjection(
fileContent,
["source-a", "source-b", "source-c"],
chainMap,
null,
ROOT,
undefined,
envCap10,
);
expect(result10.injected.length).toBe(3);
});
test("skipIfFileContains skips chain rule when file matches the guard regex", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "@vercel/postgres",
targetSkill: "micro",
skipIfFileContains: "@neondatabase/serverless",
},
]],
]);
// File already uses the replacement — chain should NOT fire
const fileContent = `import { neon } from '@neondatabase/serverless';\nimport { sql } from '@vercel/postgres';\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(0);
});
test("skipIfFileContains does NOT skip when guard regex does not match", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "@vercel/postgres",
targetSkill: "micro",
skipIfFileContains: "@neondatabase/serverless",
},
]],
]);
// File uses deprecated import but NOT the replacement — chain should fire
const fileContent = `import { sql } from '@vercel/postgres';\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].targetSkill).toBe("micro");
});
test("loop prevention: A→B chain is blocked when B is already seen", () => {
// Simulate: skill A chains to B, but B is already seen
const chainMap = new Map([
["skill-a", [
{ pattern: "TRIGGER", targetSkill: "micro" },
]],
]);
const fileContent = `const x = TRIGGER;\n`;
const envWithSeen: any = { VERCEL_PLUGIN_SEEN_SKILLS: "micro" };
const result = runChainInjection(
fileContent,
["skill-a"],
chainMap,
null,
ROOT,
undefined,
envWithSeen,
);
expect(result.injected.length).toBe(0);
});
test("loop prevention: bidirectional A↔B only injects once", () => {
// A chains to B, B chains to A — but A is already a matched skill (seen)
const chainMap = new Map([
["skill-a", [
{ pattern: "TRIGGER_B", targetSkill: "micro" },
]],
["micro", [
{ pattern: "TRIGGER_A", targetSkill: "skill-a" },
]],
]);
const fileContent = `TRIGGER_B;\nTRIGGER_A;\n`;
// skill-a is already seen (it was the matched skill that triggered this PostToolUse)
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "skill-a" };
const result = runChainInjection(
fileContent,
["skill-a"], // only skill-a is in matchedSkills — micro is NOT
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
// micro gets injected (skill-a chains to it)
// But micro's chain back to skill-a doesn't fire because micro isn't in matchedSkills
expect(result.injected.length).toBe(1);
expect(result.injected[0].targetSkill).toBe("micro");
});
test("chainTo with invalid regex pattern is skipped gracefully", () => {
const chainMap = new Map([
["test-source", [
{
pattern: "[invalid(regex",
targetSkill: "micro",
},
]],
]);
const fileContent = `const x = TRIGGER;\n`;
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runChainInjection(
fileContent,
["test-source"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(0);
});
});
// ---------------------------------------------------------------------------
// Real-world chain/validate scenario tests (use actual skill frontmatter)
// ---------------------------------------------------------------------------
describe("real-world chain and validate scenarios", () => {
let loadValidateRules: typeof import("../hooks/src/posttooluse-validate.mts").loadValidateRules;
let matchFileToSkills: typeof import("../hooks/src/posttooluse-validate.mts").matchFileToSkills;
let runValidation: typeof import("../hooks/src/posttooluse-validate.mts").runValidation;
let runChainInjection: typeof import("../hooks/src/posttooluse-validate.mts").runChainInjection;
let data: NonNullable<ReturnType<typeof loadValidateRules>>;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-validate.mjs");
loadValidateRules = mod.loadValidateRules;
matchFileToSkills = mod.matchFileToSkills;
runValidation = mod.runValidation;
runChainInjection = mod.runChainInjection;
const loaded = loadValidateRules(ROOT);
if (!loaded) throw new Error("loadValidateRules returned null — no skills with validate/chainTo rules");
data = loaded;
});
test("workflow file with DurableAgent import (no ai-sdk) triggers ai-sdk chain", () => {
const filePath = "/project/workflows/review.ts";
const fileContent = [
`import { DurableAgent } from "@workflow/ai/agent";`,
`import { createWorkflow } from "workflow";`,
``,
`const wf = createWorkflow({ id: "review" });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("workflow");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
expect(chainResult.injected.length).toBeGreaterThanOrEqual(1);
const aiSdkChain = chainResult.injected.find((i) => i.targetSkill === "ai-sdk");
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.sourceSkill).toBe("workflow");
expect(aiSdkChain!.content.length).toBeGreaterThan(0);
});
test("turbo.json with 'pipeline' key triggers turborepo validate error with upgradeToSkill", () => {
const filePath = "/project/turbo.json";
const fileContent = JSON.stringify({
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": { "dependsOn": ["^build"] },
"lint": {},
},
}, null, 2);
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("turborepo");
const violations = runValidation(fileContent, matched, data.rulesMap);
const pipelineViolation = violations.find((v) => v.skill === "turborepo" && v.matchedText.includes("pipeline"));
expect(pipelineViolation).toBeDefined();
expect(pipelineViolation!.severity).toBe("error");
expect(pipelineViolation!.upgradeToSkill).toBe("turborepo");
expect(pipelineViolation!.message).toContain("tasks");
});
test("file with generateObject( triggers ai-sdk validate error", () => {
const filePath = "/project/app/api/extract/route.ts";
const fileContent = [
`import { generateObject } from 'ai';`,
`import { z } from 'zod';`,
``,
`const result = await generateObject({`,
` model: 'openai/gpt-5.4',`,
` schema: z.object({ name: z.string() }),`,
` prompt: 'Extract the name',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const violations = runValidation(fileContent, matched, data.rulesMap);
const genObjViolation = violations.find((v) => v.skill === "ai-sdk" && v.matchedText.includes("generateObject"));
expect(genObjViolation).toBeDefined();
expect(genObjViolation!.severity).toBe("error");
expect(genObjViolation!.message).toContain("Output.object");
});
test("file with maxSteps: triggers ai-sdk validate recommendation", () => {
const filePath = "/project/app/api/agent/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
``,
`const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` maxSteps: 5,`,
` prompt: 'Plan a trip',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const violations = runValidation(fileContent, matched, data.rulesMap);
const maxStepsViolation = violations.find((v) => v.skill === "ai-sdk" && v.matchedText.includes("maxSteps"));
expect(maxStepsViolation).toBeDefined();
expect(maxStepsViolation!.severity).toBe("recommended");
expect(maxStepsViolation!.upgradeToSkill).toBe("ai-sdk");
expect(maxStepsViolation!.message).toContain("stepCountIs");
});
test("file with dall-e reference triggers ai-gateway upgrade", () => {
const filePath = "/project/app/api/image/route.ts";
const fileContent = [
`import { generateText } from 'ai';`,
``,
`const result = await generateText({`,
` model: 'openai/dall-e-3',`,
` prompt: 'A sunset over mountains',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const violations = runValidation(fileContent, matched, data.rulesMap);
const dalleViolation = violations.find((v) => v.skill === "ai-sdk" && v.matchedText.includes("dall-e"));
expect(dalleViolation).toBeDefined();
expect(dalleViolation!.severity).toBe("recommended");
expect(dalleViolation!.upgradeToSkill).toBe("ai-gateway");
expect(dalleViolation!.message).toContain("gemini-3.1-flash-image-preview");
});
test("file with toDataStreamResponse triggers ai-sdk validate recommendation", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
``,
`export async function POST(req: Request) {`,
` const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` prompt: 'Hello!',`,
` });`,
` return result.toDataStreamResponse();`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const violations = runValidation(fileContent, matched, data.rulesMap);
const tdsViolation = violations.find((v) => v.skill === "ai-sdk" && v.matchedText.includes("toDataStreamResponse"));
expect(tdsViolation).toBeDefined();
expect(tdsViolation!.severity).toBe("recommended");
expect(tdsViolation!.message).toContain("toUIMessageStreamResponse");
});
// -------------------------------------------------------------------
// New chainTo coverage: diverse cross-skill chain scenarios
// -------------------------------------------------------------------
test("vercel-storage file with @vercel/postgres import chains to nextjs", () => {
const filePath = "/project/lib/db.ts";
const fileContent = [
`import { sql } from '@vercel/postgres';`,
``,
`export async function getUsers() {`,
` const { rows } = await sql\`SELECT * FROM users\`;`,
` return rows;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-storage");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const nextjsChain = chainResult.injected.find((i) => i.targetSkill === "nextjs");
expect(nextjsChain).toBeDefined();
expect(nextjsChain!.sourceSkill).toBe("vercel-storage");
expect(nextjsChain!.message).toContain("@vercel/postgres");
expect(nextjsChain!.message).toContain("sunset");
});
test("shadcn file with react-markdown chains to ai-elements", () => {
const filePath = "/project/components/chat-display.tsx";
const fileContent = [
`import ReactMarkdown from 'react-markdown';`,
`import { cn } from '@/lib/utils';`,
``,
`export function ChatBubble({ text }: { text: string }) {`,
` return <ReactMarkdown>{text}</ReactMarkdown>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("shadcn");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiElementsChain = chainResult.injected.find((i) => i.targetSkill === "ai-elements");
expect(aiElementsChain).toBeDefined();
expect(aiElementsChain!.sourceSkill).toBe("shadcn");
expect(aiElementsChain!.message).toContain("markdown");
});
test("shadcn react-markdown chain is skipped when ai-elements already imported (skipIfFileContains)", () => {
const filePath = "/project/components/chat-display.tsx";
const fileContent = [
`import ReactMarkdown from 'react-markdown';`,
`import { MessageResponse } from '@/components/ai-elements/message';`,
`import { cn } from '@/lib/utils';`,
``,
`export function ChatBubble({ text }: { text: string }) {`,
` return <MessageResponse content={text} />;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("shadcn");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'ai-elements|MessageResponse|<Message\b' should suppress this chain
const aiElementsChain = chainResult.injected.find(
(i) => i.sourceSkill === "shadcn" && i.targetSkill === "ai-elements",
);
expect(aiElementsChain).toBeUndefined();
});
test("routing-middleware file with IP blocklist chains to vercel-firewall", () => {
const filePath = "/project/middleware.ts";
const fileContent = [
`import { NextRequest, NextResponse } from 'next/server';`,
``,
`const blockedIps = ['1.2.3.4', '5.6.7.8'];`,
``,
`export function middleware(req: NextRequest) {`,
` const ip = req.ip || '';`,
` if (blockedIps.includes(ip)) return NextResponse.json({}, { status: 403 });`,
` return NextResponse.next();`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("routing-middleware");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const firewallChain = chainResult.injected.find((i) => i.targetSkill === "vercel-firewall");
expect(firewallChain).toBeDefined();
expect(firewallChain!.sourceSkill).toBe("routing-middleware");
expect(firewallChain!.message).toContain("Firewall");
});
test("cron-jobs file with node-cron import chains to vercel-functions", () => {
const filePath = "/project/lib/scheduler.ts";
const fileContent = [
`import cron from 'node-cron';`,
``,
`cron.schedule('0 */6 * * *', async () => {`,
` await syncExternalData();`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("cron-jobs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const functionsChain = chainResult.injected.find((i) => i.targetSkill === "vercel-functions");
expect(functionsChain).toBeDefined();
expect(functionsChain!.sourceSkill).toBe("cron-jobs");
expect(functionsChain!.message).toContain("cron");
});
test("react-best-practices file with axios import chains to swr", () => {
const filePath = "/project/components/UserList.tsx";
const fileContent = [
`import React, { useEffect, useState } from 'react';`,
`import axios from 'axios';`,
``,
`export function UserList() {`,
` const [users, setUsers] = useState([]);`,
` useEffect(() => {`,
` axios.get('/api/users').then(res => setUsers(res.data));`,
` }, []);`,
` return <ul>{users.map(u => <li key={u.id}>{u.name}</li>)}</ul>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("react-best-practices");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const swrChain = chainResult.injected.find((i) => i.targetSkill === "swr");
expect(swrChain).toBeDefined();
expect(swrChain!.sourceSkill).toBe("react-best-practices");
expect(swrChain!.message).toContain("axios");
});
test("payments file with setTimeout chains to workflow", () => {
const filePath = "/project/app/api/webhooks/stripe/route.ts";
const fileContent = [
`import Stripe from 'stripe';`,
``,
`export async function POST(req: Request) {`,
` const event = await parseWebhook(req);`,
` if (event.type === 'payment_intent.succeeded') {`,
` // Retry with backoff`,
` setTimeout(() => fulfillOrder(event.data.object), 5000);`,
` }`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("payments");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowChain = chainResult.injected.find((i) => i.targetSkill === "workflow");
expect(workflowChain).toBeDefined();
expect(workflowChain!.sourceSkill).toBe("payments");
expect(workflowChain!.message).toContain("Workflow");
});
test("env-vars file with ANTHROPIC_API_KEY chains to ai-gateway", () => {
const filePath = "/project/.env.local";
const fileContent = [
`# AI provider keys`,
`ANTHROPIC_API_KEY=sk-ant-api...`,
`DATABASE_URL=postgres://...`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("env-vars");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gatewayChain = chainResult.injected.find((i) => i.targetSkill === "ai-gateway");
expect(gatewayChain).toBeDefined();
expect(gatewayChain!.sourceSkill).toBe("env-vars");
expect(gatewayChain!.message).toContain("OIDC");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: nextjs chains
// -------------------------------------------------------------------
test("nextjs file with middleware export chains to routing-middleware", () => {
const filePath = "/project/middleware.ts";
const fileContent = [
`import { NextResponse } from 'next/server';`,
``,
`export default function middleware(req) {`,
` return NextResponse.next();`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("nextjs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const routingChain = chainResult.injected.find((i) => i.targetSkill === "routing-middleware");
expect(routingChain).toBeDefined();
expect(routingChain!.sourceSkill).toBe("nextjs");
expect(routingChain!.message).toContain("proxy");
});
test("nextjs file with @ai-sdk/openai chains to ai-gateway", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
`import { openai } from '@ai-sdk/openai';`,
``,
`export async function POST(req: Request) {`,
` const result = streamText({ model: openai('gpt-5.4'), prompt: 'Hello' });`,
` return result.toUIMessageStreamResponse();`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("nextjs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gatewayChain = chainResult.injected.find((i) => i.targetSkill === "ai-gateway");
expect(gatewayChain).toBeDefined();
expect(gatewayChain!.sourceSkill).toBe("nextjs");
expect(gatewayChain!.message).toContain("Gateway");
});
test("nextjs file with next-auth chains to auth", () => {
const filePath = "/project/app/api/auth/[...nextauth]/route.ts";
const fileContent = [
`import NextAuth from 'next-auth';`,
`import { authOptions } from '@/lib/auth';`,
``,
`const handler = NextAuth(authOptions);`,
`export { handler as GET, handler as POST };`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("nextjs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find((i) => i.targetSkill === "auth");
expect(authChain).toBeDefined();
expect(authChain!.sourceSkill).toBe("nextjs");
});
test("nextjs file with NextApiRequest chains to vercel-functions", () => {
const filePath = "/project/pages/api/users.ts";
const fileContent = [
`import type { NextApiRequest, NextApiResponse } from 'next';`,
``,
`export default function handler(req: NextApiRequest, res: NextApiResponse) {`,
` res.status(200).json({ users: [] });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("nextjs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const functionsChain = chainResult.injected.find((i) => i.targetSkill === "vercel-functions");
expect(functionsChain).toBeDefined();
expect(functionsChain!.sourceSkill).toBe("nextjs");
});
test("nextjs file with lru-cache chains to runtime-cache", () => {
const filePath = "/project/lib/cache.ts";
const fileContent = [
`import { LRUCache } from 'lru-cache';`,
``,
`const cache = new LRUCache({ max: 500, ttl: 60000 });`,
`export default cache;`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
// Should match nextjs via import or another skill
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const cacheChain = chainResult.injected.find((i) => i.targetSkill === "runtime-cache");
if (cacheChain) {
expect(cacheChain.message).toContain("cache");
}
});
test("nextjs file with JWT handling chains to auth (skipIfFileContains)", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = [
`import jwt from 'jsonwebtoken';`,
``,
`export function verifyToken(token: string) {`,
` return jwt.verify(token, process.env.JWT_SECRET!);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find((i) => i.targetSkill === "auth");
if (authChain) {
expect(authChain.message).toContain("Auth");
}
});
test("nextjs JWT chain is skipped when Clerk is already imported (skipIfFileContains)", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = [
`import { clerkMiddleware } from '@clerk/nextjs/server';`,
`import jwt from 'jsonwebtoken';`,
``,
`// Legacy verification for migration`,
`export function verifyToken(token: string) {`,
` return jwt.verify(token, process.env.JWT_SECRET!);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains should suppress the auth chain since clerkMiddleware is present
const authChainFromNextjs = chainResult.injected.find(
(i) => i.sourceSkill === "nextjs" && i.targetSkill === "auth" && i.message?.includes("JWT"),
);
expect(authChainFromNextjs).toBeUndefined();
});
// -------------------------------------------------------------------
// Additional chainTo coverage: vercel-functions chains
// -------------------------------------------------------------------
test("vercel-functions file with direct OpenAI SDK chains to ai-sdk", () => {
const filePath = "/project/app/api/generate/route.ts";
const fileContent = [
`import OpenAI from 'openai';`,
``,
`const openai = new OpenAI();`,
`export async function POST(req: Request) {`,
` const completion = await openai.chat.completions.create({`,
` model: 'gpt-5.4',`,
` messages: [{ role: 'user', content: 'Hello' }],`,
` });`,
` return Response.json(completion);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-functions");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-functions" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.message).toContain("AI SDK");
});
test("vercel-functions file with writeFile chains to vercel-storage", () => {
const filePath = "/project/app/api/upload/route.ts";
const fileContent = [
`import { writeFileSync } from 'node:fs';`,
``,
`export async function POST(req: Request) {`,
` const data = await req.arrayBuffer();`,
` writeFileSync('/tmp/upload.bin', Buffer.from(data));`,
` return Response.json({ ok: true });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-functions");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-functions" && i.targetSkill === "vercel-storage",
);
expect(storageChain).toBeDefined();
expect(storageChain!.message).toContain("Storage");
});
test("vercel-functions file with deprecated AI SDK v5 APIs chains to ai-sdk", () => {
const filePath = "/project/app/api/extract/route.ts";
const fileContent = [
`import { generateObject } from 'ai';`,
``,
`export async function POST(req: Request) {`,
` const result = await generateObject({`,
` model: 'openai/gpt-5.4',`,
` schema: z.object({ name: z.string() }),`,
` prompt: 'Extract the name',`,
` });`,
` return Response.json(result.object);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-functions");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-functions" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.message).toContain("v5");
});
test("vercel-functions polling loop chain is skipped when workflow is already used (skipIfFileContains)", () => {
const filePath = "/project/app/api/poll/route.ts";
const fileContent = [
`'use workflow';`,
``,
`while (true) {`,
` const status = await checkStatus();`,
` if (status === 'done') break;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains should suppress the workflow chain since 'use workflow' is present
const workflowChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-functions" && i.targetSkill === "workflow",
);
expect(workflowChain).toBeUndefined();
});
// -------------------------------------------------------------------
// Additional chainTo coverage: ai-gateway chains
// -------------------------------------------------------------------
test("ai-gateway file with direct provider SDK import chains to ai-sdk", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { anthropic } from '@ai-sdk/anthropic';`,
`import { generateText } from 'ai';`,
``,
`const result = await generateText({`,
` model: anthropic('claude-sonnet-4.6'),`,
` prompt: 'Hello!',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-gateway");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.message).toContain("Gateway");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: email chains
// -------------------------------------------------------------------
test("email file with setTimeout chains to workflow", () => {
const filePath = "/project/lib/email.ts";
const fileContent = [
`import { Resend } from 'resend';`,
``,
`const resend = new Resend(process.env.RESEND_API_KEY);`,
``,
`export async function sendWelcomeEmail(to: string) {`,
` // Delay the follow-up email`,
` setTimeout(async () => {`,
` await resend.emails.send({ from: 'hi@example.com', to, subject: 'Follow up' });`,
` }, 86400000);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("email");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowChain = chainResult.injected.find(
(i) => i.sourceSkill === "email" && i.targetSkill === "workflow",
);
expect(workflowChain).toBeDefined();
expect(workflowChain!.message).toContain("Workflow");
});
test("email retry chain is skipped when workflow is already used (skipIfFileContains)", () => {
const filePath = "/project/lib/email.ts";
const fileContent = [
`import { createWorkflow } from 'workflow';`,
`import { Resend } from 'resend';`,
``,
`let retries = 0;`,
`const maxRetries = 3;`,
`try { await send(); } catch { retry(); }`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains should suppress the retry→workflow chain
const retryChain = chainResult.injected.find(
(i) => i.sourceSkill === "email" && i.targetSkill === "workflow" && i.message?.includes("retry"),
);
expect(retryChain).toBeUndefined();
});
// -------------------------------------------------------------------
// Additional chainTo coverage: vercel-queues chains
// -------------------------------------------------------------------
test("vercel-queues file with BullMQ chains to workflow", () => {
const filePath = "/project/lib/queue.ts";
const fileContent = [
`import { Queue, Worker } from 'bullmq';`,
``,
`const queue = new Queue('email-queue');`,
`const worker = new Worker('email-queue', async (job) => {`,
` await sendEmail(job.data);`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-queues");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-queues" && i.targetSkill === "workflow",
);
expect(workflowChain).toBeDefined();
expect(workflowChain!.message).toContain("BullMQ");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: vercel-firewall chains
// -------------------------------------------------------------------
test("vercel-firewall file with express-rate-limit chains to routing-middleware", () => {
const filePath = "/project/lib/rate-limit.ts";
const fileContent = [
`import rateLimit from 'express-rate-limit';`,
``,
`export const limiter = rateLimit({`,
` windowMs: 15 * 60 * 1000,`,
` max: 100,`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-firewall");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const routingChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-firewall" && i.targetSkill === "routing-middleware",
);
expect(routingChain).toBeDefined();
expect(routingChain!.message).toContain("rate limit");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: satori chains
// -------------------------------------------------------------------
test("satori file with puppeteer chains to vercel-functions", () => {
const filePath = "/project/lib/og.ts";
const fileContent = [
`import puppeteer from 'puppeteer';`,
``,
`export async function generateOG(title: string) {`,
` const browser = await puppeteer.launch({ headless: true });`,
` const page = await browser.newPage();`,
` await page.setContent('<h1>' + title + '</h1>');`,
` const screenshot = await page.screenshot();`,
` await browser.close();`,
` return screenshot;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("satori");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const functionsChain = chainResult.injected.find(
(i) => i.sourceSkill === "satori" && i.targetSkill === "vercel-functions",
);
expect(functionsChain).toBeDefined();
expect(functionsChain!.message).toContain("browser");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: vercel-flags chains
// -------------------------------------------------------------------
test("vercel-flags file with env var feature flags chains to vercel-storage", () => {
const filePath = "/project/lib/flags.ts";
const fileContent = [
`const isNewUI = process.env.FEATURE_NEW_UI === 'true';`,
`const isEnabled = process.env.ENABLE_DARK_MODE === '1';`,
``,
`export { isNewUI, isEnabled };`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("vercel-flags");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-flags" && i.targetSkill === "vercel-storage",
);
expect(storageChain).toBeDefined();
expect(storageChain!.message).toContain("Edge Config");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: workflow → ai-elements
// -------------------------------------------------------------------
test("workflow file with useChat chains to ai-elements (skipIfFileContains guard)", () => {
const filePath = "/project/app/workflow/chat.tsx";
const fileContent = [
`'use client';`,
`import { useChat } from '@ai-sdk/react';`,
`import { DefaultChatTransport } from '@ai-sdk/react';`,
``,
`export function WorkflowChat() {`,
` const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/workflow/chat' }) });`,
` return <div>{messages.map(m => <p key={m.id}>{m.content}</p>)}</div>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// Should chain to ai-elements since there's no ai-elements import
const aiElementsChain = chainResult.injected.find(
(i) => i.targetSkill === "ai-elements",
);
if (matched.includes("workflow")) {
expect(aiElementsChain).toBeDefined();
expect(aiElementsChain!.message).toContain("AI Elements");
}
});
test("workflow file with useChat is skipped when MessageResponse already imported", () => {
const filePath = "/project/app/workflow/chat.tsx";
const fileContent = [
`'use client';`,
`import { useChat } from '@ai-sdk/react';`,
`import { MessageResponse } from '@/components/ai-elements/message';`,
``,
`export function WorkflowChat() {`,
` const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/workflow/chat' }) });`,
` return <div>{messages.map(m => <MessageResponse key={m.id} content={m.content} />)}</div>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'ai-elements|MessageResponse|<Message\b' should suppress
const aiElementsFromWorkflow = chainResult.injected.find(
(i) => i.sourceSkill === "workflow" && i.targetSkill === "ai-elements",
);
expect(aiElementsFromWorkflow).toBeUndefined();
});
// -------------------------------------------------------------------
// Additional chainTo coverage: geistdocs → nextjs
// -------------------------------------------------------------------
test("geistdocs file with nextra import chains to nextjs", () => {
const filePath = "/project/next.config.mjs";
const fileContent = [
`import nextra from 'nextra';`,
``,
`const withNextra = nextra({ theme: 'nextra-theme-docs' });`,
`export default withNextra({});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("geistdocs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const nextjsChain = chainResult.injected.find(
(i) => i.sourceSkill === "geistdocs" && i.targetSkill === "nextjs",
);
expect(nextjsChain).toBeDefined();
expect(nextjsChain!.message).toContain("Nextra");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: shadcn → ai-elements (dangerouslySetInnerHTML)
// -------------------------------------------------------------------
test("shadcn file with dangerouslySetInnerHTML chains to ai-elements", () => {
const filePath = "/project/components/ai-output.tsx";
const fileContent = [
`import { cn } from '@/lib/utils';`,
``,
`export function AIOutput({ html }: { html: string }) {`,
` return <div className={cn('prose')} dangerouslySetInnerHTML={{ __html: html }} />;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("shadcn");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiElementsChain = chainResult.injected.find(
(i) => i.sourceSkill === "shadcn" && i.targetSkill === "ai-elements",
);
expect(aiElementsChain).toBeDefined();
expect(aiElementsChain!.message).toContain("HTML");
});
// -------------------------------------------------------------------
// Additional chainTo coverage: react-best-practices → shadcn
// -------------------------------------------------------------------
test("react-best-practices file with styled-components chains to shadcn", () => {
const filePath = "/project/components/Button.tsx";
const fileContent = [
`import styled from 'styled-components';`,
``,
`const StyledButton = styled.button\``,
` background: blue;`,
` color: white;`,
`\`;`,
``,
`export function Button({ children }: { children: React.ReactNode }) {`,
` return <StyledButton>{children}</StyledButton>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("react-best-practices");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const shadcnChain = chainResult.injected.find(
(i) => i.sourceSkill === "react-best-practices" && i.targetSkill === "shadcn",
);
expect(shadcnChain).toBeDefined();
expect(shadcnChain!.message).toContain("shadcn");
});
test("react-best-practices fetch().then() chain to swr is skipped when useSWR present (skipIfFileContains)", () => {
const filePath = "/project/components/UserList.tsx";
const fileContent = [
`import useSWR from 'swr';`,
``,
`// Legacy fetch still in codebase`,
`fetch('/api/old').then(res => res.json());`,
``,
`export function UserList() {`,
` const { data } = useSWR('/api/users', fetcher);`,
` return <ul>{data?.map(u => <li key={u.id}>{u.name}</li>)}</ul>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains should suppress since useSWR is present
const swrChainFromFetch = chainResult.injected.find(
(i) => i.sourceSkill === "react-best-practices" && i.targetSkill === "swr"
&& i.message?.includes("fetch"),
);
expect(swrChainFromFetch).toBeUndefined();
});
// -------------------------------------------------------------------------
// payments chainTo rules
// -------------------------------------------------------------------------
test("payments file with manual retry logic chains to workflow", () => {
const filePath = "/project/app/api/checkout/route.ts";
const fileContent = [
`import Stripe from 'stripe';`,
`let retries = 3;`,
`while (retries > 0) {`,
` try { await charge(); break; } catch { retries--; }`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowChain = chainResult.injected.find((i) => i.targetSkill === "workflow");
expect(workflowChain).toBeDefined();
});
test("payments retry chain is skipped when workflow already used (skipIfFileContains)", () => {
const filePath = "/project/app/api/checkout/route.ts";
const fileContent = [
`import { createWorkflow } from 'workflow';`,
`let retries = 3;`,
`while (retries > 0) { retries--; }`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowRetryChain = chainResult.injected.find(
(i) => i.targetSkill === "workflow" && i.message?.includes("retry"),
);
expect(workflowRetryChain).toBeUndefined();
});
test("payments file with Stripe webhook chains to payments", () => {
const filePath = "/project/app/api/webhook/route.ts";
const fileContent = [
`import Stripe from 'stripe';`,
`const event = stripe.webhooks.constructEvent(body, sig, STRIPE_WEBHOOK_SECRET);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const paymentsChain = chainResult.injected.find((i) => i.targetSkill === "payments");
expect(paymentsChain).toBeDefined();
});
// -------------------------------------------------------------------------
// cron-jobs chainTo rules
// -------------------------------------------------------------------------
test("cron-jobs file with setTimeout chains to workflow", () => {
const filePath = "/project/app/api/cron/route.ts";
const fileContent = [
`export async function GET() {`,
` setTimeout(() => processJobs(), 5000);`,
` return new Response('OK');`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowChain = chainResult.injected.find((i) => i.targetSkill === "workflow");
expect(workflowChain).toBeDefined();
});
test("cron-jobs file with croner import chains to cron-jobs", () => {
const filePath = "/project/lib/scheduler.ts";
const fileContent = `import { Cron } from 'croner';\nconst job = new Cron('0 * * * *', () => {});\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const cronChain = chainResult.injected.find((i) => i.targetSkill === "cron-jobs");
expect(cronChain).toBeDefined();
});
// -------------------------------------------------------------------------
// routing-middleware chainTo rules
// -------------------------------------------------------------------------
test("routing-middleware file with next-auth import chains to auth", () => {
const filePath = "/project/middleware.ts";
const fileContent = [
`import { NextResponse } from 'next/server';`,
`import { getToken } from 'next-auth/jwt';`,
`export function middleware(req) { return NextResponse.next(); }`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find((i) => i.targetSkill === "auth");
expect(authChain).toBeDefined();
});
test("routing-middleware NextResponse chain is skipped in proxy.ts context (skipIfFileContains)", () => {
const filePath = "/project/proxy.ts";
const fileContent = [
`import { NextResponse } from 'next/server';`,
`// runtime nodejs`,
`export function proxy(req) { return NextResponse.next(); }`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const nextjsChain = chainResult.injected.find(
(i) => i.targetSkill === "nextjs" && i.message?.includes("proxy.ts"),
);
expect(nextjsChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// vercel-storage chainTo rules
// -------------------------------------------------------------------------
test("vercel-storage file with @vercel/kv import chains to nextjs", () => {
const filePath = "/project/lib/cache.ts";
const fileContent = `import { kv } from '@vercel/kv';\nconst val = await kv.get('key');\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const nextjsChain = chainResult.injected.find((i) => i.targetSkill === "nextjs");
expect(nextjsChain).toBeDefined();
});
test("vercel-storage file with Supabase import chains to vercel-storage", () => {
const filePath = "/project/lib/db.ts";
const fileContent = `import { createClient } from '@supabase/supabase-js';\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find((i) => i.targetSkill === "vercel-storage");
expect(storageChain).toBeDefined();
});
test("vercel-storage file with MongoDB import chains to vercel-storage", () => {
const filePath = "/project/lib/db.ts";
const fileContent = `import mongoose from 'mongoose';\nawait mongoose.connect(uri);\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find((i) => i.targetSkill === "vercel-storage");
expect(storageChain).toBeDefined();
});
// -------------------------------------------------------------------------
// vercel-queues chainTo rules
// -------------------------------------------------------------------------
test("vercel-queues file with SQS import chains to workflow", () => {
const filePath = "/project/lib/queue.ts";
const fileContent = `import { SQSClient } from '@aws-sdk/client-sqs';\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const workflowChain = chainResult.injected.find((i) => i.targetSkill === "workflow");
expect(workflowChain).toBeDefined();
});
test("vercel-queues file with p-queue import chains to vercel-queues", () => {
const filePath = "/project/lib/workers.ts";
const fileContent = `import PQueue from 'p-queue';\nconst q = new PQueue({ concurrency: 2 });\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const queuesChain = chainResult.injected.find((i) => i.targetSkill === "vercel-queues");
expect(queuesChain).toBeDefined();
});
// -------------------------------------------------------------------------
// chat-sdk chainTo rules
// -------------------------------------------------------------------------
test("chat-sdk file with direct OpenAI import chains to ai-sdk", () => {
const filePath = "/project/lib/bot.ts";
const fileContent = [
`import { Chat } from 'chat';`,
`import OpenAI from 'openai';`,
`const openai = new OpenAI();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find((i) => i.targetSkill === "ai-sdk");
expect(aiSdkChain).toBeDefined();
});
test("chat-sdk file with @slack/bolt import chains to chat-sdk", () => {
const filePath = "/project/lib/bot.ts";
const fileContent = [
`import { App } from '@slack/bolt';`,
`const app = new App({ token: process.env.SLACK_BOT_TOKEN });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const chatSdkChain = chainResult.injected.find((i) => i.targetSkill === "chat-sdk");
expect(chatSdkChain).toBeDefined();
});
test("chat-sdk file with discord.js import chains to chat-sdk", () => {
const filePath = "/project/lib/discord-bot.ts";
const fileContent = `import { Client } from 'discord.js';\nconst client = new Client();\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const chatSdkChain = chainResult.injected.find((i) => i.targetSkill === "chat-sdk");
expect(chatSdkChain).toBeDefined();
});
// -------------------------------------------------------------------------
// email chainTo rules
// -------------------------------------------------------------------------
test("email file with nodemailer import chains to email", () => {
const filePath = "/project/lib/mailer.ts";
const fileContent = [
`import nodemailer from 'nodemailer';`,
`const transporter = nodemailer.createTransport({});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const emailChain = chainResult.injected.find((i) => i.targetSkill === "email");
expect(emailChain).toBeDefined();
});
test("email file with batch send chains to workflow (skipIfFileContains)", () => {
const filePath = "/project/lib/campaign.ts";
const fileContent = [
`const emails = users.map(u => u.email);`,
`await Promise.all(emails.map(e => sendEmail(e)));`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// If email skill matched, workflow chain should fire for batch patterns
const workflowChain = chainResult.injected.find(
(i) => i.targetSkill === "workflow" && i.message?.includes("batch"),
);
// Whether it fires depends on email skill matching — file may or may not match email pathPatterns
// The important thing: if it fires, it targets workflow
if (workflowChain) {
expect(workflowChain.targetSkill).toBe("workflow");
}
});
test("email batch chain is skipped when workflow already used", () => {
const filePath = "/project/lib/campaign.ts";
const fileContent = [
`import { createWorkflow } from 'workflow';`,
`const emails = users.map(u => u.email);`,
`await Promise.all(emails.map(e => sendEmail(e)));`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const batchChain = chainResult.injected.find(
(i) => i.targetSkill === "workflow" && i.message?.includes("batch"),
);
expect(batchChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// auth chainTo rules
// -------------------------------------------------------------------------
test("auth file with Vercel OAuth env vars chains to sign-in-with-vercel", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = [
`const clientId = process.env.VERCEL_CLIENT_ID;`,
`const clientSecret = process.env.VERCEL_CLIENT_SECRET;`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const siVercelChain = chainResult.injected.find((i) => i.targetSkill === "sign-in-with-vercel");
expect(siVercelChain).toBeDefined();
});
test("auth file with jsonwebtoken import chains to auth", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = [
`import jwt from 'jsonwebtoken';`,
`const token = jwt.sign({ userId: '123' }, secret);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find((i) => i.targetSkill === "auth");
expect(authChain).toBeDefined();
});
test("auth file with middleware export chains to routing-middleware", () => {
const filePath = "/project/middleware.ts";
const fileContent = [
`import { clerkMiddleware } from '@clerk/nextjs/server';`,
`export default function middleware(req) { return clerkMiddleware()(req); }`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const routingChain = chainResult.injected.find((i) => i.targetSkill === "routing-middleware");
expect(routingChain).toBeDefined();
});
// -------------------------------------------------------------------------
// swr chainTo rules
// -------------------------------------------------------------------------
test("swr file with direct AI provider SDK chains to ai-sdk", () => {
const filePath = "/project/components/dashboard.tsx";
const fileContent = [
`import useSWR from 'swr';`,
`import { OpenAI } from 'openai';`,
`const openai = new OpenAI();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find((i) => i.targetSkill === "ai-sdk");
expect(aiSdkChain).toBeDefined();
});
test("swr file with @vercel/kv chains to vercel-storage", () => {
const filePath = "/project/lib/data.ts";
const fileContent = [
`import useSWR from 'swr';`,
`import { kv } from '@vercel/kv';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find((i) => i.targetSkill === "vercel-storage");
expect(storageChain).toBeDefined();
});
test("swr file with useEffect+fetch chains to swr", () => {
const filePath = "/project/components/list.tsx";
const fileContent = [
`'use client';`,
`import { useEffect, useState } from 'react';`,
`useEffect(() => {`,
` fetch('/api/items').then(r => r.json()).then(setItems);`,
`}, []);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const swrChain = chainResult.injected.find((i) => i.targetSkill === "swr");
expect(swrChain).toBeDefined();
});
// -------------------------------------------------------------------------
// ai-elements chainTo rules
// -------------------------------------------------------------------------
test("ai-elements file with raw message.content chains to ai-sdk", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`'use client';`,
`import { Message } from '@/components/ai-elements/message';`,
`{message.content}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find((i) => i.targetSkill === "ai-sdk");
expect(aiSdkChain).toBeDefined();
});
test("ai-elements file with react-markdown chains to ai-elements", () => {
const filePath = "/project/components/response.tsx";
const fileContent = [
`import ReactMarkdown from 'react-markdown';`,
`<ReactMarkdown>{text}</ReactMarkdown>`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiElementsChain = chainResult.injected.find((i) => i.targetSkill === "ai-elements");
expect(aiElementsChain).toBeDefined();
});
// -------------------------------------------------------------------------
// runtime-cache chainTo rules
// -------------------------------------------------------------------------
test("runtime-cache file with @vercel/kv chains to vercel-storage", () => {
const filePath = "/project/lib/cache.ts";
const fileContent = [
`import { unstable_cache } from 'next/cache';`,
`import { kv } from '@vercel/kv';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find((i) => i.targetSkill === "vercel-storage");
expect(storageChain).toBeDefined();
});
test("runtime-cache file with ioredis chains to vercel-storage", () => {
const filePath = "/project/lib/redis.ts";
const fileContent = [
`import Redis from 'ioredis';`,
`const redis = new Redis();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find((i) => i.targetSkill === "vercel-storage");
expect(storageChain).toBeDefined();
});
// -------------------------------------------------------------------------
// vercel-sandbox chainTo rules
// -------------------------------------------------------------------------
test("vercel-sandbox file with vm2 import chains to vercel-sandbox", () => {
const filePath = "/project/lib/executor.ts";
const fileContent = [
`import { VM } from 'vm2';`,
`const vm = new VM();`,
`vm.run(userCode);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const sandboxChain = chainResult.injected.find((i) => i.targetSkill === "vercel-sandbox");
expect(sandboxChain).toBeDefined();
});
test("vercel-sandbox file with child_process exec chains to ai-sdk", () => {
const filePath = "/project/lib/executor.ts";
const fileContent = [
`import { exec } from 'child_process';`,
`exec(command, { shell: true }, callback);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find((i) => i.targetSkill === "ai-sdk");
expect(aiSdkChain).toBeDefined();
});
// -------------------------------------------------------------------------
// ai-gateway chainTo rules
// -------------------------------------------------------------------------
test("ai-gateway file with provider API key chains to ai-sdk", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { gateway } from 'ai';`,
`const key = process.env.ANTHROPIC_API_KEY;`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiSdkChain = chainResult.injected.find((i) => i.targetSkill === "ai-sdk");
expect(aiSdkChain).toBeDefined();
});
test("ai-gateway file with cost tracking tags chains to observability (skipIfFileContains)", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { gateway } from 'ai';`,
`const model = gateway({ tags: ['production'], user: userId });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const obsChain = chainResult.injected.find((i) => i.targetSkill === "observability");
expect(obsChain).toBeDefined();
});
test("ai-gateway observability chain is skipped when @vercel/analytics present", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { gateway } from 'ai';`,
`import { track } from '@vercel/analytics';`,
`const model = gateway({ tags: ['production'], user: userId });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const obsChain = chainResult.injected.find((i) => i.targetSkill === "observability");
expect(obsChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// vercel-flags chainTo rules
// -------------------------------------------------------------------------
test("vercel-flags file with LaunchDarkly SDK chains to vercel-flags", () => {
const filePath = "/project/lib/flags.ts";
const fileContent = `import LaunchDarkly from 'launchdarkly-node-server-sdk';\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const flagsChain = chainResult.injected.find((i) => i.targetSkill === "vercel-flags");
expect(flagsChain).toBeDefined();
});
// -------------------------------------------------------------------------
// satori chainTo rules
// -------------------------------------------------------------------------
test("satori file with canvas import chains to vercel-functions", () => {
const filePath = "/project/lib/og.ts";
const fileContent = [
`import { createCanvas } from 'canvas';`,
`const canvas = createCanvas(1200, 630);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const functionsChain = chainResult.injected.find((i) => i.targetSkill === "vercel-functions");
expect(functionsChain).toBeDefined();
});
// -------------------------------------------------------------------------
// vercel-firewall chainTo rules
// -------------------------------------------------------------------------
test("vercel-firewall file with manual IP blocking chains to routing-middleware", () => {
const filePath = "/project/middleware.ts";
const fileContent = [
`const ip = req.ip;`,
`const denyList = ['1.2.3.4'];`,
`if (denyList.includes(ip)) return new Response('Blocked', { status: 403 });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const routingChain = chainResult.injected.find((i) => i.targetSkill === "routing-middleware");
expect(routingChain).toBeDefined();
});
// -------------------------------------------------------------------------
// nextjs additional chainTo rules
// -------------------------------------------------------------------------
test("nextjs file with raw AI fetch URL chains to ai-gateway", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`export async function POST(req: Request) {`,
` const res = await fetch('https://api.openai.com/v1/chat/completions', {`,
` headers: { Authorization: 'Bearer ' + key },`,
` });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gatewayChain = chainResult.injected.find((i) => i.targetSkill === "ai-gateway");
expect(gatewayChain).toBeDefined();
});
test("nextjs raw AI fetch chain is skipped when ai-sdk already imported (skipIfFileContains)", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { generateText } from 'ai';`,
`// legacy: fetch('https://api.openai.com/v1/...')`,
`const result = await generateText({ model: 'openai/gpt-5.4' });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const rawFetchChain = chainResult.injected.find(
(i) => i.targetSkill === "ai-gateway" && i.message?.includes("Raw AI provider fetch"),
);
expect(rawFetchChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// turborepo chainTo rules
// -------------------------------------------------------------------------
test("turborepo file with @vercel/postgres import chains to vercel-storage", () => {
const filePath = "/project/packages/db/index.ts";
const fileContent = `import { sql } from '@vercel/postgres';\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find((i) => i.targetSkill === "vercel-storage");
expect(storageChain).toBeDefined();
});
// -------------------------------------------------------------------------
// Iteration 2: under-covered cross-skill chain rules
// -------------------------------------------------------------------------
test("vercel-storage file with @vercel/postgres chains to vercel-storage with migration message", () => {
const filePath = "/project/lib/db.ts";
const fileContent = `import { sql } from '@vercel/postgres';\nexport const getUsers = () => sql\`SELECT * FROM users\`;\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find(
(i) => i.targetSkill === "vercel-storage" && i.message?.includes("sunset"),
);
expect(storageChain).toBeDefined();
});
test("vercel-storage @vercel/postgres chain skipped when @neondatabase present", () => {
const filePath = "/project/lib/db.ts";
const fileContent = [
`import { neon } from '@neondatabase/serverless';`,
`// migrated from @vercel/postgres`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const storageChain = chainResult.injected.find(
(i) => i.targetSkill === "vercel-storage" && i.message?.includes("sunset"),
);
expect(storageChain).toBeUndefined();
});
test("auth file with bcrypt import chains to auth with managed-auth message", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = [
`import bcrypt from 'bcrypt';`,
`export async function hashPassword(pwd: string) {`,
` return bcrypt.hash(pwd, 10);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find(
(i) => i.targetSkill === "auth" && i.message?.includes("managed"),
);
expect(authChain).toBeDefined();
});
test("auth file with argon2 import chains to auth with managed-auth message", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = `import argon2 from 'argon2';\nconst hash = await argon2.hash(password);\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find(
(i) => i.targetSkill === "auth" && i.message?.includes("managed"),
);
expect(authChain).toBeDefined();
});
test("auth bcrypt chain skipped when @clerk present (skipIfFileContains)", () => {
const filePath = "/project/lib/auth.ts";
const fileContent = [
`import { clerkClient } from '@clerk/nextjs/server';`,
`// legacy: import bcrypt from 'bcrypt';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const authChain = chainResult.injected.find(
(i) => i.targetSkill === "auth" && i.message?.includes("bcrypt"),
);
expect(authChain).toBeUndefined();
});
test("payments file with paypal import chains to payments with Stripe-marketplace message", () => {
const filePath = "/project/lib/checkout.ts";
const fileContent = [
`import paypal from '@paypal/checkout-server-sdk';`,
`const client = new paypal.core.PayPalHttpClient(environment);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const paymentsChain = chainResult.injected.find(
(i) => i.targetSkill === "payments" && i.message?.includes("Stripe"),
);
expect(paymentsChain).toBeDefined();
});
test("payments file with braintree import chains to payments with Stripe-marketplace message", () => {
const filePath = "/project/lib/payments.ts";
const fileContent = `import braintree from 'braintree';\nconst gateway = new braintree.BraintreeGateway({});\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const paymentsChain = chainResult.injected.find(
(i) => i.targetSkill === "payments" && i.message?.includes("Stripe"),
);
expect(paymentsChain).toBeDefined();
});
test("chat-sdk file with @slack/web-api import chains to chat-sdk", () => {
const filePath = "/project/lib/slack-bot.ts";
const fileContent = [
`import { WebClient } from '@slack/web-api';`,
`const web = new WebClient(process.env.SLACK_BOT_TOKEN);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const chatSdkChain = chainResult.injected.find((i) => i.targetSkill === "chat-sdk");
expect(chatSdkChain).toBeDefined();
});
test("observability file with winston import chains to observability with Vercel-native message", () => {
const filePath = "/project/lib/logger.ts";
const fileContent = [
`import winston from 'winston';`,
`export const logger = winston.createLogger({`,
` level: 'info',`,
` transports: [new winston.transports.Console()],`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const obsChain = chainResult.injected.find(
(i) => i.targetSkill === "observability" && i.message?.includes("Vercel"),
);
expect(obsChain).toBeDefined();
});
test("observability file with pino import chains to observability", () => {
const filePath = "/project/lib/logger.ts";
const fileContent = `import pino from 'pino';\nconst logger = pino();\n`;
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const obsChain = chainResult.injected.find((i) => i.targetSkill === "observability");
expect(obsChain).toBeDefined();
});
test("observability winston chain skipped when @opentelemetry present", () => {
const filePath = "/project/lib/logger.ts";
const fileContent = [
`import { trace } from '@opentelemetry/api';`,
`import winston from 'winston';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const obsChain = chainResult.injected.find(
(i) => i.targetSkill === "observability" && i.message?.includes("winston"),
);
expect(obsChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// Negative tests — files that should NOT trigger chains
// -------------------------------------------------------------------------
test("clean Next.js server component does not trigger any chains", () => {
const filePath = "/project/app/page.tsx";
const fileContent = [
`export default function Page() {`,
` return <h1>Hello World</h1>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
expect(chainResult.injected.length).toBe(0);
});
test("clean AI SDK usage with gateway does not trigger provider chains", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
`const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` prompt: 'Hello!',`,
`});`,
`return result.toUIMessageStreamResponse();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// No deprecated patterns, so no chains for outdated API keys or direct providers
const providerKeyChain = chainResult.injected.find(
(i) => i.message?.includes("API key") || i.message?.includes("Provider-specific"),
);
expect(providerKeyChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// ncc chainTo rules
// -------------------------------------------------------------------------
test("ncc file with serverless bundle chains to vercel-functions", () => {
const filePath = "/project/scripts/build.sh";
const fileContent = [
`#!/bin/bash`,
`ncc build api/handler.ts -o dist`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const functionsChain = chainResult.injected.find((i) => i.targetSkill === "vercel-functions");
if (matched.includes("ncc")) {
expect(functionsChain).toBeDefined();
expect(functionsChain!.message).toContain("serverless");
}
});
test("ncc serverless chain is skipped when vercel.json present (skipIfFileContains)", () => {
const filePath = "/project/scripts/build.sh";
const fileContent = [
`#!/bin/bash`,
`ncc build api/serverless.ts -o dist`,
`# vercel.json already configured`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'vercel\.json' should suppress
const functionsChainFromNcc = chainResult.injected.find(
(i) => i.sourceSkill === "ncc" && i.targetSkill === "vercel-functions" && i.message?.includes("serverless"),
);
expect(functionsChainFromNcc).toBeUndefined();
});
test("ncc build chains to deployments-cicd", () => {
const filePath = "/project/build.ts";
const fileContent = [
`import ncc from '@vercel/ncc';`,
`const { code } = await ncc('./src/index.ts');`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const cicdChain = chainResult.injected.find((i) => i.targetSkill === "deployments-cicd");
if (matched.includes("ncc")) {
expect(cicdChain).toBeDefined();
expect(cicdChain!.message).toContain("deploy");
}
});
// -------------------------------------------------------------------------
// cms chainTo rules
// -------------------------------------------------------------------------
test("cms file with getStaticProps chains to nextjs", () => {
const filePath = "/project/pages/blog/[slug].tsx";
const fileContent = [
`import { createClient } from '@sanity/client';`,
``,
`export async function getStaticProps({ params }) {`,
` const post = await client.fetch('*[slug.current == $slug]', params);`,
` return { props: { post }, revalidate: 60 };`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("cms")) {
const nextjsChain = chainResult.injected.find(
(i) => i.sourceSkill === "cms" && i.targetSkill === "nextjs",
);
expect(nextjsChain).toBeDefined();
expect(nextjsChain!.message).toContain("Pages Router");
}
});
test("cms getStaticProps chain is skipped when App Router patterns present (skipIfFileContains)", () => {
const filePath = "/project/app/blog/[slug]/page.tsx";
const fileContent = [
`import { createClient } from '@sanity/client';`,
``,
`export function generateStaticParams() {`,
` return [{ slug: 'hello' }];`,
`}`,
``,
`// Legacy comment: getStaticProps was here`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'generateStaticParams' should suppress
const pagesRouterChain = chainResult.injected.find(
(i) => i.sourceSkill === "cms" && i.targetSkill === "nextjs" && i.message?.includes("Pages Router"),
);
expect(pagesRouterChain).toBeUndefined();
});
test("cms file with revalidatePath chains to runtime-cache", () => {
const filePath = "/project/app/api/revalidate/route.ts";
const fileContent = [
`import { createClient } from 'contentful';`,
`import { revalidatePath } from 'next/cache';`,
``,
`export async function POST(req: Request) {`,
` revalidatePath('/blog');`,
` return Response.json({ revalidated: true });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("cms")) {
const cacheChain = chainResult.injected.find(
(i) => i.sourceSkill === "cms" && i.targetSkill === "runtime-cache",
);
expect(cacheChain).toBeDefined();
expect(cacheChain!.message).toContain("Revalidation");
}
});
// -------------------------------------------------------------------------
// ai-generation-persistence chainTo rules
// -------------------------------------------------------------------------
test("ai-generation-persistence file with streamText chains to ai-gateway", () => {
const filePath = "/project/app/api/generate/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
``,
`export async function POST(req: Request) {`,
` const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` prompt: req.body,`,
` });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("ai-generation-persistence")) {
const gatewayChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-generation-persistence" && i.targetSkill === "ai-gateway",
);
expect(gatewayChain).toBeDefined();
expect(gatewayChain!.message).toContain("cost");
}
});
test("ai-generation-persistence gateway chain is skipped when @ai-sdk/gateway present (skipIfFileContains)", () => {
const filePath = "/project/app/api/generate/route.ts";
const fileContent = [
`import { streamText, gateway } from 'ai';`,
``,
`const result = streamText({`,
` model: gateway('openai/gpt-5.4', { tags: ['prod'] }),`,
` prompt: 'Hello',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'gateway(' should suppress
const gatewayChainFromPersistence = chainResult.injected.find(
(i) => i.sourceSkill === "ai-generation-persistence" && i.targetSkill === "ai-gateway",
);
expect(gatewayChainFromPersistence).toBeUndefined();
});
// -------------------------------------------------------------------------
// observability chainTo rules
// -------------------------------------------------------------------------
test("observability file with console.log error handling chains to vercel-functions", () => {
const filePath = "/project/app/api/data/route.ts";
const fileContent = [
`export async function GET(req: Request) {`,
` try {`,
` const data = await fetchData();`,
` return Response.json(data);`,
` } catch (err) {`,
` console.log("error", err);`,
` return Response.json({ error: 'failed' }, { status: 500 });`,
` }`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("observability")) {
const functionsChain = chainResult.injected.find(
(i) => i.sourceSkill === "observability" && i.targetSkill === "vercel-functions",
);
expect(functionsChain).toBeDefined();
expect(functionsChain!.message).toContain("Console.log");
}
});
test("observability console.log chain is skipped when Sentry present (skipIfFileContains)", () => {
const filePath = "/project/app/api/data/route.ts";
const fileContent = [
`import * as Sentry from '@sentry/nextjs';`,
``,
`try { await fetchData(); } catch (err) {`,
` console.log("error", err);`,
` Sentry.captureException(err);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'captureException|@sentry/' should suppress
const consoleChain = chainResult.injected.find(
(i) => i.sourceSkill === "observability" && i.targetSkill === "vercel-functions" && i.message?.includes("Console.log"),
);
expect(consoleChain).toBeUndefined();
});
test("observability file with Sentry SDK chains to nextjs", () => {
const filePath = "/project/sentry.server.config.ts";
const fileContent = [
`import * as Sentry from '@sentry/nextjs';`,
``,
`Sentry.init({`,
` dsn: process.env.SENTRY_DSN,`,
` tracesSampleRate: 1.0,`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("observability")) {
const nextjsChain = chainResult.injected.find(
(i) => i.sourceSkill === "observability" && i.targetSkill === "nextjs",
);
expect(nextjsChain).toBeDefined();
expect(nextjsChain!.message).toContain("Sentry");
}
});
// -------------------------------------------------------------------------
// sign-in-with-vercel chainTo rules
// -------------------------------------------------------------------------
test("sign-in-with-vercel file with NextAuth chains to auth", () => {
const filePath = "/project/app/api/auth/route.ts";
const fileContent = [
`import NextAuth from 'next-auth';`,
`import { vercelProvider } from './vercel-provider';`,
``,
`const handler = NextAuth({ providers: [vercelProvider] });`,
`export { handler as GET, handler as POST };`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("sign-in-with-vercel")) {
const authChain = chainResult.injected.find(
(i) => i.sourceSkill === "sign-in-with-vercel" && i.targetSkill === "auth",
);
expect(authChain).toBeDefined();
expect(authChain!.message).toContain("NextAuth");
}
});
test("sign-in-with-vercel NextAuth chain is skipped when Clerk present (skipIfFileContains)", () => {
const filePath = "/project/app/api/auth/route.ts";
const fileContent = [
`import NextAuth from 'next-auth';`,
`import { clerkMiddleware } from '@clerk/nextjs/server';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'clerkMiddleware|@clerk/' should suppress
const authChainFromSiVercel = chainResult.injected.find(
(i) => i.sourceSkill === "sign-in-with-vercel" && i.targetSkill === "auth" && i.message?.includes("NextAuth"),
);
expect(authChainFromSiVercel).toBeUndefined();
});
test("sign-in-with-vercel VERCEL_CLIENT_ID chains to env-vars", () => {
const filePath = "/project/lib/vercel-auth.ts";
const fileContent = [
`const clientId = process.env.VERCEL_CLIENT_ID;`,
`const redirectUri = 'https://app.example.com/callback';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("sign-in-with-vercel")) {
const envChain = chainResult.injected.find(
(i) => i.sourceSkill === "sign-in-with-vercel" && i.targetSkill === "env-vars",
);
expect(envChain).toBeDefined();
expect(envChain!.message).toContain("environment variable");
}
});
// -------------------------------------------------------------------------
// json-render chainTo rules
// -------------------------------------------------------------------------
test("json-render file with message.content chains to ai-sdk", () => {
const filePath = "/project/components/chat-message.tsx";
const fileContent = [
`export function ChatMessage({ message }) {`,
` return <p>{message.content}</p>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("json-render")) {
const aiSdkChain = chainResult.injected.find(
(i) => i.sourceSkill === "json-render" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.message).toContain("v5");
}
});
test("json-render file with ReactMarkdown chains to ai-elements", () => {
const filePath = "/project/components/ai-message.tsx";
const fileContent = [
`import ReactMarkdown from 'react-markdown';`,
``,
`export function AIMessage({ text }: { text: string }) {`,
` return <ReactMarkdown>{text}</ReactMarkdown>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("json-render")) {
const aiElementsChain = chainResult.injected.find(
(i) => i.sourceSkill === "json-render" && i.targetSkill === "ai-elements",
);
expect(aiElementsChain).toBeDefined();
expect(aiElementsChain!.message).toContain("markdown");
}
});
// -------------------------------------------------------------------------
// deployments-cicd chainTo rules
// -------------------------------------------------------------------------
test("deployments-cicd file with node-cron chains to cron-jobs", () => {
const filePath = "/project/scripts/deploy.ts";
const fileContent = [
`import cron from 'node-cron';`,
``,
`cron.schedule('0 2 * * *', async () => {`,
` await deployToProduction();`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("deployments-cicd")) {
const cronChain = chainResult.injected.find(
(i) => i.sourceSkill === "deployments-cicd" && i.targetSkill === "cron-jobs",
);
expect(cronChain).toBeDefined();
expect(cronChain!.message).toContain("cron");
}
});
// -------------------------------------------------------------------------
// micro chainTo rules
// -------------------------------------------------------------------------
test("micro file with micro import chains to vercel-functions", () => {
const filePath = "/project/api/hello.ts";
const fileContent = [
`import { send, json } from 'micro';`,
``,
`export default async (req, res) => {`,
` const body = await json(req);`,
` send(res, 200, { hello: body.name });`,
`};`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("micro")) {
const functionsChain = chainResult.injected.find(
(i) => i.sourceSkill === "micro" && i.targetSkill === "vercel-functions",
);
expect(functionsChain).toBeDefined();
expect(functionsChain!.message).toContain("micro");
}
});
// -------------------------------------------------------------------------
// bootstrap chainTo rules
// -------------------------------------------------------------------------
test("bootstrap file with @vercel/postgres chains to vercel-storage", () => {
const filePath = "/project/lib/db.ts";
const fileContent = [
`import { sql } from '@vercel/postgres';`,
`const POSTGRES_URL = process.env.POSTGRES_URL;`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("bootstrap")) {
const storageChain = chainResult.injected.find(
(i) => i.sourceSkill === "bootstrap" && i.targetSkill === "vercel-storage",
);
expect(storageChain).toBeDefined();
expect(storageChain!.message).toContain("sunset");
}
});
// -------------------------------------------------------------------------
// next-forge chainTo rules
// -------------------------------------------------------------------------
test("next-forge file with middleware export chains to routing-middleware", () => {
const filePath = "/project/apps/app/middleware.ts";
const fileContent = [
`import { clerkMiddleware } from '@clerk/nextjs/server';`,
``,
`export default function middleware(req) {`,
` return clerkMiddleware()(req);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("next-forge")) {
const routingChain = chainResult.injected.find(
(i) => i.sourceSkill === "next-forge" && i.targetSkill === "routing-middleware",
);
expect(routingChain).toBeDefined();
expect(routingChain!.message).toContain("middleware");
}
});
// -------------------------------------------------------------------------
// Chain cap with >2 newly added rules (DEFAULT_CHAIN_CAP=2 enforcement)
// -------------------------------------------------------------------------
test("chain cap limits injection to 2 when >2 new rules match (default cap)", () => {
// Simulate a file that triggers chains from 3 different source skills
// using realistic patterns — we use unit-level test for precise control
const mod = await import("../hooks/posttooluse-validate.mjs");
const rci = mod.runChainInjection;
const chainMap = new Map([
["source-1", [{ pattern: "MATCH_1", targetSkill: "micro" }]],
["source-2", [{ pattern: "MATCH_2", targetSkill: "swr" }]],
["source-3", [{ pattern: "MATCH_3", targetSkill: "cron-jobs" }]],
]);
const fileContent = `MATCH_1;\nMATCH_2;\nMATCH_3;\n`;
// No VERCEL_PLUGIN_CHAIN_CAP set — default is 2
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = rci(
fileContent,
["source-1", "source-2", "source-3"],
chainMap,
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(2);
});
// -------------------------------------------------------------------------
// Dedup prevents re-injection of already-seen chained skills (real-world)
// -------------------------------------------------------------------------
test("dedup prevents re-injection when target skill already seen (real-world scenario)", () => {
const filePath = "/project/lib/db.ts";
const fileContent = [
`import { sql } from '@vercel/postgres';`,
`export const getUsers = () => sql\`SELECT * FROM users\`;`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
// Simulate ai-gateway and nextjs already seen — common scenario in a session
const envWithSeen: any = { VERCEL_PLUGIN_SEEN_SKILLS: "nextjs,vercel-storage,ai-gateway" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, envWithSeen);
// nextjs is a common target from vercel-storage's @vercel/postgres chain — should be suppressed
const nextjsChain = chainResult.injected.find(
(i) => i.targetSkill === "nextjs",
);
expect(nextjsChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// Phase 1: vercel-cli chainTo rules
// -------------------------------------------------------------------------
test("vercel-cli vercel.json with crons config chains to cron-jobs", () => {
const filePath = "/project/vercel.json";
const fileContent = JSON.stringify({
"crons": [{ "path": "/api/cron", "schedule": "0 * * * *" }],
}, null, 2);
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("vercel-cli")) {
const cronChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-cli" && i.targetSkill === "cron-jobs",
);
expect(cronChain).toBeDefined();
expect(cronChain!.message).toContain("Cron");
}
});
test("vercel-cli vercel.json with functions config chains to vercel-functions", () => {
const filePath = "/project/vercel.json";
const fileContent = JSON.stringify({
"functions": { "api/**/*.ts": { "maxDuration": 60 } },
}, null, 2);
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("vercel-cli")) {
const functionsChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-cli" && i.targetSkill === "vercel-functions",
);
expect(functionsChain).toBeDefined();
expect(functionsChain!.message).toContain("Functions");
}
});
test("vercel-cli vercel.json with redirects chains to routing-middleware", () => {
const filePath = "/project/vercel.json";
const fileContent = JSON.stringify({
"redirects": [{ "source": "/old", "destination": "/new", "permanent": true }],
}, null, 2);
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("vercel-cli")) {
const routingChain = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-cli" && i.targetSkill === "routing-middleware",
);
expect(routingChain).toBeDefined();
expect(routingChain!.message).toContain("Routing");
}
});
test("vercel-cli functions chain is skipped when crons also present (skipIfFileContains)", () => {
const filePath = "/project/vercel.json";
const fileContent = JSON.stringify({
"crons": [{ "path": "/api/cron", "schedule": "0 * * * *" }],
"functions": { "api/**/*.ts": { "maxDuration": 60 } },
}, null, 2);
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: '"crons"\s*:' should suppress the functions chain
const functionsChainFromCli = chainResult.injected.find(
(i) => i.sourceSkill === "vercel-cli" && i.targetSkill === "vercel-functions",
);
expect(functionsChainFromCli).toBeUndefined();
});
// -------------------------------------------------------------------------
// Phase 1: marketplace chainTo rules
// -------------------------------------------------------------------------
test("marketplace file with Neon env var chains to vercel-storage", () => {
const filePath = "/project/lib/db.ts";
const fileContent = [
`const url = process.env.NEON_DATABASE_URL;`,
`const pool = new Pool({ connectionString: url });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("marketplace")) {
const storageChain = chainResult.injected.find(
(i) => i.sourceSkill === "marketplace" && i.targetSkill === "vercel-storage",
);
expect(storageChain).toBeDefined();
expect(storageChain!.message).toContain("Database");
}
});
test("marketplace file with Clerk env var chains to auth", () => {
const filePath = "/project/.env.local";
const fileContent = [
`CLERK_SECRET_KEY=sk_test_abc123`,
`NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_xyz`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("marketplace")) {
const authChain = chainResult.injected.find(
(i) => i.sourceSkill === "marketplace" && i.targetSkill === "auth",
);
expect(authChain).toBeDefined();
expect(authChain!.message).toContain("Clerk");
}
});
test("marketplace file with Sanity env var chains to cms", () => {
const filePath = "/project/.env.local";
const fileContent = [
`SANITY_PROJECT_ID=abc123`,
`SANITY_DATASET=production`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("marketplace")) {
const cmsChain = chainResult.injected.find(
(i) => i.sourceSkill === "marketplace" && i.targetSkill === "cms",
);
expect(cmsChain).toBeDefined();
expect(cmsChain!.message).toContain("CMS");
}
});
// -------------------------------------------------------------------------
// Phase 1: v0-dev chainTo rules
// -------------------------------------------------------------------------
test("v0-dev file with shadcn component import chains to shadcn", () => {
const filePath = "/project/components/generated.tsx";
const fileContent = [
`import { Button } from '@/components/ui/button';`,
`import { Card } from '@/components/ui/card';`,
``,
`export function GeneratedUI() {`,
` return <Card><Button>Click me</Button></Card>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("v0-dev")) {
const shadcnChain = chainResult.injected.find(
(i) => i.sourceSkill === "v0-dev" && i.targetSkill === "shadcn",
);
expect(shadcnChain).toBeDefined();
expect(shadcnChain!.message).toContain("shadcn");
}
});
test("v0-dev file with AI SDK usage chains to ai-sdk", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`import { useChat } from '@ai-sdk/react';`,
`import { Button } from '@/components/ui/button';`,
``,
`export function Chat() {`,
` const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }) });`,
` return <div>{messages.map(m => <p key={m.id}>{m.content}</p>)}</div>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("v0-dev")) {
const aiSdkChain = chainResult.injected.find(
(i) => i.sourceSkill === "v0-dev" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.message).toContain("AI SDK");
}
});
test("v0-dev ai-sdk chain is skipped when modern patterns present (skipIfFileContains)", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`import { useChat } from '@ai-sdk/react';`,
`import { convertToModelMessages } from 'ai';`,
`import { Button } from '@/components/ui/button';`,
``,
`// File already using v6 patterns`,
`const result = streamText({ model: 'openai/gpt-5.4' });`,
`return result.toUIMessageStreamResponse();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'convertToModelMessages|toUIMessageStreamResponse' should suppress
const aiSdkChainFromV0 = chainResult.injected.find(
(i) => i.sourceSkill === "v0-dev" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChainFromV0).toBeUndefined();
});
test("v0-dev file with next/image import chains to nextjs", () => {
const filePath = "/project/components/hero.tsx";
const fileContent = [
`import Image from 'next/image';`,
`import { Button } from '@/components/ui/button';`,
``,
`export function Hero() {`,
` return <Image src="/hero.jpg" width={1200} height={630} alt="Hero" />;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("v0-dev")) {
const nextjsChain = chainResult.injected.find(
(i) => i.sourceSkill === "v0-dev" && i.targetSkill === "nextjs",
);
expect(nextjsChain).toBeDefined();
expect(nextjsChain!.message).toContain("Next.js");
}
});
// -------------------------------------------------------------------------
// Phase 1: investigation-mode chainTo rules
// -------------------------------------------------------------------------
test("investigation-mode file with workflow imports chains to workflow", () => {
const filePath = "/project/workflows/review.ts";
const fileContent = [
`import { createWorkflow } from 'workflow';`,
``,
`const wf = createWorkflow({ id: 'review' });`,
`// Debugging: workflow stuck at step 3`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("investigation-mode")) {
const workflowChain = chainResult.injected.find(
(i) => i.sourceSkill === "investigation-mode" && i.targetSkill === "workflow",
);
expect(workflowChain).toBeDefined();
expect(workflowChain!.message).toContain("Workflow");
}
});
test("investigation-mode file with VERCEL_URL chains to deployments-cicd", () => {
const filePath = "/project/lib/config.ts";
const fileContent = [
`const baseUrl = process.env.VERCEL_URL`,
` ? \`https://\${process.env.VERCEL_URL}\``,
` : 'http://localhost:3000';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("investigation-mode")) {
const deployChain = chainResult.injected.find(
(i) => i.sourceSkill === "investigation-mode" && i.targetSkill === "deployments-cicd",
);
expect(deployChain).toBeDefined();
expect(deployChain!.message).toContain("Deployment");
}
});
test("investigation-mode deployment chain is skipped when vercel inspect present (skipIfFileContains)", () => {
const filePath = "/project/scripts/debug.sh";
const fileContent = [
`#!/bin/bash`,
`echo "Checking VERCEL_URL..."`,
`vercel inspect $DEPLOYMENT_ID`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'vercel\s+inspect|vercel\s+logs' should suppress
const deployChainFromInvestigation = chainResult.injected.find(
(i) => i.sourceSkill === "investigation-mode" && i.targetSkill === "deployments-cicd",
);
expect(deployChainFromInvestigation).toBeUndefined();
});
test("investigation-mode file with @vercel/analytics chains to observability", () => {
const filePath = "/project/lib/analytics.ts";
const fileContent = [
`import { track } from '@vercel/analytics';`,
``,
`export function trackEvent(name: string, data: Record<string, string>) {`,
` track(name, data);`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("investigation-mode")) {
const obsChain = chainResult.injected.find(
(i) => i.sourceSkill === "investigation-mode" && i.targetSkill === "observability",
);
expect(obsChain).toBeDefined();
expect(obsChain!.message).toContain("Observability");
}
});
// -------------------------------------------------------------------------
// Phase 1: verification chainTo rules
// -------------------------------------------------------------------------
test("verification file with process.env references chains to env-vars", () => {
const filePath = "/project/app/api/data/route.ts";
const fileContent = [
`export async function GET() {`,
` const apiKey = process.env.API_KEY;`,
` const dbUrl = process.env.DATABASE_URL;`,
` return Response.json({ ok: true });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("verification")) {
const envChain = chainResult.injected.find(
(i) => i.sourceSkill === "verification" && i.targetSkill === "env-vars",
);
expect(envChain).toBeDefined();
expect(envChain!.message).toContain("environment variable");
}
});
test("verification env-vars chain is skipped when .env.local referenced (skipIfFileContains)", () => {
const filePath = "/project/app/api/data/route.ts";
const fileContent = [
`// Config pulled from .env.local via vercel env pull`,
`const apiKey = process.env.API_KEY;`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'vercel\s+env\s+pull|\.env\.local' should suppress
const envChainFromVerification = chainResult.injected.find(
(i) => i.sourceSkill === "verification" && i.targetSkill === "env-vars",
);
expect(envChainFromVerification).toBeUndefined();
});
test("verification file with middleware.ts chains to routing-middleware", () => {
const filePath = "/project/middleware.ts";
const fileContent = [
`import { NextResponse } from 'next/server';`,
``,
`export function middleware(req) {`,
` return NextResponse.next();`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("verification")) {
const routingChain = chainResult.injected.find(
(i) => i.sourceSkill === "verification" && i.targetSkill === "routing-middleware",
);
expect(routingChain).toBeDefined();
expect(routingChain!.message).toContain("Middleware");
}
});
test("verification file with streamText chains to ai-sdk", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
``,
`export async function POST(req: Request) {`,
` const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` prompt: 'Hello!',`,
` });`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("verification")) {
const aiSdkChain = chainResult.injected.find(
(i) => i.sourceSkill === "verification" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChain).toBeDefined();
expect(aiSdkChain!.message).toContain("AI SDK");
}
});
test("verification ai-sdk chain is skipped when modern patterns present (skipIfFileContains)", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
`import { DefaultChatTransport } from '@ai-sdk/react';`,
``,
`const result = streamText({ model: 'openai/gpt-5.4' });`,
`return result.toUIMessageStreamResponse();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'toUIMessageStreamResponse|DefaultChatTransport' should suppress
const aiSdkChainFromVerification = chainResult.injected.find(
(i) => i.sourceSkill === "verification" && i.targetSkill === "ai-sdk",
);
expect(aiSdkChainFromVerification).toBeUndefined();
});
// -------------------------------------------------------------------------
// Phase 2 enrichment: nextjs → shadcn
// -------------------------------------------------------------------------
test("nextjs file with @/components/ui import chains to shadcn", () => {
const filePath = "/project/app/dashboard/page.tsx";
const fileContent = [
`import { Card } from '@/components/ui/card';`,
`import { Button } from '@/components/ui/button';`,
``,
`export default function Dashboard() {`,
` return <Card><Button>Save</Button></Card>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("nextjs");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const shadcnChain = chainResult.injected.find(
(i) => i.sourceSkill === "nextjs" && i.targetSkill === "shadcn",
);
expect(shadcnChain).toBeDefined();
expect(shadcnChain!.message).toContain("shadcn");
});
test("nextjs shadcn chain is skipped when components.json referenced (skipIfFileContains)", () => {
const filePath = "/project/app/dashboard/page.tsx";
const fileContent = [
`// Project uses shadcn — see components.json`,
`import { Card } from '@/components/ui/card';`,
``,
`export default function Dashboard() {`,
` return <Card>Content</Card>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'shadcn|components\.json' should suppress
const shadcnChainFromNextjs = chainResult.injected.find(
(i) => i.sourceSkill === "nextjs" && i.targetSkill === "shadcn",
);
expect(shadcnChainFromNextjs).toBeUndefined();
});
// -------------------------------------------------------------------------
// Phase 2 enrichment: next-forge → auth
// -------------------------------------------------------------------------
test("next-forge file with Clerk patterns chains to auth", () => {
const filePath = "/project/apps/app/lib/auth.ts";
const fileContent = [
`import { clerkMiddleware } from '@clerk/nextjs/server';`,
`import { auth } from '@clerk/nextjs/server';`,
``,
`export async function getUser() {`,
` const { userId } = await auth();`,
` return userId;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("next-forge")) {
const authChain = chainResult.injected.find(
(i) => i.sourceSkill === "next-forge" && i.targetSkill === "auth",
);
expect(authChain).toBeDefined();
expect(authChain!.message).toContain("Clerk");
}
});
test("next-forge auth chain is skipped when @auth0 present (skipIfFileContains)", () => {
const filePath = "/project/apps/app/lib/auth.ts";
const fileContent = [
`import { clerkMiddleware } from '@clerk/nextjs/server';`,
`import { auth0 } from '@auth0/nextjs-auth0';`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: '@auth0/|@descope/' should suppress
const authChainFromNextForge = chainResult.injected.find(
(i) => i.sourceSkill === "next-forge" && i.targetSkill === "auth",
);
expect(authChainFromNextForge).toBeUndefined();
});
// -------------------------------------------------------------------------
// Phase 2 enrichment: next-forge → payments
// -------------------------------------------------------------------------
test("next-forge file with Stripe import chains to payments", () => {
const filePath = "/project/apps/app/lib/stripe.ts";
const fileContent = [
`import Stripe from 'stripe';`,
``,
`export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("next-forge")) {
const paymentsChain = chainResult.injected.find(
(i) => i.sourceSkill === "next-forge" && i.targetSkill === "payments",
);
expect(paymentsChain).toBeDefined();
expect(paymentsChain!.message).toContain("Stripe");
}
});
// -------------------------------------------------------------------------
// Phase 2 enrichment: next-forge → email
// -------------------------------------------------------------------------
test("next-forge file with Resend import chains to email", () => {
const filePath = "/project/apps/app/lib/email.ts";
const fileContent = [
`import { Resend } from 'resend';`,
``,
`export const resend = new Resend(process.env.RESEND_API_KEY);`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
if (matched.includes("next-forge")) {
const emailChain = chainResult.injected.find(
(i) => i.sourceSkill === "next-forge" && i.targetSkill === "email",
);
expect(emailChain).toBeDefined();
expect(emailChain!.message).toContain("Email");
}
});
test("file with modern Upstash Redis does not trigger @vercel/kv chain", () => {
const filePath = "/project/lib/cache.ts";
const fileContent = [
`import { Redis } from '@upstash/redis';`,
`const redis = new Redis({ url: process.env.UPSTASH_REDIS_REST_URL });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// Should not trigger sunset @vercel/kv chain
const kvChain = chainResult.injected.find(
(i) => i.message?.includes("@vercel/kv"),
);
expect(kvChain).toBeUndefined();
});
// -------------------------------------------------------------------------
// AI SDK v5→v6 migration chain rules (ai-sdk chainTo)
// -------------------------------------------------------------------------
test("ai-sdk file with generateObject chains to ai-elements (skipIfFileContains Output.object)", () => {
const filePath = "/project/app/api/extract/route.ts";
const fileContent = [
`import { generateObject } from 'ai';`,
`import { z } from 'zod';`,
``,
`const result = await generateObject({`,
` model: 'openai/gpt-5.4',`,
` schema: z.object({ name: z.string() }),`,
` prompt: 'Extract',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const aiElementsChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.targetSkill === "ai-elements" && i.message?.includes("Output.object"),
);
expect(aiElementsChain).toBeDefined();
});
test("ai-sdk generateObject chain is skipped when Output.object already present", () => {
const filePath = "/project/app/api/extract/route.ts";
const fileContent = [
`import { generateText, Output } from 'ai';`,
``,
`const result = await generateText({`,
` model: 'openai/gpt-5.4',`,
` output: Output.object({ schema }),`,
` prompt: 'Extract',`,
`});`,
`// Legacy reference: generateObject was here`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const genObjChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.message?.includes("Output.object"),
);
expect(genObjChain).toBeUndefined();
});
test("ai-sdk file with maxSteps chains to ai-elements (skipIfFileContains stepCountIs)", () => {
const filePath = "/project/app/api/agent/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
``,
`const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` maxSteps: 5,`,
` prompt: 'Plan a trip',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const maxStepsChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.message?.includes("stepCountIs"),
);
expect(maxStepsChain).toBeDefined();
});
test("ai-sdk maxSteps chain is skipped when stepCountIs already present", () => {
const filePath = "/project/app/api/agent/route.ts";
const fileContent = [
`import { streamText, stepCountIs } from 'ai';`,
``,
`const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` stopWhen: stepCountIs(5),`,
` prompt: 'Plan a trip',`,
`});`,
`// Legacy comment: maxSteps: 5`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const maxStepsChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.message?.includes("stepCountIs"),
);
expect(maxStepsChain).toBeUndefined();
});
test("ai-sdk file with toDataStreamResponse chains to ai-elements (skipIfFileContains toUIMessageStreamResponse)", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
``,
`export async function POST(req: Request) {`,
` const result = streamText({ model: 'openai/gpt-5.4', prompt: 'Hello' });`,
` return result.toDataStreamResponse();`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const tdsChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.targetSkill === "ai-elements" && i.message?.includes("toUIMessageStreamResponse"),
);
expect(tdsChain).toBeDefined();
});
test("ai-sdk toDataStreamResponse chain is skipped when toUIMessageStreamResponse present", () => {
const filePath = "/project/app/api/chat/route.ts";
const fileContent = [
`import { streamText } from 'ai';`,
`// Migrated from toDataStreamResponse`,
`return result.toUIMessageStreamResponse();`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const tdsChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.message?.includes("toUIMessageStreamResponse") && i.message?.includes("v5"),
);
expect(tdsChain).toBeUndefined();
});
test("ai-sdk file with handleSubmit chains to ai-elements (skipIfFileContains sendMessage)", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`'use client';`,
`import { useChat } from '@ai-sdk/react';`,
``,
`export function Chat() {`,
` const { messages, input, handleSubmit } = useChat();`,
` return <form onSubmit={handleSubmit}><input value={input} /></form>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const handleSubmitChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.targetSkill === "ai-elements" && i.message?.includes("sendMessage"),
);
expect(handleSubmitChain).toBeDefined();
});
test("ai-sdk handleSubmit chain is skipped when sendMessage already present", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`'use client';`,
`import { useChat } from '@ai-sdk/react';`,
`const { sendMessage } = useChat();`,
`// Legacy: handleSubmit reference in comment`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const handleSubmitChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.message?.includes("sendMessage") && i.targetSkill === "ai-elements",
);
expect(handleSubmitChain).toBeUndefined();
});
test("ai-sdk file with useChat({ api: }) v5 pattern chains to ai-elements", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`'use client';`,
`import { useChat } from '@ai-sdk/react';`,
``,
`export function Chat() {`,
` const { messages } = useChat({ api: '/api/chat' });`,
` return <div>{messages.map(m => <p key={m.id}>{m.content}</p>)}</div>;`,
`}`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-sdk");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const useChatV5Chain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.targetSkill === "ai-elements" && i.message?.includes("DefaultChatTransport"),
);
expect(useChatV5Chain).toBeDefined();
});
test("ai-sdk useChat v5 api chain is skipped when DefaultChatTransport present", () => {
const filePath = "/project/components/chat.tsx";
const fileContent = [
`'use client';`,
`import { useChat, DefaultChatTransport } from '@ai-sdk/react';`,
`const { messages } = useChat({ transport: new DefaultChatTransport({ api: '/api/chat' }) });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const useChatV5Chain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-sdk" && i.message?.includes("DefaultChatTransport") && i.message?.includes("v5"),
);
expect(useChatV5Chain).toBeUndefined();
});
// -------------------------------------------------------------------------
// AI Gateway model deprecation chain rules (ai-gateway chainTo)
// -------------------------------------------------------------------------
test("ai-gateway file with gpt-4o model string chains to ai-sdk", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { generateText } from 'ai';`,
``,
`const result = await generateText({`,
` model: 'openai/gpt-4o',`,
` prompt: 'Hello!',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-gateway");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gpt4oChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("gpt-4o"),
);
expect(gpt4oChain).toBeDefined();
expect(gpt4oChain!.message).toContain("gpt-5.4");
});
test("ai-gateway gpt-4o chain is skipped when gpt-5 already present", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { generateText } from 'ai';`,
`// Migrated from gpt-4o`,
`const result = await generateText({ model: 'openai/gpt-5.4' });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gpt4oChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("gpt-4o"),
);
expect(gpt4oChain).toBeUndefined();
});
test("ai-gateway file with DALL-E reference chains to ai-sdk", () => {
const filePath = "/project/app/api/image/route.ts";
const fileContent = [
`import { generateText } from 'ai';`,
``,
`const result = await generateText({`,
` model: 'openai/dall-e-3',`,
` prompt: 'A sunset',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-gateway");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const dalleChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("DALL-E"),
);
expect(dalleChain).toBeDefined();
expect(dalleChain!.message).toContain("gemini-3.1-flash-image-preview");
});
test("ai-gateway DALL-E chain is skipped when gemini-3 present", () => {
const filePath = "/project/app/api/image/route.ts";
const fileContent = [
`import { generateText } from 'ai';`,
`// Migrated from dall-e-3`,
`const result = await generateText({ model: 'google/gemini-3.1-flash-image-preview' });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const dalleChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("DALL-E"),
);
expect(dalleChain).toBeUndefined();
});
test("ai-gateway file with gemini-2.x model chains to ai-sdk", () => {
const filePath = "/project/app/api/image/route.ts";
const fileContent = [
`import { generateText } from 'ai';`,
``,
`const result = await generateText({`,
` model: 'google/gemini-2.0-flash-exp-image-generation',`,
` prompt: 'Generate an image',`,
`});`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
expect(matched).toContain("ai-gateway");
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gemini2Chain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("Gemini 2.x"),
);
expect(gemini2Chain).toBeDefined();
expect(gemini2Chain!.message).toContain("gemini-3");
});
test("ai-gateway gemini-2.x chain is skipped when gemini-3 present", () => {
const filePath = "/project/app/api/image/route.ts";
const fileContent = [
`import { generateText } from 'ai';`,
`// Old: gemini-2.0-flash-exp-image-generation`,
`const result = await generateText({ model: 'google/gemini-3.1-flash-image-preview' });`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
const gemini2Chain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("Gemini 2.x"),
);
expect(gemini2Chain).toBeUndefined();
});
test("ai-gateway provider API key chain has skipIfFileContains for OIDC", () => {
const filePath = "/project/lib/ai.ts";
const fileContent = [
`import { gateway } from 'ai';`,
`// Using OIDC — no manual keys`,
`const token = process.env.VERCEL_OIDC_TOKEN;`,
`// Legacy reference: ANTHROPIC_API_KEY was here`,
].join("\n");
const matched = matchFileToSkills(filePath, fileContent, data.compiledSkills, data.rulesMap, undefined, data.chainMap);
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const chainResult = runChainInjection(fileContent, matched, data.chainMap, null, ROOT, undefined, cleanEnv);
// skipIfFileContains: 'VERCEL_OIDC|@ai-sdk/gateway|gateway(' should suppress
const apiKeyChain = chainResult.injected.find(
(i) => i.sourceSkill === "ai-gateway" && i.message?.includes("Provider-specific API key"),
);
expect(apiKeyChain).toBeUndefined();
});
});
// ---------------------------------------------------------------------------
// formatOutput with chainResult
// ---------------------------------------------------------------------------
describe("formatOutput with chain injection", () => {
let formatOutput: typeof import("../hooks/src/posttooluse-validate.mts").formatOutput;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-validate.mjs");
formatOutput = mod.formatOutput;
});
test("chain-only output (no violations) produces additionalContext", () => {
const chainResult = {
injected: [
{
sourceSkill: "ai-sdk",
targetSkill: "ai-gateway",
message: "Direct API key detected.",
content: "# AI Gateway\n\nUse OIDC for auth.",
},
],
totalBytes: 40,
};
const result = formatOutput([], ["ai-sdk"], "/test/file.ts", undefined, "claude-code", undefined, chainResult);
const parsed = JSON.parse(result);
expect(parsed.hookSpecificOutput).toBeDefined();
const ctx = parsed.hookSpecificOutput.additionalContext;
// Chain markers present
expect(ctx).toContain("<!-- posttooluse-chain: ai-sdk → ai-gateway -->");
expect(ctx).toContain("<!-- /posttooluse-chain: ai-gateway -->");
expect(ctx).toContain("**Skill context auto-loaded** (ai-gateway):");
expect(ctx).toContain("Direct API key detected.");
expect(ctx).toContain("# AI Gateway");
// Metadata
const meta = extractPostValidation(parsed.hookSpecificOutput);
expect(meta).toBeDefined();
expect(meta.chainedSkills).toEqual(["ai-gateway"]);
expect(meta.errorCount).toBe(0);
});
test("violations + chains appear together in additionalContext", () => {
const violations = [{
skill: "ai-sdk",
line: 3,
message: "Use @ai-sdk/openai provider",
severity: "error" as const,
matchedText: "import OpenAI from 'openai'",
}];
const chainResult = {
injected: [
{
sourceSkill: "ai-sdk",
targetSkill: "ai-gateway",
content: "# AI Gateway\n\nGateway docs here.",
},
],
totalBytes: 35,
};
const result = formatOutput(violations, ["ai-sdk"], "/test/file.ts", undefined, "claude-code", undefined, chainResult);
const parsed = JSON.parse(result);
const ctx = parsed.hookSpecificOutput.additionalContext;
// Both validation and chain content present
expect(ctx).toContain("VALIDATION");
expect(ctx).toContain("[ERROR]");
expect(ctx).toContain("<!-- posttooluse-chain: ai-sdk → ai-gateway -->");
expect(ctx).toContain("# AI Gateway");
const meta = extractPostValidation(parsed.hookSpecificOutput);
expect(meta.errorCount).toBe(1);
expect(meta.chainedSkills).toEqual(["ai-gateway"]);
});
test("no violations and no chains returns empty JSON", () => {
const result = formatOutput([], ["ai-sdk"], "/test/file.ts", undefined, "claude-code", undefined, { injected: [], totalBytes: 0 });
expect(result).toBe("{}");
});
});
// ---------------------------------------------------------------------------
// Integration tests (full hook process spawn)
// ---------------------------------------------------------------------------
describe("posttooluse-chain integration", () => {
let tmpDir: string;
let testFile: string;
beforeEach(() => {
tmpDir = join(tmpdir(), `posttooluse-chain-${Date.now()}-${Math.random().toString(36).slice(2)}`);
mkdirSync(tmpDir, { recursive: true });
testFile = join(tmpDir, "app", "api", "chat", "route.ts");
mkdirSync(join(tmpDir, "app", "api", "chat"), { recursive: true });
});
test("direct provider key in AI SDK file triggers ai-gateway chain injection", async () => {
writeFileSync(testFile, [
`import { generateText } from 'ai';`,
``,
`const key = process.env.OPENAI_API_KEY;`,
`const result = await generateText({`,
` model: 'openai/gpt-5.4',`,
` prompt: 'Hello!',`,
`});`,
].join("\n"));
const { code, parsed, ctx } = await runHook({
tool_name: "Write",
tool_input: { file_path: testFile },
});
expect(code).toBe(0);
// Should have chain injection for ai-gateway
if (ctx) {
// The ai-sdk skill's chainTo should fire for OPENAI_API_KEY
const meta = extractPostValidation(parsed.hookSpecificOutput);
if (meta?.chainedSkills?.length > 0) {
expect(meta.chainedSkills).toContain("ai-gateway");
expect(ctx).toContain("posttooluse-chain:");
}
}
});
test("toDataStreamResponse triggers chain injection", async () => {
writeFileSync(testFile, [
`import { streamText } from 'ai';`,
``,
`export async function POST(req: Request) {`,
` const result = streamText({`,
` model: 'openai/gpt-5.4',`,
` prompt: 'Hello!',`,
` });`,
` return result.toDataStreamResponse();`,
`}`,
].join("\n"));
const { code, ctx } = await runHook({
tool_name: "Write",
tool_input: { file_path: testFile },
});
expect(code).toBe(0);
// ai-sdk skill should match via import, and chainTo for toDataStreamResponse should fire
if (ctx) {
const hasChain = ctx.includes("posttooluse-chain:");
const hasValidation = ctx.includes("VALIDATION");
// Should have at least validation or chain output
expect(hasChain || hasValidation).toBe(true);
}
});
test("clean file with no deprecated patterns produces no chain injection", async () => {
writeFileSync(testFile, [
`import { generateText } from 'ai';`,
`import { useChat } from '@ai-sdk/react';`,
``,
`const result = await generateText({`,
` model: 'openai/gpt-5.4',`,
` prompt: 'Hello!',`,
`});`,
].join("\n"));
const { code, ctx } = await runHook({
tool_name: "Write",
tool_input: { file_path: testFile },
});
expect(code).toBe(0);
// No chain markers expected for clean code
expect(ctx).not.toContain("posttooluse-chain:");
});
});
// ---------------------------------------------------------------------------
// PostToolUse Bash chain: package install detection
// ---------------------------------------------------------------------------
describe("posttooluse-bash-chain: parseInstallCommand", () => {
let parseInstallCommand: typeof import("../hooks/src/posttooluse-bash-chain.mts").parseInstallCommand;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-bash-chain.mjs");
parseInstallCommand = mod.parseInstallCommand;
});
test("parses npm install with single package", () => {
expect(parseInstallCommand("npm install express")).toEqual(["express"]);
});
test("parses npm i shorthand", () => {
expect(parseInstallCommand("npm i express")).toEqual(["express"]);
});
test("parses yarn add with multiple packages", () => {
expect(parseInstallCommand("yarn add express mongoose")).toEqual(["express", "mongoose"]);
});
test("parses pnpm add with scoped package", () => {
expect(parseInstallCommand("pnpm add @vercel/postgres")).toEqual(["@vercel/postgres"]);
});
test("parses bun add", () => {
expect(parseInstallCommand("bun add openai")).toEqual(["openai"]);
});
test("strips version specifiers from unscoped packages", () => {
expect(parseInstallCommand("npm install express@latest")).toEqual(["express"]);
expect(parseInstallCommand("npm install express@^4.0.0")).toEqual(["express"]);
});
test("strips version specifiers from scoped packages", () => {
expect(parseInstallCommand("npm install @vercel/postgres@0.10.0")).toEqual(["@vercel/postgres"]);
});
test("filters out flags like --save-dev and -D", () => {
expect(parseInstallCommand("npm install --save-dev express")).toEqual(["express"]);
expect(parseInstallCommand("npm install -D express")).toEqual(["express"]);
});
test("returns empty array for non-install commands", () => {
expect(parseInstallCommand("npm run dev")).toEqual([]);
expect(parseInstallCommand("git commit -m 'test'")).toEqual([]);
expect(parseInstallCommand("ls -la")).toEqual([]);
});
test("returns empty array for empty or null input", () => {
expect(parseInstallCommand("")).toEqual([]);
expect(parseInstallCommand(null as any)).toEqual([]);
});
test("handles npm install with no packages (bare install)", () => {
// "npm install" with no packages — the regex requires at least one token after install
// but the trailing space matches empty, which gets filtered
const result = parseInstallCommand("npm install");
// Should not crash; result depends on regex behavior
expect(Array.isArray(result)).toBe(true);
});
test("filters out path arguments", () => {
expect(parseInstallCommand("npm install ./local-pkg ../other-pkg")).toEqual([]);
expect(parseInstallCommand("npm install /absolute/path")).toEqual([]);
});
});
describe("posttooluse-bash-chain: runBashChainInjection", () => {
let runBashChainInjection: typeof import("../hooks/src/posttooluse-bash-chain.mts").runBashChainInjection;
let PACKAGE_SKILL_MAP: typeof import("../hooks/src/posttooluse-bash-chain.mts").PACKAGE_SKILL_MAP;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-bash-chain.mjs");
runBashChainInjection = mod.runBashChainInjection;
PACKAGE_SKILL_MAP = mod.PACKAGE_SKILL_MAP;
});
test("express maps to vercel-functions", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["express"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-functions");
expect(result.injected[0].packageName).toBe("express");
expect(result.injected[0].content.length).toBeGreaterThan(0);
expect(result.totalBytes).toBeGreaterThan(0);
});
test("bullmq maps to vercel-queues", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["bullmq"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-queues");
});
test("mongoose maps to vercel-storage", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["mongoose"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-storage");
});
test("@vercel/postgres maps to vercel-storage", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["@vercel/postgres"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-storage");
expect(result.injected[0].message).toContain("sunset");
});
test("openai maps to ai-gateway", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["openai"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-gateway");
});
test("already-seen skill is NOT re-injected", () => {
const envWithSeen: any = { VERCEL_PLUGIN_SEEN_SKILLS: "vercel-functions" };
const result = runBashChainInjection(
["express"],
null,
ROOT,
undefined,
envWithSeen,
);
expect(result.injected.length).toBe(0);
});
test("unknown package produces no injection", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["lodash"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(0);
expect(result.totalBytes).toBe(0);
});
test("multiple packages mapping to same skill only inject once", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(
["express", "fastify"], // both map to vercel-functions
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-functions");
});
test("chain cap is respected", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "", VERCEL_PLUGIN_CHAIN_CAP: "1" };
const result = runBashChainInjection(
["express", "openai", "mongoose"], // 3 different skills
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.injected.length).toBe(1);
});
test("byte budget is respected", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "", VERCEL_PLUGIN_CHAIN_CAP: "10" };
const result = runBashChainInjection(
["express", "openai", "mongoose", "bullmq", "swr"],
null,
ROOT,
undefined,
cleanEnv,
);
expect(result.totalBytes).toBeLessThanOrEqual(18_000);
});
test("PACKAGE_SKILL_MAP has required entries", () => {
expect(PACKAGE_SKILL_MAP["express"]?.skill).toBe("vercel-functions");
expect(PACKAGE_SKILL_MAP["bullmq"]?.skill).toBe("vercel-queues");
expect(PACKAGE_SKILL_MAP["mongoose"]?.skill).toBe("vercel-storage");
expect(PACKAGE_SKILL_MAP["@vercel/postgres"]?.skill).toBe("vercel-storage");
expect(PACKAGE_SKILL_MAP["openai"]?.skill).toBe("ai-gateway");
// New entries
expect(PACKAGE_SKILL_MAP["prisma"]?.skill).toBe("vercel-storage");
expect(PACKAGE_SKILL_MAP["@libsql/client"]?.skill).toBe("vercel-storage");
expect(PACKAGE_SKILL_MAP["stripe"]?.skill).toBe("payments");
expect(PACKAGE_SKILL_MAP["langchain"]?.skill).toBe("ai-sdk");
expect(PACKAGE_SKILL_MAP["@clerk/nextjs"]?.skill).toBe("auth");
expect(PACKAGE_SKILL_MAP["@sanity/client"]?.skill).toBe("cms");
expect(PACKAGE_SKILL_MAP["contentful"]?.skill).toBe("cms");
expect(PACKAGE_SKILL_MAP["resend"]?.skill).toBe("email");
// Remaining entries
expect(PACKAGE_SKILL_MAP["fastify"]?.skill).toBe("vercel-functions");
expect(PACKAGE_SKILL_MAP["koa"]?.skill).toBe("vercel-functions");
expect(PACKAGE_SKILL_MAP["bull"]?.skill).toBe("vercel-queues");
expect(PACKAGE_SKILL_MAP["@vercel/kv"]?.skill).toBe("vercel-storage");
expect(PACKAGE_SKILL_MAP["@anthropic-ai/sdk"]?.skill).toBe("ai-gateway");
expect(PACKAGE_SKILL_MAP["@google/generative-ai"]?.skill).toBe("ai-gateway");
expect(PACKAGE_SKILL_MAP["@langchain/core"]?.skill).toBe("ai-sdk");
expect(PACKAGE_SKILL_MAP["workflow"]?.skill).toBe("workflow");
expect(PACKAGE_SKILL_MAP["ai"]?.skill).toBe("ai-sdk");
expect(PACKAGE_SKILL_MAP["@ai-sdk/react"]?.skill).toBe("ai-sdk");
expect(PACKAGE_SKILL_MAP["@vercel/flags"]?.skill).toBe("vercel-flags");
expect(PACKAGE_SKILL_MAP["swr"]?.skill).toBe("swr");
expect(PACKAGE_SKILL_MAP["node-cron"]?.skill).toBe("cron-jobs");
expect(PACKAGE_SKILL_MAP["cron"]?.skill).toBe("cron-jobs");
// Iteration 2 entries
expect(PACKAGE_SKILL_MAP["next-auth"]?.skill).toBe("auth");
expect(PACKAGE_SKILL_MAP["@slack/bolt"]?.skill).toBe("chat-sdk");
expect(PACKAGE_SKILL_MAP["@slack/web-api"]?.skill).toBe("chat-sdk");
expect(PACKAGE_SKILL_MAP["discord.js"]?.skill).toBe("chat-sdk");
expect(PACKAGE_SKILL_MAP["telegraf"]?.skill).toBe("chat-sdk");
expect(PACKAGE_SKILL_MAP["grammy"]?.skill).toBe("chat-sdk");
expect(PACKAGE_SKILL_MAP["helmet"]?.skill).toBe("vercel-firewall");
expect(PACKAGE_SKILL_MAP["cors"]?.skill).toBe("routing-middleware");
expect(PACKAGE_SKILL_MAP["dotenv"]?.skill).toBe("env-vars");
});
// -------------------------------------------------------------------
// Individual injection tests for every PACKAGE_SKILL_MAP entry
// -------------------------------------------------------------------
test("prisma maps to vercel-storage", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["prisma"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-storage");
expect(result.injected[0].message).toContain("Neon");
});
test("@libsql/client maps to vercel-storage", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@libsql/client"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-storage");
});
test("stripe maps to payments", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["stripe"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("payments");
expect(result.injected[0].message).toContain("Stripe");
});
test("langchain maps to ai-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["langchain"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-sdk");
expect(result.injected[0].message).toContain("LangChain");
});
test("@langchain/core maps to ai-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@langchain/core"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-sdk");
expect(result.injected[0].message).toContain("LangChain");
});
test("@clerk/nextjs maps to auth", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@clerk/nextjs"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("auth");
expect(result.injected[0].message).toContain("Clerk");
});
test("@anthropic-ai/sdk maps to ai-gateway", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@anthropic-ai/sdk"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-gateway");
expect(result.injected[0].message).toContain("Anthropic");
});
test("@google/generative-ai maps to ai-gateway", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@google/generative-ai"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-gateway");
expect(result.injected[0].message).toContain("Google");
});
test("@vercel/kv maps to vercel-storage with sunset message", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@vercel/kv"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-storage");
expect(result.injected[0].message).toContain("sunset");
});
test("@sanity/client maps to cms", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@sanity/client"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("cms");
});
test("contentful maps to cms", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["contentful"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("cms");
});
test("resend maps to email", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["resend"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("email");
});
test("fastify maps to vercel-functions", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["fastify"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-functions");
});
test("koa maps to vercel-functions", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["koa"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-functions");
});
test("bull maps to vercel-queues", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["bull"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-queues");
});
test("workflow maps to workflow", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["workflow"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("workflow");
});
test("ai maps to ai-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["ai"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-sdk");
});
test("@ai-sdk/react maps to ai-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@ai-sdk/react"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("ai-sdk");
});
test("@vercel/flags maps to vercel-flags", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@vercel/flags"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-flags");
});
test("swr maps to swr", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["swr"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("swr");
});
test("node-cron maps to cron-jobs", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["node-cron"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("cron-jobs");
});
test("cron maps to cron-jobs", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["cron"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("cron-jobs");
});
// -------------------------------------------------------------------
// Iteration 2: new package entries
// -------------------------------------------------------------------
test("next-auth maps to auth", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["next-auth"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("auth");
expect(result.injected[0].message).toContain("next-auth");
});
test("@slack/bolt maps to chat-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@slack/bolt"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("chat-sdk");
expect(result.injected[0].message).toContain("Chat SDK");
});
test("@slack/web-api maps to chat-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["@slack/web-api"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("chat-sdk");
});
test("discord.js maps to chat-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["discord.js"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("chat-sdk");
expect(result.injected[0].message).toContain("discord.js");
});
test("telegraf maps to chat-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["telegraf"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("chat-sdk");
expect(result.injected[0].message).toContain("Telegraf");
});
test("grammy maps to chat-sdk", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["grammy"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("chat-sdk");
expect(result.injected[0].message).toContain("Grammy");
});
test("helmet maps to vercel-firewall", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["helmet"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("vercel-firewall");
expect(result.injected[0].message).toContain("Firewall");
});
test("cors maps to routing-middleware", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["cors"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("routing-middleware");
expect(result.injected[0].message).toContain("Routing Middleware");
});
test("dotenv maps to env-vars", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "" };
const result = runBashChainInjection(["dotenv"], null, ROOT, undefined, cleanEnv);
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("env-vars");
expect(result.injected[0].message).toContain("vercel env");
});
// -------------------------------------------------------------------
// Regression: cross-package dedup and budget compliance
// -------------------------------------------------------------------
test("prisma + stripe + langchain install targets 3 distinct skills", () => {
const cleanEnv: any = { VERCEL_PLUGIN_SEEN_SKILLS: "", VERCEL_PLUGIN_CHAIN_CAP: "10" };
const result = runBashChainInjection(
["prisma", "stripe", "langchain"],
null, ROOT, undefined, cleanEnv,
);
expect(result.injected.length).toBe(3);
expect(result.injected[0].skill).toBe("vercel-storage");
expect(result.injected[1].skill).toBe("payments");
expect(result.injected[2].skill).toBe("ai-sdk");
expect(result.totalBytes).toBeLessThanOrEqual(18_000);
});
test("@clerk/nextjs + langchain + stripe dedup with seen skills", () => {
const envWithSeen: any = { VERCEL_PLUGIN_SEEN_SKILLS: "auth,ai-sdk" };
const result = runBashChainInjection(
["@clerk/nextjs", "langchain", "stripe"],
null, ROOT, undefined, envWithSeen,
);
// auth and ai-sdk already seen, only stripe/payments should inject
expect(result.injected.length).toBe(1);
expect(result.injected[0].skill).toBe("payments");
});
});
describe("posttooluse-bash-chain: formatBashChainOutput", () => {
let formatBashChainOutput: typeof import("../hooks/src/posttooluse-bash-chain.mts").formatBashChainOutput;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-bash-chain.mjs");
formatBashChainOutput = mod.formatBashChainOutput;
});
test("empty injections return empty JSON", () => {
expect(formatBashChainOutput({ injected: [], totalBytes: 0 })).toBe("{}");
});
test("non-empty injections produce hookSpecificOutput with additionalContext", () => {
const result = formatBashChainOutput({
injected: [{
packageName: "express",
skill: "vercel-functions",
message: "Express.js detected",
content: "# Vercel Functions\nSome content here.",
}],
totalBytes: 100,
});
const parsed = JSON.parse(result);
expect(parsed.hookSpecificOutput).toBeDefined();
expect(parsed.hookSpecificOutput.additionalContext).toContain("posttooluse-bash-chain:");
expect(parsed.hookSpecificOutput.additionalContext).toContain("vercel-functions");
expect(parsed.hookSpecificOutput.additionalContext).toContain("Express.js detected");
expect(parsed.hookSpecificOutput.additionalContext).toContain("postBashChain:");
});
});
describe("posttooluse-bash-chain: parseBashInput", () => {
let parseBashInput: typeof import("../hooks/src/posttooluse-bash-chain.mts").parseBashInput;
beforeEach(async () => {
const mod = await import("../hooks/posttooluse-bash-chain.mjs");
parseBashInput = mod.parseBashInput;
});
test("parses valid Bash tool input", () => {
const raw = JSON.stringify({
tool_name: "Bash",
tool_input: { command: "npm install express" },
session_id: "test-session",
});
const result = parseBashInput(raw);
expect(result).not.toBeNull();
expect(result!.command).toBe("npm install express");
expect(result!.sessionId).toBe("test-session");
});
test("returns null for non-Bash tool", () => {
const raw = JSON.stringify({
tool_name: "Write",
tool_input: { file_path: "/test.ts" },
});
expect(parseBashInput(raw)).toBeNull();
});
test("returns null for empty input", () => {
expect(parseBashInput("")).toBeNull();
});
test("returns null for invalid JSON", () => {
expect(parseBashInput("not json")).toBeNull();
});
test("returns null for Bash tool with no command", () => {
const raw = JSON.stringify({
tool_name: "Bash",
tool_input: {},
});
expect(parseBashInput(raw)).toBeNull();
});
});