diff --git a/.cursor-plugin/marketplace.json b/.cursor-plugin/marketplace.json index f878fbd2507..c7934e57f12 100644 --- a/.cursor-plugin/marketplace.json +++ b/.cursor-plugin/marketplace.json @@ -1,26 +1,16 @@ { - "name": "codecademy-gamut-cursor", + "name": "gamut", "owner": { "name": "Codecademy" }, "metadata": { - "description": "Cursor plugins for the Gamut design system: core usage, accessibility, and theming." + "description": "Gamut design system agent pack (Cursor + Claude): consumption, accessibility, and theming. Single plugin; see packages/agent-plugin." }, "plugins": [ { - "name": "codecademy-gamut", - "source": "gamut-cursor-plugins/gamut-core", - "description": "Core Gamut consumption: layout, system props, gamut-styles utilities, ESLint alignment, Storybook links." - }, - { - "name": "codecademy-gamut-a11y", - "source": "gamut-cursor-plugins/gamut-a11y", - "description": "WCAG-minded Gamut usage and composition when primitives are incomplete." - }, - { - "name": "codecademy-gamut-themes", - "source": "gamut-cursor-plugins/gamut-themes", - "description": "ColorMode, Background, semantic tokens, hooks, and platform themes." + "name": "gamut", + "source": "packages/agent-plugin", + "description": "Core Gamut usage, accessibility patterns, and ColorMode/Background theming for application code. Rules + skills; canonical docs in docs/agents/." } ] } diff --git a/.cursor/rules/gamut-component-building.mdc b/.cursor/rules/gamut-component-building.mdc index 52c0c599dc6..b27fb392bd7 100644 --- a/.cursor/rules/gamut-component-building.mdc +++ b/.cursor/rules/gamut-component-building.mdc @@ -16,4 +16,4 @@ alwaysApply: false - **TypeScript** — Derive props from implementation: `StyleProps` after `variance.compose` / `variant`; `ComponentProps` for Emotion styled roots; intersect with bases (`ButtonBaseProps & ComponentProps`). Model variant-specific APIs with discriminated unions and `never` for illegal combos. Narrow handlers with `HTMLProps<…>['onClick']` or `ComponentProps[…]`. Exemplars: `Tag/types.tsx`, `Badge/index.tsx`, `Button/shared/types.ts`, `Form/elements/Form.tsx`, `Anchor/index.tsx`. - **React** — Match neighboring `React.FC` usage; use `forwardRef` when refs matter; follow Rules of Hooks, effect cleanup, and composition over huge prop lists; prefer semantic DOM and Gamut a11y patterns. - **Tokens / ColorMode** — Semantic colors and gamut-styles tokens: see `.cursor/rules/gamut-library.mdc`. -- **Depth** — Full checklists and links: **gamut-library-authoring** skill (`.cursor/skills/gamut-library-authoring/SKILL.md`) and [reference.md](.cursor/skills/gamut-library-authoring/reference.md). +- **Depth** — Full checklists and links: **[`docs/agents/gamut-library-authoring.md`](docs/agents/gamut-library-authoring.md)** and `.cursor/skills/gamut-library-authoring/SKILL.md`. diff --git a/.cursor/rules/gamut-figma-rules.mdc b/.cursor/rules/gamut-figma-rules.mdc index 45b5e626df0..5cf8bbc8663 100644 --- a/.cursor/rules/gamut-figma-rules.mdc +++ b/.cursor/rules/gamut-figma-rules.mdc @@ -1,160 +1,19 @@ --- -description: Figma Dev Mode MCP rules +description: Figma → Gamut implementation (MCP / Dev Mode); mandatory checks before codegen alwaysApply: true --- -# Figma Dev Mode MCP Rules +# Figma → Gamut (editor rule) -You are an expert developer working with the Codecademy Gamut design system and Figma Dev Mode MCP integration. +**Canonical, tool-agnostic guide:** [`docs/agents/figma-from-design.md`](docs/agents/figma-from-design.md) — follow it in full when implementing from Figma. -When generating code from Figma designs, follow these rules: +## Mandatory (summary) -## MANDATORY Pre-Generation Steps +1. Inspect Figma layer / component metadata with your Figma integration; if icon or nested names are missing, infer from visuals, **verify icons exist** under `packages/gamut-icons`, then confirm with the user after generating code. +2. **Read** `packages/gamut-styles/src/variables/spacing.ts`, `colors.ts`, `typography.ts`, and `borderRadii.ts` before changing visuals. +3. **Search** `packages/gamut/src` (and patterns/icons/illustrations packages) for existing components; extend instead of duplicating. +4. Use **tokens and semantic colors** only (no ad hoc hex or raw `'NNpx'` where a token exists); use Emotion + `@codecademy/gamut-styles` patterns (`system.css`, `variant`, `styledOptions`, `variance.compose`) like the rest of Gamut. +5. Map UI to **`@codecademy/gamut`**, **`gamut-patterns`**, **`gamut-icons`**, **`gamut-illustrations`** per design; use real Figma/MCP assets when URLs are provided—no placeholders. +6. Meet **WCAG** expectations; align with `packages/styleguide/src/lib/Meta/Best practices.mdx`. -**BEFORE generating ANY code from Figma, you MUST:** - -1. **Inspect the Figma layer hierarchy**: - - - Call `get_metadata` WITH the nodeId to get parent component info - - Call `get_metadata` WITHOUT nodeId (empty string) to attempt to get CHILD layers - - **IMPORTANT**: Current limitation - `get_metadata` may not return nested child layer names (icons, nested components, etc.) - - **If child layer names are not available from tooling:** - - Analyze the screenshot and make your best guess about which icons/nested components are used - - Look for visual clues (user icon, gear icon, etc.) and match them to likely Gamut icon names - - Search the codebase to verify the icon exists (e.g., check for `PersonIcon`, `GearIcon`, etc. in `/packages/gamut-icons/src`) - - Generate the code using your best guess - - **AFTER generating the code**, ask the user to confirm the icons are correct - - Example: "I've generated the code using PersonIcon and GearIcon based on what I see in the screenshot. Can you confirm these are the correct icons from the Figma layers?" - - Map icon layer names to components in the codebase (e.g., "Regular/Interface/PersonIcon" → `PersonIcon`, "Mini/MiniCheckCircleIcon" → `MiniCheckCircleIcon`) - -2. **Read token files** (use read_file tool on ALL of these): - - - `/packages/gamut-styles/src/variables/spacing.ts` - - `/packages/gamut-styles/src/variables/colors.ts` - - `/packages/gamut-styles/src/variables/typography.ts` - - `/packages/gamut-styles/src/variables/borderRadii.ts` - -3. **Search for existing components** (use codebase_search): - - - Check if similar component exists in `/packages/gamut/src` - - If exists, extend it instead of creating new one - -4. **Understand the design system patterns**: - - Read example components like Badge, Tag, or Button - - Follow variance, system props, and styledOptions patterns - -## Asset Management - -- The Figma Dev Mode MCP Server provides an assets endpoint which can serve image and SVG assets -- **IMPORTANT**: do NOT use or create placeholders if a localhost source is provided - -## Component Usage - -- **IMPORTANT**: Always use components from `/packages` whenever possible -- Check if the Figma component name matches a Gamut component name and use that component -- **IMPORTANT**: All patterns should come from `/packages/gamut-patterns` - use the design's metadata to match the Figma component name to the pattern component -- **IMPORTANT**: All illustrations should come from `/packages/gamut-illustrations` - use the design's metadata to match the Figma component name to the illustration component -- **IMPORTANT**: All icons should come from `/packages/gamut-icons`: - - Try to get icon layer names from Figma metadata - - If layer names are not available, make your best guess based on the screenshot and verify the icon exists in the codebase - - Generate the code with your best guess, then confirm with the user after - - Map icon layer names to Gamut components (e.g., "Regular/Interface/PersonIcon" → `PersonIcon` from `@codecademy/gamut-icons`) - -## Styling Guidelines - STRICT RULES - -### ❌ NEVER Do This: - -```tsx -// NEVER use hardcoded hex colors -color: '#ffffff', -backgroundColor: '#000000', - -// NEVER use CSS properties not in system props -backdropFilter: 'blur(1px)', - -// NEVER use inline styles -style={{ fontSize: 14 }} -``` - -### ✅ ALWAYS Do This: - -```tsx -// ALWAYS use spacing tokens (4, 8, 12, 16, 24, 32, 40, 48, 64, 96) -height: 24, // from spacing.ts -width: 64, // from spacing.ts - -// ALWAYS use fontSize tokens (14, 16, 18, 20, 22, 26, 34, 44, 64) -fontSize: 14, // from typography.ts - -// ALWAYS use borderRadii tokens (none, sm, md, lg, xl, full) -borderRadius: 'full', // from borderRadii.ts - -// ALWAYS use semantic color tokens -bg: 'background', -color: 'text', -borderColor: 'border', - -// ALWAYS use system props via system.css or styled components -system.css({ - bg: 'black', - color: 'white', - borderRadius: 'full', -}) -``` - -### Token Mapping Rules: - -1. **Spacing/Sizing**: Map Figma values to closest token from `spacing.ts` - - - 4px, 8px, 12px, 16px, 24px, 32px, 40px, 48px, 64px, 96px - -2. **Colors**: Use semantic tokens OR core colors from `colors.ts` - - - Semantic: `background`, `text`, `border`, `text-secondary`, etc. - - Core: `navy`, `white`, `black`, `blue`, `green`, `red`, `yellow`, etc. - - For Background component only: use color names (navy, white, etc.) - -3. **Border Radius**: Use tokens from `borderRadii.ts` - - - none (0px), sm (2px), md (4px), lg (8px), xl (16px), full (999px) - -4. **Typography**: Use tokens from `typography.ts` - - - fontFamily: `accent`, `base`, `monospace`, `system` - - fontSize: 14, 16, 18, 20, 22, 26, 34, 44, 64 - - fontWeight: 400, 700 or `base`, `title` - - lineHeight: `base` (1.5), `title` (1.2), `spacedTitle` (1.3) - -5. **If no exact match**: Document in code comment why custom value needed - -### Emotion & CSS-in-JS Patterns: - -- **IMPORTANT**: Do not use inline styles -- Use `styled` from `@emotion/styled` -- Use `system.css()` for style objects -- Use `styledOptions` for styled component options -- Compose system props with `variance.compose()` - -## Accessibility & Best Practices - -- **IMPORTANT**: Follow WCAG requirements for accessibility -- Always follow best practices from `/packages/styleguide/src/lib/Meta/Best Practices.mdx` - -## Implementation - -- Use the CodeConnect implementation when available -- Generate clean, maintainable React code using TypeScript -- Follow the existing Gamut patterns and conventions - -## Post-Generation Validation - -After generating code, verify: - -- [ ] No hardcoded hex colors (`#` in color values) -- [ ] No hardcoded pixel strings (`'24px'` format) -- [ ] All spacing values match tokens from spacing.ts -- [ ] All colors use semantic tokens or theme colors -- [ ] All border radius uses borderRadii tokens -- [ ] Component follows Gamut patterns (variance, system props, styledOptions) -- [ ] No inline styles -- [ ] Uses emotion styled components +After codegen, verify: no stray `#` theme colors, token-aligned spacing/type/radius, no inline styles for token-level concerns, Storybook-aligned patterns. diff --git a/.cursor/rules/gamut-library.mdc b/.cursor/rules/gamut-library.mdc index 40f351a3e6d..9f3ff1738ce 100644 --- a/.cursor/rules/gamut-library.mdc +++ b/.cursor/rules/gamut-library.mdc @@ -19,6 +19,6 @@ alwaysApply: false - Read token sources in `packages/gamut-styles/src/variables/` before changing colors, spacing, type, or radii. Avoid hardcoded hex and non-token pixel values. - Use semantic color keys in component styles so they work under every ColorMode. - Add or update Storybook MDX in `packages/styleguide` for public API or behavior changes. -- For **component file layout, variance typing, React conventions, and Storybook file pairing** in `packages/gamut` / `packages/styleguide`, follow `.cursor/rules/gamut-component-building.mdc` and the **gamut-library-authoring** skill below. -- Follow `.cursor/rules/figma-rules.mdc` for icon/pattern/illustration package usage when implementing from design. -- For detailed workflows (tokens, styling, MDX, quality gates), use the **gamut-library-authoring** Cursor skill (`/gamut-library-authoring` in chat, or open `.cursor/skills/gamut-library-authoring/SKILL.md`). +- For **component file layout, variance typing, React conventions, and Storybook file pairing** in `packages/gamut` / `packages/styleguide`, follow `.cursor/rules/gamut-component-building.mdc` and **gamut-library-authoring** (see `docs/agents/gamut-library-authoring.md` and `.cursor/skills/gamut-library-authoring/SKILL.md` when using Cursor). +- Follow `.cursor/rules/gamut-figma-rules.mdc` and [`docs/agents/figma-from-design.md`](docs/agents/figma-from-design.md) for icon/pattern/illustration boundaries when implementing from design. +- For detailed workflows (tokens, styling, MDX, quality gates), use **[`docs/agents/gamut-library-authoring.md`](docs/agents/gamut-library-authoring.md)** and the **gamut-library-authoring** skill in `.cursor/skills/`. diff --git a/.cursor/skills/gamut-library-authoring/SKILL.md b/.cursor/skills/gamut-library-authoring/SKILL.md index b79184ecb19..8aaf2e11ca8 100644 --- a/.cursor/skills/gamut-library-authoring/SKILL.md +++ b/.cursor/skills/gamut-library-authoring/SKILL.md @@ -10,80 +10,8 @@ description: >- # Gamut library authoring -## Scope +**Canonical guide (tool-agnostic):** [`docs/agents/gamut-library-authoring.md`](../../../docs/agents/gamut-library-authoring.md) -Work lives under this monorepo: `packages/gamut`, `packages/gamut-styles`, `packages/gamut-patterns`, `packages/gamut-icons`, `packages/gamut-illustrations`, `packages/styleguide`. Do not treat this skill as guidance for app repos that only install `@codecademy/gamut`. +**Repository index:** [`AGENTS.md`](../../../AGENTS.md) -**Cursor rules:** `.cursor/rules/gamut-library.mdc` (tokens, ColorMode, Figma boundaries) and `.cursor/rules/gamut-component-building.mdc` (component structure, TS, React, Storybook pairing for `packages/gamut` + `packages/styleguide`). - -## Architecture - -- Components: `packages/gamut/src` — extend existing components before adding overlapping primitives. -- Patterns: `packages/gamut-patterns` — page-level compositions. -- Icons / illustrations: `packages/gamut-icons`, `packages/gamut-illustrations`. -- Tokens: single source in `packages/gamut-styles/src/variables/` (`spacing`, `colors`, `typography`, `borderRadii`). No ad-hoc hex or arbitrary pixel strings where a token exists. - -## Component structure (`packages/gamut`) - -- Default: one **PascalCase** folder with `index.tsx` (or `index.ts` re-exporting siblings) and `__tests__/.test.tsx`. -- Large UIs: add `shared/` for types/styles/variants, `elements/` for presentational pieces, or domain subfolders (`layout/`, `inputs/`) following `Button/`, `Form/`, `BarChart/`, `GridForm/`. -- **Barrel:** every public component or type consumers need must be exported from `packages/gamut/src/index.tsx`. Use `export type { … }` when values should not be re-exported. - -## Storybook and snippets (`packages/styleguide`) - -- Place docs under `packages/styleguide/src/lib/` in the atomic layer that matches the component (Atoms, Molecules, Organisms, Layouts, Typography, etc.); folder structure mirrors the Storybook sidebar. -- For each component: **`ComponentName.stories.tsx`** + **`ComponentName.mdx`** (kebab-case filenames) in that component’s folder. -- VS Code (repo root): type **`component-story`**, **`component-doc`**, or **`toc-story`** to insert templates from `.vscode/stories.code-snippets`. -- Include a flagship/default story, **`Controls`** where appropriate, and prose in MDX (`parameters` with `title`, `subtitle`, `design`, `status`, `source.githubLink`). Prefer examples that **Show code** as copy-paste-ready (avoid heavy indirection in the snippet users copy). -- Meta guides: `packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/About.mdx`, `Component story documentation.mdx`, `Component code examples.mdx`. - -## TypeScript and variance - -- **Derive props from styling:** after `const x = variance.compose(system.space, …)` or `variant({ … })`, extend with `StyleProps`. Chain multiple `StyleProps` when variants and states are separate (see `Anchor`, `Tag/types.tsx`). -- **Derive from styled component:** `export type FooProps = ComponentProps` for Emotion roots built with `styled('tag', styledOptions<'tag'>())(…)`. -- **Compose with bases:** e.g. `ButtonBaseProps & ComponentProps` so system props and the underlying component stay aligned. -- **Variants:** use **discriminated unions** (`export type Props = A | B | C`) when `variant` or mode changes required props. Use **`never`** on disallowed props per branch so invalid combinations fail at compile time (`Tag`, `Badge` standard vs `custom`). -- **DOM handlers:** prefer `HTMLProps['onClick']`, `ComponentProps['onClick']`, etc., over `Function` or `any`. -- **Shared types:** reuse `WithChildrenProp`, `IconComponentType`, `Partial` from `packages/gamut/src/utils/types.ts`; follow generics like `InlineIconButtonProps` in `Button/shared/types.ts` for polymorphic wrappers. -- **Gold components:** adding a variant usually means a new union member and fixing consumers; avoid new `as any`; reserve exceptions for documented edge cases only. - -## React - -- Match **local file style** (`React.FC` is common in Gamut). -- Use **`forwardRef`** when consumers or libraries need the underlying DOM ref. -- **Rules of Hooks**; name shared logic `use*`. Effects: correct dependency arrays, cleanup for subscriptions/timers; avoid mirroring props into state when derived state or a `key` reset is clearer ([You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)). -- **Memoization:** `useMemo` / `useCallback` / `React.memo` when profiling or stable identity is required—not by default. -- **Composition:** prefer `children` and subcomponents over flat prop explosion; page templates belong in `gamut-patterns`. -- **Lists:** stable keys; avoid index keys for reorderable/dynamic lists. -- **Forms:** be explicit about controlled vs uncontrolled behavior; align with `ConnectedForm` / `GridForm` when touching those flows. -- **Accessibility:** semantic elements first (`button`, `a`, `label` + `htmlFor`); use `aria-*` for bespoke widgets. See styleguide Meta and `Best practices.mdx`. - -## Styling - -- Emotion + `@codecademy/gamut-styles`: `css`, `variant`, `states`, system props via `system.css`, `styledOptions`, variance `compose` where the codebase already does. -- Semantic color keys only in component styles so components work in any ColorMode. -- Avoid nested tag selectors and `${GamutComponent}` selectors; prefer system props, layout primitives (`FlexBox`, `GridBox`), and explicit wrappers. - -## ColorMode and Background - -- When changing theme behavior, read `packages/styleguide/src/lib/Foundations/ColorMode/ColorMode.mdx` and `packages/gamut-styles/src/ColorMode.tsx` / `Background` implementation. -- Components should consume **semantic** aliases (`text`, `background`, `primary`, etc.), not raw palette names in ways that break mode switching. - -## Documentation and AI-facing MDX - -- New or changed components need Storybook MDX under `packages/styleguide`; keep props tables accurate ([Storybook Autodocs](https://storybook.js.org/docs/writing-docs/autodocs) where used). -- Cross-link [published Storybook](https://gamut.codecademy.com/) paths for reviewers and agents. -- Human-oriented overview for contributors: `packages/styleguide/src/lib/Meta/Gamut writing guide/Building components in Gamut.mdx` (if present). - -## Accessibility - -- Follow WCAG-minded patterns; use styleguide Meta and per-component pages. Prefer built-in Gamut props and semantics over bespoke DOM. - -## Quality gates - -- Respect `eslint-plugin-gamut` and repo ESLint config for touched packages. -- Add or update stories and visual/docs coverage when behavior or public API changes. - -## Further reading - -See [reference.md](reference.md) for token paths, exemplar source files, snippet names, Meta MDX paths, and Figma rule alignment. +When this repo is open in **Cursor**, project rules also apply: `.cursor/rules/gamut-library.mdc`, `.cursor/rules/gamut-component-building.mdc`, and `.cursor/rules/gamut-figma-rules.mdc` (Figma → code). Use the markdown files above for full depth; this skill exists so assistants discover the workflow. diff --git a/.cursor/skills/gamut-library-authoring/reference.md b/.cursor/skills/gamut-library-authoring/reference.md deleted file mode 100644 index d40b0f6cc7f..00000000000 --- a/.cursor/skills/gamut-library-authoring/reference.md +++ /dev/null @@ -1,54 +0,0 @@ -# Gamut library authoring — reference - -## Token files (read before changing visuals) - -- `packages/gamut-styles/src/variables/spacing.ts` -- `packages/gamut-styles/src/variables/colors.ts` -- `packages/gamut-styles/src/variables/typography.ts` -- `packages/gamut-styles/src/variables/borderRadii.ts` - -## TypeScript and structure exemplars (`packages/gamut/src`) - -| Topic | Files | -| --- | --- | -| Discriminated unions + `never` | `Tag/types.tsx` | -| Variant branches + conflicting props | `Badge/index.tsx` | -| `StyleProps` + `ComponentProps` intersection | `Button/shared/types.ts`, `ButtonBase/ButtonBase.tsx` | -| `ComponentProps` | `Form/elements/Form.tsx` | -| Multiple `StyleProps` + anchor variants | `Anchor/index.tsx` | -| `variance.compose` for layout system props | `Box/props.ts`, `Layout/LayoutGrid.tsx` | - -## VS Code snippets (repo root) - -Prefix in editor → choose snippet from `.vscode/stories.code-snippets`: - -- `component-story` — `ComponentName.stories.tsx` CSF template -- `component-doc` — `ComponentName.mdx` doc template -- `toc-story` — table-of-contents category page - -## Meta / Storybook MDX (human docs) - -- Contributing (props JSDoc, tests): `packages/styleguide/src/lib/Meta/Contributing.mdx` -- Stories guide hub: `packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/About.mdx` -- MDX structure: `…/Stories/Component story documentation.mdx` -- `.stories.tsx` patterns: `…/Stories/Component code examples.mdx` -- Building components in Gamut (overview): `packages/styleguide/src/lib/Meta/Gamut writing guide/Building components in Gamut.mdx` -- Meta best practices: `packages/styleguide/src/lib/Meta/Best practices.mdx` -- ColorMode / Background: `packages/styleguide/src/lib/Foundations/ColorMode/ColorMode.mdx` -- System compose (published): Storybook path `/docs/foundations-system-compose--page` on [gamut.codecademy.com](https://gamut.codecademy.com/) - -## Figma and package boundaries - -Project rule `.cursor/rules/figma-rules.mdc` maps Figma output to `gamut`, `gamut-patterns`, `gamut-icons`, `gamut-illustrations` and token file paths. Align new components with that rule. - -## When adding a component (checklist) - -1. Search `packages/gamut/src` for something close; extend if possible. -2. Use semantic colors and token scales from `gamut-styles` variables. -3. Model props with `StyleProps` / `ComponentProps` / unions per SKILL.md; export via `packages/gamut/src/index.tsx`. -4. Add `*.stories.tsx` + `*.mdx` under `packages/styleguide/src/lib///`. -5. Run package-level lint/tests for touched workspaces. - -## Theme / mode changes - -Document Storybook coverage and any breaking changes for consumers. Platform-specific theme docs live under styleguide Foundations; coordinate with `ColorMode` and theme providers. diff --git a/.github/scripts/validate-cursor-plugins-helpers.mjs b/.github/scripts/validate-agent-plugins-helpers.mjs similarity index 90% rename from .github/scripts/validate-cursor-plugins-helpers.mjs rename to .github/scripts/validate-agent-plugins-helpers.mjs index 9824757f722..3b1e8afd31b 100644 --- a/.github/scripts/validate-cursor-plugins-helpers.mjs +++ b/.github/scripts/validate-agent-plugins-helpers.mjs @@ -1,10 +1,9 @@ /** - * Pure helpers for Cursor plugin layout validation (unit-tested). - * @see validate-cursor-plugins.mjs + * Pure helpers for agent plugin layout validation (unit-tested). + * @see validate-agent-plugins.mjs */ -export const SEMVER = - /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/; +export const SEMVER = /^\d+\.\d+\.\d+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$/; /** @param {string} key */ export function escapeRe(key) { diff --git a/.github/scripts/validate-cursor-plugins-helpers.test.mjs b/.github/scripts/validate-agent-plugins-helpers.test.mjs similarity index 96% rename from .github/scripts/validate-cursor-plugins-helpers.test.mjs rename to .github/scripts/validate-agent-plugins-helpers.test.mjs index f7ce180b52a..377f5ef7580 100644 --- a/.github/scripts/validate-cursor-plugins-helpers.test.mjs +++ b/.github/scripts/validate-agent-plugins-helpers.test.mjs @@ -6,7 +6,7 @@ import { extractFrontmatterBlockFromText, fmHasKey, SEMVER, -} from './validate-cursor-plugins-helpers.mjs'; +} from './validate-agent-plugins-helpers.mjs'; describe('cmpSemver', () => { it('returns 0 for equal versions', () => { @@ -64,7 +64,7 @@ describe('escapeRe', () => { describe('extractFrontmatterBlockFromText', () => { it('returns block between first and second ---', () => { const r = extractFrontmatterBlockFromText( - '---\ndescription: ok\n---\n# body\n', + '---\ndescription: ok\n---\n# body\n' ); assert.ok('block' in r); assert.equal(r.block, 'description: ok'); diff --git a/.github/scripts/validate-cursor-plugins.mjs b/.github/scripts/validate-agent-plugins.mjs similarity index 84% rename from .github/scripts/validate-cursor-plugins.mjs rename to .github/scripts/validate-agent-plugins.mjs index 57aa97776f5..f7dd2ea88cd 100755 --- a/.github/scripts/validate-cursor-plugins.mjs +++ b/.github/scripts/validate-agent-plugins.mjs @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * Validate Gamut Cursor plugin layout for CI. + * Validate Gamut agent plugin layout for CI (Cursor marketplace + monorepo .cursor + dual manifests). * @see https://cursor.com/docs/reference/plugins.md */ @@ -14,7 +14,7 @@ import { extractFrontmatterBlockFromText, fmHasKey, SEMVER, -} from './validate-cursor-plugins-helpers.mjs'; +} from './validate-agent-plugins-helpers.mjs'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const ROOT = path.resolve(__dirname, '../..'); @@ -78,7 +78,7 @@ function validatePrPluginVersionBumps() { const head = process.env.CURSOR_PR_HEAD_SHA?.trim(); if (!base || !head) { errors.push( - 'version bump check: set CURSOR_PR_BASE_SHA and CURSOR_PR_HEAD_SHA (e.g. from github.event.pull_request)', + 'version bump check: set CURSOR_PR_BASE_SHA and CURSOR_PR_HEAD_SHA (e.g. from github.event.pull_request)' ); return errors; } @@ -111,7 +111,7 @@ function validatePrPluginVersionBumps() { const headRaw = gitShow(head, manifestPosix); if (!headRaw) { errors.push( - `plugin '${pname}': cannot read ${manifestPosix} at PR head (required for version bump check)`, + `plugin '${pname}': cannot read ${manifestPosix} at PR head (required for version bump check)` ); continue; } @@ -125,7 +125,7 @@ function validatePrPluginVersionBumps() { } if (typeof headVer !== 'string' || !SEMVER.test(headVer)) { errors.push( - `plugin '${pname}': PR head plugin.json must have valid semver version`, + `plugin '${pname}': PR head plugin.json must have valid semver version` ); continue; } @@ -139,20 +139,23 @@ function validatePrPluginVersionBumps() { baseVer = JSON.parse(baseRaw).version; } catch { errors.push( - `plugin '${pname}': invalid plugin.json on base ${base.slice(0, 7)}`, + `plugin '${pname}': invalid plugin.json on base ${base.slice(0, 7)}` ); continue; } if (typeof baseVer !== 'string' || !SEMVER.test(baseVer)) { errors.push( - `plugin '${pname}': base plugin.json must have valid semver version`, + `plugin '${pname}': base plugin.json must have valid semver version` ); continue; } if (cmpSemver(headVer, baseVer) <= 0) { errors.push( - `plugin '${pname}': files under ${src}/ changed vs ${base.slice(0, 7)} but version was not bumped (${baseVer} → ${headVer}); increase semver in .cursor-plugin/plugin.json`, + `plugin '${pname}': files under ${src}/ changed vs ${base.slice( + 0, + 7 + )} but version was not bumped (${baseVer} → ${headVer}); increase semver in .cursor-plugin/plugin.json` ); } } @@ -229,7 +232,7 @@ function validateMarketplace() { } if (!fs.existsSync(manifest)) { errors.push( - `plugin '${pname}': missing ${src}/.cursor-plugin/plugin.json`, + `plugin '${pname}': missing ${src}/.cursor-plugin/plugin.json` ); continue; } @@ -246,7 +249,7 @@ function validateMarketplace() { errors.push(`plugin '${pname}': plugin.json must include string name`); } else if (pm.name !== pname) { errors.push( - `plugin '${pname}': plugin.json name '${pm.name}' must match marketplace entry`, + `plugin '${pname}': plugin.json name '${pm.name}' must match marketplace entry` ); } @@ -254,11 +257,43 @@ function validateMarketplace() { if (ver != null) { if (typeof ver !== 'string' || !SEMVER.test(ver)) { errors.push( - `plugin '${pname}': plugin.json version must be semver (e.g. 1.0.0), got '${ver}'`, + `plugin '${pname}': plugin.json version must be semver (e.g. 1.0.0), got '${ver}'` ); } } else { - errors.push(`plugin '${pname}': plugin.json should include semver version`); + errors.push( + `plugin '${pname}': plugin.json should include semver version` + ); + } + + const claudeManifest = path.join( + pluginRoot, + '.claude-plugin', + 'plugin.json' + ); + if (!fs.existsSync(claudeManifest)) { + errors.push( + `plugin '${pname}': missing ${src}/.claude-plugin/plugin.json (keep in sync with Cursor manifest)` + ); + } else { + let cm; + try { + cm = JSON.parse(fs.readFileSync(claudeManifest, 'utf8')); + } catch (e) { + errors.push(`plugin '${pname}': invalid Claude plugin.json (${e})`); + } + if (cm && typeof cm === 'object') { + if (cm.name !== pm.name) { + errors.push( + `plugin '${pname}': .claude-plugin/plugin.json name must match .cursor-plugin name` + ); + } + if (cm.version !== pm.version) { + errors.push( + `plugin '${pname}': .claude-plugin/plugin.json version must match .cursor-plugin version` + ); + } + } } const rulesDir = path.join(pluginRoot, 'rules'); @@ -269,7 +304,7 @@ function validateMarketplace() { fs.existsSync(skillsDir) && fs.statSync(skillsDir).isDirectory(); if (!hasRules && !hasSkills) { errors.push( - `plugin '${pname}': expected rules/ and/or skills/ under ${src}`, + `plugin '${pname}': expected rules/ and/or skills/ under ${src}` ); } @@ -287,7 +322,10 @@ function validateMarketplace() { errors.push(result.error); } else if (!fmHasKey(result.block, 'description')) { errors.push( - `${path.relative(ROOT, mdc)}: frontmatter must include description (Cursor rules)`, + `${path.relative( + ROOT, + mdc + )}: frontmatter must include description (Cursor rules)` ); } } @@ -310,12 +348,12 @@ function validateMarketplace() { } if (!fmHasKey(result.block, 'name')) { errors.push( - `${path.relative(ROOT, sm)}: frontmatter must include name`, + `${path.relative(ROOT, sm)}: frontmatter must include name` ); } if (!fmHasKey(result.block, 'description')) { errors.push( - `${path.relative(ROOT, sm)}: frontmatter must include description`, + `${path.relative(ROOT, sm)}: frontmatter must include description` ); } } @@ -343,7 +381,10 @@ function validateMonorepoCursor() { errors.push(result.error); } else if (!fmHasKey(result.block, 'description')) { errors.push( - `${path.relative(ROOT, mdc)}: frontmatter must include description (Cursor rules)`, + `${path.relative( + ROOT, + mdc + )}: frontmatter must include description (Cursor rules)` ); } } @@ -364,12 +405,12 @@ function validateMonorepoCursor() { } if (!fmHasKey(result.block, 'name')) { errors.push( - `${path.relative(ROOT, sm)}: frontmatter must include name`, + `${path.relative(ROOT, sm)}: frontmatter must include name` ); } if (!fmHasKey(result.block, 'description')) { errors.push( - `${path.relative(ROOT, sm)}: frontmatter must include description`, + `${path.relative(ROOT, sm)}: frontmatter must include description` ); } } @@ -387,7 +428,7 @@ if (process.argv.includes(PR_VERSION_BUMPS)) { console.error(`\n${prErrors.length} validation issue(s).`); process.exit(1); } - console.log('Cursor plugins: PR semver bump check OK.'); + console.log('Agent plugins: PR semver bump check OK.'); } else { const errors = [...validateMarketplace(), ...validateMonorepoCursor()]; if (errors.length) { @@ -396,6 +437,6 @@ if (process.argv.includes(PR_VERSION_BUMPS)) { process.exit(1); } console.log( - 'Cursor config: marketplace, plugins, and monorepo .cursor rules/skills OK.', + 'Agent plugins: marketplace, plugins, and monorepo .cursor rules/skills OK.' ); } diff --git a/.github/workflows/cursor-plugins.yml b/.github/workflows/cursor-plugins.yml index d4e6d988ccc..cfd92b143b8 100644 --- a/.github/workflows/cursor-plugins.yml +++ b/.github/workflows/cursor-plugins.yml @@ -1,26 +1,30 @@ -name: Cursor plugins +name: Agent plugins on: pull_request: paths: - '.cursor/**' - - 'gamut-cursor-plugins/**' + - 'packages/agent-plugin/**' + - 'docs/agents/**' + - 'AGENTS.md' - '.cursor-plugin/**' - '.github/workflows/cursor-plugins.yml' - - '.github/scripts/validate-cursor-plugins.mjs' - - '.github/scripts/validate-cursor-plugins-helpers.mjs' - - '.github/scripts/validate-cursor-plugins-helpers.test.mjs' + - '.github/scripts/validate-agent-plugins.mjs' + - '.github/scripts/validate-agent-plugins-helpers.mjs' + - '.github/scripts/validate-agent-plugins-helpers.test.mjs' push: branches: - main paths: - '.cursor/**' - - 'gamut-cursor-plugins/**' + - 'packages/agent-plugin/**' + - 'docs/agents/**' + - 'AGENTS.md' - '.cursor-plugin/**' - '.github/workflows/cursor-plugins.yml' - - '.github/scripts/validate-cursor-plugins.mjs' - - '.github/scripts/validate-cursor-plugins-helpers.mjs' - - '.github/scripts/validate-cursor-plugins-helpers.test.mjs' + - '.github/scripts/validate-agent-plugins.mjs' + - '.github/scripts/validate-agent-plugins-helpers.mjs' + - '.github/scripts/validate-agent-plugins-helpers.test.mjs' workflow_dispatch: concurrency: @@ -41,13 +45,13 @@ jobs: - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 with: node-version-file: '.nvmrc' - - name: Test Cursor plugin validator helpers - run: node --test .github/scripts/validate-cursor-plugins-helpers.test.mjs - - name: Validate Cursor config (marketplace, plugins, .cursor) - run: node .github/scripts/validate-cursor-plugins.mjs + - name: Test agent plugin validator helpers + run: node --test .github/scripts/validate-agent-plugins-helpers.test.mjs + - name: Validate agent plugins (marketplace, .cursor, dual manifests) + run: node .github/scripts/validate-agent-plugins.mjs - name: Validate plugin semver bumps vs PR base if: github.event_name == 'pull_request' env: CURSOR_PR_BASE_SHA: ${{ github.event.pull_request.base.sha }} CURSOR_PR_HEAD_SHA: ${{ github.event.pull_request.head.sha }} - run: node .github/scripts/validate-cursor-plugins.mjs --pr-version-bumps + run: node .github/scripts/validate-agent-plugins.mjs --pr-version-bumps diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index f70178055d3..cad3529fe9e 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,5 +1,8 @@ import micromatch from 'micromatch'; +/** Shell-safe argument for paths that may contain spaces (lint-staged runs commands via a shell). */ +const shellArg = (file) => JSON.stringify(file); + export default { // Use custom function to avoid overlaps that could cause race conditions [`*`]: (allChanges) => { @@ -22,14 +25,16 @@ export default { if (eslintFiles.length) { commands.push( - `node_modules/@codecademy/eslint-config/bin/eslint-fix.js ${eslintFiles.join( - ' ' - )}` + `node_modules/@codecademy/eslint-config/bin/eslint-fix.js ${eslintFiles + .map(shellArg) + .join(' ')}` ); } // Run nx format, which will run prettier - commands.push(`nx format:write --files ${allChanges}`); + commands.push( + `nx format:write --files ${allChanges.map(shellArg).join(' ')}` + ); return commands; }, diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000000..8c293e827e2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,28 @@ +# Coding agent instructions (Gamut) + +This repository ships shared guidance for **coding agents and assistants** (Cursor, Claude Code, Copilot, and similar tools). Human-readable contributor docs also live in Storybook under `packages/styleguide`. + +## Canonical reference + +Detailed, tool-agnostic guides: + +| Topic | Document | +| --------------------------------------- | -------------------------------------------------------------------------------- | +| Design system authoring (this monorepo) | [docs/agents/gamut-library-authoring.md](docs/agents/gamut-library-authoring.md) | +| Consuming Gamut in applications | [docs/agents/gamut-consumer.md](docs/agents/gamut-consumer.md) | +| Accessibility | [docs/agents/gamut-accessibility.md](docs/agents/gamut-accessibility.md) | +| ColorMode, Background, themes | [docs/agents/gamut-theming.md](docs/agents/gamut-theming.md) | +| Figma → code (Gamut) | [docs/agents/figma-from-design.md](docs/agents/figma-from-design.md) | + +Browse on GitHub: [docs/agents on `main`](https://github.com/Codecademy/gamut/tree/main/docs/agents) (same files as in this clone; URL source of truth: [docs/agents/canonical-urls.json](docs/agents/canonical-urls.json)). Folder hub: [docs/agents/README.md](docs/agents/README.md). + +## When you are in this monorepo + +- Library work: follow **gamut-library-authoring** and the project rules under [`.cursor/rules/`](.cursor/rules/) when your environment loads them (e.g. Cursor). +- Optional packaged rules/skills for editors: [`packages/agent-plugin/`](packages/agent-plugin/) (plugin name **`gamut`**) with Cursor and Claude manifests. +- **Figma MCP (local):** team default for Cursor lives in [`.cursor/mcp.json`](.cursor/mcp.json). Setup, verification, and prompts are documented in Storybook under **Meta → MCP → Figma MCP** ([source `Figma MCP.mdx`](packages/styleguide/src/lib/Meta/MCP/Figma%20MCP.mdx)). Any MCP-capable client can use the same Figma server URL; do not commit secrets or personal MCP credentials into tracked config. + +## When you are in an application repo + +- Use the **`gamut`** agent plugin (or open the same markdown files on GitHub under `docs/agents/`) for consumption, accessibility, and theming guidance. +- Do not apply library-authoring workflows meant only for the Gamut source tree. diff --git a/docs/agents/README.md b/docs/agents/README.md new file mode 100644 index 00000000000..cf7fbf96f5c --- /dev/null +++ b/docs/agents/README.md @@ -0,0 +1,18 @@ +# Agent guidance (Gamut) + +Markdown sources for tool-agnostic instructions live in **this directory**. The full topic index and monorepo vs app context are in **[AGENTS.md](../AGENTS.md)** (repo root). + +For the packaged **gamut** editor plugin (Cursor / Claude), see **[packages/agent-plugin](../packages/agent-plugin/README.md)**. + +**Full path and URL tables** (in-repo map + external docs): [references.md](./references.md). + +## Conventions + +- **Canonical prose** lives in **`docs/agents/*.md`** (this folder). That is the single source of truth for long-form guidance. +- **`.cursor/skills/*`** (monorepo) — thin discovery files with links into the repo; regenerated from [`monorepo-skills.manifest.json`](monorepo-skills.manifest.json) via `yarn generate:agent-skills`. +- **`packages/agent-plugin/skills/*`** — thin skills for the shipped **`gamut`** plugin; regenerated from [`../../packages/agent-plugin/skills/skills.manifest.json`](../../packages/agent-plugin/skills/skills.manifest.json). GitHub links use [`canonical-urls.json`](canonical-urls.json). +- **Rules** (`.mdc` under `.cursor/rules/` or `packages/agent-plugin/rules/`) — short pointers plus non-negotiable bullets; depth stays in `docs/agents/` where applicable. + +## Maintainer note + +**Storybook** pages that link to GitHub (e.g. `blob/main` URLs) should stay aligned with [`canonical-urls.json`](canonical-urls.json) when org, repo, or branch conventions change. diff --git a/docs/agents/canonical-urls.json b/docs/agents/canonical-urls.json new file mode 100644 index 00000000000..099a459e19e --- /dev/null +++ b/docs/agents/canonical-urls.json @@ -0,0 +1,5 @@ +{ + "githubRepo": "https://github.com/Codecademy/gamut", + "docsAgentsTreeOnMain": "https://github.com/Codecademy/gamut/tree/main/docs/agents", + "docsAgentsBlobBaseOnMain": "https://github.com/Codecademy/gamut/blob/main/docs/agents" +} diff --git a/docs/agents/figma-from-design.md b/docs/agents/figma-from-design.md new file mode 100644 index 00000000000..483ac3703fa --- /dev/null +++ b/docs/agents/figma-from-design.md @@ -0,0 +1,69 @@ +# Figma → Gamut implementation + +Use this when generating or adapting UI from **Figma** (including Dev Mode and Figma-connected MCP tooling) into the Codecademy **Gamut** design system. + +## Mandatory pre-generation steps + +**Before writing code:** + +1. **Inspect the Figma layer hierarchy** using your Figma tooling (metadata APIs, layer tree, or equivalent). Obtain parent component info and child layers where the integration exposes them. + + - If nested child names (icons, nested components) are missing from metadata, use the design screenshot, infer likely Gamut icons, **search the repo** under `packages/gamut-icons` to confirm exports, then ask the user to confirm icon choices after generating code. + - Map Figma icon layers to package exports (e.g. `PersonIcon`, `MiniCheckCircleIcon` from `@codecademy/gamut-icons`). + +2. **Read these token sources** in the Gamut monorepo (in full): + + - `packages/gamut-styles/src/variables/spacing.ts` + - `packages/gamut-styles/src/variables/colors.ts` + - `packages/gamut-styles/src/variables/typography.ts` + - `packages/gamut-styles/src/variables/borderRadii.ts` + +3. **Search the codebase** for an existing component or pattern before adding a new primitive. Prefer extending `packages/gamut/src` over duplicating behavior. + +4. **Match existing patterns** — review exemplars such as Badge, Tag, or Button for variance, system props, and `styledOptions` usage. + +## Assets + +- If Figma or MCP serves real image/SVG assets (including local server URLs), **use those assets**; do not substitute placeholders when a concrete asset URL is provided. + +## Package boundaries + +- **Components:** `packages/gamut` — prefer documented components when names align with Figma. +- **Patterns:** `packages/gamut-patterns` — page-level compositions; match Figma metadata to pattern components when applicable. +- **Illustrations:** `packages/gamut-illustrations`. +- **Icons:** `packages/gamut-icons` — verify exports exist before importing. + +## Styling rules (strict) + +### Do not + +- Hardcode hex colors for theme surfaces. +- Use CSS properties not supported by Gamut system props without justification. +- Use React `style={{}}` for token-level styling where system props or `css()` apply. + +### Do + +- Map spacing to tokens from `spacing.ts` (e.g. 4, 8, 12, 16, 24, 32, 40, 48, 64, 96). +- Map type to `typography.ts` (font sizes 14, 16, 18, 20, 22, 26, 34, 44, 64; families `accent`, `base`, `monospace`, `system`; weights and line heights as defined there). +- Map radii to `borderRadii.ts` (`none`, `sm`, `md`, `lg`, `xl`, `full`). +- Use **semantic** color keys (`text`, `background`, `primary`, `border`, …) or documented core names per `colors.ts`; for `Background`, use supported surface names as documented. +- Use Emotion + `@codecademy/gamut-styles`: `styled`, `system.css()`, `variant`, `states`, `variance.compose()` consistent with surrounding code. + +If no token fits, add a short comment in code explaining why. + +## Accessibility + +- Meet WCAG-oriented expectations; align with `packages/styleguide/src/lib/Meta/Best practices.mdx` and component Storybook notes. + +## After generating code (checklist) + +- [ ] No unintended raw `#` color literals for themed UI +- [ ] No ad hoc `'NNpx'` strings where a spacing or typography token exists +- [ ] Spacing, color, radius, and type align with token files +- [ ] Components follow Gamut patterns (variance, system props, `styledOptions`) +- [ ] No inline styles for design-token-level concerns +- [ ] Uses Emotion styled / `css` patterns like the rest of Gamut + +## CodeConnect + +Prefer CodeConnect-generated implementations when available for a component. diff --git a/gamut-cursor-plugins/gamut-a11y/skills/gamut-accessibility/SKILL.md b/docs/agents/gamut-accessibility.md similarity index 91% rename from gamut-cursor-plugins/gamut-a11y/skills/gamut-accessibility/SKILL.md rename to docs/agents/gamut-accessibility.md index 0a865ebddd4..91d313015b3 100644 --- a/gamut-cursor-plugins/gamut-a11y/skills/gamut-accessibility/SKILL.md +++ b/docs/agents/gamut-accessibility.md @@ -1,14 +1,3 @@ ---- -name: gamut-accessibility -description: >- - Reviews and implements accessible UI with @codecademy/gamut: WCAG-oriented - patterns, keyboard and focus, documented Gamut components, and MDN-grounded - patterns when building custom widgets. Use when auditing accessibility, - implementing bespoke controls, or fixing a11y bugs in Gamut consumer apps. - Pair with codecademy-gamut (core) and codecademy-gamut-themes for tokens and - color context. ---- - # Gamut accessibility ## Principles @@ -43,7 +32,7 @@ Gamut’s styleguide ([Form elements — About](https://gamut.codecademy.com/?pa If no Gamut primitive fits, base behavior on **[MDN accessibility](https://developer.mozilla.org/en-US/docs/Web/Accessibility)** and **[ARIA](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA)** guidance, and on **[keyboard-navigable JavaScript widgets](https://developer.mozilla.org/en-US/docs/Web/Accessibility/Keyboard-navigable_JavaScript_widgets)**. Prefer native HTML elements and built-in keyboard behavior; add ARIA only when semantics or state cannot be expressed otherwise (see MDN’s ARIA guides for roles, properties, and live regions). -Style custom UI with **`@codecademy/gamut-styles`** semantic tokens and **`ColorMode` / `Background`** (**codecademy-gamut-themes**), not one-off hex or raw theme object drilling. +Style custom UI with **`@codecademy/gamut-styles`** semantic tokens and **`ColorMode` / `Background`** per [gamut-theming.md](./gamut-theming.md), not one-off hex or raw theme object drilling. ### React focus management diff --git a/docs/agents/gamut-consumer.md b/docs/agents/gamut-consumer.md new file mode 100644 index 00000000000..e9ba4b4faab --- /dev/null +++ b/docs/agents/gamut-consumer.md @@ -0,0 +1,91 @@ +# Gamut consumer (applications) + +## When to use + +- Application code importing `@codecademy/gamut` or `@codecademy/gamut-styles`. +- Layout, spacing, typography, and component composition with design tokens. + +## Providers (minimal) + +Ensure the app supplies appropriate color/theme context at the root. For `ColorMode`, `Background`, `useColorMode`, platform themes, and troubleshooting contrast or mode bugs, use [gamut-theming.md](./gamut-theming.md) and [ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page). + +## Semantic colors and tokens + +- Semantic names describe **role** (`text`, `background`, `primary`, `secondary`, …) and resolve per ColorMode. See [Best practices / ColorMode](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page). +- Use `css({ color: 'primary', p: 4 })`, `variant`, and `states` from `@codecademy/gamut-styles` with those semantic keys. +- Prefer `themed(...)` or variants APIs over ad-hoc `theme.colors` access when eslint recommends it. + +## System props and layout + +- Use `Box`, `FlexBox`, `GridBox` with system props; support responsive maps (`{ _: value, md: value }`) and arrays with sparse breakpoints. +- Docs: [Responsive properties](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page), [system compose](https://gamut.codecademy.com/?path=/docs-foundations-system-compose--page). + +## Anti-patterns + +- No `style={{}}` for design-token-level styling where system props or `css()` apply. +- No tag-wide or `*` selectors; no `${Box}`-style nested selectors targeting Gamut internals—use props and layout wrappers. + +## Lint + +If the repo enables `eslint-plugin-gamut`, expect rules such as `no-inline-style`, `no-css-standalone`, and `gamut-import-paths`. Match fixes to Gamut patterns. + +## Related guides + +- [gamut-accessibility.md](./gamut-accessibility.md) — forms, dialogs, custom widgets, WCAG reviews, focus/ARIA. +- [gamut-theming.md](./gamut-theming.md) — multi-mode layouts, `Background`, `background-current`, hooks, platform themes. + +--- + +## Storybook URLs and sources + +### Published Storybook + +Base URL: [https://gamut.codecademy.com/](https://gamut.codecademy.com/) + +| Topic | URL query | +| ----------------------- | ------------------------------------------------------------ | +| Meta / Best practices | `?path=/docs-meta-best-practices--page` | +| ColorMode | `?path=/docs-foundations-colormode--page` | +| System compose | `?path=/docs-foundations-system-compose--page` | +| Responsive system props | `?path=/docs-foundations-system-responsive-properties--page` | + +### Source of truth in the Gamut repo + +If you have the monorepo checked out: + +- Best practices MDX: `packages/styleguide/src/lib/Meta/Best practices.mdx` +- ColorMode MDX: `packages/styleguide/src/lib/Foundations/ColorMode/ColorMode.mdx` + +### Package imports + +- Components: `@codecademy/gamut` +- Styles / providers / hooks: `@codecademy/gamut-styles` +- Patterns / icons / illustrations: `@codecademy/gamut-patterns`, `@codecademy/gamut-icons`, `@codecademy/gamut-illustrations` when applicable + +### Styled component example + +```tsx +import { css, variant } from '@codecademy/gamut-styles'; +import styled from '@emotion/styled'; + +const Anchor = styled.a( + variant({ + base: { p: 4 }, + defaultVariant: 'interface', + variants: { + interface: { + color: 'text', + '&:hover': { color: 'text-accent' }, + }, + }, + }) +); +``` + +### System props example + +```tsx +import { Box } from '@codecademy/gamut'; + +; +``` diff --git a/docs/agents/gamut-library-authoring.md b/docs/agents/gamut-library-authoring.md new file mode 100644 index 00000000000..ab6959c5639 --- /dev/null +++ b/docs/agents/gamut-library-authoring.md @@ -0,0 +1,132 @@ +# Gamut library authoring + +## Scope + +Work lives under this monorepo: `packages/gamut`, `packages/gamut-styles`, `packages/gamut-patterns`, `packages/gamut-icons`, `packages/gamut-illustrations`, `packages/styleguide`. This document is **not** for application repos that only install `@codecademy/gamut`. + +**Editor rules (example: Cursor):** `.cursor/rules/gamut-library.mdc` (tokens, ColorMode, Figma boundaries) and `.cursor/rules/gamut-component-building.mdc` (component structure, TypeScript, React, Storybook pairing for `packages/gamut` + `packages/styleguide`). Figma-oriented rules also align with [figma-from-design.md](./figma-from-design.md). + +## Architecture + +- Components: `packages/gamut/src` — extend existing components before adding overlapping primitives. +- Patterns: `packages/gamut-patterns` — page-level compositions. +- Icons / illustrations: `packages/gamut-icons`, `packages/gamut-illustrations`. +- Tokens: single source in `packages/gamut-styles/src/variables/` (`spacing`, `colors`, `typography`, `borderRadii`). No ad-hoc hex or arbitrary pixel strings where a token exists. + +## Component structure (`packages/gamut`) + +- Default: one **PascalCase** folder with `index.tsx` (or `index.ts` re-exporting siblings) and `__tests__/.test.tsx`. +- Large UIs: add `shared/` for types/styles/variants, `elements/` for presentational pieces, or domain subfolders (`layout/`, `inputs/`) following `Button/`, `Form/`, `BarChart/`, `GridForm/`. +- **Barrel:** every public component or type consumers need must be exported from `packages/gamut/src/index.tsx`. Use `export type { … }` when values should not be re-exported. + +## Storybook and snippets (`packages/styleguide`) + +- Place docs under `packages/styleguide/src/lib/` in the atomic layer that matches the component (Atoms, Molecules, Organisms, Layouts, Typography, etc.); folder structure mirrors the Storybook sidebar. +- For each component: **`ComponentName.stories.tsx`** + **`ComponentName.mdx`** (kebab-case filenames) in that component’s folder. +- VS Code (repo root): type **`component-story`**, **`component-doc`**, or **`toc-story`** to insert templates from `.vscode/stories.code-snippets`. +- Include a flagship/default story, **`Controls`** where appropriate, and prose in MDX (`parameters` with `title`, `subtitle`, `design`, `status`, `source.githubLink`). Prefer examples that **Show code** as copy-paste-ready (avoid heavy indirection in the snippet users copy). +- Meta guides: `packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/About.mdx`, `Component story documentation.mdx`, `Component code examples.mdx`. + +## TypeScript and variance + +- **Derive props from styling:** after `const x = variance.compose(system.space, …)` or `variant({ … })`, extend with `StyleProps`. Chain multiple `StyleProps` when variants and states are separate (see `Anchor`, `Tag/types.tsx`). +- **Derive from styled component:** `export type FooProps = ComponentProps` for Emotion roots built with `styled('tag', styledOptions<'tag'>())(…)`. +- **Compose with bases:** e.g. `ButtonBaseProps & ComponentProps` so system props and the underlying component stay aligned. +- **Variants:** use **discriminated unions** (`export type Props = A | B | C`) when `variant` or mode changes required props. Use **`never`** on disallowed props per branch so invalid combinations fail at compile time (`Tag`, `Badge` standard vs `custom`). +- **DOM handlers:** prefer `HTMLProps['onClick']`, `ComponentProps['onClick']`, etc., over `Function` or `any`. +- **Shared types:** reuse `WithChildrenProp`, `IconComponentType`, `Partial` from `packages/gamut/src/utils/types.ts`; follow generics like `InlineIconButtonProps` in `Button/shared/types.ts` for polymorphic wrappers. +- **Gold components:** adding a variant usually means a new union member and fixing consumers; avoid new `as any`; reserve exceptions for documented edge cases only. + +## React + +- Match **local file style** (`React.FC` is common in Gamut). +- Use **`forwardRef`** when consumers or libraries need the underlying DOM ref. +- **Rules of Hooks**; name shared logic `use*`. Effects: correct dependency arrays, cleanup for subscriptions/timers; avoid mirroring props into state when derived state or a `key` reset is clearer ([You Might Not Need an Effect](https://react.dev/learn/you-might-not-need-an-effect)). +- **Memoization:** `useMemo` / `useCallback` / `React.memo` when profiling or stable identity is required—not by default. +- **Composition:** prefer `children` and subcomponents over flat prop explosion; page templates belong in `gamut-patterns`. +- **Lists:** stable keys; avoid index keys for reorderable/dynamic lists. +- **Forms:** be explicit about controlled vs uncontrolled behavior; align with `ConnectedForm` / `GridForm` when touching those flows. +- **Accessibility:** semantic elements first (`button`, `a`, `label` + `htmlFor`); use `aria-*` for bespoke widgets. See styleguide Meta and `Best practices.mdx`. + +## Styling + +- Emotion + `@codecademy/gamut-styles`: `css`, `variant`, `states`, system props via `system.css`, `styledOptions`, variance `compose` where the codebase already does. +- Semantic color keys only in component styles so components work in any ColorMode. +- Avoid nested tag selectors and `${GamutComponent}` selectors; prefer system props, layout primitives (`FlexBox`, `GridBox`), and explicit wrappers. + +## ColorMode and Background + +- When changing theme behavior, read `packages/styleguide/src/lib/Foundations/ColorMode/ColorMode.mdx` and `packages/gamut-styles/src/ColorMode.tsx` / `Background` implementation. +- Components should consume **semantic** aliases (`text`, `background`, `primary`, etc.), not raw palette names in ways that break mode switching. + +## Documentation and MDX + +- New or changed components need Storybook MDX under `packages/styleguide`; keep props tables accurate ([Storybook Autodocs](https://storybook.js.org/docs/writing-docs/autodocs) where used). +- Cross-link [published Storybook](https://gamut.codecademy.com/) paths for reviewers and agents. +- Human-oriented overview: `packages/styleguide/src/lib/Meta/Gamut writing guide/Building components in Gamut.mdx`. + +## Accessibility + +- Follow WCAG-minded patterns; use styleguide Meta and per-component pages. Prefer built-in Gamut props and semantics over bespoke DOM. + +## Quality gates + +- Respect `eslint-plugin-gamut` and repo ESLint config for touched packages. +- Add or update stories and visual/docs coverage when behavior or public API changes. + +--- + +## Reference: tokens and exemplars + +### Token files (read before changing visuals) + +- `packages/gamut-styles/src/variables/spacing.ts` +- `packages/gamut-styles/src/variables/colors.ts` +- `packages/gamut-styles/src/variables/typography.ts` +- `packages/gamut-styles/src/variables/borderRadii.ts` + +### TypeScript and structure exemplars (`packages/gamut/src`) + +| Topic | Files | +| -------------------------------------------- | ----------------------------------------------------- | +| Discriminated unions + `never` | `Tag/types.tsx` | +| Variant branches + conflicting props | `Badge/index.tsx` | +| `StyleProps` + `ComponentProps` intersection | `Button/shared/types.ts`, `ButtonBase/ButtonBase.tsx` | +| `ComponentProps` | `Form/elements/Form.tsx` | +| Multiple `StyleProps` + anchor variants | `Anchor/index.tsx` | +| `variance.compose` for layout system props | `Box/props.ts`, `Layout/LayoutGrid.tsx` | + +### VS Code snippets (repo root) + +Prefix in editor → choose snippet from `.vscode/stories.code-snippets`: + +- `component-story` — `ComponentName.stories.tsx` CSF template +- `component-doc` — `ComponentName.mdx` doc template +- `toc-story` — table-of-contents category page + +### Meta / Storybook MDX (human docs) + +- Contributing (props JSDoc, tests): `packages/styleguide/src/lib/Meta/Contributing.mdx` +- Stories guide hub: `packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/About.mdx` +- MDX structure: `…/Stories/Component story documentation.mdx` +- `.stories.tsx` patterns: `…/Stories/Component code examples.mdx` +- Building components in Gamut (overview): `packages/styleguide/src/lib/Meta/Gamut writing guide/Building components in Gamut.mdx` +- Meta best practices: `packages/styleguide/src/lib/Meta/Best practices.mdx` +- ColorMode / Background: `packages/styleguide/src/lib/Foundations/ColorMode/ColorMode.mdx` +- System compose (published): Storybook path `/docs/foundations-system-compose--page` on [gamut.codecademy.com](https://gamut.codecademy.com/) + +### Figma and package boundaries + +Align implementation-from-design with [figma-from-design.md](./figma-from-design.md) and, when using Cursor, `.cursor/rules/gamut-figma-rules.mdc`. + +### When adding a component (checklist) + +1. Search `packages/gamut/src` for something close; extend if possible. +2. Use semantic colors and token scales from `gamut-styles` variables. +3. Model props with `StyleProps` / `ComponentProps` / unions per this doc; export via `packages/gamut/src/index.tsx`. +4. Add `*.stories.tsx` + `*.mdx` under `packages/styleguide/src/lib///`. +5. Run package-level lint/tests for touched workspaces. + +### Theme / mode changes + +Document Storybook coverage and any breaking changes for consumers. Platform-specific theme docs live under styleguide Foundations; coordinate with `ColorMode` and theme providers. diff --git a/gamut-cursor-plugins/gamut-themes/skills/gamut-theming/SKILL.md b/docs/agents/gamut-theming.md similarity index 80% rename from gamut-cursor-plugins/gamut-themes/skills/gamut-theming/SKILL.md rename to docs/agents/gamut-theming.md index 74cedec25cc..fd469795d29 100644 --- a/gamut-cursor-plugins/gamut-themes/skills/gamut-theming/SKILL.md +++ b/docs/agents/gamut-theming.md @@ -1,12 +1,3 @@ ---- -name: gamut-theming -description: >- - Configures and debugs ColorMode, Background, semantic color tokens, and hooks - from @codecademy/gamut-styles in application code. Use when implementing - light/dark/system modes, branded sections, background-current, or platform - themes—not for one-off hex colors. Complements codecademy-gamut (core). ---- - # Gamut theming ## Documentation @@ -61,5 +52,5 @@ Platform-specific themes (e.g. Percipio, LX Studio) are documented under stylegu ## Related -- **codecademy-gamut** — system props, layout, eslint, general consumption. -- **codecademy-gamut-a11y** — focus, ARIA, overlays (often interacts with themed surfaces). +- [gamut-consumer.md](./gamut-consumer.md) — system props, layout, eslint, general consumption. +- [gamut-accessibility.md](./gamut-accessibility.md) — focus, ARIA, overlays (often interacts with themed surfaces). diff --git a/docs/agents/monorepo-skills.manifest.json b/docs/agents/monorepo-skills.manifest.json new file mode 100644 index 00000000000..96e9c1d20fa --- /dev/null +++ b/docs/agents/monorepo-skills.manifest.json @@ -0,0 +1,13 @@ +{ + "skills": [ + { + "name": "gamut-library-authoring", + "skillMdPath": ".cursor/skills/gamut-library-authoring/SKILL.md", + "canonicalDocPath": "docs/agents/gamut-library-authoring.md", + "agentsPath": "AGENTS.md", + "title": "Gamut library authoring", + "description": "Authors and maintains components in the Codecademy Gamut monorepo (packages/gamut,\ngamut-styles, patterns, icons, illustrations). Use when adding or changing design\nsystem components, tokens, Storybook MDX, variance/styledOptions, ColorMode-aware\nstyles, TypeScript prop modeling, React patterns, or eslint-plugin-gamut rules in\nthis repository—not when consuming Gamut from an application.", + "cursorRulesNote": "When this repo is open in **Cursor**, project rules also apply: `.cursor/rules/gamut-library.mdc`, `.cursor/rules/gamut-component-building.mdc`, and `.cursor/rules/gamut-figma-rules.mdc` (Figma → code). Use the markdown files above for full depth; this skill exists so assistants discover the workflow." + } + ] +} diff --git a/docs/agents/references.md b/docs/agents/references.md new file mode 100644 index 00000000000..150133445b8 --- /dev/null +++ b/docs/agents/references.md @@ -0,0 +1,54 @@ +# AI tooling — reference map + +Consolidated index of **in-repo** agent tooling and **external** documentation. Start with [AGENTS.md](../AGENTS.md) for the short index; this page is for navigation and onboarding. + +## Internal paths (repository) + +| Asset | Path | +| ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| Agent index (start here) | [AGENTS.md](../AGENTS.md) | +| Agent docs hub + conventions | [README.md](./README.md) | +| This reference map | [references.md](./references.md) | +| GitHub URL source of truth | [canonical-urls.json](./canonical-urls.json) | +| Monorepo skill manifest | [monorepo-skills.manifest.json](./monorepo-skills.manifest.json) | +| Library authoring (long form) | [gamut-library-authoring.md](./gamut-library-authoring.md) | +| Consumer / a11y / theming / Figma | [gamut-consumer.md](./gamut-consumer.md), [gamut-accessibility.md](./gamut-accessibility.md), [gamut-theming.md](./gamut-theming.md), [figma-from-design.md](./figma-from-design.md) | +| Cursor project rules | [gamut-library.mdc](../../.cursor/rules/gamut-library.mdc), [gamut-component-building.mdc](../../.cursor/rules/gamut-component-building.mdc), [gamut-figma-rules.mdc](../../.cursor/rules/gamut-figma-rules.mdc) | +| Monorepo library skill (generated) | [SKILL.md](../../.cursor/skills/gamut-library-authoring/SKILL.md) | +| Cursor MCP (Figma) | [mcp.json](../../.cursor/mcp.json) | +| **`gamut`** plugin root | [README.md](../../packages/agent-plugin/README.md) | +| Plugin manifests | [plugin.json](../../packages/agent-plugin/.cursor-plugin/plugin.json) (Cursor), [plugin.json](../../packages/agent-plugin/.claude-plugin/plugin.json) (Claude) | +| Plugin skills manifest | [skills.manifest.json](../../packages/agent-plugin/skills/skills.manifest.json) | +| Plugin rules | [rules/](../../packages/agent-plugin/rules/) | +| Skill generator | [generate-skills.mjs](../../packages/agent-plugin/scripts/generate-skills.mjs) | +| Team marketplace | [marketplace.json](../../.cursor-plugin/marketplace.json) | +| Validator | [validate-agent-plugins.mjs](../../.github/scripts/validate-agent-plugins.mjs) | +| Workflow | [cursor-plugins.yml](../../.github/workflows/cursor-plugins.yml) | +| Old plugins breadcrumb | [README.md](../../gamut-cursor-plugins/README.md) | +| Storybook source: building components | [Building components in Gamut.mdx](../../packages/styleguide/src/lib/Meta/Gamut%20writing%20guide/Stories/Building%20components%20in%20Gamut.mdx) | +| Storybook source: Figma MCP | [Figma MCP.mdx](../../packages/styleguide/src/lib/Meta/MCP/Figma%20MCP.mdx) | + +### Commands (from repository root) + +- `yarn generate:agent-skills` — regenerate packaged and monorepo `SKILL.md` files (see [package.json](../../package.json)) +- `node .github/scripts/validate-agent-plugins.mjs` — validate marketplace, plugin layout, monorepo `.cursor` rules/skills +- `yarn test:agent-plugins-validator` — unit tests for validator helpers + +### Nx + +[agent-plugin project.json](../../packages/agent-plugin/project.json) is included in the Nx graph. **`agent-plugin` is excluded from `nx release`** (not an npm package); see `release.projects` in [nx.json](../../nx.json). + +--- + +## External documentation (vendor / product) + +| Topic | URL | +| ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | +| Codecademy Gamut Storybook | https://gamut.codecademy.com/ | +| Cursor plugins | https://cursor.com/docs/plugins | +| Cursor plugin reference (metadata) | https://cursor.com/docs/reference/plugins.md | +| Claude Code plugins | https://code.claude.com/docs/en/plugins | +| Claude plugin manifest (field reference) | https://github.com/anthropics/claude-code/blob/main/plugins/plugin-dev/skills/plugin-structure/references/manifest-reference.md | +| Figma Dev Mode MCP | https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Dev-Mode-MCP-Server | +| Figma desktop | https://www.figma.com/downloads/ | +| Gamut on GitHub (`main`) | See [canonical-urls.json](./canonical-urls.json) for `githubRepo`, `docsAgentsTreeOnMain`, and `docsAgentsBlobBaseOnMain` | diff --git a/gamut-cursor-plugins/README.md b/gamut-cursor-plugins/README.md index af5341fa002..9d97ac58f39 100644 --- a/gamut-cursor-plugins/README.md +++ b/gamut-cursor-plugins/README.md @@ -1,108 +1,9 @@ -# Gamut Cursor plugins +# Deprecated: use `packages/agent-plugin` -Three [Cursor plugins](https://cursor.com/docs/plugins) for teams using the [Gamut](https://gamut.codecademy.com/) design system: +The previous three Cursor plugins (`gamut-core`, `gamut-a11y`, `gamut-themes`) have been replaced by a **single** plugin named **`gamut`**: -| Plugin | Folder | Purpose | -| --- | --- | --- | -| **codecademy-gamut** | `gamut-core/` | Layout, system props, `gamut-styles` utilities, ESLint alignment, Storybook links | -| **codecademy-gamut-a11y** | `gamut-a11y/` | WCAG-minded usage, keyboard/focus, ARIA, composition patterns | -| **codecademy-gamut-themes** | `gamut-themes/` | `ColorMode`, `Background`, semantic tokens, hooks, platform themes | +- **Package:** [`packages/agent-plugin`](../packages/agent-plugin/README.md) +- **Marketplace:** [`.cursor-plugin/marketplace.json`](../.cursor-plugin/marketplace.json) +- **Canonical docs (any agent):** [AGENTS.md](../AGENTS.md), [docs/agents/](../docs/agents/) -Library authors working **inside** the [Codecademy/gamut](https://github.com/Codecademy/gamut) repository should use the monorepo skill `.cursor/skills/gamut-library-authoring/` and `.cursor/rules/gamut-library.mdc` (not shipped in these plugins). - -## Multi-plugin marketplace - -When this folder lives inside the **Gamut monorepo**, the Team Marketplace manifest is at the **repository root**: [`.cursor-plugin/marketplace.json`](../.cursor-plugin/marketplace.json). It points at `gamut-cursor-plugins/gamut-core`, `gamut-a11y`, and `gamut-themes`. - -If you **extract** `gamut-cursor-plugins/` to its own Git repository, move `.cursor-plugin/marketplace.json` to that repo root and set each plugin `source` to `gamut-core`, `gamut-a11y`, and `gamut-themes` (no prefix). - -Admins can mark **codecademy-gamut** as required and **a11y** / **themes** as optional or required per distribution group. See [Team marketplaces](https://cursor.com/docs/plugins). - -## CI (GitHub Actions) - -Workflow [`.github/workflows/cursor-plugins.yml`](../.github/workflows/cursor-plugins.yml) runs on pull requests and pushes to `main` when **Cursor-related paths** change: [`.cursor/`](../.cursor/) (monorepo rules/skills), `gamut-cursor-plugins/`, [`.cursor-plugin/`](../.cursor-plugin/), or the workflow/script. You can also run it manually via **Actions → Cursor plugins → Run workflow**. It uses Node (see [`.nvmrc`](../.nvmrc)) and runs [`.github/scripts/validate-cursor-plugins.mjs`](../.github/scripts/validate-cursor-plugins.mjs); the job **fails** (red PR check) if validation errors are reported. - -Checks: - -- Root [`.cursor-plugin/marketplace.json`](../.cursor-plugin/marketplace.json) is valid JSON with `name`, `owner.name`, and a non-empty `plugins` list. -- Each plugin `source` directory exists and contains `.cursor-plugin/plugin.json` whose `name` matches the marketplace entry and whose `version` is semver. -- Each plugin has `rules/*.mdc` and/or `skills/*/SKILL.md` with YAML frontmatter including `description` (and `name` for skills). -- Monorepo [`.cursor/rules/*.mdc`](../.cursor/rules) and [`.cursor/skills/*/SKILL.md`](../.cursor/skills) use the same frontmatter requirements when those paths exist. -- **Pull requests only** (second workflow step, `if: github.event_name == 'pull_request'`): if any file under a marketplace plugin `source` tree (e.g. `gamut-cursor-plugins/gamut-core/`) differs from the PR base commit, that plugin’s `.cursor-plugin/plugin.json` **version** must be **strictly greater** than on the base (semver). New plugins with no prior `plugin.json` on the base are exempt from the comparison. The layout step does not run this check; it is `node …/validate-cursor-plugins.mjs --pr-version-bumps`. - -It does **not** call Cursor’s marketplace API, install plugins in Cursor, or lint Markdown body content beyond those structural checks. - -## Human tasks (not automated) - -- **Public marketplace**: Submit or update listings per [Publish to Cursor Marketplace](https://cursor.com/marketplace/publish) (open source and review requirements apply). -- **Update review**: Changes to plugins on the public marketplace are **manually reviewed** before users receive updates; plan for latency. See [Marketplace security](https://cursor.com/help/security-and-privacy/marketplace-security.md). -- **Team marketplace**: Import the repo (or fork) in the Cursor dashboard (**Settings → Plugins → Team Marketplaces**), assign required vs optional plugins per group, and confirm installs on a developer machine (see §5 below). -- **Version bumps**: When you change rules, skills, or `plugin.json` metadata in a meaningful way, bump semver in each affected `gamut-*/.cursor-plugin/plugin.json` so teams can tell updates apart. - -## Testing instructions - -Use these steps whenever you change plugin content and need to confirm Cursor loads rules and skills correctly. See [Creating plugins — Test plugins locally](https://cursor.com/docs/plugins#test-plugins-locally). - -### 1. Install plugins locally (symlink) - -Each published plugin must be its **own folder** under `~/.cursor/plugins/local/`, with `.cursor-plugin/plugin.json` at that folder’s root (the `gamut-core`, `gamut-a11y`, and `gamut-themes` directories satisfy that). - -From a clone of this repo, adjust `GAMUT_ROOT` and run: - -```bash -GAMUT_ROOT="/path/to/gamut" # e.g. ~/code/gamut - -mkdir -p ~/.cursor/plugins/local -ln -sfn "$GAMUT_ROOT/gamut-cursor-plugins/gamut-core" ~/.cursor/plugins/local/codecademy-gamut -ln -sfn "$GAMUT_ROOT/gamut-cursor-plugins/gamut-a11y" ~/.cursor/plugins/local/codecademy-gamut-a11y -ln -sfn "$GAMUT_ROOT/gamut-cursor-plugins/gamut-themes" ~/.cursor/plugins/local/codecademy-gamut-themes -``` - -Using **`-n`** forces the symlink name; the **folder name** under `local/` can match the plugin `name` in `plugin.json` (here: `codecademy-gamut`, etc.) for clarity. - -Alternatively **copy** the three folders into `~/.cursor/plugins/local/` if you prefer not to symlink. - -### 2. Reload Cursor - -- Restart the Cursor app, **or** -- Command Palette → **Developer: Reload Window** - -### 3. Verify in Settings - -1. Open **Settings** (e.g. `Cmd+Shift+J` on macOS). -2. **Rules** — Confirm entries from the plugins appear (e.g. `gamut-consumer`, `gamut-accessibility`, `gamut-theming`). Set modes (**Always** / **Agent Decides** / **Manual**) as your team prefers. -3. **Skills** — Confirm **gamut-consumer**, **gamut-accessibility**, and **gamut-theming** are listed (under Agent Decides / manual invocation per your Cursor version). - -### 4. Smoke-test behavior - -- Open any **`.tsx` / `.jsx`** file (matches rule `globs`). -- Start a chat and invoke a skill by name if supported (e.g. `/gamut-consumer` or the skill picker), or rely on **Agent Decides** so the agent can attach the skill when the description matches. -- Confirm the agent references Gamut patterns (Storybook links, `GridForm`/`ConnectedForm`, `ColorMode`/`Background`, etc.) when relevant. - -### 5. Team Marketplace (optional) - -If you validate **multi-plugin import** from this repo: - -1. Dashboard → **Settings** → **Plugins** → **Team Marketplaces** → **Import** (Teams / Enterprise). -2. Use the **GitHub URL of the Gamut repo** (or a fork) so root [`.cursor-plugin/marketplace.json`](../.cursor-plugin/marketplace.json) is discovered. -3. After import, confirm all three plugins parse; assign **required** vs **optional** per distribution group. -4. On a developer machine, open the marketplace panel and install or confirm auto-install. - -### 6. Monorepo-only pieces (not in these plugins) - -With the **Gamut** repo open, `.cursor/skills/gamut-library-authoring/` and `.cursor/rules/gamut-library.mdc` apply to library work. They are **not** loaded via the three symlinks above—test those by opening this repo and checking **Rules** / **Skills** for the monorepo paths. - -### Troubleshooting - -- **Nothing appears after symlink** — Confirm each path is a directory containing `.cursor-plugin/plugin.json` and `rules/` / `skills/` as expected; reload the window again. -- **Wrong content** — You may be pointing at an old clone; recreate symlinks with `ln -sfn`. -- **Marketplace vs local** — Team Marketplace import does not replace `~/.cursor/plugins/local/` testing; use symlinks for the fastest iteration on file edits. - -## Publishing - -- [Publish to Cursor Marketplace](https://cursor.com/marketplace/publish) (public; open source and review required)—a human step; CI does not publish. -- Or attach this repository as a **team marketplace** in the Cursor dashboard (also configured outside CI). - -## Versioning - -Bump `version` in each `gamut-*/.cursor-plugin/plugin.json` together when content changes materially. +This folder is kept only as a breadcrumb for old links; it no longer contains plugin sources. diff --git a/gamut-cursor-plugins/gamut-a11y/.cursor-plugin/plugin.json b/gamut-cursor-plugins/gamut-a11y/.cursor-plugin/plugin.json deleted file mode 100644 index d95dec762e5..00000000000 --- a/gamut-cursor-plugins/gamut-a11y/.cursor-plugin/plugin.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "codecademy-gamut-a11y", - "version": "1.0.0", - "description": "Accessibility-focused guidance for building UI with Gamut.", - "author": { - "name": "Codecademy" - }, - "keywords": ["gamut", "accessibility", "a11y", "wcag", "codecademy"], - "homepage": "https://gamut.codecademy.com/", - "repository": "https://github.com/Codecademy/gamut" -} diff --git a/gamut-cursor-plugins/gamut-a11y/rules/gamut-accessibility.mdc b/gamut-cursor-plugins/gamut-a11y/rules/gamut-accessibility.mdc deleted file mode 100644 index b1aabb4c3d8..00000000000 --- a/gamut-cursor-plugins/gamut-a11y/rules/gamut-accessibility.mdc +++ /dev/null @@ -1,20 +0,0 @@ ---- -description: >- - Use Gamut components per their documented accessibility patterns: forms, overlays, - navigation, and content. Prefer Storybook component pages over bespoke a11y wiring. -globs: - - "**/*.{tsx,jsx}" -alwaysApply: false ---- - -# Gamut accessibility - -- **Read the component's Storybook page** (especially "Accessibility", "Keyboard", or "Usage" sections on [gamut.codecademy.com](https://gamut.codecademy.com/)) before customizing behavior—Gamut components often manage focus, ARIA, and escape handling by default. -- **Forms:** Use **`GridForm`** or **`ConnectedForm`** with **`ConnectedFormGroup`** / **`useConnectedForm`**; avoid raw **`Form`** / **`FormGroup`** atoms unless the UI does not need full form behavior. Do not use **`ConnectedFormInputs`** outside **`ConnectedFormGroup`**. Render **`FormRequiredText`** from **`useConnectedForm`** when any field is required. For **`GridForm`** **`custom-group`**, you own labels and errors—follow accessible form patterns linked from GridForm Fields. -- **Overlays:** Prefer **`Modal`** / **`Flyout`** / **`Drawer`** (and related patterns) from `@codecademy/gamut` instead of custom portals. For **`Modal`**, wire **`onRequestClose`** and **`isOpen`** so outside click and **Esc** still close (defaults are intentional for a11y); use **`containerFocusRef`** only when you must override default focus. **`InfoTip`**: provide **`ariaLabel`** or **`ariaLabelledby`** for the trigger; respect built-in focus move / **Esc** / layering with modals per component docs. -- **Tabs:** Use **`Tabs`** / **`TabNav`** from Gamut; give **`TabNav`** a unique **`aria-label`** when it is page navigation. Follow docs for keyboard focus when **`TabPanel`** contains interactive content. -- **Page chrome:** Use **`SkipToContent`** with a matching **`SkipToContentTarget`** (`contentId` / `id`) on long chrome-heavy pages so keyboard users can jump to main content. -- **Data viz:** For **`BarChart`**, supply **`title`** or **`aria-labelledby`**; prefer documented props and token colors so contrast stays acceptable—do not strip generated labels for interactive bars. -- **Content:** Follow **[UX Writing — Accessibility guidelines](https://gamut.codecademy.com/?path=/docs-ux-writing-accessibility-guidelines--page)** for copy, headings, links, and alt text; use **`Text`** / imagery patterns as documented when semantics matter. -- **Theming:** Use semantic colors and **`ColorMode`** / **`Background`** (**codecademy-gamut-themes**) so text and surfaces keep readable contrast. -- **Gaps:** If Gamut has no primitive, only then mirror headless patterns ([Radix](https://www.radix-ui.com/primitives), [React ARIA](https://react-spectrum.adobe.com/react-aria/)) and style with **`@codecademy/gamut-styles`** semantic tokens. diff --git a/gamut-cursor-plugins/gamut-core/.cursor-plugin/plugin.json b/gamut-cursor-plugins/gamut-core/.cursor-plugin/plugin.json deleted file mode 100644 index 7ab1e7d43ed..00000000000 --- a/gamut-cursor-plugins/gamut-core/.cursor-plugin/plugin.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "codecademy-gamut", - "version": "1.0.0", - "description": "Core Gamut design system usage for application code.", - "author": { - "name": "Codecademy" - }, - "keywords": ["gamut", "codecademy", "design-system", "react"], - "homepage": "https://gamut.codecademy.com/", - "repository": "https://github.com/Codecademy/gamut" -} diff --git a/gamut-cursor-plugins/gamut-core/rules/gamut-consumer.mdc b/gamut-cursor-plugins/gamut-core/rules/gamut-consumer.mdc deleted file mode 100644 index 987911d6792..00000000000 --- a/gamut-cursor-plugins/gamut-core/rules/gamut-consumer.mdc +++ /dev/null @@ -1,18 +0,0 @@ ---- -description: >- - Core standards for using @codecademy/gamut and @codecademy/gamut-styles in - application code: semantic colors, system props, no unsafe nested selectors. -globs: - - "**/*.{tsx,jsx}" -alwaysApply: false ---- - -# Gamut consumer (core) - -- Prefer `Box`, `FlexBox`, `GridBox` and other Gamut components over raw layout divs when building UI. -- Use **semantic** color props and `gamut-styles` utilities (`css`, `variant`, `states`) with semantic token names (`text`, `background`, `primary`, `danger`, etc.). Avoid hardcoded palette values where tokens exist. -- Use responsive system props (object keys `_`, `sm`, `md`, … or array syntax). See Storybook foundations for responsive properties. -- Do not use tag selectors (`div`, `p`, `span`, `*`) or `${Box}`-style nested Gamut component selectors in styled components; use props, `FlexBox`/`GridBox`, and `gamut-styles` helpers instead. -- Align with `eslint-plugin-gamut` when configured (`no-inline-style`, `no-css-standalone`, `gamut-import-paths`, etc.). -- Set color context at the app root appropriately; for **ColorMode**, **Background**, hooks, and platform themes, use the **codecademy-gamut-themes** plugin and [ColorMode docs](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page). -- For accessibility reviews (dialogs, focus, ARIA, forms), enable the **codecademy-gamut-a11y** plugin. diff --git a/gamut-cursor-plugins/gamut-core/skills/gamut-consumer/SKILL.md b/gamut-cursor-plugins/gamut-core/skills/gamut-consumer/SKILL.md deleted file mode 100644 index cec7ec11410..00000000000 --- a/gamut-cursor-plugins/gamut-core/skills/gamut-consumer/SKILL.md +++ /dev/null @@ -1,49 +0,0 @@ ---- -name: gamut-consumer -description: >- - Builds and refactors UI using @codecademy/gamut and @codecademy/gamut-styles: - system props, semantic tokens, css/variant/states, eslint-plugin-gamut, and - Storybook documentation links. Use when implementing screens in apps that depend - on Gamut—not when authoring the Gamut library itself. For ColorMode/Background - depth use codecademy-gamut-themes; for WCAG-focused work use codecademy-gamut-a11y. ---- - -# Gamut consumer (core) - -## When to use - -- Application code importing `@codecademy/gamut` or `@codecademy/gamut-styles`. -- Layout, spacing, typography, and component composition with design tokens. - -## Providers (minimal) - -Ensure the app supplies appropriate color/theme context at the root. For `ColorMode`, `Background`, `useColorMode`, platform themes, and troubleshooting contrast or mode bugs, rely on the **codecademy-gamut-themes** plugin and [ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page). - -## Semantic colors and tokens - -- Semantic names describe **role** (`text`, `background`, `primary`, `secondary`, …) and resolve per ColorMode. See [Best practices / ColorMode](https://gamut.codecademy.com/?path=/docs-meta-best-practices--page). -- Use `css({ color: 'primary', p: 4 })`, `variant`, and `states` from `@codecademy/gamut-styles` with those semantic keys. -- Prefer `themed(...)` or variants APIs over ad-hoc `theme.colors` access when eslint recommends it. - -## System props and layout - -- Use `Box`, `FlexBox`, `GridBox` with system props; support responsive maps (`{ _: value, md: value }`) and arrays with sparse breakpoints. -- Docs: [Responsive properties](https://gamut.codecademy.com/storybook/?path=/docs-foundations-system-responsive-properties--page), [system compose](https://gamut.codecademy.com/?path=/docs-foundations-system-compose--page). - -## Anti-patterns - -- No `style={{}}` for design-token-level styling where system props or `css()` apply. -- No tag-wide or `*` selectors; no `${Box}`-style nested selectors targeting Gamut internals—use props and layout wrappers. - -## Lint - -If the repo enables `eslint-plugin-gamut`, expect rules such as `no-inline-style`, `no-css-standalone`, and `gamut-import-paths`. Match fixes to Gamut patterns. - -## Sibling plugins - -- **codecademy-gamut-a11y** — forms, dialogs, custom widgets, WCAG reviews, focus/ARIA. -- **codecademy-gamut-themes** — multi-mode layouts, `Background`, `background-current`, hooks, platform themes. - -## Reference - -Stable Storybook URLs and migration snippets: [reference.md](reference.md). diff --git a/gamut-cursor-plugins/gamut-core/skills/gamut-consumer/reference.md b/gamut-cursor-plugins/gamut-core/skills/gamut-consumer/reference.md deleted file mode 100644 index 77c3d0afe3f..00000000000 --- a/gamut-cursor-plugins/gamut-core/skills/gamut-consumer/reference.md +++ /dev/null @@ -1,55 +0,0 @@ -# Gamut consumer — Storybook and sources - -## Published Storybook - -Base URL: [https://gamut.codecademy.com/](https://gamut.codecademy.com/) - -Useful paths (query `path`): - -| Topic | URL | -| --- | --- | -| Meta / Best practices | `?path=/docs-meta-best-practices--page` | -| ColorMode | `?path=/docs-foundations-colormode--page` | -| System compose | `?path=/docs-foundations-system-compose--page` | -| Responsive system props | `?path=/docs-foundations-system-responsive-properties--page` | - -## Source of truth in the Gamut repo - -If you have the monorepo checked out: - -- Best practices MDX: `packages/styleguide/src/lib/Meta/Best practices.mdx` -- ColorMode MDX: `packages/styleguide/src/lib/Foundations/ColorMode/ColorMode.mdx` - -## Package imports - -- Components: `@codecademy/gamut` -- Styles / providers / hooks: `@codecademy/gamut-styles` -- Patterns / icons / illustrations: `@codecademy/gamut-patterns`, `@codecademy/gamut-icons`, `@codecademy/gamut-illustrations` when applicable - -## Styled component example - -```tsx -import { css, variant } from '@codecademy/gamut-styles'; -import styled from '@emotion/styled'; - -const Anchor = styled.a( - variant({ - base: { p: 4 }, - defaultVariant: 'interface', - variants: { - interface: { - color: 'text', - '&:hover': { color: 'text-accent' }, - }, - }, - }) -); -``` - -## System props example - -```tsx -import { Box } from '@codecademy/gamut'; - -; -``` diff --git a/gamut-cursor-plugins/gamut-themes/.cursor-plugin/plugin.json b/gamut-cursor-plugins/gamut-themes/.cursor-plugin/plugin.json deleted file mode 100644 index 037bee8e8a7..00000000000 --- a/gamut-cursor-plugins/gamut-themes/.cursor-plugin/plugin.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "name": "codecademy-gamut-themes", - "version": "1.0.0", - "description": "ColorMode, Background, and semantic theme usage for Gamut apps.", - "author": { - "name": "Codecademy" - }, - "keywords": ["gamut", "theme", "colormode", "dark-mode", "codecademy"], - "homepage": "https://gamut.codecademy.com/", - "repository": "https://github.com/Codecademy/gamut" -} diff --git a/gamut-cursor-plugins/gamut-themes/rules/gamut-theming.mdc b/gamut-cursor-plugins/gamut-themes/rules/gamut-theming.mdc deleted file mode 100644 index 0c0a4faba93..00000000000 --- a/gamut-cursor-plugins/gamut-themes/rules/gamut-theming.mdc +++ /dev/null @@ -1,19 +0,0 @@ ---- -description: >- - ColorMode, Background, semantic color tokens, and provider boundaries for - @codecademy/gamut-styles. Use when wiring theme providers or debugging contrast. -globs: - - "**/*.{tsx,jsx,ts,js}" -alwaysApply: false ---- - -# Gamut theming - -- Place **`ColorMode`** from `@codecademy/gamut-styles` **as high in the DOM as practical** (e.g. app or root layout) so descendants share one light/dark/`system` context (`mode="light" | "dark" | "system"`; `system` follows OS preference). Avoid deep or repeated `ColorMode` nesting unless a subtree truly needs a different preference. -- Use **`Background`** with `bg` token names for **page sections** that need a specific surface, a defined **`background-current`** for descendants, or a region that should **not rely on guessing mode**—`Background` picks an accessible mode for that surface and sets up its own context inside. Read [ColorMode / Background](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page). -- `background-current` reflects the active `Background` color—use when a child needs to match an ancestor surface. -- Prefer **semantic** color props (`text`, `background`, `primary`, …) in components so they track the active mode. -- For JS access to mode: `useColorMode`, `useCurrentMode`, or `useTheme` from `@emotion/react` when you need the full theme. -- Avoid fighting the system with raw CSS variables or hardcoded hex for theme surfaces; extend tokens in the **Gamut library** when new semantics are needed. -- Mental model: layered **theme + semantic tokens** (similar in spirit to [Chakra theming](https://chakra-ui.com/docs/styled-system/customize-theme) and [React Spectrum theming](https://react.spectrum.adobe.com/react-spectrum/theming.html))—apps compose providers; token keys stay semantic. -- For layout and generic Gamut usage without provider depth, use **codecademy-gamut** core rules. diff --git a/nx.json b/nx.json index 094b9ad3633..7e53554b1a0 100644 --- a/nx.json +++ b/nx.json @@ -74,7 +74,12 @@ }, "analytics": false, "release": { - "projects": ["packages/*", "!packages/styleguide", "!packages/macros"], + "projects": [ + "packages/*", + "!packages/styleguide", + "!packages/macros", + "!packages/agent-plugin" + ], "projectsRelationship": "independent", "versionPlans": true, "changelog": { diff --git a/package.json b/package.json index efa148d6e64..f3523b44b08 100644 --- a/package.json +++ b/package.json @@ -131,6 +131,7 @@ "deploy": "rm -rf ./dist/docs && mv ./dist/storybook/styleguide ./dist/docs && cp -r ./dist/static/* ./dist/docs && gh-pages -b gh-pages -d dist", "format": "yarn lint:fix && yarn prettier --write", "format:verify": "yarn prettier --check", + "generate:agent-skills": "node packages/agent-plugin/scripts/generate-skills.mjs", "lint": "eslint --ignore-path .eslintignore \"./**/*.{mdx,js,ts,tsx,json}\" --max-warnings 0", "lint:fix": "yarn lint --fix", "prepare": "husky", @@ -138,7 +139,7 @@ "start": "yarn && yarn start:storybook", "start:storybook": "nx storybook styleguide", "test": "nx run-many --target=test --all", - "test:cursor-plugins-validator": "node --test .github/scripts/validate-cursor-plugins-helpers.test.mjs", + "test:agent-plugins-validator": "node --test .github/scripts/validate-agent-plugins-helpers.test.mjs", "test:storybook": "nx run styleguide:storybook-test", "verify": "nx run-many --target=verify --parallel=3 --all", "verify-all": "yarn verify" diff --git a/packages/agent-plugin/.claude-plugin/plugin.json b/packages/agent-plugin/.claude-plugin/plugin.json new file mode 100644 index 00000000000..46d017b1b28 --- /dev/null +++ b/packages/agent-plugin/.claude-plugin/plugin.json @@ -0,0 +1,19 @@ +{ + "name": "gamut", + "version": "1.0.0", + "description": "Codecademy Gamut design system: consumption, accessibility, theming, and Storybook-aligned rules for application code.", + "author": { + "name": "Codecademy" + }, + "homepage": "https://gamut.codecademy.com/", + "repository": "https://github.com/Codecademy/gamut", + "license": "MIT", + "keywords": [ + "gamut", + "codecademy", + "design-system", + "react", + "accessibility", + "theming" + ] +} diff --git a/packages/agent-plugin/.cursor-plugin/plugin.json b/packages/agent-plugin/.cursor-plugin/plugin.json new file mode 100644 index 00000000000..8862ed7cc74 --- /dev/null +++ b/packages/agent-plugin/.cursor-plugin/plugin.json @@ -0,0 +1,18 @@ +{ + "name": "gamut", + "version": "1.0.0", + "description": "Codecademy Gamut design system: consumption, accessibility, theming, and Storybook-aligned rules for application code.", + "author": { + "name": "Codecademy" + }, + "keywords": [ + "gamut", + "codecademy", + "design-system", + "react", + "accessibility", + "theming" + ], + "homepage": "https://gamut.codecademy.com/", + "repository": "https://github.com/Codecademy/gamut" +} diff --git a/packages/agent-plugin/README.md b/packages/agent-plugin/README.md new file mode 100644 index 00000000000..5ac4ef37f35 --- /dev/null +++ b/packages/agent-plugin/README.md @@ -0,0 +1,53 @@ +# Gamut agent plugin + +Single editor plugin (**name: `gamut`**) for the [Codecademy Gamut](https://gamut.codecademy.com/) design system. It bundles **rules** and **skills** for consuming `@codecademy/gamut` in applications: core layout/tokens, accessibility, and theming. + +**Canonical documentation** (any coding agent, not editor-specific): [AGENTS.md](../../AGENTS.md) and [docs/agents/](../../docs/agents/). + +## Canonical docs on GitHub + +Published URLs for `main` are defined once in **[`docs/agents/canonical-urls.json`](../../docs/agents/canonical-urls.json)** (`docsAgentsBlobBaseOnMain`, `docsAgentsTreeOnMain`, `githubRepo`). + +**Plugin `SKILL.md` files** get their `blob/main` links from that JSON when you run **`yarn generate:agent-skills`** (see below). Do not hand-edit the GitHub base inside generated skills. + +## Layout + +- **`.cursor-plugin/plugin.json`** — Cursor marketplace / local plugin manifest +- **`.claude-plugin/plugin.json`** — Claude Code plugin manifest (same version as Cursor; keep both in sync) +- **`rules/`** — optional context rules (`.mdc`) with globs +- **`skills/`** — invocable skills; bodies are generated from **`skills/skills.manifest.json`** + **`docs/agents/canonical-urls.json`** +- **`scripts/generate-skills.mjs`** — regenerates packaged skills and monorepo Cursor skills (see [docs/agents/README.md](../../docs/agents/README.md) conventions) + +## Regenerating skills + +After editing **`skills/skills.manifest.json`**, **`docs/agents/monorepo-skills.manifest.json`**, or **`docs/agents/canonical-urls.json`**, run from the **repository root**: + +```bash +yarn generate:agent-skills +# or: node packages/agent-plugin/scripts/generate-skills.mjs +``` + +Commit updated `SKILL.md` files together with manifest or URL changes. + +## Cursor (local symlink) + +From the Gamut repo root (`GAMUT_ROOT`): + +```bash +mkdir -p ~/.cursor/plugins/local +ln -sfn "$GAMUT_ROOT/packages/agent-plugin" ~/.cursor/plugins/local/gamut +``` + +Reload the editor window. See [Cursor plugins](https://cursor.com/docs/plugins) for team marketplace setup; point the marketplace at this repo’s [`.cursor-plugin/marketplace.json`](../../.cursor-plugin/marketplace.json). + +## Claude Code + +Install or reference this directory per [Claude Code plugins](https://code.claude.com/docs/en/plugins). The manifest lives at `.claude-plugin/plugin.json`. Component discovery uses the default `./skills` and `./rules` folders at this plugin root. + +## Migrating from old plugins + +The previous **three** Cursor plugins (`codecademy-gamut`, `codecademy-gamut-a11y`, `codecademy-gamut-themes`) are replaced by this single **`gamut`** plugin. Remove old symlinks under `~/.cursor/plugins/local/` and link **`gamut`** once as above. + +## Versioning + +Bump **`version`** in **both** `.cursor-plugin/plugin.json` and `.claude-plugin/plugin.json` together when rules, skills, manifests, or packaged behavior change materially. CI validates manifests and enforces semver bumps on pull requests when this tree changes. diff --git a/packages/agent-plugin/project.json b/packages/agent-plugin/project.json new file mode 100644 index 00000000000..e08480a9a05 --- /dev/null +++ b/packages/agent-plugin/project.json @@ -0,0 +1,7 @@ +{ + "name": "agent-plugin", + "$schema": "../../node_modules/nx/schemas/project-schema.json", + "sourceRoot": "packages/agent-plugin", + "projectType": "library", + "tags": ["scope:docs"] +} diff --git a/packages/agent-plugin/rules/gamut-accessibility.mdc b/packages/agent-plugin/rules/gamut-accessibility.mdc new file mode 100644 index 00000000000..1fb440e87a1 --- /dev/null +++ b/packages/agent-plugin/rules/gamut-accessibility.mdc @@ -0,0 +1,18 @@ +--- +description: >- + Use Gamut components per their documented accessibility patterns: forms, overlays, + navigation, and content. Prefer Storybook component pages over bespoke a11y wiring. +globs: + - "**/*.{tsx,jsx}" +alwaysApply: false +--- + +# Gamut accessibility + +**Canonical doc (read in full):** `docs/agents/gamut-accessibility.md` (repo root). + +- Read the component’s **Storybook** page before changing focus, ARIA, or dismiss behavior ([gamut.codecademy.com](https://gamut.codecademy.com/)). +- **Forms:** **`GridForm`** or **`ConnectedForm`** + **`ConnectedFormGroup`** / **`useConnectedForm`**; never **`ConnectedFormInputs`** outside **`ConnectedFormGroup`**; **`FormRequiredText`** when any field is required; **`GridForm` `custom-group`** means you own labels/errors. +- **Overlays:** Prefer **`Modal`** / **`Flyout`** / **`Drawer`**; for **`Modal`**, wire **`onRequestClose`** and **`isOpen`** (Esc / outside click); restore focus after close. +- **Theming:** Semantic colors + **`ColorMode`** / **`Background`** per **`gamut-theming`** for contrast. +- **Gaps:** Only if no Gamut primitive, mirror headless patterns (e.g. Radix, React ARIA) and style with **`@codecademy/gamut-styles`** semantic tokens. diff --git a/packages/agent-plugin/rules/gamut-consumer.mdc b/packages/agent-plugin/rules/gamut-consumer.mdc new file mode 100644 index 00000000000..d6c97e5d38f --- /dev/null +++ b/packages/agent-plugin/rules/gamut-consumer.mdc @@ -0,0 +1,17 @@ +--- +description: >- + Core standards for using @codecademy/gamut and @codecademy/gamut-styles in + application code: semantic colors, system props, no unsafe nested selectors. +globs: + - "**/*.{tsx,jsx}" +alwaysApply: false +--- + +# Gamut consumer (core) + +**Canonical doc (read in full):** `docs/agents/gamut-consumer.md` (repo root). + +- Prefer `Box`, `FlexBox`, `GridBox` over raw layout divs; use **semantic** colors and `gamut-styles` (`css`, `variant`, `states`); no tag / `*` / `${Box}` nested selectors—use props and layout helpers. +- Use responsive system props; align with `eslint-plugin-gamut` when configured. +- Root **ColorMode** / **Background** / hooks: follow **`gamut-theming`** and [ColorMode](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page). +- Dialogs, focus, ARIA, forms: follow **`gamut-accessibility`** and its doc. diff --git a/packages/agent-plugin/rules/gamut-theming.mdc b/packages/agent-plugin/rules/gamut-theming.mdc new file mode 100644 index 00000000000..b8c0d580001 --- /dev/null +++ b/packages/agent-plugin/rules/gamut-theming.mdc @@ -0,0 +1,18 @@ +--- +description: >- + ColorMode, Background, semantic color tokens, and provider boundaries for + @codecademy/gamut-styles. Use when wiring theme providers or debugging contrast. +globs: + - "**/*.{tsx,jsx,ts,js}" +alwaysApply: false +--- + +# Gamut theming + +**Canonical doc (read in full):** `docs/agents/gamut-theming.md` (repo root). + +- **`ColorMode`** high in the tree (`light` / `dark` / `system`); avoid deep nesting unless a subtree must diverge. +- **`Background`** for branded sections, **`background-current`**, or surfaces that must not rely on guessed mode—see [ColorMode / Background](https://gamut.codecademy.com/?path=/docs-foundations-colormode--page). +- Prefer **semantic** color props; use `useColorMode`, `useCurrentMode`, or `useTheme` from `@emotion/react` when needed. +- Do not fight the stack with hardcoded hex for theme surfaces; extend Gamut tokens in the library when semantics are missing. +- Generic layout and component usage: **`gamut-consumer`** rules in this plugin. diff --git a/packages/agent-plugin/scripts/generate-skills.mjs b/packages/agent-plugin/scripts/generate-skills.mjs new file mode 100644 index 00000000000..9b107768da1 --- /dev/null +++ b/packages/agent-plugin/scripts/generate-skills.mjs @@ -0,0 +1,166 @@ +#!/usr/bin/env node +/** + * Regenerates: + * - packages/agent-plugin/skills//SKILL.md from skills/skills.manifest.json + * - Monorepo Cursor skills from docs/agents/monorepo-skills.manifest.json + * GitHub links use docs/agents/canonical-urls.json (docsAgentsBlobBaseOnMain). + * Run from repo root: node packages/agent-plugin/scripts/generate-skills.mjs + */ +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const PLUGIN_ROOT = path.resolve(__dirname, '..'); +const REPO_ROOT = path.resolve(PLUGIN_ROOT, '../..'); +const MANIFEST_PATH = path.join(PLUGIN_ROOT, 'skills', 'skills.manifest.json'); +const CANONICAL_URLS_PATH = path.join( + REPO_ROOT, + 'docs', + 'agents', + 'canonical-urls.json' +); +const MONOREPO_MANIFEST_PATH = path.join( + REPO_ROOT, + 'docs', + 'agents', + 'monorepo-skills.manifest.json' +); + +function loadCanonicalBlobBase() { + if (!fs.existsSync(CANONICAL_URLS_PATH)) { + console.error( + `error: missing ${path.relative(REPO_ROOT, CANONICAL_URLS_PATH)}` + ); + process.exit(1); + } + const { docsAgentsBlobBaseOnMain } = JSON.parse( + fs.readFileSync(CANONICAL_URLS_PATH, 'utf8') + ); + if (!docsAgentsBlobBaseOnMain) { + console.error( + 'error: canonical-urls.json must define docsAgentsBlobBaseOnMain' + ); + process.exit(1); + } + return String(docsAgentsBlobBaseOnMain).replace(/\/$/, ''); +} + +function yamlDescriptionBlock(description) { + const lines = description.split('\n').map((l) => l.trimEnd()); + return lines.map((l) => ` ${l}`).join('\n'); +} + +function generatePluginSkills(blobBase) { + const raw = fs.readFileSync(MANIFEST_PATH, 'utf8'); + const { skills } = JSON.parse(raw); + if (!Array.isArray(skills) || skills.length === 0) { + console.error( + 'error: skills.manifest.json must include a non-empty skills array' + ); + process.exit(1); + } + + for (const skill of skills) { + const { id, docFile, title, description, detailLine } = skill; + if (!id || !docFile || !title || !description || !detailLine) { + console.error( + 'error: each plugin skill needs id, docFile, title, description, detailLine' + ); + process.exit(1); + } + const url = `${blobBase}/${docFile}`; + const descBlock = yamlDescriptionBlock(description); + const body = `--- +name: ${id} +description: >- +${descBlock} +--- + +# ${title} + +Full guidance (tool-agnostic): **[${docFile}](${url})** in the Gamut repository. + +This skill summarizes scope only; ${detailLine} +`; + + const dir = path.join(PLUGIN_ROOT, 'skills', id); + fs.mkdirSync(dir, { recursive: true }); + const outPath = path.join(dir, 'SKILL.md'); + fs.writeFileSync(outPath, body, 'utf8'); + console.log('wrote', path.relative(REPO_ROOT, outPath)); + } +} + +function generateMonorepoSkills() { + if (!fs.existsSync(MONOREPO_MANIFEST_PATH)) { + return; + } + const { skills } = JSON.parse( + fs.readFileSync(MONOREPO_MANIFEST_PATH, 'utf8') + ); + if (!Array.isArray(skills) || skills.length === 0) { + return; + } + + for (const skill of skills) { + const { + name, + skillMdPath, + canonicalDocPath, + agentsPath, + title, + description, + cursorRulesNote, + } = skill; + if ( + !name || + !skillMdPath || + !canonicalDocPath || + !agentsPath || + !title || + !description || + !cursorRulesNote + ) { + console.error( + 'error: each monorepo skill needs name, skillMdPath, canonicalDocPath, agentsPath, title, description, cursorRulesNote' + ); + process.exit(1); + } + + const outPath = path.join(REPO_ROOT, skillMdPath); + const outDir = path.dirname(outPath); + const docAbs = path.join(REPO_ROOT, canonicalDocPath); + const agentsAbs = path.join(REPO_ROOT, agentsPath); + const docRel = path.relative(outDir, docAbs).replace(/\\/g, '/'); + const agentsRel = path.relative(outDir, agentsAbs).replace(/\\/g, '/'); + + const descBlock = yamlDescriptionBlock(description); + const body = `--- +name: ${name} +description: >- +${descBlock} +--- + +# ${title} + +**Canonical guide (tool-agnostic):** [\`${canonicalDocPath}\`](${docRel}) + +**Repository index:** [\`${agentsPath}\`](${agentsRel}) + +${cursorRulesNote} +`; + + fs.mkdirSync(outDir, { recursive: true }); + fs.writeFileSync(outPath, body, 'utf8'); + console.log('wrote', path.relative(REPO_ROOT, outPath)); + } +} + +function main() { + const blobBase = loadCanonicalBlobBase(); + generatePluginSkills(blobBase); + generateMonorepoSkills(); +} + +main(); diff --git a/packages/agent-plugin/skills/gamut-accessibility/SKILL.md b/packages/agent-plugin/skills/gamut-accessibility/SKILL.md new file mode 100644 index 00000000000..120b9113833 --- /dev/null +++ b/packages/agent-plugin/skills/gamut-accessibility/SKILL.md @@ -0,0 +1,15 @@ +--- +name: gamut-accessibility +description: >- + Reviews and implements accessible UI with @codecademy/gamut: WCAG-oriented + patterns, keyboard and focus, documented Gamut components, and MDN-grounded + patterns when building custom widgets. Use when auditing accessibility, + implementing bespoke controls, or fixing a11y bugs in Gamut consumer apps. + Pair with gamut-consumer and gamut-theming for tokens and color context. +--- + +# Gamut accessibility + +Full guidance (tool-agnostic): **[gamut-accessibility.md](https://github.com/Codecademy/gamut/blob/main/docs/agents/gamut-accessibility.md)** in the Gamut repository. + +This skill summarizes scope only; the linked document is the source of truth for forms, overlays, focus management, and checklists. diff --git a/packages/agent-plugin/skills/gamut-consumer/SKILL.md b/packages/agent-plugin/skills/gamut-consumer/SKILL.md new file mode 100644 index 00000000000..5ab48836985 --- /dev/null +++ b/packages/agent-plugin/skills/gamut-consumer/SKILL.md @@ -0,0 +1,15 @@ +--- +name: gamut-consumer +description: >- + Builds and refactors UI using @codecademy/gamut and @codecademy/gamut-styles: + system props, semantic tokens, css/variant/states, eslint-plugin-gamut, and + Storybook documentation links. Use when implementing screens in apps that depend + on Gamut—not when authoring the Gamut library itself. Pair with gamut-theming + for ColorMode/Background; gamut-accessibility for WCAG-focused work. +--- + +# Gamut consumer (core) + +Full guidance (tool-agnostic): **[gamut-consumer.md](https://github.com/Codecademy/gamut/blob/main/docs/agents/gamut-consumer.md)** in the Gamut repository. + +This skill summarizes scope only; the linked document is the source of truth for tokens, layout, lint, and Storybook URLs. diff --git a/packages/agent-plugin/skills/gamut-theming/SKILL.md b/packages/agent-plugin/skills/gamut-theming/SKILL.md new file mode 100644 index 00000000000..b7151bbf007 --- /dev/null +++ b/packages/agent-plugin/skills/gamut-theming/SKILL.md @@ -0,0 +1,14 @@ +--- +name: gamut-theming +description: >- + Configures and debugs ColorMode, Background, semantic color tokens, and hooks + from @codecademy/gamut-styles in application code. Use when implementing + light/dark/system modes, branded sections, background-current, or platform + themes—not for one-off hex colors. Complements gamut-consumer. +--- + +# Gamut theming + +Full guidance (tool-agnostic): **[gamut-theming.md](https://github.com/Codecademy/gamut/blob/main/docs/agents/gamut-theming.md)** in the Gamut repository. + +This skill summarizes scope only; the linked document is the source of truth for providers, hooks, and troubleshooting. diff --git a/packages/agent-plugin/skills/skills.manifest.json b/packages/agent-plugin/skills/skills.manifest.json new file mode 100644 index 00000000000..7c906095c3c --- /dev/null +++ b/packages/agent-plugin/skills/skills.manifest.json @@ -0,0 +1,25 @@ +{ + "skills": [ + { + "id": "gamut-consumer", + "docFile": "gamut-consumer.md", + "title": "Gamut consumer (core)", + "detailLine": "the linked document is the source of truth for tokens, layout, lint, and Storybook URLs.", + "description": "Builds and refactors UI using @codecademy/gamut and @codecademy/gamut-styles:\nsystem props, semantic tokens, css/variant/states, eslint-plugin-gamut, and\nStorybook documentation links. Use when implementing screens in apps that depend\non Gamut—not when authoring the Gamut library itself. Pair with gamut-theming\nfor ColorMode/Background; gamut-accessibility for WCAG-focused work." + }, + { + "id": "gamut-accessibility", + "docFile": "gamut-accessibility.md", + "title": "Gamut accessibility", + "detailLine": "the linked document is the source of truth for forms, overlays, focus management, and checklists.", + "description": "Reviews and implements accessible UI with @codecademy/gamut: WCAG-oriented\npatterns, keyboard and focus, documented Gamut components, and MDN-grounded\npatterns when building custom widgets. Use when auditing accessibility,\nimplementing bespoke controls, or fixing a11y bugs in Gamut consumer apps.\nPair with gamut-consumer and gamut-theming for tokens and color context." + }, + { + "id": "gamut-theming", + "docFile": "gamut-theming.md", + "title": "Gamut theming", + "detailLine": "the linked document is the source of truth for providers, hooks, and troubleshooting.", + "description": "Configures and debugs ColorMode, Background, semantic color tokens, and hooks\nfrom @codecademy/gamut-styles in application code. Use when implementing\nlight/dark/system modes, branded sections, background-current, or platform\nthemes—not for one-off hex colors. Complements gamut-consumer." + } + ] +} diff --git a/packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/Building components in Gamut.mdx b/packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/Building components in Gamut.mdx index 02e53afb0af..856de9f224c 100644 --- a/packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/Building components in Gamut.mdx +++ b/packages/styleguide/src/lib/Meta/Gamut writing guide/Stories/Building components in Gamut.mdx @@ -6,7 +6,7 @@ export const parameters = { id: 'Building components in Gamut', title: 'Building components in Gamut', subtitle: - 'Repo layout, TypeScript, React, and Storybook conventions for contributors—mirrors Cursor skill and rules for agents.', + 'Repo layout, TypeScript, React, and Storybook conventions for contributors—aligned with AGENTS.md and docs/agents for coding assistants.', status: 'static', }; @@ -14,7 +14,8 @@ export const parameters = { -This page summarizes how we build and document components in the Gamut monorepo. It aligns with the **gamut-library-authoring** Cursor skill and project rules **gamut-library** / **gamut-component-building** (under `.cursor/rules/` and `.cursor/skills/gamut-library-authoring/` in the repo). +{/* Keep GitHub URLs in sync with docs/agents/canonical-urls.json (main branch). */} +This page summarizes how we build and document components in the Gamut monorepo. It aligns with **[`AGENTS.md`](https://github.com/Codecademy/gamut/blob/main/AGENTS.md)** and **[`docs/agents/gamut-library-authoring.md`](https://github.com/Codecademy/gamut/blob/main/docs/agents/gamut-library-authoring.md)** on `main`, and with project rules under **`.cursor/rules/`** (e.g. **gamut-library**, **gamut-component-building**) plus the **gamut-library-authoring** skill in **`.cursor/skills/`** when using Cursor. ## Packages and placement @@ -56,3 +57,4 @@ Match neighboring files (`React.FC` is common). Use `forwardRef` when refs matte - Contributing — props documentation and PR expectations. - Published Storybook: [gamut.codecademy.com](https://gamut.codecademy.com/). +- Packaged editor plugin (consumption / a11y / themes): [`packages/agent-plugin`](https://github.com/Codecademy/gamut/tree/main/packages/agent-plugin). diff --git a/packages/styleguide/src/lib/Meta/MCP/Figma MCP.mdx b/packages/styleguide/src/lib/Meta/MCP/Figma MCP.mdx index 823241097ec..2404fbc06fa 100644 --- a/packages/styleguide/src/lib/Meta/MCP/Figma MCP.mdx +++ b/packages/styleguide/src/lib/Meta/MCP/Figma MCP.mdx @@ -16,7 +16,9 @@ export const parameters = { -The offical guidance and documentation comes from Figma. Please reference [Figma Dev Mode MCP documentation](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Dev-Mode-MCP-Server) for the most up to date information. This documentation below has been adapted to fit the context of the Gamut repository. +The official guidance and documentation comes from Figma. Please reference [Figma Dev Mode MCP documentation](https://help.figma.com/hc/en-us/articles/32132100833559-Guide-to-the-Dev-Mode-MCP-Server) for the most up to date information. This documentation below has been adapted to fit the context of the Gamut repository. + +Any **MCP-capable** editor or agent (Cursor, Claude Code, and others) can talk to the same local Figma MCP server; configuration shape varies by product. The steps below use **Cursor** as the primary example. ## Figma (desktop client app) @@ -35,20 +37,20 @@ Go to [Figma's download page](https://www.figma.com/downloads/) to download the ## Set up MCP Client for your text editor/IDE -We currently support Figma MCP for Cursor. Please let the Gamut team know if you'd like support for other editors. +Point your client at the local server URL (same host/port Figma exposes—often `http://127.0.0.1:3845/mcp` or `/sse` depending on Figma and client). The Gamut repo includes a team default for Cursor in [`.cursor/mcp.json`](https://github.com/Codecademy/gamut/blob/main/.cursor/mcp.json) on `main`. ### Cursor 1. Open **Cursor** → **Settings** → **Cursor Settings**. 2. Go to the **MCP** tab (might be called **MCP & Integrations**) from the left-hand sidebar. 3. Under **MCP Tools**, click **+ Add MCP server**. -4. Enter the following configuration and save: +4. Enter the following configuration and save (the path may be `/sse` or `/mcp` depending on your Figma desktop version; the Gamut repo’s [`.cursor/mcp.json`](https://github.com/Codecademy/gamut/blob/main/.cursor/mcp.json) on `main` uses `/sse`): ``` { "mcpServers": { "Figma": { - "url": "http://127.0.0.1:3845/mcp" + "url": "http://127.0.0.1:3845/sse" } } } @@ -56,13 +58,13 @@ We currently support Figma MCP for Cursor. Please let the Gamut team know if you ### Verify your MCP server is running -To check if your MCP server is running, you can visit the endpoint: `http://127.0.0.1:3845/mcp`. You should see a response, e.g.: `{"jsonrpc":"2.0","error":{"code":-32001,"message":"Invalid sessionId"},"id":null}`. +To check if your MCP server is running, you can visit the endpoint: `http://127.0.0.1:3845/sse` (or `/mcp` if that is what your client uses). You should see a response, e.g.: `{"jsonrpc":"2.0","error":{"code":-32001,"message":"Invalid sessionId"},"id":null}`. Yes, it's an odd response, but it indicates that the MCP server is running — otherwise if it's not running you'll see an error "This site can’t be reached" on the page. Similarly you can test the end point using: ```bash -curl http://127.0.0.1:3845/mcp +curl http://127.0.0.1:3845/sse ``` ## Prompt your MCP client