rogasper commited on
Commit
cfc59e4
·
0 Parent(s):

initial commit

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .agents/skills/better-auth-best-practices/SKILL.md +183 -0
  2. .agents/skills/hono/SKILL.md +579 -0
  3. .agents/skills/shadcn/SKILL.md +250 -0
  4. .agents/skills/shadcn/agents/openai.yml +5 -0
  5. .agents/skills/shadcn/assets/shadcn-small.png +3 -0
  6. .agents/skills/shadcn/assets/shadcn.png +3 -0
  7. .agents/skills/shadcn/cli.md +276 -0
  8. .agents/skills/shadcn/customization.md +208 -0
  9. .agents/skills/shadcn/evals/evals.json +47 -0
  10. .agents/skills/shadcn/mcp.md +94 -0
  11. .agents/skills/shadcn/rules/base-vs-radix.md +308 -0
  12. .agents/skills/shadcn/rules/composition.md +197 -0
  13. .agents/skills/shadcn/rules/forms.md +194 -0
  14. .agents/skills/shadcn/rules/icons.md +101 -0
  15. .agents/skills/shadcn/rules/styling.md +161 -0
  16. .agents/skills/turborepo/SKILL.md +951 -0
  17. .agents/skills/turborepo/command/turborepo.md +70 -0
  18. .agents/skills/turborepo/references/best-practices/RULE.md +241 -0
  19. .agents/skills/turborepo/references/best-practices/dependencies.md +246 -0
  20. .agents/skills/turborepo/references/best-practices/packages.md +335 -0
  21. .agents/skills/turborepo/references/best-practices/structure.md +297 -0
  22. .agents/skills/turborepo/references/boundaries/RULE.md +126 -0
  23. .agents/skills/turborepo/references/caching/RULE.md +153 -0
  24. .agents/skills/turborepo/references/caching/gotchas.md +190 -0
  25. .agents/skills/turborepo/references/caching/remote-cache.md +127 -0
  26. .agents/skills/turborepo/references/ci/RULE.md +79 -0
  27. .agents/skills/turborepo/references/ci/github-actions.md +162 -0
  28. .agents/skills/turborepo/references/ci/patterns.md +145 -0
  29. .agents/skills/turborepo/references/ci/vercel.md +103 -0
  30. .agents/skills/turborepo/references/cli/RULE.md +100 -0
  31. .agents/skills/turborepo/references/cli/commands.md +297 -0
  32. .agents/skills/turborepo/references/configuration/RULE.md +235 -0
  33. .agents/skills/turborepo/references/configuration/global-options.md +239 -0
  34. .agents/skills/turborepo/references/configuration/gotchas.md +368 -0
  35. .agents/skills/turborepo/references/configuration/tasks.md +321 -0
  36. .agents/skills/turborepo/references/environment/RULE.md +123 -0
  37. .agents/skills/turborepo/references/environment/gotchas.md +175 -0
  38. .agents/skills/turborepo/references/environment/modes.md +101 -0
  39. .agents/skills/turborepo/references/filtering/RULE.md +148 -0
  40. .agents/skills/turborepo/references/filtering/patterns.md +152 -0
  41. .agents/skills/turborepo/references/watch/RULE.md +99 -0
  42. .agents/skills/vercel-composition-patterns/AGENTS.md +917 -0
  43. .agents/skills/vercel-composition-patterns/README.md +60 -0
  44. .agents/skills/vercel-composition-patterns/SKILL.md +88 -0
  45. .agents/skills/vercel-composition-patterns/metadata.json +11 -0
  46. .agents/skills/vercel-composition-patterns/rules/_sections.md +29 -0
  47. .agents/skills/vercel-composition-patterns/rules/_template.md +24 -0
  48. .agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md +94 -0
  49. .agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md +108 -0
  50. .agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md +84 -0
.agents/skills/better-auth-best-practices/SKILL.md ADDED
@@ -0,0 +1,183 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: better-auth-best-practices
3
+ description: Configure Better Auth server and client, set up database adapters, manage sessions, add plugins, and handle environment variables. Use when users mention Better Auth, betterauth, auth.ts, or need to set up TypeScript authentication with email/password, OAuth, or plugin configuration.
4
+ ---
5
+
6
+ # Better Auth Integration Guide
7
+
8
+ **Always consult [better-auth.com/docs](https://better-auth.com/docs) for code examples and latest API.**
9
+
10
+ ---
11
+
12
+ ## Setup Workflow
13
+
14
+ 1. Install: `npm install better-auth`
15
+ 2. Set env vars: `BETTER_AUTH_SECRET` and `BETTER_AUTH_URL`
16
+ 3. Create `auth.ts` with database + config
17
+ 4. Create route handler for your framework
18
+ 5. Run `npx @better-auth/cli@latest migrate`
19
+ 6. Verify: call `GET /api/auth/ok` — should return `{ status: "ok" }`
20
+
21
+ ---
22
+
23
+ ## Quick Reference
24
+
25
+ ### Environment Variables
26
+
27
+ - `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`
28
+ - `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)
29
+
30
+ Only define `baseURL`/`secret` in config if env vars are NOT set.
31
+
32
+ ### File Location
33
+
34
+ CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.
35
+
36
+ ### CLI Commands
37
+
38
+ - `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
39
+ - `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
40
+ - `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
41
+
42
+ **Re-run after adding/changing plugins.**
43
+
44
+ ---
45
+
46
+ ## Core Config Options
47
+
48
+ | Option | Notes |
49
+ | ------------------ | ---------------------------------------------- |
50
+ | `appName` | Optional display name |
51
+ | `baseURL` | Only if `BETTER_AUTH_URL` not set |
52
+ | `basePath` | Default `/api/auth`. Set `/` for root. |
53
+ | `secret` | Only if `BETTER_AUTH_SECRET` not set |
54
+ | `database` | Required for most features. See adapters docs. |
55
+ | `secondaryStorage` | Redis/KV for sessions & rate limits |
56
+ | `emailAndPassword` | `{ enabled: true }` to activate |
57
+ | `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |
58
+ | `plugins` | Array of plugins |
59
+ | `trustedOrigins` | CSRF whitelist |
60
+
61
+ ---
62
+
63
+ ## Database
64
+
65
+ **Direct connections:** Pass `pg.Pool`, `mysql2` pool, `better-sqlite3`, or `bun:sqlite` instance.
66
+
67
+ **ORM adapters:** Import from `better-auth/adapters/drizzle`, `better-auth/adapters/prisma`, `better-auth/adapters/mongodb`.
68
+
69
+ **Critical:** Better Auth uses adapter model names, NOT underlying table names. If Prisma model is `User` mapping to table `users`, use `modelName: "user"` (Prisma reference), not `"users"`.
70
+
71
+ ---
72
+
73
+ ## Session Management
74
+
75
+ **Storage priority:**
76
+
77
+ 1. If `secondaryStorage` defined → sessions go there (not DB)
78
+ 2. Set `session.storeSessionInDatabase: true` to also persist to DB
79
+ 3. No database + `cookieCache` → fully stateless mode
80
+
81
+ **Cookie cache strategies:**
82
+
83
+ - `compact` (default) - Base64url + HMAC. Smallest.
84
+ - `jwt` - Standard JWT. Readable but signed.
85
+ - `jwe` - Encrypted. Maximum security.
86
+
87
+ **Key options:** `session.expiresIn` (default 7 days), `session.updateAge` (refresh interval), `session.cookieCache.maxAge`, `session.cookieCache.version` (change to invalidate all sessions).
88
+
89
+ ---
90
+
91
+ ## User & Account Config
92
+
93
+ **User:** `user.modelName`, `user.fields` (column mapping), `user.additionalFields`, `user.changeEmail.enabled` (disabled by default), `user.deleteUser.enabled` (disabled by default).
94
+
95
+ **Account:** `account.modelName`, `account.accountLinking.enabled`, `account.storeAccountCookie` (for stateless OAuth).
96
+
97
+ **Required for registration:** `email` and `name` fields.
98
+
99
+ ---
100
+
101
+ ## Email Flows
102
+
103
+ - `emailVerification.sendVerificationEmail` - Must be defined for verification to work
104
+ - `emailVerification.sendOnSignUp` / `sendOnSignIn` - Auto-send triggers
105
+ - `emailAndPassword.sendResetPassword` - Password reset email handler
106
+
107
+ ---
108
+
109
+ ## Security
110
+
111
+ **In `advanced`:**
112
+
113
+ - `useSecureCookies` - Force HTTPS cookies
114
+ - `disableCSRFCheck` - ⚠️ Security risk
115
+ - `disableOriginCheck` - ⚠️ Security risk
116
+ - `crossSubDomainCookies.enabled` - Share cookies across subdomains
117
+ - `ipAddress.ipAddressHeaders` - Custom IP headers for proxies
118
+ - `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false`
119
+
120
+ **Rate limiting:** `rateLimit.enabled`, `rateLimit.window`, `rateLimit.max`, `rateLimit.storage` ("memory" | "database" | "secondary-storage").
121
+
122
+ ---
123
+
124
+ ## Hooks
125
+
126
+ **Endpoint hooks:** `hooks.before` / `hooks.after` - Array of `{ matcher, handler }`. Use `createAuthMiddleware`. Access `ctx.path`, `ctx.context.returned` (after), `ctx.context.session`.
127
+
128
+ **Database hooks:** `databaseHooks.user.create.before/after`, same for `session`, `account`. Useful for adding default values or post-creation actions.
129
+
130
+ **Hook context (`ctx.context`):** `session`, `secret`, `authCookies`, `password.hash()`/`verify()`, `adapter`, `internalAdapter`, `generateId()`, `tables`, `baseURL`.
131
+
132
+ ---
133
+
134
+ ## Plugins
135
+
136
+ **Import from dedicated paths for tree-shaking:**
137
+
138
+ ```
139
+ import { twoFactor } from "better-auth/plugins/two-factor"
140
+ ```
141
+
142
+ NOT `from "better-auth/plugins"`.
143
+
144
+ **Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.
145
+
146
+ Client plugins go in `createAuthClient({ plugins: [...] })`.
147
+
148
+ ---
149
+
150
+ ## Client
151
+
152
+ Import from: `better-auth/client` (vanilla), `better-auth/react`, `better-auth/vue`, `better-auth/svelte`, `better-auth/solid`.
153
+
154
+ Key methods: `signUp.email()`, `signIn.email()`, `signIn.social()`, `signOut()`, `useSession()`, `getSession()`, `revokeSession()`, `revokeSessions()`.
155
+
156
+ ---
157
+
158
+ ## Type Safety
159
+
160
+ Infer types: `typeof auth.$Infer.Session`, `typeof auth.$Infer.Session.user`.
161
+
162
+ For separate client/server projects: `createAuthClient<typeof auth>()`.
163
+
164
+ ---
165
+
166
+ ## Common Gotchas
167
+
168
+ 1. **Model vs table name** - Config uses ORM model name, not DB table name
169
+ 2. **Plugin schema** - Re-run CLI after adding plugins
170
+ 3. **Secondary storage** - Sessions go there by default, not DB
171
+ 4. **Cookie cache** - Custom session fields NOT cached, always re-fetched
172
+ 5. **Stateless mode** - No DB = session in cookie only, logout on cache expiry
173
+ 6. **Change email flow** - Sends to current email first, then new email
174
+
175
+ ---
176
+
177
+ ## Resources
178
+
179
+ - [Docs](https://better-auth.com/docs)
180
+ - [Options Reference](https://better-auth.com/docs/reference/options)
181
+ - [LLMs.txt](https://better-auth.com/llms.txt)
182
+ - [GitHub](https://github.com/better-auth/better-auth)
183
+ - [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)
.agents/skills/hono/SKILL.md ADDED
@@ -0,0 +1,579 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: hono
3
+ description: Use when building Hono web applications or when the user asks about Hono APIs, routing, middleware, JSX, validation, testing, or streaming. TRIGGER when code imports from 'hono' or 'hono/*', or user mentions Hono. Use `npx hono request` to test endpoints.
4
+ ---
5
+
6
+ # Hono Skill
7
+
8
+ Build Hono web applications. This skill provides inline API knowledge for AI. Use `npx hono request` to test endpoints. If the `hono-docs` MCP server is configured, prefer its tools for the latest documentation over the inline reference.
9
+
10
+ ## Hono CLI Usage
11
+
12
+ ### Request Testing
13
+
14
+ Test endpoints without starting an HTTP server. Uses `app.request()` internally.
15
+
16
+ ```bash
17
+ # GET request
18
+ npx hono request [file] -P /path
19
+
20
+ # POST request with JSON body
21
+ npx hono request [file] -X POST -P /api/users -d '{"name": "test"}'
22
+ ```
23
+
24
+ **Note:** Do not pass credentials directly in CLI arguments. Use environment variables for sensitive values. `hono request` does not support Cloudflare Workers bindings (KV, D1, R2, etc.). When bindings are required, use `workers-fetch` instead:
25
+
26
+ ```bash
27
+ npx workers-fetch /path
28
+ npx workers-fetch -X POST -H "Content-Type:application/json" -d '{"name":"test"}' /api/users
29
+ ```
30
+
31
+ ---
32
+
33
+ ## Hono API Reference
34
+
35
+ ### App Constructor
36
+
37
+ ```ts
38
+ import { Hono } from "hono";
39
+
40
+ const app = new Hono();
41
+
42
+ // With TypeScript generics
43
+ type Env = {
44
+ Bindings: { DATABASE: D1Database; KV: KVNamespace };
45
+ Variables: { user: User };
46
+ };
47
+ const app = new Hono<Env>();
48
+ ```
49
+
50
+ ### Routing Methods
51
+
52
+ ```ts
53
+ app.get("/path", handler);
54
+ app.post("/path", handler);
55
+ app.put("/path", handler);
56
+ app.delete("/path", handler);
57
+ app.patch("/path", handler);
58
+ app.options("/path", handler);
59
+ app.all("/path", handler); // all HTTP methods
60
+ app.on("PURGE", "/path", handler); // custom method
61
+ app.on(["PUT", "DELETE"], "/path", handler); // multiple methods
62
+ ```
63
+
64
+ ### Routing Patterns
65
+
66
+ ```ts
67
+ // Path parameters
68
+ app.get("/user/:name", (c) => {
69
+ const name = c.req.param("name");
70
+ return c.json({ name });
71
+ });
72
+
73
+ // Multiple params
74
+ app.get("/posts/:id/comments/:commentId", (c) => {
75
+ const { id, commentId } = c.req.param();
76
+ });
77
+
78
+ // Optional parameters
79
+ app.get("/api/animal/:type?", (c) => c.text("Animal!"));
80
+
81
+ // Wildcards
82
+ app.get("/wild/*/card", (c) => c.text("Wildcard"));
83
+
84
+ // Regexp constraints
85
+ app.get("/post/:date{[0-9]+}/:title{[a-z]+}", (c) => {
86
+ const { date, title } = c.req.param();
87
+ });
88
+
89
+ // Chained routes
90
+ app
91
+ .get("/endpoint", (c) => c.text("GET"))
92
+ .post((c) => c.text("POST"))
93
+ .delete((c) => c.text("DELETE"));
94
+ ```
95
+
96
+ ### Route Grouping
97
+
98
+ ```ts
99
+ // Using route()
100
+ const api = new Hono();
101
+ api.get("/users", (c) => c.json([]));
102
+
103
+ const app = new Hono();
104
+ app.route("/api", api); // mounts at /api/users
105
+
106
+ // Using basePath()
107
+ const app = new Hono().basePath("/api");
108
+ app.get("/users", (c) => c.json([])); // GET /api/users
109
+ ```
110
+
111
+ ### Error Handling
112
+
113
+ ```ts
114
+ app.notFound((c) => c.json({ message: "Not Found" }, 404));
115
+
116
+ app.onError((err, c) => {
117
+ console.error(err);
118
+ return c.json({ message: "Internal Server Error" }, 500);
119
+ });
120
+ ```
121
+
122
+ ---
123
+
124
+ ## Context (c)
125
+
126
+ ### Response Methods
127
+
128
+ ```ts
129
+ c.text("Hello"); // text/plain
130
+ c.json({ message: "Hello" }); // application/json
131
+ c.html("<h1>Hello</h1>"); // text/html
132
+ c.redirect("/new-path"); // 302 redirect
133
+ c.redirect("/new-path", 301); // 301 redirect
134
+ c.body("raw body", 200, headers); // raw response
135
+ c.notFound(); // 404 response
136
+ ```
137
+
138
+ ### Headers & Status
139
+
140
+ ```ts
141
+ c.status(201);
142
+ c.header("X-Custom", "value");
143
+ c.header("Cache-Control", "no-store");
144
+ ```
145
+
146
+ ### Variables (request-scoped data)
147
+
148
+ ```ts
149
+ // In middleware
150
+ c.set("user", { id: 1, name: "Alice" });
151
+
152
+ // In handler
153
+ const user = c.get("user");
154
+ // or
155
+ const user = c.var.user;
156
+ ```
157
+
158
+ ### Environment (Cloudflare Workers)
159
+
160
+ ```ts
161
+ const value = await c.env.KV.get("key");
162
+ const db = c.env.DATABASE;
163
+ c.executionCtx.waitUntil(promise);
164
+ ```
165
+
166
+ ### Renderer
167
+
168
+ ```ts
169
+ app.use(async (c, next) => {
170
+ c.setRenderer((content) =>
171
+ c.html(
172
+ <html><body>{content}</body></html>
173
+ )
174
+ )
175
+ await next()
176
+ })
177
+
178
+ app.get('/', (c) => c.render(<h1>Hello</h1>))
179
+ ```
180
+
181
+ ---
182
+
183
+ ## HonoRequest (c.req)
184
+
185
+ ```ts
186
+ c.req.param("id"); // path parameter
187
+ c.req.param(); // all path params as object
188
+ c.req.query("page"); // query string parameter
189
+ c.req.query(); // all query params as object
190
+ c.req.queries("tags"); // multiple values: ?tags=A&tags=B → ['A', 'B']
191
+ c.req.header("Authorization"); // request header
192
+ c.req.header(); // all headers (keys are lowercase)
193
+
194
+ // Body parsing
195
+ await c.req.json(); // parse JSON body
196
+ await c.req.text(); // parse text body
197
+ await c.req.formData(); // parse as FormData
198
+ await c.req.parseBody(); // parse multipart/form-data or urlencoded
199
+ await c.req.arrayBuffer(); // parse as ArrayBuffer
200
+ await c.req.blob(); // parse as Blob
201
+
202
+ // Validated data (used with validator middleware)
203
+ c.req.valid("json");
204
+ c.req.valid("query");
205
+ c.req.valid("form");
206
+ c.req.valid("param");
207
+
208
+ // Properties
209
+ c.req.url; // full URL string
210
+ c.req.path; // pathname
211
+ c.req.method; // HTTP method
212
+ c.req.raw; // underlying Request object
213
+ ```
214
+
215
+ ---
216
+
217
+ ## Middleware
218
+
219
+ ### Using Built-in Middleware
220
+
221
+ ```ts
222
+ import { cors } from "hono/cors";
223
+ import { logger } from "hono/logger";
224
+ import { basicAuth } from "hono/basic-auth";
225
+ import { prettyJSON } from "hono/pretty-json";
226
+ import { secureHeaders } from "hono/secure-headers";
227
+ import { etag } from "hono/etag";
228
+ import { compress } from "hono/compress";
229
+ import { poweredBy } from "hono/powered-by";
230
+ import { timing } from "hono/timing";
231
+ import { cache } from "hono/cache";
232
+ import { bearerAuth } from "hono/bearer-auth";
233
+ import { jwt } from "hono/jwt";
234
+ import { csrf } from "hono/csrf";
235
+ import { ipRestriction } from "hono/ip-restriction";
236
+ import { bodyLimit } from "hono/body-limit";
237
+ import { requestId } from "hono/request-id";
238
+ import { methodOverride } from "hono/method-override";
239
+ import { trailingSlash, trimTrailingSlash } from "hono/trailing-slash";
240
+
241
+ // Registration
242
+ app.use(logger()); // all routes
243
+ app.use("/api/*", cors()); // specific path
244
+ app.post("/api/*", basicAuth({ username: "admin", password: "secret" }));
245
+ ```
246
+
247
+ ### Custom Middleware
248
+
249
+ ```ts
250
+ // Inline
251
+ app.use(async (c, next) => {
252
+ const start = Date.now();
253
+ await next();
254
+ const elapsed = Date.now() - start;
255
+ c.res.headers.set("X-Response-Time", `${elapsed}ms`);
256
+ });
257
+
258
+ // Reusable with createMiddleware
259
+ import { createMiddleware } from "hono/factory";
260
+
261
+ const auth = createMiddleware(async (c, next) => {
262
+ const token = c.req.header("Authorization");
263
+ if (!token) return c.json({ error: "Unauthorized" }, 401);
264
+ await next();
265
+ });
266
+
267
+ app.use("/api/*", auth);
268
+ ```
269
+
270
+ ### Middleware Execution Order
271
+
272
+ Middleware executes in registration order. `await next()` calls the next middleware/handler, and code after `next()` runs on the way back:
273
+
274
+ ```
275
+ Request → mw1 before → mw2 before → handler → mw2 after → mw1 after → Response
276
+ ```
277
+
278
+ ```ts
279
+ app.use(async (c, next) => {
280
+ // before handler
281
+ await next();
282
+ // after handler
283
+ });
284
+ ```
285
+
286
+ ---
287
+
288
+ ## Validation
289
+
290
+ Validation targets: `json`, `form`, `query`, `header`, `param`, `cookie`.
291
+
292
+ ### Zod Validator
293
+
294
+ ```ts
295
+ import { zValidator } from "@hono/zod-validator";
296
+ import { z } from "zod";
297
+
298
+ const schema = z.object({
299
+ title: z.string().min(1),
300
+ body: z.string(),
301
+ });
302
+
303
+ app.post("/posts", zValidator("json", schema), (c) => {
304
+ const data = c.req.valid("json"); // fully typed
305
+ return c.json(data, 201);
306
+ });
307
+ ```
308
+
309
+ ### Valibot / Standard Schema Validator
310
+
311
+ ```ts
312
+ import { sValidator } from "@hono/standard-validator";
313
+ import * as v from "valibot";
314
+
315
+ const schema = v.object({ name: v.string(), age: v.number() });
316
+
317
+ app.post("/users", sValidator("json", schema), (c) => {
318
+ const data = c.req.valid("json");
319
+ return c.json(data, 201);
320
+ });
321
+ ```
322
+
323
+ ---
324
+
325
+ ## JSX
326
+
327
+ ### Setup
328
+
329
+ In `tsconfig.json`:
330
+
331
+ ```json
332
+ {
333
+ "compilerOptions": {
334
+ "jsx": "react-jsx",
335
+ "jsxImportSource": "hono/jsx"
336
+ }
337
+ }
338
+ ```
339
+
340
+ Or use pragma: `/** @jsxImportSource hono/jsx */`
341
+
342
+ **Important:** Files using JSX must have a `.tsx` extension. Rename `.ts` to `.tsx` or the compiler will fail.
343
+
344
+ ### Components
345
+
346
+ ```tsx
347
+ import type { PropsWithChildren } from "hono/jsx";
348
+
349
+ const Layout = (props: PropsWithChildren) => (
350
+ <html>
351
+ <head>
352
+ <title>My App</title>
353
+ </head>
354
+ <body>{props.children}</body>
355
+ </html>
356
+ );
357
+
358
+ const UserCard = ({ name }: { name: string }) => (
359
+ <div class="card">
360
+ <h2>{name}</h2>
361
+ </div>
362
+ );
363
+
364
+ app.get("/", (c) => {
365
+ return c.html(
366
+ <Layout>
367
+ <UserCard name="Alice" />
368
+ </Layout>,
369
+ );
370
+ });
371
+ ```
372
+
373
+ ### jsxRenderer Middleware
374
+
375
+ Use `jsxRenderer` middleware for layouts. See `npx hono docs /docs/middleware/builtin/jsx-renderer` for details.
376
+
377
+ ### Async Components
378
+
379
+ ```tsx
380
+ const UserList = async () => {
381
+ const users = await fetchUsers();
382
+ return (
383
+ <ul>
384
+ {users.map((u) => (
385
+ <li>{u.name}</li>
386
+ ))}
387
+ </ul>
388
+ );
389
+ };
390
+ ```
391
+
392
+ ### Fragments
393
+
394
+ ```tsx
395
+ const Items = () => (
396
+ <>
397
+ <li>Item 1</li>
398
+ <li>Item 2</li>
399
+ </>
400
+ );
401
+ ```
402
+
403
+ ---
404
+
405
+ ## Streaming
406
+
407
+ ```ts
408
+ import { stream, streamText, streamSSE } from "hono/streaming";
409
+
410
+ // Basic stream
411
+ app.get("/stream", (c) => {
412
+ return stream(c, async (stream) => {
413
+ stream.onAbort(() => console.log("Aborted"));
414
+ await stream.write(new Uint8Array([0x48, 0x65]));
415
+ await stream.pipe(readableStream);
416
+ });
417
+ });
418
+
419
+ // Text stream
420
+ app.get("/stream-text", (c) => {
421
+ return streamText(c, async (stream) => {
422
+ await stream.writeln("Hello");
423
+ await stream.sleep(1000);
424
+ await stream.write("World");
425
+ });
426
+ });
427
+
428
+ // Server-Sent Events
429
+ app.get("/sse", (c) => {
430
+ return streamSSE(c, async (stream) => {
431
+ let id = 0;
432
+ while (true) {
433
+ await stream.writeSSE({
434
+ data: JSON.stringify({ time: new Date().toISOString() }),
435
+ event: "time-update",
436
+ id: String(id++),
437
+ });
438
+ await stream.sleep(1000);
439
+ }
440
+ });
441
+ });
442
+ ```
443
+
444
+ ---
445
+
446
+ ## Testing with app.request()
447
+
448
+ Test endpoints without starting an HTTP server:
449
+
450
+ ```ts
451
+ // GET
452
+ const res = await app.request("/posts");
453
+ expect(res.status).toBe(200);
454
+ expect(await res.json()).toEqual({ posts: [] });
455
+
456
+ // POST with JSON
457
+ const res = await app.request("/posts", {
458
+ method: "POST",
459
+ body: JSON.stringify({ title: "Hello" }),
460
+ headers: { "Content-Type": "application/json" },
461
+ });
462
+
463
+ // POST with FormData
464
+ const formData = new FormData();
465
+ formData.append("name", "Alice");
466
+ const res = await app.request("/users", { method: "POST", body: formData });
467
+
468
+ // With mock env (Cloudflare Workers bindings)
469
+ const res = await app.request("/api/data", {}, { KV: mockKV, DATABASE: mockDB });
470
+
471
+ // Using Request object
472
+ const req = new Request("http://localhost/api", { method: "DELETE" });
473
+ const res = await app.request(req);
474
+ ```
475
+
476
+ ---
477
+
478
+ ## Hono Client (RPC)
479
+
480
+ Type-safe API client using shared types between server and client.
481
+
482
+ **IMPORTANT: Routes MUST be chained for type inference to work. Without chaining, the client cannot infer route types.**
483
+
484
+ ```ts
485
+ // Server: routes MUST be chained to preserve types
486
+ const route = app
487
+ .post("/posts", zValidator("json", schema), (c) => {
488
+ return c.json({ ok: true }, 201);
489
+ })
490
+ .get("/posts", (c) => {
491
+ return c.json({ posts: [] });
492
+ });
493
+ export type AppType = typeof route;
494
+
495
+ // Client: use hc() with the exported type
496
+ import { hc } from "hono/client";
497
+ import type { AppType } from "./server";
498
+
499
+ const client = hc<AppType>("http://localhost:8787/");
500
+ const res = await client.posts.$post({ json: { title: "Hello" } });
501
+ const data = await res.json(); // fully typed
502
+ ```
503
+
504
+ Type utilities:
505
+
506
+ ```ts
507
+ import type { InferRequestType, InferResponseType } from "hono/client";
508
+
509
+ type ReqType = InferRequestType<typeof client.posts.$post>;
510
+ type ResType = InferResponseType<typeof client.posts.$post, 200>;
511
+ ```
512
+
513
+ ---
514
+
515
+ ## Helpers
516
+
517
+ Helpers are utility functions imported from `hono/<helper-name>`:
518
+
519
+ ```ts
520
+ import { getConnInfo } from "hono/conninfo";
521
+ import { getCookie, setCookie, deleteCookie } from "hono/cookie";
522
+ import { css, Style } from "hono/css";
523
+ import { createFactory } from "hono/factory";
524
+ import { html, raw } from "hono/html";
525
+ import { stream, streamText, streamSSE } from "hono/streaming";
526
+ import { testClient } from "hono/testing";
527
+ import { upgradeWebSocket } from "hono/cloudflare-workers"; // or other adapter
528
+ ```
529
+
530
+ Available helpers: Accepts, Adapter, ConnInfo, Cookie, css, Dev, Factory, html, JWT, Proxy, Route, SSG, Streaming, Testing, WebSocket.
531
+
532
+ For details, use `npx hono docs /docs/helpers/<helper-name>`.
533
+
534
+ ### Factory
535
+
536
+ Use `createFactory` to define `Env` once and share it across app, middleware, and handlers:
537
+
538
+ ```ts
539
+ import { createFactory } from "hono/factory";
540
+
541
+ const factory = createFactory<Env>();
542
+
543
+ // Create app (Env type is inherited)
544
+ const app = factory.createApp();
545
+
546
+ // Create middleware (Env type is inherited, no need to pass generics)
547
+ const mw = factory.createMiddleware(async (c, next) => {
548
+ await next();
549
+ });
550
+
551
+ // Create handlers separately (preserves type inference)
552
+ const handlers = factory.createHandlers(logger(), (c) => c.json({ message: "Hello" }));
553
+ app.get("/api", ...handlers);
554
+ ```
555
+
556
+ ---
557
+
558
+ ## Best Practices
559
+
560
+ - Write handlers inline in route definitions for proper type inference of path params.
561
+ - Use `app.route()` to organize large apps by feature, not Rails-style controllers.
562
+ - Use `createFactory()` to share Env type across app, middleware, and handlers.
563
+ - Use `c.set()`/`c.get()` to pass data between middleware and handlers.
564
+ - Chain validators for multiple request parts (param + query + json).
565
+ - Export app type for RPC: `export type AppType = typeof routes`
566
+ - Use `app.request()` for testing — no server startup needed.
567
+
568
+ ## Adapters
569
+
570
+ Hono runs on multiple runtimes. The default export works for Cloudflare Workers, Deno, and Bun. For Node.js, use the Node adapter:
571
+
572
+ ```ts
573
+ // Cloudflare Workers / Deno / Bun
574
+ export default app;
575
+
576
+ // Node.js
577
+ import { serve } from "@hono/node-server";
578
+ serve(app);
579
+ ```
.agents/skills/shadcn/SKILL.md ADDED
@@ -0,0 +1,250 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: shadcn
3
+ description: Manages shadcn components and projects — adding, searching, fixing, debugging, styling, and composing UI. Provides project context, component docs, and usage examples. Applies when working with shadcn/ui, component registries, presets, --preset codes, or any project with a components.json file. Also triggers for "shadcn init", "create an app with --preset", or "switch to --preset".
4
+ user-invocable: false
5
+ allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
6
+ ---
7
+
8
+ # shadcn/ui
9
+
10
+ A framework for building ui, components and design systems. Components are added as source code to the user's project via the CLI.
11
+
12
+ > **IMPORTANT:** Run all CLI commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest` — based on the project's `packageManager`. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
13
+
14
+ ## Current Project Context
15
+
16
+ ```json
17
+ !`npx shadcn@latest info --json`
18
+ ```
19
+
20
+ The JSON above contains the project config and installed components. Use `npx shadcn@latest docs <component>` to get documentation and example URLs for any component.
21
+
22
+ ## Principles
23
+
24
+ 1. **Use existing components first.** Use `npx shadcn@latest search` to check registries before writing custom UI. Check community registries too.
25
+ 2. **Compose, don't reinvent.** Settings page = Tabs + Card + form controls. Dashboard = Sidebar + Card + Chart + Table.
26
+ 3. **Use built-in variants before custom styles.** `variant="outline"`, `size="sm"`, etc.
27
+ 4. **Use semantic colors.** `bg-primary`, `text-muted-foreground` — never raw values like `bg-blue-500`.
28
+
29
+ ## Critical Rules
30
+
31
+ These rules are **always enforced**. Each links to a file with Incorrect/Correct code pairs.
32
+
33
+ ### Styling & Tailwind → [styling.md](./rules/styling.md)
34
+
35
+ - **`className` for layout, not styling.** Never override component colors or typography.
36
+ - **No `space-x-*` or `space-y-*`.** Use `flex` with `gap-*`. For vertical stacks, `flex flex-col gap-*`.
37
+ - **Use `size-*` when width and height are equal.** `size-10` not `w-10 h-10`.
38
+ - **Use `truncate` shorthand.** Not `overflow-hidden text-ellipsis whitespace-nowrap`.
39
+ - **No manual `dark:` color overrides.** Use semantic tokens (`bg-background`, `text-muted-foreground`).
40
+ - **Use `cn()` for conditional classes.** Don't write manual template literal ternaries.
41
+ - **No manual `z-index` on overlay components.** Dialog, Sheet, Popover, etc. handle their own stacking.
42
+
43
+ ### Forms & Inputs → [forms.md](./rules/forms.md)
44
+
45
+ - **Forms use `FieldGroup` + `Field`.** Never use raw `div` with `space-y-*` or `grid gap-*` for form layout.
46
+ - **`InputGroup` uses `InputGroupInput`/`InputGroupTextarea`.** Never raw `Input`/`Textarea` inside `InputGroup`.
47
+ - **Buttons inside inputs use `InputGroup` + `InputGroupAddon`.**
48
+ - **Option sets (2–7 choices) use `ToggleGroup`.** Don't loop `Button` with manual active state.
49
+ - **`FieldSet` + `FieldLegend` for grouping related checkboxes/radios.** Don't use a `div` with a heading.
50
+ - **Field validation uses `data-invalid` + `aria-invalid`.** `data-invalid` on `Field`, `aria-invalid` on the control. For disabled: `data-disabled` on `Field`, `disabled` on the control.
51
+
52
+ ### Component Structure → [composition.md](./rules/composition.md)
53
+
54
+ - **Items always inside their Group.** `SelectItem` → `SelectGroup`. `DropdownMenuItem` → `DropdownMenuGroup`. `CommandItem` → `CommandGroup`.
55
+ - **Use `asChild` (radix) or `render` (base) for custom triggers.** Check `base` field from `npx shadcn@latest info`. → [base-vs-radix.md](./rules/base-vs-radix.md)
56
+ - **Dialog, Sheet, and Drawer always need a Title.** `DialogTitle`, `SheetTitle`, `DrawerTitle` required for accessibility. Use `className="sr-only"` if visually hidden.
57
+ - **Use full Card composition.** `CardHeader`/`CardTitle`/`CardDescription`/`CardContent`/`CardFooter`. Don't dump everything in `CardContent`.
58
+ - **Button has no `isPending`/`isLoading`.** Compose with `Spinner` + `data-icon` + `disabled`.
59
+ - **`TabsTrigger` must be inside `TabsList`.** Never render triggers directly in `Tabs`.
60
+ - **`Avatar` always needs `AvatarFallback`.** For when the image fails to load.
61
+
62
+ ### Use Components, Not Custom Markup → [composition.md](./rules/composition.md)
63
+
64
+ - **Use existing components before custom markup.** Check if a component exists before writing a styled `div`.
65
+ - **Callouts use `Alert`.** Don't build custom styled divs.
66
+ - **Empty states use `Empty`.** Don't build custom empty state markup.
67
+ - **Toast via `sonner`.** Use `toast()` from `sonner`.
68
+ - **Use `Separator`** instead of `<hr>` or `<div className="border-t">`.
69
+ - **Use `Skeleton`** for loading placeholders. No custom `animate-pulse` divs.
70
+ - **Use `Badge`** instead of custom styled spans.
71
+
72
+ ### Icons → [icons.md](./rules/icons.md)
73
+
74
+ - **Icons in `Button` use `data-icon`.** `data-icon="inline-start"` or `data-icon="inline-end"` on the icon.
75
+ - **No sizing classes on icons inside components.** Components handle icon sizing via CSS. No `size-4` or `w-4 h-4`.
76
+ - **Pass icons as objects, not string keys.** `icon={CheckIcon}`, not a string lookup.
77
+
78
+ ### CLI
79
+
80
+ - **Never decode or fetch preset codes manually.** Pass them directly to `npx shadcn@latest apply --preset <code>` for existing projects, or `npx shadcn@latest init --preset <code>` when initializing.
81
+
82
+ ## Key Patterns
83
+
84
+ These are the most common patterns that differentiate correct shadcn/ui code. For edge cases, see the linked rule files above.
85
+
86
+ ```tsx
87
+ // Form layout: FieldGroup + Field, not div + Label.
88
+ <FieldGroup>
89
+ <Field>
90
+ <FieldLabel htmlFor="email">Email</FieldLabel>
91
+ <Input id="email" />
92
+ </Field>
93
+ </FieldGroup>
94
+
95
+ // Validation: data-invalid on Field, aria-invalid on the control.
96
+ <Field data-invalid>
97
+ <FieldLabel>Email</FieldLabel>
98
+ <Input aria-invalid />
99
+ <FieldDescription>Invalid email.</FieldDescription>
100
+ </Field>
101
+
102
+ // Icons in buttons: data-icon, no sizing classes.
103
+ <Button>
104
+ <SearchIcon data-icon="inline-start" />
105
+ Search
106
+ </Button>
107
+
108
+ // Spacing: gap-*, not space-y-*.
109
+ <div className="flex flex-col gap-4"> // correct
110
+ <div className="space-y-4"> // wrong
111
+
112
+ // Equal dimensions: size-*, not w-* h-*.
113
+ <Avatar className="size-10"> // correct
114
+ <Avatar className="w-10 h-10"> // wrong
115
+
116
+ // Status colors: Badge variants or semantic tokens, not raw colors.
117
+ <Badge variant="secondary">+20.1%</Badge> // correct
118
+ <span className="text-emerald-600">+20.1%</span> // wrong
119
+ ```
120
+
121
+ ## Component Selection
122
+
123
+ | Need | Use |
124
+ | -------------------------- | --------------------------------------------------------------------------------------------------- |
125
+ | Button/action | `Button` with appropriate variant |
126
+ | Form inputs | `Input`, `Select`, `Combobox`, `Switch`, `Checkbox`, `RadioGroup`, `Textarea`, `InputOTP`, `Slider` |
127
+ | Toggle between 2–5 options | `ToggleGroup` + `ToggleGroupItem` |
128
+ | Data display | `Table`, `Card`, `Badge`, `Avatar` |
129
+ | Navigation | `Sidebar`, `NavigationMenu`, `Breadcrumb`, `Tabs`, `Pagination` |
130
+ | Overlays | `Dialog` (modal), `Sheet` (side panel), `Drawer` (bottom sheet), `AlertDialog` (confirmation) |
131
+ | Feedback | `sonner` (toast), `Alert`, `Progress`, `Skeleton`, `Spinner` |
132
+ | Command palette | `Command` inside `Dialog` |
133
+ | Charts | `Chart` (wraps Recharts) |
134
+ | Layout | `Card`, `Separator`, `Resizable`, `ScrollArea`, `Accordion`, `Collapsible` |
135
+ | Empty states | `Empty` |
136
+ | Menus | `DropdownMenu`, `ContextMenu`, `Menubar` |
137
+ | Tooltips/info | `Tooltip`, `HoverCard`, `Popover` |
138
+
139
+ ## Key Fields
140
+
141
+ The injected project context contains these key fields:
142
+
143
+ - **`aliases`** → use the actual alias prefix for imports (e.g. `@/`, `~/`), never hardcode.
144
+ - **`isRSC`** → when `true`, components using `useState`, `useEffect`, event handlers, or browser APIs need `"use client"` at the top of the file. Always reference this field when advising on the directive.
145
+ - **`tailwindVersion`** → `"v4"` uses `@theme inline` blocks; `"v3"` uses `tailwind.config.js`.
146
+ - **`tailwindCssFile`** → the global CSS file where custom CSS variables are defined. Always edit this file, never create a new one.
147
+ - **`style`** → component visual treatment (e.g. `nova`, `vega`).
148
+ - **`base`** → primitive library (`radix` or `base`). Affects component APIs and available props.
149
+ - **`iconLibrary`** → determines icon imports. Use `lucide-react` for `lucide`, `@tabler/icons-react` for `tabler`, etc. Never assume `lucide-react`.
150
+ - **`resolvedPaths`** → exact file-system destinations for components, utils, hooks, etc.
151
+ - **`framework`** → routing and file conventions (e.g. Next.js App Router vs Vite SPA).
152
+ - **`packageManager`** → use this for any non-shadcn dependency installs (e.g. `pnpm add date-fns` vs `npm install date-fns`).
153
+
154
+ See [cli.md — `info` command](./cli.md) for the full field reference.
155
+
156
+ ## Component Docs, Examples, and Usage
157
+
158
+ Run `npx shadcn@latest docs <component>` to get the URLs for a component's documentation, examples, and API reference. Fetch these URLs to get the actual content.
159
+
160
+ ```bash
161
+ npx shadcn@latest docs button dialog select
162
+ ```
163
+
164
+ **When creating, fixing, debugging, or using a component, always run `npx shadcn@latest docs` and fetch the URLs first.** This ensures you're working with the correct API and usage patterns rather than guessing.
165
+
166
+ ## Workflow
167
+
168
+ 1. **Get project context** — already injected above. Run `npx shadcn@latest info` again if you need to refresh.
169
+ 2. **Check installed components first** — before running `add`, always check the `components` list from project context or list the `resolvedPaths.ui` directory. Don't import components that haven't been added, and don't re-add ones already installed.
170
+ 3. **Find components** — `npx shadcn@latest search`.
171
+ 4. **Get docs and examples** — run `npx shadcn@latest docs <component>` to get URLs, then fetch them. Use `npx shadcn@latest view` to browse registry items you haven't installed. To preview changes to installed components, use `npx shadcn@latest add --diff`.
172
+ 5. **Install or update** — `npx shadcn@latest add`. When updating existing components, use `--dry-run` and `--diff` to preview changes first (see [Updating Components](#updating-components) below).
173
+ 6. **Fix imports in third-party components** — After adding components from community registries (e.g. `@bundui`, `@magicui`), check the added non-UI files for hardcoded import paths like `@/components/ui/...`. These won't match the project's actual aliases. Use `npx shadcn@latest info` to get the correct `ui` alias (e.g. `@workspace/ui/components`) and rewrite the imports accordingly. The CLI rewrites imports for its own UI files, but third-party registry components may use default paths that don't match the project.
174
+ 7. **Review added components** — After adding a component or block from any registry, **always read the added files and verify they are correct**. Check for missing sub-components (e.g. `SelectItem` without `SelectGroup`), missing imports, incorrect composition, or violations of the [Critical Rules](#critical-rules). Also replace any icon imports with the project's `iconLibrary` from the project context (e.g. if the registry item uses `lucide-react` but the project uses `hugeicons`, swap the imports and icon names accordingly). Fix all issues before moving on.
175
+ 8. **Registry must be explicit** — When the user asks to add a block or component, **do not guess the registry**. If no registry is specified (e.g. user says "add a login block" without specifying `@shadcn`, `@tailark`, etc.), ask which registry to use. Never default to a registry on behalf of the user.
176
+ 9. **Switching presets** — Ask the user first: **overwrite**, **partial**, **merge**, or **skip**?
177
+ - **Overwrite**: `npx shadcn@latest apply --preset <code>`. Overwrites detected components, fonts, and CSS variables.
178
+ - **Partial**: `npx shadcn@latest apply --preset <code> --only theme,font`. Updates only the selected preset parts without reinstalling UI components. Supported values are `theme` and `font`; comma-separated combinations are allowed. `icon` is intentionally not supported, because icon changes may require full component reinstall and transforms.
179
+ - **Merge**: `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to list installed components, then for each installed component use `--dry-run` and `--diff` to [smart merge](#updating-components) it individually.
180
+ - **Skip**: `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS, leaves components as-is.
181
+ - **Important**: Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
182
+
183
+ ## Updating Components
184
+
185
+ When the user asks to update a component from upstream while keeping their local changes, use `--dry-run` and `--diff` to intelligently merge. **NEVER fetch raw files from GitHub manually — always use the CLI.**
186
+
187
+ 1. Run `npx shadcn@latest add <component> --dry-run` to see all files that would be affected.
188
+ 2. For each file, run `npx shadcn@latest add <component> --diff <file>` to see what changed upstream vs local.
189
+ 3. Decide per file based on the diff:
190
+ - No local changes → safe to overwrite.
191
+ - Has local changes → read the local file, analyze the diff, and apply upstream updates while preserving local modifications.
192
+ - User says "just update everything" → use `--overwrite`, but confirm first.
193
+ 4. **Never use `--overwrite` without the user's explicit approval.**
194
+
195
+ ## Quick Reference
196
+
197
+ ```bash
198
+ # Create a new project.
199
+ npx shadcn@latest init --name my-app --preset base-nova
200
+ npx shadcn@latest init --name my-app --preset a2r6bw --template vite
201
+
202
+ # Create a monorepo project.
203
+ npx shadcn@latest init --name my-app --preset base-nova --monorepo
204
+ npx shadcn@latest init --name my-app --preset base-nova --template next --monorepo
205
+
206
+ # Initialize existing project.
207
+ npx shadcn@latest init --preset base-nova
208
+ npx shadcn@latest init --defaults # shortcut: --template=next --preset=nova (base style implied)
209
+
210
+ # Apply a preset to an existing project.
211
+ npx shadcn@latest apply --preset a2r6bw
212
+ npx shadcn@latest apply a2r6bw
213
+ npx shadcn@latest apply --preset a2r6bw --only theme
214
+ npx shadcn@latest apply --preset a2r6bw --only font
215
+ npx shadcn@latest apply --preset a2r6bw --only theme,font
216
+
217
+ # Add components.
218
+ npx shadcn@latest add button card dialog
219
+ npx shadcn@latest add @magicui/shimmer-button
220
+ npx shadcn@latest add --all
221
+
222
+ # Preview changes before adding/updating.
223
+ npx shadcn@latest add button --dry-run
224
+ npx shadcn@latest add button --diff button.tsx
225
+ npx shadcn@latest add @acme/form --view button.tsx
226
+
227
+ # Search registries.
228
+ npx shadcn@latest search @shadcn -q "sidebar"
229
+ npx shadcn@latest search @tailark -q "stats"
230
+
231
+ # Get component docs and example URLs.
232
+ npx shadcn@latest docs button dialog select
233
+
234
+ # View registry item details (for items not yet installed).
235
+ npx shadcn@latest view @shadcn/button
236
+ ```
237
+
238
+ **Named presets:** `nova`, `vega`, `maia`, `lyra`, `mira`, `luma`
239
+ **Templates:** `next`, `vite`, `start`, `react-router`, `astro` (all support `--monorepo`) and `laravel` (not supported for monorepo)
240
+ **Preset codes:** Version-prefixed base62 strings (e.g. `a2r6bw` or `b0`), from [ui.shadcn.com](https://ui.shadcn.com).
241
+
242
+ ## Detailed References
243
+
244
+ - [rules/forms.md](./rules/forms.md) — FieldGroup, Field, InputGroup, ToggleGroup, FieldSet, validation states
245
+ - [rules/composition.md](./rules/composition.md) — Groups, overlays, Card, Tabs, Avatar, Alert, Empty, Toast, Separator, Skeleton, Badge, Button loading
246
+ - [rules/icons.md](./rules/icons.md) — data-icon, icon sizing, passing icons as objects
247
+ - [rules/styling.md](./rules/styling.md) — Semantic colors, variants, className, spacing, size, truncate, dark mode, cn(), z-index
248
+ - [rules/base-vs-radix.md](./rules/base-vs-radix.md) — asChild vs render, Select, ToggleGroup, Slider, Accordion
249
+ - [cli.md](./cli.md) — Commands, flags, presets, templates
250
+ - [customization.md](./customization.md) — Theming, CSS variables, extending components
.agents/skills/shadcn/agents/openai.yml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ interface:
2
+ display_name: "shadcn/ui"
3
+ short_description: "Manages shadcn/ui components — adding, searching, fixing, debugging, styling, and composing UI."
4
+ icon_small: "./assets/shadcn-small.png"
5
+ icon_large: "./assets/shadcn.png"
.agents/skills/shadcn/assets/shadcn-small.png ADDED

Git LFS Details

  • SHA256: 0ecc62d682727f68fc4937eed19960c706714fa084d44f63028d611ae02bf3d7
  • Pointer size: 129 Bytes
  • Size of remote file: 1.05 kB
.agents/skills/shadcn/assets/shadcn.png ADDED

Git LFS Details

  • SHA256: 7d60ad6fec4d89a0d44ba5a3c9283d2fea0047af5467f9227040e20d927d39b7
  • Pointer size: 129 Bytes
  • Size of remote file: 3.85 kB
.agents/skills/shadcn/cli.md ADDED
@@ -0,0 +1,276 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # shadcn CLI Reference
2
+
3
+ Configuration is read from `components.json`.
4
+
5
+ > **IMPORTANT:** Always run commands using the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`. Check `packageManager` from project context to choose the right one. Examples below use `npx shadcn@latest` but substitute the correct runner for the project.
6
+
7
+ > **IMPORTANT:** Only use the flags documented below. Do not invent or guess flags — if a flag isn't listed here, it doesn't exist. The CLI auto-detects the package manager from the project's lockfile; there is no `--package-manager` flag.
8
+
9
+ ## Contents
10
+
11
+ - Commands: init, apply, add (dry-run, smart merge), search, view, docs, info, build
12
+ - Templates: next, vite, start, react-router, astro
13
+ - Presets: named, code, URL formats and fields
14
+ - Switching presets
15
+
16
+ ---
17
+
18
+ ## Commands
19
+
20
+ ### `init` — Initialize or create a project
21
+
22
+ ```bash
23
+ npx shadcn@latest init [components...] [options]
24
+ ```
25
+
26
+ Initializes shadcn/ui in an existing project or creates a new project (when `--name` is provided). Optionally installs components in the same step.
27
+
28
+ | Flag | Short | Description | Default |
29
+ | ----------------------- | ----- | --------------------------------------------------------- | ------- |
30
+ | `--template <template>` | `-t` | Template (next, start, vite, next-monorepo, react-router) | — |
31
+ | `--preset [name]` | `-p` | Preset configuration (named, code, or URL) | — |
32
+ | `--yes` | `-y` | Skip confirmation prompt | `true` |
33
+ | `--defaults` | `-d` | Use defaults (`--template=next --preset=base-nova`) | `false` |
34
+ | `--force` | `-f` | Force overwrite existing configuration | `false` |
35
+ | `--cwd <cwd>` | `-c` | Working directory | current |
36
+ | `--name <name>` | `-n` | Name for new project | — |
37
+ | `--silent` | `-s` | Mute output | `false` |
38
+ | `--rtl` | | Enable RTL support | — |
39
+ | `--reinstall` | | Re-install existing UI components | `false` |
40
+ | `--monorepo` | | Scaffold a monorepo project | — |
41
+ | `--no-monorepo` | | Skip the monorepo prompt | — |
42
+
43
+ `npx shadcn@latest create` is an alias for `npx shadcn@latest init`.
44
+
45
+ ### `apply` — Apply a preset to an existing project
46
+
47
+ ```bash
48
+ npx shadcn@latest apply [preset] [options]
49
+ ```
50
+
51
+ Applies a preset to an existing project, overwriting preset-driven config, fonts, CSS variables, and detected UI components.
52
+
53
+ | Flag | Short | Description | Default |
54
+ | ------------------- | ----- | ------------------------------------------ | ------- |
55
+ | `--preset <preset>` | — | Preset configuration (named, code, or URL) | — |
56
+ | `--yes` | `-y` | Skip confirmation prompt | `false` |
57
+ | `--cwd <cwd>` | `-c` | Working directory | current |
58
+ | `--silent` | `-s` | Mute output | `false` |
59
+
60
+ `[preset]` is a shorthand for `--preset <preset>`. If both are provided, they must match.
61
+ If no preset is provided, the CLI offers to open the custom preset builder on `ui.shadcn.com/create`.
62
+
63
+ ### `add` — Add components
64
+
65
+ > **IMPORTANT:** To compare local components against upstream or to preview changes, ALWAYS use `npx shadcn@latest add <component> --dry-run`, `--diff`, or `--view`. NEVER fetch raw files from GitHub or other sources manually. The CLI handles registry resolution, file paths, and CSS diffing automatically.
66
+
67
+ ```bash
68
+ npx shadcn@latest add [components...] [options]
69
+ ```
70
+
71
+ Accepts component names, registry-prefixed names (`@magicui/shimmer-button`), URLs, or local paths.
72
+
73
+ | Flag | Short | Description | Default |
74
+ | --------------- | ----- | -------------------------------------------------------------------------------------------------------------------- | ------- |
75
+ | `--yes` | `-y` | Skip confirmation prompt | `false` |
76
+ | `--overwrite` | `-o` | Overwrite existing files | `false` |
77
+ | `--cwd <cwd>` | `-c` | Working directory | current |
78
+ | `--all` | `-a` | Add all available components | `false` |
79
+ | `--path <path>` | `-p` | Target path for the component | — |
80
+ | `--silent` | `-s` | Mute output | `false` |
81
+ | `--dry-run` | | Preview all changes without writing files | `false` |
82
+ | `--diff [path]` | | Show diffs. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
83
+ | `--view [path]` | | Show file contents. Without a path, shows the first 5 files. With a path, shows that file only (implies `--dry-run`) | — |
84
+
85
+ #### Dry-Run Mode
86
+
87
+ Use `--dry-run` to preview what `add` would do without writing any files. `--diff` and `--view` both imply `--dry-run`.
88
+
89
+ ```bash
90
+ # Preview all changes.
91
+ npx shadcn@latest add button --dry-run
92
+
93
+ # Show diffs for all files (top 5).
94
+ npx shadcn@latest add button --diff
95
+
96
+ # Show the diff for a specific file.
97
+ npx shadcn@latest add button --diff button.tsx
98
+
99
+ # Show contents for all files (top 5).
100
+ npx shadcn@latest add button --view
101
+
102
+ # Show the full content of a specific file.
103
+ npx shadcn@latest add button --view button.tsx
104
+
105
+ # Works with URLs too.
106
+ npx shadcn@latest add https://api.npoint.io/abc123 --dry-run
107
+
108
+ # CSS diffs.
109
+ npx shadcn@latest add button --diff globals.css
110
+ ```
111
+
112
+ **When to use dry-run:**
113
+
114
+ - When the user asks "what files will this add?" or "what will this change?" — use `--dry-run`.
115
+ - Before overwriting existing components — use `--diff` to preview the changes first.
116
+ - When the user wants to inspect component source code without installing — use `--view`.
117
+ - When checking what CSS changes would be made to `globals.css` — use `--diff globals.css`.
118
+ - When the user asks to review or audit third-party registry code before installing — use `--view` to inspect the source.
119
+
120
+ > **`npx shadcn@latest add --dry-run` vs `npx shadcn@latest view`:** Prefer `npx shadcn@latest add --dry-run/--diff/--view` over `npx shadcn@latest view` when the user wants to preview changes to their project. `npx shadcn@latest view` only shows raw registry metadata. `npx shadcn@latest add --dry-run` shows exactly what would happen in the user's project: resolved file paths, diffs against existing files, and CSS updates. Use `npx shadcn@latest view` only when the user wants to browse registry info without a project context.
121
+
122
+ #### Smart Merge from Upstream
123
+
124
+ See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full workflow.
125
+
126
+ ### `search` — Search registries
127
+
128
+ ```bash
129
+ npx shadcn@latest search <registries...> [options]
130
+ ```
131
+
132
+ Fuzzy search across registries. Also aliased as `npx shadcn@latest list`. Without `-q`, lists all items.
133
+
134
+ | Flag | Short | Description | Default |
135
+ | ------------------- | ----- | ---------------------- | ------- |
136
+ | `--query <query>` | `-q` | Search query | — |
137
+ | `--limit <number>` | `-l` | Max items per registry | `100` |
138
+ | `--offset <number>` | `-o` | Items to skip | `0` |
139
+ | `--cwd <cwd>` | `-c` | Working directory | current |
140
+
141
+ ### `view` — View item details
142
+
143
+ ```bash
144
+ npx shadcn@latest view <items...> [options]
145
+ ```
146
+
147
+ Displays item info including file contents. Example: `npx shadcn@latest view @shadcn/button`.
148
+
149
+ ### `docs` — Get component documentation URLs
150
+
151
+ ```bash
152
+ npx shadcn@latest docs <components...> [options]
153
+ ```
154
+
155
+ Outputs resolved URLs for component documentation, examples, and API references. Accepts one or more component names. Fetch the URLs to get the actual content.
156
+
157
+ Example output for `npx shadcn@latest docs input button`:
158
+
159
+ ```
160
+ base radix
161
+
162
+ input
163
+ docs https://ui.shadcn.com/docs/components/radix/input
164
+ examples https://raw.githubusercontent.com/.../examples/input-example.tsx
165
+
166
+ button
167
+ docs https://ui.shadcn.com/docs/components/radix/button
168
+ examples https://raw.githubusercontent.com/.../examples/button-example.tsx
169
+ ```
170
+
171
+ Some components include an `api` link to the underlying library (e.g. `cmdk` for the command component).
172
+
173
+ ### `diff` — Check for updates
174
+
175
+ Do not use this command. Use `npx shadcn@latest add --diff` instead.
176
+
177
+ ### `info` — Project information
178
+
179
+ ```bash
180
+ npx shadcn@latest info [options]
181
+ ```
182
+
183
+ Displays project info and `components.json` configuration. Run this first to discover the project's framework, aliases, Tailwind version, and resolved paths.
184
+
185
+ | Flag | Short | Description | Default |
186
+ | ------------- | ----- | ----------------- | ------- |
187
+ | `--cwd <cwd>` | `-c` | Working directory | current |
188
+
189
+ **Project Info fields:**
190
+
191
+ | Field | Type | Meaning |
192
+ | -------------------- | --------- | ------------------------------------------------------------------ |
193
+ | `framework` | `string` | Detected framework (`next`, `vite`, `react-router`, `start`, etc.) |
194
+ | `frameworkVersion` | `string` | Framework version (e.g. `15.2.4`) |
195
+ | `isSrcDir` | `boolean` | Whether the project uses a `src/` directory |
196
+ | `isRSC` | `boolean` | Whether React Server Components are enabled |
197
+ | `isTsx` | `boolean` | Whether the project uses TypeScript |
198
+ | `tailwindVersion` | `string` | `"v3"` or `"v4"` |
199
+ | `tailwindConfigFile` | `string` | Path to the Tailwind config file |
200
+ | `tailwindCssFile` | `string` | Path to the global CSS file |
201
+ | `aliasPrefix` | `string` | Import alias prefix (e.g. `@`, `~`, `@/`) |
202
+ | `packageManager` | `string` | Detected package manager (`npm`, `pnpm`, `yarn`, `bun`) |
203
+
204
+ **Components.json fields:**
205
+
206
+ | Field | Type | Meaning |
207
+ | -------------------- | --------- | ------------------------------------------------------------------------------------------ |
208
+ | `base` | `string` | Primitive library (`radix` or `base`) — determines component APIs and available props |
209
+ | `style` | `string` | Visual style (e.g. `nova`, `vega`) |
210
+ | `rsc` | `boolean` | RSC flag from config |
211
+ | `tsx` | `boolean` | TypeScript flag |
212
+ | `tailwind.config` | `string` | Tailwind config path |
213
+ | `tailwind.css` | `string` | Global CSS path — this is where custom CSS variables go |
214
+ | `iconLibrary` | `string` | Icon library — determines icon import package (e.g. `lucide-react`, `@tabler/icons-react`) |
215
+ | `aliases.components` | `string` | Component import alias (e.g. `@/components`) |
216
+ | `aliases.utils` | `string` | Utils import alias (e.g. `@/lib/utils`) |
217
+ | `aliases.ui` | `string` | UI component alias (e.g. `@/components/ui`) |
218
+ | `aliases.lib` | `string` | Lib alias (e.g. `@/lib`) |
219
+ | `aliases.hooks` | `string` | Hooks alias (e.g. `@/hooks`) |
220
+ | `resolvedPaths` | `object` | Absolute file-system paths for each alias |
221
+ | `registries` | `object` | Configured custom registries |
222
+
223
+ **Links fields:**
224
+
225
+ The `info` output includes a **Links** section with templated URLs for component docs, source, and examples. For resolved URLs, use `npx shadcn@latest docs <component>` instead.
226
+
227
+ ### `build` — Build a custom registry
228
+
229
+ ```bash
230
+ npx shadcn@latest build [registry] [options]
231
+ ```
232
+
233
+ Builds `registry.json` into individual JSON files for distribution. Default input: `./registry.json`, default output: `./public/r`.
234
+
235
+ | Flag | Short | Description | Default |
236
+ | ----------------- | ----- | ----------------- | ------------ |
237
+ | `--output <path>` | `-o` | Output directory | `./public/r` |
238
+ | `--cwd <cwd>` | `-c` | Working directory | current |
239
+
240
+ ---
241
+
242
+ ## Templates
243
+
244
+ | Value | Framework | Monorepo support |
245
+ | -------------- | -------------- | ---------------- |
246
+ | `next` | Next.js | Yes |
247
+ | `vite` | Vite | Yes |
248
+ | `start` | TanStack Start | Yes |
249
+ | `react-router` | React Router | Yes |
250
+ | `astro` | Astro | Yes |
251
+ | `laravel` | Laravel | No |
252
+
253
+ All templates support monorepo scaffolding via the `--monorepo` flag. When passed, the CLI uses a monorepo-specific template directory (e.g. `next-monorepo`, `vite-monorepo`). When neither `--monorepo` nor `--no-monorepo` is passed, the CLI prompts interactively. Laravel does not support monorepo scaffolding.
254
+
255
+ ---
256
+
257
+ ## Presets
258
+
259
+ Three ways to specify a preset via `--preset`:
260
+
261
+ 1. **Named:** `--preset nova` or `--preset lyra`
262
+ 2. **Code:** `--preset a2r6bw` (version-prefixed base62 string, e.g. `a2r6bw` or `b0`)
263
+ 3. **URL:** `--preset "https://ui.shadcn.com/init?base=radix&style=nova&..."`
264
+
265
+ > **IMPORTANT:** Never try to decode, fetch, or resolve preset codes manually. Preset codes are opaque — pass them directly to `npx shadcn@latest init --preset <code>` and let the CLI handle resolution.
266
+ > Use `npx shadcn@latest apply --preset <code>` when overwriting an existing project's preset.
267
+
268
+ ## Switching Presets
269
+
270
+ Ask the user first: **overwrite**, **merge**, or **skip** existing components?
271
+
272
+ - **Overwrite / Re-install** → `npx shadcn@latest apply --preset <code>`. Overwrites all detected component files with the new preset styles. Use when the user hasn't customized components.
273
+ - **Merge** → `npx shadcn@latest init --preset <code> --force --no-reinstall`, then run `npx shadcn@latest info` to get the list of installed components and use the [smart merge workflow](./SKILL.md#updating-components) to update them one by one, preserving local changes. Use when the user has customized components.
274
+ - **Skip** → `npx shadcn@latest init --preset <code> --force --no-reinstall`. Only updates config and CSS variables, leaves existing components as-is.
275
+
276
+ Always run preset commands inside the user's project directory. `apply` only works in an existing project with a `components.json` file. The CLI automatically preserves the current base (`base` vs `radix`) from `components.json`. If you must use a scratch/temp directory (e.g. for `--dry-run` comparisons), pass `--base <current-base>` explicitly — preset codes do not encode the base.
.agents/skills/shadcn/customization.md ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Customization & Theming
2
+
3
+ Components reference semantic CSS variable tokens. Change the variables to change every component.
4
+
5
+ ## Contents
6
+
7
+ - How it works (CSS variables → Tailwind utilities → components)
8
+ - Color variables and OKLCH format
9
+ - Dark mode setup
10
+ - Changing the theme (presets, CSS variables)
11
+ - Adding custom colors (Tailwind v3 and v4)
12
+ - Border radius
13
+ - Customizing components (variants, className, wrappers)
14
+ - Checking for updates
15
+
16
+ ---
17
+
18
+ ## How It Works
19
+
20
+ 1. CSS variables defined in `:root` (light) and `.dark` (dark mode).
21
+ 2. Tailwind maps them to utilities: `bg-primary`, `text-muted-foreground`, etc.
22
+ 3. Components use these utilities — changing a variable changes all components that reference it.
23
+
24
+ ---
25
+
26
+ ## Color Variables
27
+
28
+ Every color follows the `name` / `name-foreground` convention. The base variable is for backgrounds, `-foreground` is for text/icons on that background.
29
+
30
+ | Variable | Purpose |
31
+ | -------------------------------------------- | -------------------------------- |
32
+ | `--background` / `--foreground` | Page background and default text |
33
+ | `--card` / `--card-foreground` | Card surfaces |
34
+ | `--primary` / `--primary-foreground` | Primary buttons and actions |
35
+ | `--secondary` / `--secondary-foreground` | Secondary actions |
36
+ | `--muted` / `--muted-foreground` | Muted/disabled states |
37
+ | `--accent` / `--accent-foreground` | Hover and accent states |
38
+ | `--destructive` / `--destructive-foreground` | Error and destructive actions |
39
+ | `--border` | Default border color |
40
+ | `--input` | Form input borders |
41
+ | `--ring` | Focus ring color |
42
+ | `--chart-1` through `--chart-5` | Chart/data visualization |
43
+ | `--sidebar-*` | Sidebar-specific colors |
44
+ | `--surface` / `--surface-foreground` | Secondary surface |
45
+
46
+ Colors use OKLCH: `--primary: oklch(0.205 0 0)` where values are lightness (0–1), chroma (0 = gray), and hue (0–360).
47
+
48
+ ---
49
+
50
+ ## Dark Mode
51
+
52
+ Class-based toggle via `.dark` on the root element. In Next.js, use `next-themes`:
53
+
54
+ ```tsx
55
+ import { ThemeProvider } from "next-themes";
56
+
57
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
58
+ {children}
59
+ </ThemeProvider>;
60
+ ```
61
+
62
+ ---
63
+
64
+ ## Changing the Theme
65
+
66
+ ```bash
67
+ # Apply a preset code from ui.shadcn.com.
68
+ npx shadcn@latest apply --preset a2r6bw
69
+
70
+ # Positional shorthand also works.
71
+ npx shadcn@latest apply a2r6bw
72
+
73
+ # Switch to a named preset and overwrite existing components.
74
+ npx shadcn@latest apply --preset nova
75
+
76
+ # Preserve existing components instead.
77
+ npx shadcn@latest init --preset nova --force --no-reinstall
78
+
79
+ # Use a custom theme URL.
80
+ npx shadcn@latest apply --preset "https://ui.shadcn.com/init?base=radix&style=nova&theme=blue&..."
81
+ ```
82
+
83
+ Or edit CSS variables directly in `globals.css`.
84
+
85
+ ---
86
+
87
+ ## Adding Custom Colors
88
+
89
+ Add variables to the file at `tailwindCssFile` from `npx shadcn@latest info` (typically `globals.css`). Never create a new CSS file for this.
90
+
91
+ ```css
92
+ /* 1. Define in the global CSS file. */
93
+ :root {
94
+ --warning: oklch(0.84 0.16 84);
95
+ --warning-foreground: oklch(0.28 0.07 46);
96
+ }
97
+ .dark {
98
+ --warning: oklch(0.41 0.11 46);
99
+ --warning-foreground: oklch(0.99 0.02 95);
100
+ }
101
+ ```
102
+
103
+ ```css
104
+ /* 2a. Register with Tailwind v4 (@theme inline). */
105
+ @theme inline {
106
+ --color-warning: var(--warning);
107
+ --color-warning-foreground: var(--warning-foreground);
108
+ }
109
+ ```
110
+
111
+ When `tailwindVersion` is `"v3"` (check via `npx shadcn@latest info`), register in `tailwind.config.js` instead:
112
+
113
+ ```js
114
+ // 2b. Register with Tailwind v3 (tailwind.config.js).
115
+ module.exports = {
116
+ theme: {
117
+ extend: {
118
+ colors: {
119
+ warning: "oklch(var(--warning) / <alpha-value>)",
120
+ "warning-foreground": "oklch(var(--warning-foreground) / <alpha-value>)",
121
+ },
122
+ },
123
+ },
124
+ };
125
+ ```
126
+
127
+ ```tsx
128
+ // 3. Use in components.
129
+ <div className="bg-warning text-warning-foreground">Warning</div>
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Border Radius
135
+
136
+ `--radius` controls border radius globally. Components derive values from it (`rounded-lg` = `var(--radius)`, `rounded-md` = `calc(var(--radius) - 2px)`).
137
+
138
+ ---
139
+
140
+ ## Customizing Components
141
+
142
+ See also: [rules/styling.md](./rules/styling.md) for Incorrect/Correct examples.
143
+
144
+ Prefer these approaches in order:
145
+
146
+ ### 1. Built-in variants
147
+
148
+ ```tsx
149
+ <Button variant="outline" size="sm">
150
+ Click
151
+ </Button>
152
+ ```
153
+
154
+ ### 2. Tailwind classes via `className`
155
+
156
+ ```tsx
157
+ <Card className="mx-auto max-w-md">...</Card>
158
+ ```
159
+
160
+ ### 3. Add a new variant
161
+
162
+ Edit the component source to add a variant via `cva`:
163
+
164
+ ```tsx
165
+ // components/ui/button.tsx
166
+ warning: "bg-warning text-warning-foreground hover:bg-warning/90",
167
+ ```
168
+
169
+ ### 4. Wrapper components
170
+
171
+ Compose shadcn/ui primitives into higher-level components:
172
+
173
+ ```tsx
174
+ export function ConfirmDialog({ title, description, onConfirm, children }) {
175
+ return (
176
+ <AlertDialog>
177
+ <AlertDialogTrigger asChild>{children}</AlertDialogTrigger>
178
+ <AlertDialogContent>
179
+ <AlertDialogHeader>
180
+ <AlertDialogTitle>{title}</AlertDialogTitle>
181
+ <AlertDialogDescription>{description}</AlertDialogDescription>
182
+ </AlertDialogHeader>
183
+ <AlertDialogFooter>
184
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
185
+ <AlertDialogAction onClick={onConfirm}>Confirm</AlertDialogAction>
186
+ </AlertDialogFooter>
187
+ </AlertDialogContent>
188
+ </AlertDialog>
189
+ );
190
+ }
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Checking for Updates
196
+
197
+ ```bash
198
+ npx shadcn@latest add button --diff
199
+ ```
200
+
201
+ To preview exactly what would change before updating, use `--dry-run` and `--diff`:
202
+
203
+ ```bash
204
+ npx shadcn@latest add button --dry-run # see all affected files
205
+ npx shadcn@latest add button --diff button.tsx # see the diff for a specific file
206
+ ```
207
+
208
+ See [Updating Components in SKILL.md](./SKILL.md#updating-components) for the full smart merge workflow.
.agents/skills/shadcn/evals/evals.json ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "skill_name": "shadcn",
3
+ "evals": [
4
+ {
5
+ "id": 1,
6
+ "prompt": "I'm building a Next.js app with shadcn/ui (base-nova preset, lucide icons). Create a settings form component with fields for: full name, email address, and notification preferences (email, SMS, push notifications as toggle options). Add validation states for required fields.",
7
+ "expected_output": "A React component using FieldGroup, Field, ToggleGroup, data-invalid/aria-invalid validation, gap-* spacing, and semantic colors.",
8
+ "files": [],
9
+ "expectations": [
10
+ "Uses FieldGroup and Field components for form layout instead of raw div with space-y",
11
+ "Uses Switch for independent on/off notification toggles (not looping Button with manual active state)",
12
+ "Uses data-invalid on Field and aria-invalid on the input control for validation states",
13
+ "Uses gap-* (e.g. gap-4, gap-6) instead of space-y-* or space-x-* for spacing",
14
+ "Uses semantic color tokens (e.g. bg-background, text-muted-foreground, text-destructive) instead of raw colors like bg-red-500",
15
+ "No manual dark: color overrides"
16
+ ]
17
+ },
18
+ {
19
+ "id": 2,
20
+ "prompt": "Create a dialog component for editing a user profile. It should have the user's avatar at the top, input fields for name and bio, and Save/Cancel buttons with appropriate icons. Using shadcn/ui with radix-nova preset and tabler icons.",
21
+ "expected_output": "A React component with DialogTitle, Avatar+AvatarFallback, data-icon on icon buttons, no icon sizing classes, tabler icon imports.",
22
+ "files": [],
23
+ "expectations": [
24
+ "Includes DialogTitle for accessibility (visible or with sr-only class)",
25
+ "Avatar component includes AvatarFallback",
26
+ "Icons on buttons use the data-icon attribute (data-icon=\"inline-start\" or data-icon=\"inline-end\")",
27
+ "No sizing classes on icons inside components (no size-4, w-4, h-4, etc.)",
28
+ "Uses tabler icons (@tabler/icons-react) instead of lucide-react",
29
+ "Uses asChild for custom triggers (radix preset)"
30
+ ]
31
+ },
32
+ {
33
+ "id": 3,
34
+ "prompt": "Create a dashboard component that shows 4 stat cards in a grid. Each card has a title, large number, percentage change badge, and a loading skeleton state. Using shadcn/ui with base-nova preset and lucide icons.",
35
+ "expected_output": "A React component with full Card composition, Skeleton for loading, Badge for changes, semantic colors, gap-* spacing.",
36
+ "files": [],
37
+ "expectations": [
38
+ "Uses full Card composition with CardHeader, CardTitle, CardContent (not dumping everything into CardContent)",
39
+ "Uses Skeleton component for loading placeholders instead of custom animate-pulse divs",
40
+ "Uses Badge component for percentage change instead of custom styled spans",
41
+ "Uses semantic color tokens instead of raw color values like bg-green-500 or text-red-600",
42
+ "Uses gap-* instead of space-y-* or space-x-* for spacing",
43
+ "Uses size-* when width and height are equal instead of separate w-* h-*"
44
+ ]
45
+ }
46
+ ]
47
+ }
.agents/skills/shadcn/mcp.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # shadcn MCP Server
2
+
3
+ The CLI includes an MCP server that lets AI assistants search, browse, view, and install components from registries.
4
+
5
+ ---
6
+
7
+ ## Setup
8
+
9
+ ```bash
10
+ shadcn mcp # start the MCP server (stdio)
11
+ shadcn mcp init # write config for your editor
12
+ ```
13
+
14
+ Editor config files:
15
+
16
+ | Editor | Config file |
17
+ | ----------- | ------------------------------- |
18
+ | Claude Code | `.mcp.json` |
19
+ | Cursor | `.cursor/mcp.json` |
20
+ | VS Code | `.vscode/mcp.json` |
21
+ | OpenCode | `opencode.json` |
22
+ | Codex | `~/.codex/config.toml` (manual) |
23
+
24
+ ---
25
+
26
+ ## Tools
27
+
28
+ > **Tip:** MCP tools handle registry operations (search, view, install). For project configuration (aliases, framework, Tailwind version), use `npx shadcn@latest info` — there is no MCP equivalent.
29
+
30
+ ### `shadcn:get_project_registries`
31
+
32
+ Returns registry names from `components.json`. Errors if no `components.json` exists.
33
+
34
+ **Input:** none
35
+
36
+ ### `shadcn:list_items_in_registries`
37
+
38
+ Lists all items from one or more registries.
39
+
40
+ **Input:** `registries` (string[]), `limit` (number, optional), `offset` (number, optional)
41
+
42
+ ### `shadcn:search_items_in_registries`
43
+
44
+ Fuzzy search across registries.
45
+
46
+ **Input:** `registries` (string[]), `query` (string), `limit` (number, optional), `offset` (number, optional)
47
+
48
+ ### `shadcn:view_items_in_registries`
49
+
50
+ View item details including full file contents.
51
+
52
+ **Input:** `items` (string[]) — e.g. `["@shadcn/button", "@shadcn/card"]`
53
+
54
+ ### `shadcn:get_item_examples_from_registries`
55
+
56
+ Find usage examples and demos with source code.
57
+
58
+ **Input:** `registries` (string[]), `query` (string) — e.g. `"accordion-demo"`, `"button example"`
59
+
60
+ ### `shadcn:get_add_command_for_items`
61
+
62
+ Returns the CLI install command.
63
+
64
+ **Input:** `items` (string[]) — e.g. `["@shadcn/button"]`
65
+
66
+ ### `shadcn:get_audit_checklist`
67
+
68
+ Returns a checklist for verifying components (imports, deps, lint, TypeScript).
69
+
70
+ **Input:** none
71
+
72
+ ---
73
+
74
+ ## Configuring Registries
75
+
76
+ Registries are set in `components.json`. The `@shadcn` registry is always built-in.
77
+
78
+ ```json
79
+ {
80
+ "registries": {
81
+ "@acme": "https://acme.com/r/{name}.json",
82
+ "@private": {
83
+ "url": "https://private.com/r/{name}.json",
84
+ "headers": { "Authorization": "Bearer ${MY_TOKEN}" }
85
+ }
86
+ }
87
+ }
88
+ ```
89
+
90
+ - Names must start with `@`.
91
+ - URLs must contain `{name}`.
92
+ - `${VAR}` references are resolved from environment variables.
93
+
94
+ Community registry index: `https://ui.shadcn.com/r/registries.json`
.agents/skills/shadcn/rules/base-vs-radix.md ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Base vs Radix
2
+
3
+ API differences between `base` and `radix`. Check the `base` field from `npx shadcn@latest info`.
4
+
5
+ ## Contents
6
+
7
+ - Composition: asChild vs render
8
+ - Button / trigger as non-button element
9
+ - Select (items prop, placeholder, positioning, multiple, object values)
10
+ - ToggleGroup (type vs multiple)
11
+ - Slider (scalar vs array)
12
+ - Accordion (type and defaultValue)
13
+
14
+ ---
15
+
16
+ ## Composition: asChild (radix) vs render (base)
17
+
18
+ Radix uses `asChild` to replace the default element. Base uses `render`. Don't wrap triggers in extra elements.
19
+
20
+ **Incorrect:**
21
+
22
+ ```tsx
23
+ <DialogTrigger>
24
+ <div>
25
+ <Button>Open</Button>
26
+ </div>
27
+ </DialogTrigger>
28
+ ```
29
+
30
+ **Correct (radix):**
31
+
32
+ ```tsx
33
+ <DialogTrigger asChild>
34
+ <Button>Open</Button>
35
+ </DialogTrigger>
36
+ ```
37
+
38
+ **Correct (base):**
39
+
40
+ ```tsx
41
+ <DialogTrigger render={<Button />}>Open</DialogTrigger>
42
+ ```
43
+
44
+ This applies to all trigger and close components: `DialogTrigger`, `SheetTrigger`, `AlertDialogTrigger`, `DropdownMenuTrigger`, `PopoverTrigger`, `TooltipTrigger`, `CollapsibleTrigger`, `DialogClose`, `SheetClose`, `NavigationMenuLink`, `BreadcrumbLink`, `SidebarMenuButton`, `Badge`, `Item`.
45
+
46
+ ---
47
+
48
+ ## Button / trigger as non-button element (base only)
49
+
50
+ When `render` changes an element to a non-button (`<a>`, `<span>`), add `nativeButton={false}`.
51
+
52
+ **Incorrect (base):** missing `nativeButton={false}`.
53
+
54
+ ```tsx
55
+ <Button render={<a href="/docs" />}>Read the docs</Button>
56
+ ```
57
+
58
+ **Correct (base):**
59
+
60
+ ```tsx
61
+ <Button render={<a href="/docs" />} nativeButton={false}>
62
+ Read the docs
63
+ </Button>
64
+ ```
65
+
66
+ **Correct (radix):**
67
+
68
+ ```tsx
69
+ <Button asChild>
70
+ <a href="/docs">Read the docs</a>
71
+ </Button>
72
+ ```
73
+
74
+ Same for triggers whose `render` is not a `Button`:
75
+
76
+ ```tsx
77
+ // base.
78
+ <PopoverTrigger render={<InputGroupAddon />} nativeButton={false}>
79
+ Pick date
80
+ </PopoverTrigger>
81
+ ```
82
+
83
+ ---
84
+
85
+ ## Select
86
+
87
+ **items prop (base only).** Base requires an `items` prop on the root. Radix uses inline JSX only.
88
+
89
+ **Incorrect (base):**
90
+
91
+ ```tsx
92
+ <Select>
93
+ <SelectTrigger>
94
+ <SelectValue placeholder="Select a fruit" />
95
+ </SelectTrigger>
96
+ </Select>
97
+ ```
98
+
99
+ **Correct (base):**
100
+
101
+ ```tsx
102
+ const items = [
103
+ { label: "Select a fruit", value: null },
104
+ { label: "Apple", value: "apple" },
105
+ { label: "Banana", value: "banana" },
106
+ ]
107
+
108
+ <Select items={items}>
109
+ <SelectTrigger>
110
+ <SelectValue />
111
+ </SelectTrigger>
112
+ <SelectContent>
113
+ <SelectGroup>
114
+ {items.map((item) => (
115
+ <SelectItem key={item.value} value={item.value}>{item.label}</SelectItem>
116
+ ))}
117
+ </SelectGroup>
118
+ </SelectContent>
119
+ </Select>
120
+ ```
121
+
122
+ **Correct (radix):**
123
+
124
+ ```tsx
125
+ <Select>
126
+ <SelectTrigger>
127
+ <SelectValue placeholder="Select a fruit" />
128
+ </SelectTrigger>
129
+ <SelectContent>
130
+ <SelectGroup>
131
+ <SelectItem value="apple">Apple</SelectItem>
132
+ <SelectItem value="banana">Banana</SelectItem>
133
+ </SelectGroup>
134
+ </SelectContent>
135
+ </Select>
136
+ ```
137
+
138
+ **Placeholder.** Base uses a `{ value: null }` item in the items array. Radix uses `<SelectValue placeholder="...">`.
139
+
140
+ **Content positioning.** Base uses `alignItemWithTrigger`. Radix uses `position`.
141
+
142
+ ```tsx
143
+ // base.
144
+ <SelectContent alignItemWithTrigger={false} side="bottom">
145
+
146
+ // radix.
147
+ <SelectContent position="popper">
148
+ ```
149
+
150
+ ---
151
+
152
+ ## Select — multiple selection and object values (base only)
153
+
154
+ Base supports `multiple`, render-function children on `SelectValue`, and object values with `itemToStringValue`. Radix is single-select with string values only.
155
+
156
+ **Correct (base — multiple selection):**
157
+
158
+ ```tsx
159
+ <Select items={items} multiple defaultValue={[]}>
160
+ <SelectTrigger>
161
+ <SelectValue>
162
+ {(value: string[]) => (value.length === 0 ? "Select fruits" : `${value.length} selected`)}
163
+ </SelectValue>
164
+ </SelectTrigger>
165
+ ...
166
+ </Select>
167
+ ```
168
+
169
+ **Correct (base — object values):**
170
+
171
+ ```tsx
172
+ <Select defaultValue={plans[0]} itemToStringValue={(plan) => plan.name}>
173
+ <SelectTrigger>
174
+ <SelectValue>{(value) => value.name}</SelectValue>
175
+ </SelectTrigger>
176
+ ...
177
+ </Select>
178
+ ```
179
+
180
+ ---
181
+
182
+ ## ToggleGroup
183
+
184
+ Base uses a `multiple` boolean prop. Radix uses `type="single"` or `type="multiple"`.
185
+
186
+ **Incorrect (base):**
187
+
188
+ ```tsx
189
+ <ToggleGroup type="single" defaultValue="daily">
190
+ <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
191
+ </ToggleGroup>
192
+ ```
193
+
194
+ **Correct (base):**
195
+
196
+ ```tsx
197
+ // Single (no prop needed), defaultValue is always an array.
198
+ <ToggleGroup defaultValue={["daily"]} spacing={2}>
199
+ <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
200
+ <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
201
+ </ToggleGroup>
202
+
203
+ // Multi-selection.
204
+ <ToggleGroup multiple>
205
+ <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
206
+ <ToggleGroupItem value="italic">Italic</ToggleGroupItem>
207
+ </ToggleGroup>
208
+ ```
209
+
210
+ **Correct (radix):**
211
+
212
+ ```tsx
213
+ // Single, defaultValue is a string.
214
+ <ToggleGroup type="single" defaultValue="daily" spacing={2}>
215
+ <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
216
+ <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
217
+ </ToggleGroup>
218
+
219
+ // Multi-selection.
220
+ <ToggleGroup type="multiple">
221
+ <ToggleGroupItem value="bold">Bold</ToggleGroupItem>
222
+ <ToggleGroupItem value="italic">Italic</ToggleGroupItem>
223
+ </ToggleGroup>
224
+ ```
225
+
226
+ **Controlled single value:**
227
+
228
+ ```tsx
229
+ // base — wrap/unwrap arrays.
230
+ const [value, setValue] = React.useState("normal")
231
+ <ToggleGroup value={[value]} onValueChange={(v) => setValue(v[0])}>
232
+
233
+ // radix — plain string.
234
+ const [value, setValue] = React.useState("normal")
235
+ <ToggleGroup type="single" value={value} onValueChange={setValue}>
236
+ ```
237
+
238
+ ---
239
+
240
+ ## Slider
241
+
242
+ Base accepts a plain number for a single thumb. Radix always requires an array.
243
+
244
+ **Incorrect (base):**
245
+
246
+ ```tsx
247
+ <Slider defaultValue={[50]} max={100} step={1} />
248
+ ```
249
+
250
+ **Correct (base):**
251
+
252
+ ```tsx
253
+ <Slider defaultValue={50} max={100} step={1} />
254
+ ```
255
+
256
+ **Correct (radix):**
257
+
258
+ ```tsx
259
+ <Slider defaultValue={[50]} max={100} step={1} />
260
+ ```
261
+
262
+ Both use arrays for range sliders. Controlled `onValueChange` in base may need a cast:
263
+
264
+ ```tsx
265
+ // base.
266
+ const [value, setValue] = React.useState([0.3, 0.7])
267
+ <Slider value={value} onValueChange={(v) => setValue(v as number[])} />
268
+
269
+ // radix.
270
+ const [value, setValue] = React.useState([0.3, 0.7])
271
+ <Slider value={value} onValueChange={setValue} />
272
+ ```
273
+
274
+ ---
275
+
276
+ ## Accordion
277
+
278
+ Radix requires `type="single"` or `type="multiple"` and supports `collapsible`. `defaultValue` is a string. Base uses no `type` prop, uses `multiple` boolean, and `defaultValue` is always an array.
279
+
280
+ **Incorrect (base):**
281
+
282
+ ```tsx
283
+ <Accordion type="single" collapsible defaultValue="item-1">
284
+ <AccordionItem value="item-1">...</AccordionItem>
285
+ </Accordion>
286
+ ```
287
+
288
+ **Correct (base):**
289
+
290
+ ```tsx
291
+ <Accordion defaultValue={["item-1"]}>
292
+ <AccordionItem value="item-1">...</AccordionItem>
293
+ </Accordion>
294
+
295
+ // Multi-select.
296
+ <Accordion multiple defaultValue={["item-1", "item-2"]}>
297
+ <AccordionItem value="item-1">...</AccordionItem>
298
+ <AccordionItem value="item-2">...</AccordionItem>
299
+ </Accordion>
300
+ ```
301
+
302
+ **Correct (radix):**
303
+
304
+ ```tsx
305
+ <Accordion type="single" collapsible defaultValue="item-1">
306
+ <AccordionItem value="item-1">...</AccordionItem>
307
+ </Accordion>
308
+ ```
.agents/skills/shadcn/rules/composition.md ADDED
@@ -0,0 +1,197 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Component Composition
2
+
3
+ ## Contents
4
+
5
+ - Items always inside their Group component
6
+ - Callouts use Alert
7
+ - Empty states use Empty component
8
+ - Toast notifications use sonner
9
+ - Choosing between overlay components
10
+ - Dialog, Sheet, and Drawer always need a Title
11
+ - Card structure
12
+ - Button has no isPending or isLoading prop
13
+ - TabsTrigger must be inside TabsList
14
+ - Avatar always needs AvatarFallback
15
+ - Use Separator instead of raw hr or border divs
16
+ - Use Skeleton for loading placeholders
17
+ - Use Badge instead of custom styled spans
18
+
19
+ ---
20
+
21
+ ## Items always inside their Group component
22
+
23
+ Never render items directly inside the content container.
24
+
25
+ **Incorrect:**
26
+
27
+ ```tsx
28
+ <SelectContent>
29
+ <SelectItem value="apple">Apple</SelectItem>
30
+ <SelectItem value="banana">Banana</SelectItem>
31
+ </SelectContent>
32
+ ```
33
+
34
+ **Correct:**
35
+
36
+ ```tsx
37
+ <SelectContent>
38
+ <SelectGroup>
39
+ <SelectItem value="apple">Apple</SelectItem>
40
+ <SelectItem value="banana">Banana</SelectItem>
41
+ </SelectGroup>
42
+ </SelectContent>
43
+ ```
44
+
45
+ This applies to all group-based components:
46
+
47
+ | Item | Group |
48
+ | ---------------------------------------------------------- | ------------------- |
49
+ | `SelectItem`, `SelectLabel` | `SelectGroup` |
50
+ | `DropdownMenuItem`, `DropdownMenuLabel`, `DropdownMenuSub` | `DropdownMenuGroup` |
51
+ | `MenubarItem` | `MenubarGroup` |
52
+ | `ContextMenuItem` | `ContextMenuGroup` |
53
+ | `CommandItem` | `CommandGroup` |
54
+
55
+ ---
56
+
57
+ ## Callouts use Alert
58
+
59
+ ```tsx
60
+ <Alert>
61
+ <AlertTitle>Warning</AlertTitle>
62
+ <AlertDescription>Something needs attention.</AlertDescription>
63
+ </Alert>
64
+ ```
65
+
66
+ ---
67
+
68
+ ## Empty states use Empty component
69
+
70
+ ```tsx
71
+ <Empty>
72
+ <EmptyHeader>
73
+ <EmptyMedia variant="icon">
74
+ <FolderIcon />
75
+ </EmptyMedia>
76
+ <EmptyTitle>No projects yet</EmptyTitle>
77
+ <EmptyDescription>Get started by creating a new project.</EmptyDescription>
78
+ </EmptyHeader>
79
+ <EmptyContent>
80
+ <Button>Create Project</Button>
81
+ </EmptyContent>
82
+ </Empty>
83
+ ```
84
+
85
+ ---
86
+
87
+ ## Toast notifications use sonner
88
+
89
+ ```tsx
90
+ import { toast } from "sonner";
91
+
92
+ toast.success("Changes saved.");
93
+ toast.error("Something went wrong.");
94
+ toast("File deleted.", {
95
+ action: { label: "Undo", onClick: () => undoDelete() },
96
+ });
97
+ ```
98
+
99
+ ---
100
+
101
+ ## Choosing between overlay components
102
+
103
+ | Use case | Component |
104
+ | ---------------------------------- | ------------- |
105
+ | Focused task that requires input | `Dialog` |
106
+ | Destructive action confirmation | `AlertDialog` |
107
+ | Side panel with details or filters | `Sheet` |
108
+ | Mobile-first bottom panel | `Drawer` |
109
+ | Quick info on hover | `HoverCard` |
110
+ | Small contextual content on click | `Popover` |
111
+
112
+ ---
113
+
114
+ ## Dialog, Sheet, and Drawer always need a Title
115
+
116
+ `DialogTitle`, `SheetTitle`, `DrawerTitle` are required for accessibility. Use `className="sr-only"` if visually hidden.
117
+
118
+ ```tsx
119
+ <DialogContent>
120
+ <DialogHeader>
121
+ <DialogTitle>Edit Profile</DialogTitle>
122
+ <DialogDescription>Update your profile.</DialogDescription>
123
+ </DialogHeader>
124
+ ...
125
+ </DialogContent>
126
+ ```
127
+
128
+ ---
129
+
130
+ ## Card structure
131
+
132
+ Use full composition — don't dump everything into `CardContent`:
133
+
134
+ ```tsx
135
+ <Card>
136
+ <CardHeader>
137
+ <CardTitle>Team Members</CardTitle>
138
+ <CardDescription>Manage your team.</CardDescription>
139
+ </CardHeader>
140
+ <CardContent>...</CardContent>
141
+ <CardFooter>
142
+ <Button>Invite</Button>
143
+ </CardFooter>
144
+ </Card>
145
+ ```
146
+
147
+ ---
148
+
149
+ ## Button has no isPending or isLoading prop
150
+
151
+ Compose with `Spinner` + `data-icon` + `disabled`:
152
+
153
+ ```tsx
154
+ <Button disabled>
155
+ <Spinner data-icon="inline-start" />
156
+ Saving...
157
+ </Button>
158
+ ```
159
+
160
+ ---
161
+
162
+ ## TabsTrigger must be inside TabsList
163
+
164
+ Never render `TabsTrigger` directly inside `Tabs` — always wrap in `TabsList`:
165
+
166
+ ```tsx
167
+ <Tabs defaultValue="account">
168
+ <TabsList>
169
+ <TabsTrigger value="account">Account</TabsTrigger>
170
+ <TabsTrigger value="password">Password</TabsTrigger>
171
+ </TabsList>
172
+ <TabsContent value="account">...</TabsContent>
173
+ </Tabs>
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Avatar always needs AvatarFallback
179
+
180
+ Always include `AvatarFallback` for when the image fails to load:
181
+
182
+ ```tsx
183
+ <Avatar>
184
+ <AvatarImage src="/avatar.png" alt="User" />
185
+ <AvatarFallback>JD</AvatarFallback>
186
+ </Avatar>
187
+ ```
188
+
189
+ ---
190
+
191
+ ## Use existing components instead of custom markup
192
+
193
+ | Instead of | Use |
194
+ | -------------------------------------------------- | ------------------------------------ |
195
+ | `<hr>` or `<div className="border-t">` | `<Separator />` |
196
+ | `<div className="animate-pulse">` with styled divs | `<Skeleton className="h-4 w-3/4" />` |
197
+ | `<span className="rounded-full bg-green-100 ...">` | `<Badge variant="secondary">` |
.agents/skills/shadcn/rules/forms.md ADDED
@@ -0,0 +1,194 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Forms & Inputs
2
+
3
+ ## Contents
4
+
5
+ - Forms use FieldGroup + Field
6
+ - InputGroup requires InputGroupInput/InputGroupTextarea
7
+ - Buttons inside inputs use InputGroup + InputGroupAddon
8
+ - Option sets (2–7 choices) use ToggleGroup
9
+ - FieldSet + FieldLegend for grouping related fields
10
+ - Field validation and disabled states
11
+
12
+ ---
13
+
14
+ ## Forms use FieldGroup + Field
15
+
16
+ Always use `FieldGroup` + `Field` — never raw `div` with `space-y-*`:
17
+
18
+ ```tsx
19
+ <FieldGroup>
20
+ <Field>
21
+ <FieldLabel htmlFor="email">Email</FieldLabel>
22
+ <Input id="email" type="email" />
23
+ </Field>
24
+ <Field>
25
+ <FieldLabel htmlFor="password">Password</FieldLabel>
26
+ <Input id="password" type="password" />
27
+ </Field>
28
+ </FieldGroup>
29
+ ```
30
+
31
+ Use `Field orientation="horizontal"` for settings pages. Use `FieldLabel className="sr-only"` for visually hidden labels.
32
+
33
+ **Choosing form controls:**
34
+
35
+ - Simple text input → `Input`
36
+ - Dropdown with predefined options → `Select`
37
+ - Searchable dropdown → `Combobox`
38
+ - Native HTML select (no JS) → `native-select`
39
+ - Boolean toggle → `Switch` (for settings) or `Checkbox` (for forms)
40
+ - Single choice from few options → `RadioGroup`
41
+ - Toggle between 2–5 options → `ToggleGroup` + `ToggleGroupItem`
42
+ - OTP/verification code → `InputOTP`
43
+ - Multi-line text → `Textarea`
44
+
45
+ ---
46
+
47
+ ## InputGroup requires InputGroupInput/InputGroupTextarea
48
+
49
+ Never use raw `Input` or `Textarea` inside an `InputGroup`.
50
+
51
+ **Incorrect:**
52
+
53
+ ```tsx
54
+ <InputGroup>
55
+ <Input placeholder="Search..." />
56
+ </InputGroup>
57
+ ```
58
+
59
+ **Correct:**
60
+
61
+ ```tsx
62
+ import { InputGroup, InputGroupInput } from "@/components/ui/input-group";
63
+
64
+ <InputGroup>
65
+ <InputGroupInput placeholder="Search..." />
66
+ </InputGroup>;
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Buttons inside inputs use InputGroup + InputGroupAddon
72
+
73
+ Never place a `Button` directly inside or adjacent to an `Input` with custom positioning.
74
+
75
+ **Incorrect:**
76
+
77
+ ```tsx
78
+ <div className="relative">
79
+ <Input placeholder="Search..." className="pr-10" />
80
+ <Button className="absolute right-0 top-0" size="icon">
81
+ <SearchIcon />
82
+ </Button>
83
+ </div>
84
+ ```
85
+
86
+ **Correct:**
87
+
88
+ ```tsx
89
+ import { InputGroup, InputGroupInput, InputGroupAddon } from "@/components/ui/input-group";
90
+
91
+ <InputGroup>
92
+ <InputGroupInput placeholder="Search..." />
93
+ <InputGroupAddon>
94
+ <Button size="icon">
95
+ <SearchIcon data-icon="inline-start" />
96
+ </Button>
97
+ </InputGroupAddon>
98
+ </InputGroup>;
99
+ ```
100
+
101
+ ---
102
+
103
+ ## Option sets (2–7 choices) use ToggleGroup
104
+
105
+ Don't manually loop `Button` components with active state.
106
+
107
+ **Incorrect:**
108
+
109
+ ```tsx
110
+ const [selected, setSelected] = useState("daily")
111
+
112
+ <div className="flex gap-2">
113
+ {["daily", "weekly", "monthly"].map((option) => (
114
+ <Button
115
+ key={option}
116
+ variant={selected === option ? "default" : "outline"}
117
+ onClick={() => setSelected(option)}
118
+ >
119
+ {option}
120
+ </Button>
121
+ ))}
122
+ </div>
123
+ ```
124
+
125
+ **Correct:**
126
+
127
+ ```tsx
128
+ import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
129
+
130
+ <ToggleGroup spacing={2}>
131
+ <ToggleGroupItem value="daily">Daily</ToggleGroupItem>
132
+ <ToggleGroupItem value="weekly">Weekly</ToggleGroupItem>
133
+ <ToggleGroupItem value="monthly">Monthly</ToggleGroupItem>
134
+ </ToggleGroup>;
135
+ ```
136
+
137
+ Combine with `Field` for labelled toggle groups:
138
+
139
+ ```tsx
140
+ <Field orientation="horizontal">
141
+ <FieldTitle id="theme-label">Theme</FieldTitle>
142
+ <ToggleGroup aria-labelledby="theme-label" spacing={2}>
143
+ <ToggleGroupItem value="light">Light</ToggleGroupItem>
144
+ <ToggleGroupItem value="dark">Dark</ToggleGroupItem>
145
+ <ToggleGroupItem value="system">System</ToggleGroupItem>
146
+ </ToggleGroup>
147
+ </Field>
148
+ ```
149
+
150
+ > **Note:** `defaultValue` and `type`/`multiple` props differ between base and radix. See [base-vs-radix.md](./base-vs-radix.md#togglegroup).
151
+
152
+ ---
153
+
154
+ ## FieldSet + FieldLegend for grouping related fields
155
+
156
+ Use `FieldSet` + `FieldLegend` for related checkboxes, radios, or switches — not `div` with a heading:
157
+
158
+ ```tsx
159
+ <FieldSet>
160
+ <FieldLegend variant="label">Preferences</FieldLegend>
161
+ <FieldDescription>Select all that apply.</FieldDescription>
162
+ <FieldGroup className="gap-3">
163
+ <Field orientation="horizontal">
164
+ <Checkbox id="dark" />
165
+ <FieldLabel htmlFor="dark" className="font-normal">
166
+ Dark mode
167
+ </FieldLabel>
168
+ </Field>
169
+ </FieldGroup>
170
+ </FieldSet>
171
+ ```
172
+
173
+ ---
174
+
175
+ ## Field validation and disabled states
176
+
177
+ Both attributes are needed — `data-invalid`/`data-disabled` styles the field (label, description), while `aria-invalid`/`disabled` styles the control.
178
+
179
+ ```tsx
180
+ // Invalid.
181
+ <Field data-invalid>
182
+ <FieldLabel htmlFor="email">Email</FieldLabel>
183
+ <Input id="email" aria-invalid />
184
+ <FieldDescription>Invalid email address.</FieldDescription>
185
+ </Field>
186
+
187
+ // Disabled.
188
+ <Field data-disabled>
189
+ <FieldLabel htmlFor="email">Email</FieldLabel>
190
+ <Input id="email" disabled />
191
+ </Field>
192
+ ```
193
+
194
+ Works for all controls: `Input`, `Textarea`, `Select`, `Checkbox`, `RadioGroupItem`, `Switch`, `Slider`, `NativeSelect`, `InputOTP`.
.agents/skills/shadcn/rules/icons.md ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Icons
2
+
3
+ **Always use the project's configured `iconLibrary` for imports.** Check the `iconLibrary` field from project context: `lucide` → `lucide-react`, `tabler` → `@tabler/icons-react`, etc. Never assume `lucide-react`.
4
+
5
+ ---
6
+
7
+ ## Icons in Button use data-icon attribute
8
+
9
+ Add `data-icon="inline-start"` (prefix) or `data-icon="inline-end"` (suffix) to the icon. No sizing classes on the icon.
10
+
11
+ **Incorrect:**
12
+
13
+ ```tsx
14
+ <Button>
15
+ <SearchIcon className="mr-2 size-4" />
16
+ Search
17
+ </Button>
18
+ ```
19
+
20
+ **Correct:**
21
+
22
+ ```tsx
23
+ <Button>
24
+ <SearchIcon data-icon="inline-start"/>
25
+ Search
26
+ </Button>
27
+
28
+ <Button>
29
+ Next
30
+ <ArrowRightIcon data-icon="inline-end"/>
31
+ </Button>
32
+ ```
33
+
34
+ ---
35
+
36
+ ## No sizing classes on icons inside components
37
+
38
+ Components handle icon sizing via CSS. Don't add `size-4`, `w-4 h-4`, or other sizing classes to icons inside `Button`, `DropdownMenuItem`, `Alert`, `Sidebar*`, or other shadcn components. Unless the user explicitly asks for custom icon sizes.
39
+
40
+ **Incorrect:**
41
+
42
+ ```tsx
43
+ <Button>
44
+ <SearchIcon className="size-4" data-icon="inline-start" />
45
+ Search
46
+ </Button>
47
+
48
+ <DropdownMenuItem>
49
+ <SettingsIcon className="mr-2 size-4" />
50
+ Settings
51
+ </DropdownMenuItem>
52
+ ```
53
+
54
+ **Correct:**
55
+
56
+ ```tsx
57
+ <Button>
58
+ <SearchIcon data-icon="inline-start" />
59
+ Search
60
+ </Button>
61
+
62
+ <DropdownMenuItem>
63
+ <SettingsIcon />
64
+ Settings
65
+ </DropdownMenuItem>
66
+ ```
67
+
68
+ ---
69
+
70
+ ## Pass icons as component objects, not string keys
71
+
72
+ Use `icon={CheckIcon}`, not a string key to a lookup map.
73
+
74
+ **Incorrect:**
75
+
76
+ ```tsx
77
+ const iconMap = {
78
+ check: CheckIcon,
79
+ alert: AlertIcon,
80
+ };
81
+
82
+ function StatusBadge({ icon }: { icon: string }) {
83
+ const Icon = iconMap[icon];
84
+ return <Icon />;
85
+ }
86
+
87
+ <StatusBadge icon="check" />;
88
+ ```
89
+
90
+ **Correct:**
91
+
92
+ ```tsx
93
+ // Import from the project's configured iconLibrary (e.g. lucide-react, @tabler/icons-react).
94
+ import { CheckIcon } from "lucide-react";
95
+
96
+ function StatusBadge({ icon: Icon }: { icon: React.ComponentType }) {
97
+ return <Icon />;
98
+ }
99
+
100
+ <StatusBadge icon={CheckIcon} />;
101
+ ```
.agents/skills/shadcn/rules/styling.md ADDED
@@ -0,0 +1,161 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Styling & Customization
2
+
3
+ See [customization.md](../customization.md) for theming, CSS variables, and adding custom colors.
4
+
5
+ ## Contents
6
+
7
+ - Semantic colors
8
+ - Built-in variants first
9
+ - className for layout only
10
+ - No space-x-_ / space-y-_
11
+ - Prefer size-_ over w-_ h-\* when equal
12
+ - Prefer truncate shorthand
13
+ - No manual dark: color overrides
14
+ - Use cn() for conditional classes
15
+ - No manual z-index on overlay components
16
+
17
+ ---
18
+
19
+ ## Semantic colors
20
+
21
+ **Incorrect:**
22
+
23
+ ```tsx
24
+ <div className="bg-blue-500 text-white">
25
+ <p className="text-gray-600">Secondary text</p>
26
+ </div>
27
+ ```
28
+
29
+ **Correct:**
30
+
31
+ ```tsx
32
+ <div className="bg-primary text-primary-foreground">
33
+ <p className="text-muted-foreground">Secondary text</p>
34
+ </div>
35
+ ```
36
+
37
+ ---
38
+
39
+ ## No raw color values for status/state indicators
40
+
41
+ For positive, negative, or status indicators, use Badge variants, semantic tokens like `text-destructive`, or define custom CSS variables — don't reach for raw Tailwind colors.
42
+
43
+ **Incorrect:**
44
+
45
+ ```tsx
46
+ <span className="text-emerald-600">+20.1%</span>
47
+ <span className="text-green-500">Active</span>
48
+ <span className="text-red-600">-3.2%</span>
49
+ ```
50
+
51
+ **Correct:**
52
+
53
+ ```tsx
54
+ <Badge variant="secondary">+20.1%</Badge>
55
+ <Badge>Active</Badge>
56
+ <span className="text-destructive">-3.2%</span>
57
+ ```
58
+
59
+ If you need a success/positive color that doesn't exist as a semantic token, use a Badge variant or ask the user about adding a custom CSS variable to the theme (see [customization.md](../customization.md)).
60
+
61
+ ---
62
+
63
+ ## Built-in variants first
64
+
65
+ **Incorrect:**
66
+
67
+ ```tsx
68
+ <Button className="border border-input bg-transparent hover:bg-accent">Click me</Button>
69
+ ```
70
+
71
+ **Correct:**
72
+
73
+ ```tsx
74
+ <Button variant="outline">Click me</Button>
75
+ ```
76
+
77
+ ---
78
+
79
+ ## className for layout only
80
+
81
+ Use `className` for layout (e.g. `max-w-md`, `mx-auto`, `mt-4`), **not** for overriding component colors or typography. To change colors, use semantic tokens, built-in variants, or CSS variables.
82
+
83
+ **Incorrect:**
84
+
85
+ ```tsx
86
+ <Card className="bg-blue-100 text-blue-900 font-bold">
87
+ <CardContent>Dashboard</CardContent>
88
+ </Card>
89
+ ```
90
+
91
+ **Correct:**
92
+
93
+ ```tsx
94
+ <Card className="max-w-md mx-auto">
95
+ <CardContent>Dashboard</CardContent>
96
+ </Card>
97
+ ```
98
+
99
+ To customize a component's appearance, prefer these approaches in order:
100
+
101
+ 1. **Built-in variants** — `variant="outline"`, `variant="destructive"`, etc.
102
+ 2. **Semantic color tokens** — `bg-primary`, `text-muted-foreground`.
103
+ 3. **CSS variables** — define custom colors in the global CSS file (see [customization.md](../customization.md)).
104
+
105
+ ---
106
+
107
+ ## No space-x-_ / space-y-_
108
+
109
+ Use `gap-*` instead. `space-y-4` → `flex flex-col gap-4`. `space-x-2` → `flex gap-2`.
110
+
111
+ ```tsx
112
+ <div className="flex flex-col gap-4">
113
+ <Input />
114
+ <Input />
115
+ <Button>Submit</Button>
116
+ </div>
117
+ ```
118
+
119
+ ---
120
+
121
+ ## Prefer size-_ over w-_ h-\* when equal
122
+
123
+ `size-10` not `w-10 h-10`. Applies to icons, avatars, skeletons, etc.
124
+
125
+ ---
126
+
127
+ ## Prefer truncate shorthand
128
+
129
+ `truncate` not `overflow-hidden text-ellipsis whitespace-nowrap`.
130
+
131
+ ---
132
+
133
+ ## No manual dark: color overrides
134
+
135
+ Use semantic tokens — they handle light/dark via CSS variables. `bg-background text-foreground` not `bg-white dark:bg-gray-950`.
136
+
137
+ ---
138
+
139
+ ## Use cn() for conditional classes
140
+
141
+ Use the `cn()` utility from the project for conditional or merged class names. Don't write manual ternaries in className strings.
142
+
143
+ **Incorrect:**
144
+
145
+ ```tsx
146
+ <div className={`flex items-center ${isActive ? "bg-primary text-primary-foreground" : "bg-muted"}`}>
147
+ ```
148
+
149
+ **Correct:**
150
+
151
+ ```tsx
152
+ import { cn } from "@/lib/utils"
153
+
154
+ <div className={cn("flex items-center", isActive ? "bg-primary text-primary-foreground" : "bg-muted")}>
155
+ ```
156
+
157
+ ---
158
+
159
+ ## No manual z-index on overlay components
160
+
161
+ `Dialog`, `Sheet`, `Drawer`, `AlertDialog`, `DropdownMenu`, `Popover`, `Tooltip`, `HoverCard` handle their own stacking. Never add `z-50` or `z-[999]`.
.agents/skills/turborepo/SKILL.md ADDED
@@ -0,0 +1,951 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: turborepo
3
+ description: |
4
+ Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines,
5
+ dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment
6
+ variables, internal packages, monorepo structure/best practices, and boundaries.
7
+
8
+ Use when user: configures tasks/workflows/pipelines, creates packages, sets up
9
+ monorepo, shares code between apps, runs changed/affected packages, debugs cache,
10
+ or has apps/packages directories.
11
+ metadata:
12
+ version: 2.9.7-canary.13
13
+ ---
14
+
15
+ # Turborepo Skill
16
+
17
+ Build system for JavaScript/TypeScript monorepos. Turborepo caches task outputs and runs tasks in parallel based on dependency graph.
18
+
19
+ ## IMPORTANT: Package Tasks, Not Root Tasks
20
+
21
+ **DO NOT create Root Tasks. ALWAYS create package tasks.**
22
+
23
+ When creating tasks/scripts/pipelines, you MUST:
24
+
25
+ 1. Add the script to each relevant package's `package.json`
26
+ 2. Register the task in root `turbo.json`
27
+ 3. Root `package.json` only delegates via `turbo run <task>`
28
+
29
+ **DO NOT** put task logic in root `package.json`. This defeats Turborepo's parallelization.
30
+
31
+ ```json
32
+ // DO THIS: Scripts in each package
33
+ // apps/web/package.json
34
+ { "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
35
+
36
+ // apps/api/package.json
37
+ { "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
38
+
39
+ // packages/ui/package.json
40
+ { "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
41
+ ```
42
+
43
+ ```json
44
+ // turbo.json - register tasks
45
+ {
46
+ "tasks": {
47
+ "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
48
+ "lint": {},
49
+ "test": { "dependsOn": ["build"] }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ```json
55
+ // Root package.json - ONLY delegates, no task logic
56
+ {
57
+ "scripts": {
58
+ "build": "turbo run build",
59
+ "lint": "turbo run lint",
60
+ "test": "turbo run test"
61
+ }
62
+ }
63
+ ```
64
+
65
+ ```json
66
+ // DO NOT DO THIS - defeats parallelization
67
+ // Root package.json
68
+ {
69
+ "scripts": {
70
+ "build": "cd apps/web && next build && cd ../api && tsc",
71
+ "lint": "eslint apps/ packages/",
72
+ "test": "vitest"
73
+ }
74
+ }
75
+ ```
76
+
77
+ Root Tasks (`//#taskname`) are ONLY for tasks that truly cannot exist in packages (rare).
78
+
79
+ ## Secondary Rule: `turbo run` vs `turbo`
80
+
81
+ **Always use `turbo run` when the command is written into code:**
82
+
83
+ ```json
84
+ // package.json - ALWAYS "turbo run"
85
+ {
86
+ "scripts": {
87
+ "build": "turbo run build"
88
+ }
89
+ }
90
+ ```
91
+
92
+ ```yaml
93
+ # CI workflows - ALWAYS "turbo run"
94
+ - run: turbo run build --affected
95
+ ```
96
+
97
+ **The shorthand `turbo <tasks>` is ONLY for one-off terminal commands** typed directly by humans or agents. Never write `turbo build` into package.json, CI, or scripts.
98
+
99
+ ## Quick Decision Trees
100
+
101
+ ### "I need to configure a task"
102
+
103
+ ```
104
+ Configure a task?
105
+ ├─ Define task dependencies → references/configuration/tasks.md
106
+ ├─ Lint/check-types (parallel + caching) → Use Transit Nodes pattern (see below)
107
+ ├─ Specify build outputs → references/configuration/tasks.md#outputs
108
+ ├─ Handle environment variables → references/environment/RULE.md
109
+ ├─ Set up dev/watch tasks → references/configuration/tasks.md#persistent
110
+ ├─ Package-specific config → references/configuration/RULE.md#package-configurations
111
+ └─ Global settings (cacheDir, daemon) → references/configuration/global-options.md
112
+ ```
113
+
114
+ ### "My cache isn't working"
115
+
116
+ ```
117
+ Cache problems?
118
+ ├─ Tasks run but outputs not restored → Missing `outputs` key
119
+ ├─ Cache misses unexpectedly → references/caching/gotchas.md
120
+ ├─ Need to debug hash inputs → Use --summarize or --dry
121
+ ├─ Want to skip cache entirely → Use --force or cache: false
122
+ ├─ Remote cache not working → references/caching/remote-cache.md
123
+ └─ Environment causing misses → references/environment/gotchas.md
124
+ ```
125
+
126
+ ### "I want to run only changed packages"
127
+
128
+ ```
129
+ Run only what changed?
130
+ ├─ Changed packages + dependents (RECOMMENDED) → turbo run build --affected
131
+ ├─ Custom base branch → --affected --affected-base=origin/develop
132
+ ├─ Manual git comparison → --filter=...[origin/main]
133
+ └─ See all filter options → references/filtering/RULE.md
134
+ ```
135
+
136
+ **`--affected` is the primary way to run only changed packages.** It automatically compares against the default branch and includes dependents.
137
+
138
+ ### "I want to filter packages"
139
+
140
+ ```
141
+ Filter packages?
142
+ ├─ Only changed packages → --affected (see above)
143
+ ├─ By package name → --filter=web
144
+ ├─ By directory → --filter=./apps/*
145
+ ├─ Package + dependencies → --filter=web...
146
+ ├─ Package + dependents → --filter=...web
147
+ └─ Complex combinations → references/filtering/patterns.md
148
+ ```
149
+
150
+ ### "Environment variables aren't working"
151
+
152
+ ```
153
+ Environment issues?
154
+ ├─ Vars not available at runtime → Strict mode filtering (default)
155
+ ├─ Cache hits with wrong env → Var not in `env` key
156
+ ├─ .env changes not causing rebuilds → .env not in `inputs`
157
+ ├─ CI variables missing → references/environment/gotchas.md
158
+ └─ Framework vars (NEXT_PUBLIC_*) → Auto-included via inference
159
+ ```
160
+
161
+ ### "I need to set up CI"
162
+
163
+ ```
164
+ CI setup?
165
+ ├─ GitHub Actions → references/ci/github-actions.md
166
+ ├─ Vercel deployment → references/ci/vercel.md
167
+ ├─ Remote cache in CI → references/caching/remote-cache.md
168
+ ├─ Only build changed packages → --affected flag
169
+ ├─ Skip unnecessary builds → turbo-ignore (references/cli/commands.md)
170
+ └─ Skip container setup when no changes → turbo-ignore
171
+ ```
172
+
173
+ ### "I want to watch for changes during development"
174
+
175
+ ```
176
+ Watch mode?
177
+ ├─ Re-run tasks on change → turbo watch (references/watch/RULE.md)
178
+ ├─ Dev servers with dependencies → Use `with` key (references/configuration/tasks.md#with)
179
+ ├─ Restart dev server on dep change → Use `interruptible: true`
180
+ └─ Persistent dev tasks → Use `persistent: true`
181
+ ```
182
+
183
+ ### "I need to create/structure a package"
184
+
185
+ ```
186
+ Package creation/structure?
187
+ ├─ Create an internal package → references/best-practices/packages.md
188
+ ├─ Repository structure → references/best-practices/structure.md
189
+ ├─ Dependency management → references/best-practices/dependencies.md
190
+ ├─ Best practices overview → references/best-practices/RULE.md
191
+ ├─ JIT vs Compiled packages → references/best-practices/packages.md#compilation-strategies
192
+ └─ Sharing code between apps → references/best-practices/RULE.md#package-types
193
+ ```
194
+
195
+ ### "How should I structure my monorepo?"
196
+
197
+ ```
198
+ Monorepo structure?
199
+ ├─ Standard layout (apps/, packages/) → references/best-practices/RULE.md
200
+ ├─ Package types (apps vs libraries) → references/best-practices/RULE.md#package-types
201
+ ├─ Creating internal packages → references/best-practices/packages.md
202
+ ├─ TypeScript configuration → references/best-practices/structure.md#typescript-configuration
203
+ ├─ ESLint configuration → references/best-practices/structure.md#eslint-configuration
204
+ ├─ Dependency management → references/best-practices/dependencies.md
205
+ └─ Enforce package boundaries → references/boundaries/RULE.md
206
+ ```
207
+
208
+ ### "I want to enforce architectural boundaries"
209
+
210
+ ```
211
+ Enforce boundaries?
212
+ ├─ Check for violations → turbo boundaries
213
+ ├─ Tag packages → references/boundaries/RULE.md#tags
214
+ ├─ Restrict which packages can import others → references/boundaries/RULE.md#rule-types
215
+ └─ Prevent cross-package file imports → references/boundaries/RULE.md
216
+ ```
217
+
218
+ ## Critical Anti-Patterns
219
+
220
+ ### Using `turbo` Shorthand in Code
221
+
222
+ **`turbo run` is recommended in package.json scripts and CI pipelines.** The shorthand `turbo <task>` is intended for interactive terminal use.
223
+
224
+ ```json
225
+ // WRONG - using shorthand in package.json
226
+ {
227
+ "scripts": {
228
+ "build": "turbo build",
229
+ "dev": "turbo dev"
230
+ }
231
+ }
232
+
233
+ // CORRECT
234
+ {
235
+ "scripts": {
236
+ "build": "turbo run build",
237
+ "dev": "turbo run dev"
238
+ }
239
+ }
240
+ ```
241
+
242
+ ```yaml
243
+ # WRONG - using shorthand in CI
244
+ - run: turbo build --affected
245
+
246
+ # CORRECT
247
+ - run: turbo run build --affected
248
+ ```
249
+
250
+ ### Root Scripts Bypassing Turbo
251
+
252
+ Root `package.json` scripts MUST delegate to `turbo run`, not run tasks directly.
253
+
254
+ ```json
255
+ // WRONG - bypasses turbo entirely
256
+ {
257
+ "scripts": {
258
+ "build": "bun build",
259
+ "dev": "bun dev"
260
+ }
261
+ }
262
+
263
+ // CORRECT - delegates to turbo
264
+ {
265
+ "scripts": {
266
+ "build": "turbo run build",
267
+ "dev": "turbo run dev"
268
+ }
269
+ }
270
+ ```
271
+
272
+ ### Using `&&` to Chain Turbo Tasks
273
+
274
+ Don't chain turbo tasks with `&&`. Let turbo orchestrate.
275
+
276
+ ```json
277
+ // WRONG - turbo task not using turbo run
278
+ {
279
+ "scripts": {
280
+ "changeset:publish": "bun build && changeset publish"
281
+ }
282
+ }
283
+
284
+ // CORRECT
285
+ {
286
+ "scripts": {
287
+ "changeset:publish": "turbo run build && changeset publish"
288
+ }
289
+ }
290
+ ```
291
+
292
+ ### `prebuild` Scripts That Manually Build Dependencies
293
+
294
+ Scripts like `prebuild` that manually build other packages bypass Turborepo's dependency graph.
295
+
296
+ ```json
297
+ // WRONG - manually building dependencies
298
+ {
299
+ "scripts": {
300
+ "prebuild": "cd ../../packages/types && bun run build && cd ../utils && bun run build",
301
+ "build": "next build"
302
+ }
303
+ }
304
+ ```
305
+
306
+ **However, the fix depends on whether workspace dependencies are declared:**
307
+
308
+ 1. **If dependencies ARE declared** (e.g., `"@repo/types": "workspace:*"` in package.json), remove the `prebuild` script. Turbo's `dependsOn: ["^build"]` handles this automatically.
309
+
310
+ 2. **If dependencies are NOT declared**, the `prebuild` exists because `^build` won't trigger without a dependency relationship. The fix is to:
311
+ - Add the dependency to package.json: `"@repo/types": "workspace:*"`
312
+ - Then remove the `prebuild` script
313
+
314
+ ```json
315
+ // CORRECT - declare dependency, let turbo handle build order
316
+ // package.json
317
+ {
318
+ "dependencies": {
319
+ "@repo/types": "workspace:*",
320
+ "@repo/utils": "workspace:*"
321
+ },
322
+ "scripts": {
323
+ "build": "next build"
324
+ }
325
+ }
326
+
327
+ // turbo.json
328
+ {
329
+ "tasks": {
330
+ "build": {
331
+ "dependsOn": ["^build"]
332
+ }
333
+ }
334
+ }
335
+ ```
336
+
337
+ **Key insight:** `^build` only runs build in packages listed as dependencies. No dependency declaration = no automatic build ordering.
338
+
339
+ ### Overly Broad `globalDependencies`
340
+
341
+ `globalDependencies` affects ALL tasks in ALL packages via the **global hash** — tasks cannot opt out of specific files, even with negation globs in `inputs`. Be specific.
342
+
343
+ ```json
344
+ // WRONG - heavy hammer, affects all hashes
345
+ {
346
+ "globalDependencies": ["**/.env.*local"]
347
+ }
348
+
349
+ // BETTER - move to task-level inputs
350
+ {
351
+ "globalDependencies": [".env"],
352
+ "tasks": {
353
+ "build": {
354
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
355
+ "outputs": ["dist/**"]
356
+ }
357
+ }
358
+ }
359
+ ```
360
+
361
+ With `futureFlags.globalConfiguration`, this problem is reduced because `global.inputs` files are folded into each task's inputs (not the global hash). Tasks can exclude specific files:
362
+
363
+ ```json
364
+ // BEST - global.inputs with per-task exclusion
365
+ {
366
+ "futureFlags": { "globalConfiguration": true },
367
+ "global": {
368
+ "inputs": [".env"]
369
+ },
370
+ "tasks": {
371
+ "build": { "outputs": ["dist/**"] },
372
+ "lint": {
373
+ "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/.env"]
374
+ }
375
+ }
376
+ }
377
+ ```
378
+
379
+ ### Repetitive Task Configuration
380
+
381
+ Look for repeated configuration across tasks that can be collapsed. Turborepo supports shared configuration patterns.
382
+
383
+ ```json
384
+ // WRONG - repetitive env and inputs across tasks
385
+ {
386
+ "tasks": {
387
+ "build": {
388
+ "env": ["API_URL", "DATABASE_URL"],
389
+ "inputs": ["$TURBO_DEFAULT$", ".env*"]
390
+ },
391
+ "test": {
392
+ "env": ["API_URL", "DATABASE_URL"],
393
+ "inputs": ["$TURBO_DEFAULT$", ".env*"]
394
+ },
395
+ "dev": {
396
+ "env": ["API_URL", "DATABASE_URL"],
397
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
398
+ "cache": false,
399
+ "persistent": true
400
+ }
401
+ }
402
+ }
403
+
404
+ // BETTER - use globalEnv and globalDependencies for shared config
405
+ {
406
+ "globalEnv": ["API_URL", "DATABASE_URL"],
407
+ "globalDependencies": [".env*"],
408
+ "tasks": {
409
+ "build": {},
410
+ "test": {},
411
+ "dev": {
412
+ "cache": false,
413
+ "persistent": true
414
+ }
415
+ }
416
+ }
417
+ ```
418
+
419
+ **When to use global vs task-level:**
420
+
421
+ - `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
422
+ - Task-level `env` / `inputs` - use when only specific tasks need it
423
+
424
+ ### NOT an Anti-Pattern: Large `env` Arrays
425
+
426
+ A large `env` array (even 50+ variables) is **not** a problem. It usually means the user was thorough about declaring their build's environment dependencies. Do not flag this as an issue.
427
+
428
+ ### Using `--parallel` Flag
429
+
430
+ The `--parallel` flag bypasses Turborepo's dependency graph. If tasks need parallel execution, configure `dependsOn` correctly instead.
431
+
432
+ ```bash
433
+ # WRONG - bypasses dependency graph
434
+ turbo run lint --parallel
435
+
436
+ # CORRECT - configure tasks to allow parallel execution
437
+ # In turbo.json, set dependsOn appropriately (or use transit nodes)
438
+ turbo run lint
439
+ ```
440
+
441
+ ### Package-Specific Task Overrides in Root turbo.json
442
+
443
+ When multiple packages need different task configurations, use **Package Configurations** (`turbo.json` in each package) instead of cluttering root `turbo.json` with `package#task` overrides.
444
+
445
+ ```json
446
+ // WRONG - root turbo.json with many package-specific overrides
447
+ {
448
+ "tasks": {
449
+ "test": { "dependsOn": ["build"] },
450
+ "@repo/web#test": { "outputs": ["coverage/**"] },
451
+ "@repo/api#test": { "outputs": ["coverage/**"] },
452
+ "@repo/utils#test": { "outputs": [] },
453
+ "@repo/cli#test": { "outputs": [] },
454
+ "@repo/core#test": { "outputs": [] }
455
+ }
456
+ }
457
+
458
+ // CORRECT - use Package Configurations
459
+ // Root turbo.json - base config only
460
+ {
461
+ "tasks": {
462
+ "test": { "dependsOn": ["build"] }
463
+ }
464
+ }
465
+
466
+ // packages/web/turbo.json - package-specific override
467
+ {
468
+ "extends": ["//"],
469
+ "tasks": {
470
+ "test": { "outputs": ["coverage/**"] }
471
+ }
472
+ }
473
+
474
+ // packages/api/turbo.json
475
+ {
476
+ "extends": ["//"],
477
+ "tasks": {
478
+ "test": { "outputs": ["coverage/**"] }
479
+ }
480
+ }
481
+ ```
482
+
483
+ **Benefits of Package Configurations:**
484
+
485
+ - Keeps configuration close to the code it affects
486
+ - Root turbo.json stays clean and focused on base patterns
487
+ - Easier to understand what's special about each package
488
+ - Works with `$TURBO_EXTENDS$` to inherit + extend arrays
489
+
490
+ **When to use `package#task` in root:**
491
+
492
+ - Single package needs a unique dependency (e.g., `"deploy": { "dependsOn": ["web#build"] }`)
493
+ - Temporary override while migrating
494
+
495
+ See `references/configuration/RULE.md#package-configurations` for full details.
496
+
497
+ ### Using `../` to Traverse Out of Package in `inputs`
498
+
499
+ Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
500
+
501
+ ```json
502
+ // WRONG - traversing out of package
503
+ {
504
+ "tasks": {
505
+ "build": {
506
+ "inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
507
+ }
508
+ }
509
+ }
510
+
511
+ // CORRECT - use $TURBO_ROOT$ for repo root
512
+ {
513
+ "tasks": {
514
+ "build": {
515
+ "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
516
+ }
517
+ }
518
+ }
519
+ ```
520
+
521
+ ### Missing `outputs` for File-Producing Tasks
522
+
523
+ **Before flagging missing `outputs`, check what the task actually produces:**
524
+
525
+ 1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
526
+ 2. Determine if it writes files to disk or only outputs to stdout
527
+ 3. Only flag if the task produces files that should be cached
528
+
529
+ ```json
530
+ // WRONG: build produces files but they're not cached
531
+ {
532
+ "tasks": {
533
+ "build": {
534
+ "dependsOn": ["^build"]
535
+ }
536
+ }
537
+ }
538
+
539
+ // CORRECT: build outputs are cached
540
+ {
541
+ "tasks": {
542
+ "build": {
543
+ "dependsOn": ["^build"],
544
+ "outputs": ["dist/**"]
545
+ }
546
+ }
547
+ }
548
+ ```
549
+
550
+ Common outputs by framework:
551
+
552
+ - Next.js: `[".next/**", "!.next/cache/**"]`
553
+ - Vite/Rollup: `["dist/**"]`
554
+ - tsc: `["dist/**"]` or custom `outDir`
555
+
556
+ **TypeScript `--noEmit` can still produce cache files:**
557
+
558
+ When `incremental: true` in tsconfig.json, `tsc --noEmit` writes `.tsbuildinfo` files even without emitting JS. Check the tsconfig before assuming no outputs:
559
+
560
+ ```json
561
+ // If tsconfig has incremental: true, tsc --noEmit produces cache files
562
+ {
563
+ "tasks": {
564
+ "typecheck": {
565
+ "outputs": ["node_modules/.cache/tsbuildinfo.json"] // or wherever tsBuildInfoFile points
566
+ }
567
+ }
568
+ }
569
+ ```
570
+
571
+ To determine correct outputs for TypeScript tasks:
572
+
573
+ 1. Check if `incremental` or `composite` is enabled in tsconfig
574
+ 2. Check `tsBuildInfoFile` for custom cache location (default: alongside `outDir` or in project root)
575
+ 3. If no incremental mode, `tsc --noEmit` produces no files
576
+
577
+ ### `^build` vs `build` Confusion
578
+
579
+ ```json
580
+ {
581
+ "tasks": {
582
+ // ^build = run build in DEPENDENCIES first (other packages this one imports)
583
+ "build": {
584
+ "dependsOn": ["^build"]
585
+ },
586
+ // build (no ^) = run build in SAME PACKAGE first
587
+ "test": {
588
+ "dependsOn": ["build"]
589
+ },
590
+ // pkg#task = specific package's task
591
+ "deploy": {
592
+ "dependsOn": ["web#build"]
593
+ }
594
+ }
595
+ }
596
+ ```
597
+
598
+ ### Environment Variables Not Hashed
599
+
600
+ ```json
601
+ // WRONG: API_URL changes won't cause rebuilds
602
+ {
603
+ "tasks": {
604
+ "build": {
605
+ "outputs": ["dist/**"]
606
+ }
607
+ }
608
+ }
609
+
610
+ // CORRECT: API_URL changes invalidate cache
611
+ {
612
+ "tasks": {
613
+ "build": {
614
+ "outputs": ["dist/**"],
615
+ "env": ["API_URL", "API_KEY"]
616
+ }
617
+ }
618
+ }
619
+ ```
620
+
621
+ ### `.env` Files Not in Inputs
622
+
623
+ Turbo does NOT load `.env` files - your framework does. But Turbo needs to know about changes:
624
+
625
+ ```json
626
+ // WRONG: .env changes don't invalidate cache
627
+ {
628
+ "tasks": {
629
+ "build": {
630
+ "env": ["API_URL"]
631
+ }
632
+ }
633
+ }
634
+
635
+ // CORRECT: .env file changes invalidate cache
636
+ {
637
+ "tasks": {
638
+ "build": {
639
+ "env": ["API_URL"],
640
+ "inputs": ["$TURBO_DEFAULT$", ".env", ".env.*"]
641
+ }
642
+ }
643
+ }
644
+ ```
645
+
646
+ ### Root `.env` File in Monorepo
647
+
648
+ A `.env` file at the repo root is an anti-pattern — even for small monorepos or starter templates. It creates implicit coupling between packages and makes it unclear which packages depend on which variables.
649
+
650
+ ```
651
+ // WRONG - root .env affects all packages implicitly
652
+ my-monorepo/
653
+ ├── .env # Which packages use this?
654
+ ├── apps/
655
+ │ ├── web/
656
+ │ └── api/
657
+ └── packages/
658
+
659
+ // CORRECT - .env files in packages that need them
660
+ my-monorepo/
661
+ ├── apps/
662
+ │ ├── web/
663
+ │ │ └── .env # Clear: web needs DATABASE_URL
664
+ │ └── api/
665
+ │ └── .env # Clear: api needs API_KEY
666
+ └── packages/
667
+ ```
668
+
669
+ **Problems with root `.env`:**
670
+
671
+ - Unclear which packages consume which variables
672
+ - All packages get all variables (even ones they don't need)
673
+ - Cache invalidation is coarse-grained (root .env change invalidates everything)
674
+ - Security risk: packages may accidentally access sensitive vars meant for others
675
+ - Bad habits start small — starter templates should model correct patterns
676
+
677
+ **If you must share variables**, use `globalEnv` to be explicit about what's shared, and document why.
678
+
679
+ ### Strict Mode Filtering CI Variables
680
+
681
+ By default, Turborepo filters environment variables to only those in `env`/`globalEnv`. CI variables may be missing:
682
+
683
+ ```json
684
+ // If CI scripts need GITHUB_TOKEN but it's not in env:
685
+ {
686
+ "globalPassThroughEnv": ["GITHUB_TOKEN", "CI"],
687
+ "tasks": { ... }
688
+ }
689
+ ```
690
+
691
+ Or use `--env-mode=loose` (not recommended for production).
692
+
693
+ ### Shared Code in Apps (Should Be a Package)
694
+
695
+ ```
696
+ // WRONG: Shared code inside an app
697
+ apps/
698
+ web/
699
+ shared/ # This breaks monorepo principles!
700
+ utils.ts
701
+
702
+ // CORRECT: Extract to a package
703
+ packages/
704
+ utils/
705
+ src/utils.ts
706
+ ```
707
+
708
+ ### Accessing Files Across Package Boundaries
709
+
710
+ ```typescript
711
+ // WRONG: Reaching into another package's internals
712
+ import { Button } from "../../packages/ui/src/button";
713
+
714
+ // CORRECT: Install and import properly
715
+ import { Button } from "@repo/ui/button";
716
+ ```
717
+
718
+ ### Too Many Root Dependencies
719
+
720
+ ```json
721
+ // WRONG: App dependencies in root
722
+ {
723
+ "dependencies": {
724
+ "react": "^18",
725
+ "next": "^14"
726
+ }
727
+ }
728
+
729
+ // CORRECT: Only repo tools in root
730
+ {
731
+ "devDependencies": {
732
+ "turbo": "latest"
733
+ }
734
+ }
735
+ ```
736
+
737
+ ## Common Task Configurations
738
+
739
+ ### Standard Build Pipeline
740
+
741
+ ```json
742
+ {
743
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
744
+ "tasks": {
745
+ "build": {
746
+ "dependsOn": ["^build"],
747
+ "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
748
+ },
749
+ "dev": {
750
+ "cache": false,
751
+ "persistent": true
752
+ }
753
+ }
754
+ }
755
+ ```
756
+
757
+ Add a `transit` task if you have tasks that need parallel execution with cache invalidation (see below).
758
+
759
+ ### Dev Task with `^dev` Pattern (for `turbo watch`)
760
+
761
+ A `dev` task with `dependsOn: ["^dev"]` and `persistent: false` in root turbo.json may look unusual but is **correct for `turbo watch` workflows**:
762
+
763
+ ```json
764
+ // Root turbo.json
765
+ {
766
+ "tasks": {
767
+ "dev": {
768
+ "dependsOn": ["^dev"],
769
+ "cache": false,
770
+ "persistent": false // Packages have one-shot dev scripts
771
+ }
772
+ }
773
+ }
774
+
775
+ // Package turbo.json (apps/web/turbo.json)
776
+ {
777
+ "extends": ["//"],
778
+ "tasks": {
779
+ "dev": {
780
+ "persistent": true // Apps run long-running dev servers
781
+ }
782
+ }
783
+ }
784
+ ```
785
+
786
+ **Why this works:**
787
+
788
+ - **Packages** (e.g., `@acme/db`, `@acme/validators`) have `"dev": "tsc"` — one-shot type generation that completes quickly
789
+ - **Apps** override with `persistent: true` for actual dev servers (Next.js, etc.)
790
+ - **`turbo watch`** re-runs the one-shot package `dev` scripts when source files change, keeping types in sync
791
+
792
+ **Intended usage:** Run `turbo watch dev` (not `turbo run dev`). Watch mode re-executes one-shot tasks on file changes while keeping persistent tasks running.
793
+
794
+ **Alternative pattern:** Use a separate task name like `prepare` or `generate` for one-shot dependency builds to make the intent clearer:
795
+
796
+ ```json
797
+ {
798
+ "tasks": {
799
+ "prepare": {
800
+ "dependsOn": ["^prepare"],
801
+ "outputs": ["dist/**"]
802
+ },
803
+ "dev": {
804
+ "dependsOn": ["prepare"],
805
+ "cache": false,
806
+ "persistent": true
807
+ }
808
+ }
809
+ }
810
+ ```
811
+
812
+ ### Transit Nodes for Parallel Tasks with Cache Invalidation
813
+
814
+ Some tasks can run in parallel (don't need built output from dependencies) but must invalidate cache when dependency source code changes.
815
+
816
+ **The problem with `dependsOn: ["^taskname"]`:**
817
+
818
+ - Forces sequential execution (slow)
819
+
820
+ **The problem with `dependsOn: []` (no dependencies):**
821
+
822
+ - Allows parallel execution (fast)
823
+ - But cache is INCORRECT - changing dependency source won't invalidate cache
824
+
825
+ **Transit Nodes solve both:**
826
+
827
+ ```json
828
+ {
829
+ "tasks": {
830
+ "transit": { "dependsOn": ["^transit"] },
831
+ "my-task": { "dependsOn": ["transit"] }
832
+ }
833
+ }
834
+ ```
835
+
836
+ The `transit` task creates dependency relationships without matching any actual script, so tasks run in parallel with correct cache invalidation.
837
+
838
+ **How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
839
+
840
+ ### With Environment Variables
841
+
842
+ ```json
843
+ {
844
+ "globalEnv": ["NODE_ENV"],
845
+ "globalDependencies": [".env"],
846
+ "tasks": {
847
+ "build": {
848
+ "dependsOn": ["^build"],
849
+ "outputs": ["dist/**"],
850
+ "env": ["API_URL", "DATABASE_URL"]
851
+ }
852
+ }
853
+ }
854
+ ```
855
+
856
+ With `futureFlags.globalConfiguration`, the same config moves global settings under `global` — and `.env` becomes a per-task input instead of a global hash input:
857
+
858
+ ```json
859
+ {
860
+ "futureFlags": { "globalConfiguration": true },
861
+ "global": {
862
+ "env": ["NODE_ENV"],
863
+ "inputs": [".env"]
864
+ },
865
+ "tasks": {
866
+ "build": {
867
+ "dependsOn": ["^build"],
868
+ "outputs": ["dist/**"],
869
+ "env": ["API_URL", "DATABASE_URL"]
870
+ }
871
+ }
872
+ }
873
+ ```
874
+
875
+ ## Reference Index
876
+
877
+ ### Configuration
878
+
879
+ | File | Purpose |
880
+ | ------------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
881
+ | [configuration/RULE.md](./references/configuration/RULE.md) | turbo.json overview, Package Configurations |
882
+ | [configuration/tasks.md](./references/configuration/tasks.md) | dependsOn, outputs, inputs, env, cache, persistent |
883
+ | [configuration/global-options.md](./references/configuration/global-options.md) | globalEnv, globalDependencies, global key, futureFlags, cacheDir, envMode |
884
+ | [configuration/gotchas.md](./references/configuration/gotchas.md) | Common configuration mistakes |
885
+
886
+ ### Caching
887
+
888
+ | File | Purpose |
889
+ | --------------------------------------------------------------- | -------------------------------------------- |
890
+ | [caching/RULE.md](./references/caching/RULE.md) | How caching works, hash inputs |
891
+ | [caching/remote-cache.md](./references/caching/remote-cache.md) | Vercel Remote Cache, self-hosted, login/link |
892
+ | [caching/gotchas.md](./references/caching/gotchas.md) | Debugging cache misses, --summarize, --dry |
893
+
894
+ ### Environment Variables
895
+
896
+ | File | Purpose |
897
+ | ------------------------------------------------------------- | ----------------------------------------- |
898
+ | [environment/RULE.md](./references/environment/RULE.md) | env, globalEnv, passThroughEnv |
899
+ | [environment/modes.md](./references/environment/modes.md) | Strict vs Loose mode, framework inference |
900
+ | [environment/gotchas.md](./references/environment/gotchas.md) | .env files, CI issues |
901
+
902
+ ### Filtering
903
+
904
+ | File | Purpose |
905
+ | ----------------------------------------------------------- | ------------------------ |
906
+ | [filtering/RULE.md](./references/filtering/RULE.md) | --filter syntax overview |
907
+ | [filtering/patterns.md](./references/filtering/patterns.md) | Common filter patterns |
908
+
909
+ ### CI/CD
910
+
911
+ | File | Purpose |
912
+ | --------------------------------------------------------- | ------------------------------- |
913
+ | [ci/RULE.md](./references/ci/RULE.md) | General CI principles |
914
+ | [ci/github-actions.md](./references/ci/github-actions.md) | Complete GitHub Actions setup |
915
+ | [ci/vercel.md](./references/ci/vercel.md) | Vercel deployment, turbo-ignore |
916
+ | [ci/patterns.md](./references/ci/patterns.md) | --affected, caching strategies |
917
+
918
+ ### CLI
919
+
920
+ | File | Purpose |
921
+ | ----------------------------------------------- | --------------------------------------------- |
922
+ | [cli/RULE.md](./references/cli/RULE.md) | turbo run basics |
923
+ | [cli/commands.md](./references/cli/commands.md) | turbo run flags, turbo-ignore, other commands |
924
+
925
+ ### Best Practices
926
+
927
+ | File | Purpose |
928
+ | ----------------------------------------------------------------------------- | --------------------------------------------------------------- |
929
+ | [best-practices/RULE.md](./references/best-practices/RULE.md) | Monorepo best practices overview |
930
+ | [best-practices/structure.md](./references/best-practices/structure.md) | Repository structure, workspace config, TypeScript/ESLint setup |
931
+ | [best-practices/packages.md](./references/best-practices/packages.md) | Creating internal packages, JIT vs Compiled, exports |
932
+ | [best-practices/dependencies.md](./references/best-practices/dependencies.md) | Dependency management, installing, version sync |
933
+
934
+ ### Watch Mode
935
+
936
+ | File | Purpose |
937
+ | ------------------------------------------- | ----------------------------------------------- |
938
+ | [watch/RULE.md](./references/watch/RULE.md) | turbo watch, interruptible tasks, dev workflows |
939
+
940
+ ### Boundaries (Experimental)
941
+
942
+ | File | Purpose |
943
+ | ----------------------------------------------------- | ----------------------------------------------------- |
944
+ | [boundaries/RULE.md](./references/boundaries/RULE.md) | Enforce package isolation, tag-based dependency rules |
945
+
946
+ ## Source Documentation
947
+
948
+ This skill is based on the official Turborepo documentation at:
949
+
950
+ - Source: `apps/docs/content/docs/` in the Turborepo repository
951
+ - Live: https://turborepo.dev/docs
.agents/skills/turborepo/command/turborepo.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ description: Load Turborepo skill for creating workflows, tasks, and pipelines in monorepos. Use when users ask to "create a workflow", "make a task", "generate a pipeline", or set up build orchestration.
3
+ ---
4
+
5
+ Load the Turborepo skill and help with monorepo task orchestration: creating workflows, configuring tasks, setting up pipelines, and optimizing builds.
6
+
7
+ ## Workflow
8
+
9
+ ### Step 1: Load turborepo skill
10
+
11
+ ```
12
+ skill({ name: 'turborepo' })
13
+ ```
14
+
15
+ ### Step 2: Identify task type from user request
16
+
17
+ Analyze $ARGUMENTS to determine:
18
+
19
+ - **Topic**: configuration, caching, filtering, environment, CI, or CLI
20
+ - **Task type**: new setup, debugging, optimization, or implementation
21
+
22
+ Use decision trees in SKILL.md to select the relevant reference files.
23
+
24
+ ### Step 3: Read relevant reference files
25
+
26
+ Based on task type, read from `references/<topic>/`:
27
+
28
+ | Task | Files to Read |
29
+ | -------------------- | ------------------------------------------------------- |
30
+ | Configure turbo.json | `configuration/RULE.md` + `configuration/tasks.md` |
31
+ | Debug cache issues | `caching/gotchas.md` |
32
+ | Set up remote cache | `caching/remote-cache.md` |
33
+ | Filter packages | `filtering/RULE.md` + `filtering/patterns.md` |
34
+ | Environment problems | `environment/gotchas.md` + `environment/modes.md` |
35
+ | Set up CI | `ci/RULE.md` + `ci/github-actions.md` or `ci/vercel.md` |
36
+ | CLI usage | `cli/commands.md` |
37
+
38
+ ### Step 4: Execute task
39
+
40
+ Apply Turborepo-specific patterns from references to complete the user's request.
41
+
42
+ **CRITICAL - When creating tasks/scripts/pipelines:**
43
+
44
+ 1. **DO NOT create Root Tasks** - Always create package tasks
45
+ 2. Add scripts to each relevant package's `package.json` (e.g., `apps/web/package.json`, `packages/ui/package.json`)
46
+ 3. Register the task in root `turbo.json`
47
+ 4. Root `package.json` only contains `turbo run <task>` - never actual task logic
48
+
49
+ **Other things to verify:**
50
+
51
+ - `outputs` defined for cacheable tasks
52
+ - `dependsOn` uses correct syntax (`^task` vs `task`)
53
+ - Environment variables in `env` key
54
+ - `.env` files in `inputs` if used
55
+ - Use `turbo run` (not `turbo`) in package.json and CI
56
+
57
+ ### Step 5: Summarize
58
+
59
+ ```
60
+ === Turborepo Task Complete ===
61
+
62
+ Topic: <configuration|caching|filtering|environment|ci|cli>
63
+ Files referenced: <reference files consulted>
64
+
65
+ <brief summary of what was done>
66
+ ```
67
+
68
+ <user-request>
69
+ $ARGUMENTS
70
+ </user-request>
.agents/skills/turborepo/references/best-practices/RULE.md ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Monorepo Best Practices
2
+
3
+ Essential patterns for structuring and maintaining a healthy Turborepo monorepo.
4
+
5
+ ## Repository Structure
6
+
7
+ ### Standard Layout
8
+
9
+ ```
10
+ my-monorepo/
11
+ ├── apps/ # Application packages (deployable)
12
+ │ ├── web/
13
+ │ ├── docs/
14
+ │ └── api/
15
+ ├── packages/ # Library packages (shared code)
16
+ │ ├── ui/
17
+ │ ├── utils/
18
+ │ └── config-*/ # Shared configs (eslint, typescript, etc.)
19
+ ├── package.json # Root package.json (minimal deps)
20
+ ├── turbo.json # Turborepo configuration
21
+ ├── pnpm-workspace.yaml # (pnpm) or workspaces in package.json
22
+ └── pnpm-lock.yaml # Lockfile (required)
23
+ ```
24
+
25
+ ### Key Principles
26
+
27
+ 1. **`apps/` for deployables**: Next.js sites, APIs, CLIs - things that get deployed
28
+ 2. **`packages/` for libraries**: Shared code consumed by apps or other packages
29
+ 3. **One purpose per package**: Each package should do one thing well
30
+ 4. **No nested packages**: Don't put packages inside packages
31
+
32
+ ## Package Types
33
+
34
+ ### Application Packages (`apps/`)
35
+
36
+ - **Deployable**: These are the "endpoints" of your package graph
37
+ - **Not installed by other packages**: Apps shouldn't be dependencies of other packages
38
+ - **No shared code**: If code needs sharing, extract to `packages/`
39
+
40
+ ```json
41
+ // apps/web/package.json
42
+ {
43
+ "name": "web",
44
+ "private": true,
45
+ "dependencies": {
46
+ "@repo/ui": "workspace:*",
47
+ "next": "latest"
48
+ }
49
+ }
50
+ ```
51
+
52
+ ### Library Packages (`packages/`)
53
+
54
+ - **Shared code**: Utilities, components, configs
55
+ - **Namespaced names**: Use `@repo/` or `@yourorg/` prefix
56
+ - **Clear exports**: Define what the package exposes
57
+
58
+ ```json
59
+ // packages/ui/package.json
60
+ {
61
+ "name": "@repo/ui",
62
+ "exports": {
63
+ "./button": "./src/button.tsx",
64
+ "./card": "./src/card.tsx"
65
+ }
66
+ }
67
+ ```
68
+
69
+ ## Package Compilation Strategies
70
+
71
+ ### Just-in-Time (Simplest)
72
+
73
+ Export TypeScript directly; let the app's bundler compile it.
74
+
75
+ ```json
76
+ {
77
+ "name": "@repo/ui",
78
+ "exports": {
79
+ "./button": "./src/button.tsx"
80
+ }
81
+ }
82
+ ```
83
+
84
+ **Pros**: Zero build config, instant changes
85
+ **Cons**: Can't cache builds, requires app bundler support
86
+
87
+ ### Compiled (Recommended for Libraries)
88
+
89
+ Package compiles itself with `tsc` or bundler.
90
+
91
+ ```json
92
+ {
93
+ "name": "@repo/ui",
94
+ "exports": {
95
+ "./button": {
96
+ "types": "./src/button.tsx",
97
+ "default": "./dist/button.js"
98
+ }
99
+ },
100
+ "scripts": {
101
+ "build": "tsc"
102
+ }
103
+ }
104
+ ```
105
+
106
+ **Pros**: Cacheable by Turborepo, works everywhere
107
+ **Cons**: More configuration
108
+
109
+ ## Dependency Management
110
+
111
+ ### Install Where Used
112
+
113
+ Install dependencies in the package that uses them, not the root.
114
+
115
+ ```bash
116
+ # Good: Install in the package that needs it
117
+ pnpm add lodash --filter=@repo/utils
118
+
119
+ # Avoid: Installing everything at root
120
+ pnpm add lodash -w # Only for repo-level tools
121
+ ```
122
+
123
+ ### Root Dependencies
124
+
125
+ Only these belong in root `package.json`:
126
+
127
+ - `turbo` - The build system
128
+ - `husky`, `lint-staged` - Git hooks
129
+ - Repository-level tooling
130
+
131
+ ### Internal Dependencies
132
+
133
+ Use workspace protocol for internal packages:
134
+
135
+ ```json
136
+ // pnpm/bun
137
+ { "@repo/ui": "workspace:*" }
138
+
139
+ // npm/yarn
140
+ { "@repo/ui": "*" }
141
+ ```
142
+
143
+ ## Exports Best Practices
144
+
145
+ ### Use `exports` Field (Not `main`)
146
+
147
+ ```json
148
+ {
149
+ "exports": {
150
+ ".": "./src/index.ts",
151
+ "./button": "./src/button.tsx",
152
+ "./utils": "./src/utils.ts"
153
+ }
154
+ }
155
+ ```
156
+
157
+ ### Avoid Barrel Files
158
+
159
+ Don't create `index.ts` files that re-export everything:
160
+
161
+ ```typescript
162
+ // BAD: packages/ui/src/index.ts
163
+ export * from './button';
164
+ export * from './card';
165
+ export * from './modal';
166
+ // ... imports everything even if you need one thing
167
+
168
+ // GOOD: Direct exports in package.json
169
+ {
170
+ "exports": {
171
+ "./button": "./src/button.tsx",
172
+ "./card": "./src/card.tsx"
173
+ }
174
+ }
175
+ ```
176
+
177
+ ### Namespace Your Packages
178
+
179
+ ```json
180
+ // Good
181
+ { "name": "@repo/ui" }
182
+ { "name": "@acme/utils" }
183
+
184
+ // Avoid (conflicts with npm registry)
185
+ { "name": "ui" }
186
+ { "name": "utils" }
187
+ ```
188
+
189
+ ## Common Anti-Patterns
190
+
191
+ ### Accessing Files Across Package Boundaries
192
+
193
+ ```typescript
194
+ // BAD: Reaching into another package
195
+ import { Button } from "../../packages/ui/src/button";
196
+
197
+ // GOOD: Install and import properly
198
+ import { Button } from "@repo/ui/button";
199
+ ```
200
+
201
+ ### Shared Code in Apps
202
+
203
+ ```
204
+ // BAD
205
+ apps/
206
+ web/
207
+ shared/ # This should be a package!
208
+ utils.ts
209
+
210
+ // GOOD
211
+ packages/
212
+ utils/ # Proper shared package
213
+ src/utils.ts
214
+ ```
215
+
216
+ ### Too Many Root Dependencies
217
+
218
+ ```json
219
+ // BAD: Root has app dependencies
220
+ {
221
+ "dependencies": {
222
+ "react": "^18",
223
+ "next": "^14",
224
+ "lodash": "^4"
225
+ }
226
+ }
227
+
228
+ // GOOD: Root only has repo tools
229
+ {
230
+ "devDependencies": {
231
+ "turbo": "latest",
232
+ "husky": "latest"
233
+ }
234
+ }
235
+ ```
236
+
237
+ ## See Also
238
+
239
+ - [structure.md](./structure.md) - Detailed repository structure patterns
240
+ - [packages.md](./packages.md) - Creating and managing internal packages
241
+ - [dependencies.md](./dependencies.md) - Dependency management strategies
.agents/skills/turborepo/references/best-practices/dependencies.md ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Dependency Management
2
+
3
+ Best practices for managing dependencies in a Turborepo monorepo.
4
+
5
+ ## Core Principle: Install Where Used
6
+
7
+ Dependencies belong in the package that uses them, not the root.
8
+
9
+ ```bash
10
+ # Good: Install in specific package
11
+ pnpm add react --filter=@repo/ui
12
+ pnpm add next --filter=web
13
+
14
+ # Avoid: Installing in root
15
+ pnpm add react -w # Only for repo-level tools!
16
+ ```
17
+
18
+ ## Benefits of Local Installation
19
+
20
+ ### 1. Clarity
21
+
22
+ Each package's `package.json` lists exactly what it needs:
23
+
24
+ ```json
25
+ // packages/ui/package.json
26
+ {
27
+ "dependencies": {
28
+ "react": "^18.0.0",
29
+ "class-variance-authority": "^0.7.0"
30
+ }
31
+ }
32
+ ```
33
+
34
+ ### 2. Flexibility
35
+
36
+ Different packages can use different versions when needed:
37
+
38
+ ```json
39
+ // packages/legacy-ui/package.json
40
+ { "dependencies": { "react": "^17.0.0" } }
41
+
42
+ // packages/ui/package.json
43
+ { "dependencies": { "react": "^18.0.0" } }
44
+ ```
45
+
46
+ ### 3. Better Caching
47
+
48
+ Installing in root changes workspace lockfile, invalidating all caches.
49
+
50
+ ### 4. Pruning Support
51
+
52
+ `turbo prune` can remove unused dependencies for Docker images.
53
+
54
+ ## What Belongs in Root
55
+
56
+ Only repository-level tools:
57
+
58
+ ```json
59
+ // Root package.json
60
+ {
61
+ "devDependencies": {
62
+ "turbo": "latest",
63
+ "husky": "^8.0.0",
64
+ "lint-staged": "^15.0.0"
65
+ }
66
+ }
67
+ ```
68
+
69
+ **NOT** application dependencies:
70
+
71
+ - react, next, express
72
+ - lodash, axios, zod
73
+ - Testing libraries (unless truly repo-wide)
74
+
75
+ ## Installing Dependencies
76
+
77
+ ### Single Package
78
+
79
+ ```bash
80
+ # pnpm
81
+ pnpm add lodash --filter=@repo/utils
82
+
83
+ # npm
84
+ npm install lodash --workspace=@repo/utils
85
+
86
+ # yarn
87
+ yarn workspace @repo/utils add lodash
88
+
89
+ # bun
90
+ cd packages/utils && bun add lodash
91
+ ```
92
+
93
+ ### Multiple Packages
94
+
95
+ ```bash
96
+ # pnpm
97
+ pnpm add jest --save-dev --filter=web --filter=@repo/ui
98
+
99
+ # npm
100
+ npm install jest --save-dev --workspace=web --workspace=@repo/ui
101
+
102
+ # yarn (v2+)
103
+ yarn workspaces foreach -R --from '{web,@repo/ui}' add jest --dev
104
+ ```
105
+
106
+ ### Internal Packages
107
+
108
+ ```bash
109
+ # pnpm
110
+ pnpm add @repo/ui --filter=web
111
+
112
+ # This updates package.json:
113
+ {
114
+ "dependencies": {
115
+ "@repo/ui": "workspace:*"
116
+ }
117
+ }
118
+ ```
119
+
120
+ ## Keeping Versions in Sync
121
+
122
+ ### Option 1: Tooling
123
+
124
+ ```bash
125
+ # syncpack - Check and fix version mismatches
126
+ npx syncpack list-mismatches
127
+ npx syncpack fix-mismatches
128
+
129
+ # manypkg - Similar functionality
130
+ npx @manypkg/cli check
131
+ npx @manypkg/cli fix
132
+
133
+ # sherif - Rust-based, very fast
134
+ npx sherif
135
+ ```
136
+
137
+ ### Option 2: Package Manager Commands
138
+
139
+ ```bash
140
+ # pnpm - Update everywhere
141
+ pnpm up --recursive typescript@latest
142
+
143
+ # npm - Update in all workspaces
144
+ npm install typescript@latest --workspaces
145
+ ```
146
+
147
+ ### Option 3: pnpm Catalogs (pnpm 9.5+)
148
+
149
+ ```yaml
150
+ # pnpm-workspace.yaml
151
+ packages:
152
+ - "apps/*"
153
+ - "packages/*"
154
+
155
+ catalog:
156
+ react: ^18.2.0
157
+ typescript: ^5.3.0
158
+ ```
159
+
160
+ ```json
161
+ // Any package.json
162
+ {
163
+ "dependencies": {
164
+ "react": "catalog:" // Uses version from catalog
165
+ }
166
+ }
167
+ ```
168
+
169
+ ## Internal vs External Dependencies
170
+
171
+ ### Internal (Workspace)
172
+
173
+ ```json
174
+ // pnpm/bun
175
+ { "@repo/ui": "workspace:*" }
176
+
177
+ // npm/yarn
178
+ { "@repo/ui": "*" }
179
+ ```
180
+
181
+ Turborepo understands these relationships and orders builds accordingly.
182
+
183
+ ### External (npm Registry)
184
+
185
+ ```json
186
+ { "lodash": "^4.17.21" }
187
+ ```
188
+
189
+ Standard semver versioning from npm.
190
+
191
+ ## Peer Dependencies
192
+
193
+ For library packages that expect the consumer to provide dependencies:
194
+
195
+ ```json
196
+ // packages/ui/package.json
197
+ {
198
+ "peerDependencies": {
199
+ "react": "^18.0.0",
200
+ "react-dom": "^18.0.0"
201
+ },
202
+ "devDependencies": {
203
+ "react": "^18.0.0", // For development/testing
204
+ "react-dom": "^18.0.0"
205
+ }
206
+ }
207
+ ```
208
+
209
+ ## Common Issues
210
+
211
+ ### "Module not found"
212
+
213
+ 1. Check the dependency is installed in the right package
214
+ 2. Run `pnpm install` / `npm install` to update lockfile
215
+ 3. Check exports are defined in the package
216
+
217
+ ### Version Conflicts
218
+
219
+ Packages can use different versions - this is a feature, not a bug. But if you need consistency:
220
+
221
+ 1. Use tooling (syncpack, manypkg)
222
+ 2. Use pnpm catalogs
223
+ 3. Create a lint rule
224
+
225
+ ### Hoisting Issues
226
+
227
+ Some tools expect dependencies in specific locations. Use package manager config:
228
+
229
+ ```yaml
230
+ # .npmrc (pnpm)
231
+ public-hoist-pattern[]=*eslint*
232
+ public-hoist-pattern[]=*prettier*
233
+ ```
234
+
235
+ ## Lockfile
236
+
237
+ **Required** for:
238
+
239
+ - Reproducible builds
240
+ - Turborepo dependency analysis
241
+ - Cache correctness
242
+
243
+ ```bash
244
+ # Commit your lockfile!
245
+ git add pnpm-lock.yaml # or package-lock.json, yarn.lock
246
+ ```
.agents/skills/turborepo/references/best-practices/packages.md ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Creating Internal Packages
2
+
3
+ How to create and structure internal packages in your monorepo.
4
+
5
+ ## Package Creation Checklist
6
+
7
+ 1. Create directory in `packages/`
8
+ 2. Add `package.json` with name and exports
9
+ 3. Add source code in `src/`
10
+ 4. Add `tsconfig.json` if using TypeScript
11
+ 5. Install as dependency in consuming packages
12
+ 6. Run package manager install to update lockfile
13
+
14
+ ## Package Compilation Strategies
15
+
16
+ ### Just-in-Time (JIT)
17
+
18
+ Export TypeScript directly. The consuming app's bundler compiles it.
19
+
20
+ ```json
21
+ // packages/ui/package.json
22
+ {
23
+ "name": "@repo/ui",
24
+ "exports": {
25
+ "./button": "./src/button.tsx",
26
+ "./card": "./src/card.tsx"
27
+ },
28
+ "scripts": {
29
+ "lint": "eslint .",
30
+ "check-types": "tsc --noEmit"
31
+ }
32
+ }
33
+ ```
34
+
35
+ **When to use:**
36
+
37
+ - Apps use modern bundlers (Turbopack, webpack, Vite)
38
+ - You want minimal configuration
39
+ - Build times are acceptable without caching
40
+
41
+ **Limitations:**
42
+
43
+ - No Turborepo cache for the package itself
44
+ - Consumer must support TypeScript compilation
45
+ - Can't use TypeScript `paths` (use Node.js subpath imports instead)
46
+
47
+ ### Compiled
48
+
49
+ Package handles its own compilation.
50
+
51
+ ```json
52
+ // packages/ui/package.json
53
+ {
54
+ "name": "@repo/ui",
55
+ "exports": {
56
+ "./button": {
57
+ "types": "./src/button.tsx",
58
+ "default": "./dist/button.js"
59
+ }
60
+ },
61
+ "scripts": {
62
+ "build": "tsc",
63
+ "dev": "tsc --watch"
64
+ }
65
+ }
66
+ ```
67
+
68
+ ```json
69
+ // packages/ui/tsconfig.json
70
+ {
71
+ "extends": "@repo/typescript-config/library.json",
72
+ "compilerOptions": {
73
+ "outDir": "dist",
74
+ "rootDir": "src"
75
+ },
76
+ "include": ["src"],
77
+ "exclude": ["node_modules", "dist"]
78
+ }
79
+ ```
80
+
81
+ **When to use:**
82
+
83
+ - You want Turborepo to cache builds
84
+ - Package will be used by non-bundler tools
85
+ - You need maximum compatibility
86
+
87
+ **Remember:** Add `dist/**` to turbo.json outputs!
88
+
89
+ ## Defining Exports
90
+
91
+ ### Multiple Entrypoints
92
+
93
+ ```json
94
+ {
95
+ "exports": {
96
+ ".": "./src/index.ts", // @repo/ui
97
+ "./button": "./src/button.tsx", // @repo/ui/button
98
+ "./card": "./src/card.tsx", // @repo/ui/card
99
+ "./hooks": "./src/hooks/index.ts" // @repo/ui/hooks
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Conditional Exports (Compiled)
105
+
106
+ ```json
107
+ {
108
+ "exports": {
109
+ "./button": {
110
+ "types": "./src/button.tsx",
111
+ "import": "./dist/button.mjs",
112
+ "require": "./dist/button.cjs",
113
+ "default": "./dist/button.js"
114
+ }
115
+ }
116
+ }
117
+ ```
118
+
119
+ ## Installing Internal Packages
120
+
121
+ ### Add to Consuming Package
122
+
123
+ ```json
124
+ // apps/web/package.json
125
+ {
126
+ "dependencies": {
127
+ "@repo/ui": "workspace:*" // pnpm/bun
128
+ // "@repo/ui": "*" // npm/yarn
129
+ }
130
+ }
131
+ ```
132
+
133
+ ### Run Install
134
+
135
+ ```bash
136
+ pnpm install # Updates lockfile with new dependency
137
+ ```
138
+
139
+ ### Import and Use
140
+
141
+ ```typescript
142
+ // apps/web/src/page.tsx
143
+ import { Button } from '@repo/ui/button';
144
+
145
+ export default function Page() {
146
+ return <Button>Click me</Button>;
147
+ }
148
+ ```
149
+
150
+ ## One Purpose Per Package
151
+
152
+ ### Good Examples
153
+
154
+ ```
155
+ packages/
156
+ ├── ui/ # Shared UI components
157
+ ├── utils/ # General utilities
158
+ ├── auth/ # Authentication logic
159
+ ├── database/ # Database client/schemas
160
+ ├── eslint-config/ # ESLint configuration
161
+ ├── typescript-config/ # TypeScript configuration
162
+ └── api-client/ # Generated API client
163
+ ```
164
+
165
+ ### Avoid Mega-Packages
166
+
167
+ ```
168
+ // BAD: One package for everything
169
+ packages/
170
+ └── shared/
171
+ ├── components/
172
+ ├── utils/
173
+ ├── hooks/
174
+ ├── types/
175
+ └── api/
176
+
177
+ // GOOD: Separate by purpose
178
+ packages/
179
+ ├── ui/ # Components
180
+ ├── utils/ # Utilities
181
+ ├── hooks/ # React hooks
182
+ ├── types/ # Shared TypeScript types
183
+ └── api-client/ # API utilities
184
+ ```
185
+
186
+ ## Config Packages
187
+
188
+ ### TypeScript Config
189
+
190
+ ```json
191
+ // packages/typescript-config/package.json
192
+ {
193
+ "name": "@repo/typescript-config",
194
+ "exports": {
195
+ "./base.json": "./base.json",
196
+ "./nextjs.json": "./nextjs.json",
197
+ "./library.json": "./library.json"
198
+ }
199
+ }
200
+ ```
201
+
202
+ ### ESLint Config
203
+
204
+ ```json
205
+ // packages/eslint-config/package.json
206
+ {
207
+ "name": "@repo/eslint-config",
208
+ "exports": {
209
+ "./base": "./base.js",
210
+ "./next": "./next.js"
211
+ },
212
+ "dependencies": {
213
+ "eslint": "^8.0.0",
214
+ "eslint-config-next": "latest"
215
+ }
216
+ }
217
+ ```
218
+
219
+ ## Common Mistakes
220
+
221
+ ### Forgetting to Export
222
+
223
+ ```json
224
+ // BAD: No exports defined
225
+ {
226
+ "name": "@repo/ui"
227
+ }
228
+
229
+ // GOOD: Clear exports
230
+ {
231
+ "name": "@repo/ui",
232
+ "exports": {
233
+ "./button": "./src/button.tsx"
234
+ }
235
+ }
236
+ ```
237
+
238
+ ### Wrong Workspace Syntax
239
+
240
+ ```json
241
+ // pnpm/bun
242
+ { "@repo/ui": "workspace:*" } // Correct
243
+
244
+ // npm/yarn
245
+ { "@repo/ui": "*" } // Correct
246
+ { "@repo/ui": "workspace:*" } // Wrong for npm/yarn!
247
+ ```
248
+
249
+ ### Missing from turbo.json Outputs
250
+
251
+ ```json
252
+ // Package builds to dist/, but turbo.json doesn't know
253
+ {
254
+ "tasks": {
255
+ "build": {
256
+ "outputs": [".next/**"] // Missing dist/**!
257
+ }
258
+ }
259
+ }
260
+
261
+ // Correct
262
+ {
263
+ "tasks": {
264
+ "build": {
265
+ "outputs": [".next/**", "dist/**"]
266
+ }
267
+ }
268
+ }
269
+ ```
270
+
271
+ ## TypeScript Best Practices
272
+
273
+ ### Use Node.js Subpath Imports (Not `paths`)
274
+
275
+ TypeScript `compilerOptions.paths` breaks with JIT packages. Use Node.js subpath imports instead (TypeScript 5.4+).
276
+
277
+ **JIT Package:**
278
+
279
+ ```json
280
+ // packages/ui/package.json
281
+ {
282
+ "imports": {
283
+ "#*": "./src/*"
284
+ }
285
+ }
286
+ ```
287
+
288
+ ```typescript
289
+ // packages/ui/button.tsx
290
+ import { MY_STRING } from "#utils.ts"; // Uses .ts extension
291
+ ```
292
+
293
+ **Compiled Package:**
294
+
295
+ ```json
296
+ // packages/ui/package.json
297
+ {
298
+ "imports": {
299
+ "#*": "./dist/*"
300
+ }
301
+ }
302
+ ```
303
+
304
+ ```typescript
305
+ // packages/ui/button.tsx
306
+ import { MY_STRING } from "#utils.js"; // Uses .js extension
307
+ ```
308
+
309
+ ### Use `tsc` for Internal Packages
310
+
311
+ For internal packages, prefer `tsc` over bundlers. Bundlers can mangle code before it reaches your app's bundler, causing hard-to-debug issues.
312
+
313
+ ### Enable Go-to-Definition
314
+
315
+ For Compiled Packages, enable declaration maps:
316
+
317
+ ```json
318
+ // tsconfig.json
319
+ {
320
+ "compilerOptions": {
321
+ "declaration": true,
322
+ "declarationMap": true
323
+ }
324
+ }
325
+ ```
326
+
327
+ This creates `.d.ts` and `.d.ts.map` files for IDE navigation.
328
+
329
+ ### No Root tsconfig.json Needed
330
+
331
+ Each package should have its own `tsconfig.json`. A root one causes all tasks to miss cache when changed. Only use root `tsconfig.json` for non-package scripts.
332
+
333
+ ### Avoid TypeScript Project References
334
+
335
+ They add complexity and another caching layer. Turborepo handles dependencies better.
.agents/skills/turborepo/references/best-practices/structure.md ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Repository Structure
2
+
3
+ Detailed guidance on structuring a Turborepo monorepo.
4
+
5
+ ## Workspace Configuration
6
+
7
+ ### pnpm (Recommended)
8
+
9
+ ```yaml
10
+ # pnpm-workspace.yaml
11
+ packages:
12
+ - "apps/*"
13
+ - "packages/*"
14
+ ```
15
+
16
+ ### npm/yarn/bun
17
+
18
+ ```json
19
+ // package.json
20
+ {
21
+ "workspaces": ["apps/*", "packages/*"]
22
+ }
23
+ ```
24
+
25
+ ## Root package.json
26
+
27
+ ```json
28
+ {
29
+ "name": "my-monorepo",
30
+ "private": true,
31
+ "packageManager": "pnpm@9.0.0",
32
+ "scripts": {
33
+ "build": "turbo run build",
34
+ "dev": "turbo run dev",
35
+ "lint": "turbo run lint",
36
+ "test": "turbo run test"
37
+ },
38
+ "devDependencies": {
39
+ "turbo": "latest"
40
+ }
41
+ }
42
+ ```
43
+
44
+ Key points:
45
+
46
+ - `private: true` - Prevents accidental publishing
47
+ - `packageManager` - Enforces consistent package manager version
48
+ - **Scripts only delegate to `turbo run`** - No actual build logic here!
49
+ - Minimal devDependencies (just turbo and repo tools)
50
+
51
+ ## Always Prefer Package Tasks
52
+
53
+ **Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
54
+
55
+ ```json
56
+ // packages/web/package.json
57
+ {
58
+ "scripts": {
59
+ "build": "next build",
60
+ "lint": "eslint .",
61
+ "test": "vitest",
62
+ "typecheck": "tsc --noEmit"
63
+ }
64
+ }
65
+
66
+ // packages/api/package.json
67
+ {
68
+ "scripts": {
69
+ "build": "tsc",
70
+ "lint": "eslint .",
71
+ "test": "vitest",
72
+ "typecheck": "tsc --noEmit"
73
+ }
74
+ }
75
+ ```
76
+
77
+ Package tasks enable Turborepo to:
78
+
79
+ 1. **Parallelize** - Run `web#lint` and `api#lint` simultaneously
80
+ 2. **Cache individually** - Each package's task output is cached separately
81
+ 3. **Filter precisely** - Run `turbo run test --filter=web` for just one package
82
+
83
+ **Root Tasks are a fallback** for tasks that truly cannot run per-package:
84
+
85
+ ```json
86
+ // AVOID unless necessary - sequential, not parallelized, can't filter
87
+ {
88
+ "scripts": {
89
+ "lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
90
+ }
91
+ }
92
+ ```
93
+
94
+ ## Root turbo.json
95
+
96
+ ```json
97
+ {
98
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
99
+ "tasks": {
100
+ "build": {
101
+ "dependsOn": ["^build"],
102
+ "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
103
+ },
104
+ "lint": {},
105
+ "test": {
106
+ "dependsOn": ["build"]
107
+ },
108
+ "dev": {
109
+ "cache": false,
110
+ "persistent": true
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ With `futureFlags.globalConfiguration`, global settings move under a `global` key:
117
+
118
+ ```json
119
+ {
120
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
121
+ "futureFlags": { "globalConfiguration": true },
122
+ "global": {
123
+ "inputs": ["tsconfig.json"],
124
+ "env": ["CI"]
125
+ },
126
+ "tasks": {
127
+ "build": {
128
+ "dependsOn": ["^build"],
129
+ "outputs": ["dist/**", ".next/**", "!.next/cache/**"]
130
+ },
131
+ "lint": {},
132
+ "test": {
133
+ "dependsOn": ["build"]
134
+ },
135
+ "dev": {
136
+ "cache": false,
137
+ "persistent": true
138
+ }
139
+ }
140
+ }
141
+ ```
142
+
143
+ ## Directory Organization
144
+
145
+ ### Grouping Packages
146
+
147
+ You can group packages by adding more workspace paths:
148
+
149
+ ```yaml
150
+ # pnpm-workspace.yaml
151
+ packages:
152
+ - "apps/*"
153
+ - "packages/*"
154
+ - "packages/config/*" # Grouped configs
155
+ - "packages/features/*" # Feature packages
156
+ ```
157
+
158
+ This allows:
159
+
160
+ ```
161
+ packages/
162
+ ├── ui/
163
+ ├── utils/
164
+ ├── config/
165
+ │ ├── eslint/
166
+ │ ├── typescript/
167
+ │ └── tailwind/
168
+ └── features/
169
+ ├── auth/
170
+ └── payments/
171
+ ```
172
+
173
+ ### What NOT to Do
174
+
175
+ ```yaml
176
+ # BAD: Nested wildcards cause ambiguous behavior
177
+ packages:
178
+ - "packages/**" # Don't do this!
179
+ ```
180
+
181
+ ## Package Anatomy
182
+
183
+ ### Minimum Required Files
184
+
185
+ ```
186
+ packages/ui/
187
+ ├── package.json # Required: Makes it a package
188
+ ├── src/ # Source code
189
+ │ └── button.tsx
190
+ └── tsconfig.json # TypeScript config (if using TS)
191
+ ```
192
+
193
+ ### package.json Requirements
194
+
195
+ ```json
196
+ {
197
+ "name": "@repo/ui", // Unique, namespaced name
198
+ "version": "0.0.0", // Version (can be 0.0.0 for internal)
199
+ "private": true, // Prevents accidental publishing
200
+ "exports": {
201
+ // Entry points
202
+ "./button": "./src/button.tsx"
203
+ }
204
+ }
205
+ ```
206
+
207
+ ## TypeScript Configuration
208
+
209
+ ### Shared Base Config
210
+
211
+ Create a shared TypeScript config package:
212
+
213
+ ```
214
+ packages/
215
+ └── typescript-config/
216
+ ├── package.json
217
+ ├── base.json
218
+ ├── nextjs.json
219
+ └── library.json
220
+ ```
221
+
222
+ ```json
223
+ // packages/typescript-config/base.json
224
+ {
225
+ "compilerOptions": {
226
+ "strict": true,
227
+ "esModuleInterop": true,
228
+ "skipLibCheck": true,
229
+ "moduleResolution": "bundler",
230
+ "module": "ESNext",
231
+ "target": "ES2022"
232
+ }
233
+ }
234
+ ```
235
+
236
+ ### Extending in Packages
237
+
238
+ ```json
239
+ // packages/ui/tsconfig.json
240
+ {
241
+ "extends": "@repo/typescript-config/library.json",
242
+ "compilerOptions": {
243
+ "outDir": "dist",
244
+ "rootDir": "src"
245
+ },
246
+ "include": ["src"],
247
+ "exclude": ["node_modules", "dist"]
248
+ }
249
+ ```
250
+
251
+ ### No Root tsconfig.json
252
+
253
+ You likely don't need a `tsconfig.json` in the workspace root. Each package should have its own config extending from the shared config package.
254
+
255
+ ## ESLint Configuration
256
+
257
+ ### Shared Config Package
258
+
259
+ ```
260
+ packages/
261
+ └── eslint-config/
262
+ ├── package.json
263
+ ├── base.js
264
+ ├── next.js
265
+ └── library.js
266
+ ```
267
+
268
+ ```json
269
+ // packages/eslint-config/package.json
270
+ {
271
+ "name": "@repo/eslint-config",
272
+ "exports": {
273
+ "./base": "./base.js",
274
+ "./next": "./next.js",
275
+ "./library": "./library.js"
276
+ }
277
+ }
278
+ ```
279
+
280
+ ### Using in Packages
281
+
282
+ ```js
283
+ // apps/web/.eslintrc.js
284
+ module.exports = {
285
+ extends: ["@repo/eslint-config/next"],
286
+ };
287
+ ```
288
+
289
+ ## Lockfile
290
+
291
+ A lockfile is **required** for:
292
+
293
+ - Reproducible builds
294
+ - Turborepo to understand package dependencies
295
+ - Cache correctness
296
+
297
+ Without a lockfile, you'll see unpredictable behavior.
.agents/skills/turborepo/references/boundaries/RULE.md ADDED
@@ -0,0 +1,126 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Boundaries
2
+
3
+ **Experimental feature** - See [RFC](https://github.com/vercel/turborepo/discussions/9435)
4
+
5
+ Full docs: https://turborepo.dev/docs/reference/boundaries
6
+
7
+ Boundaries enforce package isolation by detecting:
8
+
9
+ 1. Imports of files outside the package's directory
10
+ 2. Imports of packages not declared in `package.json` dependencies
11
+
12
+ ## Usage
13
+
14
+ ```bash
15
+ turbo boundaries
16
+ ```
17
+
18
+ Run this to check for workspace violations across your monorepo.
19
+
20
+ ## Tags
21
+
22
+ Tags allow you to create rules for which packages can depend on each other.
23
+
24
+ ### Adding Tags to a Package
25
+
26
+ ```json
27
+ // packages/ui/turbo.json
28
+ {
29
+ "tags": ["internal"]
30
+ }
31
+ ```
32
+
33
+ ### Configuring Tag Rules
34
+
35
+ Rules go in root `turbo.json`:
36
+
37
+ ```json
38
+ // turbo.json
39
+ {
40
+ "boundaries": {
41
+ "tags": {
42
+ "public": {
43
+ "dependencies": {
44
+ "deny": ["internal"]
45
+ }
46
+ }
47
+ }
48
+ }
49
+ }
50
+ ```
51
+
52
+ This prevents `public`-tagged packages from importing `internal`-tagged packages.
53
+
54
+ ### Rule Types
55
+
56
+ **Allow-list approach** (only allow specific tags):
57
+
58
+ ```json
59
+ {
60
+ "boundaries": {
61
+ "tags": {
62
+ "public": {
63
+ "dependencies": {
64
+ "allow": ["public"]
65
+ }
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ **Deny-list approach** (block specific tags):
73
+
74
+ ```json
75
+ {
76
+ "boundaries": {
77
+ "tags": {
78
+ "public": {
79
+ "dependencies": {
80
+ "deny": ["internal"]
81
+ }
82
+ }
83
+ }
84
+ }
85
+ }
86
+ ```
87
+
88
+ **Restrict dependents** (who can import this package):
89
+
90
+ ```json
91
+ {
92
+ "boundaries": {
93
+ "tags": {
94
+ "private": {
95
+ "dependents": {
96
+ "deny": ["public"]
97
+ }
98
+ }
99
+ }
100
+ }
101
+ }
102
+ ```
103
+
104
+ ### Using Package Names
105
+
106
+ Package names work in place of tags:
107
+
108
+ ```json
109
+ {
110
+ "boundaries": {
111
+ "tags": {
112
+ "private": {
113
+ "dependents": {
114
+ "deny": ["@repo/my-pkg"]
115
+ }
116
+ }
117
+ }
118
+ }
119
+ }
120
+ ```
121
+
122
+ ## Key Points
123
+
124
+ - Rules apply transitively (dependencies of dependencies)
125
+ - Helps enforce architectural boundaries at scale
126
+ - Catches violations before runtime/build errors
.agents/skills/turborepo/references/caching/RULE.md ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # How Turborepo Caching Works
2
+
3
+ Turborepo's core principle: **never do the same work twice**.
4
+
5
+ ## The Cache Equation
6
+
7
+ ```
8
+ fingerprint(inputs) → stored outputs
9
+ ```
10
+
11
+ If inputs haven't changed, restore outputs from cache instead of re-running the task.
12
+
13
+ ## What Determines the Cache Key
14
+
15
+ ### Global Hash Inputs
16
+
17
+ These affect ALL tasks in the repo:
18
+
19
+ - `package-lock.json` / `yarn.lock` / `pnpm-lock.yaml`
20
+ - Files listed in `globalDependencies` (or `global.env` when using `globalConfiguration`)
21
+ - Environment variables in `globalEnv` (or `global.env`)
22
+ - `turbo.json` configuration
23
+
24
+ ```json
25
+ {
26
+ "globalDependencies": [".env", "tsconfig.base.json"],
27
+ "globalEnv": ["CI", "NODE_ENV"]
28
+ }
29
+ ```
30
+
31
+ ### Task Hash Inputs
32
+
33
+ These affect specific tasks:
34
+
35
+ - All files in the package (unless filtered by `inputs`)
36
+ - `package.json` contents
37
+ - Environment variables in task's `env` key
38
+ - Task configuration (command, outputs, dependencies)
39
+ - Hashes of dependent tasks (`dependsOn`)
40
+ - Files from `global.inputs` (when using `futureFlags.globalConfiguration` — see below)
41
+
42
+ ```json
43
+ {
44
+ "tasks": {
45
+ "build": {
46
+ "dependsOn": ["^build"],
47
+ "inputs": ["src/**", "package.json", "tsconfig.json"],
48
+ "env": ["API_URL"]
49
+ }
50
+ }
51
+ }
52
+ ```
53
+
54
+ ### How `global.inputs` Changes the Hash Equation
55
+
56
+ When `futureFlags.globalConfiguration` is enabled, `global.inputs` files are **not** part of the global hash. Instead, they are prepended to every task's `inputs` and folded into the **task hash**. This is a fundamental change from `globalDependencies`.
57
+
58
+ **With `globalDependencies` (default):**
59
+
60
+ ```
61
+ task cache key = hash(global hash, task hash)
62
+ ↑ includes globalDependencies file hashes
63
+ ```
64
+
65
+ Changing a `globalDependencies` file invalidates **every** task, regardless of task-level `inputs`. There is no way for a task to opt out.
66
+
67
+ **With `global.inputs` (`futureFlags.globalConfiguration`):**
68
+
69
+ ```
70
+ task cache key = hash(global hash, task hash)
71
+ ↑ includes global.inputs file hashes (merged with task inputs)
72
+ ```
73
+
74
+ `global.inputs` files are merged into each task's input globs. This means:
75
+
76
+ - Tasks can **exclude** specific global files with negation globs: `"inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]`
77
+ - The global hash is smaller (it still includes lockfile, engines, `global.env`, etc. — but not file hashes from `global.inputs`)
78
+ - The task hash correctly includes the global input file hashes alongside the task's own inputs
79
+
80
+ ```json
81
+ {
82
+ "futureFlags": { "globalConfiguration": true },
83
+ "global": {
84
+ "inputs": ["tsconfig.json", ".env"]
85
+ },
86
+ "tasks": {
87
+ "build": {
88
+ "outputs": ["dist/**"]
89
+ },
90
+ "lint": {
91
+ "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]
92
+ }
93
+ }
94
+ }
95
+ ```
96
+
97
+ In this example, changing `tsconfig.json` invalidates `build` (it's in the task's inputs) but **not** `lint` (which explicitly excludes it). With `globalDependencies`, both would have been invalidated.
98
+
99
+ ## What Gets Cached
100
+
101
+ 1. **File outputs** - files/directories specified in `outputs`
102
+ 2. **Task logs** - stdout/stderr for replay on cache hit
103
+
104
+ ```json
105
+ {
106
+ "tasks": {
107
+ "build": {
108
+ "outputs": ["dist/**", ".next/**"]
109
+ }
110
+ }
111
+ }
112
+ ```
113
+
114
+ ## Local Cache Location
115
+
116
+ ```
117
+ .turbo/cache/
118
+ ├── <hash1>.tar.zst # compressed outputs
119
+ ├── <hash2>.tar.zst
120
+ └── ...
121
+ ```
122
+
123
+ Add `.turbo` to `.gitignore`.
124
+
125
+ ## Cache Restoration
126
+
127
+ On cache hit, Turborepo:
128
+
129
+ 1. Extracts archived outputs to their original locations
130
+ 2. Replays the logged stdout/stderr
131
+ 3. Reports the task as cached (shows `FULL TURBO` in output)
132
+
133
+ ## Example Flow
134
+
135
+ ```bash
136
+ # First run - executes build, caches result
137
+ turbo build
138
+ # → packages/ui: cache miss, executing...
139
+ # → packages/web: cache miss, executing...
140
+
141
+ # Second run - same inputs, restores from cache
142
+ turbo build
143
+ # → packages/ui: cache hit, replaying output
144
+ # → packages/web: cache hit, replaying output
145
+ # → FULL TURBO
146
+ ```
147
+
148
+ ## Key Points
149
+
150
+ - Cache is content-addressed (based on input hash, not timestamps)
151
+ - Empty `outputs` array means task runs but nothing is cached
152
+ - Tasks without `outputs` key cache nothing (use `"outputs": []` to be explicit)
153
+ - Cache is invalidated when ANY input changes
.agents/skills/turborepo/references/caching/gotchas.md ADDED
@@ -0,0 +1,190 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Debugging Cache Issues
2
+
3
+ ## Diagnostic Tools
4
+
5
+ ### `--summarize`
6
+
7
+ Generates a JSON file with all hash inputs. Compare two runs to find differences.
8
+
9
+ ```bash
10
+ turbo build --summarize
11
+ # Creates .turbo/runs/<run-id>.json
12
+ ```
13
+
14
+ The summary includes:
15
+
16
+ - Global hash and its inputs
17
+ - Per-task hashes and their inputs
18
+ - Environment variables that affected the hash
19
+
20
+ **Comparing runs:**
21
+
22
+ ```bash
23
+ # Run twice, compare the summaries
24
+ diff .turbo/runs/<first-run>.json .turbo/runs/<second-run>.json
25
+ ```
26
+
27
+ ### `--dry` / `--dry=json`
28
+
29
+ See what would run without executing anything:
30
+
31
+ ```bash
32
+ turbo build --dry
33
+ turbo build --dry=json # machine-readable output
34
+ ```
35
+
36
+ Shows cache status for each task without running them.
37
+
38
+ ### `--force`
39
+
40
+ Skip reading cache, re-execute all tasks:
41
+
42
+ ```bash
43
+ turbo build --force
44
+ ```
45
+
46
+ Useful to verify tasks actually work (not just cached results).
47
+
48
+ ## Unexpected Cache Misses
49
+
50
+ **Symptom:** Task runs when you expected a cache hit.
51
+
52
+ ### Environment Variable Changed
53
+
54
+ Check if an env var in the `env` key changed:
55
+
56
+ ```json
57
+ {
58
+ "tasks": {
59
+ "build": {
60
+ "env": ["API_URL", "NODE_ENV"]
61
+ }
62
+ }
63
+ }
64
+ ```
65
+
66
+ Different `API_URL` between runs = cache miss.
67
+
68
+ ### .env File Changed
69
+
70
+ `.env` files aren't tracked by default. Add to `inputs`:
71
+
72
+ ```json
73
+ {
74
+ "tasks": {
75
+ "build": {
76
+ "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"]
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ Or use `globalDependencies` for repo-wide env files:
83
+
84
+ ```json
85
+ {
86
+ "globalDependencies": [".env"]
87
+ }
88
+ ```
89
+
90
+ With `futureFlags.globalConfiguration`, use `global.inputs` instead. The key difference: `global.inputs` files are folded into each task's hash individually (not the global hash), so tasks can exclude specific files with negation globs.
91
+
92
+ ```json
93
+ {
94
+ "futureFlags": { "globalConfiguration": true },
95
+ "global": {
96
+ "inputs": [".env"]
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Lockfile Changed
102
+
103
+ Installing/updating packages changes the global hash.
104
+
105
+ ### Source Files Changed
106
+
107
+ Any file in the package (or in `inputs`) triggers a miss.
108
+
109
+ ### turbo.json Changed
110
+
111
+ Config changes invalidate the global hash.
112
+
113
+ ## Incorrect Cache Hits
114
+
115
+ **Symptom:** Cached output is stale/wrong.
116
+
117
+ ### Missing Environment Variable
118
+
119
+ Task uses an env var not listed in `env`:
120
+
121
+ ```javascript
122
+ // build.js
123
+ const apiUrl = process.env.API_URL; // not tracked!
124
+ ```
125
+
126
+ Fix: add to task config:
127
+
128
+ ```json
129
+ {
130
+ "tasks": {
131
+ "build": {
132
+ "env": ["API_URL"]
133
+ }
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### Missing File in Inputs
139
+
140
+ Task reads a file outside default inputs:
141
+
142
+ ```json
143
+ {
144
+ "tasks": {
145
+ "build": {
146
+ "inputs": [
147
+ "$TURBO_DEFAULT$",
148
+ "../../shared-config.json" // file outside package
149
+ ]
150
+ }
151
+ }
152
+ }
153
+ ```
154
+
155
+ ## Useful Flags
156
+
157
+ ```bash
158
+ # Only show output for cache misses
159
+ turbo build --output-logs=new-only
160
+
161
+ # Show output for everything (debugging)
162
+ turbo build --output-logs=full
163
+
164
+ # See why tasks are running
165
+ turbo build --verbosity=2
166
+ ```
167
+
168
+ ## Debugging with `globalConfiguration` Enabled
169
+
170
+ When `futureFlags.globalConfiguration` is on, `global.inputs` files appear in per-task hash inputs (not the global hash). If you're getting unexpected cache misses:
171
+
172
+ 1. Check `--summarize` output — global input files will show up in the **task inputs** section, not the global hash section
173
+ 2. Verify tasks aren't accidentally excluding global inputs via negation globs in `inputs`
174
+ 3. Remember that toggling the `globalConfiguration` flag itself invalidates all caches (the flag value is part of the global hash)
175
+
176
+ If you're getting unexpected cache **hits** after changing a global input file, the task may be excluding that file with a negation glob. Check the task's `inputs` for `!$TURBO_ROOT$/...` patterns.
177
+
178
+ ## Quick Checklist
179
+
180
+ Cache miss when expected hit:
181
+
182
+ 1. Run with `--summarize`, compare with previous run
183
+ 2. Check env vars with `--dry=json`
184
+ 3. Look for lockfile/config changes in git
185
+
186
+ Cache hit when expected miss:
187
+
188
+ 1. Verify env var is in `env` array
189
+ 2. Verify file is in `inputs` array
190
+ 3. Check if file is outside package directory
.agents/skills/turborepo/references/caching/remote-cache.md ADDED
@@ -0,0 +1,127 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Remote Caching
2
+
3
+ Share cache artifacts across your team and CI pipelines.
4
+
5
+ ## Benefits
6
+
7
+ - Team members get cache hits from each other's work
8
+ - CI gets cache hits from local development (and vice versa)
9
+ - Dramatically faster CI runs after first build
10
+ - No more "works on my machine" rebuilds
11
+
12
+ ## Vercel Remote Cache
13
+
14
+ Free, zero-config when deploying on Vercel. For local dev and other CI:
15
+
16
+ ### Local Development Setup
17
+
18
+ ```bash
19
+ # Authenticate with Vercel
20
+ npx turbo login
21
+
22
+ # Link repo to your Vercel team
23
+ npx turbo link
24
+ ```
25
+
26
+ This creates `.turbo/config.json` with your team info (gitignored by default).
27
+
28
+ ### CI Setup
29
+
30
+ Set these environment variables:
31
+
32
+ ```bash
33
+ TURBO_TOKEN=<your-token>
34
+ TURBO_TEAM=<your-team-slug>
35
+ ```
36
+
37
+ Get your token from Vercel dashboard → Settings → Tokens.
38
+
39
+ **GitHub Actions example:**
40
+
41
+ ```yaml
42
+ - name: Build
43
+ run: npx turbo build
44
+ env:
45
+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
46
+ TURBO_TEAM: ${{ vars.TURBO_TEAM }}
47
+ ```
48
+
49
+ ## Configuration in turbo.json
50
+
51
+ ```json
52
+ {
53
+ "remoteCache": {
54
+ "enabled": true,
55
+ "signature": false
56
+ }
57
+ }
58
+ ```
59
+
60
+ Options:
61
+
62
+ - `enabled`: toggle remote cache (default: true when authenticated)
63
+ - `signature`: require artifact signing (default: false)
64
+
65
+ ## Artifact Signing
66
+
67
+ Verify cache artifacts haven't been tampered with:
68
+
69
+ ```bash
70
+ # Set a secret key (use same key across all environments)
71
+ export TURBO_REMOTE_CACHE_SIGNATURE_KEY="your-secret-key"
72
+ ```
73
+
74
+ Enable in config:
75
+
76
+ ```json
77
+ {
78
+ "remoteCache": {
79
+ "signature": true
80
+ }
81
+ }
82
+ ```
83
+
84
+ Signed artifacts can only be restored if the signature matches.
85
+
86
+ ## Self-Hosted Options
87
+
88
+ Community implementations for running your own cache server:
89
+
90
+ - **turbo-remote-cache** (Node.js) - supports S3, GCS, Azure
91
+ - **turborepo-remote-cache** (Go) - lightweight, S3-compatible
92
+ - **ducktape** (Rust) - high-performance option
93
+
94
+ Configure with environment variables:
95
+
96
+ ```bash
97
+ TURBO_API=https://your-cache-server.com
98
+ TURBO_TOKEN=your-auth-token
99
+ TURBO_TEAM=your-team
100
+ ```
101
+
102
+ ## Cache Behavior Control
103
+
104
+ ```bash
105
+ # Disable remote cache for a run
106
+ turbo build --remote-cache-read-only # read but don't write
107
+ turbo build --no-cache # skip cache entirely
108
+
109
+ # Environment variable alternative
110
+ TURBO_REMOTE_ONLY=true # only use remote, skip local
111
+ ```
112
+
113
+ ## Debugging Remote Cache
114
+
115
+ ```bash
116
+ # Verbose output shows cache operations
117
+ turbo build --verbosity=2
118
+
119
+ # Check if remote cache is configured
120
+ turbo config
121
+ ```
122
+
123
+ Look for:
124
+
125
+ - "Remote caching enabled" in output
126
+ - Upload/download messages during runs
127
+ - "cache hit, replaying output" with remote cache indicator
.agents/skills/turborepo/references/ci/RULE.md ADDED
@@ -0,0 +1,79 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CI/CD with Turborepo
2
+
3
+ General principles for running Turborepo in continuous integration environments.
4
+
5
+ ## Core Principles
6
+
7
+ ### Always Use `turbo run` in CI
8
+
9
+ **Never use the `turbo <tasks>` shorthand in CI or scripts.** Always use `turbo run`:
10
+
11
+ ```bash
12
+ # CORRECT - Always use in CI, package.json, scripts
13
+ turbo run build test lint
14
+
15
+ # WRONG - Shorthand is only for one-off terminal commands
16
+ turbo build test lint
17
+ ```
18
+
19
+ The shorthand `turbo <tasks>` is only for one-off invocations typed directly in terminal by humans or agents. Anywhere the command is written into code (CI, package.json, scripts), use `turbo run`.
20
+
21
+ ### Enable Remote Caching
22
+
23
+ Remote caching dramatically speeds up CI by sharing cached artifacts across runs.
24
+
25
+ Required environment variables:
26
+
27
+ ```bash
28
+ TURBO_TOKEN=your_vercel_token
29
+ TURBO_TEAM=your_team_slug
30
+ ```
31
+
32
+ ### Use --affected for PR Builds
33
+
34
+ The `--affected` flag only runs tasks for packages changed since the base branch:
35
+
36
+ ```bash
37
+ turbo run build test --affected
38
+ ```
39
+
40
+ This requires Git history to compute what changed.
41
+
42
+ ## Git History Requirements
43
+
44
+ ### Fetch Depth
45
+
46
+ `--affected` needs access to the merge base. Shallow clones break this.
47
+
48
+ ```yaml
49
+ # GitHub Actions
50
+ - uses: actions/checkout@v4
51
+ with:
52
+ fetch-depth: 2 # Minimum for --affected
53
+ # Use 0 for full history if merge base is far
54
+ ```
55
+
56
+ ### Why Shallow Clones Break --affected
57
+
58
+ Turborepo compares the current HEAD to the merge base with `main`. If that commit isn't fetched, `--affected` falls back to running everything.
59
+
60
+ For PRs with many commits, consider:
61
+
62
+ ```yaml
63
+ fetch-depth: 0 # Full history
64
+ ```
65
+
66
+ ## Environment Variables Reference
67
+
68
+ | Variable | Purpose |
69
+ | ------------------- | ------------------------------------ |
70
+ | `TURBO_TOKEN` | Vercel access token for remote cache |
71
+ | `TURBO_TEAM` | Your Vercel team slug |
72
+ | `TURBO_REMOTE_ONLY` | Skip local cache, use remote only |
73
+ | `TURBO_LOG_ORDER` | Set to `grouped` for cleaner CI logs |
74
+
75
+ ## See Also
76
+
77
+ - [github-actions.md](./github-actions.md) - GitHub Actions setup
78
+ - [vercel.md](./vercel.md) - Vercel deployment
79
+ - [patterns.md](./patterns.md) - CI optimization patterns
.agents/skills/turborepo/references/ci/github-actions.md ADDED
@@ -0,0 +1,162 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # GitHub Actions
2
+
3
+ Complete setup guide for Turborepo with GitHub Actions.
4
+
5
+ ## Basic Workflow Structure
6
+
7
+ ```yaml
8
+ name: CI
9
+
10
+ on:
11
+ push:
12
+ branches: [main]
13
+ pull_request:
14
+ branches: [main]
15
+
16
+ jobs:
17
+ build:
18
+ runs-on: ubuntu-latest
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 2
23
+
24
+ - uses: actions/setup-node@v4
25
+ with:
26
+ node-version: 20
27
+
28
+ - name: Install dependencies
29
+ run: npm ci
30
+
31
+ - name: Build and Test
32
+ run: turbo run build test lint
33
+ ```
34
+
35
+ ## Package Manager Setup
36
+
37
+ ### pnpm
38
+
39
+ ```yaml
40
+ - uses: pnpm/action-setup@v3
41
+ with:
42
+ version: 9
43
+
44
+ - uses: actions/setup-node@v4
45
+ with:
46
+ node-version: 20
47
+ cache: "pnpm"
48
+
49
+ - run: pnpm install --frozen-lockfile
50
+ ```
51
+
52
+ ### Yarn
53
+
54
+ ```yaml
55
+ - uses: actions/setup-node@v4
56
+ with:
57
+ node-version: 20
58
+ cache: "yarn"
59
+
60
+ - run: yarn install --frozen-lockfile
61
+ ```
62
+
63
+ ### Bun
64
+
65
+ ```yaml
66
+ - uses: oven-sh/setup-bun@v1
67
+ with:
68
+ bun-version: latest
69
+
70
+ - run: bun install --frozen-lockfile
71
+ ```
72
+
73
+ ## Remote Cache Setup
74
+
75
+ ### 1. Create Vercel Access Token
76
+
77
+ 1. Go to [Vercel Dashboard](https://vercel.com/account/tokens)
78
+ 2. Create a new token with appropriate scope
79
+ 3. Copy the token value
80
+
81
+ ### 2. Add Secrets and Variables
82
+
83
+ In your GitHub repository settings:
84
+
85
+ **Secrets** (Settings > Secrets and variables > Actions > Secrets):
86
+
87
+ - `TURBO_TOKEN`: Your Vercel access token
88
+
89
+ **Variables** (Settings > Secrets and variables > Actions > Variables):
90
+
91
+ - `TURBO_TEAM`: Your Vercel team slug
92
+
93
+ ### 3. Add to Workflow
94
+
95
+ ```yaml
96
+ jobs:
97
+ build:
98
+ runs-on: ubuntu-latest
99
+ env:
100
+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
101
+ TURBO_TEAM: ${{ vars.TURBO_TEAM }}
102
+ ```
103
+
104
+ ## Alternative: actions/cache
105
+
106
+ If you can't use remote cache, cache Turborepo's local cache directory:
107
+
108
+ ```yaml
109
+ - uses: actions/cache@v4
110
+ with:
111
+ path: .turbo
112
+ key: turbo-${{ runner.os }}-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}
113
+ restore-keys: |
114
+ turbo-${{ runner.os }}-
115
+ ```
116
+
117
+ Note: This is less effective than remote cache since it's per-branch.
118
+
119
+ ## Complete Example
120
+
121
+ ```yaml
122
+ name: CI
123
+
124
+ on:
125
+ push:
126
+ branches: [main]
127
+ pull_request:
128
+ branches: [main]
129
+
130
+ jobs:
131
+ build:
132
+ runs-on: ubuntu-latest
133
+ env:
134
+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
135
+ TURBO_TEAM: ${{ vars.TURBO_TEAM }}
136
+
137
+ steps:
138
+ - uses: actions/checkout@v4
139
+ with:
140
+ fetch-depth: 2
141
+
142
+ - uses: pnpm/action-setup@v3
143
+ with:
144
+ version: 9
145
+
146
+ - uses: actions/setup-node@v4
147
+ with:
148
+ node-version: 20
149
+ cache: "pnpm"
150
+
151
+ - name: Install dependencies
152
+ run: pnpm install --frozen-lockfile
153
+
154
+ - name: Build
155
+ run: turbo run build --affected
156
+
157
+ - name: Test
158
+ run: turbo run test --affected
159
+
160
+ - name: Lint
161
+ run: turbo run lint --affected
162
+ ```
.agents/skills/turborepo/references/ci/patterns.md ADDED
@@ -0,0 +1,145 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # CI Optimization Patterns
2
+
3
+ Strategies for efficient CI/CD with Turborepo.
4
+
5
+ ## PR vs Main Branch Builds
6
+
7
+ ### PR Builds: Only Affected
8
+
9
+ Test only what changed in the PR:
10
+
11
+ ```yaml
12
+ - name: Test (PR)
13
+ if: github.event_name == 'pull_request'
14
+ run: turbo run build test --affected
15
+ ```
16
+
17
+ ### Main Branch: Full Build
18
+
19
+ Ensure complete validation on merge:
20
+
21
+ ```yaml
22
+ - name: Test (Main)
23
+ if: github.ref == 'refs/heads/main'
24
+ run: turbo run build test
25
+ ```
26
+
27
+ ## Custom Git Ranges with --filter
28
+
29
+ For advanced scenarios, use `--filter` with git refs:
30
+
31
+ ```bash
32
+ # Changes since specific commit
33
+ turbo run test --filter="...[abc123]"
34
+
35
+ # Changes between refs
36
+ turbo run test --filter="...[main...HEAD]"
37
+
38
+ # Changes in last 3 commits
39
+ turbo run test --filter="...[HEAD~3]"
40
+ ```
41
+
42
+ ## Caching Strategies
43
+
44
+ ### Remote Cache (Recommended)
45
+
46
+ Best performance - shared across all CI runs and developers:
47
+
48
+ ```yaml
49
+ env:
50
+ TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
51
+ TURBO_TEAM: ${{ vars.TURBO_TEAM }}
52
+ ```
53
+
54
+ ### actions/cache Fallback
55
+
56
+ When remote cache isn't available:
57
+
58
+ ```yaml
59
+ - uses: actions/cache@v4
60
+ with:
61
+ path: .turbo
62
+ key: turbo-${{ runner.os }}-${{ github.sha }}
63
+ restore-keys: |
64
+ turbo-${{ runner.os }}-${{ github.ref }}-
65
+ turbo-${{ runner.os }}-
66
+ ```
67
+
68
+ Limitations:
69
+
70
+ - Cache is branch-scoped
71
+ - PRs restore from base branch cache
72
+ - Less efficient than remote cache
73
+
74
+ ## Matrix Builds
75
+
76
+ Test across Node versions:
77
+
78
+ ```yaml
79
+ strategy:
80
+ matrix:
81
+ node: [18, 20, 22]
82
+
83
+ steps:
84
+ - uses: actions/setup-node@v4
85
+ with:
86
+ node-version: ${{ matrix.node }}
87
+
88
+ - run: turbo run test
89
+ ```
90
+
91
+ ## Parallelizing Across Jobs
92
+
93
+ Split tasks into separate jobs:
94
+
95
+ ```yaml
96
+ jobs:
97
+ lint:
98
+ runs-on: ubuntu-latest
99
+ steps:
100
+ - run: turbo run lint --affected
101
+
102
+ test:
103
+ runs-on: ubuntu-latest
104
+ steps:
105
+ - run: turbo run test --affected
106
+
107
+ build:
108
+ runs-on: ubuntu-latest
109
+ needs: [lint, test]
110
+ steps:
111
+ - run: turbo run build
112
+ ```
113
+
114
+ ### Cache Considerations
115
+
116
+ When parallelizing:
117
+
118
+ - Each job has separate cache writes
119
+ - Remote cache handles this automatically
120
+ - With actions/cache, use unique keys per job to avoid conflicts
121
+
122
+ ```yaml
123
+ - uses: actions/cache@v4
124
+ with:
125
+ path: .turbo
126
+ key: turbo-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
127
+ ```
128
+
129
+ ## Conditional Tasks
130
+
131
+ Skip expensive tasks on draft PRs:
132
+
133
+ ```yaml
134
+ - name: E2E Tests
135
+ if: github.event.pull_request.draft == false
136
+ run: turbo run test:e2e --affected
137
+ ```
138
+
139
+ Or require label for full test:
140
+
141
+ ```yaml
142
+ - name: Full Test Suite
143
+ if: contains(github.event.pull_request.labels.*.name, 'full-test')
144
+ run: turbo run test
145
+ ```
.agents/skills/turborepo/references/ci/vercel.md ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Vercel Deployment
2
+
3
+ Turborepo integrates seamlessly with Vercel for monorepo deployments.
4
+
5
+ ## Remote Cache
6
+
7
+ Remote caching is **automatically enabled** when deploying to Vercel. No configuration needed - Vercel detects Turborepo and enables caching.
8
+
9
+ This means:
10
+
11
+ - No `TURBO_TOKEN` or `TURBO_TEAM` setup required on Vercel
12
+ - Cache is shared across all deployments
13
+ - Preview and production builds benefit from cache
14
+
15
+ ## turbo-ignore
16
+
17
+ Skip unnecessary builds when a package hasn't changed using `turbo-ignore`.
18
+
19
+ ### Installation
20
+
21
+ ```bash
22
+ npx turbo-ignore
23
+ ```
24
+
25
+ Or install globally in your project:
26
+
27
+ ```bash
28
+ pnpm add -D turbo-ignore
29
+ ```
30
+
31
+ ### Setup in Vercel
32
+
33
+ 1. Go to your project in Vercel Dashboard
34
+ 2. Navigate to Settings > Git > Ignored Build Step
35
+ 3. Select "Custom" and enter:
36
+
37
+ ```bash
38
+ npx turbo-ignore
39
+ ```
40
+
41
+ ### How It Works
42
+
43
+ `turbo-ignore` checks if the current package (or its dependencies) changed since the last successful deployment:
44
+
45
+ 1. Compares current commit to last deployed commit
46
+ 2. Uses Turborepo's dependency graph
47
+ 3. Returns exit code 0 (skip) if no changes
48
+ 4. Returns exit code 1 (build) if changes detected
49
+
50
+ ### Options
51
+
52
+ ```bash
53
+ # Check specific package
54
+ npx turbo-ignore web
55
+
56
+ # Use specific comparison ref
57
+ npx turbo-ignore --fallback=HEAD~1
58
+
59
+ # Verbose output
60
+ npx turbo-ignore --verbose
61
+ ```
62
+
63
+ ## Environment Variables
64
+
65
+ Set environment variables in Vercel Dashboard:
66
+
67
+ 1. Go to Project Settings > Environment Variables
68
+ 2. Add variables for each environment (Production, Preview, Development)
69
+
70
+ Common variables:
71
+
72
+ - `DATABASE_URL`
73
+ - `API_KEY`
74
+ - Package-specific config
75
+
76
+ ## Monorepo Root Directory
77
+
78
+ For monorepos, set the root directory in Vercel:
79
+
80
+ 1. Project Settings > General > Root Directory
81
+ 2. Set to the package path (e.g., `apps/web`)
82
+
83
+ Vercel automatically:
84
+
85
+ - Installs dependencies from monorepo root
86
+ - Runs build from the package directory
87
+ - Detects framework settings
88
+
89
+ ## Build Command
90
+
91
+ Vercel auto-detects `turbo run build` when `turbo.json` exists at root.
92
+
93
+ Override if needed:
94
+
95
+ ```bash
96
+ turbo run build --filter=web
97
+ ```
98
+
99
+ Or for production-only optimizations:
100
+
101
+ ```bash
102
+ turbo run build --filter=web --env-mode=strict
103
+ ```
.agents/skills/turborepo/references/cli/RULE.md ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # turbo run
2
+
3
+ The primary command for executing tasks across your monorepo.
4
+
5
+ ## Basic Usage
6
+
7
+ ```bash
8
+ # Full form (use in CI, package.json, scripts)
9
+ turbo run <tasks>
10
+
11
+ # Shorthand (only for one-off terminal invocations)
12
+ turbo <tasks>
13
+ ```
14
+
15
+ ## When to Use `turbo run` vs `turbo`
16
+
17
+ **Always use `turbo run` when the command is written into code:**
18
+
19
+ - `package.json` scripts
20
+ - CI/CD workflows (GitHub Actions, etc.)
21
+ - Shell scripts
22
+ - Documentation
23
+ - Any static/committed configuration
24
+
25
+ **Only use `turbo` (shorthand) for:**
26
+
27
+ - One-off commands typed directly in terminal
28
+ - Ad-hoc invocations by humans or agents
29
+
30
+ ```json
31
+ // package.json - ALWAYS use "turbo run"
32
+ {
33
+ "scripts": {
34
+ "build": "turbo run build",
35
+ "dev": "turbo run dev",
36
+ "lint": "turbo run lint",
37
+ "test": "turbo run test"
38
+ }
39
+ }
40
+ ```
41
+
42
+ ```yaml
43
+ # CI workflow - ALWAYS use "turbo run"
44
+ - run: turbo run build --affected
45
+ - run: turbo run test --affected
46
+ ```
47
+
48
+ ```bash
49
+ # Terminal one-off - shorthand OK
50
+ turbo build --filter=web
51
+ ```
52
+
53
+ ## Running Tasks
54
+
55
+ Tasks must be defined in `turbo.json` before running.
56
+
57
+ ```bash
58
+ # Single task
59
+ turbo build
60
+
61
+ # Multiple tasks
62
+ turbo run build lint test
63
+
64
+ # See available tasks (run without arguments)
65
+ turbo run
66
+ ```
67
+
68
+ ## Passing Arguments to Scripts
69
+
70
+ Use `--` to pass arguments through to the underlying package scripts:
71
+
72
+ ```bash
73
+ turbo run build -- --sourcemap
74
+ turbo test -- --watch
75
+ turbo lint -- --fix
76
+ ```
77
+
78
+ Everything after `--` goes directly to the task's script.
79
+
80
+ ## Package Selection
81
+
82
+ By default, turbo runs tasks in all packages. Use `--filter` to narrow scope:
83
+
84
+ ```bash
85
+ turbo build --filter=web
86
+ turbo test --filter=./apps/*
87
+ ```
88
+
89
+ See `filtering/` for complete filter syntax.
90
+
91
+ ## Quick Reference
92
+
93
+ | Goal | Command |
94
+ | ------------------- | -------------------------- |
95
+ | Build everything | `turbo build` |
96
+ | Build one package | `turbo build --filter=web` |
97
+ | Multiple tasks | `turbo build lint test` |
98
+ | Pass args to script | `turbo build -- --arg` |
99
+ | Preview run | `turbo build --dry` |
100
+ | Force rebuild | `turbo build --force` |
.agents/skills/turborepo/references/cli/commands.md ADDED
@@ -0,0 +1,297 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # turbo run Flags Reference
2
+
3
+ Full docs: https://turborepo.dev/docs/reference/run
4
+
5
+ ## Package Selection
6
+
7
+ ### `--filter` / `-F`
8
+
9
+ Select specific packages to run tasks in.
10
+
11
+ ```bash
12
+ turbo build --filter=web
13
+ turbo build -F=@repo/ui -F=@repo/utils
14
+ turbo test --filter=./apps/*
15
+ ```
16
+
17
+ See `filtering/` for complete syntax (globs, dependencies, git ranges).
18
+
19
+ ### Task Identifier Syntax (v2.2.4+)
20
+
21
+ Run specific package tasks directly:
22
+
23
+ ```bash
24
+ turbo run web#build # Build web package
25
+ turbo run web#build docs#lint # Multiple specific tasks
26
+ ```
27
+
28
+ ### `--affected`
29
+
30
+ Run only in packages changed since the base branch.
31
+
32
+ ```bash
33
+ turbo build --affected
34
+ turbo test --affected --filter=./apps/* # combine with filter
35
+ ```
36
+
37
+ **How it works:**
38
+
39
+ - Default: compares `main...HEAD`
40
+ - In GitHub Actions: auto-detects `GITHUB_BASE_REF`
41
+ - Override base: `TURBO_SCM_BASE=development turbo build --affected`
42
+ - Override head: `TURBO_SCM_HEAD=your-branch turbo build --affected`
43
+
44
+ **Requires git history** - shallow clones may fall back to running all tasks.
45
+
46
+ ## Execution Control
47
+
48
+ ### `--dry` / `--dry=json`
49
+
50
+ Preview what would run without executing.
51
+
52
+ ```bash
53
+ turbo build --dry # human-readable
54
+ turbo build --dry=json # machine-readable
55
+ ```
56
+
57
+ ### `--force`
58
+
59
+ Ignore all cached artifacts, re-run everything.
60
+
61
+ ```bash
62
+ turbo build --force
63
+ ```
64
+
65
+ ### `--concurrency`
66
+
67
+ Limit parallel task execution.
68
+
69
+ ```bash
70
+ turbo build --concurrency=4 # max 4 tasks
71
+ turbo build --concurrency=50% # 50% of CPU cores
72
+ ```
73
+
74
+ ### `--continue`
75
+
76
+ Keep running other tasks when one fails.
77
+
78
+ ```bash
79
+ turbo build test --continue
80
+ ```
81
+
82
+ ### `--only`
83
+
84
+ Run only the specified task, skip its dependencies.
85
+
86
+ ```bash
87
+ turbo build --only # skip running dependsOn tasks
88
+ ```
89
+
90
+ ### `--parallel` (Discouraged)
91
+
92
+ Ignores task graph dependencies, runs all tasks simultaneously. **Avoid using this flag**—if tasks need to run in parallel, configure `dependsOn` correctly instead. Using `--parallel` bypasses Turborepo's dependency graph, which can cause race conditions and incorrect builds.
93
+
94
+ ## Cache Control
95
+
96
+ ### `--cache`
97
+
98
+ Fine-grained cache behavior control.
99
+
100
+ ```bash
101
+ # Default: read/write both local and remote
102
+ turbo build --cache=local:rw,remote:rw
103
+
104
+ # Read-only local, no remote
105
+ turbo build --cache=local:r,remote:
106
+
107
+ # Disable local, read-only remote
108
+ turbo build --cache=local:,remote:r
109
+
110
+ # Disable all caching
111
+ turbo build --cache=local:,remote:
112
+ ```
113
+
114
+ ## Output & Debugging
115
+
116
+ ### `--graph`
117
+
118
+ Generate task graph visualization.
119
+
120
+ ```bash
121
+ turbo build --graph # opens in browser
122
+ turbo build --graph=graph.svg # SVG file
123
+ turbo build --graph=graph.png # PNG file
124
+ turbo build --graph=graph.json # JSON data
125
+ turbo build --graph=graph.mermaid # Mermaid diagram
126
+ ```
127
+
128
+ ### `--summarize`
129
+
130
+ Generate JSON run summary for debugging.
131
+
132
+ ```bash
133
+ turbo build --summarize
134
+ # creates .turbo/runs/<run-id>.json
135
+ ```
136
+
137
+ ### `--output-logs`
138
+
139
+ Control log output verbosity.
140
+
141
+ ```bash
142
+ turbo build --output-logs=full # all logs (default)
143
+ turbo build --output-logs=new-only # only cache misses
144
+ turbo build --output-logs=errors-only # only failures
145
+ turbo build --output-logs=none # silent
146
+ ```
147
+
148
+ ### `--profile`
149
+
150
+ Generate Chrome tracing profile for performance analysis.
151
+
152
+ ```bash
153
+ turbo build --profile=profile.json
154
+ # open chrome://tracing and load the file
155
+ ```
156
+
157
+ ### `--verbosity` / `-v`
158
+
159
+ Control turbo's own log level.
160
+
161
+ ```bash
162
+ turbo build -v # verbose
163
+ turbo build -vv # more verbose
164
+ turbo build -vvv # maximum verbosity
165
+ ```
166
+
167
+ ## Environment
168
+
169
+ ### `--env-mode`
170
+
171
+ Control environment variable handling.
172
+
173
+ ```bash
174
+ turbo build --env-mode=strict # only declared env vars (default)
175
+ turbo build --env-mode=loose # include all env vars in hash
176
+ ```
177
+
178
+ ## UI
179
+
180
+ ### `--ui`
181
+
182
+ Select output interface.
183
+
184
+ ```bash
185
+ turbo build --ui=tui # interactive terminal UI (default in TTY)
186
+ turbo build --ui=stream # streaming logs (default in CI)
187
+ ```
188
+
189
+ ---
190
+
191
+ # turbo-ignore
192
+
193
+ Full docs: https://turborepo.dev/docs/reference/turbo-ignore
194
+
195
+ Skip CI work when nothing relevant changed. Useful for skipping container setup.
196
+
197
+ ## Basic Usage
198
+
199
+ ```bash
200
+ # Check if build is needed for current package (uses Automatic Package Scoping)
201
+ npx turbo-ignore
202
+
203
+ # Check specific package
204
+ npx turbo-ignore web
205
+
206
+ # Check specific task
207
+ npx turbo-ignore --task=test
208
+ ```
209
+
210
+ ## Exit Codes
211
+
212
+ - `0`: No changes detected - skip CI work
213
+ - `1`: Changes detected - proceed with CI
214
+
215
+ ## CI Integration Example
216
+
217
+ ```yaml
218
+ # GitHub Actions
219
+ - name: Check for changes
220
+ id: turbo-ignore
221
+ run: npx turbo-ignore web
222
+ continue-on-error: true
223
+
224
+ - name: Build
225
+ if: steps.turbo-ignore.outcome == 'failure' # changes detected
226
+ run: pnpm build
227
+ ```
228
+
229
+ ## Comparison Depth
230
+
231
+ Default: compares to parent commit (`HEAD^1`).
232
+
233
+ ```bash
234
+ # Compare to specific commit
235
+ npx turbo-ignore --fallback=abc123
236
+
237
+ # Compare to branch
238
+ npx turbo-ignore --fallback=main
239
+ ```
240
+
241
+ ---
242
+
243
+ # Other Commands
244
+
245
+ ## turbo boundaries
246
+
247
+ Check workspace violations (experimental).
248
+
249
+ ```bash
250
+ turbo boundaries
251
+ ```
252
+
253
+ See `references/boundaries/` for configuration.
254
+
255
+ ## turbo watch
256
+
257
+ Re-run tasks on file changes.
258
+
259
+ ```bash
260
+ turbo watch build test
261
+ ```
262
+
263
+ See `references/watch/` for details.
264
+
265
+ ## turbo prune
266
+
267
+ Create sparse checkout for Docker.
268
+
269
+ ```bash
270
+ turbo prune web --docker
271
+ ```
272
+
273
+ ## turbo link / unlink
274
+
275
+ Connect/disconnect Remote Cache.
276
+
277
+ ```bash
278
+ turbo link # connect to Vercel Remote Cache
279
+ turbo unlink # disconnect
280
+ ```
281
+
282
+ ## turbo login / logout
283
+
284
+ Authenticate with Remote Cache provider.
285
+
286
+ ```bash
287
+ turbo login # authenticate
288
+ turbo logout # log out
289
+ ```
290
+
291
+ ## turbo generate
292
+
293
+ Scaffold new packages.
294
+
295
+ ```bash
296
+ turbo generate
297
+ ```
.agents/skills/turborepo/references/configuration/RULE.md ADDED
@@ -0,0 +1,235 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # turbo.json Configuration Overview
2
+
3
+ Configuration reference for Turborepo. Full docs: https://turborepo.dev/docs/reference/configuration
4
+
5
+ ## File Location
6
+
7
+ Root `turbo.json` lives at repo root, sibling to root `package.json`:
8
+
9
+ ```
10
+ my-monorepo/
11
+ ├── turbo.json # Root configuration
12
+ ├── package.json
13
+ └── packages/
14
+ └── web/
15
+ ├── turbo.json # Package Configuration (optional)
16
+ └── package.json
17
+ ```
18
+
19
+ ## Always Prefer Package Tasks Over Root Tasks
20
+
21
+ **Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
22
+
23
+ Package tasks enable parallelization, individual caching, and filtering. Define scripts in each package's `package.json`:
24
+
25
+ ```json
26
+ // packages/web/package.json
27
+ {
28
+ "scripts": {
29
+ "build": "next build",
30
+ "lint": "eslint .",
31
+ "test": "vitest",
32
+ "typecheck": "tsc --noEmit"
33
+ }
34
+ }
35
+
36
+ // packages/api/package.json
37
+ {
38
+ "scripts": {
39
+ "build": "tsc",
40
+ "lint": "eslint .",
41
+ "test": "vitest",
42
+ "typecheck": "tsc --noEmit"
43
+ }
44
+ }
45
+ ```
46
+
47
+ ```json
48
+ // Root package.json - delegates to turbo
49
+ {
50
+ "scripts": {
51
+ "build": "turbo run build",
52
+ "lint": "turbo run lint",
53
+ "test": "turbo run test",
54
+ "typecheck": "turbo run typecheck"
55
+ }
56
+ }
57
+ ```
58
+
59
+ When you run `turbo run lint`, Turborepo finds all packages with a `lint` script and runs them **in parallel**.
60
+
61
+ **Root Tasks are a fallback**, not the default. Only use them for tasks that truly cannot run per-package (e.g., repo-level CI scripts, workspace-wide config generation).
62
+
63
+ ```json
64
+ // AVOID: Task logic in root defeats parallelization
65
+ {
66
+ "scripts": {
67
+ "lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
68
+ }
69
+ }
70
+ ```
71
+
72
+ ## Basic Structure
73
+
74
+ ```json
75
+ {
76
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
77
+ "globalEnv": ["CI"],
78
+ "globalDependencies": ["tsconfig.json"],
79
+ "tasks": {
80
+ "build": {
81
+ "dependsOn": ["^build"],
82
+ "outputs": ["dist/**"]
83
+ },
84
+ "dev": {
85
+ "cache": false,
86
+ "persistent": true
87
+ }
88
+ }
89
+ }
90
+ ```
91
+
92
+ The `$schema` key enables IDE autocompletion and validation.
93
+
94
+ ### With `futureFlags.globalConfiguration`
95
+
96
+ When the `globalConfiguration` future flag is enabled, global options move under a `global` key with cleaner names:
97
+
98
+ ```json
99
+ {
100
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
101
+ "futureFlags": { "globalConfiguration": true },
102
+ "global": {
103
+ "inputs": ["tsconfig.json"],
104
+ "env": ["CI"],
105
+ "ui": "tui"
106
+ },
107
+ "tasks": {
108
+ "build": {
109
+ "dependsOn": ["^build"],
110
+ "outputs": ["dist/**"]
111
+ }
112
+ }
113
+ }
114
+ ```
115
+
116
+ See the [global options reference](./global-options.md) for the full rename mapping and behavior changes.
117
+
118
+ ## Configuration Sections
119
+
120
+ **Global options** - Settings affecting all tasks:
121
+
122
+ - Without flag: `globalEnv`, `globalDependencies`, `globalPassThroughEnv`, `cacheDir`, `daemon`, `envMode`, `ui`, `remoteCache`
123
+ - With `globalConfiguration` flag: all of the above move under the `global` key (see [global options](./global-options.md))
124
+
125
+ **Task definitions** - Per-task settings in `tasks` object:
126
+
127
+ - `dependsOn`, `outputs`, `inputs`, `env`
128
+ - `cache`, `persistent`, `interactive`, `outputLogs`
129
+
130
+ ## Package Configurations
131
+
132
+ Use `turbo.json` in individual packages to override root settings:
133
+
134
+ ```json
135
+ // packages/web/turbo.json
136
+ {
137
+ "extends": ["//"],
138
+ "tasks": {
139
+ "build": {
140
+ "outputs": [".next/**", "!.next/cache/**"]
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ The `"extends": ["//"]` is required - it references the root configuration.
147
+
148
+ **When to use Package Configurations:**
149
+
150
+ - Framework-specific outputs (Next.js, Vite, etc.)
151
+ - Package-specific env vars
152
+ - Different caching rules for specific packages
153
+ - Keeping framework config close to the framework code
154
+
155
+ ### Extending from Other Packages
156
+
157
+ You can extend from config packages instead of just root:
158
+
159
+ ```json
160
+ // packages/web/turbo.json
161
+ {
162
+ "extends": ["//", "@repo/turbo-config"]
163
+ }
164
+ ```
165
+
166
+ ### Adding to Inherited Arrays with `$TURBO_EXTENDS$`
167
+
168
+ By default, array fields in Package Configurations **replace** root values. Use `$TURBO_EXTENDS$` to **append** instead:
169
+
170
+ ```json
171
+ // Root turbo.json
172
+ {
173
+ "tasks": {
174
+ "build": {
175
+ "outputs": ["dist/**"]
176
+ }
177
+ }
178
+ }
179
+ ```
180
+
181
+ ```json
182
+ // packages/web/turbo.json
183
+ {
184
+ "extends": ["//"],
185
+ "tasks": {
186
+ "build": {
187
+ // Inherits "dist/**" from root, adds ".next/**"
188
+ "outputs": ["$TURBO_EXTENDS$", ".next/**", "!.next/cache/**"]
189
+ }
190
+ }
191
+ }
192
+ ```
193
+
194
+ Without `$TURBO_EXTENDS$`, outputs would only be `[".next/**", "!.next/cache/**"]`.
195
+
196
+ **Works with:**
197
+
198
+ - `dependsOn`
199
+ - `env`
200
+ - `inputs`
201
+ - `outputs`
202
+ - `passThroughEnv`
203
+ - `with`
204
+
205
+ ### Excluding Tasks from Packages
206
+
207
+ Use `extends: false` to exclude a task from a package:
208
+
209
+ ```json
210
+ // packages/ui/turbo.json
211
+ {
212
+ "extends": ["//"],
213
+ "tasks": {
214
+ "e2e": {
215
+ "extends": false // UI package doesn't have e2e tests
216
+ }
217
+ }
218
+ }
219
+ ```
220
+
221
+ ## `turbo.jsonc` for Comments
222
+
223
+ Use `turbo.jsonc` extension to add comments with IDE support:
224
+
225
+ ```jsonc
226
+ // turbo.jsonc
227
+ {
228
+ "tasks": {
229
+ "build": {
230
+ // Next.js outputs
231
+ "outputs": [".next/**", "!.next/cache/**"],
232
+ },
233
+ },
234
+ }
235
+ ```
.agents/skills/turborepo/references/configuration/global-options.md ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Global Options Reference
2
+
3
+ Options that affect all tasks. Full docs: https://turborepo.dev/docs/reference/configuration
4
+
5
+ ## globalEnv
6
+
7
+ Environment variables affecting all task hashes.
8
+
9
+ ```json
10
+ {
11
+ "globalEnv": ["CI", "NODE_ENV", "VERCEL_*"]
12
+ }
13
+ ```
14
+
15
+ Use for variables that should invalidate all caches when changed.
16
+
17
+ ## globalDependencies
18
+
19
+ Files that affect all task hashes.
20
+
21
+ ```json
22
+ {
23
+ "globalDependencies": ["tsconfig.json", ".env", "pnpm-lock.yaml"]
24
+ }
25
+ ```
26
+
27
+ Lockfile is included by default. Add shared configs here.
28
+
29
+ ## globalPassThroughEnv
30
+
31
+ Variables available to tasks but not included in hash.
32
+
33
+ ```json
34
+ {
35
+ "globalPassThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
36
+ }
37
+ ```
38
+
39
+ Use for credentials that shouldn't affect cache keys.
40
+
41
+ ## cacheDir
42
+
43
+ Custom cache location. Default: `node_modules/.cache/turbo`.
44
+
45
+ ```json
46
+ {
47
+ "cacheDir": ".turbo/cache"
48
+ }
49
+ ```
50
+
51
+ ## daemon
52
+
53
+ **Deprecated**: The daemon is no longer used for `turbo run` and this option will be removed in version 3.0. The daemon is still used by `turbo watch` and the Turborepo LSP.
54
+
55
+ ## envMode
56
+
57
+ How unspecified env vars are handled. Default: `"strict"`.
58
+
59
+ ```json
60
+ {
61
+ "envMode": "strict" // Only specified vars available
62
+ // or
63
+ "envMode": "loose" // All vars pass through
64
+ }
65
+ ```
66
+
67
+ Strict mode catches missing env declarations.
68
+
69
+ ## ui
70
+
71
+ Terminal UI mode. Default: `"stream"`.
72
+
73
+ ```json
74
+ {
75
+ "ui": "tui" // Interactive terminal UI
76
+ // or
77
+ "ui": "stream" // Traditional streaming logs
78
+ }
79
+ ```
80
+
81
+ TUI provides better UX for parallel tasks.
82
+
83
+ ## remoteCache
84
+
85
+ Configure remote caching.
86
+
87
+ ```json
88
+ {
89
+ "remoteCache": {
90
+ "enabled": true,
91
+ "signature": true,
92
+ "timeout": 30,
93
+ "uploadTimeout": 60
94
+ }
95
+ }
96
+ ```
97
+
98
+ | Option | Default | Description |
99
+ | --------------- | ---------------------- | ------------------------------------------------------ |
100
+ | `enabled` | `true` | Enable/disable remote caching |
101
+ | `signature` | `false` | Sign artifacts with `TURBO_REMOTE_CACHE_SIGNATURE_KEY` |
102
+ | `preflight` | `false` | Send OPTIONS request before cache requests |
103
+ | `timeout` | `30` | Timeout in seconds for cache operations |
104
+ | `uploadTimeout` | `60` | Timeout in seconds for uploads |
105
+ | `apiUrl` | `"https://vercel.com"` | Remote cache API endpoint |
106
+ | `loginUrl` | `"https://vercel.com"` | Login endpoint |
107
+ | `teamId` | - | Team ID (must start with `team_`) |
108
+ | `teamSlug` | - | Team slug for querystring |
109
+
110
+ See https://turborepo.dev/docs/core-concepts/remote-caching for setup.
111
+
112
+ ## concurrency
113
+
114
+ Default: `"10"`
115
+
116
+ Limit parallel task execution.
117
+
118
+ ```json
119
+ {
120
+ "concurrency": "4" // Max 4 tasks at once
121
+ // or
122
+ "concurrency": "50%" // 50% of available CPUs
123
+ }
124
+ ```
125
+
126
+ ## futureFlags
127
+
128
+ Enable experimental features that will become default in future versions.
129
+
130
+ ```json
131
+ {
132
+ "futureFlags": {
133
+ "errorsOnlyShowHash": true
134
+ }
135
+ }
136
+ ```
137
+
138
+ ### `errorsOnlyShowHash`
139
+
140
+ When using `outputLogs: "errors-only"`, show task hashes on start/completion:
141
+
142
+ - Cache miss: `cache miss, executing <hash> (only logging errors)`
143
+ - Cache hit: `cache hit, replaying logs (no errors) <hash>`
144
+
145
+ ### `longerSignatureKey`
146
+
147
+ Enforce a minimum key length of 32 bytes for `TURBO_REMOTE_CACHE_SIGNATURE_KEY` when `remoteCache.signature` is enabled. Short keys weaken HMAC-SHA256 signatures. Fails the run immediately if the key is too short.
148
+
149
+ ### `globalConfiguration`
150
+
151
+ Moves global configuration keys under a top-level `global` key for clarity and changes how `global.inputs` (formerly `globalDependencies`) affects task hashing.
152
+
153
+ When enabled:
154
+
155
+ - Global config keys move under `global` with cleaner names
156
+ - `global.inputs` files are **prepended to every task's inputs** instead of being folded into the global hash — tasks can opt out of specific global inputs using negation globs
157
+
158
+ ```json
159
+ {
160
+ "futureFlags": { "globalConfiguration": true },
161
+ "global": {
162
+ "inputs": ["tsconfig.json", ".env"],
163
+ "env": ["CI", "NODE_ENV"],
164
+ "passThroughEnv": ["AWS_SECRET_KEY"],
165
+ "ui": "tui",
166
+ "envMode": "strict",
167
+ "cacheDir": ".turbo/cache",
168
+ "remoteCache": { "enabled": true },
169
+ "concurrency": "50%"
170
+ },
171
+ "tasks": {
172
+ "build": {
173
+ "dependsOn": ["^build"],
174
+ "outputs": ["dist/**"]
175
+ }
176
+ }
177
+ }
178
+ ```
179
+
180
+ **Key rename mapping:**
181
+
182
+ | Old (top-level) | New (`global.`) |
183
+ | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------- |
184
+ | `globalDependencies` | `inputs` |
185
+ | `globalEnv` | `env` |
186
+ | `globalPassThroughEnv` | `passThroughEnv` |
187
+ | `ui`, `envMode`, `cacheDir`, `daemon`, `concurrency`, `noUpdateNotifier`, `dangerouslyDisablePackageManagerCheck`, `remoteCache` | Same names under `global` |
188
+
189
+ **Behavior change for `global.inputs`:**
190
+
191
+ With `globalDependencies` (old): files are hashed into the **global hash**, which is embedded in every task's cache key. Changing any of these files invalidates all tasks — there is no opt-out.
192
+
193
+ With `global.inputs` (new): files are treated as **implicit task inputs** prepended to each task's `inputs` globs. This means:
194
+
195
+ - Tasks can exclude specific global files: `"inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/tsconfig.json"]`
196
+ - The global hash no longer includes these file hashes (it still includes lockfile, engines, global env, etc.)
197
+ - Tasks with no explicit `inputs` still hash all package files plus the global inputs
198
+
199
+ See the [gotchas doc](./gotchas.md) for guidance on using `$TURBO_DEFAULT$` with `global.inputs`.
200
+
201
+ ## noUpdateNotifier
202
+
203
+ Disable update notifications when new turbo versions are available.
204
+
205
+ ```json
206
+ {
207
+ "noUpdateNotifier": true
208
+ }
209
+ ```
210
+
211
+ ## dangerouslyDisablePackageManagerCheck
212
+
213
+ Bypass the `packageManager` field requirement. Use for incremental migration.
214
+
215
+ ```json
216
+ {
217
+ "dangerouslyDisablePackageManagerCheck": true
218
+ }
219
+ ```
220
+
221
+ **Warning**: Unstable lockfiles can cause unpredictable behavior.
222
+
223
+ ## Git Worktree Cache Sharing
224
+
225
+ When working in Git worktrees, Turborepo automatically shares local cache between the main worktree and linked worktrees.
226
+
227
+ **How it works:**
228
+
229
+ - Detects worktree configuration
230
+ - Redirects cache to main worktree's `.turbo/cache`
231
+ - Works alongside Remote Cache
232
+
233
+ **Benefits:**
234
+
235
+ - Cache hits across branches
236
+ - Reduced disk usage
237
+ - Faster branch switching
238
+
239
+ **Disabled by**: Setting explicit `cacheDir` in turbo.json.
.agents/skills/turborepo/references/configuration/gotchas.md ADDED
@@ -0,0 +1,368 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Configuration Gotchas
2
+
3
+ Common mistakes and how to fix them.
4
+
5
+ ## #1 Root Scripts Not Using `turbo run`
6
+
7
+ Root `package.json` scripts for turbo tasks MUST use `turbo run`, not direct commands.
8
+
9
+ ```json
10
+ // WRONG - bypasses turbo, no parallelization or caching
11
+ {
12
+ "scripts": {
13
+ "build": "bun build",
14
+ "dev": "bun dev"
15
+ }
16
+ }
17
+
18
+ // CORRECT - delegates to turbo
19
+ {
20
+ "scripts": {
21
+ "build": "turbo run build",
22
+ "dev": "turbo run dev"
23
+ }
24
+ }
25
+ ```
26
+
27
+ **Why this matters:** Running `bun build` or `npm run build` at root bypasses Turborepo entirely - no parallelization, no caching, no dependency graph awareness.
28
+
29
+ ## #2 Using `&&` to Chain Turbo Tasks
30
+
31
+ Don't use `&&` to chain tasks that turbo should orchestrate.
32
+
33
+ ```json
34
+ // WRONG - changeset:publish chains turbo task with non-turbo command
35
+ {
36
+ "scripts": {
37
+ "changeset:publish": "bun build && changeset publish"
38
+ }
39
+ }
40
+
41
+ // CORRECT - use turbo run, let turbo handle dependencies
42
+ {
43
+ "scripts": {
44
+ "changeset:publish": "turbo run build && changeset publish"
45
+ }
46
+ }
47
+ ```
48
+
49
+ If the second command (`changeset publish`) depends on build outputs, the turbo task should run through turbo to get caching and parallelization benefits.
50
+
51
+ ## #3 Overly Broad globalDependencies
52
+
53
+ `globalDependencies` affects hash for ALL tasks in ALL packages. Be specific.
54
+
55
+ ```json
56
+ // WRONG - affects all hashes
57
+ {
58
+ "globalDependencies": ["**/.env.*local"]
59
+ }
60
+
61
+ // CORRECT - move to specific tasks that need it
62
+ {
63
+ "globalDependencies": [".env"],
64
+ "tasks": {
65
+ "build": {
66
+ "inputs": ["$TURBO_DEFAULT$", ".env*"],
67
+ "outputs": ["dist/**"]
68
+ }
69
+ }
70
+ }
71
+ ```
72
+
73
+ **Why this matters:** `**/.env.*local` matches .env files in ALL packages, causing unnecessary cache invalidation. Instead:
74
+
75
+ - Use `globalDependencies` only for truly global files (root `.env`)
76
+ - Use task-level `inputs` for package-specific .env files with `$TURBO_DEFAULT$` to preserve default behavior
77
+
78
+ With `futureFlags.globalConfiguration`, this is less of a concern because `global.inputs` acts as implicit task inputs — tasks can opt out of specific files with negation globs. But keeping the list focused is still good practice.
79
+
80
+ ## #4 Repetitive Task Configuration
81
+
82
+ Look for repeated configuration across tasks that can be collapsed.
83
+
84
+ ```json
85
+ // WRONG - repetitive env and inputs across tasks
86
+ {
87
+ "tasks": {
88
+ "build": {
89
+ "env": ["API_URL", "DATABASE_URL"],
90
+ "inputs": ["$TURBO_DEFAULT$", ".env*"]
91
+ },
92
+ "test": {
93
+ "env": ["API_URL", "DATABASE_URL"],
94
+ "inputs": ["$TURBO_DEFAULT$", ".env*"]
95
+ }
96
+ }
97
+ }
98
+
99
+ // BETTER - use globalEnv and globalDependencies
100
+ {
101
+ "globalEnv": ["API_URL", "DATABASE_URL"],
102
+ "globalDependencies": [".env*"],
103
+ "tasks": {
104
+ "build": {},
105
+ "test": {}
106
+ }
107
+ }
108
+ ```
109
+
110
+ **When to use global vs task-level:**
111
+
112
+ - `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
113
+ - Task-level `env` / `inputs` - use when only specific tasks need it
114
+
115
+ ## #5 Using `../` to Traverse Out of Package in `inputs`
116
+
117
+ Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
118
+
119
+ ```json
120
+ // WRONG - traversing out of package
121
+ {
122
+ "tasks": {
123
+ "build": {
124
+ "inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
125
+ }
126
+ }
127
+ }
128
+
129
+ // CORRECT - use $TURBO_ROOT$ for repo root
130
+ {
131
+ "tasks": {
132
+ "build": {
133
+ "inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
134
+ }
135
+ }
136
+ }
137
+ ```
138
+
139
+ ## #6 MOST COMMON MISTAKE: Creating Root Tasks
140
+
141
+ **DO NOT create Root Tasks. ALWAYS create package tasks.**
142
+
143
+ When you need to create a task (build, lint, test, typecheck, etc.):
144
+
145
+ 1. Add the script to **each relevant package's** `package.json`
146
+ 2. Register the task in root `turbo.json`
147
+ 3. Root `package.json` only contains `turbo run <task>`
148
+
149
+ ```json
150
+ // WRONG - DO NOT DO THIS
151
+ // Root package.json with task logic
152
+ {
153
+ "scripts": {
154
+ "build": "cd apps/web && next build && cd ../api && tsc",
155
+ "lint": "eslint apps/ packages/",
156
+ "test": "vitest"
157
+ }
158
+ }
159
+
160
+ // CORRECT - DO THIS
161
+ // apps/web/package.json
162
+ { "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
163
+
164
+ // apps/api/package.json
165
+ { "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
166
+
167
+ // packages/ui/package.json
168
+ { "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
169
+
170
+ // Root package.json - ONLY delegates
171
+ { "scripts": { "build": "turbo run build", "lint": "turbo run lint", "test": "turbo run test" } }
172
+
173
+ // turbo.json - register tasks
174
+ {
175
+ "tasks": {
176
+ "build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
177
+ "lint": {},
178
+ "test": {}
179
+ }
180
+ }
181
+ ```
182
+
183
+ **Why this matters:**
184
+
185
+ - Package tasks run in **parallel** across all packages
186
+ - Each package's output is cached **individually**
187
+ - You can **filter** to specific packages: `turbo run test --filter=web`
188
+
189
+ Root Tasks (`//#taskname`) defeat all these benefits. Only use them for tasks that truly cannot exist in any package (extremely rare).
190
+
191
+ ## #7 Tasks That Need Parallel Execution + Cache Invalidation
192
+
193
+ Some tasks can run in parallel (don't need built output from dependencies) but must still invalidate cache when dependency source code changes. Using `dependsOn: ["^taskname"]` forces sequential execution. Using no dependencies breaks cache invalidation.
194
+
195
+ **Use Transit Nodes for these tasks:**
196
+
197
+ ```json
198
+ // WRONG - forces sequential execution (SLOW)
199
+ "my-task": {
200
+ "dependsOn": ["^my-task"]
201
+ }
202
+
203
+ // ALSO WRONG - no dependency awareness (INCORRECT CACHING)
204
+ "my-task": {}
205
+
206
+ // CORRECT - use Transit Nodes for parallel + correct caching
207
+ {
208
+ "tasks": {
209
+ "transit": { "dependsOn": ["^transit"] },
210
+ "my-task": { "dependsOn": ["transit"] }
211
+ }
212
+ }
213
+ ```
214
+
215
+ **Why Transit Nodes work:**
216
+
217
+ - `transit` creates dependency relationships without matching any actual script
218
+ - Tasks that depend on `transit` gain dependency awareness
219
+ - Since `transit` completes instantly (no script), tasks run in parallel
220
+ - Cache correctly invalidates when dependency source code changes
221
+
222
+ **How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
223
+
224
+ ## Missing outputs for File-Producing Tasks
225
+
226
+ **Before flagging missing `outputs`, check what the task actually produces:**
227
+
228
+ 1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
229
+ 2. Determine if it writes files to disk or only outputs to stdout
230
+ 3. Only flag if the task produces files that should be cached
231
+
232
+ ```json
233
+ // WRONG - build produces files but they're not cached
234
+ "build": {
235
+ "dependsOn": ["^build"]
236
+ }
237
+
238
+ // CORRECT - outputs are cached
239
+ "build": {
240
+ "dependsOn": ["^build"],
241
+ "outputs": ["dist/**"]
242
+ }
243
+ ```
244
+
245
+ No `outputs` key is fine for stdout-only tasks. For file-producing tasks, missing `outputs` means Turbo has nothing to cache.
246
+
247
+ ## Forgetting ^ in dependsOn
248
+
249
+ ```json
250
+ // WRONG - looks for "build" in SAME package (infinite loop or missing)
251
+ "build": {
252
+ "dependsOn": ["build"]
253
+ }
254
+
255
+ // CORRECT - runs dependencies' build first
256
+ "build": {
257
+ "dependsOn": ["^build"]
258
+ }
259
+ ```
260
+
261
+ The `^` means "in dependency packages", not "in this package".
262
+
263
+ ## Missing persistent on Dev Tasks
264
+
265
+ ```json
266
+ // WRONG - dependent tasks hang waiting for dev to "finish"
267
+ "dev": {
268
+ "cache": false
269
+ }
270
+
271
+ // CORRECT
272
+ "dev": {
273
+ "cache": false,
274
+ "persistent": true
275
+ }
276
+ ```
277
+
278
+ ## Package Config Missing extends
279
+
280
+ ```json
281
+ // WRONG - packages/web/turbo.json
282
+ {
283
+ "tasks": {
284
+ "build": { "outputs": [".next/**"] }
285
+ }
286
+ }
287
+
288
+ // CORRECT
289
+ {
290
+ "extends": ["//"],
291
+ "tasks": {
292
+ "build": { "outputs": [".next/**"] }
293
+ }
294
+ }
295
+ ```
296
+
297
+ Without `"extends": ["//"]`, Package Configurations are invalid.
298
+
299
+ ## Root Tasks Need Special Syntax
300
+
301
+ To run a task defined only in root `package.json`:
302
+
303
+ ```bash
304
+ # WRONG
305
+ turbo run format
306
+
307
+ # CORRECT
308
+ turbo run //#format
309
+ ```
310
+
311
+ And in dependsOn:
312
+
313
+ ```json
314
+ "build": {
315
+ "dependsOn": ["//#codegen"] // Root package's codegen
316
+ }
317
+ ```
318
+
319
+ ## Overwriting Default Inputs
320
+
321
+ ```json
322
+ // WRONG - only watches test files, ignores source changes
323
+ "test": {
324
+ "inputs": ["tests/**"]
325
+ }
326
+
327
+ // CORRECT - extends defaults, adds test files
328
+ "test": {
329
+ "inputs": ["$TURBO_DEFAULT$", "tests/**"]
330
+ }
331
+ ```
332
+
333
+ Without `$TURBO_DEFAULT$`, you replace all default file watching.
334
+
335
+ ## Excluding `global.inputs` Without `$TURBO_DEFAULT$`
336
+
337
+ When using `futureFlags.globalConfiguration`, `global.inputs` values are prepended to every task's inputs. If you want to exclude a global input from a specific task, you **must** include `$TURBO_DEFAULT$` to preserve default file hashing.
338
+
339
+ ```json
340
+ // WRONG - task hashes NO files at all (global input cancelled, no defaults)
341
+ "build": {
342
+ "inputs": ["!$TURBO_ROOT$/config.txt"]
343
+ }
344
+
345
+ // CORRECT - task hashes all package files, minus config.txt
346
+ "build": {
347
+ "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/config.txt"]
348
+ }
349
+ ```
350
+
351
+ Without `$TURBO_DEFAULT$`, the only inclusion glob comes from `global.inputs`, which the negation cancels out. The task ends up with no inclusions and no default file hashing, so it hashes nothing. Changes to source files won't cause cache misses.
352
+
353
+ ## Caching Tasks with Side Effects
354
+
355
+ ```json
356
+ // WRONG - deploy might be skipped on cache hit
357
+ "deploy": {
358
+ "dependsOn": ["build"]
359
+ }
360
+
361
+ // CORRECT
362
+ "deploy": {
363
+ "dependsOn": ["build"],
364
+ "cache": false
365
+ }
366
+ ```
367
+
368
+ Always disable cache for deploy, publish, or mutation tasks.
.agents/skills/turborepo/references/configuration/tasks.md ADDED
@@ -0,0 +1,321 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Task Configuration Reference
2
+
3
+ Full docs: https://turborepo.dev/docs/reference/configuration#tasks
4
+
5
+ ## dependsOn
6
+
7
+ Controls task execution order.
8
+
9
+ ```json
10
+ {
11
+ "tasks": {
12
+ "build": {
13
+ "dependsOn": [
14
+ "^build", // Dependencies' build tasks first
15
+ "codegen", // Same package's codegen task first
16
+ "shared#build" // Specific package's build task
17
+ ]
18
+ }
19
+ }
20
+ }
21
+ ```
22
+
23
+ | Syntax | Meaning |
24
+ | ---------- | ------------------------------------ |
25
+ | `^task` | Run `task` in all dependencies first |
26
+ | `task` | Run `task` in same package first |
27
+ | `pkg#task` | Run specific package's task first |
28
+
29
+ The `^` prefix is crucial - without it, you're referencing the same package.
30
+
31
+ ### Transit Nodes for Parallel Tasks
32
+
33
+ For tasks like `lint` and `check-types` that can run in parallel but need dependency-aware caching:
34
+
35
+ ```json
36
+ {
37
+ "tasks": {
38
+ "transit": { "dependsOn": ["^transit"] },
39
+ "lint": { "dependsOn": ["transit"] },
40
+ "check-types": { "dependsOn": ["transit"] }
41
+ }
42
+ }
43
+ ```
44
+
45
+ **DO NOT use `dependsOn: ["^lint"]`** - this forces sequential execution.
46
+ **DO NOT use `dependsOn: []`** - this breaks cache invalidation.
47
+
48
+ The `transit` task creates dependency relationships without running anything (no matching script), so tasks run in parallel with correct caching.
49
+
50
+ ## outputs
51
+
52
+ Glob patterns for files to cache. **If omitted, nothing is cached.**
53
+
54
+ ```json
55
+ {
56
+ "tasks": {
57
+ "build": {
58
+ "outputs": ["dist/**", "build/**"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ **Framework examples:**
65
+
66
+ ```json
67
+ // Next.js
68
+ "outputs": [".next/**", "!.next/cache/**"]
69
+
70
+ // Vite
71
+ "outputs": ["dist/**"]
72
+
73
+ // TypeScript (tsc)
74
+ "outputs": ["dist/**", "*.tsbuildinfo"]
75
+
76
+ // No file outputs (lint, typecheck)
77
+ "outputs": []
78
+ ```
79
+
80
+ Use `!` prefix to exclude patterns from caching.
81
+
82
+ ## inputs
83
+
84
+ Files considered when calculating task hash. Defaults to all tracked files in package.
85
+
86
+ ```json
87
+ {
88
+ "tasks": {
89
+ "test": {
90
+ "inputs": ["src/**", "tests/**", "vitest.config.ts"]
91
+ }
92
+ }
93
+ }
94
+ ```
95
+
96
+ **Special values:**
97
+
98
+ | Value | Meaning |
99
+ | --------------------- | --------------------------------------- |
100
+ | `$TURBO_DEFAULT$` | Include default inputs, then add/remove |
101
+ | `$TURBO_ROOT$/<path>` | Reference files from repo root |
102
+
103
+ ```json
104
+ {
105
+ "tasks": {
106
+ "build": {
107
+ "inputs": ["$TURBO_DEFAULT$", "!README.md", "$TURBO_ROOT$/tsconfig.base.json"]
108
+ }
109
+ }
110
+ }
111
+ ```
112
+
113
+ ### Interaction with `global.inputs`
114
+
115
+ When `futureFlags.globalConfiguration` is enabled, files listed in `global.inputs` are prepended to every task's `inputs`. The combined list is then used to compute the task hash.
116
+
117
+ This is different from `globalDependencies`, where files were hashed into the **global** hash and could not be influenced by task-level `inputs`.
118
+
119
+ **With `globalDependencies` (old behavior):**
120
+
121
+ - `globalDependencies` files contribute to the global hash
122
+ - Task `inputs` only control which **package** files are hashed
123
+ - There is no way for a task to "opt out" of a `globalDependencies` file
124
+
125
+ **With `global.inputs` (new behavior):**
126
+
127
+ - `global.inputs` files are merged into each task's `inputs` globs
128
+ - Task `inputs` and `global.inputs` are combined, then the full list is hashed into the **task** hash
129
+ - Tasks can exclude specific global files with negation globs
130
+
131
+ ```json
132
+ {
133
+ "futureFlags": { "globalConfiguration": true },
134
+ "global": {
135
+ "inputs": ["tsconfig.json", ".env"]
136
+ },
137
+ "tasks": {
138
+ "build": {},
139
+ "lint": {
140
+ "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/.env"]
141
+ }
142
+ }
143
+ }
144
+ ```
145
+
146
+ In this example:
147
+
148
+ - `build` hashes all package files + `tsconfig.json` + `.env` (from `global.inputs`)
149
+ - `lint` hashes all package files + `tsconfig.json`, but **excludes** `.env` because of the negation glob
150
+
151
+ Tasks with no explicit `inputs` key still hash all package files (the default behavior) plus the `global.inputs` files.
152
+
153
+ ## env
154
+
155
+ Environment variables to include in task hash.
156
+
157
+ ```json
158
+ {
159
+ "tasks": {
160
+ "build": {
161
+ "env": [
162
+ "API_URL",
163
+ "NEXT_PUBLIC_*", // Wildcard matching
164
+ "!DEBUG" // Exclude from hash
165
+ ]
166
+ }
167
+ }
168
+ }
169
+ ```
170
+
171
+ Variables listed here affect cache hits - changing the value invalidates cache.
172
+
173
+ ## cache
174
+
175
+ Enable/disable caching for a task. Default: `true`.
176
+
177
+ ```json
178
+ {
179
+ "tasks": {
180
+ "dev": { "cache": false },
181
+ "deploy": { "cache": false }
182
+ }
183
+ }
184
+ ```
185
+
186
+ Disable for: dev servers, deploy commands, tasks with side effects.
187
+
188
+ ## persistent
189
+
190
+ Mark long-running tasks that don't exit. Default: `false`.
191
+
192
+ ```json
193
+ {
194
+ "tasks": {
195
+ "dev": {
196
+ "cache": false,
197
+ "persistent": true
198
+ }
199
+ }
200
+ }
201
+ ```
202
+
203
+ Required for dev servers - without it, dependent tasks wait forever.
204
+
205
+ ## interactive
206
+
207
+ Allow task to receive stdin input. Default: `false`.
208
+
209
+ ```json
210
+ {
211
+ "tasks": {
212
+ "login": {
213
+ "cache": false,
214
+ "interactive": true
215
+ }
216
+ }
217
+ }
218
+ ```
219
+
220
+ ## outputLogs
221
+
222
+ Control when logs are shown. Options: `full`, `hash-only`, `new-only`, `errors-only`, `none`.
223
+
224
+ ```json
225
+ {
226
+ "tasks": {
227
+ "build": {
228
+ "outputLogs": "new-only" // Only show logs on cache miss
229
+ }
230
+ }
231
+ }
232
+ ```
233
+
234
+ ## with
235
+
236
+ Run tasks alongside this task. For long-running tasks that need runtime dependencies.
237
+
238
+ ```json
239
+ {
240
+ "tasks": {
241
+ "dev": {
242
+ "with": ["api#dev"],
243
+ "persistent": true,
244
+ "cache": false
245
+ }
246
+ }
247
+ }
248
+ ```
249
+
250
+ Unlike `dependsOn`, `with` runs tasks concurrently (not sequentially). Use for dev servers that need other services running.
251
+
252
+ ## interruptible
253
+
254
+ Allow `turbo watch` to restart the task on changes. Default: `false`.
255
+
256
+ ```json
257
+ {
258
+ "tasks": {
259
+ "dev": {
260
+ "persistent": true,
261
+ "interruptible": true,
262
+ "cache": false
263
+ }
264
+ }
265
+ }
266
+ ```
267
+
268
+ Use for dev servers that don't automatically detect dependency changes.
269
+
270
+ ## description
271
+
272
+ Human-readable description of the task.
273
+
274
+ ```json
275
+ {
276
+ "tasks": {
277
+ "build": {
278
+ "description": "Compiles the application for production deployment"
279
+ }
280
+ }
281
+ }
282
+ ```
283
+
284
+ For documentation only - doesn't affect execution or caching.
285
+
286
+ ## passThroughEnv
287
+
288
+ Environment variables available at runtime but NOT included in cache hash.
289
+
290
+ ```json
291
+ {
292
+ "tasks": {
293
+ "build": {
294
+ "passThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
295
+ }
296
+ }
297
+ }
298
+ ```
299
+
300
+ **Warning**: Changes to these vars won't cause cache misses. Use `env` if changes should invalidate cache.
301
+
302
+ ## extends (Package Configuration only)
303
+
304
+ Control task inheritance in Package Configurations.
305
+
306
+ ```json
307
+ // packages/ui/turbo.json
308
+ {
309
+ "extends": ["//"],
310
+ "tasks": {
311
+ "lint": {
312
+ "extends": false // Exclude from this package
313
+ }
314
+ }
315
+ }
316
+ ```
317
+
318
+ | Value | Behavior |
319
+ | ---------------- | -------------------------------------------------------------- |
320
+ | `true` (default) | Inherit from root turbo.json |
321
+ | `false` | Exclude task from package, or define fresh without inheritance |
.agents/skills/turborepo/references/environment/RULE.md ADDED
@@ -0,0 +1,123 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment Variables in Turborepo
2
+
3
+ Turborepo provides fine-grained control over which environment variables affect task hashing and runtime availability.
4
+
5
+ ## Configuration Keys
6
+
7
+ ### `env` - Task-Specific Variables
8
+
9
+ Variables that affect a specific task's hash. When these change, only that task rebuilds.
10
+
11
+ ```json
12
+ {
13
+ "tasks": {
14
+ "build": {
15
+ "env": ["DATABASE_URL", "API_KEY"]
16
+ }
17
+ }
18
+ }
19
+ ```
20
+
21
+ ### `globalEnv` - Variables Affecting All Tasks
22
+
23
+ Variables that affect EVERY task's hash. When these change, all tasks rebuild.
24
+
25
+ ```json
26
+ {
27
+ "globalEnv": ["CI", "NODE_ENV"]
28
+ }
29
+ ```
30
+
31
+ ### `passThroughEnv` - Runtime-Only Variables (Not Hashed)
32
+
33
+ Variables available at runtime but NOT included in hash. **Use with caution** - changes won't trigger rebuilds.
34
+
35
+ ```json
36
+ {
37
+ "tasks": {
38
+ "deploy": {
39
+ "passThroughEnv": ["AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"]
40
+ }
41
+ }
42
+ }
43
+ ```
44
+
45
+ ### `globalPassThroughEnv` - Global Runtime Variables
46
+
47
+ Same as `passThroughEnv` but for all tasks.
48
+
49
+ ```json
50
+ {
51
+ "globalPassThroughEnv": ["GITHUB_TOKEN"]
52
+ }
53
+ ```
54
+
55
+ ## Wildcards and Negation
56
+
57
+ ### Wildcards
58
+
59
+ Match multiple variables with `*`:
60
+
61
+ ```json
62
+ {
63
+ "env": ["MY_API_*", "FEATURE_FLAG_*"]
64
+ }
65
+ ```
66
+
67
+ This matches `MY_API_URL`, `MY_API_KEY`, `FEATURE_FLAG_DARK_MODE`, etc.
68
+
69
+ ### Negation
70
+
71
+ Exclude variables (useful with framework inference):
72
+
73
+ ```json
74
+ {
75
+ "env": ["!NEXT_PUBLIC_ANALYTICS_ID"]
76
+ }
77
+ ```
78
+
79
+ ## With `futureFlags.globalConfiguration`
80
+
81
+ When the `globalConfiguration` future flag is enabled, global environment keys move under the `global` key with cleaner names:
82
+
83
+ | Old (top-level) | New (`global.`) |
84
+ | ---------------------- | ---------------- |
85
+ | `globalEnv` | `env` |
86
+ | `globalPassThroughEnv` | `passThroughEnv` |
87
+
88
+ `global.env` and `global.passThroughEnv` behave identically to their top-level counterparts — they affect the global hash and all tasks, respectively. The rename is purely organizational.
89
+
90
+ ```json
91
+ {
92
+ "futureFlags": { "globalConfiguration": true },
93
+ "global": {
94
+ "env": ["CI", "NODE_ENV"],
95
+ "passThroughEnv": ["GITHUB_TOKEN", "NPM_TOKEN"]
96
+ },
97
+ "tasks": {
98
+ "build": {
99
+ "env": ["DATABASE_URL", "API_*"],
100
+ "passThroughEnv": ["SENTRY_AUTH_TOKEN"]
101
+ }
102
+ }
103
+ }
104
+ ```
105
+
106
+ ## Complete Example
107
+
108
+ ```json
109
+ {
110
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
111
+ "globalEnv": ["CI", "NODE_ENV"],
112
+ "globalPassThroughEnv": ["GITHUB_TOKEN", "NPM_TOKEN"],
113
+ "tasks": {
114
+ "build": {
115
+ "env": ["DATABASE_URL", "API_*"],
116
+ "passThroughEnv": ["SENTRY_AUTH_TOKEN"]
117
+ },
118
+ "test": {
119
+ "env": ["TEST_DATABASE_URL"]
120
+ }
121
+ }
122
+ }
123
+ ```
.agents/skills/turborepo/references/environment/gotchas.md ADDED
@@ -0,0 +1,175 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment Variable Gotchas
2
+
3
+ Common mistakes and how to fix them.
4
+
5
+ ## .env Files Must Be in `inputs`
6
+
7
+ Turbo does NOT read `.env` files. Your framework (Next.js, Vite, etc.) or `dotenv` loads them. But Turbo needs to know when they change.
8
+
9
+ **Wrong:**
10
+
11
+ ```json
12
+ {
13
+ "tasks": {
14
+ "build": {
15
+ "env": ["DATABASE_URL"]
16
+ }
17
+ }
18
+ }
19
+ ```
20
+
21
+ **Right:**
22
+
23
+ ```json
24
+ {
25
+ "tasks": {
26
+ "build": {
27
+ "env": ["DATABASE_URL"],
28
+ "inputs": ["$TURBO_DEFAULT$", ".env", ".env.local", ".env.production"]
29
+ }
30
+ }
31
+ }
32
+ ```
33
+
34
+ ## Strict Mode Filters CI Variables
35
+
36
+ In strict mode, CI provider variables (GITHUB_TOKEN, GITLAB_CI, etc.) are filtered unless explicitly listed.
37
+
38
+ **Symptom:** Task fails with "authentication required" or "permission denied" in CI.
39
+
40
+ **Solution:**
41
+
42
+ ```json
43
+ {
44
+ "globalPassThroughEnv": ["GITHUB_TOKEN", "GITLAB_CI", "CI"]
45
+ }
46
+ ```
47
+
48
+ ## passThroughEnv Doesn't Affect Hash
49
+
50
+ Variables in `passThroughEnv` are available at runtime but changes WON'T trigger rebuilds.
51
+
52
+ **Dangerous example:**
53
+
54
+ ```json
55
+ {
56
+ "tasks": {
57
+ "build": {
58
+ "passThroughEnv": ["API_URL"]
59
+ }
60
+ }
61
+ }
62
+ ```
63
+
64
+ If `API_URL` changes from staging to production, Turbo may serve a cached build pointing to the wrong API.
65
+
66
+ **Use passThroughEnv only for:**
67
+
68
+ - Auth tokens that don't affect output (SENTRY_AUTH_TOKEN)
69
+ - CI metadata (GITHUB_RUN_ID)
70
+ - Variables consumed after build (deploy credentials)
71
+
72
+ ## Runtime-Created Variables Are Invisible
73
+
74
+ Turbo captures env vars at startup. Variables created during execution aren't seen.
75
+
76
+ **Won't work:**
77
+
78
+ ```bash
79
+ # In package.json scripts
80
+ "build": "export API_URL=$COMPUTED_VALUE && next build"
81
+ ```
82
+
83
+ **Solution:** Set vars before invoking turbo:
84
+
85
+ ```bash
86
+ API_URL=$COMPUTED_VALUE turbo run build
87
+ ```
88
+
89
+ ## Different .env Files for Different Environments
90
+
91
+ If you use `.env.development` and `.env.production`, both should be in inputs.
92
+
93
+ ```json
94
+ {
95
+ "tasks": {
96
+ "build": {
97
+ "inputs": [
98
+ "$TURBO_DEFAULT$",
99
+ ".env",
100
+ ".env.local",
101
+ ".env.development",
102
+ ".env.development.local",
103
+ ".env.production",
104
+ ".env.production.local"
105
+ ]
106
+ }
107
+ }
108
+ }
109
+ ```
110
+
111
+ ## Complete Next.js Example
112
+
113
+ ```json
114
+ {
115
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
116
+ "globalEnv": ["CI", "NODE_ENV", "VERCEL"],
117
+ "globalPassThroughEnv": ["GITHUB_TOKEN", "VERCEL_URL"],
118
+ "tasks": {
119
+ "build": {
120
+ "dependsOn": ["^build"],
121
+ "env": ["DATABASE_URL", "NEXT_PUBLIC_*", "!NEXT_PUBLIC_ANALYTICS_ID"],
122
+ "passThroughEnv": ["SENTRY_AUTH_TOKEN"],
123
+ "inputs": [
124
+ "$TURBO_DEFAULT$",
125
+ ".env",
126
+ ".env.local",
127
+ ".env.production",
128
+ ".env.production.local"
129
+ ],
130
+ "outputs": [".next/**", "!.next/cache/**"]
131
+ }
132
+ }
133
+ }
134
+ ```
135
+
136
+ This config:
137
+
138
+ - Hashes DATABASE*URL and NEXT_PUBLIC*\* vars (except analytics)
139
+ - Passes through SENTRY_AUTH_TOKEN without hashing
140
+ - Includes all .env file variants in the hash
141
+ - Makes CI tokens available globally
142
+
143
+ ### With `futureFlags.globalConfiguration`
144
+
145
+ The same config using the `global` key. The `.env` files move to `global.inputs`, which means they get folded into each task's hash individually rather than the global hash. This lets tasks exclude specific `.env` files if needed.
146
+
147
+ ```json
148
+ {
149
+ "$schema": "https://v2-9-7-canary-13.turborepo.dev/schema.json",
150
+ "futureFlags": { "globalConfiguration": true },
151
+ "global": {
152
+ "env": ["CI", "NODE_ENV", "VERCEL"],
153
+ "passThroughEnv": ["GITHUB_TOKEN", "VERCEL_URL"],
154
+ "inputs": [".env", ".env.local", ".env.production", ".env.production.local"]
155
+ },
156
+ "tasks": {
157
+ "build": {
158
+ "dependsOn": ["^build"],
159
+ "env": ["DATABASE_URL", "NEXT_PUBLIC_*", "!NEXT_PUBLIC_ANALYTICS_ID"],
160
+ "passThroughEnv": ["SENTRY_AUTH_TOKEN"],
161
+ "outputs": [".next/**", "!.next/cache/**"]
162
+ }
163
+ }
164
+ }
165
+ ```
166
+
167
+ With this approach, a task that doesn't care about `.env.production` can exclude it:
168
+
169
+ ```json
170
+ "lint": {
171
+ "inputs": ["$TURBO_DEFAULT$", "!$TURBO_ROOT$/.env.production"]
172
+ }
173
+ ```
174
+
175
+ This wouldn't have been possible with `globalDependencies`, where `.env.production` would be baked into the global hash and affect every task unconditionally.
.agents/skills/turborepo/references/environment/modes.md ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Environment Modes
2
+
3
+ Turborepo supports different modes for handling environment variables during task execution.
4
+
5
+ ## Strict Mode (Default)
6
+
7
+ Only explicitly configured variables are available to tasks.
8
+
9
+ **Behavior:**
10
+
11
+ - Tasks only see vars listed in `env`, `globalEnv`, `passThroughEnv`, or `globalPassThroughEnv`
12
+ - Unlisted vars are filtered out
13
+ - Tasks fail if they require unlisted variables
14
+
15
+ **Benefits:**
16
+
17
+ - Guarantees cache correctness
18
+ - Prevents accidental dependencies on system vars
19
+ - Reproducible builds across machines
20
+
21
+ ```bash
22
+ # Explicit (though it's the default)
23
+ turbo run build --env-mode=strict
24
+ ```
25
+
26
+ ## Loose Mode
27
+
28
+ All system environment variables are available to tasks.
29
+
30
+ ```bash
31
+ turbo run build --env-mode=loose
32
+ ```
33
+
34
+ **Behavior:**
35
+
36
+ - Every system env var is passed through
37
+ - Only vars in `env`/`globalEnv` affect the hash
38
+ - Other vars are available but NOT hashed
39
+
40
+ **Risks:**
41
+
42
+ - Cache may restore incorrect results if unhashed vars changed
43
+ - "Works on my machine" bugs
44
+ - CI vs local environment mismatches
45
+
46
+ **Use case:** Migrating legacy projects or debugging strict mode issues.
47
+
48
+ ## Framework Inference (Automatic)
49
+
50
+ Turborepo automatically detects frameworks and includes their conventional env vars.
51
+
52
+ ### Inferred Variables by Framework
53
+
54
+ | Framework | Pattern |
55
+ | ---------------- | ------------------- |
56
+ | Next.js | `NEXT_PUBLIC_*` |
57
+ | Vite | `VITE_*` |
58
+ | Create React App | `REACT_APP_*` |
59
+ | Gatsby | `GATSBY_*` |
60
+ | Nuxt | `NUXT_*`, `NITRO_*` |
61
+ | Expo | `EXPO_PUBLIC_*` |
62
+ | Astro | `PUBLIC_*` |
63
+ | SvelteKit | `PUBLIC_*` |
64
+ | Remix | `REMIX_*` |
65
+ | Redwood | `REDWOOD_ENV_*` |
66
+ | Sanity | `SANITY_STUDIO_*` |
67
+ | Solid | `VITE_*` |
68
+
69
+ ### Disabling Framework Inference
70
+
71
+ Globally via CLI:
72
+
73
+ ```bash
74
+ turbo run build --framework-inference=false
75
+ ```
76
+
77
+ Or exclude specific patterns in config:
78
+
79
+ ```json
80
+ {
81
+ "tasks": {
82
+ "build": {
83
+ "env": ["!NEXT_PUBLIC_*"]
84
+ }
85
+ }
86
+ }
87
+ ```
88
+
89
+ ### Why Disable?
90
+
91
+ - You want explicit control over all env vars
92
+ - Framework vars shouldn't bust the cache (e.g., analytics IDs)
93
+ - Debugging unexpected cache misses
94
+
95
+ ## Checking Environment Mode
96
+
97
+ Use `--dry` to see which vars affect each task:
98
+
99
+ ```bash
100
+ turbo run build --dry=json | jq '.tasks[].environmentVariables'
101
+ ```
.agents/skills/turborepo/references/filtering/RULE.md ADDED
@@ -0,0 +1,148 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Turborepo Filter Syntax Reference
2
+
3
+ ## Running Only Changed Packages: `--affected`
4
+
5
+ **The primary way to run only changed packages is `--affected`:**
6
+
7
+ ```bash
8
+ # Run build/test/lint only in changed packages and their dependents
9
+ turbo run build test lint --affected
10
+ ```
11
+
12
+ This compares your current branch to the default branch (usually `main` or `master`) and runs tasks in:
13
+
14
+ 1. Packages with file changes
15
+ 2. Packages that depend on changed packages (dependents)
16
+
17
+ ### Why Include Dependents?
18
+
19
+ If you change `@repo/ui`, packages that import `@repo/ui` (like `apps/web`) need to re-run their tasks to verify they still work with the changes.
20
+
21
+ ### Customizing --affected
22
+
23
+ ```bash
24
+ # Use a different base branch
25
+ turbo run build --affected --affected-base=origin/develop
26
+
27
+ # Use a different head (current state)
28
+ turbo run build --affected --affected-head=HEAD~5
29
+ ```
30
+
31
+ ### Common CI Pattern
32
+
33
+ ```yaml
34
+ # .github/workflows/ci.yml
35
+ - run: turbo run build test lint --affected
36
+ ```
37
+
38
+ This is the most efficient CI setup - only run tasks for what actually changed.
39
+
40
+ ---
41
+
42
+ ## Manual Git Comparison with --filter
43
+
44
+ For more control, use `--filter` with git comparison syntax:
45
+
46
+ ```bash
47
+ # Changed packages + dependents (same as --affected)
48
+ turbo run build --filter=...[origin/main]
49
+
50
+ # Only changed packages (no dependents)
51
+ turbo run build --filter=[origin/main]
52
+
53
+ # Changed packages + dependencies (packages they import)
54
+ turbo run build --filter=[origin/main]...
55
+
56
+ # Changed since last commit
57
+ turbo run build --filter=...[HEAD^1]
58
+
59
+ # Changed between two commits
60
+ turbo run build --filter=[a1b2c3d...e4f5g6h]
61
+ ```
62
+
63
+ ### Comparison Syntax
64
+
65
+ | Syntax | Meaning |
66
+ | ------------- | ------------------------------------- |
67
+ | `[ref]` | Packages changed since `ref` |
68
+ | `...[ref]` | Changed packages + their dependents |
69
+ | `[ref]...` | Changed packages + their dependencies |
70
+ | `...[ref]...` | Dependencies, changed, AND dependents |
71
+
72
+ ---
73
+
74
+ ## Other Filter Types
75
+
76
+ Filters select which packages to include in a `turbo run` invocation.
77
+
78
+ ### Basic Syntax
79
+
80
+ ```bash
81
+ turbo run build --filter=<package-name>
82
+ turbo run build -F <package-name>
83
+ ```
84
+
85
+ Multiple filters combine as a union (packages matching ANY filter run).
86
+
87
+ ### By Package Name
88
+
89
+ ```bash
90
+ --filter=web # exact match
91
+ --filter=@acme/* # scope glob
92
+ --filter=*-app # name glob
93
+ ```
94
+
95
+ ### By Directory
96
+
97
+ ```bash
98
+ --filter=./apps/* # all packages in apps/
99
+ --filter=./packages/ui # specific directory
100
+ ```
101
+
102
+ ### By Dependencies/Dependents
103
+
104
+ | Syntax | Meaning |
105
+ | ----------- | -------------------------------------- |
106
+ | `pkg...` | Package AND all its dependencies |
107
+ | `...pkg` | Package AND all its dependents |
108
+ | `...pkg...` | Dependencies, package, AND dependents |
109
+ | `^pkg...` | Only dependencies (exclude pkg itself) |
110
+ | `...^pkg` | Only dependents (exclude pkg itself) |
111
+
112
+ ### Negation
113
+
114
+ Exclude packages with `!`:
115
+
116
+ ```bash
117
+ --filter=!web # exclude web
118
+ --filter=./apps/* --filter=!admin # apps except admin
119
+ ```
120
+
121
+ ### Task Identifiers
122
+
123
+ Run a specific task in a specific package:
124
+
125
+ ```bash
126
+ turbo run web#build # only web's build task
127
+ turbo run web#build api#test # web build + api test
128
+ ```
129
+
130
+ ### Combining Filters
131
+
132
+ Multiple `--filter` flags create a union:
133
+
134
+ ```bash
135
+ turbo run build --filter=web --filter=api # runs in both
136
+ ```
137
+
138
+ ---
139
+
140
+ ## Quick Reference: Changed Packages
141
+
142
+ | Goal | Command |
143
+ | ---------------------------------- | ----------------------------------------------------------- |
144
+ | Changed + dependents (recommended) | `turbo run build --affected` |
145
+ | Custom base branch | `turbo run build --affected --affected-base=origin/develop` |
146
+ | Only changed (no dependents) | `turbo run build --filter=[origin/main]` |
147
+ | Changed + dependencies | `turbo run build --filter=[origin/main]...` |
148
+ | Since last commit | `turbo run build --filter=...[HEAD^1]` |
.agents/skills/turborepo/references/filtering/patterns.md ADDED
@@ -0,0 +1,152 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Common Filter Patterns
2
+
3
+ Practical examples for typical monorepo scenarios.
4
+
5
+ ## Single Package
6
+
7
+ Run task in one package:
8
+
9
+ ```bash
10
+ turbo run build --filter=web
11
+ turbo run test --filter=@acme/api
12
+ ```
13
+
14
+ ## Package with Dependencies
15
+
16
+ Build a package and everything it depends on:
17
+
18
+ ```bash
19
+ turbo run build --filter=web...
20
+ ```
21
+
22
+ Useful for: ensuring all dependencies are built before the target.
23
+
24
+ ## Package Dependents
25
+
26
+ Run in all packages that depend on a library:
27
+
28
+ ```bash
29
+ turbo run test --filter=...ui
30
+ ```
31
+
32
+ Useful for: testing consumers after changing a shared package.
33
+
34
+ ## Dependents Only (Exclude Target)
35
+
36
+ Test packages that depend on ui, but not ui itself:
37
+
38
+ ```bash
39
+ turbo run test --filter=...^ui
40
+ ```
41
+
42
+ ## Changed Packages
43
+
44
+ Run only in packages with file changes since last commit:
45
+
46
+ ```bash
47
+ turbo run lint --filter=[HEAD^1]
48
+ ```
49
+
50
+ Since a specific branch point:
51
+
52
+ ```bash
53
+ turbo run lint --filter=[main...HEAD]
54
+ ```
55
+
56
+ ## Changed + Dependents (PR Builds)
57
+
58
+ Run in changed packages AND packages that depend on them:
59
+
60
+ ```bash
61
+ turbo run build test --filter=...[HEAD^1]
62
+ ```
63
+
64
+ Or use the shortcut:
65
+
66
+ ```bash
67
+ turbo run build test --affected
68
+ ```
69
+
70
+ ## Directory-Based
71
+
72
+ Run in all apps:
73
+
74
+ ```bash
75
+ turbo run build --filter=./apps/*
76
+ ```
77
+
78
+ Run in specific directories:
79
+
80
+ ```bash
81
+ turbo run build --filter=./apps/web --filter=./apps/api
82
+ ```
83
+
84
+ ## Scope-Based
85
+
86
+ Run in all packages under a scope:
87
+
88
+ ```bash
89
+ turbo run build --filter=@acme/*
90
+ ```
91
+
92
+ ## Exclusions
93
+
94
+ Run in all apps except admin:
95
+
96
+ ```bash
97
+ turbo run build --filter=./apps/* --filter=!admin
98
+ ```
99
+
100
+ Run everywhere except specific packages:
101
+
102
+ ```bash
103
+ turbo run lint --filter=!legacy-app --filter=!deprecated-pkg
104
+ ```
105
+
106
+ ## Complex Combinations
107
+
108
+ Apps that changed, plus their dependents:
109
+
110
+ ```bash
111
+ turbo run build --filter=...[HEAD^1] --filter=./apps/*
112
+ ```
113
+
114
+ All packages except docs, but only if changed:
115
+
116
+ ```bash
117
+ turbo run build --filter=[main...HEAD] --filter=!docs
118
+ ```
119
+
120
+ ## Debugging Filters
121
+
122
+ Use `--dry` to see what would run without executing:
123
+
124
+ ```bash
125
+ turbo run build --filter=web... --dry
126
+ ```
127
+
128
+ Use `--dry=json` for machine-readable output:
129
+
130
+ ```bash
131
+ turbo run build --filter=...[HEAD^1] --dry=json
132
+ ```
133
+
134
+ ## CI/CD Patterns
135
+
136
+ PR validation (most common):
137
+
138
+ ```bash
139
+ turbo run build test lint --affected
140
+ ```
141
+
142
+ Deploy only changed apps:
143
+
144
+ ```bash
145
+ turbo run deploy --filter=./apps/* --filter=[main...HEAD]
146
+ ```
147
+
148
+ Full rebuild of specific app and deps:
149
+
150
+ ```bash
151
+ turbo run build --filter=production-app...
152
+ ```
.agents/skills/turborepo/references/watch/RULE.md ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # turbo watch
2
+
3
+ Full docs: https://turborepo.dev/docs/reference/watch
4
+
5
+ Re-run tasks automatically when code changes. Dependency-aware.
6
+
7
+ ```bash
8
+ turbo watch [tasks]
9
+ ```
10
+
11
+ ## Basic Usage
12
+
13
+ ```bash
14
+ # Watch and re-run build task when code changes
15
+ turbo watch build
16
+
17
+ # Watch multiple tasks
18
+ turbo watch build test lint
19
+ ```
20
+
21
+ Tasks re-run in order configured in `turbo.json` when source files change.
22
+
23
+ ## With Persistent Tasks
24
+
25
+ Persistent tasks (`"persistent": true`) won't exit, so they can't be depended on. They work the same in `turbo watch` as `turbo run`.
26
+
27
+ ### Dependency-Aware Persistent Tasks
28
+
29
+ If your tool has built-in watching (like `next dev`), use its watcher:
30
+
31
+ ```json
32
+ {
33
+ "tasks": {
34
+ "dev": {
35
+ "persistent": true,
36
+ "cache": false
37
+ }
38
+ }
39
+ }
40
+ ```
41
+
42
+ ### Non-Dependency-Aware Tools
43
+
44
+ For tools that don't detect dependency changes, use `interruptible`:
45
+
46
+ ```json
47
+ {
48
+ "tasks": {
49
+ "dev": {
50
+ "persistent": true,
51
+ "interruptible": true,
52
+ "cache": false
53
+ }
54
+ }
55
+ }
56
+ ```
57
+
58
+ `turbo watch` will restart interruptible tasks when dependencies change.
59
+
60
+ ## Limitations
61
+
62
+ ### Caching
63
+
64
+ Caching is experimental with watch mode:
65
+
66
+ ```bash
67
+ turbo watch your-tasks --experimental-write-cache
68
+ ```
69
+
70
+ ### Task Outputs in Source Control
71
+
72
+ If tasks write files tracked by git, watch mode may loop infinitely. Watch mode uses file hashes to prevent this but it's not foolproof.
73
+
74
+ **Recommendation**: Remove task outputs from git.
75
+
76
+ ## vs turbo run
77
+
78
+ | Feature | `turbo run` | `turbo watch` |
79
+ | ----------------- | ----------- | ------------- |
80
+ | Runs once | Yes | No |
81
+ | Re-runs on change | No | Yes |
82
+ | Caching | Full | Experimental |
83
+ | Use case | CI, one-off | Development |
84
+
85
+ ## Common Patterns
86
+
87
+ ### Development Workflow
88
+
89
+ ```bash
90
+ # Run dev servers and watch for build changes
91
+ turbo watch dev build
92
+ ```
93
+
94
+ ### Type Checking During Development
95
+
96
+ ```bash
97
+ # Watch and re-run type checks
98
+ turbo watch check-types
99
+ ```
.agents/skills/vercel-composition-patterns/AGENTS.md ADDED
@@ -0,0 +1,917 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React Composition Patterns
2
+
3
+ **Version 1.0.0**
4
+ Engineering
5
+ January 2026
6
+
7
+ > **Note:**
8
+ > This document is mainly for agents and LLMs to follow when maintaining,
9
+ > generating, or refactoring React codebases using composition. Humans
10
+ > may also find it useful, but guidance here is optimized for automation
11
+ > and consistency by AI-assisted workflows.
12
+
13
+ ---
14
+
15
+ ## Abstract
16
+
17
+ Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale.
18
+
19
+ ---
20
+
21
+ ## Table of Contents
22
+
23
+ 1. [Component Architecture](#1-component-architecture) — **HIGH**
24
+ - 1.1 [Avoid Boolean Prop Proliferation](#11-avoid-boolean-prop-proliferation)
25
+ - 1.2 [Use Compound Components](#12-use-compound-components)
26
+ 2. [State Management](#2-state-management) — **MEDIUM**
27
+ - 2.1 [Decouple State Management from UI](#21-decouple-state-management-from-ui)
28
+ - 2.2 [Define Generic Context Interfaces for Dependency Injection](#22-define-generic-context-interfaces-for-dependency-injection)
29
+ - 2.3 [Lift State into Provider Components](#23-lift-state-into-provider-components)
30
+ 3. [Implementation Patterns](#3-implementation-patterns) — **MEDIUM**
31
+ - 3.1 [Create Explicit Component Variants](#31-create-explicit-component-variants)
32
+ - 3.2 [Prefer Composing Children Over Render Props](#32-prefer-composing-children-over-render-props)
33
+ 4. [React 19 APIs](#4-react-19-apis) — **MEDIUM**
34
+ - 4.1 [React 19 API Changes](#41-react-19-api-changes)
35
+
36
+ ---
37
+
38
+ ## 1. Component Architecture
39
+
40
+ **Impact: HIGH**
41
+
42
+ Fundamental patterns for structuring components to avoid prop
43
+ proliferation and enable flexible composition.
44
+
45
+ ### 1.1 Avoid Boolean Prop Proliferation
46
+
47
+ **Impact: CRITICAL (prevents unmaintainable component variants)**
48
+
49
+ Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize
50
+
51
+ component behavior. Each boolean doubles possible states and creates
52
+
53
+ unmaintainable conditional logic. Use composition instead.
54
+
55
+ **Incorrect: boolean props create exponential complexity**
56
+
57
+ ```tsx
58
+ function Composer({
59
+ onSubmit,
60
+ isThread,
61
+ channelId,
62
+ isDMThread,
63
+ dmId,
64
+ isEditing,
65
+ isForwarding,
66
+ }: Props) {
67
+ return (
68
+ <form>
69
+ <Header />
70
+ <Input />
71
+ {isDMThread ? (
72
+ <AlsoSendToDMField id={dmId} />
73
+ ) : isThread ? (
74
+ <AlsoSendToChannelField id={channelId} />
75
+ ) : null}
76
+ {isEditing ? <EditActions /> : isForwarding ? <ForwardActions /> : <DefaultActions />}
77
+ <Footer onSubmit={onSubmit} />
78
+ </form>
79
+ );
80
+ }
81
+ ```
82
+
83
+ **Correct: composition eliminates conditionals**
84
+
85
+ ```tsx
86
+ // Channel composer
87
+ function ChannelComposer() {
88
+ return (
89
+ <Composer.Frame>
90
+ <Composer.Header />
91
+ <Composer.Input />
92
+ <Composer.Footer>
93
+ <Composer.Attachments />
94
+ <Composer.Formatting />
95
+ <Composer.Emojis />
96
+ <Composer.Submit />
97
+ </Composer.Footer>
98
+ </Composer.Frame>
99
+ );
100
+ }
101
+
102
+ // Thread composer - adds "also send to channel" field
103
+ function ThreadComposer({ channelId }: { channelId: string }) {
104
+ return (
105
+ <Composer.Frame>
106
+ <Composer.Header />
107
+ <Composer.Input />
108
+ <AlsoSendToChannelField id={channelId} />
109
+ <Composer.Footer>
110
+ <Composer.Formatting />
111
+ <Composer.Emojis />
112
+ <Composer.Submit />
113
+ </Composer.Footer>
114
+ </Composer.Frame>
115
+ );
116
+ }
117
+
118
+ // Edit composer - different footer actions
119
+ function EditComposer() {
120
+ return (
121
+ <Composer.Frame>
122
+ <Composer.Input />
123
+ <Composer.Footer>
124
+ <Composer.Formatting />
125
+ <Composer.Emojis />
126
+ <Composer.CancelEdit />
127
+ <Composer.SaveEdit />
128
+ </Composer.Footer>
129
+ </Composer.Frame>
130
+ );
131
+ }
132
+ ```
133
+
134
+ Each variant is explicit about what it renders. We can share internals without
135
+
136
+ sharing a single monolithic parent.
137
+
138
+ ### 1.2 Use Compound Components
139
+
140
+ **Impact: HIGH (enables flexible composition without prop drilling)**
141
+
142
+ Structure complex components as compound components with a shared context. Each
143
+
144
+ subcomponent accesses shared state via context, not props. Consumers compose the
145
+
146
+ pieces they need.
147
+
148
+ **Incorrect: monolithic component with render props**
149
+
150
+ ```tsx
151
+ function Composer({
152
+ renderHeader,
153
+ renderFooter,
154
+ renderActions,
155
+ showAttachments,
156
+ showFormatting,
157
+ showEmojis,
158
+ }: Props) {
159
+ return (
160
+ <form>
161
+ {renderHeader?.()}
162
+ <Input />
163
+ {showAttachments && <Attachments />}
164
+ {renderFooter ? (
165
+ renderFooter()
166
+ ) : (
167
+ <Footer>
168
+ {showFormatting && <Formatting />}
169
+ {showEmojis && <Emojis />}
170
+ {renderActions?.()}
171
+ </Footer>
172
+ )}
173
+ </form>
174
+ );
175
+ }
176
+ ```
177
+
178
+ **Correct: compound components with shared context**
179
+
180
+ ```tsx
181
+ const ComposerContext = createContext<ComposerContextValue | null>(null);
182
+
183
+ function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
184
+ return <ComposerContext value={{ state, actions, meta }}>{children}</ComposerContext>;
185
+ }
186
+
187
+ function ComposerFrame({ children }: { children: React.ReactNode }) {
188
+ return <form>{children}</form>;
189
+ }
190
+
191
+ function ComposerInput() {
192
+ const {
193
+ state,
194
+ actions: { update },
195
+ meta: { inputRef },
196
+ } = use(ComposerContext);
197
+ return (
198
+ <TextInput
199
+ ref={inputRef}
200
+ value={state.input}
201
+ onChangeText={(text) => update((s) => ({ ...s, input: text }))}
202
+ />
203
+ );
204
+ }
205
+
206
+ function ComposerSubmit() {
207
+ const {
208
+ actions: { submit },
209
+ } = use(ComposerContext);
210
+ return <Button onPress={submit}>Send</Button>;
211
+ }
212
+
213
+ // Export as compound component
214
+ const Composer = {
215
+ Provider: ComposerProvider,
216
+ Frame: ComposerFrame,
217
+ Input: ComposerInput,
218
+ Submit: ComposerSubmit,
219
+ Header: ComposerHeader,
220
+ Footer: ComposerFooter,
221
+ Attachments: ComposerAttachments,
222
+ Formatting: ComposerFormatting,
223
+ Emojis: ComposerEmojis,
224
+ };
225
+ ```
226
+
227
+ **Usage:**
228
+
229
+ ```tsx
230
+ <Composer.Provider state={state} actions={actions} meta={meta}>
231
+ <Composer.Frame>
232
+ <Composer.Header />
233
+ <Composer.Input />
234
+ <Composer.Footer>
235
+ <Composer.Formatting />
236
+ <Composer.Submit />
237
+ </Composer.Footer>
238
+ </Composer.Frame>
239
+ </Composer.Provider>
240
+ ```
241
+
242
+ Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.
243
+
244
+ ---
245
+
246
+ ## 2. State Management
247
+
248
+ **Impact: MEDIUM**
249
+
250
+ Patterns for lifting state and managing shared context across
251
+ composed components.
252
+
253
+ ### 2.1 Decouple State Management from UI
254
+
255
+ **Impact: MEDIUM (enables swapping state implementations without changing UI)**
256
+
257
+ The provider component should be the only place that knows how state is managed.
258
+
259
+ UI components consume the context interface—they don't know if state comes from
260
+
261
+ useState, Zustand, or a server sync.
262
+
263
+ **Incorrect: UI coupled to state implementation**
264
+
265
+ ```tsx
266
+ function ChannelComposer({ channelId }: { channelId: string }) {
267
+ // UI component knows about global state implementation
268
+ const state = useGlobalChannelState(channelId);
269
+ const { submit, updateInput } = useChannelSync(channelId);
270
+
271
+ return (
272
+ <Composer.Frame>
273
+ <Composer.Input value={state.input} onChange={(text) => sync.updateInput(text)} />
274
+ <Composer.Submit onPress={() => sync.submit()} />
275
+ </Composer.Frame>
276
+ );
277
+ }
278
+ ```
279
+
280
+ **Correct: state management isolated in provider**
281
+
282
+ ```tsx
283
+ // Provider handles all state management details
284
+ function ChannelProvider({
285
+ channelId,
286
+ children,
287
+ }: {
288
+ channelId: string;
289
+ children: React.ReactNode;
290
+ }) {
291
+ const { state, update, submit } = useGlobalChannel(channelId);
292
+ const inputRef = useRef(null);
293
+
294
+ return (
295
+ <Composer.Provider state={state} actions={{ update, submit }} meta={{ inputRef }}>
296
+ {children}
297
+ </Composer.Provider>
298
+ );
299
+ }
300
+
301
+ // UI component only knows about the context interface
302
+ function ChannelComposer() {
303
+ return (
304
+ <Composer.Frame>
305
+ <Composer.Header />
306
+ <Composer.Input />
307
+ <Composer.Footer>
308
+ <Composer.Submit />
309
+ </Composer.Footer>
310
+ </Composer.Frame>
311
+ );
312
+ }
313
+
314
+ // Usage
315
+ function Channel({ channelId }: { channelId: string }) {
316
+ return (
317
+ <ChannelProvider channelId={channelId}>
318
+ <ChannelComposer />
319
+ </ChannelProvider>
320
+ );
321
+ }
322
+ ```
323
+
324
+ **Different providers, same UI:**
325
+
326
+ ```tsx
327
+ // Local state for ephemeral forms
328
+ function ForwardMessageProvider({ children }) {
329
+ const [state, setState] = useState(initialState);
330
+ const forwardMessage = useForwardMessage();
331
+
332
+ return (
333
+ <Composer.Provider state={state} actions={{ update: setState, submit: forwardMessage }}>
334
+ {children}
335
+ </Composer.Provider>
336
+ );
337
+ }
338
+
339
+ // Global synced state for channels
340
+ function ChannelProvider({ channelId, children }) {
341
+ const { state, update, submit } = useGlobalChannel(channelId);
342
+
343
+ return (
344
+ <Composer.Provider state={state} actions={{ update, submit }}>
345
+ {children}
346
+ </Composer.Provider>
347
+ );
348
+ }
349
+ ```
350
+
351
+ The same `Composer.Input` component works with both providers because it only
352
+
353
+ depends on the context interface, not the implementation.
354
+
355
+ ### 2.2 Define Generic Context Interfaces for Dependency Injection
356
+
357
+ **Impact: HIGH (enables dependency-injectable state across use-cases)**
358
+
359
+ Define a **generic interface** for your component context with three parts:
360
+
361
+ `state`, `actions`, and `meta`. This interface is a contract that any provider
362
+
363
+ can implement—enabling the same UI components to work with completely different
364
+
365
+ state implementations.
366
+
367
+ **Core principle:** Lift state, compose internals, make state
368
+
369
+ dependency-injectable.
370
+
371
+ **Incorrect: UI coupled to specific state implementation**
372
+
373
+ ```tsx
374
+ function ComposerInput() {
375
+ // Tightly coupled to a specific hook
376
+ const { input, setInput } = useChannelComposerState();
377
+ return <TextInput value={input} onChangeText={setInput} />;
378
+ }
379
+ ```
380
+
381
+ **Correct: generic interface enables dependency injection**
382
+
383
+ ```tsx
384
+ // Define a GENERIC interface that any provider can implement
385
+ interface ComposerState {
386
+ input: string;
387
+ attachments: Attachment[];
388
+ isSubmitting: boolean;
389
+ }
390
+
391
+ interface ComposerActions {
392
+ update: (updater: (state: ComposerState) => ComposerState) => void;
393
+ submit: () => void;
394
+ }
395
+
396
+ interface ComposerMeta {
397
+ inputRef: React.RefObject<TextInput>;
398
+ }
399
+
400
+ interface ComposerContextValue {
401
+ state: ComposerState;
402
+ actions: ComposerActions;
403
+ meta: ComposerMeta;
404
+ }
405
+
406
+ const ComposerContext = createContext<ComposerContextValue | null>(null);
407
+ ```
408
+
409
+ **UI components consume the interface, not the implementation:**
410
+
411
+ ```tsx
412
+ function ComposerInput() {
413
+ const {
414
+ state,
415
+ actions: { update },
416
+ meta,
417
+ } = use(ComposerContext);
418
+
419
+ // This component works with ANY provider that implements the interface
420
+ return (
421
+ <TextInput
422
+ ref={meta.inputRef}
423
+ value={state.input}
424
+ onChangeText={(text) => update((s) => ({ ...s, input: text }))}
425
+ />
426
+ );
427
+ }
428
+ ```
429
+
430
+ **Different providers implement the same interface:**
431
+
432
+ ```tsx
433
+ // Provider A: Local state for ephemeral forms
434
+ function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
435
+ const [state, setState] = useState(initialState);
436
+ const inputRef = useRef(null);
437
+ const submit = useForwardMessage();
438
+
439
+ return (
440
+ <ComposerContext
441
+ value={{
442
+ state,
443
+ actions: { update: setState, submit },
444
+ meta: { inputRef },
445
+ }}
446
+ >
447
+ {children}
448
+ </ComposerContext>
449
+ );
450
+ }
451
+
452
+ // Provider B: Global synced state for channels
453
+ function ChannelProvider({ channelId, children }: Props) {
454
+ const { state, update, submit } = useGlobalChannel(channelId);
455
+ const inputRef = useRef(null);
456
+
457
+ return (
458
+ <ComposerContext
459
+ value={{
460
+ state,
461
+ actions: { update, submit },
462
+ meta: { inputRef },
463
+ }}
464
+ >
465
+ {children}
466
+ </ComposerContext>
467
+ );
468
+ }
469
+ ```
470
+
471
+ **The same composed UI works with both:**
472
+
473
+ ```tsx
474
+ // Works with ForwardMessageProvider (local state)
475
+ <ForwardMessageProvider>
476
+ <Composer.Frame>
477
+ <Composer.Input />
478
+ <Composer.Submit />
479
+ </Composer.Frame>
480
+ </ForwardMessageProvider>
481
+
482
+ // Works with ChannelProvider (global synced state)
483
+ <ChannelProvider channelId="abc">
484
+ <Composer.Frame>
485
+ <Composer.Input />
486
+ <Composer.Submit />
487
+ </Composer.Frame>
488
+ </ChannelProvider>
489
+ ```
490
+
491
+ **Custom UI outside the component can access state and actions:**
492
+
493
+ ```tsx
494
+ function ForwardMessageDialog() {
495
+ return (
496
+ <ForwardMessageProvider>
497
+ <Dialog>
498
+ {/* The composer UI */}
499
+ <Composer.Frame>
500
+ <Composer.Input placeholder="Add a message, if you'd like." />
501
+ <Composer.Footer>
502
+ <Composer.Formatting />
503
+ <Composer.Emojis />
504
+ </Composer.Footer>
505
+ </Composer.Frame>
506
+
507
+ {/* Custom UI OUTSIDE the composer, but INSIDE the provider */}
508
+ <MessagePreview />
509
+
510
+ {/* Actions at the bottom of the dialog */}
511
+ <DialogActions>
512
+ <CancelButton />
513
+ <ForwardButton />
514
+ </DialogActions>
515
+ </Dialog>
516
+ </ForwardMessageProvider>
517
+ );
518
+ }
519
+
520
+ // This button lives OUTSIDE Composer.Frame but can still submit based on its context!
521
+ function ForwardButton() {
522
+ const {
523
+ actions: { submit },
524
+ } = use(ComposerContext);
525
+ return <Button onPress={submit}>Forward</Button>;
526
+ }
527
+
528
+ // This preview lives OUTSIDE Composer.Frame but can read composer's state!
529
+ function MessagePreview() {
530
+ const { state } = use(ComposerContext);
531
+ return <Preview message={state.input} attachments={state.attachments} />;
532
+ }
533
+ ```
534
+
535
+ The provider boundary is what matters—not the visual nesting. Components that
536
+
537
+ need shared state don't have to be inside the `Composer.Frame`. They just need
538
+
539
+ to be within the provider.
540
+
541
+ The `ForwardButton` and `MessagePreview` are not visually inside the composer
542
+
543
+ box, but they can still access its state and actions. This is the power of
544
+
545
+ lifting state into providers.
546
+
547
+ The UI is reusable bits you compose together. The state is dependency-injected
548
+
549
+ by the provider. Swap the provider, keep the UI.
550
+
551
+ ### 2.3 Lift State into Provider Components
552
+
553
+ **Impact: HIGH (enables state sharing outside component boundaries)**
554
+
555
+ Move state management into dedicated provider components. This allows sibling
556
+
557
+ components outside the main UI to access and modify state without prop drilling
558
+
559
+ or awkward refs.
560
+
561
+ **Incorrect: state trapped inside component**
562
+
563
+ ```tsx
564
+ function ForwardMessageComposer() {
565
+ const [state, setState] = useState(initialState);
566
+ const forwardMessage = useForwardMessage();
567
+
568
+ return (
569
+ <Composer.Frame>
570
+ <Composer.Input />
571
+ <Composer.Footer />
572
+ </Composer.Frame>
573
+ );
574
+ }
575
+
576
+ // Problem: How does this button access composer state?
577
+ function ForwardMessageDialog() {
578
+ return (
579
+ <Dialog>
580
+ <ForwardMessageComposer />
581
+ <MessagePreview /> {/* Needs composer state */}
582
+ <DialogActions>
583
+ <CancelButton />
584
+ <ForwardButton /> {/* Needs to call submit */}
585
+ </DialogActions>
586
+ </Dialog>
587
+ );
588
+ }
589
+ ```
590
+
591
+ **Incorrect: useEffect to sync state up**
592
+
593
+ ```tsx
594
+ function ForwardMessageDialog() {
595
+ const [input, setInput] = useState("");
596
+ return (
597
+ <Dialog>
598
+ <ForwardMessageComposer onInputChange={setInput} />
599
+ <MessagePreview input={input} />
600
+ </Dialog>
601
+ );
602
+ }
603
+
604
+ function ForwardMessageComposer({ onInputChange }) {
605
+ const [state, setState] = useState(initialState);
606
+ useEffect(() => {
607
+ onInputChange(state.input); // Sync on every change 😬
608
+ }, [state.input]);
609
+ }
610
+ ```
611
+
612
+ **Incorrect: reading state from ref on submit**
613
+
614
+ ```tsx
615
+ function ForwardMessageDialog() {
616
+ const stateRef = useRef(null);
617
+ return (
618
+ <Dialog>
619
+ <ForwardMessageComposer stateRef={stateRef} />
620
+ <ForwardButton onPress={() => submit(stateRef.current)} />
621
+ </Dialog>
622
+ );
623
+ }
624
+ ```
625
+
626
+ **Correct: state lifted to provider**
627
+
628
+ ```tsx
629
+ function ForwardMessageProvider({ children }: { children: React.ReactNode }) {
630
+ const [state, setState] = useState(initialState);
631
+ const forwardMessage = useForwardMessage();
632
+ const inputRef = useRef(null);
633
+
634
+ return (
635
+ <Composer.Provider
636
+ state={state}
637
+ actions={{ update: setState, submit: forwardMessage }}
638
+ meta={{ inputRef }}
639
+ >
640
+ {children}
641
+ </Composer.Provider>
642
+ );
643
+ }
644
+
645
+ function ForwardMessageDialog() {
646
+ return (
647
+ <ForwardMessageProvider>
648
+ <Dialog>
649
+ <ForwardMessageComposer />
650
+ <MessagePreview /> {/* Custom components can access state and actions */}
651
+ <DialogActions>
652
+ <CancelButton />
653
+ <ForwardButton /> {/* Custom components can access state and actions */}
654
+ </DialogActions>
655
+ </Dialog>
656
+ </ForwardMessageProvider>
657
+ );
658
+ }
659
+
660
+ function ForwardButton() {
661
+ const { actions } = use(Composer.Context);
662
+ return <Button onPress={actions.submit}>Forward</Button>;
663
+ }
664
+ ```
665
+
666
+ The ForwardButton lives outside the Composer.Frame but still has access to the
667
+
668
+ submit action because it's within the provider. Even though it's a one-off
669
+
670
+ component, it can still access the composer's state and actions from outside the
671
+
672
+ UI itself.
673
+
674
+ **Key insight:** Components that need shared state don't have to be visually
675
+
676
+ nested inside each other—they just need to be within the same provider.
677
+
678
+ ---
679
+
680
+ ## 3. Implementation Patterns
681
+
682
+ **Impact: MEDIUM**
683
+
684
+ Specific techniques for implementing compound components and
685
+ context providers.
686
+
687
+ ### 3.1 Create Explicit Component Variants
688
+
689
+ **Impact: MEDIUM (self-documenting code, no hidden conditionals)**
690
+
691
+ Instead of one component with many boolean props, create explicit variant
692
+
693
+ components. Each variant composes the pieces it needs. The code documents
694
+
695
+ itself.
696
+
697
+ **Incorrect: one component, many modes**
698
+
699
+ ```tsx
700
+ // What does this component actually render?
701
+ <Composer isThread isEditing={false} channelId="abc" showAttachments showFormatting={false} />
702
+ ```
703
+
704
+ **Correct: explicit variants**
705
+
706
+ ```tsx
707
+ // Immediately clear what this renders
708
+ <ThreadComposer channelId="abc" />
709
+
710
+ // Or
711
+ <EditMessageComposer messageId="xyz" />
712
+
713
+ // Or
714
+ <ForwardMessageComposer messageId="123" />
715
+ ```
716
+
717
+ Each implementation is unique, explicit and self-contained. Yet they can each
718
+
719
+ use shared parts.
720
+
721
+ **Implementation:**
722
+
723
+ ```tsx
724
+ function ThreadComposer({ channelId }: { channelId: string }) {
725
+ return (
726
+ <ThreadProvider channelId={channelId}>
727
+ <Composer.Frame>
728
+ <Composer.Input />
729
+ <AlsoSendToChannelField channelId={channelId} />
730
+ <Composer.Footer>
731
+ <Composer.Formatting />
732
+ <Composer.Emojis />
733
+ <Composer.Submit />
734
+ </Composer.Footer>
735
+ </Composer.Frame>
736
+ </ThreadProvider>
737
+ );
738
+ }
739
+
740
+ function EditMessageComposer({ messageId }: { messageId: string }) {
741
+ return (
742
+ <EditMessageProvider messageId={messageId}>
743
+ <Composer.Frame>
744
+ <Composer.Input />
745
+ <Composer.Footer>
746
+ <Composer.Formatting />
747
+ <Composer.Emojis />
748
+ <Composer.CancelEdit />
749
+ <Composer.SaveEdit />
750
+ </Composer.Footer>
751
+ </Composer.Frame>
752
+ </EditMessageProvider>
753
+ );
754
+ }
755
+
756
+ function ForwardMessageComposer({ messageId }: { messageId: string }) {
757
+ return (
758
+ <ForwardMessageProvider messageId={messageId}>
759
+ <Composer.Frame>
760
+ <Composer.Input placeholder="Add a message, if you'd like." />
761
+ <Composer.Footer>
762
+ <Composer.Formatting />
763
+ <Composer.Emojis />
764
+ <Composer.Mentions />
765
+ </Composer.Footer>
766
+ </Composer.Frame>
767
+ </ForwardMessageProvider>
768
+ );
769
+ }
770
+ ```
771
+
772
+ Each variant is explicit about:
773
+
774
+ - What provider/state it uses
775
+
776
+ - What UI elements it includes
777
+
778
+ - What actions are available
779
+
780
+ No boolean prop combinations to reason about. No impossible states.
781
+
782
+ ### 3.2 Prefer Composing Children Over Render Props
783
+
784
+ **Impact: MEDIUM (cleaner composition, better readability)**
785
+
786
+ Use `children` for composition instead of `renderX` props. Children are more
787
+
788
+ readable, compose naturally, and don't require understanding callback
789
+
790
+ signatures.
791
+
792
+ **Incorrect: render props**
793
+
794
+ ```tsx
795
+ function Composer({
796
+ renderHeader,
797
+ renderFooter,
798
+ renderActions,
799
+ }: {
800
+ renderHeader?: () => React.ReactNode;
801
+ renderFooter?: () => React.ReactNode;
802
+ renderActions?: () => React.ReactNode;
803
+ }) {
804
+ return (
805
+ <form>
806
+ {renderHeader?.()}
807
+ <Input />
808
+ {renderFooter ? renderFooter() : <DefaultFooter />}
809
+ {renderActions?.()}
810
+ </form>
811
+ );
812
+ }
813
+
814
+ // Usage is awkward and inflexible
815
+ return (
816
+ <Composer
817
+ renderHeader={() => <CustomHeader />}
818
+ renderFooter={() => (
819
+ <>
820
+ <Formatting />
821
+ <Emojis />
822
+ </>
823
+ )}
824
+ renderActions={() => <SubmitButton />}
825
+ />
826
+ );
827
+ ```
828
+
829
+ **Correct: compound components with children**
830
+
831
+ ```tsx
832
+ function ComposerFrame({ children }: { children: React.ReactNode }) {
833
+ return <form>{children}</form>;
834
+ }
835
+
836
+ function ComposerFooter({ children }: { children: React.ReactNode }) {
837
+ return <footer className="flex">{children}</footer>;
838
+ }
839
+
840
+ // Usage is flexible
841
+ return (
842
+ <Composer.Frame>
843
+ <CustomHeader />
844
+ <Composer.Input />
845
+ <Composer.Footer>
846
+ <Composer.Formatting />
847
+ <Composer.Emojis />
848
+ <SubmitButton />
849
+ </Composer.Footer>
850
+ </Composer.Frame>
851
+ );
852
+ ```
853
+
854
+ **When render props are appropriate:**
855
+
856
+ ```tsx
857
+ // Render props work well when you need to pass data back
858
+ <List data={items} renderItem={({ item, index }) => <Item item={item} index={index} />} />
859
+ ```
860
+
861
+ Use render props when the parent needs to provide data or state to the child.
862
+
863
+ Use children when composing static structure.
864
+
865
+ ---
866
+
867
+ ## 4. React 19 APIs
868
+
869
+ **Impact: MEDIUM**
870
+
871
+ React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.
872
+
873
+ ### 4.1 React 19 API Changes
874
+
875
+ **Impact: MEDIUM (cleaner component definitions and context usage)**
876
+
877
+ > **⚠️ React 19+ only.** Skip this if you're on React 18 or earlier.
878
+
879
+ In React 19, `ref` is now a regular prop (no `forwardRef` wrapper needed), and `use()` replaces `useContext()`.
880
+
881
+ **Incorrect: forwardRef in React 19**
882
+
883
+ ```tsx
884
+ const ComposerInput = forwardRef<TextInput, Props>((props, ref) => {
885
+ return <TextInput ref={ref} {...props} />;
886
+ });
887
+ ```
888
+
889
+ **Correct: ref as a regular prop**
890
+
891
+ ```tsx
892
+ function ComposerInput({ ref, ...props }: Props & { ref?: React.Ref<TextInput> }) {
893
+ return <TextInput ref={ref} {...props} />;
894
+ }
895
+ ```
896
+
897
+ **Incorrect: useContext in React 19**
898
+
899
+ ```tsx
900
+ const value = useContext(MyContext);
901
+ ```
902
+
903
+ **Correct: use instead of useContext**
904
+
905
+ ```tsx
906
+ const value = use(MyContext);
907
+ ```
908
+
909
+ `use()` can also be called conditionally, unlike `useContext()`.
910
+
911
+ ---
912
+
913
+ ## References
914
+
915
+ 1. [https://react.dev](https://react.dev)
916
+ 2. [https://react.dev/learn/passing-data-deeply-with-context](https://react.dev/learn/passing-data-deeply-with-context)
917
+ 3. [https://react.dev/reference/react/use](https://react.dev/reference/react/use)
.agents/skills/vercel-composition-patterns/README.md ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # React Composition Patterns
2
+
3
+ A structured repository for React composition patterns that scale. These
4
+ patterns help avoid boolean prop proliferation by using compound components,
5
+ lifting state, and composing internals.
6
+
7
+ ## Structure
8
+
9
+ - `rules/` - Individual rule files (one per rule)
10
+ - `_sections.md` - Section metadata (titles, impacts, descriptions)
11
+ - `_template.md` - Template for creating new rules
12
+ - `area-description.md` - Individual rule files
13
+ - `metadata.json` - Document metadata (version, organization, abstract)
14
+ - **`AGENTS.md`** - Compiled output (generated)
15
+
16
+ ## Rules
17
+
18
+ ### Component Architecture (CRITICAL)
19
+
20
+ - `architecture-avoid-boolean-props.md` - Don't add boolean props to customize
21
+ behavior
22
+ - `architecture-compound-components.md` - Structure as compound components with
23
+ shared context
24
+
25
+ ### State Management (HIGH)
26
+
27
+ - `state-lift-state.md` - Lift state into provider components
28
+ - `state-context-interface.md` - Define clear context interfaces
29
+ (state/actions/meta)
30
+ - `state-decouple-implementation.md` - Decouple state management from UI
31
+
32
+ ### Implementation Patterns (MEDIUM)
33
+
34
+ - `patterns-children-over-render-props.md` - Prefer children over renderX props
35
+ - `patterns-explicit-variants.md` - Create explicit component variants
36
+
37
+ ## Core Principles
38
+
39
+ 1. **Composition over configuration** — Instead of adding props, let consumers
40
+ compose
41
+ 2. **Lift your state** — State in providers, not trapped in components
42
+ 3. **Compose your internals** — Subcomponents access context, not props
43
+ 4. **Explicit variants** — Create ThreadComposer, EditComposer, not Composer
44
+ with isThread
45
+
46
+ ## Creating a New Rule
47
+
48
+ 1. Copy `rules/_template.md` to `rules/area-description.md`
49
+ 2. Choose the appropriate area prefix:
50
+ - `architecture-` for Component Architecture
51
+ - `state-` for State Management
52
+ - `patterns-` for Implementation Patterns
53
+ 3. Fill in the frontmatter and content
54
+ 4. Ensure you have clear examples with explanations
55
+
56
+ ## Impact Levels
57
+
58
+ - `CRITICAL` - Foundational patterns, prevents unmaintainable code
59
+ - `HIGH` - Significant maintainability improvements
60
+ - `MEDIUM` - Good practices for cleaner code
.agents/skills/vercel-composition-patterns/SKILL.md ADDED
@@ -0,0 +1,88 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: vercel-composition-patterns
3
+ description: React composition patterns that scale. Use when refactoring components with
4
+ boolean prop proliferation, building flexible component libraries, or
5
+ designing reusable APIs. Triggers on tasks involving compound components,
6
+ render props, context providers, or component architecture. Includes React 19
7
+ API changes.
8
+ license: MIT
9
+ metadata:
10
+ author: vercel
11
+ version: "1.0.0"
12
+ ---
13
+
14
+ # React Composition Patterns
15
+
16
+ Composition patterns for building flexible, maintainable React components. Avoid
17
+ boolean prop proliferation by using compound components, lifting state, and
18
+ composing internals. These patterns make codebases easier for both humans and AI
19
+ agents to work with as they scale.
20
+
21
+ ## When to Apply
22
+
23
+ Reference these guidelines when:
24
+
25
+ - Refactoring components with many boolean props
26
+ - Building reusable component libraries
27
+ - Designing flexible component APIs
28
+ - Reviewing component architecture
29
+ - Working with compound components or context providers
30
+
31
+ ## Rule Categories by Priority
32
+
33
+ | Priority | Category | Impact | Prefix |
34
+ | -------- | ----------------------- | ------ | --------------- |
35
+ | 1 | Component Architecture | HIGH | `architecture-` |
36
+ | 2 | State Management | MEDIUM | `state-` |
37
+ | 3 | Implementation Patterns | MEDIUM | `patterns-` |
38
+ | 4 | React 19 APIs | MEDIUM | `react19-` |
39
+
40
+ ## Quick Reference
41
+
42
+ ### 1. Component Architecture (HIGH)
43
+
44
+ - `architecture-avoid-boolean-props` - Don't add boolean props to customize
45
+ behavior; use composition
46
+ - `architecture-compound-components` - Structure complex components with shared
47
+ context
48
+
49
+ ### 2. State Management (MEDIUM)
50
+
51
+ - `state-decouple-implementation` - Provider is the only place that knows how
52
+ state is managed
53
+ - `state-context-interface` - Define generic interface with state, actions, meta
54
+ for dependency injection
55
+ - `state-lift-state` - Move state into provider components for sibling access
56
+
57
+ ### 3. Implementation Patterns (MEDIUM)
58
+
59
+ - `patterns-explicit-variants` - Create explicit variant components instead of
60
+ boolean modes
61
+ - `patterns-children-over-render-props` - Use children for composition instead
62
+ of renderX props
63
+
64
+ ### 4. React 19 APIs (MEDIUM)
65
+
66
+ > **⚠️ React 19+ only.** Skip this section if using React 18 or earlier.
67
+
68
+ - `react19-no-forwardref` - Don't use `forwardRef`; use `use()` instead of `useContext()`
69
+
70
+ ## How to Use
71
+
72
+ Read individual rule files for detailed explanations and code examples:
73
+
74
+ ```
75
+ rules/architecture-avoid-boolean-props.md
76
+ rules/state-context-interface.md
77
+ ```
78
+
79
+ Each rule file contains:
80
+
81
+ - Brief explanation of why it matters
82
+ - Incorrect code example with explanation
83
+ - Correct code example with explanation
84
+ - Additional context and references
85
+
86
+ ## Full Compiled Document
87
+
88
+ For the complete guide with all rules expanded: `AGENTS.md`
.agents/skills/vercel-composition-patterns/metadata.json ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "version": "1.0.0",
3
+ "organization": "Engineering",
4
+ "date": "January 2026",
5
+ "abstract": "Composition patterns for building flexible, maintainable React components. Avoid boolean prop proliferation by using compound components, lifting state, and composing internals. These patterns make codebases easier for both humans and AI agents to work with as they scale.",
6
+ "references": [
7
+ "https://react.dev",
8
+ "https://react.dev/learn/passing-data-deeply-with-context",
9
+ "https://react.dev/reference/react/use"
10
+ ]
11
+ }
.agents/skills/vercel-composition-patterns/rules/_sections.md ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Sections
2
+
3
+ This file defines all sections, their ordering, impact levels, and descriptions.
4
+ The section ID (in parentheses) is the filename prefix used to group rules.
5
+
6
+ ---
7
+
8
+ ## 1. Component Architecture (architecture)
9
+
10
+ **Impact:** HIGH
11
+ **Description:** Fundamental patterns for structuring components to avoid prop
12
+ proliferation and enable flexible composition.
13
+
14
+ ## 2. State Management (state)
15
+
16
+ **Impact:** MEDIUM
17
+ **Description:** Patterns for lifting state and managing shared context across
18
+ composed components.
19
+
20
+ ## 3. Implementation Patterns (patterns)
21
+
22
+ **Impact:** MEDIUM
23
+ **Description:** Specific techniques for implementing compound components and
24
+ context providers.
25
+
26
+ ## 4. React 19 APIs (react19)
27
+
28
+ **Impact:** MEDIUM
29
+ **Description:** React 19+ only. Don't use `forwardRef`; use `use()` instead of `useContext()`.
.agents/skills/vercel-composition-patterns/rules/_template.md ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Rule Title Here
3
+ impact: MEDIUM
4
+ impactDescription: brief description of impact
5
+ tags: composition, components
6
+ ---
7
+
8
+ ## Rule Title Here
9
+
10
+ Brief explanation of the rule and why it matters.
11
+
12
+ **Incorrect:**
13
+
14
+ ```tsx
15
+ // Bad code example
16
+ ```
17
+
18
+ **Correct:**
19
+
20
+ ```tsx
21
+ // Good code example
22
+ ```
23
+
24
+ Reference: [Link](https://example.com)
.agents/skills/vercel-composition-patterns/rules/architecture-avoid-boolean-props.md ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Avoid Boolean Prop Proliferation
3
+ impact: CRITICAL
4
+ impactDescription: prevents unmaintainable component variants
5
+ tags: composition, props, architecture
6
+ ---
7
+
8
+ ## Avoid Boolean Prop Proliferation
9
+
10
+ Don't add boolean props like `isThread`, `isEditing`, `isDMThread` to customize
11
+ component behavior. Each boolean doubles possible states and creates
12
+ unmaintainable conditional logic. Use composition instead.
13
+
14
+ **Incorrect (boolean props create exponential complexity):**
15
+
16
+ ```tsx
17
+ function Composer({
18
+ onSubmit,
19
+ isThread,
20
+ channelId,
21
+ isDMThread,
22
+ dmId,
23
+ isEditing,
24
+ isForwarding,
25
+ }: Props) {
26
+ return (
27
+ <form>
28
+ <Header />
29
+ <Input />
30
+ {isDMThread ? (
31
+ <AlsoSendToDMField id={dmId} />
32
+ ) : isThread ? (
33
+ <AlsoSendToChannelField id={channelId} />
34
+ ) : null}
35
+ {isEditing ? <EditActions /> : isForwarding ? <ForwardActions /> : <DefaultActions />}
36
+ <Footer onSubmit={onSubmit} />
37
+ </form>
38
+ );
39
+ }
40
+ ```
41
+
42
+ **Correct (composition eliminates conditionals):**
43
+
44
+ ```tsx
45
+ // Channel composer
46
+ function ChannelComposer() {
47
+ return (
48
+ <Composer.Frame>
49
+ <Composer.Header />
50
+ <Composer.Input />
51
+ <Composer.Footer>
52
+ <Composer.Attachments />
53
+ <Composer.Formatting />
54
+ <Composer.Emojis />
55
+ <Composer.Submit />
56
+ </Composer.Footer>
57
+ </Composer.Frame>
58
+ );
59
+ }
60
+
61
+ // Thread composer - adds "also send to channel" field
62
+ function ThreadComposer({ channelId }: { channelId: string }) {
63
+ return (
64
+ <Composer.Frame>
65
+ <Composer.Header />
66
+ <Composer.Input />
67
+ <AlsoSendToChannelField id={channelId} />
68
+ <Composer.Footer>
69
+ <Composer.Formatting />
70
+ <Composer.Emojis />
71
+ <Composer.Submit />
72
+ </Composer.Footer>
73
+ </Composer.Frame>
74
+ );
75
+ }
76
+
77
+ // Edit composer - different footer actions
78
+ function EditComposer() {
79
+ return (
80
+ <Composer.Frame>
81
+ <Composer.Input />
82
+ <Composer.Footer>
83
+ <Composer.Formatting />
84
+ <Composer.Emojis />
85
+ <Composer.CancelEdit />
86
+ <Composer.SaveEdit />
87
+ </Composer.Footer>
88
+ </Composer.Frame>
89
+ );
90
+ }
91
+ ```
92
+
93
+ Each variant is explicit about what it renders. We can share internals without
94
+ sharing a single monolithic parent.
.agents/skills/vercel-composition-patterns/rules/architecture-compound-components.md ADDED
@@ -0,0 +1,108 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Use Compound Components
3
+ impact: HIGH
4
+ impactDescription: enables flexible composition without prop drilling
5
+ tags: composition, compound-components, architecture
6
+ ---
7
+
8
+ ## Use Compound Components
9
+
10
+ Structure complex components as compound components with a shared context. Each
11
+ subcomponent accesses shared state via context, not props. Consumers compose the
12
+ pieces they need.
13
+
14
+ **Incorrect (monolithic component with render props):**
15
+
16
+ ```tsx
17
+ function Composer({
18
+ renderHeader,
19
+ renderFooter,
20
+ renderActions,
21
+ showAttachments,
22
+ showFormatting,
23
+ showEmojis,
24
+ }: Props) {
25
+ return (
26
+ <form>
27
+ {renderHeader?.()}
28
+ <Input />
29
+ {showAttachments && <Attachments />}
30
+ {renderFooter ? (
31
+ renderFooter()
32
+ ) : (
33
+ <Footer>
34
+ {showFormatting && <Formatting />}
35
+ {showEmojis && <Emojis />}
36
+ {renderActions?.()}
37
+ </Footer>
38
+ )}
39
+ </form>
40
+ );
41
+ }
42
+ ```
43
+
44
+ **Correct (compound components with shared context):**
45
+
46
+ ```tsx
47
+ const ComposerContext = createContext<ComposerContextValue | null>(null);
48
+
49
+ function ComposerProvider({ children, state, actions, meta }: ProviderProps) {
50
+ return <ComposerContext value={{ state, actions, meta }}>{children}</ComposerContext>;
51
+ }
52
+
53
+ function ComposerFrame({ children }: { children: React.ReactNode }) {
54
+ return <form>{children}</form>;
55
+ }
56
+
57
+ function ComposerInput() {
58
+ const {
59
+ state,
60
+ actions: { update },
61
+ meta: { inputRef },
62
+ } = use(ComposerContext);
63
+ return (
64
+ <TextInput
65
+ ref={inputRef}
66
+ value={state.input}
67
+ onChangeText={(text) => update((s) => ({ ...s, input: text }))}
68
+ />
69
+ );
70
+ }
71
+
72
+ function ComposerSubmit() {
73
+ const {
74
+ actions: { submit },
75
+ } = use(ComposerContext);
76
+ return <Button onPress={submit}>Send</Button>;
77
+ }
78
+
79
+ // Export as compound component
80
+ const Composer = {
81
+ Provider: ComposerProvider,
82
+ Frame: ComposerFrame,
83
+ Input: ComposerInput,
84
+ Submit: ComposerSubmit,
85
+ Header: ComposerHeader,
86
+ Footer: ComposerFooter,
87
+ Attachments: ComposerAttachments,
88
+ Formatting: ComposerFormatting,
89
+ Emojis: ComposerEmojis,
90
+ };
91
+ ```
92
+
93
+ **Usage:**
94
+
95
+ ```tsx
96
+ <Composer.Provider state={state} actions={actions} meta={meta}>
97
+ <Composer.Frame>
98
+ <Composer.Header />
99
+ <Composer.Input />
100
+ <Composer.Footer>
101
+ <Composer.Formatting />
102
+ <Composer.Submit />
103
+ </Composer.Footer>
104
+ </Composer.Frame>
105
+ </Composer.Provider>
106
+ ```
107
+
108
+ Consumers explicitly compose exactly what they need. No hidden conditionals. And the state, actions and meta are dependency-injected by a parent provider, allowing multiple usages of the same component structure.
.agents/skills/vercel-composition-patterns/rules/patterns-children-over-render-props.md ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Prefer Composing Children Over Render Props
3
+ impact: MEDIUM
4
+ impactDescription: cleaner composition, better readability
5
+ tags: composition, children, render-props
6
+ ---
7
+
8
+ ## Prefer Children Over Render Props
9
+
10
+ Use `children` for composition instead of `renderX` props. Children are more
11
+ readable, compose naturally, and don't require understanding callback
12
+ signatures.
13
+
14
+ **Incorrect (render props):**
15
+
16
+ ```tsx
17
+ function Composer({
18
+ renderHeader,
19
+ renderFooter,
20
+ renderActions,
21
+ }: {
22
+ renderHeader?: () => React.ReactNode;
23
+ renderFooter?: () => React.ReactNode;
24
+ renderActions?: () => React.ReactNode;
25
+ }) {
26
+ return (
27
+ <form>
28
+ {renderHeader?.()}
29
+ <Input />
30
+ {renderFooter ? renderFooter() : <DefaultFooter />}
31
+ {renderActions?.()}
32
+ </form>
33
+ );
34
+ }
35
+
36
+ // Usage is awkward and inflexible
37
+ return (
38
+ <Composer
39
+ renderHeader={() => <CustomHeader />}
40
+ renderFooter={() => (
41
+ <>
42
+ <Formatting />
43
+ <Emojis />
44
+ </>
45
+ )}
46
+ renderActions={() => <SubmitButton />}
47
+ />
48
+ );
49
+ ```
50
+
51
+ **Correct (compound components with children):**
52
+
53
+ ```tsx
54
+ function ComposerFrame({ children }: { children: React.ReactNode }) {
55
+ return <form>{children}</form>;
56
+ }
57
+
58
+ function ComposerFooter({ children }: { children: React.ReactNode }) {
59
+ return <footer className="flex">{children}</footer>;
60
+ }
61
+
62
+ // Usage is flexible
63
+ return (
64
+ <Composer.Frame>
65
+ <CustomHeader />
66
+ <Composer.Input />
67
+ <Composer.Footer>
68
+ <Composer.Formatting />
69
+ <Composer.Emojis />
70
+ <SubmitButton />
71
+ </Composer.Footer>
72
+ </Composer.Frame>
73
+ );
74
+ ```
75
+
76
+ **When render props are appropriate:**
77
+
78
+ ```tsx
79
+ // Render props work well when you need to pass data back
80
+ <List data={items} renderItem={({ item, index }) => <Item item={item} index={index} />} />
81
+ ```
82
+
83
+ Use render props when the parent needs to provide data or state to the child.
84
+ Use children when composing static structure.