Skip to content

Commit 4aaad3b

Browse files
authored
Merge pull request #1 from ryandmonk/feature/phase-16c-storybook-adapter
feat(phase-16c): Storybook MCP adapter + cross-surface drift analysis
2 parents d4e2175 + 3d741f5 commit 4aaad3b

23 files changed

Lines changed: 2756 additions & 100 deletions

README.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ It keeps them in sync continuously.
9797
| **Reconciled** | Design overrides, code markers, AST values, and defaults are merged with explicit precedence: `override > marker > ast > code`. |
9898
| **Auditable** | Every operation produces artifacts. Every decision is traceable. CI gates enforce drift thresholds. |
9999
| **Safe** | Dry-run by default. Opt-in writes. Echo suppression prevents feedback loops. Rollback previews before destructive changes. |
100-
| **Read-only adapters** | External integrations (e.g., Figma MCP, Storybook) are read-only with default-deny tool policies. Adapters are classified by surface type, access mode, authority role, and stability. AF is the only mutation authority. |
100+
| **Read-only adapters** | External integrations (Figma MCP, Storybook MCP) are read-only with default-deny tool policies. Adapters are classified by surface type, access mode, authority role, and stability. AF is the only mutation authority. |
101101

102102
### How It Differs From…
103103

@@ -106,6 +106,7 @@ It keeps them in sync continuously.
106106
| **Prompt-to-code** (v0, Bolt, etc.) | AF doesn't generate code from designs. It *reconciles* code and design as a continuous system. |
107107
| **Design token export** (Style Dictionary, etc.) | AF goes beyond tokens — it syncs component structure, variants, states, and properties bidirectionally. |
108108
| **MCP integrations** (figma-console-mcp, etc.) | AF uses MCP as a *read-only data source*, never as a mutation path. AF's control plane is watcher → server → plugin. |
109+
| **Storybook MCP** (@storybook/addon-mcp) | AF connects to Storybook's MCP endpoint to read component metadata for cross-surface drift analysis — it does not run or control Storybook. |
109110
| **Figma plugins** (code-gen plugins) | AF's plugin is a *mutation executor*, not a decision-maker. Reconciliation happens in the watcher. |
110111

111112
## Architecture
@@ -200,6 +201,7 @@ Available profiles: `designer-first`, `code-first`, `balanced`, `strict-review`.
200201
| `af design pull` | Pull design data (tokens + components + styles) |
201202
| `af design screenshot` | Capture design screenshot |
202203
| `af design component [name]` | List or inspect components |
204+
| `af design drift [name]` | Cross-surface drift analysis (Figma vs Storybook vs code) |
203205

204206
## Project Structure
205207

@@ -208,10 +210,14 @@ aesthetic-function/
208210
├── packages/
209211
│ ├── shared/ # Protocol definitions, shared types
210212
│ ├── watcher/ # Reconciliation engine, AST analysis, adapters
213+
│ │ └── src/
214+
│ │ ├── designAdapter/ # Figma + Storybook MCP adapters
215+
│ │ └── crossSurfaceDrift/ # Cross-surface drift analysis engine
211216
│ ├── server/ # WebSocket/HTTP relay, audit logging
212217
│ ├── figma-plugin/ # Figma sandbox plugin (mutation executor)
213218
│ └── cli/ # `af` CLI control surface
214-
├── demo-app/ # Sample React app with @figma markers
219+
├── demo-app/ # Sample React app with @figma markers + Storybook
220+
│ └── .storybook/ # Storybook config (addon-mcp enabled)
215221
├── docs/
216222
│ └── architecture-reference.md # Full internal reference
217223
├── .github/
@@ -228,6 +234,8 @@ aesthetic-function/
228234
| `FIGMA_FILE_KEY` || Figma file key |
229235
| `USE_LLM_ANALYZER` | `false` | Enable LLM intent parsing (optional) |
230236
| `TRACE` | `false` | Enable trace logging |
237+
| `STORYBOOK_URL` | `http://localhost:6006` | Storybook dev server URL |
238+
| `STORYBOOK_ENABLED` | `false` | Enable Storybook MCP adapter |
231239

