From 56a65b433759a7daa10e5082420714848d9eedb5 Mon Sep 17 00:00:00 2001 From: Ricardo Gonzalez Date: Wed, 5 Aug 2026 19:54:34 -0700 Subject: [PATCH 1/2] Add Vercel Services skill --- README.md | 3 +- generated/build-from-skills.manifest.json | 2 +- generated/skill-catalog.md | 24 ++- generated/skill-manifest.json | 102 ++++++++++++- hooks/session-start-profiler.mjs | 3 + hooks/src/session-start-profiler.mts | 3 + hooks/src/vercel-config.mts | 3 + hooks/vercel-config.mjs | 5 +- skills/vercel-services/SKILL.md | 178 ++++++++++++++++++++++ tests/fixtures/pattern-fixtures.json | 4 +- tests/session-start-activation.test.ts | 21 +++ tests/vercel-config.test.ts | 29 +++- tests/vercel-services-skill.test.ts | 115 ++++++++++++++ vercel.md | 10 +- 14 files changed, 489 insertions(+), 13 deletions(-) create mode 100644 skills/vercel-services/SKILL.md create mode 100644 tests/vercel-services-skill.test.ts diff --git a/README.md b/README.md index a5f4cd4a..7571d9de 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ A text-form relational graph covering: - Common cross-product workflows - Migration awareness for sunset products -### Skills (31 skills) +### Skills (32 skills) | Skill | Covers | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | @@ -78,6 +78,7 @@ A text-form relational graph covering: | `vercel-firewall` | DDoS protection, WAF rules, rate limiting, bot filtering, and IP controls | | `vercel-functions` | Serverless, Edge, Fluid Compute, streaming, Cron Jobs, configuration | | `vercel-sandbox` | Ephemeral Firecracker microVMs for running untrusted/AI-generated code safely | +| `vercel-services` | Multiple frontends and backends in one project, with public rewrites and private service bindings | | `vercel-storage` | Blob, Edge Config, Neon Postgres, Upstash Redis, migration from sunset packages | | `verification` | Full-story verification — infers user story, verifies end-to-end browser → API → data → response | | `workflow` | Workflow DevKit — durable execution, DurableAgent, steps, Worlds, pause/resume | diff --git a/generated/build-from-skills.manifest.json b/generated/build-from-skills.manifest.json index bfd7e1bc..9c43af3a 100644 --- a/generated/build-from-skills.manifest.json +++ b/generated/build-from-skills.manifest.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-07-24T23:01:09.007Z", + "generatedAt": "2026-08-06T02:59:03.202Z", "templates": [ { "template": "agents/ai-architect.md.tmpl", diff --git a/generated/skill-catalog.md b/generated/skill-catalog.md index 1074e602..f68ecc7f 100644 --- a/generated/skill-catalog.md +++ b/generated/skill-catalog.md @@ -1,8 +1,8 @@ # Skill Catalog > Auto-generated by `scripts/generate-catalog.ts` — do not edit manually. -> Generated: 2026-07-31T16:50:47.870Z -> Skills: 31 +> Generated: 2026-08-06T02:59:07.756Z +> Skills: 32 ## Table of Contents @@ -27,6 +27,7 @@ | `env-vars` | 7 | 10 | 4 | 0 | | `microfrontends` | 7 | 4 | 6 | 1 | | `vercel-firewall` | 7 | 0 | 1 | 0 | +| `vercel-services` | 7 | 2 | 1 | 0 | | `vercel-storage` | 7 | 15 | 48 | 8 | | `verification` | 7 | 0 | 8 | 0 | | `auth` | 6 | 14 | 12 | 0 | @@ -380,6 +381,19 @@ **Matched examples:** - `vercel firewall` (bash) +#### `vercel-services` (priority 7) + +**Path patterns:** +- `vercel.json` +- `apps/*/vercel.json` + +**Bash patterns:** +- `\b(?:vercel|vc)\s+dev\b[^\n]*(?:--local|-L)(?:\s|$)` + +**Matched examples:** +- `vercel.json` (path) +- `apps/web/vercel.json` (path) + #### `vercel-storage` (priority 7) **Path patterns:** @@ -922,8 +936,8 @@ Shows which skills compete on shared file targets. | Target File | Competing Skills (priority) | |-------------|---------------------------| -| `vercel.json` | `vercel-functions` (8), `deployments-cicd` (6), `routing-middleware` (6), `vercel-cli` (4) | -| `apps/web/vercel.json` | `vercel-functions` (8), `deployments-cicd` (6), `next-forge` (6), `routing-middleware` (6) | +| `vercel.json` | `vercel-functions` (8), `vercel-services` (7), `deployments-cicd` (6), `routing-middleware` (6), `vercel-cli` (4) | +| `apps/web/vercel.json` | `vercel-functions` (8), `vercel-services` (7), `deployments-cicd` (6), `next-forge` (6), `routing-middleware` (6) | | `middleware.ts` | `auth` (6), `routing-middleware` (6) | | `middleware.js` | `auth` (6), `routing-middleware` (6) | | `src/middleware.ts` | `auth` (6), `routing-middleware` (6) | @@ -970,7 +984,7 @@ Shows which skills compete on shared bash commands. **Priority 8:** `access-protected-vercel-deployment`, `ai-sdk`, `bootstrap`, `chat-sdk`, `eve`, `vercel-functions` -**Priority 7:** `ai-gateway`, `env-vars`, `microfrontends`, `vercel-firewall`, `vercel-storage`, `verification` +**Priority 7:** `ai-gateway`, `env-vars`, `microfrontends`, `vercel-firewall`, `vercel-services`, `vercel-storage`, `verification` **Priority 6:** `auth`, `cdn-caching`, `deployments-cicd`, `next-cache-components`, `next-forge`, `next-upgrade`, `routing-middleware`, `runtime-cache`, `shadcn` diff --git a/generated/skill-manifest.json b/generated/skill-manifest.json index 170937b0..e3bc6a01 100644 --- a/generated/skill-manifest.json +++ b/generated/skill-manifest.json @@ -1,5 +1,5 @@ { - "generatedAt": "2026-07-31T18:16:05.834Z", + "generatedAt": "2026-08-06T02:59:36.893Z", "version": 2, "skills": { "access-protected-vercel-deployment": { @@ -3998,6 +3998,106 @@ "examples": [] } }, + "vercel-services": { + "priority": 7, + "summary": "Compose multiple frontends and backends in one Vercel project", + "docs": [ + "https://vercel.com/docs/services", + "https://vercel.com/docs/services/routing", + "https://vercel.com/docs/services/bindings", + "https://vercel.com/docs/services/config-reference" + ], + "sitemap": "https://vercel.com/sitemap/docs.xml", + "pathPatterns": [ + "vercel.json", + "apps/*/vercel.json" + ], + "bashPatterns": [ + "\\b(?:vercel|vc)\\s+dev\\b[^\\n]*(?:--local|-L)(?:\\s|$)" + ], + "importPatterns": [], + "bodyPath": "skills/vercel-services/SKILL.md", + "pathRegexSources": [ + "^vercel\\.json$", + "^apps\\/[^/]*\\/vercel\\.json$" + ], + "bashRegexSources": [ + "\\b(?:vercel|vc)\\s+dev\\b[^\\n]*(?:--local|-L)(?:\\s|$)" + ], + "importRegexSources": [], + "promptSignals": { + "phrases": [ + "vercel services", + "vercel service binding", + "vercel service bindings", + "multi-service vercel", + "multiple services on vercel", + "frontend and backend on vercel" + ], + "allOf": [ + [ + "frontend", + "backend", + "vercel" + ], + [ + "polyglot", + "vercel" + ], + [ + "multiple", + "services", + "vercel" + ], + [ + "services", + "vercel.json" + ], + [ + "vercel", + "service", + "binding" + ] + ], + "anyOf": [ + "frontend", + "backend", + "monorepo", + "polyglot", + "service", + "binding", + "vercel" + ], + "noneOf": [], + "minScore": 6 + }, + "retrieval": { + "aliases": [ + "Vercel Services", + "multi-service project", + "polyglot project", + "service binding", + "service rewrite" + ], + "intents": [ + "deploy frontend and backend together on Vercel", + "configure multiple services in vercel.json", + "route requests to a service", + "call another service privately" + ], + "entities": [ + "services", + "bindings", + "destination.service", + "root" + ], + "examples": [ + "put a Next.js frontend and FastAPI backend in one Vercel project", + "expose one service at /api and keep another service private", + "inject an internal backend URL into the frontend service" + ] + } + }, "vercel-storage": { "priority": 7, "summary": "", diff --git a/hooks/session-start-profiler.mjs b/hooks/session-start-profiler.mjs index 6d715083..ef3ec681 100644 --- a/hooks/session-start-profiler.mjs +++ b/hooks/session-start-profiler.mjs @@ -117,6 +117,9 @@ function profileProject(projectRoot) { skills.add("routing-middleware"); } if (vercelConfig.functions) skills.add("vercel-functions"); + if (vercelConfig.services) { + skills.add("vercel-services"); + } } return [...skills].sort(); } diff --git a/hooks/src/session-start-profiler.mts b/hooks/src/session-start-profiler.mts index 74b7a59a..be81b10d 100644 --- a/hooks/src/session-start-profiler.mts +++ b/hooks/src/session-start-profiler.mts @@ -197,6 +197,9 @@ export function profileProject(projectRoot: string): string[] { skills.add("routing-middleware"); } if (vercelConfig.functions) skills.add("vercel-functions"); + if (vercelConfig.services) { + skills.add("vercel-services"); + } } return [...skills].sort(); diff --git a/hooks/src/vercel-config.mts b/hooks/src/vercel-config.mts index 2e2c88f7..569de415 100644 --- a/hooks/src/vercel-config.mts +++ b/hooks/src/vercel-config.mts @@ -40,6 +40,8 @@ const KEY_SKILL_MAP: Record = { devCommand: ["deployments-cicd"], ignoreCommand: ["deployments-cicd"], + // Services + services: ["vercel-services"], }; /** @@ -49,6 +51,7 @@ const KEY_SKILL_MAP: Record = { export const VERCEL_JSON_SKILLS = new Set([ "deployments-cicd", "routing-middleware", + "vercel-services", "vercel-functions", ]); diff --git a/hooks/vercel-config.mjs b/hooks/vercel-config.mjs index c0b000db..38de0c93 100644 --- a/hooks/vercel-config.mjs +++ b/hooks/vercel-config.mjs @@ -17,11 +17,14 @@ var KEY_SKILL_MAP = { outputDirectory: ["deployments-cicd"], framework: ["deployments-cicd"], devCommand: ["deployments-cicd"], - ignoreCommand: ["deployments-cicd"] + ignoreCommand: ["deployments-cicd"], + // Services + services: ["vercel-services"] }; var VERCEL_JSON_SKILLS = /* @__PURE__ */ new Set([ "deployments-cicd", "routing-middleware", + "vercel-services", "vercel-functions" ]); function resolveVercelJsonSkills(filePath) { diff --git a/skills/vercel-services/SKILL.md b/skills/vercel-services/SKILL.md new file mode 100644 index 00000000..f1f30d88 --- /dev/null +++ b/skills/vercel-services/SKILL.md @@ -0,0 +1,178 @@ +--- +name: vercel-services +description: Configure and troubleshoot Vercel Services for multiple frontends and backends in one project. Use when composing a polyglot or multi-service application on one Vercel deployment; defining the `services` key, service-targeted rewrites, or service bindings in `vercel.json`; or running all services with `vercel dev`. +summary: Compose multiple frontends and backends in one Vercel project +metadata: + priority: 7 + docs: + - "https://vercel.com/docs/services" + - "https://vercel.com/docs/services/routing" + - "https://vercel.com/docs/services/bindings" + - "https://vercel.com/docs/services/config-reference" + sitemap: "https://vercel.com/sitemap/docs.xml" + pathPatterns: + - 'vercel.json' + - 'apps/*/vercel.json' + bashPatterns: + - '\b(?:vercel|vc)\s+dev\b[^\n]*(?:--local|-L)(?:\s|$)' + importPatterns: [] + promptSignals: + phrases: + - "vercel services" + - "vercel service binding" + - "vercel service bindings" + - "multi-service vercel" + - "multiple services on vercel" + - "frontend and backend on vercel" + allOf: + - [frontend, backend, vercel] + - [polyglot, vercel] + - [multiple, services, vercel] + - [services, vercel.json] + - [vercel, service, binding] + anyOf: + - "frontend" + - "backend" + - "monorepo" + - "polyglot" + - "service" + - "binding" + - "vercel" + noneOf: [] + minScore: 6 +retrieval: + aliases: + - Vercel Services + - multi-service project + - polyglot project + - service binding + - service rewrite + intents: + - deploy frontend and backend together on Vercel + - configure multiple services in vercel.json + - route requests to a service + - call another service privately + entities: + - services + - bindings + - destination.service + - root + examples: + - put a Next.js frontend and FastAPI backend in one Vercel project + - expose one service at /api and keep another service private + - inject an internal backend URL into the frontend service +--- + +# Vercel Services + +Use the `services` model to define multiple independently built components in one Vercel project. + +Services build independently but share one project, domain, preview, deployment, and rollback. Public traffic enters through one ordered route table. Private service-to-service traffic uses explicit bindings. + +## Choose the right structure + +| Need | Use | +| --- | --- | +| One framework can own the whole app, such as Next.js with Route Handlers | One normal Vercel project without Services | +| Multiple frameworks or backends should deploy and roll back together on one domain | Vercel Services | +| Applications need separate domains or independent deploy lifecycles | Separate Vercel projects in a monorepo | +| Independently deployed frontends must render as one site | Vercel Microfrontends | + +Do not introduce Services just to split one framework into arbitrary processes. Use it when an independently built component has a real runtime, framework, dependency, or deployment-boundary reason to exist. + +## Define services and public ingress + +Each service requires a `root` relative to `vercel.json`. Let Vercel detect the framework unless pinning it is necessary. Set `entrypoint` relative to the service root when the runtime needs one. + +```json filename="vercel.json" +{ + "services": { + "frontend": { + "root": "apps/web", + "framework": "nextjs", + "bindings": [ + { + "type": "service", + "service": "backend", + "format": "url", + "env": "BACKEND_INTERNAL_URL" + } + ] + }, + "backend": { + "root": "apps/backend", + "framework": "fastapi", + "entrypoint": "main:app" + } + }, + "rewrites": [ + { "source": "/api/(.*)", "destination": { "service": "backend" } }, + { "source": "/(.*)", "destination": { "service": "frontend" } } + ] +} +``` + +The top-level rewrites expose the services. A service without a matching top-level rewrite is not publicly reachable. + +Keep configuration ownership clear: + +- Keep public `rewrites`, `redirects`, `headers`, and other URL behavior at the top level. +- Put `functions`, `installCommand`, `buildCommand`, `devCommand`, `ignoreCommand`, `outputDirectory`, and framework settings on the service that owns them. +- Put service-local `headers`, `redirects`, `rewrites`, or `routes` inside a service only when they should run after public ingress selects that service. +- Set `runtime: "container"` when a service must build from a Dockerfile or OCI image. Use `entrypoint` for a nonstandard Dockerfile and `command` to override the image command. + +## Route requests correctly + +Top-level rewrites are evaluated in order. Put specific rules before the catch-all. + +Routing into a service is final. If the selected service returns a 404 or 405, Vercel does not try the next top-level rewrite. + +The service receives the original request path. With the example above, `GET /api/users` reaches `backend` as `/api/users`, not `/users`. Define the backend route accordingly. + +`destination.path` changes route lookup inside the service, not the path observed by application code. Use a service-level `request.path` transform only when application code must observe a changed path. + +## Call services privately with bindings + +Declare a binding on the caller service, name the target service, and choose the environment variable that receives the generated URL. Do not hardcode deployment hostnames or manually set binding variables. + +```ts +const url = new URL('/api/users', process.env.BACKEND_INTERNAL_URL); +const response = await fetch(url); +``` + +Bindings are deployment-aware and do not create public routes. They are available to functions at runtime, not during builds or in Routing Middleware. Internal calls skip the public Firewall, Deployment Protection, top-level middleware, and CDN pipeline. + +A binding grants network reachability, not application authentication. Add service-level authorization when the target must verify the caller. + +Native Go and Rust runtime services cannot currently consume bindings. Build those callers as container services when they need bindings. Node.js and Python services can use bindings directly. + +## Develop and deploy + +Run every service and inject local binding variables: + +```bash +vercel dev +``` + +Use local-only mode when cloud authentication is unnecessary: + +```bash +vercel dev -L +``` + +Deploy the project normally with `vercel` or Git integration. All services participate in the same preview and production deployment. + +## Troubleshoot + +- **No public traffic reaches a service:** add a top-level rewrite targeting it. +- **The wrong service receives a request:** reorder rewrites so the most specific rule comes first and the catch-all is last. +- **A backend returns 404:** confirm its routes include the public prefix because Vercel preserves the original request path. +- **A binding variable is missing:** declare the binding on the caller and access it from runtime function code, not build code or middleware. +- **Build settings are ignored or rejected:** move top-level build and runtime fields into the owning service. +- **Framework detection is wrong:** set that service's `framework` or `entrypoint` explicitly instead of changing the whole project. + +## Related skills + +- Deployment commands and CI: `⤳ skill: deployments-cicd` +- Function runtime behavior and limits: `⤳ skill: vercel-functions` +- Independent frontend deployments: `⤳ skill: microfrontends` diff --git a/tests/fixtures/pattern-fixtures.json b/tests/fixtures/pattern-fixtures.json index 2bfb5b7d..af657f9c 100644 --- a/tests/fixtures/pattern-fixtures.json +++ b/tests/fixtures/pattern-fixtures.json @@ -2,10 +2,10 @@ "$schema": "Pattern fixture dry-run: maps file paths and bash commands to expected skill matches", "fixtures": [ { - "description": "vercel.json triggers multiple skills (routing, deploy, cli, functions)", + "description": "vercel.json triggers multiple skills (routing, deploy, cli, functions, services)", "type": "path", "input": "vercel.json", - "expectedSkills": ["routing-middleware", "deployments-cicd", "vercel-cli", "vercel-functions"] + "expectedSkills": ["routing-middleware", "deployments-cicd", "vercel-cli", "vercel-functions", "vercel-services"] }, { "description": "next.config.ts triggers nextjs, turbopack, next-cache-components, and next-upgrade", diff --git a/tests/session-start-activation.test.ts b/tests/session-start-activation.test.ts index 29387f6f..e0fe46e8 100644 --- a/tests/session-start-activation.test.ts +++ b/tests/session-start-activation.test.ts @@ -104,6 +104,27 @@ describe("session-start activation", () => { expect(readSessionFile(testSessionId, "likely-skills")).toContain("vercel-storage"); }); + test("services configuration profiles the Vercel Services skill", async () => { + const projectDir = join(tempDir, "services-project"); + mkdirSync(projectDir); + writeFileSync( + join(projectDir, "vercel.json"), + JSON.stringify({ + services: { + web: { root: "apps/web" }, + api: { root: "apps/api", entrypoint: "main:app" }, + }, + }), + ); + + const result = await runProfiler(projectDir); + + expect(result.code).toBe(0); + expect(readSessionFile(testSessionId, "likely-skills")).toContain( + "vercel-services", + ); + }); + test("the eve package activates the plugin and profiles the eve skill", async () => { const projectDir = join(tempDir, "eve-project"); mkdirSync(projectDir); diff --git a/tests/vercel-config.test.ts b/tests/vercel-config.test.ts index 31083f6f..d5aeae0a 100644 --- a/tests/vercel-config.test.ts +++ b/tests/vercel-config.test.ts @@ -101,6 +101,13 @@ describe("vercel-config.mjs", () => { expect(result!.relevantSkills.has("vercel-functions")).toBe(true); }); + test("resolveVercelJsonSkills maps the services key", () => { + const p = join(tempDir, "vercel-services.json"); + writeFileSync(p, JSON.stringify({ services: {} }), "utf-8"); + const result = resolveVercelJsonSkills(p); + expect(result!.relevantSkills.has("vercel-services")).toBe(true); + }); + test("resolveVercelJsonSkills maps mixed keys correctly", () => { const p = writeVercelJson({ redirects: [], @@ -119,6 +126,7 @@ describe("vercel-config.mjs", () => { "redirects", "rewrites", "headers", "cleanUrls", "trailingSlash", "functions", "regions", "builds", "buildCommand", "installCommand", "outputDirectory", "framework", + "services", ]; for (const key of expectedKeys) { const content: Record = { [key]: {} }; @@ -181,6 +189,20 @@ describe("vercel.json key-aware routing (collision scenarios)", () => { expect(injectedSkills[0]).toBe("vercel-functions"); }); + test("vercel.json with services key → vercel-services stays at top", async () => { + const filePath = writeVercelJson({ + services: { + frontend: { root: "apps/web" }, + backend: { root: "apps/backend", entrypoint: "main:app" }, + }, + }); + const { injectedSkills } = await runHook({ + tool_name: "Edit", + tool_input: { file_path: filePath }, + }); + expect(injectedSkills[0]).toBe("vercel-services"); + }); + test("Scenario 5: vercel.json with all key types → no duplicates, cap respected", async () => { const filePath = writeVercelJson({ redirects: [], @@ -198,7 +220,12 @@ describe("vercel.json key-aware routing (collision scenarios)", () => { // The injected skills should all be current vercel.json skills. // The ones that ARE injected should all be vercel.json skills for (const skill of injectedSkills) { - expect(["deployments-cicd", "routing-middleware", "vercel-functions"]).toContain(skill); + expect([ + "deployments-cicd", + "routing-middleware", + "vercel-functions", + "vercel-services", + ]).toContain(skill); } }); diff --git a/tests/vercel-services-skill.test.ts b/tests/vercel-services-skill.test.ts new file mode 100644 index 00000000..e66da1a9 --- /dev/null +++ b/tests/vercel-services-skill.test.ts @@ -0,0 +1,115 @@ +import { beforeAll, describe, expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; +import { + compileSkillPatterns, + matchBashWithReason, + matchPathWithReason, + type CompiledSkillEntry, +} from "../hooks/src/patterns.mts"; +import { + compilePromptSignals, + matchPromptWithReason, + normalizePromptText, + type CompiledPromptSignals, +} from "../hooks/src/prompt-patterns.mts"; +import { loadValidatedSkillMap } from "../src/shared/skill-map-loader.ts"; + +const ROOT = resolve(import.meta.dirname, ".."); +const SKILL_PATH = resolve(ROOT, "skills/vercel-services/SKILL.md"); + +let compiledPromptSignals: CompiledPromptSignals; +let compiledSkill: CompiledSkillEntry; + +beforeAll(() => { + const { skills } = loadValidatedSkillMap(resolve(ROOT, "skills")); + const services = skills["vercel-services"]; + + expect(services).toBeDefined(); + expect(services.priority).toBe(7); + expect(services.promptSignals).toBeDefined(); + + compiledPromptSignals = compilePromptSignals(services.promptSignals!); + compiledSkill = compileSkillPatterns({ + "vercel-services": services, + })[0]; +}); + +function matchesPrompt(prompt: string): boolean { + return matchPromptWithReason( + normalizePromptText(prompt), + compiledPromptSignals, + ).matched; +} + +describe("Vercel Services prompt activation", () => { + test.each([ + "Set up Vercel Services for a Next.js frontend and FastAPI backend.", + "Configure a Vercel service binding from orders to inventory.", + "Deploy multiple services on Vercel in one project.", + "Configure services in vercel.json for this polyglot app.", + ])("matches Services intent: %s", (prompt) => { + expect(matchesPrompt(prompt)).toBe(true); + }); + + test.each([ + "Create a systemd service for this daemon.", + "Deploy these microservices to Kubernetes.", + "Add an API Route Handler to my existing Next.js app.", + "Split these frontends into independently deployed microfrontends.", + "Configure an AWS ECS service.", + "Create a Cloud Foundry service binding.", + ])("does not match unrelated service intent: %s", (prompt) => { + expect(matchesPrompt(prompt)).toBe(false); + }); +}); + +describe("Vercel Services artifact activation", () => { + test("matches vercel.json paths", () => { + expect( + matchPathWithReason("vercel.json", compiledSkill.compiledPaths), + ).not.toBeNull(); + expect( + matchPathWithReason( + "apps/dashboard/vercel.json", + compiledSkill.compiledPaths, + ), + ).not.toBeNull(); + }); + + test.each(["vercel dev -L", "vc dev --local"])( + "matches local Services development: %s", + (command) => { + expect( + matchBashWithReason(command, compiledSkill.compiledBash), + ).not.toBeNull(); + }, + ); + + test.each(["vercel dev", "npm run dev", "docker compose up"])( + "does not match generic development commands: %s", + (command) => { + expect( + matchBashWithReason(command, compiledSkill.compiledBash), + ).toBeNull(); + }, + ); +}); + +describe("Vercel Services guidance", () => { + test("teaches the current configuration and routing model", () => { + const skill = readFileSync(SKILL_PATH, "utf8"); + + expect(skill).toContain('"services"'); + expect(skill).toContain('"bindings"'); + expect(skill).toContain('"destination": { "service": "backend" }'); + expect(skill).toContain("The service receives the original request path"); + expect(skill).toContain("Declare a binding on the caller service"); + expect(skill).toContain( + "runtime, not during builds or in Routing Middleware", + ); + expect(skill).toContain( + "Native Go and Rust runtime services cannot currently consume bindings", + ); + }); +}); diff --git a/vercel.md b/vercel.md index 59167c43..c8d1b0c2 100644 --- a/vercel.md +++ b/vercel.md @@ -1,4 +1,4 @@ -# Vercel Ecosystem — Relational Knowledge Graph (as of Jul 30, 2026) +# Vercel Ecosystem — Relational Knowledge Graph (as of Aug 5, 2026) > This document is the master reference for understanding the entire Vercel ecosystem. > It maps every product, library, CLI, API, and service — how they relate, when to use each, @@ -94,6 +94,14 @@ VERCEL PLATFORM 📖 docs: https://vercel.com/docs │ ↔ @vercel/microfrontends (Next.js, SvelteKit, React Router, Vite) │ ⤳ skill: microfrontends │ +├── Services (multiple components in one Vercel project) +│ ⊃ Independently built frontends and backends +│ ⊃ Top-level rewrites (shared public routing and domain) +│ ⊃ Service bindings (private, deployment-aware communication) +│ → Deployment Engine (atomic previews, deploys, and rollbacks) +│ ↔ Vercel CLI (vercel dev runs all services locally) +│ ⤳ skill: vercel-services 📖 docs: https://vercel.com/docs/services +│ └── Teams & Access Control ↔ Vercel REST API ↔ Vercel Dashboard From d0acdb5a24243e017aa5d7fe8773d46ffddfae60 Mon Sep 17 00:00:00 2001 From: Ricardo Gonzalez Date: Wed, 5 Aug 2026 20:29:39 -0700 Subject: [PATCH 2/2] Prepare Services skill release; bump to 0.47.0 --- .claude-plugin/plugin.json | 2 +- .cursor-plugin/plugin.json | 2 +- .kimi-plugin/plugin.json | 2 +- .plugin/plugin.json | 2 +- README.md | 3 ++- generated/build-from-skills.manifest.json | 2 +- hooks/telemetry.mjs | 2 +- package.json | 2 +- tests/telemetry.test.ts | 4 ++-- 9 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 9d8dac21..66e631bc 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vercel", - "version": "0.46.0", + "version": "0.47.0", "description": "Build and deploy web apps and agents", "author": { "name": "Vercel", diff --git a/.cursor-plugin/plugin.json b/.cursor-plugin/plugin.json index 68b828b5..b75a49e5 100644 --- a/.cursor-plugin/plugin.json +++ b/.cursor-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vercel", - "version": "0.46.0", + "version": "0.47.0", "description": "Build and deploy web apps and agents", "author": { "name": "Vercel", diff --git a/.kimi-plugin/plugin.json b/.kimi-plugin/plugin.json index 56d4278d..baad5381 100644 --- a/.kimi-plugin/plugin.json +++ b/.kimi-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vercel-plugin", - "version": "0.46.0", + "version": "0.47.0", "description": "Comprehensive Vercel ecosystem plugin — relational knowledge graph, skills for every major product, specialized agents, and Vercel conventions. Turns any AI agent into a Vercel expert.", "keywords": [ "vercel", diff --git a/.plugin/plugin.json b/.plugin/plugin.json index 510de2fe..c8e953c8 100644 --- a/.plugin/plugin.json +++ b/.plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "vercel-plugin", - "version": "0.46.0", + "version": "0.47.0", "description": "Comprehensive Vercel ecosystem plugin — relational knowledge graph, skills for every major product, specialized agents, and Vercel conventions. Turns any AI agent into a Vercel expert.", "author": { "name": "Vercel", diff --git a/README.md b/README.md index 7571d9de..8b76471d 100644 --- a/README.md +++ b/README.md @@ -228,7 +228,7 @@ Exits non-zero if any `SKILL.md` is stale. Add to CI to catch drift. vercel-plugin/ ├── .plugin/plugin.json # Plugin manifest ├── vercel.md # Ecosystem graph + conventions (injected via SessionStart hook) -├── skills/ # 31 skills +├── skills/ # 32 skills │ ├── ai-sdk/ # Upstream-synced skill example: │ │ ├── overlay.yaml # Plugin injection metadata │ │ ├── upstream/ # Pure upstream content @@ -268,6 +268,7 @@ bun run build:from-skills # Stage 4: Resolve template includes - Workflow DevKit (DurableAgent, Worlds, open source) - AI Gateway (100+ models, provider routing, cost tracking) - Vercel Functions (Fluid Compute, streaming, Cron Jobs) +- Vercel Services (multiple frontends and backends, public rewrites, private bindings) - Storage (Blob, Edge Config, Neon Postgres, Upstash Redis) - Routing Middleware (request interception, Edge/Node.js/Bun runtimes) - Runtime Cache API (per-region KV cache, tag-based invalidation) diff --git a/generated/build-from-skills.manifest.json b/generated/build-from-skills.manifest.json index 9c43af3a..bfd7e1bc 100644 --- a/generated/build-from-skills.manifest.json +++ b/generated/build-from-skills.manifest.json @@ -1,6 +1,6 @@ { "version": 1, - "generatedAt": "2026-08-06T02:59:03.202Z", + "generatedAt": "2026-07-24T23:01:09.007Z", "templates": [ { "template": "agents/ai-architect.md.tmpl", diff --git a/hooks/telemetry.mjs b/hooks/telemetry.mjs index 44736f15..4dc6030d 100644 --- a/hooks/telemetry.mjs +++ b/hooks/telemetry.mjs @@ -5,7 +5,7 @@ import { join, dirname } from "path"; import { homedir } from "os"; var BRIDGE_ENDPOINT = "https://telemetry.vercel.com/api/vercel-plugin/v1/events"; var FLUSH_TIMEOUT_MS = 3e3; -var PLUGIN_VERSION = true ? "0.46.0" : "0.43.0"; +var PLUGIN_VERSION = true ? "0.47.0" : "0.43.0"; var ACTIVE_SESSION_TTL_MS = 60 * 60 * 1e3; var DAU_STAMP_PATH = join(homedir(), ".config", "vercel-plugin", "dau-stamp"); var FIRST_USE_STAMP_PATH = join(homedir(), ".config", "vercel-plugin", "first-use-stamp"); diff --git a/package.json b/package.json index 180dbfdf..22df0dd8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vercel-plugin", - "version": "0.46.0", + "version": "0.47.0", "private": true, "license": "Apache-2.0", "bin": { diff --git a/tests/telemetry.test.ts b/tests/telemetry.test.ts index e4351353..a4bcb8a5 100644 --- a/tests/telemetry.test.ts +++ b/tests/telemetry.test.ts @@ -115,7 +115,7 @@ describe("telemetry controls", () => { expect(result.activeSessionMarker).toEqual({ schema: 1, active: true, - pluginVersion: "0.46.0", + pluginVersion: "0.47.0", updatedAt: Date.parse("2026-05-15T12:00:00.000Z"), expiresAt: Date.parse("2026-05-15T13:00:00.000Z"), }); @@ -131,7 +131,7 @@ describe("telemetry controls", () => { }), expect.objectContaining({ key: "plugin:version", - value: "0.46.0", + value: "0.47.0", }), ], ]);