From 1f4e59ddb075ffa6c7ea35dcca5aa7e67c6fe004 Mon Sep 17 00:00:00 2001
From: Alaister Young <10985857+alaister@users.noreply.github.com>
Date: Fri, 26 Jun 2026 14:20:17 +0800
Subject: [PATCH] chore(studio): generate apps/studio/AGENTS.md for CodeRabbit
from skills
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
CodeRabbit's code-guidelines feature auto-detects guideline files and
directory-scopes them: a file applies only to its own directory and below.
Our Studio conventions live in .claude/skills/, which contains no code, so
CodeRabbit would never apply them to apps/studio. AGENTS.md is the right
carrier: CodeRabbit auto-detects **/AGENTS.md, and apps/studio/AGENTS.md
scopes correctly to apps/studio/**. Unlike CLAUDE.md, Claude Code does not
read AGENTS.md, so this adds zero context cost — Claude keeps using the
skills directly, and they stay the single source of truth.
A generator inlines the relevant skills into apps/studio/AGENTS.md (a derived
artifact) so there is no maintenance duplication; CI fails if it drifts.
---
.github/workflows/studio-guidelines-sync.yml | 39 ++
.prettierignore | 2 +
apps/studio/AGENTS.md | 585 +++++++++++++++++++
package.json | 1 +
scripts/sync-studio-guidelines.mjs | 100 ++++
5 files changed, 727 insertions(+)
create mode 100644 .github/workflows/studio-guidelines-sync.yml
create mode 100644 apps/studio/AGENTS.md
create mode 100644 scripts/sync-studio-guidelines.mjs
diff --git a/.github/workflows/studio-guidelines-sync.yml b/.github/workflows/studio-guidelines-sync.yml
new file mode 100644
index 00000000000..388b527b6ab
--- /dev/null
+++ b/.github/workflows/studio-guidelines-sync.yml
@@ -0,0 +1,39 @@
+name: Studio Guidelines Sync
+
+on:
+ pull_request:
+ branches:
+ - 'master'
+ paths:
+ - '.claude/skills/**'
+ - 'apps/studio/AGENTS.md'
+ - 'scripts/sync-studio-guidelines.mjs'
+
+# Cancel old builds on new commit for same workflow + branch/PR
+concurrency:
+ group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
+ cancel-in-progress: true
+
+permissions:
+ contents: read
+
+jobs:
+ check:
+ name: Check apps/studio/AGENTS.md is in sync
+ runs-on: blacksmith-4vcpu-ubuntu-2404
+ steps:
+ - name: Check out repo
+ uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
+ with:
+ persist-credentials: false
+ sparse-checkout: |
+ .claude/skills
+ apps/studio/AGENTS.md
+ scripts
+ .nvmrc
+ - name: Setup node
+ uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
+ with:
+ node-version-file: '.nvmrc'
+ - name: Verify guidelines are up to date
+ run: node scripts/sync-studio-guidelines.mjs --check
diff --git a/.prettierignore b/.prettierignore
index 66e4d58fa92..f92e507b1b8 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -21,6 +21,8 @@ apps/studio/public
apps/**/.turbo
apps/docs/CONTRIBUTING.md
apps/docs/__generated__
+# Generated by scripts/sync-studio-guidelines.mjs from .claude/skills/
+apps/studio/AGENTS.md
apps/design-system/__registry__
# TanStack Router auto-generated route tree; the file header explicitly
# says to exclude it from formatters.
diff --git a/apps/studio/AGENTS.md b/apps/studio/AGENTS.md
new file mode 100644
index 00000000000..bfd4bbe52ad
--- /dev/null
+++ b/apps/studio/AGENTS.md
@@ -0,0 +1,585 @@
+
+
+# Studio Code Review Guidelines
+
+Conventions for `apps/studio` code, compiled from the Studio skills. Apply these
+when reviewing changes under `apps/studio/`.
+
+
+
+# Studio Best Practices
+
+Applies to `apps/studio/**/*.{ts,tsx}`.
+
+## Boolean Naming
+
+Use descriptive prefixes — derive from existing state rather than storing separately:
+
+- `is` — state/identity: `isLoading`, `isPaused`, `isNewRecord`
+- `has` — possession: `hasPermission`, `hasData`
+- `can` — capability: `canUpdateColumns`, `canDelete`
+- `should` — conditional behavior: `shouldFetch`, `shouldRender`
+
+Extract complex conditions into named variables:
+
+```tsx
+// ❌ inline multi-condition
+{
+ !isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading &&
+}
+
+// ✅ named variable
+const canShowAddButton =
+ !isSchemaLocked && isTableLike(selectedTable) && canUpdateColumns && !isLoading
+{
+ canShowAddButton &&
+}
+```
+
+Derive booleans — don't store them:
+
+```tsx
+// ❌ stored derived state
+const [isFormValid, setIsFormValid] = useState(false)
+useEffect(() => {
+ setIsFormValid(name.length > 0 && email.includes('@'))
+}, [name, email])
+
+// ✅ derived
+const isFormValid = name.length > 0 && email.includes('@')
+```
+
+## Component Structure
+
+See `vercel-composition-patterns` skill for compound component and composition patterns.
+
+Keep components under 200–300 lines. Split when you see:
+
+- Multiple distinct UI sections
+- Complex conditional rendering
+- Multiple unrelated `useState` calls
+- Hard to understand at a glance
+
+Co-locate sub-components in the same directory as the parent. Avoid barrel re-export files.
+
+Extract repeated JSX patterns into small components.
+
+## Data Fetching
+
+All data fetching uses TanStack Query (React Query). See `studio-queries` skill for query/mutation patterns and `studio-error-handling` skill for error display conventions.
+
+### Loading / Error / Success Pattern
+
+Top level:
+
+```tsx
+const { data, error, isLoading, isError, isSuccess } = useQuery(...)
+
+if (isLoading) return