Initial commit: Clean DSS implementation
Migrated from design-system-swarm with fresh git history.
Old project history preserved in /home/overbits/apps/design-system-swarm
Core components:
- MCP Server (Python FastAPI with mcp 1.23.1)
- Claude Plugin (agents, commands, skills, strategies, hooks, core)
- DSS Backend (dss-mvp1 - token translation, Figma sync)
- Admin UI (Node.js/React)
- Server (Node.js/Express)
- Storybook integration (dss-mvp1/.storybook)
Self-contained configuration:
- All paths relative or use DSS_BASE_PATH=/home/overbits/dss
- PYTHONPATH configured for dss-mvp1 and dss-claude-plugin
- .env file with all configuration
- Claude plugin uses ${CLAUDE_PLUGIN_ROOT} for portability
Migration completed: $(date)
🤖 Clean migration with full functionality preserved
This commit is contained in:
571
docs/TOKEN_INGESTION.md
Normal file
571
docs/TOKEN_INGESTION.md
Normal file
@@ -0,0 +1,571 @@
|
||||
# DSS Token Ingestion Guide
|
||||
|
||||
Complete documentation for multi-source token ingestion, merge strategies, and translation dictionaries.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Supported Sources](#supported-sources)
|
||||
3. [Token Extraction](#token-extraction)
|
||||
4. [Merge System](#merge-system)
|
||||
5. [Translation Dictionaries](#translation-dictionaries)
|
||||
6. [MCP Tools](#mcp-tools)
|
||||
7. [Examples](#examples)
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
DSS provides a unified token ingestion pipeline that:
|
||||
|
||||
- Extracts tokens from multiple source formats (CSS, SCSS, Tailwind, JSON, Figma)
|
||||
- Normalizes naming conventions to DSS canonical format
|
||||
- Merges tokens with intelligent conflict resolution
|
||||
- Generates translation dictionaries for traceability
|
||||
- Exports to multiple output formats
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ DSS INGESTION PIPELINE │
|
||||
├─────────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ SOURCES PROCESS OUTPUT │
|
||||
│ ─────── ─────── ────── │
|
||||
│ CSS ─┐ │
|
||||
│ SCSS ─┼─→ Extract → Normalize → Merge → Export │
|
||||
│ Tailwind ─┤ ↓ ↓ ↓ ↓ │
|
||||
│ JSON/W3C ─┤ Tokens DSS Names Unified CSS/SCSS/ │
|
||||
│ Figma ─┘ TS/JSON │
|
||||
│ │
|
||||
│ ↓ │
|
||||
│ Translation Dictionary │
|
||||
│ (per-project mapping) │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Supported Sources
|
||||
|
||||
### CSS Custom Properties
|
||||
|
||||
Extracts CSS variables from `:root` and other selectors.
|
||||
|
||||
```css
|
||||
:root {
|
||||
/* Primary colors */
|
||||
--primary-500: #3B82F6;
|
||||
--primary-600: #2563EB;
|
||||
|
||||
/* Spacing */
|
||||
--spacing-md: 16px;
|
||||
}
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Comment extraction for descriptions
|
||||
- Auto-detection of token type (color, dimension, etc.)
|
||||
- Category inference from naming patterns
|
||||
|
||||
### SCSS Variables
|
||||
|
||||
Extracts SCSS variables and maps.
|
||||
|
||||
```scss
|
||||
// Brand colors
|
||||
$primary-500: #3B82F6;
|
||||
$primary-600: #2563EB;
|
||||
|
||||
// Spacing map
|
||||
$spacing: (
|
||||
xs: 4px,
|
||||
sm: 8px,
|
||||
md: 16px,
|
||||
lg: 24px,
|
||||
);
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Single variable extraction
|
||||
- Map/object flattening
|
||||
- Comment-based descriptions
|
||||
|
||||
### Tailwind Configuration
|
||||
|
||||
Extracts tokens from `tailwind.config.js/ts` or Tailwind v4 CSS.
|
||||
|
||||
```javascript
|
||||
// tailwind.config.js
|
||||
module.exports = {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
primary: {
|
||||
500: '#3B82F6',
|
||||
600: '#2563EB',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
**Features:**
|
||||
- Theme object parsing
|
||||
- Extend section support
|
||||
- Tailwind v4 `@theme` directive
|
||||
|
||||
### JSON Token Files
|
||||
|
||||
Supports multiple JSON formats:
|
||||
|
||||
**W3C Design Tokens:**
|
||||
```json
|
||||
{
|
||||
"color": {
|
||||
"primary": {
|
||||
"500": {
|
||||
"$value": "#3B82F6",
|
||||
"$type": "color",
|
||||
"$description": "Primary brand color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Style Dictionary:**
|
||||
```json
|
||||
{
|
||||
"color": {
|
||||
"primary": {
|
||||
"500": {
|
||||
"value": "#3B82F6",
|
||||
"comment": "Primary brand color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Tokens Studio (Figma Plugin):**
|
||||
```json
|
||||
{
|
||||
"global": {
|
||||
"color": {
|
||||
"primary": {
|
||||
"value": "#3B82F6",
|
||||
"type": "color"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Token Extraction
|
||||
|
||||
### Basic Usage
|
||||
|
||||
```python
|
||||
from tools.ingest import CSSTokenSource, SCSSTokenSource, JSONTokenSource
|
||||
|
||||
# Extract from CSS
|
||||
css_source = CSSTokenSource()
|
||||
css_tokens = await css_source.extract("/path/to/tokens.css")
|
||||
|
||||
# Extract from SCSS
|
||||
scss_source = SCSSTokenSource()
|
||||
scss_tokens = await scss_source.extract("/path/to/variables.scss")
|
||||
|
||||
# Extract from JSON
|
||||
json_source = JSONTokenSource()
|
||||
json_tokens = await json_source.extract("/path/to/tokens.json")
|
||||
```
|
||||
|
||||
### Token Structure
|
||||
|
||||
Each extracted token contains:
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class DesignToken:
|
||||
name: str # Normalized name: "color.primary.500"
|
||||
value: Any # Token value: "#3B82F6"
|
||||
type: TokenType # Type enum: COLOR, DIMENSION, etc.
|
||||
description: str # From comments/descriptions
|
||||
source: str # Source identifier: "css:tokens.css:12"
|
||||
source_file: str # Original file path
|
||||
source_line: int # Line number in source
|
||||
original_name: str # Original name: "--primary-500"
|
||||
original_value: str # Original value before processing
|
||||
category: TokenCategory # Category: COLORS, SPACING, etc.
|
||||
tags: List[str] # Custom tags
|
||||
deprecated: bool # Deprecation flag
|
||||
version: str # Version string
|
||||
```
|
||||
|
||||
### Name Normalization
|
||||
|
||||
All source names are normalized to DSS canonical format:
|
||||
|
||||
| Source Format | Original Name | DSS Canonical |
|
||||
|---------------|---------------|---------------|
|
||||
| CSS | `--primary-500` | `primary.500` |
|
||||
| CSS (prefixed) | `--heroui-primary-500` | `heroui.primary.500` |
|
||||
| SCSS | `$primary-500` | `primary.500` |
|
||||
| camelCase | `--brandAccent` | `brand.accent` |
|
||||
| SCREAMING | `--SPACING_LG` | `spacing.lg` |
|
||||
|
||||
---
|
||||
|
||||
## Merge System
|
||||
|
||||
### Merge Strategies
|
||||
|
||||
| Strategy | Description | Best For |
|
||||
|----------|-------------|----------|
|
||||
| `FIRST` | Keep first occurrence | Preserving original values |
|
||||
| `LAST` | Use latest value (override) | Latest source wins |
|
||||
| `PREFER_FIGMA` | Prioritize Figma sources | Design-led workflow |
|
||||
| `PREFER_CODE` | Prioritize CSS/SCSS sources | Code-led workflow |
|
||||
| `PREFER_SPECIFIC` | Prefer concrete values over `var()` | Resolving references |
|
||||
| `MERGE_METADATA` | Combine metadata, use latest value | Preserving history |
|
||||
| `ERROR` | Raise error on conflict | Strict validation |
|
||||
| `INTERACTIVE` | Require user decision | Manual review |
|
||||
|
||||
### Usage
|
||||
|
||||
```python
|
||||
from tools.ingest import TokenMerger, MergeStrategy
|
||||
|
||||
# Create merger with strategy
|
||||
merger = TokenMerger(strategy=MergeStrategy.PREFER_FIGMA)
|
||||
|
||||
# Merge multiple collections
|
||||
result = merger.merge([css_tokens, scss_tokens, figma_tokens])
|
||||
|
||||
# Access results
|
||||
print(f"Total tokens: {result.stats['total_tokens']}")
|
||||
print(f"Conflicts resolved: {result.stats['conflicts_resolved']}")
|
||||
|
||||
# Examine conflicts
|
||||
for conflict in result.conflicts:
|
||||
print(f"Token: {conflict.token_name}")
|
||||
print(f" Source A: {conflict.existing.value} ({conflict.existing.source})")
|
||||
print(f" Source B: {conflict.incoming.value} ({conflict.incoming.source})")
|
||||
print(f" Resolution: {conflict.resolution}")
|
||||
print(f" Final value: {conflict.resolved_token.value}")
|
||||
```
|
||||
|
||||
### Custom Resolver
|
||||
|
||||
```python
|
||||
def custom_resolver(conflict: MergeConflict) -> DesignToken:
|
||||
"""Custom conflict resolution logic."""
|
||||
# Always prefer hex colors over named colors
|
||||
if conflict.incoming.value.startswith('#'):
|
||||
return conflict.incoming
|
||||
return conflict.existing
|
||||
|
||||
merger = TokenMerger(
|
||||
strategy=MergeStrategy.LAST,
|
||||
custom_resolver=custom_resolver
|
||||
)
|
||||
```
|
||||
|
||||
### Merge Result
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class MergeResult:
|
||||
collection: TokenCollection # Merged tokens
|
||||
conflicts: List[MergeConflict] # All conflicts encountered
|
||||
stats: Dict[str, int] # Statistics
|
||||
warnings: List[str] # Warning messages
|
||||
```
|
||||
|
||||
Statistics include:
|
||||
- `total_tokens`: Final token count
|
||||
- `new_tokens`: Tokens added without conflict
|
||||
- `updated_tokens`: Tokens updated during merge
|
||||
- `conflicts_resolved`: Conflicts auto-resolved
|
||||
- `conflicts_unresolved`: Conflicts requiring review
|
||||
|
||||
---
|
||||
|
||||
## Translation Dictionaries
|
||||
|
||||
### Purpose
|
||||
|
||||
Translation dictionaries map external token systems TO DSS canonical structure:
|
||||
|
||||
1. **Traceability** - Know where each token came from
|
||||
2. **Reproducibility** - Re-run ingestion with same mappings
|
||||
3. **Documentation** - Human-readable mapping reference
|
||||
4. **Custom Props** - Track client-specific extensions
|
||||
|
||||
### Schema
|
||||
|
||||
```json
|
||||
{
|
||||
"$schema": "dss-translation-v1",
|
||||
"project": "project-name",
|
||||
"source": "heroui | shadcn | css | scss | figma | tailwind",
|
||||
"version": "1.0.0",
|
||||
"created": "2025-01-15",
|
||||
"mappings": {
|
||||
"tokens": {
|
||||
"<source-token>": "<dss-canonical-token>"
|
||||
},
|
||||
"components": {
|
||||
"<source-component>": "<dss-component>[variant=value]"
|
||||
}
|
||||
},
|
||||
"custom_props": {
|
||||
"<dss-namespaced-token>": "<value>"
|
||||
},
|
||||
"unmapped": ["tokens that couldn't be mapped"],
|
||||
"notes": ["human-readable notes"]
|
||||
}
|
||||
```
|
||||
|
||||
### Project Structure
|
||||
|
||||
```
|
||||
project-acme/
|
||||
├── .dss/
|
||||
│ ├── config.json # Project configuration
|
||||
│ └── translations/
|
||||
│ ├── heroui.json # HeroUI → DSS mappings
|
||||
│ ├── legacy-css.json # Legacy CSS → DSS mappings
|
||||
│ └── custom.json # Custom props specific to ACME
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## MCP Tools
|
||||
|
||||
DSS provides MCP tools for token ingestion:
|
||||
|
||||
### `ingest_css_tokens`
|
||||
Extract tokens from CSS file.
|
||||
```
|
||||
ingest_css_tokens(source: "/path/to/tokens.css")
|
||||
```
|
||||
|
||||
### `ingest_scss_tokens`
|
||||
Extract tokens from SCSS file.
|
||||
```
|
||||
ingest_scss_tokens(source: "/path/to/variables.scss")
|
||||
```
|
||||
|
||||
### `ingest_tailwind_tokens`
|
||||
Extract tokens from Tailwind config.
|
||||
```
|
||||
ingest_tailwind_tokens(source: "/path/to/tailwind.config.js")
|
||||
```
|
||||
|
||||
### `ingest_json_tokens`
|
||||
Extract tokens from JSON file (W3C, Style Dictionary, Tokens Studio).
|
||||
```
|
||||
ingest_json_tokens(source: "/path/to/tokens.json")
|
||||
```
|
||||
|
||||
### `merge_tokens`
|
||||
Merge multiple token sources.
|
||||
```
|
||||
merge_tokens(
|
||||
sources: "/path/a.css,/path/b.scss",
|
||||
strategy: "prefer_figma"
|
||||
)
|
||||
```
|
||||
|
||||
### `export_tokens`
|
||||
Export tokens to various formats.
|
||||
```
|
||||
export_tokens(
|
||||
source: "/path/to/tokens.css",
|
||||
format: "typescript", # css, scss, typescript, json, tailwind
|
||||
output_path: "/path/to/output.ts"
|
||||
)
|
||||
```
|
||||
|
||||
### `validate_tokens`
|
||||
Validate token collection.
|
||||
```
|
||||
validate_tokens(source: "/path/to/tokens.css")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Examples
|
||||
|
||||
### Example 1: Merge HeroUI + Legacy CSS
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
from tools.ingest import (
|
||||
CSSTokenSource, TokenMerger, MergeStrategy
|
||||
)
|
||||
|
||||
async def merge_heroui_legacy():
|
||||
css_source = CSSTokenSource()
|
||||
|
||||
# Extract from both sources
|
||||
heroui_tokens = await css_source.extract("heroui-theme.css")
|
||||
legacy_tokens = await css_source.extract("legacy-styles.css")
|
||||
|
||||
# Merge with HeroUI priority (newer system)
|
||||
merger = TokenMerger(strategy=MergeStrategy.LAST)
|
||||
result = merger.merge([legacy_tokens, heroui_tokens])
|
||||
|
||||
print(f"Merged {result.stats['total_tokens']} tokens")
|
||||
print(f"Resolved {len(result.conflicts)} conflicts")
|
||||
|
||||
# Export to TypeScript
|
||||
print(result.collection.to_typescript())
|
||||
|
||||
return result
|
||||
|
||||
asyncio.run(merge_heroui_legacy())
|
||||
```
|
||||
|
||||
### Example 2: Build Translation Dictionary
|
||||
|
||||
```python
|
||||
import json
|
||||
from tools.ingest import CSSTokenSource
|
||||
|
||||
async def build_translation():
|
||||
css_source = CSSTokenSource()
|
||||
tokens = await css_source.extract("heroui-theme.css")
|
||||
|
||||
translation = {
|
||||
"$schema": "dss-translation-v1",
|
||||
"project": "heroui-migration",
|
||||
"source": "heroui",
|
||||
"mappings": {
|
||||
"tokens": {}
|
||||
}
|
||||
}
|
||||
|
||||
# Build mappings
|
||||
for token in tokens.tokens:
|
||||
# Map HeroUI naming to DSS canonical
|
||||
dss_name = token.normalize_name()
|
||||
|
||||
# Convert: heroui.primary.500 → color.primary.500
|
||||
if "primary" in dss_name or "secondary" in dss_name:
|
||||
dss_name = f"color.{dss_name.replace('heroui.', '')}"
|
||||
|
||||
translation["mappings"]["tokens"][token.original_name] = dss_name
|
||||
|
||||
# Save translation dictionary
|
||||
with open(".dss/translations/heroui.json", "w") as f:
|
||||
json.dump(translation, f, indent=2)
|
||||
|
||||
return translation
|
||||
```
|
||||
|
||||
### Example 3: Diff Two Token Collections
|
||||
|
||||
```python
|
||||
from tools.ingest import CSSTokenSource
|
||||
from tools.ingest.merge import TokenDiff
|
||||
|
||||
async def compare_versions():
|
||||
css_source = CSSTokenSource()
|
||||
|
||||
old_tokens = await css_source.extract("tokens-v1.css")
|
||||
new_tokens = await css_source.extract("tokens-v2.css")
|
||||
|
||||
diff = TokenDiff.diff(old_tokens, new_tokens)
|
||||
|
||||
print(TokenDiff.summary(diff))
|
||||
# Output:
|
||||
# Token Diff Summary:
|
||||
# ========================================
|
||||
#
|
||||
# + Added (5):
|
||||
# + color.accent.500: #F59E0B
|
||||
# + color.accent.600: #D97706
|
||||
# ...
|
||||
#
|
||||
# - Removed (2):
|
||||
# - color.deprecated.old: #FF0000
|
||||
# ...
|
||||
#
|
||||
# ~ Changed (3):
|
||||
# ~ color.primary.500: #3B82F6 → #2563EB
|
||||
# ...
|
||||
#
|
||||
# Unchanged: 120
|
||||
|
||||
asyncio.run(compare_versions())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
### 1. Always Use Translation Dictionaries
|
||||
|
||||
Even for simple projects, maintain translation dictionaries for:
|
||||
- Audit trail of token origins
|
||||
- Reproducible migrations
|
||||
- Team documentation
|
||||
|
||||
### 2. Choose Appropriate Merge Strategy
|
||||
|
||||
| Scenario | Recommended Strategy |
|
||||
|----------|---------------------|
|
||||
| Design system update | `LAST` |
|
||||
| Design-led project | `PREFER_FIGMA` |
|
||||
| Code-led project | `PREFER_CODE` |
|
||||
| Strict validation | `ERROR` |
|
||||
| Complex migration | `MERGE_METADATA` |
|
||||
|
||||
### 3. Isolate Custom Props
|
||||
|
||||
Never pollute DSS core with client-specific tokens:
|
||||
|
||||
```json
|
||||
{
|
||||
"custom_props": {
|
||||
"color.brand.acme.primary": "#1E40AF",
|
||||
"color.brand.acme.accent": "#F59E0B"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Validate Before Production
|
||||
|
||||
Always run validation before deploying:
|
||||
|
||||
```python
|
||||
result = await validate_tokens("/path/to/tokens.css")
|
||||
if result["issues"]:
|
||||
print("Validation failed!")
|
||||
for issue in result["issues"]:
|
||||
print(f" - {issue}")
|
||||
```
|
||||
|
||||
### 5. Version Translation Dictionaries
|
||||
|
||||
Include version in translation dictionaries and track changes:
|
||||
|
||||
```json
|
||||
{
|
||||
"version": "1.2.0",
|
||||
"created": "2025-01-15",
|
||||
"updated": "2025-01-20",
|
||||
"changelog": [
|
||||
"1.2.0: Added accent color mappings",
|
||||
"1.1.0: Updated primary scale",
|
||||
"1.0.0: Initial translation"
|
||||
]
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user