View source

Initialize Design System

You are initializing the design-to-code workflow for this project. Your job is to deeply understand the existing codebase's design patterns and produce a design-tokens.json file at .claude/d2c/design-tokens.json.

Non-negotiables

These rules hold across every phase of this skill. No exceptions.

  1. Design tokens MUST be loaded before any decision. Read .claude/d2c/design-tokens.json. If it is missing, unreadable, or has d2c_schema_version < 1, STOP AND ASK the user to run /d2c-init (or /d2c-init --force if outdated).
  2. NEVER use a library outside preferred_libraries.<category>.selected. The user explicitly chose which library to use for each capability. NEVER substitute an installed-but-not-selected library. If the design requires a capability not covered by preferred_libraries, STOP AND ASK.
  3. NEVER hardcode color, spacing, typography, shadow, or radius values. Every visual value MUST reference a design token from design-tokens.json. No raw hex, no magic numbers, no exceptions.
  4. MUST reuse existing components when an existing component can serve the need. Check the components array in design-tokens.json before creating anything new. If an existing component can do the job, MUST use it.
  5. MUST follow project conventions when confidence > 0.6 and value ≠ "mixed". Project conventions (declaration style, export style, type definitions, import ordering, file naming, CSS wrapper, barrel exports, props pattern) override framework defaults.
  6. NEVER re-decide a locked component or token. Read decisions.lock.json from the IR run directory at the start of every phase after Phase 2. Only nodes with status: "failed" may have their component choice or token mapping changed. If a locked decision must change, STOP AND ASK.

When any rule is ambiguous, STOP AND ASK — do not guess.

Note for d2c-init: This skill creates the infrastructure (design-tokens.json, preferred_libraries, conventions) that the other skills enforce. Rules 1–5 above describe the contract d2c-init writes; d2c-build, d2c-audit, and d2c-guard depend on this file being correct. During initialization, any ambiguity about what to write to the tokens file MUST result in a STOP AND ASK — never guess.

Arguments

