diff --git a/.gitignore b/.gitignore index 31325dc..08290be 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,11 @@ packages/*/.prisma/ /prisma.config.ts tmp/ +# Local agent skill installs +.agents/skills/ +.claude/skills/ +skills-lock.json + # Build and test output dist/ .publish/ diff --git a/README.md b/README.md index 446fa5b..0f0ee5f 100644 --- a/README.md +++ b/README.md @@ -65,8 +65,13 @@ Run tests: ```bash pnpm test +pnpm lint:skills +pnpm test:skills ``` +`pnpm install` also wires the local skill cluster into supported agent +runtimes, including `.agents/skills/` and `.claude/skills/`. + Build the package: ```bash @@ -105,6 +110,13 @@ workflows without introducing product-specific namespaces. The public docs start at `docs/README.md`. +Agent skills for guided Next.js deploys live in `skills/`. Install the cluster +into an app project with: + +```bash +pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v --all +``` + Product behavior is defined in `docs/product`. Start here when changing command behavior: diff --git a/package.json b/package.json index 201a4fc..93da959 100644 --- a/package.json +++ b/package.json @@ -4,14 +4,19 @@ "packageManager": "pnpm@10.30.0", "scripts": { "build:cli": "pnpm --filter @prisma/cli build", + "lint:skills": "node scripts/validate-skills.mjs", + "prepare": "skills add ./skills --skill '*' --agent universal claude-code -y", "prepare:cli-publish": "node scripts/prepare-cli-publish.mjs", "smoke:cli-nextjs": "node scripts/smoke-cli-nextjs-artifact.mjs", + "test:skills": "node --test scripts/validate-skills.test.mjs", "test": "pnpm --filter @prisma/cli test", "prisma-cli": "tsx packages/cli/src/bin.ts", "prisma": "tsx packages/cli/src/bin.ts" }, "devDependencies": { + "gray-matter": "^4.0.3", "pkg-pr-new": "^0.0.75", + "skills": "^1.5.7", "tsx": "^4.19.2" } } diff --git a/packages/cli/README.md b/packages/cli/README.md index e16325d..44dc0f6 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -96,6 +96,19 @@ npx prisma-cli app promote - Stable command groups, flags, and error codes for scripts and agents. - Environment variable values are not printed back to the terminal. +### Agent skills + +For agent-guided Next.js deploys, install the Prisma CLI skill cluster at the +project level. Match the skill ref to the installed CLI version: + +```bash +npx prisma-cli version +pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v --all +``` + +The skills teach agents how to prepare a Next.js app, run `app deploy`, verify +the result, and route CLI / Compute feedback to the right Prisma channel. + --- ## Beta notes diff --git a/packages/cli/src/shell/command-meta.ts b/packages/cli/src/shell/command-meta.ts index ea364c7..760bf7e 100644 --- a/packages/cli/src/shell/command-meta.ts +++ b/packages/cli/src/shell/command-meta.ts @@ -139,11 +139,14 @@ const DESCRIPTORS: CommandDescriptor[] = [ id: "app.deploy", path: ["prisma", "app", "deploy"], description: "Creates a new deployment for the app", + longDescription: + "Agent skills for guided Next.js deploys are available from the Prisma CLI skill cluster.", examples: [ "prisma-cli app deploy", "prisma-cli app deploy --app my-app --env DATABASE_URL=postgresql://example", "prisma-cli app deploy --app my-app --framework nextjs --http-port 3000", "prisma-cli app deploy --branch feat-login --framework hono", + "pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v --all", ], }, { diff --git a/packages/cli/tests/app.test.ts b/packages/cli/tests/app.test.ts index 58589b7..25e3cc2 100644 --- a/packages/cli/tests/app.test.ts +++ b/packages/cli/tests/app.test.ts @@ -146,8 +146,10 @@ describe("app commands", () => { expect(deployHelp.exitCode).toBe(0); expect(deployHelp.stderr).toContain("Creates a new deployment for the app"); + expect(deployHelp.stderr).toContain("Agent skills for guided Next.js deploys"); expect(deployHelp.stderr).toContain("$ prisma-cli app deploy"); expect(deployHelp.stderr).toContain("$ prisma-cli app deploy --app my-app --framework nextjs --http-port 3000"); + expect(deployHelp.stderr).toContain("$ pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v --all"); expect(deployHelp.stderr).toContain("--entry "); expect(deployHelp.stderr).toContain("--framework "); expect(deployHelp.stderr).not.toContain("--build-type "); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bc2e2db..37b31e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,15 @@ importers: .: devDependencies: + gray-matter: + specifier: ^4.0.3 + version: 4.0.3 pkg-pr-new: specifier: ^0.0.75 version: 0.0.75 + skills: + specifier: ^1.5.7 + version: 1.5.9 tsx: specifier: ^4.19.2 version: 4.21.0 @@ -68,7 +74,7 @@ importers: version: 5.9.3 vitest: specifier: ^3.2.4 - version: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0) + version: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0) packages: @@ -593,6 +599,9 @@ packages: resolution: {integrity: sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==} engines: {node: '>=14'} + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + assertion-error@2.0.1: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} @@ -762,6 +771,11 @@ packages: engines: {node: '>=18'} hasBin: true + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + estree-walker@3.0.3: resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} @@ -775,6 +789,10 @@ packages: exsolve@1.0.8: resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + extend-shallow@2.0.1: + resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} + engines: {node: '>=0.10.0'} + fast-fifo@1.3.2: resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} @@ -808,6 +826,10 @@ packages: get-tsconfig@4.13.7: resolution: {integrity: sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==} + gray-matter@4.0.3: + resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} + engines: {node: '>=6.0'} + hookable@5.5.3: resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} @@ -816,6 +838,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} hasBin: true + is-extendable@0.1.1: + resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} + engines: {node: '>=0.10.0'} + is-in-ssh@1.0.0: resolution: {integrity: sha512-jYa6Q9rH90kR1vKB6NM7qqd1mge3Fx4Dhw5TVlK1MUBqhEOuCagrEHMevNuCcbECmXZ0ThXkRm+Ymr51HwEPAw==} engines: {node: '>=20'} @@ -836,11 +862,19 @@ packages: js-tokens@9.0.1: resolution: {integrity: sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==} + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} hasBin: true + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + loupe@3.2.1: resolution: {integrity: sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==} @@ -944,6 +978,10 @@ packages: resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==} engines: {node: '>=18'} + section-matter@1.0.0: + resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} + engines: {node: '>=4'} + semver@7.7.4: resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} @@ -955,10 +993,18 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + skills@1.5.9: + resolution: {integrity: sha512-BjxnhlIy5IrQ30q1nGgNDSf4gy84iNA2o4Ieq4im/HsRvOjGtqvDNpb/xx15FZryXMnAPeDUWqU1ndPRQ0IyOQ==} + engines: {node: '>=18'} + hasBin: true + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + stackback@0.0.2: resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} @@ -976,6 +1022,10 @@ packages: resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} + strip-bom-string@1.0.0: + resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} + engines: {node: '>=0.10.0'} + strip-literal@3.1.0: resolution: {integrity: sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==} @@ -1167,6 +1217,11 @@ packages: resolution: {integrity: sha512-xrcqhWDvtZ7WLmt8G4f3hHy37iK7D2idtosRgkeiSPZEPmBShp0VfmRBLWAPC6zLF48APJ21yfea+RfQMF4/Aw==} engines: {node: '>= 4.0'} + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + snapshots: '@babel/generator@7.29.1': @@ -1497,13 +1552,13 @@ snapshots: chai: 5.3.3 tinyrainbow: 2.0.0 - '@vitest/mocker@3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0))': + '@vitest/mocker@3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0))': dependencies: '@vitest/spy': 3.2.4 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0) '@vitest/pretty-format@3.2.4': dependencies: @@ -1537,6 +1592,10 @@ snapshots: ansis@4.2.0: {} + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + assertion-error@2.0.1: {} ast-kit@2.2.0: @@ -1676,6 +1735,8 @@ snapshots: '@esbuild/win32-ia32': 0.27.7 '@esbuild/win32-x64': 0.27.7 + esprima@4.0.1: {} + estree-walker@3.0.3: dependencies: '@types/estree': 1.0.8 @@ -1690,6 +1751,10 @@ snapshots: exsolve@1.0.8: {} + extend-shallow@2.0.1: + dependencies: + is-extendable: 0.1.1 + fast-fifo@1.3.2: {} fast-string-truncated-width@1.2.1: {} @@ -1715,10 +1780,19 @@ snapshots: dependencies: resolve-pkg-maps: 1.0.0 + gray-matter@4.0.3: + dependencies: + js-yaml: 3.14.2 + kind-of: 6.0.3 + section-matter: 1.0.0 + strip-bom-string: 1.0.0 + hookable@5.5.3: {} is-docker@3.0.0: {} + is-extendable@0.1.1: {} + is-in-ssh@1.0.0: {} is-inside-container@1.0.0: @@ -1733,8 +1807,15 @@ snapshots: js-tokens@9.0.1: {} + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + jsesc@3.1.0: {} + kind-of@6.0.3: {} + loupe@3.2.1: {} magic-string@0.30.21: @@ -1876,14 +1957,25 @@ snapshots: run-applescript@7.1.0: {} + section-matter@1.0.0: + dependencies: + extend-shallow: 2.0.1 + kind-of: 6.0.3 + semver@7.7.4: {} siginfo@2.0.0: {} sisteransi@1.0.5: {} + skills@1.5.9: + dependencies: + yaml: 2.9.0 + source-map-js@1.2.1: {} + sprintf-js@1.0.3: {} + stackback@0.0.2: {} std-env@3.10.0: {} @@ -1906,6 +1998,8 @@ snapshots: dependencies: ansi-regex: 6.2.2 + strip-bom-string@1.0.0: {} + strip-literal@3.1.0: dependencies: js-tokens: 9.0.1 @@ -2003,13 +2097,13 @@ snapshots: undici-types@7.16.0: {} - vite-node@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0): + vite-node@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0): dependencies: cac: 6.7.14 debug: 4.4.3 es-module-lexer: 1.7.0 pathe: 2.0.3 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0) transitivePeerDependencies: - '@types/node' - jiti @@ -2024,7 +2118,7 @@ snapshots: - tsx - yaml - vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0): + vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0): dependencies: esbuild: 0.27.7 fdir: 6.5.0(picomatch@4.0.4) @@ -2037,12 +2131,13 @@ snapshots: fsevents: 2.3.3 jiti: 2.6.1 tsx: 4.21.0 + yaml: 2.9.0 - vitest@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0): + vitest@3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0): dependencies: '@types/chai': 5.2.3 '@vitest/expect': 3.2.4 - '@vitest/mocker': 3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)) + '@vitest/mocker': 3.2.4(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0)) '@vitest/pretty-format': 3.2.4 '@vitest/runner': 3.2.4 '@vitest/snapshot': 3.2.4 @@ -2060,8 +2155,8 @@ snapshots: tinyglobby: 0.2.16 tinypool: 1.1.1 tinyrainbow: 2.0.0 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0) - vite-node: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0) + vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0) + vite-node: 3.2.4(@types/node@24.12.2)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.9.0) why-is-node-running: 2.3.0 optionalDependencies: '@types/node': 24.12.2 @@ -2108,3 +2203,5 @@ snapshots: os-paths: 7.4.0 optionalDependencies: fsevents: 2.3.3 + + yaml@2.9.0: {} diff --git a/scripts/validate-skills.mjs b/scripts/validate-skills.mjs new file mode 100644 index 0000000..f737fb1 --- /dev/null +++ b/scripts/validate-skills.mjs @@ -0,0 +1,133 @@ +#!/usr/bin/env node + +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { createRequire } from "node:module"; +import { join, relative } from "node:path"; +import { fileURLToPath } from "node:url"; + +const require = createRequire(import.meta.url); +const matter = require("gray-matter"); +const repoRoot = join(fileURLToPath(new URL(".", import.meta.url)), ".."); + +export const SKILLS_DIR = "skills"; +export const MAX_DESCRIPTION_LENGTH = 1024; + +export function validateSkillMd(content) { + const errors = []; + if (!/^---\s*\r?\n[\s\S]*?\r?\n---/.test(content)) { + errors.push("missing frontmatter block"); + return errors; + } + + let data; + try { + ({ data } = matter(content)); + } catch (error) { + const message = error instanceof Error ? error.message.split("\n")[0] : String(error); + errors.push(`frontmatter parse error: ${message}`); + return errors; + } + + if (typeof data.name !== "string" || !data.name.trim()) { + errors.push("missing or invalid 'name' (must be a non-empty string)"); + } + + if (typeof data.description !== "string" || !data.description.trim()) { + errors.push("missing or invalid 'description' (must be a non-empty string)"); + } else if (data.description.length > MAX_DESCRIPTION_LENGTH) { + errors.push( + `description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${data.description.length}); use a folded block scalar (description: >) or shorten the text`, + ); + } + + return errors; +} + +function listSkillFiles(root, skillsDir = SKILLS_DIR) { + const base = join(root, skillsDir); + let entries; + try { + entries = readdirSync(base); + } catch { + return []; + } + + const files = []; + for (const name of entries) { + const skillDir = join(base, name); + if (!statSync(skillDir).isDirectory()) continue; + const skillMd = join(skillDir, "SKILL.md"); + try { + if (statSync(skillMd).isFile()) { + files.push({ dirName: name, path: skillMd }); + } + } catch { + // no SKILL.md in this directory + } + } + return files.sort((a, b) => a.dirName.localeCompare(b.dirName)); +} + +export function validateSkillFile(filePath, root) { + let content; + try { + content = readFileSync(filePath, "utf8"); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + return { + file: relative(root, filePath), + errors: [`Unable to read file: ${message}`], + }; + } + + const errors = validateSkillMd(content); + if (errors.length === 0) return null; + return { + file: relative(root, filePath), + errors, + }; +} + +export function runCheck({ root = repoRoot, skillsDir = SKILLS_DIR, files } = {}) { + if (files?.length) { + return files.map((file) => validateSkillFile(file, root)).filter((offence) => offence !== null); + } + + const offences = []; + for (const { path } of listSkillFiles(root, skillsDir)) { + const content = readFileSync(path, "utf8"); + const errors = validateSkillMd(content); + if (errors.length > 0) { + offences.push({ + file: relative(root, path), + errors, + }); + } + } + return offences; +} + +function main() { + const files = process.argv.slice(2).filter((arg) => !arg.startsWith("-")); + const offences = runCheck(files.length > 0 ? { files } : {}); + if (offences.length === 0) { + console.log("All skills passed validation."); + return 0; + } + + console.error("Skill validation failed:\n"); + for (const { file, errors } of offences) { + for (const error of errors) { + console.error(` ${file}: ${error}`); + } + } + console.error( + "\nUnparseable frontmatter is silently skipped by `skills add` during pnpm prepare. " + + "Use a folded block scalar (description: >) when the description contains colons.", + ); + return 1; +} + +if (import.meta.url === `file://${process.argv[1]}`) { + process.exit(main()); +} diff --git a/scripts/validate-skills.test.mjs b/scripts/validate-skills.test.mjs new file mode 100644 index 0000000..bc26616 --- /dev/null +++ b/scripts/validate-skills.test.mjs @@ -0,0 +1,97 @@ +import { deepStrictEqual, strictEqual } from "node:assert/strict"; +import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join, normalize } from "node:path"; +import { describe, it } from "node:test"; +import { MAX_DESCRIPTION_LENGTH, runCheck, validateSkillMd } from "./validate-skills.mjs"; + +const validSkill = `--- +name: example-skill +description: > + A short description that is safe for YAML and long enough to be meaningful + without bare colons on the same line as mapping keys. +--- + +# Example +`; + +describe("validateSkillMd", () => { + it("passes valid frontmatter", () => { + deepStrictEqual(validateSkillMd(validSkill), []); + }); + + it("fails when bare colons break YAML-style parsing", () => { + const broken = `--- +name: drive-discussion +description: Invoke when resolution: before deploy. +--- + +# Discussion +`; + const errors = validateSkillMd(broken); + strictEqual(errors.length, 1); + strictEqual(errors[0].startsWith("frontmatter parse error:"), true); + }); + + it("fails when description exceeds the skill installer limit", () => { + const longDescription = "x".repeat(MAX_DESCRIPTION_LENGTH + 1); + const content = `--- +name: long-desc +description: ${longDescription} +--- + +# Long +`; + const errors = validateSkillMd(content); + deepStrictEqual(errors, [ + `description exceeds ${MAX_DESCRIPTION_LENGTH} characters (${MAX_DESCRIPTION_LENGTH + 1}); use a folded block scalar (description: >) or shorten the text`, + ]); + }); + + it("fails when frontmatter is missing", () => { + deepStrictEqual(validateSkillMd("# No frontmatter\n"), ["missing frontmatter block"]); + }); +}); + +describe("runCheck", () => { + it("validates every direct skill under a skills directory", () => { + const root = mkdtempSync(join(tmpdir(), "validate-skills-")); + const skillDir = join(root, "skills", "good-skill"); + mkdirSync(skillDir, { recursive: true }); + writeFileSync(join(skillDir, "SKILL.md"), validSkill.replace("example-skill", "good-skill")); + + deepStrictEqual(runCheck({ root }), []); + }); + + it("reports offences for broken skills in a directory tree", () => { + const root = mkdtempSync(join(tmpdir(), "validate-skills-bad-")); + const skillDir = join(root, "skills", "bad-skill"); + mkdirSync(skillDir, { recursive: true }); + writeFileSync( + join(skillDir, "SKILL.md"), + `--- +name: bad-skill +description: broken yaml: this colon breaks parsing +--- + +# Bad +`, + ); + + const offences = runCheck({ root }); + strictEqual(offences.length, 1); + strictEqual(normalize(offences[0].file), normalize(join("skills", "bad-skill", "SKILL.md"))); + strictEqual(offences[0].errors[0].startsWith("frontmatter parse error:"), true); + }); + + it("reports unreadable explicit files without aborting", () => { + const root = mkdtempSync(join(tmpdir(), "validate-skills-missing-")); + const missingSkill = join(root, "missing", "SKILL.md"); + + const offences = runCheck({ root, files: [missingSkill] }); + + strictEqual(offences.length, 1); + strictEqual(normalize(offences[0].file), normalize(join("missing", "SKILL.md"))); + strictEqual(offences[0].errors[0].startsWith("Unable to read file:"), true); + }); +}); diff --git a/skills/DEVELOPING.md b/skills/DEVELOPING.md new file mode 100644 index 0000000..07cccd4 --- /dev/null +++ b/skills/DEVELOPING.md @@ -0,0 +1,43 @@ +# Developing Prisma CLI skills + +Contributor guide for the Prisma CLI skill cluster. + +## What this cluster is + +This cluster teaches an LLM agent how to operate the Prisma CLI app-deploy +workflow. Each skill is workflow-scoped and is matched by the `description:` +frontmatter in its `SKILL.md`. + +## Authoring rules + +- Verify every CLI command, flag, error code, config key, and file path against + this repo while writing the skill. If `rg` cannot find the surface, do not + claim it exists. +- Keep the CLI as the owner of project, branch, and app resolution. Skills + should guide the user and interpret CLI output, not duplicate the resolution + algorithm. +- Use `description:` as a runtime matcher. Include phrases users actually type: + `app deploy`, `Prisma Compute`, `deploy my app`, `Next.js`, `project`, + `branch`, `app`, `bug`, `feedback`. +- One workflow per skill. If a workflow grows beyond one goal, split it. +- Every workflow skill must include a "What Prisma CLI doesn't do yet" section + that names gaps honestly and routes feedback to `prisma-cli-feedback`. +- Do not add new CLI commands or flags from a skill change alone. Product + behavior lives in `docs/product` first. + +## Useful verification searches + +```bash +rg "new Command\\(" packages/cli/src +rg "\\.option\\(" packages/cli/src/commands packages/cli/src/shell +rg "code: \\"" packages/cli/src docs/product +rg "app deploy" docs packages/cli/src packages/cli/tests +``` + +## Journey tests + +Journey tests live in `skills/journey-tests/`. They are manual checklists that +install the local skill cluster into a real app, paste a real prompt into an +agent runtime, and verify the expected end state. + +Add or update a journey test whenever a skill workflow changes. diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 0000000..67a26f7 --- /dev/null +++ b/skills/README.md @@ -0,0 +1,51 @@ +# Prisma CLI skills + +Agent skills for the Prisma CLI beta. The cluster teaches an agent how to help a +user deploy an app with `prisma-cli app deploy` without re-deriving the command +surface from docs on every run. + +## What's in the box + +| Skill | Scope | +| --- | --- | +| `prisma-cli` | Router for vague Prisma CLI, Prisma Compute, and app deploy prompts. | +| `prisma-cli-deploy-nextjs` | Guided Next.js deployment workflow. | +| `prisma-cli-feedback` | File CLI / Compute feedback or route open-ended team questions. | + +## Install + +Install the skill cluster at the project level: + +```bash +pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v --all +``` + +For an in-flight branch or local checkout: + +```bash +pnpm dlx skills@latest add prisma/prisma-cli/skills#main --all +pnpm dlx skills@latest add /absolute/path/to/prisma-cli/skills --all +``` + +Use the release tag that matches the installed `@prisma/cli` version. For a CLI +reported as `3.0.0-beta.4`, install from `#cli-v3.0.0-beta.4`. + +Project-level install is intentional. Agent runtimes discover skills once the +`skills` installer materializes them into the current app repo, usually under +runtime-specific directories such as `.agents/skills/` or `.claude/skills/`. + +Inside this repo, `pnpm install` runs the same wiring through the root +`prepare` script so local contributors can test the cluster immediately. + +## Versioning + +The skill source ships with the Prisma CLI repository and is versioned by the +same release tags as the CLI package. Keep the installed skill ref aligned with +the CLI version whose commands the skill references. + +## Contributing + +Read [`DEVELOPING.md`](./DEVELOPING.md) before changing a skill. The short +version: verify every command, flag, error code, and file path against the repo +while authoring, keep each skill workflow-scoped, and add a journey test for any +new or changed workflow. diff --git a/skills/journey-tests/01-nextjs-first-deploy.md b/skills/journey-tests/01-nextjs-first-deploy.md new file mode 100644 index 0000000..ea2bf48 --- /dev/null +++ b/skills/journey-tests/01-nextjs-first-deploy.md @@ -0,0 +1,47 @@ +# Journey: First Next.js Deploy + +## Prompt + +```text +Deploy this Next.js app to Prisma Compute with the Prisma CLI. I am already authenticated. +``` + +## App + +Create a fresh app: + +```bash +pnpm create next-app@latest my-app --yes +cd my-app +``` + +Install the local skill cluster: + +```bash +pnpm dlx skills@latest add /absolute/path/to/prisma-cli/skills --all +``` + +## Expected agent behavior + +- [ ] Uses `prisma-cli` or `prisma-cli-deploy-nextjs`, not Prisma Next skills. +- [ ] Detects this is a Next.js app by inspecting `package.json` or + `next.config.*`. +- [ ] Ensures the Prisma CLI is available, installing `@prisma/cli` only if + needed. +- [ ] Runs `prisma-cli auth whoami` before deploy. +- [ ] If OAuth/browser auth hangs, stops waiting and uses + `PRISMA_SERVICE_TOKEN` when the user can provide it through a safe + secret-handling channel. +- [ ] Ensures `output: "standalone"` is present in `next.config.*`. +- [ ] Runs `prisma-cli app deploy --framework nextjs`. +- [ ] Does not pass `--branch production` unless the prompt explicitly asks. +- [ ] Does not use a dry-run command. +- [ ] Captures the deploy URL. +- [ ] Verifies with `prisma-cli app show --json` or + `prisma-cli app list-deploys --json`. + +## Success criteria + +- Deploy exits successfully. +- A live URL is returned. +- A CLI verification command shows the deployed app/deployment. diff --git a/skills/journey-tests/02-feedback-bug.md b/skills/journey-tests/02-feedback-bug.md new file mode 100644 index 0000000..fce1177 --- /dev/null +++ b/skills/journey-tests/02-feedback-bug.md @@ -0,0 +1,25 @@ +# Journey: Feedback Bug + +## Prompt + +```text +This Prisma Compute deploy behavior looks wrong. Can you file it with the Prisma team? +``` + +## Expected agent behavior + +- [ ] Uses `prisma-cli-feedback`, not `prisma-next-feedback`. +- [ ] Classifies the report as bug, feature request, or Q&A. +- [ ] Collects `prisma-cli version`, Node, package manager, OS, command, and + redacted output. +- [ ] If the report is about a hung command, records the duration, auth source, + and endpoint reachability without printing token values. +- [ ] Redacts secrets such as `DATABASE_URL` values. +- [ ] Produces a GitHub issue title in `bug(): ...` or + `feat(): ...` form when the GitHub path is chosen. +- [ ] Shows the issue body to the user before submission. +- [ ] Does not submit without explicit user confirmation. + +## Success criteria + +- The drafted issue is public-safe and targets `prisma/prisma-cli`. diff --git a/skills/journey-tests/README.md b/skills/journey-tests/README.md new file mode 100644 index 0000000..a3457fe --- /dev/null +++ b/skills/journey-tests/README.md @@ -0,0 +1,31 @@ +# Journey tests + +These are manual checklists for validating the Prisma CLI skill cluster against +real agent behavior. + +## How to run a journey test + +1. Create or check out the app named in the journey file. +2. Install the local skill cluster at the project level: + + ```bash + pnpm dlx skills@latest add /absolute/path/to/prisma-cli/skills --all + ``` + + To test a release tag: + + ```bash + pnpm dlx skills@latest add prisma/prisma-cli/skills#cli-v --all + ``` + +3. Open the project in an agent runtime. +4. Paste the prompt exactly. +5. Watch what the agent does and tick each checklist item. +6. Verify the final state with the Prisma CLI. + +## Journey index + +| File | Skill(s) under test | Acceptance criterion | +| --- | --- | --- | +| `01-nextjs-first-deploy.md` | `prisma-cli`, `prisma-cli-deploy-nextjs` | Fresh Next.js app deploys successfully and is verified by CLI output. | +| `02-feedback-bug.md` | `prisma-cli-feedback` | Agent produces a public-safe GitHub issue body and waits for confirmation. | diff --git a/skills/prisma-cli-deploy-nextjs/SKILL.md b/skills/prisma-cli-deploy-nextjs/SKILL.md new file mode 100644 index 0000000..8b9bf90 --- /dev/null +++ b/skills/prisma-cli-deploy-nextjs/SKILL.md @@ -0,0 +1,181 @@ +--- +name: prisma-cli-deploy-nextjs +description: > + Help an agent deploy a Next.js app with Prisma CLI and Prisma Compute. Use for + "deploy this Next.js app", "deploy my Next app to Prisma", "prisma-cli app + deploy nextjs", "Prisma Compute Next.js deploy", "set up project branch app + for a Next.js app", "Next.js standalone deploy", and first-time app deploy + prompts after auth. Covers CLI install checks, auth, Next.js standalone + output, project/branch/app resolution, deploy verification, logs, and routing + deploy gaps to prisma-cli-feedback. +--- + +# Prisma CLI - Deploy a Next.js App + +Use this skill when the user wants an agent-guided Next.js deploy with the +Prisma CLI beta. + +## When to Use + +- The current repo is a Next.js app or has a `next` dependency. +- The user asks to deploy a Next.js app to Prisma, Prisma Compute, or with + `prisma-cli app deploy`. +- The user has authenticated and wants the agent to complete first deploy. + +## When Not to Use + +- The app is Hono, TanStack Start, Astro, Nuxt, Bun-only, or another framework. + The CLI may support some of these, but this v0 skill is only the Next.js path. +- The user wants to file a bug, feature request, or platform feedback. Use + `prisma-cli-feedback`. +- The user is asking for Prisma ORM migration or client generation help. + +## Key Concepts + +- **The CLI owns target resolution.** `prisma-cli app deploy` resolves or + creates project, branch, and app state. Prefer the bare command for a fresh, + unambiguous app; add `--project`, `--branch`, or `--app` only when the user + asks for a specific target or the CLI reports ambiguity. +- **First deploy binds the directory.** Successful project binding writes local + `.prisma/` state. Treat that as local CLI state, not committed app config. +- **Next.js deploy uses standalone output today.** If the app does not already + set `output: "standalone"` in `next.config.*`, add it before deploy using the + smallest edit that preserves the existing config. +- **Headless auth has a first-class path.** `PRISMA_SERVICE_TOKEN` is the + preferred auth source for CI and agent-run deploys. Use it when OAuth or a + browser callback is unavailable, but never ask the user to paste the token + into chat. +- **Silent commands are a signal.** If `auth whoami`, `auth login`, or + `app deploy` produces no useful output for about 30 seconds, stop the command, + report that auth/API communication appears stalled, and move to diagnostics + instead of waiting indefinitely. +- **Verification is a CLI step.** After deploy, verify with the returned URL and + an introspection command such as `prisma-cli app show --json` or + `prisma-cli app list-deploys --json`. + +## Workflow + +### 1. Confirm the local app + +Inspect `package.json` and `next.config.*`. + +- If `next` is missing, stop and explain that this skill only covers Next.js. +- If `@prisma/cli` is missing, install it as a dev dependency with the user's + package manager, or run via `pnpm dlx @prisma/cli` / `npx prisma-cli` when the + user does not want a dependency. +- Check the CLI with: + +```bash +prisma-cli version +``` + +If the project uses a package-manager script such as `pnpm prisma-cli`, use that +form consistently. + +### 2. Confirm auth + +Ask the CLI for state: + +```bash +prisma-cli auth whoami +``` + +If `PRISMA_SERVICE_TOKEN` is already set in the command environment, keep using +that environment for deploy and verification. If the user is signed out, run: + +```bash +prisma-cli auth login +``` + +If the browser/OAuth path hangs or cannot be used, ask the user to provide a +service token through their normal secret-handling channel, for example by +exporting `PRISMA_SERVICE_TOKEN` in the shell used for the deploy command. Do +not fabricate tokens or ask the user to paste credentials into chat. + +If auth commands hang, collect the command, duration, CLI version, token source +(`PRISMA_SERVICE_TOKEN` versus stored OAuth, without printing token values), and +whether `https://auth.prisma.io` and `https://api.prisma.io/v1/me` are reachable, +then route the finding to `prisma-cli-feedback`. + +### 3. Prepare Next.js standalone output + +Check `next.config.js`, `next.config.mjs`, `next.config.cjs`, or +`next.config.ts`. + +- If it already contains `output: "standalone"`, leave it alone. +- If a config exists without standalone output, add `output: "standalone"` to + the exported config object. +- If there is no config file, create the smallest `next.config.ts` or + `next.config.mjs` that sets standalone output, matching the app's module style + where obvious. + +### 4. Deploy + +For a first deploy to an unambiguous target, prefer: + +```bash +prisma-cli app deploy --framework nextjs +``` + +Use explicit flags only when they express user intent or repair ambiguity: + +```bash +prisma-cli app deploy --project --app --branch --framework nextjs +``` + +Do not use `--branch production` for a first deploy unless the user explicitly +asks for production. The default remote deploy path is preview-oriented. + +### 5. Verify + +Capture the deploy URL from the success output, then verify with the CLI: + +```bash +prisma-cli app show --json +prisma-cli app list-deploys --json +``` + +If the deploy succeeded but the app does not respond, inspect logs: + +```bash +prisma-cli app logs +``` + +When reporting success to the user, include the URL, project/app/branch if the +CLI showed them, and the verification command that passed. + +## Common Pitfalls + +- Do not add a dry-run step. The current CLI does not expose a deploy dry run. +- Do not duplicate project, branch, or app resolution in the agent. Let the CLI + decide, then interpret its output. +- Do not print secret values from `--env`, `.env`, or platform env commands. +- Do not silently retarget production. Production requires explicit intent. +- Do not continue with another framework under this skill; route unsupported + framework needs to `prisma-cli-feedback`. + +## What Prisma CLI doesn't do yet + +- **No in-CLI skill installer.** Install this cluster with `skills add` at the + project level. +- **No deploy dry run.** Use the normal deploy flow and verify afterward. +- **This skill is Next.js-only.** Hono and TanStack Start need follow-up skills. + +Route requests for those gaps to `prisma-cli-feedback`. + +## Reference Files + +- `docs/product/command-spec.md` +- `docs/product/resource-model.md` +- `docs/product/error-conventions.md` +- `examples/next-smoke/README.md` + +## Checklist + +- [ ] Confirmed this is a Next.js app. +- [ ] Confirmed `@prisma/cli` / `prisma-cli` is available. +- [ ] Confirmed auth with `prisma-cli auth whoami` or completed login. +- [ ] Ensured `output: "standalone"` is present in Next.js config. +- [ ] Ran `prisma-cli app deploy --framework nextjs`. +- [ ] Verified success with URL plus `app show --json` or `app list-deploys --json`. +- [ ] Routed CLI/platform gaps to `prisma-cli-feedback`. diff --git a/skills/prisma-cli-feedback/SKILL.md b/skills/prisma-cli-feedback/SKILL.md new file mode 100644 index 0000000..cb8a5ee --- /dev/null +++ b/skills/prisma-cli-feedback/SKILL.md @@ -0,0 +1,192 @@ +--- +name: prisma-cli-feedback +description: > + Hand a Prisma CLI, Prisma Compute, or app deploy question or report off to the + team. Use for bug, bug report, file an issue, report a bug, feature request, + missing feature, deploy failed unexpectedly, project branch app resolution bug, + Prisma Compute feedback, app deploy feedback, platform bug, surprising + behavior, file this, send feedback, ask the Prisma team, talk to Prisma, + Discord, and "is this intended?" prompts. +--- + +# Prisma CLI - Feedback + +This skill is the terminal route for Prisma CLI and Prisma Compute feedback. +Use it when a user wants a platform issue, deploy bug, missing feature, or open +question handed to the team. + +Canonical channels: + +- GitHub issues for bugs and concrete feature requests: + +- Prisma Discord for open-ended Q&A and design discussion: + + +Never submit a public issue without explicit user confirmation. + +## When to Use + +- The user says "this is a bug", "file this", "send feedback", or "feature + request". +- `app deploy`, project resolution, branch resolution, app selection, auth, env, + logs, or deploy output behaves in a surprising way. +- A CLI command hangs or produces no useful output for about 30 seconds during + auth, project resolution, deploy, logs, or verification. +- A Prisma CLI skill's "What Prisma CLI doesn't do yet" section routes here. +- The user wants to ask the Prisma team whether a deploy behavior is intended. + +## When Not to Use + +- The user wants the agent to keep deploying a Next.js app. Use + `prisma-cli-deploy-nextjs` first, then return here only if the CLI/platform + behavior itself appears wrong. +- The issue is in the user's application code and can be fixed locally. +- The request is about Prisma ORM commands rather than the Prisma CLI beta. + +## Pick the Channel + +Use a **GitHub issue** when the user has: + +- a concrete bug, +- a concrete feature request, +- misleading CLI output, error code, or next step, +- a reproducible deploy, auth, project, branch, app, env, or logs problem. + +Use **Discord** when the user has: + +- an open-ended "is this intended?" question, +- design feedback that needs discussion before it becomes a feature request, +- a team-contact request without a concrete bug or feature. + +## Issue Body + +Collect the minimum public-safe context: + +- Prisma CLI version: `prisma-cli version` +- Node version: `node -v` +- Package manager and version +- OS +- Exact command run +- Full output, with secrets redacted +- Whether the command hung, how long it ran, and whether interrupting it exited + cleanly +- Auth source used: `PRISMA_SERVICE_TOKEN`, stored OAuth, or unauthenticated. + Never print token values. +- Reachability checks, if relevant: `https://auth.prisma.io` and + `https://api.prisma.io/v1/me` +- Whether the app is Next.js and whether `output: "standalone"` is set +- Project/branch/app names only if they are safe to share +- Expected behavior and actual behavior +- Workaround, if any + +Bug title: + +```text +bug(): +``` + +Feature title: + +```text +feat(): +``` + +Areas: `auth`, `project`, `branch`, `app`, `deploy`, `env`, `logs`, `docs`, +`output`, `errors`, `skills`. + +Bug body: + +```markdown +## CLI version + + + +## What happened? + + + +## What did you expect to happen? + + + +## Steps to reproduce + +1. +2. +3. + +## Environment + +- Node: +- OS: +- Package manager: +- Framework: + +## Additional context + + +``` + +Feature body: + +```markdown +## What problem are you trying to solve? + + + +## Proposed solution + + + +## Alternatives considered + + + +## Scope and impact + + +``` + +## Submit + +Show the title and body to the user first. Ask for explicit confirmation before +submitting. + +Preferred submission flow: + +1. Write the rendered body to a real temporary file. +2. Submit with: + +```bash +gh issue create --repo prisma/prisma-cli --title "" --body-file <path> +``` + +Do not inline the body through a heredoc or command substitution. + +If `gh` is unavailable, give the user the issue URL and the rendered body. + +## Discord Path + +For Q&A or design feedback, give the user <https://pris.ly/discord> and draft a +short opening message: + +- What they are trying to deploy or understand +- Prisma CLI version +- Relevant command/output, redacted +- The specific question + +Do not auto-post to Discord. + +## What Prisma CLI doesn't do yet + +- **No in-product feedback command.** GitHub issues and Discord are the public + feedback surfaces today. If the user wants `prisma-cli feedback`, file that + as a feature request with this skill. + +## Checklist + +- [ ] Classified as bug, feature request, or Q&A. +- [ ] Redacted secrets and private data. +- [ ] Collected CLI version, command, output, environment, and repro. +- [ ] Showed title/body or Discord draft to the user before submission. +- [ ] Submitted only after explicit confirmation. diff --git a/skills/prisma-cli/SKILL.md b/skills/prisma-cli/SKILL.md new file mode 100644 index 0000000..d73876c --- /dev/null +++ b/skills/prisma-cli/SKILL.md @@ -0,0 +1,65 @@ +--- +name: prisma-cli +description: > + Route a vague Prisma CLI, Prisma Compute, or app deployment prompt to the + right specific skill. Use for "deploy my app to Prisma", "deploy this app", + "set up Prisma Compute", "use prisma-cli app deploy", "help me with Prisma + CLI", "project branch app setup", "what project should this deploy to", + "Prisma Compute feedback", "file a Compute bug", and broad app deploy + questions. Do not use when the prompt clearly matches Next.js deployment or + feedback; load prisma-cli-deploy-nextjs or prisma-cli-feedback directly. +--- + +# Prisma CLI - Router + +This skill is the front door for Prisma CLI app deploy work. It catches broad +or vague prompts and routes to the narrower workflow skill. + +## When to Use + +- The user asks to deploy, set up, or understand a Prisma CLI app deploy flow + but has not named a framework or exact workflow. +- The user says "Prisma Compute" while asking about deploy, project, branch, or + app setup. +- The user is unsure whether they are dealing with project, branch, or app + resolution. + +## When Not to Use + +- The user is deploying a Next.js app or the repo clearly contains Next.js. Use + `prisma-cli-deploy-nextjs`. +- The user wants to file a bug, feature request, feedback, or team question. Use + `prisma-cli-feedback`. +- The user is asking for Prisma ORM commands such as `prisma generate` or + `prisma migrate`. Those are not owned by this CLI beta. + +## Routing Rules + +- If the prompt says `Next.js`, `nextjs`, `next.config`, or the repo has a + `next` dependency, load `prisma-cli-deploy-nextjs`. +- If the prompt is "deploy this app" and the framework is unknown, inspect + `package.json` and framework config. Route to `prisma-cli-deploy-nextjs` only + for Next.js. For other frameworks, explain that this skill cluster only + covers Next.js in v0. +- If the user reports surprising CLI behavior, a missing deploy capability, a + platform issue, or asks to "file this", load `prisma-cli-feedback`. +- If the user asks about project, branch, or app selection during deploy, load + `prisma-cli-deploy-nextjs`; the CLI owns that resolution. + +If no route is clear, ask one short question: "Are you trying to deploy a +Next.js app, report a Prisma CLI issue, or understand the deploy model?" + +## Canonical Model + +The Prisma CLI deploy flow resolves `workspace -> project -> branch -> app`. +`app deploy` is allowed to create missing project, branch, and app state when +the target is unambiguous. The agent should help the user prepare the local app, +run the CLI, and interpret the result; it should not recreate the CLI's +resolution rules in its own logic. + +## Checklist + +- [ ] Routed clear Next.js deploy prompts to `prisma-cli-deploy-nextjs`. +- [ ] Routed bugs, feature requests, gaps, and team questions to + `prisma-cli-feedback`. +- [ ] Did not invent commands outside the Prisma CLI beta surface.