232240
See [docs/architecture-reference.md](docs/architecture-reference.md) for the complete environment variable reference.
233241

claude.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ This repository implements an AI-driven Code → Design synchronization system.
2222
- ui.html: network allowed
2323
- code.ts: NO network, NO filesystem
2424

25+
4. Storybook Dev Server (optional, Phase 16C)
26+
- Runs on http://localhost:6006 (configurable)
27+
- Exposes MCP endpoint via @storybook/addon-mcp at /mcp
28+
- Read-only data source for cross-surface drift analysis
29+
- Start with: `pnpm dev:storybook`
30+
2531
## Protocol Rules
2632
- All cross-process communication uses shared TypeScript interfaces
2733
- Single canonical protocol file: `/packages/shared/src/protocol.ts`
@@ -33,6 +39,12 @@ This repository implements an AI-driven Code → Design synchronization system.
3339
- Never mix explanation with structured output
3440
- Retry with repair prompts if validation fails
3541

42+
## Design Adapter Rules
43+
- Adapters are read-only with default-deny tool policies
44+
- Storybook MCP adapter: `af design drift [component]` for cross-surface analysis
45+
- Storybook adapter requires dev server running (`pnpm dev:storybook`)
46+
- Cross-surface drift is a separate analysis pass — it does NOT modify reconciliation
47+
3648
## Design Token Rules
3749
- Prefer semantic tokens over raw values
3850
- Token resolution happens before Figma operations

demo-app/.storybook/main.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import type { StorybookConfig } from '@storybook/react-vite';
2+
3+
const config: StorybookConfig = {
4+
stories: ['../src/**/*.stories.@(ts|tsx)'],
5+
addons: [
6+
'@storybook/addon-essentials',
7+
'@storybook/addon-mcp',
8+
],
9+
framework: '@storybook/react-vite',
10+
features: {
11+
componentsManifest: true,
12+
},
13+
};
14+
15+
export default config;

demo-app/.storybook/preview.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import type { Preview } from '@storybook/react';
2+
3+
const preview: Preview = {
4+
parameters: {
5+
controls: {
6+
matchers: {
7+
color: /(background|color)$/i,
8+
date: /Date$/i,
9+
},
10+
},
11+
},
12+
};
13+
14+
export default preview;

