Skip to Content
How It Works

How It Works

A technical deep-dive into AI Kit’s architecture and design decisions.

Architecture Overview

┌─────────────────────────────────────────────────┐ │ CLI (Commander.js) │ │ init | update | reset │ └───────────┬──────────┬──────────┬───────────────┘ │ │ │ ┌───────▼───┐ ┌────▼────┐ ┌──▼──────────┐ │ Scanner │ │Generator│ │ Copier │ │ Module │ │ Module │ │ Module │ └───────┬───┘ └────┬────┘ └──┬──────────┘ │ │ │ ┌───────▼───┐ ┌────▼────┐ ┌──▼──────────┐ │ 7 Detect │ │Template │ │ Skills │ │ Functions │ │Assembler│ │ Commands │ │ │ │ │ │ Guides │ │ │ │ │ │ Docs │ └───────────┘ └─────────┘ └─────────────┘

The Scanner

The scanner is the core intelligence. It reads project indicators and produces a ProjectScan object.

Scan Flow

1. Read package.json 2. Extract dependencies, devDependencies, scripts 3. Run 7 parallel detectors: ├── detectNextjs(projectPath, pkg) ├── detectSitecore(pkg) ├── detectStyling(projectPath, pkg) ├── detectTypescript(projectPath) ├── detectMonorepo(projectPath, pkg) ├── detectPackageManager(projectPath) └── detectFigma(projectPath, pkg) 4. Merge results into ProjectScan

Detection Strategy

Each detector follows a priority chain:

  1. Package dependencies — Most reliable signal. If next is in dependencies, it’s a Next.js project.
  2. Config files — Second signal. turbo.json means Turborepo, tsconfig.json means TypeScript.
  3. Directory structure — Third signal. app/ directory means App Router.
  4. File content — Last resort. Reading globals.css for @theme to detect Tailwind v4.

Detectors never make assumptions. If something can’t be determined, it’s left as undefined and the CLI asks the user.

ProjectScan Type

interface ProjectScan { framework: 'nextjs' | 'react' | 'unknown'; nextjsVersion?: string; routerType?: 'app' | 'pages' | 'hybrid'; cms: 'sitecore-xmc' | 'sitecore-jss' | 'none'; sitecorejssVersion?: string; styling: ('tailwind' | 'css-modules' | 'styled-components' | 'scss')[]; tailwindVersion?: string; typescript: boolean; typescriptStrict?: boolean; monorepo: boolean; monorepoTool?: 'turborepo' | 'nx' | 'lerna' | 'pnpm-workspaces'; figma: { detected: boolean; figmaMcp: boolean; figmaCodeCli: boolean; designTokens: boolean; tokenFormat: 'tailwind-v4' | 'tailwind-v3' | 'css-variables' | 'none'; visualTests: boolean; }; packageManager: 'npm' | 'pnpm' | 'yarn' | 'bun'; projectName: string; projectPath: string; scripts: Record<string, string>; }

The Generator

The generator turns a ProjectScan into output files using a template fragment system.

Fragment Selection

function selectFragments(scan: ProjectScan): string[] { const fragments = ['base']; // Always included // Conditional fragments based on scan results if (scan.framework === 'nextjs' && scan.routerType === 'app') fragments.push('nextjs-app-router'); if (scan.cms !== 'none') fragments.push('sitecore-xmc'); if (scan.styling.includes('tailwind')) fragments.push('tailwind'); // ... etc return fragments; }

Template Assembly

1. Read header.md template 2. For each selected fragment, read templates/{subfolder}/{fragment}.md 3. Join fragments with --- separators 4. Replace {{placeholders}} with scan-derived values 5. Wrap in <!-- AI-KIT:START/END --> markers 6. Add <!-- Generated by ai-kit vX.X.X --> comment

Variable Substitution

The assembler replaces {{variable}} placeholders:

function replacePlaceholders(content, variables) { let result = content; for (const [key, value] of Object.entries(variables)) { result = result.replaceAll(`{{${key}}}`, value); } return result; }

Variables are built from the scan:

  • {{projectName}} — from package.json name
  • {{techStack}} — concatenated detected technologies
  • {{packageManager}} — detected package manager
  • {{scripts}} — filtered standard scripts

The Copier

Simple file copy operations with smart behavior:

  • Skills: Copy from skills/.claude/skills/[name]/SKILL.md and .cursor/skills/[name]/SKILL.md. Always overwrites AI Kit-created skill directories.
  • Commands: Copy from commands/.claude/commands/. Always overwrites. Legacy format kept for backward compatibility.
  • Guides: Copy from guides/ai-kit/guides/. Always overwrites.
  • Doc scaffolds: Copy from docs-scaffolds/docs/. Never overwrites existing files.

The “never overwrite” behavior for docs is intentional — users add content to these files over time. Custom skill directories (those not created by AI Kit) are also never overwritten.


Safe Update Mechanism

The update flow preserves user edits:

1. Read existing file 2. Find <!-- AI-KIT:START --> and <!-- AI-KIT:END --> markers 3. If markers found: - Keep everything BEFORE the start marker - Replace content between markers with new generated content - Keep everything AFTER the end marker 4. If no markers found: - Replace the entire file (backward compatibility)
function mergeWithMarkers(existing: string, newGenerated: string): string { const startIdx = existing.indexOf('<!-- AI-KIT:START -->'); const endIdx = existing.indexOf('<!-- AI-KIT:END -->'); if (startIdx === -1 || endIdx === -1) return newGenerated; const before = existing.substring(0, startIdx); const after = existing.substring(endIdx + '<!-- AI-KIT:END -->'.length); return `${before}${newGenerated}${after}`; }

Design Decisions

Why fragments instead of a single template?

A single large template with conditionals would be:

  • Harder to maintain
  • Harder to test
  • Impossible to scope for Cursor .mdc files

Fragments allow:

  • Independent testing per technology
  • Mix-and-match composition
  • Per-fragment glob targeting in .mdc files

Why detect from package.json instead of AST parsing?

Speed and reliability. package.json is always present, small, and standardized. AST parsing would be slower, fragile across file formats, and overkill for the detection signals we need.

Why markers instead of a separate “custom rules” file?

Developers expect CLAUDE.md to be one file. Splitting into CLAUDE.md + CLAUDE.custom.md would confuse the tool and complicate the setup. Markers are invisible to the AI tool but give ai-kit update a safe boundary.

Why both .cursorrules and .mdc files?

.cursorrules is the established format — all Cursor users can use it. .mdc files are the newer per-file-type format that’s more precise. Generating both ensures backward and forward compatibility.

Last updated on