Quant Agent — multi-agent quant terminal on Hyperliquid
All checks were successful
deploy / deploy (push) Successful in 7s

Live HL data → in-browser TA → 4-agent committee → LONG/SHORT/SKIP verdict.
Terminal grid command bar + interactive agent-workflow graph. Paper-trading
worker reuses the browser brain (dry-run + Telegram). Forgejo static deploy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
zefiroff 2026-06-09 19:00:13 +04:00
commit f3b533b032
110 changed files with 15045 additions and 0 deletions

View file

@ -0,0 +1,97 @@
---
name: add-x-tweet
description: Add X (Twitter) testimonials to the bklit homepage. Fetches username, avatar, and tweet text from a status URL and appends an entry to apps/web/lib/testimonials.ts. Use when the user shares an x.com/twitter.com tweet URL to add or replace a testimonial.
---
# Add X Tweet Testimonial
Add social proof tweets to the homepage testimonial grid.
## When to use
- User provides one or more `https://x.com/.../status/...` URLs
- User asks to replace, prioritize, or reorder testimonials
- User says "add tweet", "new testimonial", or similar
## Workflow
### 1. Fetch tweet metadata
Run the fetch script from the repo root (requires network):
```bash
node .agents/skills/add-x-tweet/scripts/fetch-tweet.mjs "https://x.com/user/status/123..."
```
For multiple URLs, pass each URL or run once per URL.
The script prints JSON:
```json
{
"id": "123...",
"url": "https://x.com/user/status/123...",
"author": { "name": "...", "handle": "@user", "avatar": "https://pbs.twimg.com/...", "verified": true },
"content": "tweet text without @uixmat prefix"
}
```
**Fallback:** If the script fails, use Twitter oEmbed:
```bash
curl -sL "https://publish.twitter.com/oembed?url=TWEET_URL&omit_script=1"
```
Parse `author_name` and HTML `<p>` for text. Avatar still needs fxtwitter or manual profile image URL.
### 2. Edit `apps/web/lib/testimonials.ts`
- Use **status id** as `id` (numeric string from URL)
- Set `url` to the canonical tweet URL (no query params)
- Run avatar through `twitterAvatarUrl()` (upgrades `_200x200``_400x400`)
- Strip leading `@uixmat` from content; trim whitespace
- Shorten very long tweets if they include trailing promo links (keep the praise, drop link-only tails)
### 3. Ordering rules
Masonry uses CSS columns (top-to-bottom per column). On `lg`, **indices 02** = column 1 top 3, **35** = column 2 top 3, **68** = column 3 top 3.
- **Prioritize** when asked: place in the next open slot among indices 08 without duplicating ids
- **Replace** when asked: remove the old entry (match by handle or old id) before adding the new one
- Set `author.verified` from script output (`author.verification.verified` via FxTwitter)
### 4. Collapsed grid
The homepage shows `testimonialCollapsedCount` (6) cards before **See more**. No change needed when adding tweets unless the user asks to change visible rows.
### 5. Verify
```bash
cd apps/web && npx tsc --noEmit -p tsconfig.json
```
Open homepage — grid is 3 columns, bottom fade + **See more** expands max-height.
## Data source
Primary: [FxTwitter API](https://github.com/FixTweet/FxTwitter) `https://api.fxtwitter.com/{user}/status/{id}` — returns author name, screen_name, avatar_url, and text.
Do not commit API keys; the public endpoint is used by the script only at add-tweet time.
## Example entry
```ts
{
id: "2056717453816729751",
url: "https://x.com/jordienr/status/2056717453816729751",
author: {
name: "jordi",
handle: "@jordienr",
avatar: twitterAvatarUrl(
"https://pbs.twimg.com/profile_images/2053541405121769475/TUDez6zL_200x200.jpg"
),
verified: true,
},
content: "bklit saved my marriage",
},
```

View file

@ -0,0 +1,71 @@
#!/usr/bin/env node
/**
* Fetch X tweet metadata for homepage testimonials.
* Usage: node fetch-tweet.mjs "https://x.com/user/status/123"
*/
const urls = process.argv.slice(2);
if (urls.length === 0) {
console.error("Usage: node fetch-tweet.mjs <tweet-url> [tweet-url...]");
process.exit(1);
}
function parseTweetUrl(url) {
const match = url.match(/(?:x|twitter)\.com\/([^/]+)\/status\/(\d+)/i);
if (!match) {
throw new Error(`Invalid tweet URL: ${url}`);
}
return { screenName: match[1], statusId: match[2] };
}
function stripReplyPrefix(text) {
return text.replace(/^@uixmat\s*/i, "").trim();
}
function upgradeAvatar(url) {
return url.replace(/_200x200\.(jpg|png|webp)$/i, "_400x400.$1");
}
async function fetchTweet(url) {
const { screenName, statusId } = parseTweetUrl(url);
const apiUrl = `https://api.fxtwitter.com/${screenName}/status/${statusId}`;
const res = await fetch(apiUrl, {
headers: { "User-Agent": "bklit-ui-add-x-tweet/1.0" },
});
if (!res.ok) {
throw new Error(`FxTwitter ${res.status} for ${url}`);
}
const data = await res.json();
const tweet = data.tweet ?? data;
const author = tweet.author ?? {};
const canonicalUrl = `https://x.com/${screenName}/status/${statusId}`;
const verified = author.verification?.verified === true;
return {
id: statusId,
url: canonicalUrl,
author: {
name: author.name ?? screenName,
handle: `@${author.screen_name ?? screenName}`,
avatar: upgradeAvatar(author.avatar_url ?? author.avatar ?? ""),
verified,
},
content: stripReplyPrefix(tweet.text ?? ""),
};
}
for (const url of urls) {
try {
const entry = await fetchTweet(url);
console.log(JSON.stringify(entry, null, 2));
} catch (err) {
console.error(`Failed: ${url}`);
console.error(err.message);
process.exitCode = 1;
}
}

View file

@ -0,0 +1,30 @@
---
name: bklit-playground
description: >
DEPRECATED — use bklit-studio instead. Local /playground is no longer the
chart development workflow for bklit-ui contributors.
disable-model-invocation: true
---
# Deprecated: bklit-playground
**This skill is retired.** Chart prototyping and editing now happen in **Studio** only.
## Use instead
Read and follow **`.agents/skills/bklit-studio/SKILL.md`**.
- Dev URL: **http://localhost:3000/studio** (`pnpm dev` from repo root)
- Optional: `?chart=line-chart` (or any slug from `packages/studio/src/chart-slugs.ts`)
## Why
Studio already includes the editor shell, component tree, properties, motion controls, codegen, and registry-backed previews. The gitignored `/playground` route duplicated wiring and drifted from production Studio.
## `/playground` route
Redirects to `/studio`. Do not scaffold `apps/web/app/playground/page.tsx` or `apps/web/components/playground/` for new chart work.
## Shipping
Unchanged: **`.agents/skills/bklit-ship/SKILL.md`** — prototypes should live in `packages/ui` and `packages/studio`, not under `apps/web/components/playground/`.

View file

@ -0,0 +1,7 @@
/**
* DEPRECATED do not copy to apps/web/app/playground/page.tsx.
*
* Chart development uses Studio: http://localhost:3000/studio
* See .agents/skills/bklit-studio/SKILL.md
*/
export {};

View file

@ -0,0 +1,115 @@
---
name: bklit-ship
description: bklit-ui monorepo contributors only — ship a chart or component from Studio prototype to production in packages/ui with docs and registry.
disable-model-invocation: true
---
# Bklit Ship Skill
This skill is for **bklit-ui monorepo contributors** taking a chart or component validated in **Studio** into **production**: published in the UI package, documented, and ready for users.
## When to use this skill
- You cloned `bklit/bklit-ui` and have a working prototype validated in **Studio** (`/studio`) and want to ship it.
- You are ready to move from "scaffolding" to "permanent": the API and key props/variants are decided from the prototyping phase.
---
## Plan: Prototype → Production
Follow these steps in order. Treat this as a checklist; each step has concrete locations in the repo.
### 1. Move into the UI package and export
- **Move** or finalize chart/component source in `packages/ui/` (e.g. `packages/ui/src/charts/` for charts). Prototypes should already live here if you followed **bklit-studio**; remove any leftover copies under `apps/web/components/playground/` if present.
- **Export** the new chart/component from the packages public API:
- For charts: add exports in `packages/ui/src/charts/index.ts`.
- If the package uses `exports` in `package.json` for specific entry points, add or update the relevant entry so the new component is importable as `@bklitui/ui/charts` (or the appropriate path).
- **Do not** add app-only chart copies under `apps/web/components/playground/` (deprecated). Studio previews belong in `packages/studio`.
### 2. Documentation and examples (apps/web)
Documentation lives under `apps/web/`. Do all of the following.
- **Update existing component docs** when shipping extends an existing chart (new props, subcomponents, or behavior):
- Add new props to the relevant tables in the **parent** doc (e.g. `line-chart.mdx` for `Grid highlightRowValues`, `ChartTooltip indicatorColor`).
- Update related utility docs if needed (e.g. `content/docs/utility/grid.mdx`, `tooltip.mdx`).
- Keep the **primary docs preview unchanged** — do not swap the standard preview on an existing page for the new variant.
- Add a short section linking to a dedicated doc page when the feature warrants one (e.g. Profit/Loss → `profit-loss-line.mdx`).
- **Chart examples (live demos on `/charts/[slug]`)**
- Add examples to the **corresponding gallery route** (e.g. profit/loss variants under `/charts/line-chart`, not only a new docs preview).
- If the feature is a new chart kind, add its slug to `apps/web/components/charts/chart-slugs.ts` and register examples in `apps/web/components/charts/chart-examples.tsx` (`CHART_NAV_ITEMS` / factory registry as appropriate).
- Reuse or mirror the prop variants you validated in the scaffolding phase.
- **Dedicated doc page** (when shipping a new composable or chart kind)
- Add `apps/web/content/docs/components/<name>.mdx` with frontmatter, `<ComponentPreview>`, installation, usage, and props — consistent with existing component docs.
- Add the slug to `apps/web/content/docs/components/meta.json` (desktop sidebar).
- Add an entry to `apps/web/components/docs/site-header.tsx` (mobile nav).
### 3. Studio
- **Update the existing studio chart** when the feature is a variant of an existing type, **or add a new studio chart** when it is a distinct kind.
- Wire **all tunable props** into studio (if not already done while prototyping):
- `packages/studio/src/lib/studio-parsers.ts` — URL state keys and defaults
- `packages/studio/src/lib/registry-control-groups.ts` — control groups
- `packages/studio/src/lib/registry.tsx` — render preview + `generateCode`
- `packages/studio/src/lib/studio-components.ts` — layer tree
- Chart-type defaults in `packages/studio/src/components/studio-state-provider.tsx` when switching chart type
### 4. Rebuild the shadcn registry
- From the **repo root**: run `pnpm registry:build`.
- This updates `apps/web/public/r/` from `packages/ui`. Ensure new components are listed in `packages/ui/registry.json` when they should be installable via shadcn.
### 5. Lint, format, test, and build
Run from repo root until clean:
```bash
pnpm lint
pnpm format
pnpm --filter @bklitui/ui test # when package logic changed
pnpm build
pnpm registry:build # if not already run in step 4
```
Fix all errors; repeat until hooks pass on commit.
### 6. Commit, push, and open a PR
- **Commit** with a short, clear message (e.g. `feat(charts): add ProfitLossLine component`).
- **Push** the branch.
- **Open a PR** with the ship checklist filled in (see below).
---
## PR checklist
- [ ] Chart/component moved to `packages/ui` and exported
- [ ] **Existing docs updated** with new props/features (standard preview unchanged)
- [ ] Gallery examples on the correct `/charts/**` route
- [ ] Dedicated doc page + sidebar + mobile nav (if new composable/chart kind)
- [ ] **Studio** chart updated or added with all props in control groups
- [ ] Registry rebuilt (`pnpm registry:build`)
- [ ] `pnpm lint`, tests (if applicable), and `pnpm build` pass
---
## File reference (quick lookup)
| Step | Location |
|------|----------|
| Chart exports | `packages/ui/src/charts/index.ts` |
| Chart slugs | `apps/web/components/charts/chart-slugs.ts` |
| Chart examples (nav + registry) | `apps/web/components/charts/chart-examples.tsx` |
| Component docs | `apps/web/content/docs/components/<name>.mdx` |
| Utility docs (Grid, Tooltip, …) | `apps/web/content/docs/utility/*.mdx` |
| Sidebar (desktop) | `apps/web/content/docs/components/meta.json``pages` |
| Mobile nav | `apps/web/components/docs/site-header.tsx``components` array |
| Studio registry | `packages/studio/src/lib/registry.tsx` |
| Studio components tree | `packages/studio/src/lib/studio-components.ts` |
| Studio controls | `packages/studio/src/lib/registry-control-groups.ts` |
| Studio URL state | `packages/studio/src/lib/studio-parsers.ts` |
| Registry (source) | `packages/ui/registry.json`; build output: `apps/web/public/r/` |
| Registry build | From root: `pnpm registry:build` |

View file

@ -0,0 +1,134 @@
---
name: bklit-studio-chart-performance
description: >
Reusable Studio chart performance audit and fix workflow. Use when a chart
feels sluggish in /studio (pan, slider ticks, legend hover) but siblings
like pie-chart feel fine.
---
# Studio chart performance
Use when a chart feels sluggish in Studio but similar charts (e.g. pie-chart) are fine.
## One-line rule
Keep enter animation on paths if you need it, then **drop Motion path subscriptions** and **isolate hover** so Studio slider and legend updates don't replay expensive arc/path math across every series every frame.
---
## 1. Find what re-renders on every interaction
Studio updates `displayState` on every slider tick and on legend/slice hover. Trace:
- Does hover live in the **same context** as data, scales, and animation config?
- Does the preview **recreate children** (`data.map`, pattern defs, motion props) every render?
- Does the chart **remount unnecessarily** (`key` tied to motion signature vs manual replay)?
**Pattern:** Split context like cartesian / pie charts — **stable slice** (data, geometry, animation config) vs **hover slice** (`hoveredIndex`, tooltip). Consumers that don't need hover use only the stable hook (`usePieStable`, `useRingStable`, `useChartStable`, …).
**Studio pan:** Wrap chart render in `StudioChartRender` (`packages/studio/src/components/studio-chart-render.tsx`) so camera pan / FPS counter parent updates skip the chart tree when render props are unchanged.
---
## 2. Treat SVG path `d` animation as expensive
Animating `d` with Motion / `useTransform` + d3 arc (or similar) runs layout + paint every frame, per series.
| Prefer | Avoid |
|--------|--------|
| `transform` / `opacity` for hover (compositor-friendly) | Continuous `d` morphing after enter is done |
| Static `d` once enter finishes | Keeping Motion subscriptions on `d` for the chart's lifetime |
| Enter animation only, then static paths | Re-running enter path math on unrelated prop changes |
**Pattern:** `useMountProgress` for enter → when progress ≥ 1 (`useEnterComplete`), render **static paths** and only animate hover with `x`/`y`/`opacity`/`scale` on a **`motion.g`** wrapper (not per-path `scale` on `motion.path`).
Shared hook: `packages/ui/src/charts/use-enter-complete.ts`
---
## 3. Memoize chart shell context
Unmemoized provider values force all children to reconcile on every parent render.
- Memoize the **stable context object** with explicit deps (data, arcs/radii, dimensions, callbacks).
- Memoize **hover context** on `hoveredIndex` + stable `setHoveredIndex` (`useCallback` in chart shell).
- Match **`isLoaded`** to ring/cartesian: `useEffect` + timeout, not a lazy `useState` initializer.
Reference: `pie-context.tsx`, `ring-context.tsx`, `chart-context.tsx`, `PieChartCore` / `RingChartCore` `useMemo` on provider value.
---
## 4. Studio previewspecific wins
Chart-agnostic; apply in `packages/studio/src/components/charts/*-studio*.tsx`:
| Win | How |
|-----|-----|
| Conditional defs | Only pass `patternDefs` / gradients when a series uses patterns |
| Memo derived data | Colored/mapped data arrays; slice/series lists (`useMemo`, deps: `dataSeed` + design fields that affect color) |
| Memo motion enter | Don't call `getStudioMotionEnterProps` inline; `useMemo` with **motion-only** deps (not full `state`) |
| Memo legend hover | `{ hoveredIndex, setHoveredIndex }` in `useMemo` — already in `studio-legend-hover.tsx` |
| Memo chart body | `memo()` wrapper; pass **primitives** (`chartKey`, `chartSize`, `data`) not whole `ctx` so pan/shell re-renders skip rebuild |
| Disable glow in Studio | `showGlow={false}` on series components |
Reference: `pie-studio-preview.tsx`, `ring-studio-preview.tsx`
---
## 5. Compare against a “fast” sibling in Studio
Diff the slow chart against one that feels smooth in the same editor (usually **pie-chart**):
| Check | Slow chart often has | Fast chart often has |
|-------|----------------------|----------------------|
| Shell | Inline render, extra defs | `StudioChartShell` + conditional patterns |
| Series count | Many animated paths | Fewer paths or simpler geometry |
| Hover | Context + full tree re-render | Stable subscribers; hover on `motion.g` / translate |
| Enter | Path `d` wipe per series | Static `d` after enter; transform-only hover |
| Pan | Chart tree rebuilds every frame | `StudioChartRender` memo boundary |
---
## 6. Validation bar
Before opening a PR:
```bash
pnpm lint
pnpm --filter @bklitui/ui check-types
pnpm --filter @bklitui/studio check-types
# scoped production build when touching studio/web
```
Manual `/studio?chart=<slug>`:
- [ ] Enter animation
- [ ] Hover / legend sync
- [ ] Drag geometry sliders (no unnecessary remount)
- [ ] Canvas pan (space + drag) after enter — FPS near pie-chart baseline
- [ ] Pattern/gradient mode if supported
---
## Chart status (bklit-ui)
| Chart | Slug | Status |
|-------|------|--------|
| Pie | `pie-chart` | ✅ Reference (#120) |
| Ring | `ring-chart` | ✅ Aligned to checklist (context split, static paths, `StudioChartRender`, preview memo) |
| Radar / Funnel / Choropleth | various | Partial — run checklist |
| Cartesian / scatter / live-line | various | ✅ #91 decimation + hover batching |
| Sankey | `sankey-chart` | `useTransform` on link stroke — candidate for `useEnterComplete` |
| Gauge | `gauge-chart` | Low priority (single arc) |
---
## Key files
| Area | Path |
|------|------|
| Enter-complete hook | `packages/ui/src/charts/use-enter-complete.ts` |
| Pie reference | `packages/ui/src/charts/pie-slice.tsx`, `pie-context.tsx`, `pie-studio-preview.tsx` |
| Ring | `packages/ui/src/charts/ring.tsx`, `ring-chart.tsx`, `ring-studio-preview.tsx` |
| Pan isolation | `packages/studio/src/components/studio-chart-render.tsx` |
| Registry | `packages/studio/src/lib/registry.tsx` |

View file

@ -0,0 +1,107 @@
---
name: bklit-studio
description: >
bklit-ui monorepo contributors only. Use automatically when building or editing
charts, tuning props/animation, or prototyping in Studio (/studio). Replaces
the deprecated local /playground route.
---
# Bklit Studio Skill (chart development)
**Monorepo contributors only.** Use this skill **automatically** whenever you build, edit, or prototype a chart — before shipping via **bklit-ship**.
Studio at **`/studio`** is the single development surface: full editor shell, component tree, properties, motion, codegen, and registry previews. Do **not** scaffold `apps/web/app/playground/`.
## When to use (auto-trigger)
- Build, prototype, or edit a chart (new or existing)
- Tune props, layers, data, styling, or animation
- Mentions Studio, `/studio`, registry preview, or chart controls
- Work continues from a prior playground task → use Studio instead
## Quick start
```bash
pnpm dev # repo root
```
Open **http://localhost:3000/studio** (optional query: `?chart=line-chart`).
| Task | URL |
|------|-----|
| Edit existing chart | `/studio?chart=<slug>` — slugs in `packages/studio/src/chart-slugs.ts` |
| Profit/loss line mode | `/studio?chart=line-chart&lineChartMode=profitLoss` |
## Editor layout
| Region | Purpose |
|--------|---------|
| **Left** | Chart type (in full Studio), layer list, data controls, animation |
| **Center** | Canvas, rulers, resizable frame, replay/scramble |
| **Right** | Properties for the **selected layer** |
**Pane rules:** animation → left (`motionPanel` on chart config); per-layer props → right; visibility → eye icon on layer (uses `hiddenComponents` in URL state).
## Where to implement (existing charts)
Edit the chart in **`packages/studio`** and **`packages/ui`** — not `apps/web/components/playground/`.
| Concern | Path |
|---------|------|
| Chart preview + codegen | `packages/studio/src/lib/registry.tsx` (`render`, `generateCode`) |
| Layer tree (components panel) | `packages/studio/src/lib/studio-components.ts` (`resolve*Components`) |
| Control groups (properties) | `packages/studio/src/lib/registry-control-groups.ts` |
| URL / default state | `packages/studio/src/lib/studio-parsers.ts` |
| Chart UI | `packages/ui/src/charts/` |
| Slugs | `packages/studio/src/chart-slugs.ts` + `studioRegistry` in `registry.tsx` |
| Studio-only previews | `packages/studio/src/components/charts/*-studio-preview.tsx` |
### Patterns in `registry.tsx` render
Reuse committed helpers (do not reimplement):
- `StudioChartShell` + `studioCartesianLegendItems` — legend grid + `showLegend`
- `StudioVisibleLayer` + `componentId` — ties render to layer visibility (`line.grid`, `line.series.0`, …)
- `getStudioCssRevealPropsForPreview` — motion / reveal
- `isStudioComponentVisible(state, componentId)` — conditional children
- `seriesStrokePropsFromState`, `fadeEdgesPropValue`, `chartTooltipPropsFromState`
Reference implementation: `lineConfig.render` in `registry.tsx`.
## New chart (not in registry yet)
1. Implement component(s) in `packages/ui/src/charts/` (minimal API first).
2. Add slug to `packages/studio/src/chart-slugs.ts`.
3. Add `StudioChartConfig` to `studioRegistry` in `registry.tsx`:
- `render(state, ctx)` — preview inside `EditorChartFrame` via `StudioShell`
- `resolveComponents` in `studio-components.ts`
- `controlGroups` / `resolveControlGroups`
- `generateCode` for the code sheet
4. Open `/studio?chart=<new-slug>` and iterate.
5. When stable, follow **bklit-ship** for docs, gallery, shadcn registry.
## Adding a prop or layer
1. Add key to `StudioUrlState` / `defaultStudioState` in `studio-parsers.ts` if missing.
2. Add control(s) on the right layer in `registry-control-groups.ts` or per-component `controlGroups` in `studio-components.ts`.
3. Wire prop in `registry.tsx` `render` (and in `packages/ui` chart code).
4. If the prop affects animation → ensure `motionPanel: true` on chart config.
5. Verify in Studio: layer list, visibility toggle, properties, replay.
## Shipping
When the API is stable, read `.agents/skills/bklit-ship/SKILL.md` — prototype code should already live in `packages/ui` + `packages/studio`; no playground route to migrate.
## Deprecated: `/playground`
The local **`/playground`** route and **`bklit-playground`** skill are deprecated. `/playground` redirects to `/studio`. Do not copy `playground-editor-shell` or `apps/web/components/playground/` for new work.
## File reference
| Item | Path |
|------|------|
| Studio app route | `apps/web/app/studio/page.tsx` (`StudioShell`) |
| Studio package | `packages/studio/` |
| Studio skill | `.agents/skills/bklit-studio/SKILL.md` |
| Ship checklist | `.agents/skills/bklit-ship/SKILL.md` |
| Performance audit | `.agents/skills/bklit-studio-chart-performance/SKILL.md` |

View file

@ -0,0 +1,136 @@
---
name: bklit-ui
description: >
Bklit UI charts and data visualization for any project using the @bklit
shadcn registry. Install, compose, theme, and animate charts correctly.
Triggers when working with @bklitui/ui/charts, @bklit components, data
visualization, dashboards, or chart theming. Also invoke manually for
chart tasks.
allowed-tools: Bash(npx shadcn@latest *), Bash(pnpm dlx shadcn@latest *), Bash(bunx --bun shadcn@latest *)
---
# Bklit UI
Composable chart components for React, distributed via the `@bklit` shadcn registry. Charts are installed as source into the user's project.
> **IMPORTANT:** Run shadcn CLI commands with the project's package runner: `npx shadcn@latest`, `pnpm dlx shadcn@latest`, or `bunx --bun shadcn@latest`.
## Current Project Context
```json
!`npx shadcn@latest info --json`
```
Use the JSON above for framework, aliases, Tailwind version, installed components, and resolved paths. Confirm the `@bklit` registry is configured before adding charts.
## Principles
1. **Install before inventing.** Use `npx shadcn@latest add @bklit/<chart>` — charts are registry components, not hand-rolled SVG.
2. **Compose, don't flatten.** Root chart → `Grid` → series → axes → `ChartTooltip`. See [composition.md](./rules/composition.md).
3. **Theme with tokens.** Use `chartCssVars` and `--chart-*` variables — never hardcode one-off colors. See [theming.md](./rules/theming.md).
4. **Read the doc page first.** Each chart has props, data shape, and examples at `https://ui.bklit.com/docs/components/<slug>`.
5. **Browse variants.** Gallery: `https://ui.bklit.com/charts/<slug>` — Studio: `https://ui.bklit.com/studio?chart=<slug>`.
## Critical Rules
These rules are **always enforced**. Each links to Incorrect/Correct examples.
### Composition → [composition.md](./rules/composition.md)
- **Series and axes live inside the root chart**`LineChart`, `BarChart`, `AreaChart`, etc.
- **One root per chart** — use `ComposedChart` for mixed series types.
- **Grid before series** so lines/bars render above grid lines.
- **`ChartTooltip` as a chart child** — required for crosshair and hover context.
### Theming → [theming.md](./rules/theming.md)
- **Use `chartCssVars`** from `@bklitui/ui/charts` instead of raw `"var(--chart-…)"` strings.
- **Series palette:** `--chart-1``--chart-5` for multi-series charts.
- **Tooltip surfaces:** `bg-popover text-popover-foreground` — avoids white-on-white in light mode.
### Animation → [animation.md](./rules/animation.md)
- **Default duration ~1100ms** for cartesian enter animations unless the doc specifies otherwise.
- **Replay:** change `revealSignature` or remount with a new `key`.
- **Live charts:** use `paused` on `LiveLineChart` to debug without stopping the rAF loop manually.
### Tooltips → [tooltips.md](./rules/tooltips.md)
- **Custom content via `ChartTooltip` `content` prop** or children patterns from docs.
- **`indicatorColor` function** for candlestick / dynamic crosshair colors.
- **Custom indicators:** use `useChart()` — do not track mouse globally outside chart context.
### Installation → [installation.md](./rules/installation.md)
- **Require `@bklit` registry** in `components.json`.
- **Install:** `npx shadcn@latest add @bklit/<slug>`.
- **Let the CLI install peer dependencies** — do not pin `@visx/*` / `motion` manually unless resolving a conflict.
## Chart Catalog
| Slug | Use when | Install | Docs | Gallery |
|------|----------|---------|------|---------|
| `area-chart` | Trends with filled regions under lines | `@bklit/area-chart` | [/docs/components/area-chart](https://ui.bklit.com/docs/components/area-chart) | [/charts/area-chart](https://ui.bklit.com/charts/area-chart) |
| `bar-chart` | Category comparisons, stacked or grouped bars | `@bklit/bar-chart` | [/docs/components/bar-chart](https://ui.bklit.com/docs/components/bar-chart) | [/charts/bar-chart](https://ui.bklit.com/charts/bar-chart) |
| `line-chart` | Time series, multi-line trends, markers | `@bklit/line-chart` | [/docs/components/line-chart](https://ui.bklit.com/docs/components/line-chart) | [/charts/line-chart](https://ui.bklit.com/charts/line-chart) |
| `live-line-chart` | Streaming / real-time data | `@bklit/live-line-chart` | [/docs/components/live-line-chart](https://ui.bklit.com/docs/components/live-line-chart) | [/charts/live-line-chart](https://ui.bklit.com/charts/live-line-chart) |
| `composed-chart` | Mixed bar + line (or similar) on one axis | `@bklit/composed-chart` | [/docs/components/composed-chart](https://ui.bklit.com/docs/components/composed-chart) | [/charts/composed-chart](https://ui.bklit.com/charts/composed-chart) |
| `scatter-chart` | Correlation, distribution, bubble sizing | `@bklit/scatter-chart` | [/docs/components/scatter-chart](https://ui.bklit.com/docs/components/scatter-chart) | [/charts/scatter-chart](https://ui.bklit.com/charts/scatter-chart) |
| `candlestick-chart` | OHLC financial data, brushes | `@bklit/candlestick-chart` | [/docs/components/candlestick-chart](https://ui.bklit.com/docs/components/candlestick-chart) | [/charts/candlestick-chart](https://ui.bklit.com/charts/candlestick-chart) |
| `pie-chart` | Part-to-whole slices | `@bklit/pie-chart` | [/docs/components/pie-chart](https://ui.bklit.com/docs/components/pie-chart) | [/charts/pie-chart](https://ui.bklit.com/charts/pie-chart) |
| `ring-chart` | Donut / ring KPIs | `@bklit/ring-chart` | [/docs/components/ring-chart](https://ui.bklit.com/docs/components/ring-chart) | [/charts/ring-chart](https://ui.bklit.com/charts/ring-chart) |
| `radar-chart` | Multi-axis comparison | `@bklit/radar-chart` | [/docs/components/radar-chart](https://ui.bklit.com/docs/components/radar-chart) | [/charts/radar-chart](https://ui.bklit.com/charts/radar-chart) |
| `gauge-chart` | Single-value KPI dial | `@bklit/gauge-chart` | [/docs/components/gauge-chart](https://ui.bklit.com/docs/components/gauge-chart) | [/charts/gauge-chart](https://ui.bklit.com/charts/gauge-chart) |
| `funnel-chart` | Stage conversion funnels | `@bklit/funnel-chart` | [/docs/components/funnel-chart](https://ui.bklit.com/docs/components/funnel-chart) | [/charts/funnel-chart](https://ui.bklit.com/charts/funnel-chart) |
| `sankey-chart` | Flow between nodes | `@bklit/sankey-chart` | [/docs/components/sankey-chart](https://ui.bklit.com/docs/components/sankey-chart) | [/charts/sankey-chart](https://ui.bklit.com/charts/sankey-chart) |
| `choropleth-chart` | Geo regions colored by value | `@bklit/choropleth-chart` | [/docs/components/choropleth-chart](https://ui.bklit.com/docs/components/choropleth-chart) | [/charts/choropleth-chart](https://ui.bklit.com/charts/choropleth-chart) |
## Workflow
1. Run `npx shadcn@latest info --json` — verify `@bklit` registry and aliases.
2. Pick a chart from the catalog (or ask the user what story the data tells).
3. Open the doc URL for data shape and props.
4. If not installed: `npx shadcn@latest add @bklit/<slug>`.
5. Compose with grid, series, axes, tooltip — apply theming tokens.
6. Point the user to the gallery or Studio URL for variant inspiration.
## Quick Reference
```bash
# Project info
npx shadcn@latest info --json
# Add a chart
npx shadcn@latest add @bklit/line-chart
# Search registries (if configured)
npx shadcn@latest search @bklit
```
```tsx
import { LineChart, Line, Grid, XAxis, ChartTooltip, chartCssVars } from "@bklitui/ui/charts";
<LineChart data={data} xDataKey="date">
<Grid horizontal />
<Line dataKey="users" stroke={chartCssVars.linePrimary} />
<XAxis />
<ChartTooltip />
</LineChart>
```
## Utility docs
- Theming: https://ui.bklit.com/docs/theming
- Grid: https://ui.bklit.com/docs/utility/grid
- Legend: https://ui.bklit.com/docs/utility/legend
- Tooltip: https://ui.bklit.com/docs/utility/tooltip
- Custom indicator: https://ui.bklit.com/docs/utility/custom-indicator
- useChart: https://ui.bklit.com/docs/utility/use-chart
## Detailed References
- [composition.md](./rules/composition.md)
- [theming.md](./rules/theming.md)
- [animation.md](./rules/animation.md)
- [tooltips.md](./rules/tooltips.md)
- [installation.md](./rules/installation.md)

View file

@ -0,0 +1,42 @@
# Animation
## Defaults
Most cartesian charts default to ~1100ms enter animation. Bar charts often use staggered reveals (~1.2s total) with easing `cubic-bezier(0.85, 0, 0.15, 1)`.
## Replay enter animations
Pass a changing `revealSignature` (or remount the chart with a new `key`) to replay mount animations after prop changes.
### Correct
```tsx
const [replayKey, setReplayKey] = useState(0);
<LineChart key={replayKey} data={data} revealSignature={String(replayKey)}>
{/* ... */}
</LineChart>
<button type="button" onClick={() => setReplayKey((k) => k + 1)}>
Replay
</button>
```
## Live charts
- `LiveLineChart` runs its own animation loop — use `paused` to freeze updates for debugging.
- Avoid nesting heavy state updates inside the rAF path; pass stable props where possible.
## Reduced motion
Respect `prefers-reduced-motion` when adding custom animation wrappers around charts. Bklit charts handle reduced motion internally for enter transitions.
## Performance
- Prefer CSS/SVG-friendly props over re-creating large data arrays every frame.
- For streaming data, append points and trim the window instead of replacing the full array when possible.
## Inspiration
Browse animated variants: https://ui.bklit.com/charts/<chart-slug>
Tune motion interactively: https://ui.bklit.com/studio?chart=<chart-slug>

View file

@ -0,0 +1,57 @@
# Chart Composition
## Root + children
Charts use a composable API. Always wrap series, axes, and overlays inside the root chart component.
### Incorrect
```tsx
<div className="h-80">
<Line dataKey="users" />
<XAxis />
</div>
```
### Correct
```tsx
<LineChart data={data}>
<Grid horizontal />
<Line dataKey="users" />
<XAxis />
<ChartTooltip />
</LineChart>
```
## Axes and grid
- Put `Grid` before series so lines render on top.
- Use `XAxis` / `YAxis` as siblings inside the root chart — not outside the chart context.
- For live streaming charts, use `LiveXAxis` and `LiveYAxis` inside `LiveLineChart`.
## Tooltips and markers
- `ChartTooltip` must be a child of the root chart so it can read hover state.
- Custom tooltip content receives chart context — prefer `useChart()` when building custom indicators.
- `ChartMarkers` and marker content render inside `ChartTooltip` when showing event callouts.
## Multi-series charts
- One `Line` / `Bar` / `Area` child per `dataKey`.
- Use `--chart-1``--chart-5` or explicit stroke/fill props for series differentiation.
- For combined line + bar, use `ComposedChart` instead of nesting unrelated roots.
## Data shape
- Cartesian charts: array of objects; set `xDataKey` on the root (default `"date"`).
- OHLC: use `CandlestickChart` with `{ date, open, high, low, close }`.
- Live line: `{ time, value }` points with a separate `value` prop on the root.
- Sankey / funnel / pie: follow each charts doc for node/link or slice shape.
## Docs
- Composition patterns: https://ui.bklit.com/docs/components/line-chart
- `useChart` hook: https://ui.bklit.com/docs/utility/use-chart
- Grid: https://ui.bklit.com/docs/utility/grid
- Legend: https://ui.bklit.com/docs/utility/legend

View file

@ -0,0 +1,74 @@
# Installation
## Prerequisites
Bklit UI is a shadcn registry. Initialize shadcn in the project first:
```bash
npx shadcn@latest init
```
## Registry namespace
Add the `@bklit` namespace to `components.json` if it is not already present:
```json
{
"registries": {
"@bklit": "https://ui.bklit.com/r/{name}.json"
}
}
```
New projects scaffolded with Bklit often include this automatically.
## Install a chart
```bash
npx shadcn@latest add @bklit/line-chart
```
Replace `line-chart` with any chart slug from the catalog. The CLI copies source into your components directory and installs peer dependencies.
## Verify project context
```bash
npx shadcn@latest info --json
```
Use the JSON to confirm framework, aliases, Tailwind version, and which `@bklit` components are already installed.
## Import path
After install, import from the charts entry (path may vary by alias — check `info --json`):
```tsx
import { LineChart, Line, Grid, XAxis, ChartTooltip } from "@/components/ui/line-chart";
// or from package re-exports when using the npm package directly:
import { LineChart, Line, Grid, XAxis, ChartTooltip } from "@bklitui/ui/charts";
```
Prefer the import path your projects shadcn install actually generated.
## Common peer dependencies
| Chart | Typical dependencies |
|-------|---------------------|
| line-chart, area-chart | `@visx/curve`, `@visx/shape`, `motion` |
| bar-chart | `@visx/gradient`, `@visx/pattern`, `@visx/shape`, `motion` |
| candlestick-chart | `@visx/scale`, `@visx/shape`, `@visx/responsive`, `d3-array`, `motion` |
| live-line-chart | `@visx/curve`, `@visx/scale`, `@visx/shape`, `@visx/responsive`, `@visx/event`, `d3-array`, `motion` |
| choropleth-chart | `@visx/geo`, `@visx/responsive`, `@visx/zoom`, `d3-geo`, `topojson-client`, `motion` |
| sankey-chart | `@visx/gradient`, `@visx/pattern`, `@visx/responsive`, `@visx/sankey`, `motion` |
| scatter-chart | `d3-scale`, `d3-array`, `motion`, `react-use-measure` |
| pie-chart, ring-chart | `@visx/responsive`, `@visx/shape`, `motion` |
| radar-chart | `@visx/responsive`, `d3-shape`, `motion` |
| funnel-chart | `motion` |
| gauge-chart | `@visx/responsive`, `motion` |
The CLI installs these when adding a chart — do not guess versions; let `shadcn add` resolve them.
## Docs
- Installation: https://ui.bklit.com/docs/installation
- Per-chart install tabs: https://ui.bklit.com/docs/components/<slug>

View file

@ -0,0 +1,55 @@
# Theming
## Use chartCssVars
Prefer the typed `chartCssVars` export over raw CSS variable strings.
### Incorrect
```tsx
<line stroke="var(--chart-crosshair)" />
<rect fill="var(--chart-indicator-color)" />
```
### Correct
```tsx
import { chartCssVars } from "@bklitui/ui/charts";
<line stroke={chartCssVars.crosshair} />
<rect fill={chartCssVars.indicatorColor} />
```
## Series colors
- Multi-series: `var(--chart-1)` through `var(--chart-5)` or theme tokens.
- Line charts: `var(--chart-line-primary)` / `var(--chart-line-secondary)` for default strokes.
- Do not hardcode hex colors unless the design explicitly requires brand colors outside the theme.
## Tooltip and badge surfaces
Tooltip boxes and live value badges should use shadcn popover tokens so text stays readable in light and dark mode:
```tsx
// Tooltip content / badges
className="bg-popover text-popover-foreground"
```
## Dark mode
Override chart variables in `:root` and `.dark` — do not sprinkle `dark:` on individual chart SVG elements.
```css
:root {
--chart-background: oklch(1 0 0);
--chart-grid: oklch(0.9 0 0);
}
.dark {
--chart-background: oklch(0.145 0.004 285);
--chart-grid: oklch(0.25 0 0);
}
```
## Docs
- Full theming guide: https://ui.bklit.com/docs/theming

View file

@ -0,0 +1,71 @@
# Tooltips
## Default tooltip
Use `ChartTooltip` as a child of the root chart. Defaults include crosshair, dots, and date pill where applicable.
```tsx
<LineChart data={data}>
<Line dataKey="users" />
<XAxis />
<ChartTooltip />
</LineChart>
```
## Custom content
```tsx
<ChartTooltip
content={({ point, formattedDate }) => (
<div className="rounded-md bg-popover px-3 py-2 text-popover-foreground text-sm">
<div className="font-medium">{formattedDate}</div>
<div>{point.users as number} users</div>
</div>
)}
/>
```
## indicatorColor (candlestick and crosshair)
Match the crosshair to the hovered datum — e.g. green/red candles:
```tsx
<ChartTooltip
indicatorColor={(point) =>
(point.close as number) >= (point.open as number)
? "var(--chart-1)"
: "var(--chart-3)"
}
showDots={false}
/>
```
## Custom indicators
For advanced crosshair/markers, use `useChart()` and the patterns in the custom indicator docs.
### Incorrect
```tsx
// Reading mouse position manually outside chart context
const [x, setX] = useState(0);
useEffect(() => {
window.addEventListener("mousemove", ...);
}, []);
```
### Correct
```tsx
import { useChart } from "@bklitui/ui/charts";
function CustomIndicator() {
const { tooltipData } = useChart();
// render from tooltipData
}
```
## Docs
- ChartTooltip: https://ui.bklit.com/docs/utility/tooltip
- Custom indicators: https://ui.bklit.com/docs/utility/custom-indicator

View file

@ -0,0 +1,299 @@
---
name: pr-open
description: |
Open a pull request the bklit-ui way: stage and commit with pre-commit hooks,
run ultracite from the repo root, rebuild the shadcn registry, run a production test
build, fix failures, push, and create a PR with a structured summary. Use when the
user asks to commit, push,
open a PR, "ship it", or run the full pre-PR checklist.
---
# PR Open Skill
End-to-end workflow for committing work and opening a merge-ready PR in **bklit-ui**.
Read this skill when the user wants to add, commit, validate, push, and open a PR — or references `@pr-open`.
---
## Before you start
1. **Confirm branch context**
- If the user merged an earlier PR on this branch and there are new changes, branch from latest `main` instead of stacking on a stale feature branch:
```bash
git fetch origin main
git checkout -b <new-branch-name> origin/main
```
- Re-apply or cherry-pick only the intended changes; do not commit unrelated `registry:build` noise (e.g. reformatted `packages/ui/registry/examples/*` unless those edits are intentional).
2. **Never commit secrets** — skip `.env`, credentials, API keys, etc. Warn the user if they ask to commit them.
3. **Git safety** (required)
- Do not update git config.
- Do not run destructive commands (`push --force`, `reset --hard`) unless the user explicitly asks.
- Do not skip hooks (`--no-verify`) unless the user explicitly asks.
- Do not `git commit --amend` unless all amend rules in the user's git rules are satisfied (HEAD commit is yours, not pushed, or user requested amend).
- If a commit **fails** due to a hook, fix the issue and create a **new** commit — do not amend a failed commit.
- Use HEREDOC for commit messages (see below).
- Do not push unless the user asked to push or open a PR.
---
## Step 1 — Inspect changes
Run in parallel from the repo root:
```bash
git status
git diff
git diff --staged
git log -5 --oneline
```
If opening a PR, also check divergence from base:
```bash
git fetch origin main
git log origin/main..HEAD --oneline
git diff origin/main...HEAD --stat
```
Draft a commit message that explains **why**, not just what. Match recent repo style (e.g. `feat(charts): …`, `fix(web): …`).
---
## Step 2 — Stage and commit (pre-commit loop)
### Stage
```bash
git add <paths> # prefer explicit paths over blind git add -A
```
### Commit
```bash
git commit -m "$(cat <<'EOF'
<subject line>
<optional body 12 sentences on why>
EOF
)"
```
Husky **pre-commit** runs `npx ultracite fix` (see `.husky/pre-commit`).
### If pre-commit fails or leaves unstaged fixes
1. Read the hook output and fix every reported issue (lint correctness, nested ternaries, `biome-ignore` only when justified, etc.).
2. Re-stage affected files: `git add <paths>`
3. Commit again with a **new** commit (or amend only if amend rules allow and the previous commit succeeded but the hook auto-modified files).
Repeat until `git commit` succeeds **and** `git status` is clean (no leftover hook formatting).
---
## Step 3 — Ultracite from repo root
After commits are clean, run the root pnpm scripts (not `npx ultracite` directly unless fixing a one-off):
```bash
pnpm lint # ultracite check
```
If check fails:
```bash
pnpm lint:fix # ultracite fix (same as pnpm format)
```
Then:
1. Review `git diff` for unexpected changes.
2. If files changed: `git add <paths>` → `git commit -m "$(cat <<'EOF'
Fix lint issues from ultracite
EOF
)"`
3. Re-run `pnpm lint` until it passes with no fixes pending.
Do not open a PR while `pnpm lint` fails or while lint fixes are unstaged.
---
## Step 4 — Rebuild shadcn registry (required)
**Always** run this before the production build on every PR — not only when `packages/ui` changed. Registry artifacts under `apps/web/public/r/` must stay in sync with the committed UI package.
From the repo root:
```bash
pnpm registry:build
```
This updates `apps/web/public/r/` from `packages/ui` (see `packages/ui/package.json``registry:build`).
1. Review `git diff` after the build. **Include** intentional changes under `apps/web/public/r/`.
2. **Do not** commit incidental reformats on `packages/ui/registry/examples/*` unless those edits are intentional.
3. If `apps/web/public/r/` (or `packages/ui/registry.json` when you edited it) changed:
```bash
git add apps/web/public/r packages/ui/registry.json
git commit -m "$(cat <<'EOF'
chore(registry): rebuild shadcn registry for PR
EOF
)"
```
4. Re-run **Step 3** (`pnpm lint`) if any source files changed outside `public/r/`.
Do not open a PR while registry outputs are stale (uncommitted `apps/web/public/r/` diffs after `registry:build`).
---
## Step 5 — Test build
Run a production build to catch type and compile errors before the PR.
**Default (whole monorepo):**
```bash
pnpm build
```
**Web app only** (faster when changes are confined to `apps/web`):
```bash
cd apps/web && pnpm build
```
If the web build OOMs in dev/CI-like environments, retry with more heap:
```bash
cd apps/web && NODE_OPTIONS='--max-old-space-size=8192' pnpm build
```
**Optional but recommended** when touching TypeScript:
```bash
pnpm check-types
```
### If build fails
1. Fix the root cause (types, imports, missing registry files, etc.).
2. Re-run the failing command until it passes.
3. Commit fixes with a clear message, then re-run **Step 3** (`pnpm lint`) and **Step 4** (`pnpm registry:build`) if source files under `packages/ui` changed.
---
## Step 6 — Push
Only when the user asked to push or open a PR:
```bash
git push -u origin HEAD
```
If the branch was already pushed and you added commits, `git push` is enough.
---
## Step 7 — Open the PR
Use GitHub CLI from the repo root:
```bash
gh pr create --title "<PR title>" --body "$(cat <<'EOF'
## Summary
- <bullet: what changed and why>
- <bullet: user-facing impact>
- <bullet: follow-ups or deploy notes if any>
## Test plan
- [ ] `pnpm lint` passes at repo root
- [ ] `pnpm registry:build` run; `apps/web/public/r/` committed if updated
- [ ] `pnpm build` (or `apps/web` build for web-only changes)
- [ ] <manual verification step 1>
- [ ] <manual verification step 2>
EOF
)"
```
Return the PR URL to the user.
### PR title guidelines
- Short, imperative, scoped (e.g. `Add @bklit/chart-animation registry item`)
- Match the primary commit subject when possible
### PR body template (copy structure every time)
```markdown
## Summary
- <13 bullets: what and why>
## Test plan
- [ ] `pnpm lint` passes at repo root
- [ ] `pnpm registry:build` run; registry outputs committed if changed
- [ ] Production build succeeds (`pnpm build` or scoped web build)
- [ ] <feature-specific check>
- [ ] <regression check if applicable>
```
Add extra sections only when useful:
- **Context** — link to a merged PR or issue (e.g. "Follow-up to #70")
- **Deploy notes** — e.g. registry JSON must be live on `ui.bklit.com` for Open in v0
### Repo-specific PR notes
- **Registry**: Step 4 always runs `pnpm registry:build`; commit `apps/web/public/r/` when the build updates it.
- **Do not** commit incidental reformats from `registry:build` on `packages/ui/registry/examples/*` unless intentional.
- Chart/docs work: mention manual checks for docs pages, Studio, or Open in v0 when relevant.
---
## Full checklist (quick reference)
| Step | Command / action | Must pass before next step |
|------|------------------|----------------------------|
| 1 | `git status` / `git diff` / `git log` | Understand scope |
| 2 | `git add``git commit` | Pre-commit hook clean; working tree clean |
| 3 | `pnpm lint``pnpm lint:fix` if needed → re-commit | `pnpm lint` exits 0 |
| 4 | `pnpm registry:build` → commit `apps/web/public/r/` if changed | No stale registry diff |
| 5 | `pnpm build` (and `pnpm check-types` if TS changed) | Build exits 0 |
| 6 | `git push -u origin HEAD` | Only if user requested push/PR |
| 7 | `gh pr create` with Summary + Test plan | PR URL returned |
---
## Common failures
| Failure | Fix |
|---------|-----|
| Pre-commit biome errors | Fix code; add `biome-ignore` only with a one-line justification |
| Hook fixed files but commit already succeeded | `git add` + new commit (or amend if allowed) |
| `pnpm lint` fails after commit | `pnpm lint:fix`, review diff, commit, re-run `pnpm lint` |
| `Can't resolve './animation'` in registry consumers | Add missing `@bklit/*` registry deps; run `pnpm registry:build` |
| Web build OOM | `NODE_OPTIONS='--max-old-space-size=8192'` for `apps/web` build |
| PR branch already merged | New branch from `origin/main`, re-apply only new changes |
---
## Example flow
User: "commit this and open a PR"
1. `git status` + `git diff` + `git log -3`
2. `git add apps/web packages/ui``git commit` (fix pre-commit until clean)
3. `pnpm lint``pnpm lint:fix` if needed → commit → `pnpm lint`
4. `pnpm registry:build` → commit `apps/web/public/r/` if changed
5. `pnpm build` (fix until green) → commit if needed
6. `git push -u origin HEAD`
7. `gh pr create` with Summary + Test plan template
8. Reply with PR link and one-line summary of what was validated

View file

@ -0,0 +1,914 @@
---
name: turborepo
description: |
Turborepo monorepo build system guidance. Triggers on: turbo.json, task pipelines,
dependsOn, caching, remote cache, the "turbo" CLI, --filter, --affected, CI optimization, environment
variables, internal packages, monorepo structure/best practices, and boundaries.
Use when user: configures tasks/workflows/pipelines, creates packages, sets up
monorepo, shares code between apps, runs changed/affected packages, debugs cache,
or has apps/packages directories.
metadata:
version: 2.7.6-canary.3
---
# Turborepo Skill
Build system for JavaScript/TypeScript monorepos. Turborepo caches task outputs and runs tasks in parallel based on dependency graph.
## IMPORTANT: Package Tasks, Not Root Tasks
**DO NOT create Root Tasks. ALWAYS create package tasks.**
When creating tasks/scripts/pipelines, you MUST:
1. Add the script to each relevant package's `package.json`
2. Register the task in root `turbo.json`
3. Root `package.json` only delegates via `turbo run <task>`
**DO NOT** put task logic in root `package.json`. This defeats Turborepo's parallelization.
```json
// DO THIS: Scripts in each package
// apps/web/package.json
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
// apps/api/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
// packages/ui/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
```
```json
// turbo.json - register tasks
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"lint": {},
"test": { "dependsOn": ["build"] }
}
}
```
```json
// Root package.json - ONLY delegates, no task logic
{
"scripts": {
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test"
}
}
```
```json
// DO NOT DO THIS - defeats parallelization
// Root package.json
{
"scripts": {
"build": "cd apps/web && next build && cd ../api && tsc",
"lint": "eslint apps/ packages/",
"test": "vitest"
}
}
```
Root Tasks (`//#taskname`) are ONLY for tasks that truly cannot exist in packages (rare).
## Secondary Rule: `turbo run` vs `turbo`
**Always use `turbo run` when the command is written into code:**
```json
// package.json - ALWAYS "turbo run"
{
"scripts": {
"build": "turbo run build"
}
}
```
```yaml
# CI workflows - ALWAYS "turbo run"
- run: turbo run build --affected
```
**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.
## Quick Decision Trees
### "I need to configure a task"
```
Configure a task?
├─ Define task dependencies → references/configuration/tasks.md
├─ Lint/check-types (parallel + caching) → Use Transit Nodes pattern (see below)
├─ Specify build outputs → references/configuration/tasks.md#outputs
├─ Handle environment variables → references/environment/README.md
├─ Set up dev/watch tasks → references/configuration/tasks.md#persistent
├─ Package-specific config → references/configuration/README.md#package-configurations
└─ Global settings (cacheDir, daemon) → references/configuration/global-options.md
```
### "My cache isn't working"
```
Cache problems?
├─ Tasks run but outputs not restored → Missing `outputs` key
├─ Cache misses unexpectedly → references/caching/gotchas.md
├─ Need to debug hash inputs → Use --summarize or --dry
├─ Want to skip cache entirely → Use --force or cache: false
├─ Remote cache not working → references/caching/remote-cache.md
└─ Environment causing misses → references/environment/gotchas.md
```
### "I want to run only changed packages"
```
Run only what changed?
├─ Changed packages + dependents (RECOMMENDED) → turbo run build --affected
├─ Custom base branch → --affected --affected-base=origin/develop
├─ Manual git comparison → --filter=...[origin/main]
└─ See all filter options → references/filtering/README.md
```
**`--affected` is the primary way to run only changed packages.** It automatically compares against the default branch and includes dependents.
### "I want to filter packages"
```
Filter packages?
├─ Only changed packages → --affected (see above)
├─ By package name → --filter=web
├─ By directory → --filter=./apps/*
├─ Package + dependencies → --filter=web...
├─ Package + dependents → --filter=...web
└─ Complex combinations → references/filtering/patterns.md
```
### "Environment variables aren't working"
```
Environment issues?
├─ Vars not available at runtime → Strict mode filtering (default)
├─ Cache hits with wrong env → Var not in `env` key
├─ .env changes not causing rebuilds → .env not in `inputs`
├─ CI variables missing → references/environment/gotchas.md
└─ Framework vars (NEXT_PUBLIC_*) → Auto-included via inference
```
### "I need to set up CI"
```
CI setup?
├─ GitHub Actions → references/ci/github-actions.md
├─ Vercel deployment → references/ci/vercel.md
├─ Remote cache in CI → references/caching/remote-cache.md
├─ Only build changed packages → --affected flag
├─ Skip unnecessary builds → turbo-ignore (references/cli/commands.md)
└─ Skip container setup when no changes → turbo-ignore
```
### "I want to watch for changes during development"
```
Watch mode?
├─ Re-run tasks on change → turbo watch (references/watch/README.md)
├─ Dev servers with dependencies → Use `with` key (references/configuration/tasks.md#with)
├─ Restart dev server on dep change → Use `interruptible: true`
└─ Persistent dev tasks → Use `persistent: true`
```
### "I need to create/structure a package"
```
Package creation/structure?
├─ Create an internal package → references/best-practices/packages.md
├─ Repository structure → references/best-practices/structure.md
├─ Dependency management → references/best-practices/dependencies.md
├─ Best practices overview → references/best-practices/README.md
├─ JIT vs Compiled packages → references/best-practices/packages.md#compilation-strategies
└─ Sharing code between apps → references/best-practices/README.md#package-types
```
### "How should I structure my monorepo?"
```
Monorepo structure?
├─ Standard layout (apps/, packages/) → references/best-practices/README.md
├─ Package types (apps vs libraries) → references/best-practices/README.md#package-types
├─ Creating internal packages → references/best-practices/packages.md
├─ TypeScript configuration → references/best-practices/structure.md#typescript-configuration
├─ ESLint configuration → references/best-practices/structure.md#eslint-configuration
├─ Dependency management → references/best-practices/dependencies.md
└─ Enforce package boundaries → references/boundaries/README.md
```
### "I want to enforce architectural boundaries"
```
Enforce boundaries?
├─ Check for violations → turbo boundaries
├─ Tag packages → references/boundaries/README.md#tags
├─ Restrict which packages can import others → references/boundaries/README.md#rule-types
└─ Prevent cross-package file imports → references/boundaries/README.md
```
## Critical Anti-Patterns
### Using `turbo` Shorthand in Code
**`turbo run` is recommended in package.json scripts and CI pipelines.** The shorthand `turbo <task>` is intended for interactive terminal use.
```json
// WRONG - using shorthand in package.json
{
"scripts": {
"build": "turbo build",
"dev": "turbo dev"
}
}
// CORRECT
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev"
}
}
```
```yaml
# WRONG - using shorthand in CI
- run: turbo build --affected
# CORRECT
- run: turbo run build --affected
```
### Root Scripts Bypassing Turbo
Root `package.json` scripts MUST delegate to `turbo run`, not run tasks directly.
```json
// WRONG - bypasses turbo entirely
{
"scripts": {
"build": "bun build",
"dev": "bun dev"
}
}
// CORRECT - delegates to turbo
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev"
}
}
```
### Using `&&` to Chain Turbo Tasks
Don't chain turbo tasks with `&&`. Let turbo orchestrate.
```json
// WRONG - turbo task not using turbo run
{
"scripts": {
"changeset:publish": "bun build && changeset publish"
}
}
// CORRECT
{
"scripts": {
"changeset:publish": "turbo run build && changeset publish"
}
}
```
### `prebuild` Scripts That Manually Build Dependencies
Scripts like `prebuild` that manually build other packages bypass Turborepo's dependency graph.
```json
// WRONG - manually building dependencies
{
"scripts": {
"prebuild": "cd ../../packages/types && bun run build && cd ../utils && bun run build",
"build": "next build"
}
}
```
**However, the fix depends on whether workspace dependencies are declared:**
1. **If dependencies ARE declared** (e.g., `"@repo/types": "workspace:*"` in package.json), remove the `prebuild` script. Turbo's `dependsOn: ["^build"]` handles this automatically.
2. **If dependencies are NOT declared**, the `prebuild` exists because `^build` won't trigger without a dependency relationship. The fix is to:
- Add the dependency to package.json: `"@repo/types": "workspace:*"`
- Then remove the `prebuild` script
```json
// CORRECT - declare dependency, let turbo handle build order
// package.json
{
"dependencies": {
"@repo/types": "workspace:*",
"@repo/utils": "workspace:*"
},
"scripts": {
"build": "next build"
}
}
// turbo.json
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
```
**Key insight:** `^build` only runs build in packages listed as dependencies. No dependency declaration = no automatic build ordering.
### Overly Broad `globalDependencies`
`globalDependencies` affects ALL tasks in ALL packages. Be specific.
```json
// WRONG - heavy hammer, affects all hashes
{
"globalDependencies": ["**/.env.*local"]
}
// BETTER - move to task-level inputs
{
"globalDependencies": [".env"],
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**"]
}
}
}
```
### Repetitive Task Configuration
Look for repeated configuration across tasks that can be collapsed. Turborepo supports shared configuration patterns.
```json
// WRONG - repetitive env and inputs across tasks
{
"tasks": {
"build": {
"env": ["API_URL", "DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env*"]
},
"test": {
"env": ["API_URL", "DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env*"]
},
"dev": {
"env": ["API_URL", "DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"cache": false,
"persistent": true
}
}
}
// BETTER - use globalEnv and globalDependencies for shared config
{
"globalEnv": ["API_URL", "DATABASE_URL"],
"globalDependencies": [".env*"],
"tasks": {
"build": {},
"test": {},
"dev": {
"cache": false,
"persistent": true
}
}
}
```
**When to use global vs task-level:**
- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
- Task-level `env` / `inputs` - use when only specific tasks need it
### NOT an Anti-Pattern: Large `env` Arrays
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.
### Using `--parallel` Flag
The `--parallel` flag bypasses Turborepo's dependency graph. If tasks need parallel execution, configure `dependsOn` correctly instead.
```bash
# WRONG - bypasses dependency graph
turbo run lint --parallel
# CORRECT - configure tasks to allow parallel execution
# In turbo.json, set dependsOn appropriately (or use transit nodes)
turbo run lint
```
### Package-Specific Task Overrides in Root turbo.json
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.
```json
// WRONG - root turbo.json with many package-specific overrides
{
"tasks": {
"test": { "dependsOn": ["build"] },
"@repo/web#test": { "outputs": ["coverage/**"] },
"@repo/api#test": { "outputs": ["coverage/**"] },
"@repo/utils#test": { "outputs": [] },
"@repo/cli#test": { "outputs": [] },
"@repo/core#test": { "outputs": [] }
}
}
// CORRECT - use Package Configurations
// Root turbo.json - base config only
{
"tasks": {
"test": { "dependsOn": ["build"] }
}
}
// packages/web/turbo.json - package-specific override
{
"extends": ["//"],
"tasks": {
"test": { "outputs": ["coverage/**"] }
}
}
// packages/api/turbo.json
{
"extends": ["//"],
"tasks": {
"test": { "outputs": ["coverage/**"] }
}
}
```
**Benefits of Package Configurations:**
- Keeps configuration close to the code it affects
- Root turbo.json stays clean and focused on base patterns
- Easier to understand what's special about each package
- Works with `$TURBO_EXTENDS$` to inherit + extend arrays
**When to use `package#task` in root:**
- Single package needs a unique dependency (e.g., `"deploy": { "dependsOn": ["web#build"] }`)
- Temporary override while migrating
See `references/configuration/README.md#package-configurations` for full details.
### Using `../` to Traverse Out of Package in `inputs`
Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
```json
// WRONG - traversing out of package
{
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
}
}
}
// CORRECT - use $TURBO_ROOT$ for repo root
{
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
}
}
}
```
### Missing `outputs` for File-Producing Tasks
**Before flagging missing `outputs`, check what the task actually produces:**
1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
2. Determine if it writes files to disk or only outputs to stdout
3. Only flag if the task produces files that should be cached
```json
// WRONG: build produces files but they're not cached
{
"tasks": {
"build": {
"dependsOn": ["^build"]
}
}
}
// CORRECT: build outputs are cached
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
}
}
```
Common outputs by framework:
- Next.js: `[".next/**", "!.next/cache/**"]`
- Vite/Rollup: `["dist/**"]`
- tsc: `["dist/**"]` or custom `outDir`
**TypeScript `--noEmit` can still produce cache files:**
When `incremental: true` in tsconfig.json, `tsc --noEmit` writes `.tsbuildinfo` files even without emitting JS. Check the tsconfig before assuming no outputs:
```json
// If tsconfig has incremental: true, tsc --noEmit produces cache files
{
"tasks": {
"typecheck": {
"outputs": ["node_modules/.cache/tsbuildinfo.json"] // or wherever tsBuildInfoFile points
}
}
}
```
To determine correct outputs for TypeScript tasks:
1. Check if `incremental` or `composite` is enabled in tsconfig
2. Check `tsBuildInfoFile` for custom cache location (default: alongside `outDir` or in project root)
3. If no incremental mode, `tsc --noEmit` produces no files
### `^build` vs `build` Confusion
```json
{
"tasks": {
// ^build = run build in DEPENDENCIES first (other packages this one imports)
"build": {
"dependsOn": ["^build"]
},
// build (no ^) = run build in SAME PACKAGE first
"test": {
"dependsOn": ["build"]
},
// pkg#task = specific package's task
"deploy": {
"dependsOn": ["web#build"]
}
}
}
```
### Environment Variables Not Hashed
```json
// WRONG: API_URL changes won't cause rebuilds
{
"tasks": {
"build": {
"outputs": ["dist/**"]
}
}
}
// CORRECT: API_URL changes invalidate cache
{
"tasks": {
"build": {
"outputs": ["dist/**"],
"env": ["API_URL", "API_KEY"]
}
}
}
```
### `.env` Files Not in Inputs
Turbo does NOT load `.env` files - your framework does. But Turbo needs to know about changes:
```json
// WRONG: .env changes don't invalidate cache
{
"tasks": {
"build": {
"env": ["API_URL"]
}
}
}
// CORRECT: .env file changes invalidate cache
{
"tasks": {
"build": {
"env": ["API_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.*"]
}
}
}
```
### Root `.env` File in Monorepo
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.
```
// WRONG - root .env affects all packages implicitly
my-monorepo/
├── .env # Which packages use this?
├── apps/
│ ├── web/
│ └── api/
└── packages/
// CORRECT - .env files in packages that need them
my-monorepo/
├── apps/
│ ├── web/
│ │ └── .env # Clear: web needs DATABASE_URL
│ └── api/
│ └── .env # Clear: api needs API_KEY
└── packages/
```
**Problems with root `.env`:**
- Unclear which packages consume which variables
- All packages get all variables (even ones they don't need)
- Cache invalidation is coarse-grained (root .env change invalidates everything)
- Security risk: packages may accidentally access sensitive vars meant for others
- Bad habits start small — starter templates should model correct patterns
**If you must share variables**, use `globalEnv` to be explicit about what's shared, and document why.
### Strict Mode Filtering CI Variables
By default, Turborepo filters environment variables to only those in `env`/`globalEnv`. CI variables may be missing:
```json
// If CI scripts need GITHUB_TOKEN but it's not in env:
{
"globalPassThroughEnv": ["GITHUB_TOKEN", "CI"],
"tasks": { ... }
}
```
Or use `--env-mode=loose` (not recommended for production).
### Shared Code in Apps (Should Be a Package)
```
// WRONG: Shared code inside an app
apps/
web/
shared/ # This breaks monorepo principles!
utils.ts
// CORRECT: Extract to a package
packages/
utils/
src/utils.ts
```
### Accessing Files Across Package Boundaries
```typescript
// WRONG: Reaching into another package's internals
import { Button } from "../../packages/ui/src/button";
// CORRECT: Install and import properly
import { Button } from "@repo/ui/button";
```
### Too Many Root Dependencies
```json
// WRONG: App dependencies in root
{
"dependencies": {
"react": "^18",
"next": "^14"
}
}
// CORRECT: Only repo tools in root
{
"devDependencies": {
"turbo": "latest"
}
}
```
## Common Task Configurations
### Standard Build Pipeline
```json
{
"$schema": "https://turborepo.dev/schema.v2.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
```
Add a `transit` task if you have tasks that need parallel execution with cache invalidation (see below).
### Dev Task with `^dev` Pattern (for `turbo watch`)
A `dev` task with `dependsOn: ["^dev"]` and `persistent: false` in root turbo.json may look unusual but is **correct for `turbo watch` workflows**:
```json
// Root turbo.json
{
"tasks": {
"dev": {
"dependsOn": ["^dev"],
"cache": false,
"persistent": false // Packages have one-shot dev scripts
}
}
}
// Package turbo.json (apps/web/turbo.json)
{
"extends": ["//"],
"tasks": {
"dev": {
"persistent": true // Apps run long-running dev servers
}
}
}
```
**Why this works:**
- **Packages** (e.g., `@acme/db`, `@acme/validators`) have `"dev": "tsc"` — one-shot type generation that completes quickly
- **Apps** override with `persistent: true` for actual dev servers (Next.js, etc.)
- **`turbo watch`** re-runs the one-shot package `dev` scripts when source files change, keeping types in sync
**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.
**Alternative pattern:** Use a separate task name like `prepare` or `generate` for one-shot dependency builds to make the intent clearer:
```json
{
"tasks": {
"prepare": {
"dependsOn": ["^prepare"],
"outputs": ["dist/**"]
},
"dev": {
"dependsOn": ["prepare"],
"cache": false,
"persistent": true
}
}
}
```
### Transit Nodes for Parallel Tasks with Cache Invalidation
Some tasks can run in parallel (don't need built output from dependencies) but must invalidate cache when dependency source code changes.
**The problem with `dependsOn: ["^taskname"]`:**
- Forces sequential execution (slow)
**The problem with `dependsOn: []` (no dependencies):**
- Allows parallel execution (fast)
- But cache is INCORRECT - changing dependency source won't invalidate cache
**Transit Nodes solve both:**
```json
{
"tasks": {
"transit": { "dependsOn": ["^transit"] },
"my-task": { "dependsOn": ["transit"] }
}
}
```
The `transit` task creates dependency relationships without matching any actual script, so tasks run in parallel with correct cache invalidation.
**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
### With Environment Variables
```json
{
"globalEnv": ["NODE_ENV"],
"globalDependencies": [".env"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"env": ["API_URL", "DATABASE_URL"]
}
}
}
```
## Reference Index
### Configuration
| File | Purpose |
| ------------------------------------------------------------------------------- | -------------------------------------------------------- |
| [configuration/README.md](./references/configuration/README.md) | turbo.json overview, Package Configurations |
| [configuration/tasks.md](./references/configuration/tasks.md) | dependsOn, outputs, inputs, env, cache, persistent |
| [configuration/global-options.md](./references/configuration/global-options.md) | globalEnv, globalDependencies, cacheDir, daemon, envMode |
| [configuration/gotchas.md](./references/configuration/gotchas.md) | Common configuration mistakes |
### Caching
| File | Purpose |
| --------------------------------------------------------------- | -------------------------------------------- |
| [caching/README.md](./references/caching/README.md) | How caching works, hash inputs |
| [caching/remote-cache.md](./references/caching/remote-cache.md) | Vercel Remote Cache, self-hosted, login/link |
| [caching/gotchas.md](./references/caching/gotchas.md) | Debugging cache misses, --summarize, --dry |
### Environment Variables
| File | Purpose |
| ------------------------------------------------------------- | ----------------------------------------- |
| [environment/README.md](./references/environment/README.md) | env, globalEnv, passThroughEnv |
| [environment/modes.md](./references/environment/modes.md) | Strict vs Loose mode, framework inference |
| [environment/gotchas.md](./references/environment/gotchas.md) | .env files, CI issues |
### Filtering
| File | Purpose |
| ----------------------------------------------------------- | ------------------------ |
| [filtering/README.md](./references/filtering/README.md) | --filter syntax overview |
| [filtering/patterns.md](./references/filtering/patterns.md) | Common filter patterns |
### CI/CD
| File | Purpose |
| --------------------------------------------------------- | ------------------------------- |
| [ci/README.md](./references/ci/README.md) | General CI principles |
| [ci/github-actions.md](./references/ci/github-actions.md) | Complete GitHub Actions setup |
| [ci/vercel.md](./references/ci/vercel.md) | Vercel deployment, turbo-ignore |
| [ci/patterns.md](./references/ci/patterns.md) | --affected, caching strategies |
### CLI
| File | Purpose |
| ----------------------------------------------- | --------------------------------------------- |
| [cli/README.md](./references/cli/README.md) | turbo run basics |
| [cli/commands.md](./references/cli/commands.md) | turbo run flags, turbo-ignore, other commands |
### Best Practices
| File | Purpose |
| ----------------------------------------------------------------------------- | --------------------------------------------------------------- |
| [best-practices/README.md](./references/best-practices/README.md) | Monorepo best practices overview |
| [best-practices/structure.md](./references/best-practices/structure.md) | Repository structure, workspace config, TypeScript/ESLint setup |
| [best-practices/packages.md](./references/best-practices/packages.md) | Creating internal packages, JIT vs Compiled, exports |
| [best-practices/dependencies.md](./references/best-practices/dependencies.md) | Dependency management, installing, version sync |
### Watch Mode
| File | Purpose |
| ----------------------------------------------- | ----------------------------------------------- |
| [watch/README.md](./references/watch/README.md) | turbo watch, interruptible tasks, dev workflows |
### Boundaries (Experimental)
| File | Purpose |
| --------------------------------------------------------- | ----------------------------------------------------- |
| [boundaries/README.md](./references/boundaries/README.md) | Enforce package isolation, tag-based dependency rules |
## Source Documentation
This skill is based on the official Turborepo documentation at:
- Source: `docs/site/content/docs/` in the Turborepo repository
- Live: https://turborepo.dev/docs

View file

@ -0,0 +1,70 @@
---
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.
---
Load the Turborepo skill and help with monorepo task orchestration: creating workflows, configuring tasks, setting up pipelines, and optimizing builds.
## Workflow
### Step 1: Load turborepo skill
```
skill({ name: 'turborepo' })
```
### Step 2: Identify task type from user request
Analyze $ARGUMENTS to determine:
- **Topic**: configuration, caching, filtering, environment, CI, or CLI
- **Task type**: new setup, debugging, optimization, or implementation
Use decision trees in SKILL.md to select the relevant reference files.
### Step 3: Read relevant reference files
Based on task type, read from `references/<topic>/`:
| Task | Files to Read |
| -------------------- | --------------------------------------------------------- |
| Configure turbo.json | `configuration/README.md` + `configuration/tasks.md` |
| Debug cache issues | `caching/gotchas.md` |
| Set up remote cache | `caching/remote-cache.md` |
| Filter packages | `filtering/README.md` + `filtering/patterns.md` |
| Environment problems | `environment/gotchas.md` + `environment/modes.md` |
| Set up CI | `ci/README.md` + `ci/github-actions.md` or `ci/vercel.md` |
| CLI usage | `cli/commands.md` |
### Step 4: Execute task
Apply Turborepo-specific patterns from references to complete the user's request.
**CRITICAL - When creating tasks/scripts/pipelines:**
1. **DO NOT create Root Tasks** - Always create package tasks
2. Add scripts to each relevant package's `package.json` (e.g., `apps/web/package.json`, `packages/ui/package.json`)
3. Register the task in root `turbo.json`
4. Root `package.json` only contains `turbo run <task>` - never actual task logic
**Other things to verify:**
- `outputs` defined for cacheable tasks
- `dependsOn` uses correct syntax (`^task` vs `task`)
- Environment variables in `env` key
- `.env` files in `inputs` if used
- Use `turbo run` (not `turbo`) in package.json and CI
### Step 5: Summarize
```
=== Turborepo Task Complete ===
Topic: <configuration|caching|filtering|environment|ci|cli>
Files referenced: <reference files consulted>
<brief summary of what was done>
```
<user-request>
$ARGUMENTS
</user-request>

View file

@ -0,0 +1,246 @@
# Dependency Management
Best practices for managing dependencies in a Turborepo monorepo.
## Core Principle: Install Where Used
Dependencies belong in the package that uses them, not the root.
```bash
# Good: Install in specific package
pnpm add react --filter=@repo/ui
pnpm add next --filter=web
# Avoid: Installing in root
pnpm add react -w # Only for repo-level tools!
```
## Benefits of Local Installation
### 1. Clarity
Each package's `package.json` lists exactly what it needs:
```json
// packages/ui/package.json
{
"dependencies": {
"react": "^18.0.0",
"class-variance-authority": "^0.7.0"
}
}
```
### 2. Flexibility
Different packages can use different versions when needed:
```json
// packages/legacy-ui/package.json
{ "dependencies": { "react": "^17.0.0" } }
// packages/ui/package.json
{ "dependencies": { "react": "^18.0.0" } }
```
### 3. Better Caching
Installing in root changes workspace lockfile, invalidating all caches.
### 4. Pruning Support
`turbo prune` can remove unused dependencies for Docker images.
## What Belongs in Root
Only repository-level tools:
```json
// Root package.json
{
"devDependencies": {
"turbo": "latest",
"husky": "^8.0.0",
"lint-staged": "^15.0.0"
}
}
```
**NOT** application dependencies:
- react, next, express
- lodash, axios, zod
- Testing libraries (unless truly repo-wide)
## Installing Dependencies
### Single Package
```bash
# pnpm
pnpm add lodash --filter=@repo/utils
# npm
npm install lodash --workspace=@repo/utils
# yarn
yarn workspace @repo/utils add lodash
# bun
cd packages/utils && bun add lodash
```
### Multiple Packages
```bash
# pnpm
pnpm add jest --save-dev --filter=web --filter=@repo/ui
# npm
npm install jest --save-dev --workspace=web --workspace=@repo/ui
# yarn (v2+)
yarn workspaces foreach -R --from '{web,@repo/ui}' add jest --dev
```
### Internal Packages
```bash
# pnpm
pnpm add @repo/ui --filter=web
# This updates package.json:
{
"dependencies": {
"@repo/ui": "workspace:*"
}
}
```
## Keeping Versions in Sync
### Option 1: Tooling
```bash
# syncpack - Check and fix version mismatches
npx syncpack list-mismatches
npx syncpack fix-mismatches
# manypkg - Similar functionality
npx @manypkg/cli check
npx @manypkg/cli fix
# sherif - Rust-based, very fast
npx sherif
```
### Option 2: Package Manager Commands
```bash
# pnpm - Update everywhere
pnpm up --recursive typescript@latest
# npm - Update in all workspaces
npm install typescript@latest --workspaces
```
### Option 3: pnpm Catalogs (pnpm 9.5+)
```yaml
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
catalog:
react: ^18.2.0
typescript: ^5.3.0
```
```json
// Any package.json
{
"dependencies": {
"react": "catalog:" // Uses version from catalog
}
}
```
## Internal vs External Dependencies
### Internal (Workspace)
```json
// pnpm/bun
{ "@repo/ui": "workspace:*" }
// npm/yarn
{ "@repo/ui": "*" }
```
Turborepo understands these relationships and orders builds accordingly.
### External (npm Registry)
```json
{ "lodash": "^4.17.21" }
```
Standard semver versioning from npm.
## Peer Dependencies
For library packages that expect the consumer to provide dependencies:
```json
// packages/ui/package.json
{
"peerDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
},
"devDependencies": {
"react": "^18.0.0", // For development/testing
"react-dom": "^18.0.0"
}
}
```
## Common Issues
### "Module not found"
1. Check the dependency is installed in the right package
2. Run `pnpm install` / `npm install` to update lockfile
3. Check exports are defined in the package
### Version Conflicts
Packages can use different versions - this is a feature, not a bug. But if you need consistency:
1. Use tooling (syncpack, manypkg)
2. Use pnpm catalogs
3. Create a lint rule
### Hoisting Issues
Some tools expect dependencies in specific locations. Use package manager config:
```yaml
# .npmrc (pnpm)
public-hoist-pattern[]=*eslint*
public-hoist-pattern[]=*prettier*
```
## Lockfile
**Required** for:
- Reproducible builds
- Turborepo dependency analysis
- Cache correctness
```bash
# Commit your lockfile!
git add pnpm-lock.yaml # or package-lock.json, yarn.lock
```

View file

@ -0,0 +1,335 @@
# Creating Internal Packages
How to create and structure internal packages in your monorepo.
## Package Creation Checklist
1. Create directory in `packages/`
2. Add `package.json` with name and exports
3. Add source code in `src/`
4. Add `tsconfig.json` if using TypeScript
5. Install as dependency in consuming packages
6. Run package manager install to update lockfile
## Package Compilation Strategies
### Just-in-Time (JIT)
Export TypeScript directly. The consuming app's bundler compiles it.
```json
// packages/ui/package.json
{
"name": "@repo/ui",
"exports": {
"./button": "./src/button.tsx",
"./card": "./src/card.tsx"
},
"scripts": {
"lint": "eslint .",
"check-types": "tsc --noEmit"
}
}
```
**When to use:**
- Apps use modern bundlers (Turbopack, webpack, Vite)
- You want minimal configuration
- Build times are acceptable without caching
**Limitations:**
- No Turborepo cache for the package itself
- Consumer must support TypeScript compilation
- Can't use TypeScript `paths` (use Node.js subpath imports instead)
### Compiled
Package handles its own compilation.
```json
// packages/ui/package.json
{
"name": "@repo/ui",
"exports": {
"./button": {
"types": "./src/button.tsx",
"default": "./dist/button.js"
}
},
"scripts": {
"build": "tsc",
"dev": "tsc --watch"
}
}
```
```json
// packages/ui/tsconfig.json
{
"extends": "@repo/typescript-config/library.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
```
**When to use:**
- You want Turborepo to cache builds
- Package will be used by non-bundler tools
- You need maximum compatibility
**Remember:** Add `dist/**` to turbo.json outputs!
## Defining Exports
### Multiple Entrypoints
```json
{
"exports": {
".": "./src/index.ts", // @repo/ui
"./button": "./src/button.tsx", // @repo/ui/button
"./card": "./src/card.tsx", // @repo/ui/card
"./hooks": "./src/hooks/index.ts" // @repo/ui/hooks
}
}
```
### Conditional Exports (Compiled)
```json
{
"exports": {
"./button": {
"types": "./src/button.tsx",
"import": "./dist/button.mjs",
"require": "./dist/button.cjs",
"default": "./dist/button.js"
}
}
}
```
## Installing Internal Packages
### Add to Consuming Package
```json
// apps/web/package.json
{
"dependencies": {
"@repo/ui": "workspace:*" // pnpm/bun
// "@repo/ui": "*" // npm/yarn
}
}
```
### Run Install
```bash
pnpm install # Updates lockfile with new dependency
```
### Import and Use
```typescript
// apps/web/src/page.tsx
import { Button } from '@repo/ui/button';
export default function Page() {
return <Button>Click me</Button>;
}
```
## One Purpose Per Package
### Good Examples
```
packages/
├── ui/ # Shared UI components
├── utils/ # General utilities
├── auth/ # Authentication logic
├── database/ # Database client/schemas
├── eslint-config/ # ESLint configuration
├── typescript-config/ # TypeScript configuration
└── api-client/ # Generated API client
```
### Avoid Mega-Packages
```
// BAD: One package for everything
packages/
└── shared/
├── components/
├── utils/
├── hooks/
├── types/
└── api/
// GOOD: Separate by purpose
packages/
├── ui/ # Components
├── utils/ # Utilities
├── hooks/ # React hooks
├── types/ # Shared TypeScript types
└── api-client/ # API utilities
```
## Config Packages
### TypeScript Config
```json
// packages/typescript-config/package.json
{
"name": "@repo/typescript-config",
"exports": {
"./base.json": "./base.json",
"./nextjs.json": "./nextjs.json",
"./library.json": "./library.json"
}
}
```
### ESLint Config
```json
// packages/eslint-config/package.json
{
"name": "@repo/eslint-config",
"exports": {
"./base": "./base.js",
"./next": "./next.js"
},
"dependencies": {
"eslint": "^8.0.0",
"eslint-config-next": "latest"
}
}
```
## Common Mistakes
### Forgetting to Export
```json
// BAD: No exports defined
{
"name": "@repo/ui"
}
// GOOD: Clear exports
{
"name": "@repo/ui",
"exports": {
"./button": "./src/button.tsx"
}
}
```
### Wrong Workspace Syntax
```json
// pnpm/bun
{ "@repo/ui": "workspace:*" } // Correct
// npm/yarn
{ "@repo/ui": "*" } // Correct
{ "@repo/ui": "workspace:*" } // Wrong for npm/yarn!
```
### Missing from turbo.json Outputs
```json
// Package builds to dist/, but turbo.json doesn't know
{
"tasks": {
"build": {
"outputs": [".next/**"] // Missing dist/**!
}
}
}
// Correct
{
"tasks": {
"build": {
"outputs": [".next/**", "dist/**"]
}
}
}
```
## TypeScript Best Practices
### Use Node.js Subpath Imports (Not `paths`)
TypeScript `compilerOptions.paths` breaks with JIT packages. Use Node.js subpath imports instead (TypeScript 5.4+).
**JIT Package:**
```json
// packages/ui/package.json
{
"imports": {
"#*": "./src/*"
}
}
```
```typescript
// packages/ui/button.tsx
import { MY_STRING } from "#utils.ts"; // Uses .ts extension
```
**Compiled Package:**
```json
// packages/ui/package.json
{
"imports": {
"#*": "./dist/*"
}
}
```
```typescript
// packages/ui/button.tsx
import { MY_STRING } from "#utils.js"; // Uses .js extension
```
### Use `tsc` for Internal Packages
For internal packages, prefer `tsc` over bundlers. Bundlers can mangle code before it reaches your app's bundler, causing hard-to-debug issues.
### Enable Go-to-Definition
For Compiled Packages, enable declaration maps:
```json
// tsconfig.json
{
"compilerOptions": {
"declaration": true,
"declarationMap": true
}
}
```
This creates `.d.ts` and `.d.ts.map` files for IDE navigation.
### No Root tsconfig.json Needed
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.
### Avoid TypeScript Project References
They add complexity and another caching layer. Turborepo handles dependencies better.

View file

@ -0,0 +1,269 @@
# Repository Structure
Detailed guidance on structuring a Turborepo monorepo.
## Workspace Configuration
### pnpm (Recommended)
```yaml
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
```
### npm/yarn/bun
```json
// package.json
{
"workspaces": ["apps/*", "packages/*"]
}
```
## Root package.json
```json
{
"name": "my-monorepo",
"private": true,
"packageManager": "pnpm@9.0.0",
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev",
"lint": "turbo run lint",
"test": "turbo run test"
},
"devDependencies": {
"turbo": "latest"
}
}
```
Key points:
- `private: true` - Prevents accidental publishing
- `packageManager` - Enforces consistent package manager version
- **Scripts only delegate to `turbo run`** - No actual build logic here!
- Minimal devDependencies (just turbo and repo tools)
## Always Prefer Package Tasks
**Always use package tasks. Only use Root Tasks if you cannot succeed with package tasks.**
```json
// packages/web/package.json
{
"scripts": {
"build": "next build",
"lint": "eslint .",
"test": "vitest",
"typecheck": "tsc --noEmit"
}
}
// packages/api/package.json
{
"scripts": {
"build": "tsc",
"lint": "eslint .",
"test": "vitest",
"typecheck": "tsc --noEmit"
}
}
```
Package tasks enable Turborepo to:
1. **Parallelize** - Run `web#lint` and `api#lint` simultaneously
2. **Cache individually** - Each package's task output is cached separately
3. **Filter precisely** - Run `turbo run test --filter=web` for just one package
**Root Tasks are a fallback** for tasks that truly cannot run per-package:
```json
// AVOID unless necessary - sequential, not parallelized, can't filter
{
"scripts": {
"lint": "eslint apps/web && eslint apps/api && eslint packages/ui"
}
}
```
## Root turbo.json
```json
{
"$schema": "https://turborepo.dev/schema.v2.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {},
"test": {
"dependsOn": ["build"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
```
## Directory Organization
### Grouping Packages
You can group packages by adding more workspace paths:
```yaml
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
- "packages/config/*" # Grouped configs
- "packages/features/*" # Feature packages
```
This allows:
```
packages/
├── ui/
├── utils/
├── config/
│ ├── eslint/
│ ├── typescript/
│ └── tailwind/
└── features/
├── auth/
└── payments/
```
### What NOT to Do
```yaml
# BAD: Nested wildcards cause ambiguous behavior
packages:
- "packages/**" # Don't do this!
```
## Package Anatomy
### Minimum Required Files
```
packages/ui/
├── package.json # Required: Makes it a package
├── src/ # Source code
│ └── button.tsx
└── tsconfig.json # TypeScript config (if using TS)
```
### package.json Requirements
```json
{
"name": "@repo/ui", // Unique, namespaced name
"version": "0.0.0", // Version (can be 0.0.0 for internal)
"private": true, // Prevents accidental publishing
"exports": { // Entry points
"./button": "./src/button.tsx"
}
}
```
## TypeScript Configuration
### Shared Base Config
Create a shared TypeScript config package:
```
packages/
└── typescript-config/
├── package.json
├── base.json
├── nextjs.json
└── library.json
```
```json
// packages/typescript-config/base.json
{
"compilerOptions": {
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"moduleResolution": "bundler",
"module": "ESNext",
"target": "ES2022"
}
}
```
### Extending in Packages
```json
// packages/ui/tsconfig.json
{
"extends": "@repo/typescript-config/library.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
```
### No Root tsconfig.json
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.
## ESLint Configuration
### Shared Config Package
```
packages/
└── eslint-config/
├── package.json
├── base.js
├── next.js
└── library.js
```
```json
// packages/eslint-config/package.json
{
"name": "@repo/eslint-config",
"exports": {
"./base": "./base.js",
"./next": "./next.js",
"./library": "./library.js"
}
}
```
### Using in Packages
```js
// apps/web/.eslintrc.js
module.exports = {
extends: ["@repo/eslint-config/next"],
};
```
## Lockfile
A lockfile is **required** for:
- Reproducible builds
- Turborepo to understand package dependencies
- Cache correctness
Without a lockfile, you'll see unpredictable behavior.

View file

@ -0,0 +1,169 @@
# Debugging Cache Issues
## Diagnostic Tools
### `--summarize`
Generates a JSON file with all hash inputs. Compare two runs to find differences.
```bash
turbo build --summarize
# Creates .turbo/runs/<run-id>.json
```
The summary includes:
- Global hash and its inputs
- Per-task hashes and their inputs
- Environment variables that affected the hash
**Comparing runs:**
```bash
# Run twice, compare the summaries
diff .turbo/runs/<first-run>.json .turbo/runs/<second-run>.json
```
### `--dry` / `--dry=json`
See what would run without executing anything:
```bash
turbo build --dry
turbo build --dry=json # machine-readable output
```
Shows cache status for each task without running them.
### `--force`
Skip reading cache, re-execute all tasks:
```bash
turbo build --force
```
Useful to verify tasks actually work (not just cached results).
## Unexpected Cache Misses
**Symptom:** Task runs when you expected a cache hit.
### Environment Variable Changed
Check if an env var in the `env` key changed:
```json
{
"tasks": {
"build": {
"env": ["API_URL", "NODE_ENV"]
}
}
}
```
Different `API_URL` between runs = cache miss.
### .env File Changed
`.env` files aren't tracked by default. Add to `inputs`:
```json
{
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.local"]
}
}
}
```
Or use `globalDependencies` for repo-wide env files:
```json
{
"globalDependencies": [".env"]
}
```
### Lockfile Changed
Installing/updating packages changes the global hash.
### Source Files Changed
Any file in the package (or in `inputs`) triggers a miss.
### turbo.json Changed
Config changes invalidate the global hash.
## Incorrect Cache Hits
**Symptom:** Cached output is stale/wrong.
### Missing Environment Variable
Task uses an env var not listed in `env`:
```javascript
// build.js
const apiUrl = process.env.API_URL; // not tracked!
```
Fix: add to task config:
```json
{
"tasks": {
"build": {
"env": ["API_URL"]
}
}
}
```
### Missing File in Inputs
Task reads a file outside default inputs:
```json
{
"tasks": {
"build": {
"inputs": [
"$TURBO_DEFAULT$",
"../../shared-config.json" // file outside package
]
}
}
}
```
## Useful Flags
```bash
# Only show output for cache misses
turbo build --output-logs=new-only
# Show output for everything (debugging)
turbo build --output-logs=full
# See why tasks are running
turbo build --verbosity=2
```
## Quick Checklist
Cache miss when expected hit:
1. Run with `--summarize`, compare with previous run
2. Check env vars with `--dry=json`
3. Look for lockfile/config changes in git
Cache hit when expected miss:
1. Verify env var is in `env` array
2. Verify file is in `inputs` array
3. Check if file is outside package directory

View file

@ -0,0 +1,127 @@
# Remote Caching
Share cache artifacts across your team and CI pipelines.
## Benefits
- Team members get cache hits from each other's work
- CI gets cache hits from local development (and vice versa)
- Dramatically faster CI runs after first build
- No more "works on my machine" rebuilds
## Vercel Remote Cache
Free, zero-config when deploying on Vercel. For local dev and other CI:
### Local Development Setup
```bash
# Authenticate with Vercel
npx turbo login
# Link repo to your Vercel team
npx turbo link
```
This creates `.turbo/config.json` with your team info (gitignored by default).
### CI Setup
Set these environment variables:
```bash
TURBO_TOKEN=<your-token>
TURBO_TEAM=<your-team-slug>
```
Get your token from Vercel dashboard → Settings → Tokens.
**GitHub Actions example:**
```yaml
- name: Build
run: npx turbo build
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
```
## Configuration in turbo.json
```json
{
"remoteCache": {
"enabled": true,
"signature": false
}
}
```
Options:
- `enabled`: toggle remote cache (default: true when authenticated)
- `signature`: require artifact signing (default: false)
## Artifact Signing
Verify cache artifacts haven't been tampered with:
```bash
# Set a secret key (use same key across all environments)
export TURBO_REMOTE_CACHE_SIGNATURE_KEY="your-secret-key"
```
Enable in config:
```json
{
"remoteCache": {
"signature": true
}
}
```
Signed artifacts can only be restored if the signature matches.
## Self-Hosted Options
Community implementations for running your own cache server:
- **turbo-remote-cache** (Node.js) - supports S3, GCS, Azure
- **turborepo-remote-cache** (Go) - lightweight, S3-compatible
- **ducktape** (Rust) - high-performance option
Configure with environment variables:
```bash
TURBO_API=https://your-cache-server.com
TURBO_TOKEN=your-auth-token
TURBO_TEAM=your-team
```
## Cache Behavior Control
```bash
# Disable remote cache for a run
turbo build --remote-cache-read-only # read but don't write
turbo build --no-cache # skip cache entirely
# Environment variable alternative
TURBO_REMOTE_ONLY=true # only use remote, skip local
```
## Debugging Remote Cache
```bash
# Verbose output shows cache operations
turbo build --verbosity=2
# Check if remote cache is configured
turbo config
```
Look for:
- "Remote caching enabled" in output
- Upload/download messages during runs
- "cache hit, replaying output" with remote cache indicator

View file

@ -0,0 +1,162 @@
# GitHub Actions
Complete setup guide for Turborepo with GitHub Actions.
## Basic Workflow Structure
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Build and Test
run: turbo run build test lint
```
## Package Manager Setup
### pnpm
```yaml
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
```
### Yarn
```yaml
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'yarn'
- run: yarn install --frozen-lockfile
```
### Bun
```yaml
- uses: oven-sh/setup-bun@v1
with:
bun-version: latest
- run: bun install --frozen-lockfile
```
## Remote Cache Setup
### 1. Create Vercel Access Token
1. Go to [Vercel Dashboard](https://vercel.com/account/tokens)
2. Create a new token with appropriate scope
3. Copy the token value
### 2. Add Secrets and Variables
In your GitHub repository settings:
**Secrets** (Settings > Secrets and variables > Actions > Secrets):
- `TURBO_TOKEN`: Your Vercel access token
**Variables** (Settings > Secrets and variables > Actions > Variables):
- `TURBO_TEAM`: Your Vercel team slug
### 3. Add to Workflow
```yaml
jobs:
build:
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
```
## Alternative: actions/cache
If you can't use remote cache, cache Turborepo's local cache directory:
```yaml
- uses: actions/cache@v4
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ hashFiles('**/turbo.json', '**/package-lock.json') }}
restore-keys: |
turbo-${{ runner.os }}-
```
Note: This is less effective than remote cache since it's per-branch.
## Complete Example
```yaml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 2
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build
run: turbo run build --affected
- name: Test
run: turbo run test --affected
- name: Lint
run: turbo run lint --affected
```

View file

@ -0,0 +1,145 @@
# CI Optimization Patterns
Strategies for efficient CI/CD with Turborepo.
## PR vs Main Branch Builds
### PR Builds: Only Affected
Test only what changed in the PR:
```yaml
- name: Test (PR)
if: github.event_name == 'pull_request'
run: turbo run build test --affected
```
### Main Branch: Full Build
Ensure complete validation on merge:
```yaml
- name: Test (Main)
if: github.ref == 'refs/heads/main'
run: turbo run build test
```
## Custom Git Ranges with --filter
For advanced scenarios, use `--filter` with git refs:
```bash
# Changes since specific commit
turbo run test --filter="...[abc123]"
# Changes between refs
turbo run test --filter="...[main...HEAD]"
# Changes in last 3 commits
turbo run test --filter="...[HEAD~3]"
```
## Caching Strategies
### Remote Cache (Recommended)
Best performance - shared across all CI runs and developers:
```yaml
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ vars.TURBO_TEAM }}
```
### actions/cache Fallback
When remote cache isn't available:
```yaml
- uses: actions/cache@v4
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ github.sha }}
restore-keys: |
turbo-${{ runner.os }}-${{ github.ref }}-
turbo-${{ runner.os }}-
```
Limitations:
- Cache is branch-scoped
- PRs restore from base branch cache
- Less efficient than remote cache
## Matrix Builds
Test across Node versions:
```yaml
strategy:
matrix:
node: [18, 20, 22]
steps:
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- run: turbo run test
```
## Parallelizing Across Jobs
Split tasks into separate jobs:
```yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: turbo run lint --affected
test:
runs-on: ubuntu-latest
steps:
- run: turbo run test --affected
build:
runs-on: ubuntu-latest
needs: [lint, test]
steps:
- run: turbo run build
```
### Cache Considerations
When parallelizing:
- Each job has separate cache writes
- Remote cache handles this automatically
- With actions/cache, use unique keys per job to avoid conflicts
```yaml
- uses: actions/cache@v4
with:
path: .turbo
key: turbo-${{ runner.os }}-${{ github.job }}-${{ github.sha }}
```
## Conditional Tasks
Skip expensive tasks on draft PRs:
```yaml
- name: E2E Tests
if: github.event.pull_request.draft == false
run: turbo run test:e2e --affected
```
Or require label for full test:
```yaml
- name: Full Test Suite
if: contains(github.event.pull_request.labels.*.name, 'full-test')
run: turbo run test
```

View file

@ -0,0 +1,103 @@
# Vercel Deployment
Turborepo integrates seamlessly with Vercel for monorepo deployments.
## Remote Cache
Remote caching is **automatically enabled** when deploying to Vercel. No configuration needed - Vercel detects Turborepo and enables caching.
This means:
- No `TURBO_TOKEN` or `TURBO_TEAM` setup required on Vercel
- Cache is shared across all deployments
- Preview and production builds benefit from cache
## turbo-ignore
Skip unnecessary builds when a package hasn't changed using `turbo-ignore`.
### Installation
```bash
npx turbo-ignore
```
Or install globally in your project:
```bash
pnpm add -D turbo-ignore
```
### Setup in Vercel
1. Go to your project in Vercel Dashboard
2. Navigate to Settings > Git > Ignored Build Step
3. Select "Custom" and enter:
```bash
npx turbo-ignore
```
### How It Works
`turbo-ignore` checks if the current package (or its dependencies) changed since the last successful deployment:
1. Compares current commit to last deployed commit
2. Uses Turborepo's dependency graph
3. Returns exit code 0 (skip) if no changes
4. Returns exit code 1 (build) if changes detected
### Options
```bash
# Check specific package
npx turbo-ignore web
# Use specific comparison ref
npx turbo-ignore --fallback=HEAD~1
# Verbose output
npx turbo-ignore --verbose
```
## Environment Variables
Set environment variables in Vercel Dashboard:
1. Go to Project Settings > Environment Variables
2. Add variables for each environment (Production, Preview, Development)
Common variables:
- `DATABASE_URL`
- `API_KEY`
- Package-specific config
## Monorepo Root Directory
For monorepos, set the root directory in Vercel:
1. Project Settings > General > Root Directory
2. Set to the package path (e.g., `apps/web`)
Vercel automatically:
- Installs dependencies from monorepo root
- Runs build from the package directory
- Detects framework settings
## Build Command
Vercel auto-detects `turbo run build` when `turbo.json` exists at root.
Override if needed:
```bash
turbo run build --filter=web
```
Or for production-only optimizations:
```bash
turbo run build --filter=web --env-mode=strict
```

View file

@ -0,0 +1,297 @@
# turbo run Flags Reference
Full docs: https://turborepo.dev/docs/reference/run
## Package Selection
### `--filter` / `-F`
Select specific packages to run tasks in.
```bash
turbo build --filter=web
turbo build -F=@repo/ui -F=@repo/utils
turbo test --filter=./apps/*
```
See `filtering/` for complete syntax (globs, dependencies, git ranges).
### Task Identifier Syntax (v2.2.4+)
Run specific package tasks directly:
```bash
turbo run web#build # Build web package
turbo run web#build docs#lint # Multiple specific tasks
```
### `--affected`
Run only in packages changed since the base branch.
```bash
turbo build --affected
turbo test --affected --filter=./apps/* # combine with filter
```
**How it works:**
- Default: compares `main...HEAD`
- In GitHub Actions: auto-detects `GITHUB_BASE_REF`
- Override base: `TURBO_SCM_BASE=development turbo build --affected`
- Override head: `TURBO_SCM_HEAD=your-branch turbo build --affected`
**Requires git history** - shallow clones may fall back to running all tasks.
## Execution Control
### `--dry` / `--dry=json`
Preview what would run without executing.
```bash
turbo build --dry # human-readable
turbo build --dry=json # machine-readable
```
### `--force`
Ignore all cached artifacts, re-run everything.
```bash
turbo build --force
```
### `--concurrency`
Limit parallel task execution.
```bash
turbo build --concurrency=4 # max 4 tasks
turbo build --concurrency=50% # 50% of CPU cores
```
### `--continue`
Keep running other tasks when one fails.
```bash
turbo build test --continue
```
### `--only`
Run only the specified task, skip its dependencies.
```bash
turbo build --only # skip running dependsOn tasks
```
### `--parallel` (Discouraged)
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.
## Cache Control
### `--cache`
Fine-grained cache behavior control.
```bash
# Default: read/write both local and remote
turbo build --cache=local:rw,remote:rw
# Read-only local, no remote
turbo build --cache=local:r,remote:
# Disable local, read-only remote
turbo build --cache=local:,remote:r
# Disable all caching
turbo build --cache=local:,remote:
```
## Output & Debugging
### `--graph`
Generate task graph visualization.
```bash
turbo build --graph # opens in browser
turbo build --graph=graph.svg # SVG file
turbo build --graph=graph.png # PNG file
turbo build --graph=graph.json # JSON data
turbo build --graph=graph.mermaid # Mermaid diagram
```
### `--summarize`
Generate JSON run summary for debugging.
```bash
turbo build --summarize
# creates .turbo/runs/<run-id>.json
```
### `--output-logs`
Control log output verbosity.
```bash
turbo build --output-logs=full # all logs (default)
turbo build --output-logs=new-only # only cache misses
turbo build --output-logs=errors-only # only failures
turbo build --output-logs=none # silent
```
### `--profile`
Generate Chrome tracing profile for performance analysis.
```bash
turbo build --profile=profile.json
# open chrome://tracing and load the file
```
### `--verbosity` / `-v`
Control turbo's own log level.
```bash
turbo build -v # verbose
turbo build -vv # more verbose
turbo build -vvv # maximum verbosity
```
## Environment
### `--env-mode`
Control environment variable handling.
```bash
turbo build --env-mode=strict # only declared env vars (default)
turbo build --env-mode=loose # include all env vars in hash
```
## UI
### `--ui`
Select output interface.
```bash
turbo build --ui=tui # interactive terminal UI (default in TTY)
turbo build --ui=stream # streaming logs (default in CI)
```
---
# turbo-ignore
Full docs: https://turborepo.dev/docs/reference/turbo-ignore
Skip CI work when nothing relevant changed. Useful for skipping container setup.
## Basic Usage
```bash
# Check if build is needed for current package (uses Automatic Package Scoping)
npx turbo-ignore
# Check specific package
npx turbo-ignore web
# Check specific task
npx turbo-ignore --task=test
```
## Exit Codes
- `0`: No changes detected - skip CI work
- `1`: Changes detected - proceed with CI
## CI Integration Example
```yaml
# GitHub Actions
- name: Check for changes
id: turbo-ignore
run: npx turbo-ignore web
continue-on-error: true
- name: Build
if: steps.turbo-ignore.outcome == 'failure' # changes detected
run: pnpm build
```
## Comparison Depth
Default: compares to parent commit (`HEAD^1`).
```bash
# Compare to specific commit
npx turbo-ignore --fallback=abc123
# Compare to branch
npx turbo-ignore --fallback=main
```
---
# Other Commands
## turbo boundaries
Check workspace violations (experimental).
```bash
turbo boundaries
```
See `references/boundaries/` for configuration.
## turbo watch
Re-run tasks on file changes.
```bash
turbo watch build test
```
See `references/watch/` for details.
## turbo prune
Create sparse checkout for Docker.
```bash
turbo prune web --docker
```
## turbo link / unlink
Connect/disconnect Remote Cache.
```bash
turbo link # connect to Vercel Remote Cache
turbo unlink # disconnect
```
## turbo login / logout
Authenticate with Remote Cache provider.
```bash
turbo login # authenticate
turbo logout # log out
```
## turbo generate
Scaffold new packages.
```bash
turbo generate
```

View file

@ -0,0 +1,195 @@
# Global Options Reference
Options that affect all tasks. Full docs: https://turborepo.dev/docs/reference/configuration
## globalEnv
Environment variables affecting all task hashes.
```json
{
"globalEnv": ["CI", "NODE_ENV", "VERCEL_*"]
}
```
Use for variables that should invalidate all caches when changed.
## globalDependencies
Files that affect all task hashes.
```json
{
"globalDependencies": [
"tsconfig.json",
".env",
"pnpm-lock.yaml"
]
}
```
Lockfile is included by default. Add shared configs here.
## globalPassThroughEnv
Variables available to tasks but not included in hash.
```json
{
"globalPassThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
}
```
Use for credentials that shouldn't affect cache keys.
## cacheDir
Custom cache location. Default: `node_modules/.cache/turbo`.
```json
{
"cacheDir": ".turbo/cache"
}
```
## daemon
Background process for faster subsequent runs. Default: `true`.
```json
{
"daemon": false
}
```
Disable in CI or when debugging.
## envMode
How unspecified env vars are handled. Default: `"strict"`.
```json
{
"envMode": "strict" // Only specified vars available
// or
"envMode": "loose" // All vars pass through
}
```
Strict mode catches missing env declarations.
## ui
Terminal UI mode. Default: `"stream"`.
```json
{
"ui": "tui" // Interactive terminal UI
// or
"ui": "stream" // Traditional streaming logs
}
```
TUI provides better UX for parallel tasks.
## remoteCache
Configure remote caching.
```json
{
"remoteCache": {
"enabled": true,
"signature": true,
"timeout": 30,
"uploadTimeout": 60
}
}
```
| Option | Default | Description |
| --------------- | ---------------------- | ------------------------------------------------------ |
| `enabled` | `true` | Enable/disable remote caching |
| `signature` | `false` | Sign artifacts with `TURBO_REMOTE_CACHE_SIGNATURE_KEY` |
| `preflight` | `false` | Send OPTIONS request before cache requests |
| `timeout` | `30` | Timeout in seconds for cache operations |
| `uploadTimeout` | `60` | Timeout in seconds for uploads |
| `apiUrl` | `"https://vercel.com"` | Remote cache API endpoint |
| `loginUrl` | `"https://vercel.com"` | Login endpoint |
| `teamId` | - | Team ID (must start with `team_`) |
| `teamSlug` | - | Team slug for querystring |
See https://turborepo.dev/docs/core-concepts/remote-caching for setup.
## concurrency
Default: `"10"`
Limit parallel task execution.
```json
{
"concurrency": "4" // Max 4 tasks at once
// or
"concurrency": "50%" // 50% of available CPUs
}
```
## futureFlags
Enable experimental features that will become default in future versions.
```json
{
"futureFlags": {
"errorsOnlyShowHash": true
}
}
```
### `errorsOnlyShowHash`
When using `outputLogs: "errors-only"`, show task hashes on start/completion:
- Cache miss: `cache miss, executing <hash> (only logging errors)`
- Cache hit: `cache hit, replaying logs (no errors) <hash>`
## noUpdateNotifier
Disable update notifications when new turbo versions are available.
```json
{
"noUpdateNotifier": true
}
```
## dangerouslyDisablePackageManagerCheck
Bypass the `packageManager` field requirement. Use for incremental migration.
```json
{
"dangerouslyDisablePackageManagerCheck": true
}
```
**Warning**: Unstable lockfiles can cause unpredictable behavior.
## Git Worktree Cache Sharing (Pre-release)
When working in Git worktrees, Turborepo automatically shares local cache between the main worktree and linked worktrees.
**How it works:**
- Detects worktree configuration
- Redirects cache to main worktree's `.turbo/cache`
- Works alongside Remote Cache
**Benefits:**
- Cache hits across branches
- Reduced disk usage
- Faster branch switching
**Disabled by**: Setting explicit `cacheDir` in turbo.json.

View file

@ -0,0 +1,348 @@
# Configuration Gotchas
Common mistakes and how to fix them.
## #1 Root Scripts Not Using `turbo run`
Root `package.json` scripts for turbo tasks MUST use `turbo run`, not direct commands.
```json
// WRONG - bypasses turbo, no parallelization or caching
{
"scripts": {
"build": "bun build",
"dev": "bun dev"
}
}
// CORRECT - delegates to turbo
{
"scripts": {
"build": "turbo run build",
"dev": "turbo run dev"
}
}
```
**Why this matters:** Running `bun build` or `npm run build` at root bypasses Turborepo entirely - no parallelization, no caching, no dependency graph awareness.
## #2 Using `&&` to Chain Turbo Tasks
Don't use `&&` to chain tasks that turbo should orchestrate.
```json
// WRONG - changeset:publish chains turbo task with non-turbo command
{
"scripts": {
"changeset:publish": "bun build && changeset publish"
}
}
// CORRECT - use turbo run, let turbo handle dependencies
{
"scripts": {
"changeset:publish": "turbo run build && changeset publish"
}
}
```
If the second command (`changeset publish`) depends on build outputs, the turbo task should run through turbo to get caching and parallelization benefits.
## #3 Overly Broad globalDependencies
`globalDependencies` affects hash for ALL tasks in ALL packages. Be specific.
```json
// WRONG - affects all hashes
{
"globalDependencies": ["**/.env.*local"]
}
// CORRECT - move to specific tasks that need it
{
"globalDependencies": [".env"],
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**"]
}
}
}
```
**Why this matters:** `**/.env.*local` matches .env files in ALL packages, causing unnecessary cache invalidation. Instead:
- Use `globalDependencies` only for truly global files (root `.env`)
- Use task-level `inputs` for package-specific .env files with `$TURBO_DEFAULT$` to preserve default behavior
## #4 Repetitive Task Configuration
Look for repeated configuration across tasks that can be collapsed.
```json
// WRONG - repetitive env and inputs across tasks
{
"tasks": {
"build": {
"env": ["API_URL", "DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env*"]
},
"test": {
"env": ["API_URL", "DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env*"]
}
}
}
// BETTER - use globalEnv and globalDependencies
{
"globalEnv": ["API_URL", "DATABASE_URL"],
"globalDependencies": [".env*"],
"tasks": {
"build": {},
"test": {}
}
}
```
**When to use global vs task-level:**
- `globalEnv` / `globalDependencies` - affects ALL tasks, use for truly shared config
- Task-level `env` / `inputs` - use when only specific tasks need it
## #5 Using `../` to Traverse Out of Package in `inputs`
Don't use relative paths like `../` to reference files outside the package. Use `$TURBO_ROOT$` instead.
```json
// WRONG - traversing out of package
{
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", "../shared-config.json"]
}
}
}
// CORRECT - use $TURBO_ROOT$ for repo root
{
"tasks": {
"build": {
"inputs": ["$TURBO_DEFAULT$", "$TURBO_ROOT$/shared-config.json"]
}
}
}
```
## #6 MOST COMMON MISTAKE: Creating Root Tasks
**DO NOT create Root Tasks. ALWAYS create package tasks.**
When you need to create a task (build, lint, test, typecheck, etc.):
1. Add the script to **each relevant package's** `package.json`
2. Register the task in root `turbo.json`
3. Root `package.json` only contains `turbo run <task>`
```json
// WRONG - DO NOT DO THIS
// Root package.json with task logic
{
"scripts": {
"build": "cd apps/web && next build && cd ../api && tsc",
"lint": "eslint apps/ packages/",
"test": "vitest"
}
}
// CORRECT - DO THIS
// apps/web/package.json
{ "scripts": { "build": "next build", "lint": "eslint .", "test": "vitest" } }
// apps/api/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
// packages/ui/package.json
{ "scripts": { "build": "tsc", "lint": "eslint .", "test": "vitest" } }
// Root package.json - ONLY delegates
{ "scripts": { "build": "turbo run build", "lint": "turbo run lint", "test": "turbo run test" } }
// turbo.json - register tasks
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"lint": {},
"test": {}
}
}
```
**Why this matters:**
- Package tasks run in **parallel** across all packages
- Each package's output is cached **individually**
- You can **filter** to specific packages: `turbo run test --filter=web`
Root Tasks (`//#taskname`) defeat all these benefits. Only use them for tasks that truly cannot exist in any package (extremely rare).
## #7 Tasks That Need Parallel Execution + Cache Invalidation
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.
**Use Transit Nodes for these tasks:**
```json
// WRONG - forces sequential execution (SLOW)
"my-task": {
"dependsOn": ["^my-task"]
}
// ALSO WRONG - no dependency awareness (INCORRECT CACHING)
"my-task": {}
// CORRECT - use Transit Nodes for parallel + correct caching
{
"tasks": {
"transit": { "dependsOn": ["^transit"] },
"my-task": { "dependsOn": ["transit"] }
}
}
```
**Why Transit Nodes work:**
- `transit` creates dependency relationships without matching any actual script
- Tasks that depend on `transit` gain dependency awareness
- Since `transit` completes instantly (no script), tasks run in parallel
- Cache correctly invalidates when dependency source code changes
**How to identify tasks that need this pattern:** Look for tasks that read source files from dependencies but don't need their build outputs.
## Missing outputs for File-Producing Tasks
**Before flagging missing `outputs`, check what the task actually produces:**
1. Read the package's script (e.g., `"build": "tsc"`, `"test": "vitest"`)
2. Determine if it writes files to disk or only outputs to stdout
3. Only flag if the task produces files that should be cached
```json
// WRONG - build produces files but they're not cached
"build": {
"dependsOn": ["^build"]
}
// CORRECT - outputs are cached
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"]
}
```
No `outputs` key is fine for stdout-only tasks. For file-producing tasks, missing `outputs` means Turbo has nothing to cache.
## Forgetting ^ in dependsOn
```json
// WRONG - looks for "build" in SAME package (infinite loop or missing)
"build": {
"dependsOn": ["build"]
}
// CORRECT - runs dependencies' build first
"build": {
"dependsOn": ["^build"]
}
```
The `^` means "in dependency packages", not "in this package".
## Missing persistent on Dev Tasks
```json
// WRONG - dependent tasks hang waiting for dev to "finish"
"dev": {
"cache": false
}
// CORRECT
"dev": {
"cache": false,
"persistent": true
}
```
## Package Config Missing extends
```json
// WRONG - packages/web/turbo.json
{
"tasks": {
"build": { "outputs": [".next/**"] }
}
}
// CORRECT
{
"extends": ["//"],
"tasks": {
"build": { "outputs": [".next/**"] }
}
}
```
Without `"extends": ["//"]`, Package Configurations are invalid.
## Root Tasks Need Special Syntax
To run a task defined only in root `package.json`:
```bash
# WRONG
turbo run format
# CORRECT
turbo run //#format
```
And in dependsOn:
```json
"build": {
"dependsOn": ["//#codegen"] // Root package's codegen
}
```
## Overwriting Default Inputs
```json
// WRONG - only watches test files, ignores source changes
"test": {
"inputs": ["tests/**"]
}
// CORRECT - extends defaults, adds test files
"test": {
"inputs": ["$TURBO_DEFAULT$", "tests/**"]
}
```
Without `$TURBO_DEFAULT$`, you replace all default file watching.
## Caching Tasks with Side Effects
```json
// WRONG - deploy might be skipped on cache hit
"deploy": {
"dependsOn": ["build"]
}
// CORRECT
"deploy": {
"dependsOn": ["build"],
"cache": false
}
```
Always disable cache for deploy, publish, or mutation tasks.

View file

@ -0,0 +1,285 @@
# Task Configuration Reference
Full docs: https://turborepo.dev/docs/reference/configuration#tasks
## dependsOn
Controls task execution order.
```json
{
"tasks": {
"build": {
"dependsOn": [
"^build", // Dependencies' build tasks first
"codegen", // Same package's codegen task first
"shared#build" // Specific package's build task
]
}
}
}
```
| Syntax | Meaning |
| ---------- | ------------------------------------ |
| `^task` | Run `task` in all dependencies first |
| `task` | Run `task` in same package first |
| `pkg#task` | Run specific package's task first |
The `^` prefix is crucial - without it, you're referencing the same package.
### Transit Nodes for Parallel Tasks
For tasks like `lint` and `check-types` that can run in parallel but need dependency-aware caching:
```json
{
"tasks": {
"transit": { "dependsOn": ["^transit"] },
"lint": { "dependsOn": ["transit"] },
"check-types": { "dependsOn": ["transit"] }
}
}
```
**DO NOT use `dependsOn: ["^lint"]`** - this forces sequential execution.
**DO NOT use `dependsOn: []`** - this breaks cache invalidation.
The `transit` task creates dependency relationships without running anything (no matching script), so tasks run in parallel with correct caching.
## outputs
Glob patterns for files to cache. **If omitted, nothing is cached.**
```json
{
"tasks": {
"build": {
"outputs": ["dist/**", "build/**"]
}
}
}
```
**Framework examples:**
```json
// Next.js
"outputs": [".next/**", "!.next/cache/**"]
// Vite
"outputs": ["dist/**"]
// TypeScript (tsc)
"outputs": ["dist/**", "*.tsbuildinfo"]
// No file outputs (lint, typecheck)
"outputs": []
```
Use `!` prefix to exclude patterns from caching.
## inputs
Files considered when calculating task hash. Defaults to all tracked files in package.
```json
{
"tasks": {
"test": {
"inputs": ["src/**", "tests/**", "vitest.config.ts"]
}
}
}
```
**Special values:**
| Value | Meaning |
| --------------------- | --------------------------------------- |
| `$TURBO_DEFAULT$` | Include default inputs, then add/remove |
| `$TURBO_ROOT$/<path>` | Reference files from repo root |
```json
{
"tasks": {
"build": {
"inputs": [
"$TURBO_DEFAULT$",
"!README.md",
"$TURBO_ROOT$/tsconfig.base.json"
]
}
}
}
```
## env
Environment variables to include in task hash.
```json
{
"tasks": {
"build": {
"env": [
"API_URL",
"NEXT_PUBLIC_*", // Wildcard matching
"!DEBUG" // Exclude from hash
]
}
}
}
```
Variables listed here affect cache hits - changing the value invalidates cache.
## cache
Enable/disable caching for a task. Default: `true`.
```json
{
"tasks": {
"dev": { "cache": false },
"deploy": { "cache": false }
}
}
```
Disable for: dev servers, deploy commands, tasks with side effects.
## persistent
Mark long-running tasks that don't exit. Default: `false`.
```json
{
"tasks": {
"dev": {
"cache": false,
"persistent": true
}
}
}
```
Required for dev servers - without it, dependent tasks wait forever.
## interactive
Allow task to receive stdin input. Default: `false`.
```json
{
"tasks": {
"login": {
"cache": false,
"interactive": true
}
}
}
```
## outputLogs
Control when logs are shown. Options: `full`, `hash-only`, `new-only`, `errors-only`, `none`.
```json
{
"tasks": {
"build": {
"outputLogs": "new-only" // Only show logs on cache miss
}
}
}
```
## with
Run tasks alongside this task. For long-running tasks that need runtime dependencies.
```json
{
"tasks": {
"dev": {
"with": ["api#dev"],
"persistent": true,
"cache": false
}
}
}
```
Unlike `dependsOn`, `with` runs tasks concurrently (not sequentially). Use for dev servers that need other services running.
## interruptible
Allow `turbo watch` to restart the task on changes. Default: `false`.
```json
{
"tasks": {
"dev": {
"persistent": true,
"interruptible": true,
"cache": false
}
}
}
```
Use for dev servers that don't automatically detect dependency changes.
## description (Pre-release)
Human-readable description of the task.
```json
{
"tasks": {
"build": {
"description": "Compiles the application for production deployment"
}
}
}
```
For documentation only - doesn't affect execution or caching.
## passThroughEnv
Environment variables available at runtime but NOT included in cache hash.
```json
{
"tasks": {
"build": {
"passThroughEnv": ["AWS_SECRET_KEY", "GITHUB_TOKEN"]
}
}
}
```
**Warning**: Changes to these vars won't cause cache misses. Use `env` if changes should invalidate cache.
## extends (Package Configuration only)
Control task inheritance in Package Configurations.
```json
// packages/ui/turbo.json
{
"extends": ["//"],
"tasks": {
"lint": {
"extends": false // Exclude from this package
}
}
}
```
| Value | Behavior |
| ---------------- | -------------------------------------------------------------- |
| `true` (default) | Inherit from root turbo.json |
| `false` | Exclude task from package, or define fresh without inheritance |

View file

@ -0,0 +1,145 @@
# Environment Variable Gotchas
Common mistakes and how to fix them.
## .env Files Must Be in `inputs`
Turbo does NOT read `.env` files. Your framework (Next.js, Vite, etc.) or `dotenv` loads them. But Turbo needs to know when they change.
**Wrong:**
```json
{
"tasks": {
"build": {
"env": ["DATABASE_URL"]
}
}
}
```
**Right:**
```json
{
"tasks": {
"build": {
"env": ["DATABASE_URL"],
"inputs": ["$TURBO_DEFAULT$", ".env", ".env.local", ".env.production"]
}
}
}
```
## Strict Mode Filters CI Variables
In strict mode, CI provider variables (GITHUB_TOKEN, GITLAB_CI, etc.) are filtered unless explicitly listed.
**Symptom:** Task fails with "authentication required" or "permission denied" in CI.
**Solution:**
```json
{
"globalPassThroughEnv": ["GITHUB_TOKEN", "GITLAB_CI", "CI"]
}
```
## passThroughEnv Doesn't Affect Hash
Variables in `passThroughEnv` are available at runtime but changes WON'T trigger rebuilds.
**Dangerous example:**
```json
{
"tasks": {
"build": {
"passThroughEnv": ["API_URL"]
}
}
}
```
If `API_URL` changes from staging to production, Turbo may serve a cached build pointing to the wrong API.
**Use passThroughEnv only for:**
- Auth tokens that don't affect output (SENTRY_AUTH_TOKEN)
- CI metadata (GITHUB_RUN_ID)
- Variables consumed after build (deploy credentials)
## Runtime-Created Variables Are Invisible
Turbo captures env vars at startup. Variables created during execution aren't seen.
**Won't work:**
```bash
# In package.json scripts
"build": "export API_URL=$COMPUTED_VALUE && next build"
```
**Solution:** Set vars before invoking turbo:
```bash
API_URL=$COMPUTED_VALUE turbo run build
```
## Different .env Files for Different Environments
If you use `.env.development` and `.env.production`, both should be in inputs.
```json
{
"tasks": {
"build": {
"inputs": [
"$TURBO_DEFAULT$",
".env",
".env.local",
".env.development",
".env.development.local",
".env.production",
".env.production.local"
]
}
}
}
```
## Complete Next.js Example
```json
{
"$schema": "https://turborepo.dev/schema.v2.json",
"globalEnv": ["CI", "NODE_ENV", "VERCEL"],
"globalPassThroughEnv": ["GITHUB_TOKEN", "VERCEL_URL"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"env": [
"DATABASE_URL",
"NEXT_PUBLIC_*",
"!NEXT_PUBLIC_ANALYTICS_ID"
],
"passThroughEnv": ["SENTRY_AUTH_TOKEN"],
"inputs": [
"$TURBO_DEFAULT$",
".env",
".env.local",
".env.production",
".env.production.local"
],
"outputs": [".next/**", "!.next/cache/**"]
}
}
}
```
This config:
- Hashes DATABASE*URL and NEXT_PUBLIC*\* vars (except analytics)
- Passes through SENTRY_AUTH_TOKEN without hashing
- Includes all .env file variants in the hash
- Makes CI tokens available globally

View file

@ -0,0 +1,101 @@
# Environment Modes
Turborepo supports different modes for handling environment variables during task execution.
## Strict Mode (Default)
Only explicitly configured variables are available to tasks.
**Behavior:**
- Tasks only see vars listed in `env`, `globalEnv`, `passThroughEnv`, or `globalPassThroughEnv`
- Unlisted vars are filtered out
- Tasks fail if they require unlisted variables
**Benefits:**
- Guarantees cache correctness
- Prevents accidental dependencies on system vars
- Reproducible builds across machines
```bash
# Explicit (though it's the default)
turbo run build --env-mode=strict
```
## Loose Mode
All system environment variables are available to tasks.
```bash
turbo run build --env-mode=loose
```
**Behavior:**
- Every system env var is passed through
- Only vars in `env`/`globalEnv` affect the hash
- Other vars are available but NOT hashed
**Risks:**
- Cache may restore incorrect results if unhashed vars changed
- "Works on my machine" bugs
- CI vs local environment mismatches
**Use case:** Migrating legacy projects or debugging strict mode issues.
## Framework Inference (Automatic)
Turborepo automatically detects frameworks and includes their conventional env vars.
### Inferred Variables by Framework
| Framework | Pattern |
| ---------------- | ------------------- |
| Next.js | `NEXT_PUBLIC_*` |
| Vite | `VITE_*` |
| Create React App | `REACT_APP_*` |
| Gatsby | `GATSBY_*` |
| Nuxt | `NUXT_*`, `NITRO_*` |
| Expo | `EXPO_PUBLIC_*` |
| Astro | `PUBLIC_*` |
| SvelteKit | `PUBLIC_*` |
| Remix | `REMIX_*` |
| Redwood | `REDWOOD_ENV_*` |
| Sanity | `SANITY_STUDIO_*` |
| Solid | `VITE_*` |
### Disabling Framework Inference
Globally via CLI:
```bash
turbo run build --framework-inference=false
```
Or exclude specific patterns in config:
```json
{
"tasks": {
"build": {
"env": ["!NEXT_PUBLIC_*"]
}
}
}
```
### Why Disable?
- You want explicit control over all env vars
- Framework vars shouldn't bust the cache (e.g., analytics IDs)
- Debugging unexpected cache misses
## Checking Environment Mode
Use `--dry-run` to see which vars affect each task:
```bash
turbo run build --dry-run=json | jq '.tasks[].environmentVariables'
```

View file

@ -0,0 +1,152 @@
# Common Filter Patterns
Practical examples for typical monorepo scenarios.
## Single Package
Run task in one package:
```bash
turbo run build --filter=web
turbo run test --filter=@acme/api
```
## Package with Dependencies
Build a package and everything it depends on:
```bash
turbo run build --filter=web...
```
Useful for: ensuring all dependencies are built before the target.
## Package Dependents
Run in all packages that depend on a library:
```bash
turbo run test --filter=...ui
```
Useful for: testing consumers after changing a shared package.
## Dependents Only (Exclude Target)
Test packages that depend on ui, but not ui itself:
```bash
turbo run test --filter=...^ui
```
## Changed Packages
Run only in packages with file changes since last commit:
```bash
turbo run lint --filter=[HEAD^1]
```
Since a specific branch point:
```bash
turbo run lint --filter=[main...HEAD]
```
## Changed + Dependents (PR Builds)
Run in changed packages AND packages that depend on them:
```bash
turbo run build test --filter=...[HEAD^1]
```
Or use the shortcut:
```bash
turbo run build test --affected
```
## Directory-Based
Run in all apps:
```bash
turbo run build --filter=./apps/*
```
Run in specific directories:
```bash
turbo run build --filter=./apps/web --filter=./apps/api
```
## Scope-Based
Run in all packages under a scope:
```bash
turbo run build --filter=@acme/*
```
## Exclusions
Run in all apps except admin:
```bash
turbo run build --filter=./apps/* --filter=!admin
```
Run everywhere except specific packages:
```bash
turbo run lint --filter=!legacy-app --filter=!deprecated-pkg
```
## Complex Combinations
Apps that changed, plus their dependents:
```bash
turbo run build --filter=...[HEAD^1] --filter=./apps/*
```
All packages except docs, but only if changed:
```bash
turbo run build --filter=[main...HEAD] --filter=!docs
```
## Debugging Filters
Use `--dry` to see what would run without executing:
```bash
turbo run build --filter=web... --dry
```
Use `--dry=json` for machine-readable output:
```bash
turbo run build --filter=...[HEAD^1] --dry=json
```
## CI/CD Patterns
PR validation (most common):
```bash
turbo run build test lint --affected
```
Deploy only changed apps:
```bash
turbo run deploy --filter=./apps/* --filter=[main...HEAD]
```
Full rebuild of specific app and deps:
```bash
turbo run build --filter=production-app...
```

View file

@ -0,0 +1,111 @@
---
name: unit-tests
description: |
Guardrails for adding unit tests in bklit-ui without over-testing. Use when the
user mentions unit test, unit tests, tests, test coverage, add tests, write tests,
vitest, jest, or asks whether something should be tested.
---
# Unit Tests (bklit-ui)
Read this skill before proposing or writing tests. **Default stance: fewer, higher-signal tests.**
---
## When to add tests
Add tests when they **lock in behavior that is easy to break silently**:
| Worth testing | Why |
|---------------|-----|
| Pure functions / modules | Stable inputs → outputs; fast; no DOM |
| Formatters, parsers, scale math, bounds | Regression on string/number output is user-visible |
| Codegen / export / registry helpers | Output shape is the contract |
| Non-obvious edge cases | Empty data, reversed ranges, clamping |
**Examples in this repo:** `chart-formatters.test.ts`, `highlight-segment-bounds.test.ts`, `animation.test.ts`, `apps/web/lib/studio/__tests__/*`.
---
## When NOT to add tests (unless explicitly requested)
Do **not** add tests just to increase coverage or “be thorough”:
| Skip | Why |
|------|-----|
| React component render smoke tests | visx, motion, portals → brittle; manual/docs check is cheaper |
| `memo()` / guard extractions (#65-style) | Structural perf refactors; output unchanged; RTL mount assertions are heavy |
| Default prop passthrough | `formatValue = intFmt` — types + formatter tests cover it |
| Third-party library behavior | Dont re-test d3, visx, or React |
| Trivial getters / one-line wrappers | No regression signal |
| Snapshot entire chart SVG/JSX | High churn, low signal |
If the user asks “should we test X?” — **say no** when X falls in this table, and suggest a lighter alternative (pure helper test, manual check, CI build).
---
## Repo conventions
**Runner:** Node built-in test runner + `tsx` (not Jest/Vitest unless the repo adopts them later).
```bash
pnpm test # root — turbo runs packages with a test script
pnpm --filter @bklitui/ui test
cd apps/web && pnpm test
```
**Place tests:** `**/__tests__/**/*.test.ts` next to the code under test.
**Pattern:**
```ts
import assert from "node:assert/strict";
import { describe, it } from "node:test";
import { myFn } from "../my-module";
describe("myFn", () => {
it("handles empty input", () => {
assert.equal(myFn([]), expected);
});
});
```
**Equivalence tests** (preferred for formatters): assert shared module output matches the **previous inline** call (e.g. `toLocaleDateString` with same locale/options) so tests stay timezone-safe and prove no visual regression.
**New package test script:** add to `package.json`:
```json
"test": "node scripts/run-tests.mjs"
```
Use a small `scripts/run-tests.mjs` that collects `*.test.ts` from `__tests__` and invokes `node --import tsx --test` — shell globs break on Linux CI. Add `tsx` as a devDependency if missing. Wire into root `turbo.json` `test` task; CI runs `pnpm test`.
---
## Decision checklist (run before writing)
1. Is the logic **pure** or extractable to pure functions? → Test that. Consider extracting first.
2. Would a test fail on a **real user-visible bug**? → Good candidate.
3. Does it need **jsdom / RTL / Playwright**? → Stop; justify to user or defer to manual/visual check.
4. Did the user **ask** for tests? → Still apply this skill; dont over-deliver.
5. How many cases? → **310 focused cases**, not exhaustive matrices.
---
## What to tell the user
When recommending tests, be explicit:
- **Add:** “Test `chart-formatters.ts` — pure, high regression value.”
- **Skip:** “Skip component tests for the memo split — no output change; build + manual chart docs are enough.”
- **CI:** Mention `pnpm test` in PR test plan when adding or changing tests.
---
## Anti-patterns
- Adding Jest/Vitest/Testing Library for a one-off without team buy-in
- Mocking entire chart context to assert `useMemo` call counts
- Testing implementation details (hook order, internal component names)
- Duplicating type-checker work (`expect(typeof x).toBe('function')`)
- Committing tests that only pass locally due to hard-coded timezone/locale strings

View file

@ -0,0 +1,129 @@
---
name: wiki-llms-txt
description: Generates llms.txt and llms-full.txt files for LLM-friendly project documentation following the llms.txt specification. Use when the user wants to create LLM-readable summaries, llms.txt files, or make their wiki accessible to language models.
license: MIT
metadata:
author: Microsoft
version: "1.0.0"
---
# llms.txt Generator
Generate `llms.txt` and `llms-full.txt` files that provide LLM-friendly access to wiki documentation, following the [llms.txt specification](https://llmstxt.org/).
## When This Skill Activates
- User asks to generate `llms.txt` or mentions the llms.txt standard
- User wants to make documentation "LLM-friendly" or "LLM-readable"
- User asks for a project summary file for language models
- User mentions `llms-full.txt` or context-expanded documentation
## Source Repository Resolution (MUST DO FIRST)
Before generating, resolve the source repository context:
1. **Check for git remote**: Run `git remote get-url origin`
2. **Ask the user**: _"Is this a local-only repository, or do you have a source repository URL?"_
- Remote URL → store as `REPO_URL`
- Local → use relative paths only
3. **Determine default branch**: Run `git rev-parse --abbrev-ref HEAD`
4. **Do NOT proceed** until resolved
## llms.txt Format (Spec-Compliant)
The file follows the [llms.txt specification](https://llmstxt.org/):
```markdown
# {Project Name}
> {Dense one-paragraph summary — what it does, who it's for, key technologies}
{Important context paragraphs — constraints, architectural philosophy, non-obvious things}
## {Section Name}
- [{Page Title}]({relative-path-to-md}): {One-sentence description of what the reader will learn}
## Optional
- [{Page Title}]({relative-path-to-md}): {Description — these can be skipped for shorter context}
```
### Key Rules
1. **H1** — Project name (exactly one, required)
2. **Blockquote** — Dense, specific summary (required). Must be unique to THIS project.
3. **Context paragraphs** — Non-obvious constraints, things LLMs would get wrong without being told
4. **H2 sections** — Organized by topic, each with a list of `[Title](url): Description` entries
5. **"Optional" H2** — Special meaning: links here can be skipped for shorter context
6. **Relative links** — All paths relative to wiki directory
7. **Dynamic** — ALL content derived from actual wiki pages, not templates
8. **Section order** — Most important first: Onboarding → Architecture → Getting Started → Deep Dive → Optional
### Description Quality
| ❌ Bad | ✅ Good |
|--------|---------|
| "Architecture overview" | "System architecture showing how Orleans grains communicate via message passing with at-least-once delivery" |
| "Getting started guide" | "Prerequisites, local dev setup with Docker Compose, and first API call walkthrough" |
| "The API reference" | "REST endpoints with auth requirements, rate limits, and request/response schemas" |
## llms-full.txt Format
Same structure as `llms.txt` but with full content inlined:
```markdown
# {Project Name}
> {Same summary}
{Same context}
## {Section Name}
<doc title="{Page Title}" path="{relative-path}">
{Full markdown content — frontmatter stripped, citations and diagrams preserved}
</doc>
```
### Inlining Rules
- **Strip YAML frontmatter** (`---` blocks) from each page
- **Preserve Mermaid diagrams** — keep `` ```mermaid `` fences intact
- **Preserve citations** — all `[file:line](URL)` links stay as-is
- **Preserve tables** — all markdown tables stay intact
- **Preserve `<!-- Sources: -->` comments** — these provide diagram provenance
## Prerequisites
This skill works best when wiki pages already exist (via `/deep-wiki:generate` or `/deep-wiki:page`). If no wiki exists yet:
1. Suggest running `/deep-wiki:generate` first
2. OR generate a minimal `llms.txt` from README + source code scan (without wiki page links)
## Output Files
Generate three files:
| File | Purpose | Discoverability |
|------|---------|-----------------|
| `./llms.txt` | Root discovery file | Standard path per llms.txt spec. GitHub MCP `get_file_contents` and `search_code` find this first. |
| `wiki/llms.txt` | Wiki-relative links | For VitePress deployment and wiki-internal navigation. |
| `wiki/llms-full.txt` | Full inlined content | Comprehensive reference for agents needing all docs in one file. |
The root `./llms.txt` links into `wiki/` (e.g., `[Guide](./wiki/onboarding/contributor-guide.md)`). The `wiki/llms.txt` uses wiki-relative paths (e.g., `[Guide](./onboarding/contributor-guide.md)`).
If a root `llms.txt` already exists and was NOT generated by deep-wiki, do NOT overwrite it.
## Validation Checklist
Before finalizing:
- [ ] All linked files in `llms.txt` actually exist
- [ ] All `<doc>` blocks in `llms-full.txt` have real content (not empty)
- [ ] Blockquote is specific to this project (not generic boilerplate)
- [ ] Sections ordered by importance
- [ ] No duplicate page entries across sections
- [ ] "Optional" section only contains truly optional content
- [ ] `llms.txt` is concise (1-5 KB)
- [ ] `llms-full.txt` contains all wiki pages