Parse $ARGUMENTS for optional flags:

  • Figma URL — If a Figma URL is provided (e.g., /d2c-init https://www.figma.com/design/...), store it for use in Step 5g (Figma Variables import). This avoids requiring the user to provide the URL separately.
  • --force — Skip incremental update detection and force a full scan (Steps 0-8). Useful if the design-tokens.json is out of sync and incremental detection fails.

Pre-check: Incremental Update

Before scanning, check if .claude/d2c/design-tokens.json exists.

  • If --force was passed in $ARGUMENTS: skip incremental detection, create the .claude/d2c/ directory if needed, and proceed with a full scan (Steps 0-8).
  • If it does not exist: create the .claude/d2c/ directory and proceed with a full scan (Steps 0-8).
  • If it does exist: read it into context. Check the d2c_schema_version field — if it is missing or less than 1 (the current version), the model MUST STOP AND ASK the user for confirmation before overwriting, using this prompt: "design-tokens.json uses an older schema version (found: {version or 'none'}). You MUST run /d2c-init --force to regenerate with the latest schema before any d2c-build or d2c-audit run. Confirm to proceed, or cancel to leave the file untouched." Only after explicit user confirmation, run a targeted scan:
    1. Check if the styling approach or config file has changed. If yes, re-extract tokens (Step 2) and re-run conflict detection (Step 2b).
    2. Scan only for new, modified, or deleted components and hooks since the file was last generated (compare file paths in the existing JSON against what's on disk).
    3. Check if package.json dependencies have changed — compare installed packages against the preferred_libraries section. If new libraries were added to an existing category, or a new category now has libraries, re-run Step 5 for those categories only. Ask the user to re-choose if a new competing library was added to a category that previously had only one.
    4. Merge changes into the existing design-tokens.json — add new entries, update changed entries, remove entries for deleted files.
    5. If tokens changed (steps 1 or 4 modified token values), re-run Step 2b conflict detection. Preserve existing user-resolved entries in token-conflicts.json where the tokens and values are still valid.
    6. Skip Steps 7-8's Playwright/pixelmatch install if already installed.
    7. Check if the framework has changed (e.g., project migrated from React to Vue). If framework in existing file doesn't match current detection, warn the user: "Framework change detected: {old} → {new}. This will regenerate all framework-specific fields and may update preferred library categories. Proceed?" Wait for confirmation before re-running Step 0.
    8. Check if the conventions section exists in the existing design-tokens.json. If it does not exist (e.g., file created by an older version of init), run Step 5h to detect conventions. If it does exist, only re-scan conventions if component files have changed (same logic as item 2 — compare file paths on disk against what was previously scanned).

This avoids re-reading the entire codebase when only a few components have changed.

Step 0: Detect Framework

Scan the project to identify the frontend framework. Check in this order (first match wins):

Priority Check framework meta_framework
1 nuxt.config.ts or nuxt.config.js exists vue nuxt
2 svelte.config.js exists AND @sveltejs/kit in package.json svelte sveltekit
3 svelte.config.js exists without @sveltejs/kit svelte svelte
4 angular.json exists angular angular
5 astro.config.mjs or astro.config.ts exists astro astro
6 @solidjs/start in package.json dependencies solid solidstart
7 solid-js in package.json without @solidjs/start solid solid
8 next.config.mjs or next.config.js or next.config.ts exists react next
9 react in package.json dependencies react react
10 None detected Ask user to specify

For Astro projects: Also detect island frameworks by reading astro.config.mjs/astro.config.ts for integration imports (@astrojs/react, @astrojs/vue, @astrojs/svelte, @astrojs/solid-js). Record as island_frameworks array.

Determine component file extension:

  • react, solid: .tsx
  • vue: .vue
  • svelte: .svelte
  • angular: .component.ts
  • astro: .astro

Record framework, meta_framework, and component_file_extension in design-tokens.json (Step 6).

Step 1: Identify the Styling Approach

Scan the project for how styles are written. Look for:

  • tailwind.config.ts or tailwind.config.js → Tailwind CSS
  • *.module.css or *.module.scss files → CSS/SCSS Modules
  • styled-components, @emotion/styled, or @emotion/css in package.json → CSS-in-JS
  • .css files imported directly into components → Vanilla CSS
  • A combination of the above

Record the primary styling approach. If mixed: count the number of component files (using the component_file_extension detected in Step 0) using each approach. The approach used by >50% of component files is primary. Others are secondary. If no approach exceeds 50%, record as mixed and list all approaches in descending order of file count.

Step 2: Extract Design Tokens

Based on the styling approach, extract tokens from the appropriate source:

If Tailwind: Read tailwind.config.ts/js and extract the theme / extend values — colors, spacing, fontFamily, fontSize, borderRadius, boxShadow, screens (breakpoints).

If CSS Variables: Search for :root or [data-theme] blocks in CSS files. Extract all custom properties.

If CSS-in-JS / Theme file: Look for theme objects (commonly in theme.ts, styles/theme.ts, lib/theme.ts, or similar). Extract the token values.

If Vanilla CSS: Scan all .css files in src/ and app/. A value is a token if it appears in 3+ separate files. Record the exact value and assign a context-aware name using these rules:

  • Colors: Name by the CSS property context where the value most frequently appears + the nearest named web color. Examples: bg-slate (a gray used mostly in background), text-blue (a blue used mostly in color), border-gray (used in border-color). If the same color is used across multiple property types, use the most frequent context. If two colors would get the same name, append a number: bg-gray, bg-gray-2.
  • Spacing: Sort all spacing values numerically and assign t-shirt size names: spacing-xs, spacing-sm, spacing-md, spacing-lg, spacing-xl, spacing-2xl. If more than 6 values, continue with spacing-3xl, etc. or use the pixel value for outliers: spacing-72.
  • Typography: Name by inferred purpose from the selectors where the value appears. font-size-body (used in p, li, span), font-size-heading (used in h1-h6), font-size-small (used in .caption, .helper-text, or smaller than body). If purpose can't be inferred, use the size scale: font-size-sm, font-size-md, font-size-lg.
  • All auto-generated tokens: Mark with "source": "extracted" in the tokens object so the user knows these names were inferred, not defined by the project.

Sort tokens by frequency descending.

For all approaches, extract:

  • Colors: primary, secondary, background, surface, text, border, error, success, warning, info — and any other semantic color names used
  • Spacing: the scale or common spacing values used
  • Typography: font families, size scale, weight scale, line heights
  • Breakpoints: responsive breakpoints
  • Shadows: box-shadow values
  • Borders: border-radius values, border widths

Step 2b: Detect Token Conflicts

After extracting tokens (Step 2) and before discovering components (Step 3), run a duplicate-detection pass to identify within-category value collisions — two or more tokens whose resolved value is identical within the same category. This prevents invisible drift where the build silently picks one of several equivalent tokens.

Detection

Resolve the conflict detection script. Try these locations in order (first match wins):

  1. Relative to this skill file: scripts/detect-conflicts.js
  2. Agent Skills directory: ~/.agents/skills/d2c-init/scripts/detect-conflicts.js
  3. Global skills directory: ~/.claude/skills/d2c-init/scripts/detect-conflicts.js
  4. Global commands directory: ~/.claude/commands/d2c-init/scripts/detect-conflicts.js
  5. Glob fallback: Search for **/detect-conflicts.js

Run the conflict detection script:

node "$DETECT_CONFLICTS_SCRIPT" .claude/d2c/design-tokens.json

Parse the stdout for conflict: lines. Each line has the format:

conflict: <category>:<normalized_value> — <path1>, <path2>, ...

If detect-conflicts: ok (no conflicts), skip the rest of this step.

Usage Counting

For each conflict group detected, compute a rough usage count for each token name. The count is a simple grep for the token's leaf name (the last segment of the dotted path, e.g., primary from colors.primary) across source files with framework-appropriate extensions (.tsx, .vue, .svelte, .component.ts, .astro, .ts, .jsx, .js, .css). Search src/ and app/ directories. The count = number of matching lines.

Only compute usage counts for tokens that are IN CONFLICT — not for every token.

Resolution Rules

For each conflict group, apply these rules in order:

  1. Deprecated-pattern check: If ANY token name in the group contains a substring matching one of deprecated, old, legacy, v1, v0, temp, tmp (case-insensitive), force status: "unresolved" regardless of usage counts.

  2. Zero-usage check: If ALL tokens in the group have 0 usage count, set status: "unresolved".

  3. Usage-ratio check: Sort tokens by usage count descending. If the highest-usage token has >= 2x the count of the second-highest, set status: "auto-resolved", canonical = highest-usage path, chosen_by: "usage_frequency".

  4. Ambiguous (within 2x): Otherwise, set status: "unresolved", chosen_by: "pending".

Threshold

The default threshold is 5 unresolved conflicts. If the user passed --conflict-threshold <N> in $ARGUMENTS, use that value instead.

If the number of unresolved conflicts exceeds the threshold, all unresolved conflicts will be presented to the user in Step 8 for resolution. Auto-resolved conflicts are always fine and do not count toward the threshold.

Output

Write .claude/d2c/token-conflicts.json with the schema defined in skills/d2c-build/schemas/token-conflicts.schema.json. Each conflict entry includes:

  • id: sequential conflict-001, conflict-002, etc.
  • category: the token category
  • resolved_value: the normalized value
  • canonical: dotted token path of the winner (null if unresolved)
  • duplicates: array of dotted paths of non-canonical tokens (or all paths if unresolved)
  • chosen_by: "usage_frequency", "user_choice", or "pending"
  • status: "auto-resolved", "user-resolved", or "unresolved"
  • usage_counts: { "path": count } for all tokens in the group
  • resolution_note: human-readable explanation

Incremental Behavior

On incremental d2c-init re-runs (when token-conflicts.json already exists):

  1. Read the existing conflicts file.
  2. After running conflict detection on the fresh tokens, compare against existing entries.
  3. Preserve user-resolved entries where: the canonical token still exists in the fresh tokens, all duplicate tokens still exist, and they all still resolve to the same value.
  4. Remove entries where any token was deleted or its value changed.
  5. Add new conflict groups not already tracked.
  6. Re-compute unresolved_count.

Step 3: Discover Reusable Components

Scan the project for existing UI components. Common locations:

  • src/components/, src/components/ui/, components/, app/components/
  • Any barrel export files (index.ts) that re-export components

Also scan framework-specific locations:

  • Vue/Nuxt: components/ (auto-imported by Nuxt)
  • Svelte/SvelteKit: src/lib/components/
  • Angular: src/app/shared/components/, src/app/*/components/
  • Astro: src/components/
  • Solid: src/components/

For each component found, record:

  • name: The component name
  • path: File path relative to project root
  • description: Must follow this exact format: [Component type] that [primary function]. Supports [key prop categories]. Example: Button component that triggers actions. Supports variant, size, and loading state. Do not use free-form descriptions — stick to this template.
  • props: The props it accepts (read from TypeScript types, PropTypes, or the JSX usage). List all prop names alphabetically.
  • import_count: The number of files that import this component (the same count used for inclusion criteria below). MUST be an integer ≥ 0. This value is used by /d2c-build Phase 2 for component matching scoring.

Inclusion criteria (deterministic):

  • Include any component that is imported by 2+ files (check with grep for import statements referencing the component file).
  • Include any component that is imported by only 1 file IF it lives in a directory named ui/, shared/, common/, or primitives/.
  • Skip everything else — these are page-specific components.

Persist the import count: The grep-based import count computed for the inclusion check above MUST be recorded as import_count on each included component entry. Do not discard this number — it feeds the component matching scoring system in /d2c-build.

Props extraction by framework:

  • React/Solid/Qwik (.tsx/.jsx): Read TypeScript interface for props (e.g., interface Props { ... } or inline type annotation)
  • Vue (.vue): Read defineProps<T>() in <script setup> block. Extract property names from the type literal.
  • Svelte (.svelte): Read let { ... }: Props = $props() destructuring. Extract the property names.
  • Angular (.component.ts): Read input() and input.required() calls. Extract property names. Also note output() calls.
  • Astro (.astro): Read Astro.props type annotation or interface Props in frontmatter.

A component is page-specific if it lives inside a route directory (e.g., app/dashboard/components/) AND is imported by only 1 file.

Step 4: Discover Shared Hooks, Composables, and Services

Look for custom hooks relevant to UI development:

  • useMediaQuery, useBreakpoint → responsive behavior
  • useDebounce, useThrottle → input handling
  • useClickOutside → dropdowns, modals
  • useForm, useFormField → form state
  • Any data fetching hooks

Framework-specific shared logic locations:

  • React/Solid/Qwik: Custom hooks (use* functions) in src/hooks/ or hooks/
  • Vue/Nuxt: Composables (use* functions) in composables/ (auto-imported in Nuxt) or src/composables/
  • Svelte: Stores in src/lib/stores/ or shared functions in src/lib/
  • Angular: Injectable services (*.service.ts) in src/app/core/services/ or src/app/shared/services/

Record these in a section named based on framework:

  • React/Solid/Qwik: hooks
  • Vue: composables
  • Svelte: stores
  • Angular: services

The JSON key in design-tokens.json is always hooks for consistency, but the description MUST use the framework's terminology (e.g., "composables" for Vue, "stores" for Svelte, "services" for Angular, "hooks" for React).

Record these in a hooks section with name, path, and description.

Step 5: Detect Libraries and Resolve Competing Choices

This step scans package.json to find all installed libraries, groups them by capability category, and asks the user to choose a preferred library when multiple options exist in the same category. This ensures the build skill always uses the right library for new code.

Step 5a: Scan package.json

Read package.jsondependencies and devDependencies. For every installed package, check if it belongs to a known capability category. A library belongs to a category if its package name matches.

Read the library categories reference file. Try these locations in order (first found wins):

  1. references/library-categories.md (relative to this SKILL.md file)
  2. ~/.agents/skills/d2c-init/references/library-categories.md
  3. ~/.claude/skills/d2c-init/references/library-categories.md
  4. ~/.claude/commands/d2c-init/references/library-categories.md
  5. Glob fallback: search for **/library-categories.md in .claude/, .agents/, and the project root

For each installed package, check if it belongs to a known category. Framework-specific categories (prefixed with vue_, svelte_, angular_, solid_) only apply if the detected framework matches.

Step 5b: Also detect fetch usage

fetch is not a package — it's a built-in. Scan source files for fetch( usage. If found, add fetch (built-in) to the data_fetching category.

Similarly, scan for:

  • "use server" → add next-server-actions (built-in) to data_fetching
  • CSS @import / .css file imports → add vanilla-css (built-in) to css_in_js if relevant

Step 5c: Count usage per library

For each detected library, count how many source files (.ts, .tsx, .js, .jsx) import or use it. This is used for the recommendation.

Step 5d: Identify categories with competing libraries

For each category:

  • If 0 libraries detected → skip the category entirely, do not record it.
  • If exactly 1 library detected → auto-select it. No question needed. Record it silently.
  • If 2+ libraries detected → this is a conflict. Ask the user to choose (Step 5e).

Step 5e: Ask the user to choose preferred libraries

For each category with 2+ libraries, present the options to the user with a recommendation. Ask all conflicting categories in a single message.

Recommendation logic (deterministic):

  1. The library used in the most source files is the default recommendation.
  2. Exception: if a library is widely known to be deprecated or unmaintained (e.g., moment is deprecated, react-beautiful-dnd is unmaintained), recommend the next most-used alternative instead and note the deprecation.

Format:

I found multiple libraries for the same purpose in these categories:

Data fetching: @tanstack/react-query (8 files), swr (5 files), fetch (12 files) → Recommended: @tanstack/react-query (most structured usage, best loading/error state support)

Dates: date-fns (14 files), moment (6 files) → Recommended: date-fns (moment is deprecated, date-fns is actively maintained)

Forms: react-hook-form (4 files), formik (7 files) → Recommended: formik (used in more files)

For each category, which library should I use when generating new code?

Wait for the user to respond. If the user says "go with recommendations" or similar, treat those as the user's selected options and write them to preferred_libraries.<category>.selected. The user's choices are final — once written, NEVER substitute a different library when other skills read this file.

Step 5f: Detect API-specific configuration

After the user selects a preferred data fetching library, scan for its configuration. Record ALL of the following fields in the api section of design-tokens.json:

config_path — Path to the API config/setup file

Search for the primary API configuration file. Check these locations in order (first match wins):

  • For React Query: glob for **/react-query*.ts, **/query-client*.ts, lib/**/api.ts, lib/**/client.ts
  • For SWR: glob for **/swr-config*.ts, **/swr*.ts, lib/**/fetcher.ts
  • For Axios: glob for lib/**/api.ts, utils/**/api.ts, services/**/api.ts, **/axios*.ts
  • For fetch (built-in): glob for lib/**/api.ts, utils/**/api.ts, lib/**/fetch*.ts
  • General fallback: lib/api.ts, utils/api.ts, services/api.ts, api/index.ts

Record the path as api.config_path. If no config file is found, record null.

query_client_path — Path to QueryClient/SWRConfig provider

If React Query or SWR is the selected data fetching library, search for the provider component:

  • Glob for **/providers/*query*.tsx, **/providers/*swr*.tsx, **/QueryProvider*.tsx, **/QueryClientProvider*.tsx
  • Also check app layout files (app/layout.tsx, pages/_app.tsx) for inline QueryClientProvider/SWRConfig usage

Record the path as api.query_client_path. If not using React Query/SWR, or no provider found, record null.

base_url_env — API base URL environment variable

Look for axios.create({ baseURL }), environment variables like NEXT_PUBLIC_API_URL, NEXT_PUBLIC_BASE_URL, or similar. If the project uses relative API routes (e.g., Next.js /api/ routes), record null.

auth_pattern — Authentication approach

Search for auth header injection patterns:

  • Interceptors: axios.interceptors.request.use, fetch wrappers adding Authorization headers
  • Framework auth: next-auth/@auth0/*/@clerk/* session middleware, getServerSession, withApiAuthRequired
  • Manual headers: Authorization: Bearer in fetch/axios calls

Must be one of: axios-interceptor, fetch-wrapper, next-auth-session, auth0-session, clerk-session, auth-header-manual, none-detected. Match the actual auth library installed — do NOT guess. If the installed auth library is @auth0/nextjs-auth0, the pattern is auth0-session, not next-auth-session.

error_handling, loading_pattern, response_envelope

  • error_handling — must be one of: toast, error-boundary, inline-message, console-only, none-detected. If multiple patterns coexist, use the one that appears in >50% of API call sites. If no pattern exceeds 50%, use mixed and note the top two.
  • loading_pattern — must be one of: react-query-isLoading, swr-isLoading, suspense, useState-loading, skeleton-component, none-detected. Same >50% rule applies.
  • response_envelope — search for a consistent wrapper object shape across 3+ API responses. Record the exact key names (e.g., { data, error, meta }). If no consistent shape across 3+ responses, record none-detected.
  • Shared types — check for API response types in types/, interfaces/, or co-located with API calls. Record the path to the types directory if one exists.

Step 5g: Import Figma Variables (Optional)

If Figma MCP is available and the user provides a Figma file URL (or one was used in a previous build), attempt to import Figma Variables to enrich the design tokens.

  1. Use the Figma MCP get_variable_defs tool with the Figma file's node ID and file key to pull the file's variable definitions.

  2. Figma Variables typically include:

    • Color variables: semantic colors like primary, secondary, background, text, organized by collection/mode (e.g., light/dark)
    • Spacing variables: spacing scale values
    • Typography variables: font sizes, line heights, font weights
    • Border radius variables: radius values
  3. Merge strategy — Figma Variables supplement, they do not override. Match by exact key name only:

    • If a Figma Variable's key name exactly matches a codebase token key name, keep the codebase version as the source of truth. Do NOT match by value similarity or fuzzy name matching — exact key name match only.
    • If a Figma Variable has no codebase equivalent (no exact key name match), add it to the tokens with a "source": "figma" annotation.
    • If a Figma Variable and codebase token have the same exact key name but different values, flag the mismatch to the user in the verification step (Step 8).
  4. Record any imported Figma Variables in a figma_variables section for reference:

"figma_variables": {
  "imported_from": "<figma file URL or key>",
  "mismatches": [
    {
      "name": "primary",
      "figma_value": "#2563EB",
      "code_value": "#3B82F6",
      "status": "flagged"
    }
  ]
}

If Figma MCP is not available or no Figma file URL is known, skip this step entirely. Do not ask the user for a Figma URL during init — this is an optional enrichment, not a requirement.

Step 5h: Detect Project Conventions

Scan existing component files to infer the team's coding conventions. These conventions will take the highest priority during code generation — above framework reference file defaults.

Which files to scan: Use files from the directories where components were found in Step 3. Scan up to 20 component files (using component_file_extension from Step 0). If fewer than 10 component files exist, scan all of them. Exclude test files, story files, and generated files (*.test.*, *.spec.*, *.stories.*, *.generated.*).

For each convention below, count occurrences of each pattern variant. A pattern is the project convention if it appears in >60% of scanned files. If no variant exceeds 60%, record as "mixed" — no convention will be enforced for that category.

Convention 1: Component Declaration Style

Applies to: react, solid only. Skip for other frameworks.

Scan for:

  • Arrow function: const [A-Z]\w+ = ( or const [A-Z]\w+ = React.memo( → count as arrow_function
  • Function declaration: function [A-Z]\w+( or export default function [A-Z] → count as function_declaration

Record: "arrow_function", "function_declaration", or "mixed".

Convention 2: Export Style

Applies to: all frameworks.

Scan for:

  • export default (including export default function, export default class) → count as default_export
  • export const [A-Z] or export function [A-Z] (without default) → count as named_export

Record: "default_export", "named_export", or "mixed".

Convention 3: Type Definition Style

Applies to: TypeScript projects only (.tsx, .ts files). Skip for plain JS projects.

Scan for:

  • interface \w+Props or interface \w+ in component files → count as interface
  • type \w+Props = or type \w+ = in component files → count as type_alias

Record: "interface", "type_alias", or "mixed".

Convention 4: Type Location

Applies to: TypeScript projects only.

Check if component directories contain separate type files:

  • Separate: types.ts, types/index.ts, *.types.ts, or *.d.ts files exist alongside components → count as separate_file
  • Colocated: type definitions are inside the component file itself → count as colocated

Record: "colocated", "separate_file", or "mixed".

Convention 5: File Naming Convention

Applies to: all frameworks.

Look at component file names from Step 3 directories:

  • PascalCase: file names match [A-Z][a-z]+([A-Z][a-z]+)* (e.g., UserProfile.tsx)
  • kebab-case: file names match [a-z]+(-[a-z]+)+ (e.g., user-profile.tsx)
  • camelCase: file names match [a-z]+([A-Z][a-z]+)+ (e.g., userProfile.tsx)

Record: "PascalCase", "kebab-case", "camelCase", or "mixed".

Convention 6: Import Ordering

Applies to: all frameworks.

Read the import block of 10+ files. Detect consistent grouping by blank lines between import groups. Identify each group:

  • "framework": imports from the framework package (react, next, vue, svelte, @angular, solid-js, astro)
  • "external": imports from node_modules packages (no . or @/ prefix in path)
  • "internal": imports using aliased paths (@/, ~/, #/)
  • "relative": imports using ./ or ../
  • "type": type-only imports (import type, import { type })
  • "style": CSS/SCSS/style imports (.css, .scss, .module.css)

Record: an ordered array of group names reflecting the detected order, or "mixed" if no consistent pattern.

Convention 7: CSS Class Utility Pattern

Applies to: only if styling_approach includes tailwind. Skip otherwise.

Scan for:

  • Wrapper imports: import { cn }, import { clsx }, import { cva }, import { twMerge }, import { cx } — note the function name AND the import path
  • Usage: cn(, clsx(, cva(, twMerge(, cx( in JSX/template class attributes
  • Raw classes: Tailwind class strings directly in className/class without a wrapper

If a wrapper is used in >60% of files, record the wrapper name (e.g., "cn") and set wrapper_import to its import path (e.g., "@/lib/utils"). If no wrapper dominates, record "raw_classes" or "mixed".

Convention 8: Barrel Exports

Applies to: all frameworks.

Count component directories (from Step 3) that contain an index.ts or index.js re-export file:

  • 60% of directories have barrel exports → "yes"

  • <40% → "no"
  • Otherwise → "mixed"

Convention 9: Test File Location

Applies to: all frameworks.

Check where test files live:

  • Colocated: *.test.* or *.spec.* files in the same directory as the source file → "colocated"
  • Separate: test files in __tests__/, tests/, or test/ directories → "separate"
  • If no test files exist → "none_detected"

Record: "colocated", "separate", "none_detected", or "mixed".

Convention 10: Props Pattern

Applies to: react, solid only. Skip for other frameworks.

Scan component function signatures:

  • Destructured: ({ prop1, prop2 }: \w+Props) or ({ prop1, prop2 }) → count as destructured
  • Props object: (props: \w+Props) or (props) → count as props_object

Record: "destructured", "props_object", or "mixed".

After Detection: User Confirmation

Present the detected conventions to the user in a summary table:

Detected project conventions (scanned X component files):

Convention Detected Confidence
Component declaration arrow_function 85%
Export style named_export 92%
Type definition interface 78%
Type location colocated 90%
File naming PascalCase 100%
Import ordering [framework, external, internal, relative, style] 70%
CSS utility pattern cn (from @/lib/utils) 88%
Barrel exports yes 65%
Test location colocated 75%
Props pattern destructured 82%

Conventions with <60% confidence are marked as "mixed" and won't be enforced.

Do these look right? You can override any value.

Wait for the user's response. If they override a value, set "override": true for that convention. If they say "looks good" or similar, proceed with the detected values.

Step 6: Generate design-tokens.json

Read the token schema reference file. Try these locations in order (first found wins):

  1. references/token-schema.md (relative to this SKILL.md file)
  2. ~/.agents/skills/d2c-init/references/token-schema.md
  3. ~/.claude/skills/d2c-init/references/token-schema.md
  4. ~/.claude/commands/d2c-init/references/token-schema.md
  5. Glob fallback: search for **/token-schema.md in .claude/, .agents/, and the project root

Write the file to .claude/d2c/design-tokens.json. Create the .claude/d2c/ directory if it does not exist. Follow the schema exactly.

Schema version: Always write "d2c_schema_version": 1 as the FIRST field in the JSON file. This is required for forward compatibility — the build and audit skills check this version before reading.

Schema validation: Before writing the file, validate the generated JSON against the JSON Schema. Try these locations in order (first found wins):

  1. references/design-tokens.schema.json (relative to this SKILL.md file)
  2. ~/.agents/skills/d2c-init/references/design-tokens.schema.json
  3. ~/.claude/skills/d2c-init/references/design-tokens.schema.json
  4. ~/.claude/commands/d2c-init/references/design-tokens.schema.json
  5. Glob fallback: search for **/design-tokens.schema.json in .claude/, .agents/, and the project root

If validation fails, fix the generated JSON to conform to the schema before writing. If the schema file is not found, proceed without validation but warn: "Schema validation skipped — design-tokens.schema.json not found."

Structural validation (flat tokens): After schema validation, verify that all token values under colors, spacing, typography, breakpoints, shadows, and borders are primitive (string or number), never nested objects. The d2c-build validator (walkTokens) walks to leaf values and expects flat paths like colors.primary or spacing.md. If any token value is an object (e.g., { value: "#2563EB", css_var: "--color-primary" }), flatten it: extract the resolved CSS value and store it directly (e.g., "primary": "#2563EB"). Also verify token categorization: spacing values (4px, 0.5rem, 16px) belong under spacing, border-radius values (0.375rem, 8px) belong under borders — never mix them. Warn the user if any recategorization was needed.

If no libraries are detected in any category, omit the preferred_libraries section. If no data fetching library exists, omit the api section. Include the conventions section from Step 5h — omit conventions that don't apply to the detected framework (see Step 5h for applicability rules).

Step 6b: Auto-Split for Large Projects

After writing design-tokens.json in Step 6, check its size:

  1. Count the line count of .claude/d2c/design-tokens.json.
  2. Estimate tokens as Math.ceil(character_count / 4).

If the file exceeds 400 lines OR ~20,000 tokens:

Split the monolithic file into focused files in .claude/d2c/:

Split file Contents
tokens-core.json d2c_schema_version, framework, meta_framework, component_file_extension, island_frameworks, styling_approach, spacing, typography, breakpoints, hooks
tokens-colors.json colors, shadows, borders (all visual tokens)
tokens-components.json components array
tokens-conventions.json conventions, preferred_libraries, api, figma_variables

Steps:

  1. Read the complete design-tokens.json.
  2. For each split file, extract the relevant keys and write as a standalone JSON object.
  3. Rewrite design-tokens.json to contain ONLY d2c_schema_version, split_files: true, framework, and meta_framework. Remove all keys that were copied into split files (colors, spacing, typography, breakpoints, shadows, borders, components, conventions, preferred_libraries, api, hooks, figma_variables, etc.). The split files are now the source of truth — the monolithic file becomes a lightweight pointer.
  4. Tell the user: "design-tokens.json is large ({lines} lines, ~{tokens} tokens). Created 4 split files for efficient per-phase loading: tokens-core.json, tokens-colors.json, tokens-components.json, tokens-conventions.json. The main file has been trimmed to avoid duplication."

If the file is under 400 lines AND under 20,000 tokens:

  • Do not create split files.
  • If split_files was previously set to true in the file but the file is now small enough, remove the flag and delete any stale split files.

On incremental updates: When running an incremental update (pre-check detected existing file), if split files exist, regenerate only the split files whose source sections changed. For example, if only components changed, only rewrite tokens-components.json. If framework or meta_framework changed, also update the trimmed design-tokens.json pointer file.

Step 7: Check Playwright, Pixelmatch, and Pngjs

Ensure Playwright, pixelmatch, and pngjs are installed globally and ready for the visual verification workflow. These are installed globally so they work regardless of how d2c was installed (plugin, skills, manual) and across all projects.

Playwright:

  1. Check if already available: Run npx --no-install playwright --version 2>/dev/null or check if playwright exists in the project's node_modules/ or package.json devDependencies. If available, skip to Chromium check below.
  2. If not found, install globally: npm install -g playwright && npx playwright install chromium
  3. Chromium check: Regardless of where Playwright was found, ensure Chromium is installed: run npx playwright install chromium.
  4. If install fails, warn the user: "Could not install Playwright. Install it manually with npm install -g playwright && npx playwright install chromium. Visual verification will be unavailable until then." Do not block — continue with init.

Pixelmatch and pngjs (for objective visual scoring):

  1. Check if already available globally: Run node -e "require('pixelmatch'); require('pngjs'); console.log('pixeldiff ready')". If this succeeds, skip installation.
  2. If not found globally, check the project's node_modules/ — look for node_modules/pixelmatch and node_modules/pngjs. If both exist, skip.
  3. If not found anywhere, install globally: npm install -g pixelmatch pngjs
  4. Verify after install: Run node -e "require('pixelmatch'); require('pngjs'); console.log('pixeldiff ready')" to confirm they resolve.
  5. If install fails, warn the user: "Could not install pixelmatch/pngjs. Pixel-diff scoring will be unavailable — builds will use visual-only comparison." Do not block.
  6. If already installed, skip.

Step 8: Verify and Confirm

After generating the file:

  1. Show the user a summary:
    • Framework detected (e.g., Vue 3 / Nuxt 3)
    • Styling approach detected
    • Number of colors/tokens extracted
    • Components discovered (count and names)
    • Hooks found (count and names)
    • Preferred libraries — list each category and the selected library. For categories where the user made a choice, note it. For auto-selected (single library) categories, note they were auto-selected.
    • API configuration detected (if any)
    • Figma variable mismatches (if any)
    • Token conflicts — if token-conflicts.json exists and has entries, show the conflict summary (see below)
  2. Ask if anything looks wrong or missing
  3. If they correct something, update the file
  4. Confirm initialization is complete and they can now use /d2c-build

Token Conflict Presentation (Step 8)

If token-conflicts.json has any entries, present them in the summary as follows:

Auto-resolved conflicts (FYI):

For each conflict with status: "auto-resolved", show a single line:

  • {canonical} chosen over {duplicates joined with ", "} ({usage ratio}x usage ratio)

The user can override any auto-resolution by saying so. If they override, update the entry to status: "user-resolved", chosen_by: "user_choice", and set the new canonical.

Unresolved conflicts (needs input):

If there are unresolved conflicts, present each one:

Token conflicts detected — {unresolved_count} token groups need your input:

  1. {category} value {resolved_value} is shared by:
    • {dotted_path} ({usage_count} references)
    • {dotted_path} ({usage_count} references) Recommendation: {highest-usage name if ratio >= 2x | "needs your input (usage too close)"}

For each group, which token should be the canonical name? Options:

  • Accept all recommendations
  • Choose differently for specific groups
  • Skip for now (d2c-build will ask again in Phase 2)

When the user resolves a conflict, update token-conflicts.json:

  • Set canonical to the user's choice
  • Move the other paths to duplicates
  • Set status: "user-resolved", chosen_by: "user_choice"
  • Re-compute unresolved_count

Important Rules

  • Do NOT invent tokens that don't exist in the codebase. Only record what's actually there.
  • If the project is new with almost no tokens or components, say so. A sparse file is fine.
  • If you find inconsistencies (e.g., 5 different grays with no naming convention), note them but record as-is. Don't "clean up" the design system — that's the user's job.