demo-app/package.json

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,23 @@
22
"name": "demo-app",
33
"version": "0.1.0",
44
"private": true,
5-
"description": "Demo React app with @figma markers for testing the watcher"
5+
"description": "Demo React app with @figma markers for testing the watcher",
6+
"scripts": {
7+
"storybook": "storybook dev -p 6006",
8+
"build-storybook": "storybook build"
9+
},
10+
"dependencies": {
11+
"react": "^18.3.1",
12+
"react-dom": "^18.3.1"
13+
},
14+
"devDependencies": {
15+
"@storybook/addon-essentials": "^8.6.14",
16+
"@storybook/addon-mcp": "^0.5.0",
17+
"@storybook/react": "^8.6.14",
18+
"@storybook/react-vite": "^8.6.14",
19+
"storybook": "^8.6.14",
20+
"typescript": "^5.3.3",
21+
"@types/react": "^18.3.20",
22+
"@types/react-dom": "^18.3.7"
23+
}
624
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { DemoButton, TestBox, WelcomeHeading } from '../App';
3+
4+
// =============================================================================
5+
// DEMO BUTTON
6+
// =============================================================================
7+
8+
const buttonMeta: Meta<typeof DemoButton> = {
9+
title: 'Components/DemoButton',
10+
component: DemoButton,
11+
};
12+
13+
export default buttonMeta;
14+
15+
type ButtonStory = StoryObj<typeof DemoButton>;
16+
17+
export const Default: ButtonStory = {};
18+
19+
// =============================================================================
20+
// TEST BOX
21+
// =============================================================================
22+
23+
export const Box: StoryObj<typeof TestBox> = {
24+
render: () => <TestBox />,
25+
};
26+
27+
// =============================================================================
28+
// WELCOME HEADING
29+
// =============================================================================
30+
31+
export const Heading: StoryObj<typeof WelcomeHeading> = {
32+
render: () => <WelcomeHeading />,
33+
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import type { Meta, StoryObj } from '@storybook/react';
2+
import { Card, SuccessButton, ErrorButton } from '../Card';
3+
4+
// =============================================================================
5+
// CARD
6+
// =============================================================================
7+
8+
const cardMeta: Meta<typeof Card> = {
9+
title: 'Components/Card',
10+
component: Card,
11+
args: {
12+
title: 'Card Title',
13+
children: 'Card content goes here.',
14+
},
15+
};
16+
17+
export default cardMeta;
18+
19+
type CardStory = StoryObj<typeof Card>;
20+
21+
export const Default: CardStory = {};
22+
23+
export const WithLongTitle: CardStory = {
24+
args: { title: 'A Very Long Card Title That Wraps' },
25+
};
26+
27+
// =============================================================================
28+
// SUCCESS BUTTON (co-located for simplicity)
29+
// =============================================================================
30+
31+
export const Success: StoryObj<typeof SuccessButton> = {
32+
render: () => <SuccessButton />,
33+
};
34+
35+
// =============================================================================
36+
// ERROR BUTTON (co-located for simplicity)
37+
// =============================================================================
38+
39+
export const Error: StoryObj<typeof ErrorButton> = {
40+
render: () => <ErrorButton />,
41+
};

docs/adapter-model.md

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ AF uses design adapters to read data from external design systems. Adapters are
1313
|---------|-----------|------------|-------|
1414
| **Figma REST** | HTTP (Figma API) | Tokens, components, styles | 16A |
1515
| **Figma Console MCP** | stdio / SSE / REST fallback | Screenshots, component inspection | 16B |
16+
| **Storybook MCP** | StreamableHTTP / SSE / HTTP fallback | Component metadata, stories, props | 16C |
1617

1718
## Safety Model
1819

@@ -21,6 +22,51 @@ AF uses design adapters to read data from external design systems. Adapters are
2122
- The MCP adapter validates tool names against an allow-list before every call
2223
- Blocked tools are rejected with a descriptive error, never silently dropped
2324

25+
## Storybook MCP Adapter (Phase 16C)
26+
27+
The Storybook MCP adapter connects to `@storybook/addon-mcp` running inside a local Storybook dev server. It enables **cross-surface drift analysis** — comparing component metadata across Figma, Storybook, and code AST.
28+
29+
### Transport
30+
31+
Connects via HTTP to the Storybook dev server's `/mcp` endpoint (not stdio). Falls back to direct HTTP (`/manifests/components.json`) if MCP is unavailable.
32+
33+
### Operating Modes
34+
35+
| Mode | Condition | Capabilities |
36+
|------|-----------|-------------|
37+
| `mcp` | MCP endpoint responds | Full: component metadata, stories, props, screenshots |
38+
| `http-fallback` | Server up, MCP unavailable | Reduced: component metadata from manifest only |
39+
| `unavailable` | Server unreachable | None — `isAvailable()` returns false |
40+
41+
The capability manifest (`getCapabilities()`) reflects the actual operating mode. MCP-only capabilities (e.g., `readScreenshots`) report `false` in HTTP-fallback mode.
42+
43+
### Tool Policy
44+
45+
Same default-deny pattern as Figma Console MCP:
46+
47+
| Tool | Status | Reason |
48+
|------|--------|--------|
49+
| `list-all-documentation` | Allowed | Read-only component listing |
50+
| `get-documentation` | Allowed | Read-only component metadata |
51+
| `get-documentation-for-story` | Allowed | Read-only story metadata |
52+
| `get-storybook-story-instructions` | Allowed | Read-only story instructions |
53+
| `run-story-tests` | **Blocked** | Side-effects (test execution) |
54+
| `preview-stories` | **Blocked** | May trigger renders |
55+
| *(any unlisted tool)* | **Blocked** | Default-deny |
56+
57+
### Framework Guard
58+
59+
The adapter validates that the Storybook instance is React-based. Non-React frameworks (Vue, Angular, Svelte) cause `isAvailable()` to return `false` with an explicit error.
60+
61+
### Cross-Surface Drift Analysis
62+
63+
The `af design drift` command compares component data across available surfaces:
64+
- **Figma** — variants, properties from Figma adapter
65+
- **Storybook** — props, stories, variant axes from Storybook MCP
66+
- **Code** — props, union types from AST analysis
67+
68+
Drift findings use **corroboration rules** to filter noise: a story-derived variant is only reported if (1) the variant axis maps to a real prop, (2) the value appears in the prop's type definition. Findings carry `confidence: 'high'` (constrained union match) or `'low'` (unconstrained type like `string`).
69+
2470
## Detailed Reference
2571

26-
The full adapter model, including MCP transport configuration, tool policies, and Phase 16A/16B implementation details, is documented in [architecture-reference.md](architecture-reference.md).
72+
The full adapter model, including MCP transport configuration, tool policies, and Phase 16A/16B/16C implementation details, is documented in [architecture-reference.md](architecture-reference.md).

docs/architecture-reference.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -534,6 +534,7 @@ The system follows a **three-legged stool** design with strict runtime boundarie
534534
| **Phase 16A** | Design Adapter Interface (verification-scoped) ||
535535
| **Phase 16A.1** | Surface Classification Metadata (adapter taxonomy) ||
536536
| **Phase 16B** | Figma Console MCP Adapter (read-only) ||
537+
| **Phase 16C** | Storybook MCP Adapter + Cross-Surface Drift Analysis ||
537538

538539
### Not Implemented Yet
539540

@@ -557,7 +558,7 @@ The reconciliation system is **feature-complete through Phase 14F**. Key capabil
557558
- **Unified Reconcile CLI (14A–14F)**: `figma:reconcile` entry point, profiles (local/record/ci), bundle artifacts, GitHub Actions matrix workflow, multi-source discovery
558559
- **Configuration & Profiles (15A–15B)**: `af.config.json`, named policy profiles (designer-first, code-first, balanced, strict-review)
559560
- **CLI & Inspector (15C–15D)**: Unified `af` CLI (control surface, not runtime), artifact listing/inspection/trace
560-
- **Design Adapters (16A–16B)**: Read-only design adapter interface, Figma Console MCP adapter, surface classification metadata (taxonomy layer for adapter categorization)
561+
- **Design Adapters (16A–16C)**: Read-only design adapter interface, Figma Console MCP adapter, Storybook MCP adapter, surface classification metadata, cross-surface drift analysis
561562

562563
Echo suppression prevents feedback loops when AST writes trigger file save events.
563564

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"demo:watcher": "echo '👁 Starting watcher...' && pnpm --filter @aesthetic-function/watcher watch",
2323
"demo:feature": "echo '🎯 Running feature orchestrator...' && pnpm --filter @aesthetic-function/watcher cli:feature",
2424
"demo:fast": "echo '⚡ Quick demo: server + watcher + feature (use --prompt)' && concurrently -n server,watcher 'pnpm demo:server' 'sleep 2 && pnpm demo:watcher'",
25-
"demo:tunnel": "echo '🚇 Expose server for Figma plugin...' && pnpm tunnel && echo 'Copy the HTTPS URL to Figma plugin settings'"
25+
"demo:tunnel": "echo '🚇 Expose server for Figma plugin...' && pnpm tunnel && echo 'Copy the HTTPS URL to Figma plugin settings'",
26+
"dev:storybook": "cd demo-app && npx storybook dev -p 6006"
2627
},
2728
"engines": {
2829
"node": ">=18.0.0",

0 commit comments

Comments
 (0)