@catchdrift/cli 0.1.18 → 0.1.19
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/commands/drift-context.md +208 -0
- package/commands/drift-prd.md +250 -0
- package/commands/drift-push.md +274 -0
- package/commands/drift-scaffold.md +281 -0
- package/commands/drift-setup.md +506 -0
- package/commands/drift-sync.md +229 -0
- package/commands/drift.md +252 -0
- package/package.json +2 -1
- package/src/commands/init.mjs +4 -1
- package/src/lib/writers.mjs +27 -1
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Sync Figma and/or Storybook into drift.config.ts and regenerate CLAUDE.md, .cursorrules, and .windsurfrules. Run after adding new DS components or updating Figma."
|
|
3
|
+
allowed-tools: Read, Glob, Grep, Bash, Edit, Write
|
|
4
|
+
argument-hint: "[figma | storybook | tokens | status | --dry-run]"
|
|
5
|
+
disable-model-invocation: true
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# /drift-sync — Sync Figma + Storybook → update manifest
|
|
9
|
+
|
|
10
|
+
Pull the latest component state from Figma and/or Storybook and keep
|
|
11
|
+
`drift.config.ts`, `CLAUDE.md`, `.cursorrules`, and `.windsurfrules` in sync.
|
|
12
|
+
Works with any product team — property management, SaaS, fintech, consumer, B2B.
|
|
13
|
+
|
|
14
|
+
## Arguments: `$ARGUMENTS`
|
|
15
|
+
- *(no args)* — sync from both Storybook and Figma, update all config files
|
|
16
|
+
- `figma` — pull component list from Figma only (all pages)
|
|
17
|
+
- `storybook` — re-discover components from Storybook index only
|
|
18
|
+
- `tokens` — run figma-sync (design tokens + icons only, no component changes)
|
|
19
|
+
- `status` — report sync status without making changes (safe read-only audit)
|
|
20
|
+
- `--dry-run` — show what would change without writing any files
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Step 1 — Read current state
|
|
25
|
+
|
|
26
|
+
Read `drift.config.ts` (or `src/ds-coverage/config.ts`) to get:
|
|
27
|
+
- **Figma source** — the config may use either shape:
|
|
28
|
+
- Single file: `figmaFileKey` + optional `figmaComponentPages` (one-file setup)
|
|
29
|
+
- Multi-file: `figmaFiles: [{ key: string, componentPages?: string[] }, ...]` (components spread across multiple Figma files, e.g. Core DS, Icons, Patterns)
|
|
30
|
+
- Normalise both into a working array: `const files = config.figmaFiles ?? (config.figmaFileKey ? [{ key: config.figmaFileKey, componentPages: config.figmaComponentPages }] : [])`
|
|
31
|
+
- `storybookUrl`
|
|
32
|
+
- `components` — the current registry (component name → storyPath / figmaLink)
|
|
33
|
+
- `threshold`
|
|
34
|
+
- Any `approvedGaps` entries
|
|
35
|
+
|
|
36
|
+
**Component pages** are an inclusive filter — only pull components from these pages. If `componentPages` is not set for a file, include all pages (and flag any page whose name contains "wip", "in progress", "draft", "proposal", "graveyard", or "archive" as potentially unready so the team can review them).
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Step 2 — Dispatch
|
|
41
|
+
|
|
42
|
+
### `status` → Read-only audit
|
|
43
|
+
|
|
44
|
+
Report sync status across all sources without changing anything:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
## Drift Sync Status — <date>
|
|
48
|
+
|
|
49
|
+
Storybook: <storybookUrl>
|
|
50
|
+
Last successful sync: <date if known, else "unknown">
|
|
51
|
+
Reachable: ✅ / ❌
|
|
52
|
+
|
|
53
|
+
Figma files: <N> configured
|
|
54
|
+
figma.com/design/<key1> Reachable: ✅ / ❌
|
|
55
|
+
figma.com/design/<key2> Reachable: ✅ / ❌ (if multi-file)
|
|
56
|
+
FIGMA_API_TOKEN: ✅ set / ❌ missing
|
|
57
|
+
|
|
58
|
+
Config: drift.config.ts
|
|
59
|
+
Components registered: N
|
|
60
|
+
Missing storyPath: N
|
|
61
|
+
Missing figmaLink: N
|
|
62
|
+
Approved gaps: N
|
|
63
|
+
|
|
64
|
+
AI rules files:
|
|
65
|
+
CLAUDE.md: ✅ / ❌ (stale — last component: <X>)
|
|
66
|
+
.cursorrules: ✅ / ❌
|
|
67
|
+
.windsurfrules: ✅ / ❌
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### `tokens` or no args → sync tokens from Figma
|
|
73
|
+
|
|
74
|
+
Run:
|
|
75
|
+
```bash
|
|
76
|
+
npm run figma-sync
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
Requires `FIGMA_API_TOKEN`. If missing:
|
|
80
|
+
```
|
|
81
|
+
export FIGMA_API_TOKEN=your-token
|
|
82
|
+
npm run figma-sync
|
|
83
|
+
|
|
84
|
+
Get token: figma.com → Profile → Settings → Security → Personal access tokens
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
After running, report which token categories updated (colors added/changed, typography changes, spacing changes).
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
### `storybook` or no args → re-discover from Storybook
|
|
92
|
+
|
|
93
|
+
Fetch `{storybookUrl}/index.json`. The response has a flat map of all stories
|
|
94
|
+
across all story files. Parse it to get unique component names and story paths.
|
|
95
|
+
|
|
96
|
+
If Storybook isn't reachable, try the deployed URL (`chromaticUrl`) from config. If
|
|
97
|
+
neither is reachable, report:
|
|
98
|
+
```
|
|
99
|
+
⚠️ Storybook not reachable at <url>
|
|
100
|
+
Run `npm run storybook` first, or provide your deployed Storybook URL in drift.config.ts:
|
|
101
|
+
chromaticUrl: 'https://main--abc123.chromatic.com'
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Compare against `config.components` and report:
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
## Storybook sync
|
|
108
|
+
|
|
109
|
+
New (in Storybook, not in config):
|
|
110
|
+
+ DataTable → story: data-table--default
|
|
111
|
+
+ FilterBar → story: filters-filter-bar--default
|
|
112
|
+
|
|
113
|
+
Removed (in config, no matching story):
|
|
114
|
+
- OldWidget
|
|
115
|
+
|
|
116
|
+
Unchanged: 34 components
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Ask for each new/removed component before changing config. For removed components,
|
|
120
|
+
ask whether to delete from config or keep with a `deprecated: true` flag.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
### `figma` or no args → pull all published components from Figma
|
|
125
|
+
|
|
126
|
+
**Key:** Figma components live on different pages. Use the dedicated
|
|
127
|
+
`/components` endpoint — it returns ALL published components across ALL pages
|
|
128
|
+
with page metadata included. Do NOT try to walk the file tree page by page.
|
|
129
|
+
|
|
130
|
+
Iterate over every file in the normalised `files` array (see Step 1). For each:
|
|
131
|
+
```
|
|
132
|
+
GET https://api.figma.com/v1/files/{file.key}/components
|
|
133
|
+
Headers: X-Figma-Token: {FIGMA_API_TOKEN}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Each component in the response has:
|
|
137
|
+
- `name` — full path e.g. `"Button/Primary/Default"` or `"Forms/Input/Filled"`
|
|
138
|
+
- `node_id` — unique ID for building the figmaLink URL
|
|
139
|
+
- `containing_frame.name` — the frame it lives in
|
|
140
|
+
- `containing_frame.pageName` — **the Figma page it's on**
|
|
141
|
+
- `description` — designer's notes (preserve this — use as component description in config)
|
|
142
|
+
|
|
143
|
+
Group and display results by file, then by page within each file. If multiple files are configured, prefix each section with the file key/URL so it's clear which file the components come from:
|
|
144
|
+
```
|
|
145
|
+
## Figma components found
|
|
146
|
+
|
|
147
|
+
### figma.com/design/<key1> (Core DS)
|
|
148
|
+
📄 Primitives (12 components)
|
|
149
|
+
✅ Button → in config
|
|
150
|
+
✅ Input → in config
|
|
151
|
+
❌ Toggle → NOT in config (node: 123:456)
|
|
152
|
+
❌ Checkbox → NOT in config (node: 123:789)
|
|
153
|
+
|
|
154
|
+
📄 🚧 In Progress (3 components)
|
|
155
|
+
⚠️ SearchBar → draft (will not be added)
|
|
156
|
+
⚠️ FilterChip → draft (will not be added)
|
|
157
|
+
|
|
158
|
+
### figma.com/design/<key2> (Icons & Patterns)
|
|
159
|
+
📄 Patterns (8 components)
|
|
160
|
+
✅ TenantsTable → in config
|
|
161
|
+
❌ DataGrid → NOT in config (node: 456:123)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
For each file, apply the `componentPages` filter:
|
|
165
|
+
- If `componentPages` is set: only include components from those pages; skip everything else
|
|
166
|
+
- If not set: include all pages, but flag any page matching "wip/draft/graveyard/archive/proposal" in a separate "Unreviewed pages" section so the team can decide whether to add them
|
|
167
|
+
|
|
168
|
+
For each component NOT in config (excluding draft pages), ask:
|
|
169
|
+
```
|
|
170
|
+
Add these to drift.config.ts?
|
|
171
|
+
- Toggle (key1 / Primitives page)
|
|
172
|
+
- Checkbox (key1 / Primitives page)
|
|
173
|
+
- DataGrid (key2 / Patterns page)
|
|
174
|
+
|
|
175
|
+
For each, I'll also add the figmaLink pointing to that node.
|
|
176
|
+
Reply with which ones to add, or "all" / "none".
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
When adding, build the figmaLink URL using the file key that component came from:
|
|
180
|
+
```
|
|
181
|
+
https://www.figma.com/design/{file.key}?node-id={node_id}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Also report components in the codebase (from static scan of `src/`) that exist
|
|
185
|
+
in Storybook but have NO matching Figma component — these are code-first gaps:
|
|
186
|
+
```
|
|
187
|
+
In code but not in Figma (consider pushing to Figma):
|
|
188
|
+
⚡ OccupancyWidget — used 1× — run /drift-push OccupancyWidget to add
|
|
189
|
+
⚡ FMKPIRow — used 1×
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## Step 3 — Update files
|
|
195
|
+
|
|
196
|
+
After confirmation (or immediately if `--dry-run` was NOT passed), update:
|
|
197
|
+
|
|
198
|
+
1. **`drift.config.ts`** — add components with storyPath + figmaLink where available; mark removed with `deprecated: true` rather than deleting (preserves history)
|
|
199
|
+
2. **`CLAUDE.md`** — regenerate only the components table (preserve all other content — rules, workflow, etc.)
|
|
200
|
+
3. **`.cursorrules`** — same regeneration (Cursor reads this automatically)
|
|
201
|
+
4. **`.windsurfrules`** — same regeneration (Windsurf reads this automatically); create if it doesn't exist
|
|
202
|
+
|
|
203
|
+
For AI rules files, regenerate ONLY the component table section, bounded by these markers:
|
|
204
|
+
```
|
|
205
|
+
<!-- drift:components-start -->
|
|
206
|
+
...
|
|
207
|
+
<!-- drift:components-end -->
|
|
208
|
+
```
|
|
209
|
+
If markers don't exist, append the table at the end of the file.
|
|
210
|
+
|
|
211
|
+
Report:
|
|
212
|
+
```
|
|
213
|
+
## Sync complete — <date>
|
|
214
|
+
|
|
215
|
+
drift.config.ts — +3 added, 0 deprecated (37 total)
|
|
216
|
+
CLAUDE.md — components table updated (37 components)
|
|
217
|
+
.cursorrules — components table updated
|
|
218
|
+
.windsurfrules — created (37 components)
|
|
219
|
+
|
|
220
|
+
New figmaLinks added: Toggle, Checkbox, DataGrid
|
|
221
|
+
New storyPaths added: DataTable, FilterBar
|
|
222
|
+
|
|
223
|
+
Storybook links: 37/37 ✅
|
|
224
|
+
Figma links: 31/37 (6 missing — run /drift-push gaps to add them)
|
|
225
|
+
|
|
226
|
+
Next: npm run dev and press D to see the updated overlay.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
If `--dry-run` was passed, show what would change without writing anything.
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Analyze DS coverage across the codebase. Find gaps, suggest DS replacements, migrate components, approve exceptions, promote candidates. Sub-commands: fix, approve, promote, manifest, check, history, audit."
|
|
3
|
+
allowed-tools: Read, Glob, Grep, Bash, Edit
|
|
4
|
+
argument-hint: "[fix <ComponentName> | approve <Name> \"<reason>\" | promote <Name> | manifest | check | history | audit]"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# /drift — Design System Drift Analyzer
|
|
8
|
+
|
|
9
|
+
Analyze design system coverage for this codebase. Identify components that drift from
|
|
10
|
+
the DS, suggest replacements, and optionally migrate code. Works with any React
|
|
11
|
+
product team — property management, SaaS, fintech, e-commerce, etc.
|
|
12
|
+
|
|
13
|
+
## Arguments: `$ARGUMENTS`
|
|
14
|
+
|
|
15
|
+
Supported sub-commands:
|
|
16
|
+
- *(no args)* — full coverage report + top gap analysis
|
|
17
|
+
- `fix <ComponentName>` — migrate one custom component to its DS equivalent
|
|
18
|
+
- `approve <ComponentName> "<rationale>"` — approve a gap with documented rationale
|
|
19
|
+
- `promote <ComponentName>` — flag a high-frequency custom component for DS promotion
|
|
20
|
+
- `manifest` — print the DS component registry with story + Figma links
|
|
21
|
+
- `check` — run the headless drift-check script and parse results
|
|
22
|
+
- `history` — show coverage trend over last N scans (from saved reports)
|
|
23
|
+
- `audit` — full audit mode: coverage + token violations + rationale gaps + promotion candidates
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Step 1 — Read the DS registry
|
|
28
|
+
|
|
29
|
+
Read `src/ds-coverage/config.ts` (or `drift.config.ts` at project root) to understand:
|
|
30
|
+
- Every registered DS component, its story path, and Figma link
|
|
31
|
+
- `threshold` — the CI pass/fail threshold
|
|
32
|
+
- `storybookUrl` and `figmaFileKey`
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Step 2 — Dispatch on $ARGUMENTS
|
|
37
|
+
|
|
38
|
+
### No arguments → Full Coverage Report
|
|
39
|
+
|
|
40
|
+
1. Glob `src/**/*.tsx` (excluding `src/stories/`, `src/tokens/`, `node_modules/`, `*.stories.*`, `*.test.*`, `*.spec.*`) to find all screens, views, and feature files.
|
|
41
|
+
2. Grep each file for JSX component usage. Classify every component as:
|
|
42
|
+
- **DS** — name is in `config.components`
|
|
43
|
+
- **Approved gap** — custom, but has an approval entry (check for `// drift-approved:` comment or approval record)
|
|
44
|
+
- **Custom** — name is not in `config.components` and not approved
|
|
45
|
+
3. Compute per-file and overall DS coverage %.
|
|
46
|
+
4. List the top custom components by frequency (the gap map).
|
|
47
|
+
5. For any custom component used ≥ 3 times:
|
|
48
|
+
- Check whether a DS equivalent exists → suggest it
|
|
49
|
+
- If used ≥ 5 times → flag as **promotion candidate**
|
|
50
|
+
6. Print a report in this format:
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
## Drift Report — <date>
|
|
54
|
+
|
|
55
|
+
Overall DS coverage: XX% (threshold: XX%)
|
|
56
|
+
Status: ✅ PASS or 🔴 FAIL
|
|
57
|
+
|
|
58
|
+
### By file
|
|
59
|
+
| File | DS | Custom | Approved | Coverage |
|
|
60
|
+
|------|----|--------|----------|----------|
|
|
61
|
+
| ... | .. | ... | ... | ...% |
|
|
62
|
+
|
|
63
|
+
### Top gaps (custom components not in DS)
|
|
64
|
+
| Component | Uses | Status | Suggested DS replacement |
|
|
65
|
+
|-----------|------|--------|--------------------------|
|
|
66
|
+
| BtnGrp | 6 | ⚠️ Gap | Use `<Tabs>` (segmented variant) |
|
|
67
|
+
| LinkBtn | 5 | 🔁 Promote candidate | Use `<Button variant="ghost">` |
|
|
68
|
+
| AvatarRow | 3 | ✅ Approved — "needed for nav micro-interaction" | — |
|
|
69
|
+
|
|
70
|
+
### Token violations
|
|
71
|
+
| File | Violation | Line |
|
|
72
|
+
|------|-----------|------|
|
|
73
|
+
| ... | Hardcoded `#3b82f6` — use `var(--ds-color-brand-500)` | 42 |
|
|
74
|
+
|
|
75
|
+
### Promotion candidates (used ≥5× with no DS equivalent)
|
|
76
|
+
These components appear frequently enough to justify adding to the DS:
|
|
77
|
+
1. <ComponentName> — used N× across N files
|
|
78
|
+
→ Run `/drift promote <ComponentName>` to create a promotion request
|
|
79
|
+
|
|
80
|
+
### Recommendations
|
|
81
|
+
1. Highest impact: migrate <X> → saves N% coverage across N files
|
|
82
|
+
2. ...
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### `fix <ComponentName>`
|
|
88
|
+
|
|
89
|
+
1. Find all usages of `<ComponentName>` across `src/` (excluding stories and tests).
|
|
90
|
+
2. Read the DS equivalent component file to understand its props API.
|
|
91
|
+
3. For each usage, generate a code diff that replaces the custom component with the DS component, preserving existing behavior.
|
|
92
|
+
4. Show a summary first:
|
|
93
|
+
```
|
|
94
|
+
Found 6 usages of <ComponentName> across 3 files.
|
|
95
|
+
DS equivalent: <DSName> — props mapping:
|
|
96
|
+
old.size="large" → new.size="lg"
|
|
97
|
+
old.color="red" → new.variant="danger"
|
|
98
|
+
|
|
99
|
+
Estimated coverage improvement: +2.3%
|
|
100
|
+
|
|
101
|
+
Apply changes? (yes/no/preview)
|
|
102
|
+
```
|
|
103
|
+
5. After applying, re-run coverage calculation and show before/after.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
### `approve <ComponentName> "<rationale>"`
|
|
108
|
+
|
|
109
|
+
Approve a custom component as an intentional exception to DS coverage rules.
|
|
110
|
+
|
|
111
|
+
Use this when a custom component is genuinely needed and cannot be replaced by a DS component. Rationale must include:
|
|
112
|
+
- Why no DS component covers this need
|
|
113
|
+
- Whether it should be proposed for DS inclusion in the future
|
|
114
|
+
|
|
115
|
+
1. Verify the component is actually used in the codebase.
|
|
116
|
+
2. Check it's not already approved.
|
|
117
|
+
3. Add an approval entry to `drift.config.ts`:
|
|
118
|
+
```ts
|
|
119
|
+
approvedGaps: {
|
|
120
|
+
'<ComponentName>': {
|
|
121
|
+
rationale: '<rationale>',
|
|
122
|
+
approvedBy: '<ask for name>',
|
|
123
|
+
approvedAt: '<today ISO date>',
|
|
124
|
+
promoteToDS: true/false, // ask: "Should this be proposed for DS inclusion?"
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
4. Also add an inline comment convention to the usage site so rationale travels with the code:
|
|
129
|
+
```tsx
|
|
130
|
+
{/* drift:ignore reason="<rationale>" approvedBy="<name>" */}
|
|
131
|
+
<ComponentName ... />
|
|
132
|
+
```
|
|
133
|
+
This is visible in code review without needing to cross-reference config.
|
|
134
|
+
5. Confirm: "Approved. This component will show as ✅ Approved in drift reports and will not count against coverage."
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `promote <ComponentName>`
|
|
139
|
+
|
|
140
|
+
Flag a high-frequency custom component as a DS promotion candidate.
|
|
141
|
+
|
|
142
|
+
1. Read the component file (or search for it if it's a one-off inline component).
|
|
143
|
+
2. Count its usage frequency and list the files it appears in.
|
|
144
|
+
3. Generate a promotion brief:
|
|
145
|
+
```
|
|
146
|
+
## DS Promotion Request: <ComponentName>
|
|
147
|
+
|
|
148
|
+
**Usage:** N× across N files
|
|
149
|
+
**Files:** list up to 5 most common locations
|
|
150
|
+
|
|
151
|
+
**Props API (current):**
|
|
152
|
+
<extracted interface or inferred from usage>
|
|
153
|
+
|
|
154
|
+
**Suggested DS entry:**
|
|
155
|
+
<ComponentName>: {
|
|
156
|
+
storyPath: '<suggested-story-path>',
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
**Design request:** This component appears frequently enough to warrant a
|
|
160
|
+
Figma design + DS review. Recommend filing a design request.
|
|
161
|
+
|
|
162
|
+
Next steps:
|
|
163
|
+
1. Designer creates the spec in Figma
|
|
164
|
+
2. Run /drift-push <ComponentName> to attach implementation notes
|
|
165
|
+
3. After Figma review, run /drift-sync to register it officially
|
|
166
|
+
```
|
|
167
|
+
4. Ask if user wants to open a Jira ticket (if `jiraBaseUrl` is configured).
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
### `manifest`
|
|
172
|
+
|
|
173
|
+
Print a formatted table of all DS components from `config.components`:
|
|
174
|
+
|
|
175
|
+
```
|
|
176
|
+
## DS Component Registry — <date>
|
|
177
|
+
|
|
178
|
+
| Component | Story | Figma | Status |
|
|
179
|
+
|--------------------|-------|-------|--------|
|
|
180
|
+
| Button | ✅ | ✅ | Stable |
|
|
181
|
+
| Tabs | ✅ | — | Needs Figma |
|
|
182
|
+
| ... | ... | ... | ... |
|
|
183
|
+
|
|
184
|
+
Approved gaps (N):
|
|
185
|
+
| Component | Rationale | Approved by |
|
|
186
|
+
|--------------|-----------|-------------|
|
|
187
|
+
| CustomHeader | "..." | Michelle, 2026-01-15 |
|
|
188
|
+
|
|
189
|
+
Missing story paths: X components
|
|
190
|
+
Missing Figma links: X components
|
|
191
|
+
Run /drift-sync to fill gaps automatically.
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
### `check`
|
|
197
|
+
|
|
198
|
+
Run the headless drift check and parse results:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
npm run build && npx vite preview --port 4173 &
|
|
202
|
+
npx wait-on http://localhost:4173 --timeout 30000
|
|
203
|
+
node scripts/drift-check.mjs --url http://localhost:4173 --json > /tmp/drift-report.json
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Then read `/tmp/drift-report.json` and print the full report format including:
|
|
207
|
+
- Coverage % vs threshold
|
|
208
|
+
- Per-route breakdown
|
|
209
|
+
- Token violations (hardcoded colors, spacing)
|
|
210
|
+
- Gap map with promotion candidates
|
|
211
|
+
|
|
212
|
+
---
|
|
213
|
+
|
|
214
|
+
### `history`
|
|
215
|
+
|
|
216
|
+
Read any saved `drift-report-*.json` files or GitHub Actions artifacts from `.github/`.
|
|
217
|
+
Show coverage trend:
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
## Coverage History
|
|
221
|
+
|
|
222
|
+
Date Coverage Delta Status
|
|
223
|
+
2026-03-30 78% +2% 🔴 Below threshold
|
|
224
|
+
2026-03-23 76% +1% 🔴 Below threshold
|
|
225
|
+
2026-03-16 75% — 🔴 Below threshold
|
|
226
|
+
|
|
227
|
+
Trend: ↑ improving (+3% over 3 weeks)
|
|
228
|
+
At current rate, threshold (80%) reached in ~2 weeks.
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
---
|
|
232
|
+
|
|
233
|
+
### `audit`
|
|
234
|
+
|
|
235
|
+
Full audit combining all modes:
|
|
236
|
+
1. Run `check` (headless scan)
|
|
237
|
+
2. Run the static analysis (no-args path)
|
|
238
|
+
3. Cross-reference: flag any component approved as a gap that now has a DS equivalent
|
|
239
|
+
4. List components that have been custom for ≥ 30 days (from git log if available)
|
|
240
|
+
5. Output a single comprehensive report suitable for a DS quarterly review
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## Style rules for output
|
|
245
|
+
|
|
246
|
+
- Lead with the numbers — coverage %, counts, file names
|
|
247
|
+
- Use exact component names from the codebase (case-sensitive)
|
|
248
|
+
- When suggesting a DS replacement, always show a before/after code snippet
|
|
249
|
+
- If coverage is below threshold, surface the 3 highest-impact gaps first with estimated improvement per fix
|
|
250
|
+
- Never suggest creating new components — only DS components from `config.components`
|
|
251
|
+
- For teams unfamiliar with the DS, always explain *why* a replacement is better, not just *what* to use
|
|
252
|
+
- Approved gaps always show ✅ and are excluded from failure calculations
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@catchdrift/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.19",
|
|
4
4
|
"description": "CLI for Drift — install, check, and manage design system coverage for any React app.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"design-system",
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"bin",
|
|
32
32
|
"src",
|
|
33
33
|
"scripts",
|
|
34
|
+
"commands",
|
|
34
35
|
"LICENSE",
|
|
35
36
|
"README.md"
|
|
36
37
|
],
|
package/src/commands/init.mjs
CHANGED
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
import {
|
|
33
33
|
writeDriftConfig,
|
|
34
34
|
writeAIRulesFiles,
|
|
35
|
+
writeClaudeSkills,
|
|
35
36
|
patchAppEntry,
|
|
36
37
|
writeGithubAction,
|
|
37
38
|
} from '../lib/writers.mjs'
|
|
@@ -349,7 +350,8 @@ export async function init(argv) {
|
|
|
349
350
|
storybookUrl: storybookUrl || '',
|
|
350
351
|
figmaFiles: figmaFiles.length ? figmaFiles : undefined,
|
|
351
352
|
})
|
|
352
|
-
|
|
353
|
+
const skillFiles = writeClaudeSkills(cwd)
|
|
354
|
+
spinner.stop(`Written: ${rulesFiles.join(', ')}${skillFiles.length ? ` + ${skillFiles.length} Claude skills` : ''}`)
|
|
353
355
|
|
|
354
356
|
// ── Step 8: Install @catchdrift/overlay ──────────────────────────────────────
|
|
355
357
|
const pkgJson = JSON.parse(readFileSync(join(cwd, 'package.json'), 'utf8'))
|
|
@@ -417,6 +419,7 @@ export async function init(argv) {
|
|
|
417
419
|
${pc.bold('What was created:')}
|
|
418
420
|
drift.config.ts ${pc.dim('DS component registry')}
|
|
419
421
|
${rulesFiles.map(f => f.padEnd(34)).join('\n ')}${pc.dim('AI constraints')}
|
|
422
|
+
${skillFiles.length ? `.claude/commands/ ${pc.dim('Claude Code skills (/drift-sync, /drift-scaffold, etc.)')}` : ''}
|
|
420
423
|
${addCI ? '.github/workflows/drift-check.yml ' + pc.dim('CI drift check on every PR') : ''}
|
|
421
424
|
${patched ? patched.padEnd(34) + pc.dim('DriftOverlay added (dev-only)') : ''}
|
|
422
425
|
|
package/src/lib/writers.mjs
CHANGED
|
@@ -3,11 +3,14 @@
|
|
|
3
3
|
* All writers are idempotent — safe to re-run.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'
|
|
6
|
+
import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync } from 'fs'
|
|
7
7
|
import { resolve, join, dirname, relative, posix } from 'path'
|
|
8
|
+
import { fileURLToPath } from 'url'
|
|
8
9
|
import { buildComponentRegistry } from './storybook.mjs'
|
|
9
10
|
import { findAppEntry } from './detect.mjs'
|
|
10
11
|
|
|
12
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
13
|
+
|
|
11
14
|
// ── drift.config.ts ───────────────────────────────────────────────────────────
|
|
12
15
|
|
|
13
16
|
export function writeDriftConfig(cwd, { storybookUrl, chromaticUrl, figmaFiles, dsPackages, threshold, components }) {
|
|
@@ -327,3 +330,26 @@ jobs:
|
|
|
327
330
|
|
|
328
331
|
writeFileSync(filepath, yaml, 'utf8')
|
|
329
332
|
}
|
|
333
|
+
|
|
334
|
+
// ── Claude Code skill files ───────────────────────────────────────────────────
|
|
335
|
+
|
|
336
|
+
export function writeClaudeSkills(cwd) {
|
|
337
|
+
// Skills ship with the CLI package under commands/
|
|
338
|
+
const skillsSource = resolve(__dirname, '../../commands')
|
|
339
|
+
if (!existsSync(skillsSource)) return []
|
|
340
|
+
|
|
341
|
+
const destDir = join(cwd, '.claude', 'commands')
|
|
342
|
+
mkdirSync(destDir, { recursive: true })
|
|
343
|
+
|
|
344
|
+
const written = []
|
|
345
|
+
for (const file of readdirSync(skillsSource)) {
|
|
346
|
+
if (!file.endsWith('.md')) continue
|
|
347
|
+
const dest = join(destDir, file)
|
|
348
|
+
// Don't overwrite files the user may have customised
|
|
349
|
+
if (!existsSync(dest)) {
|
|
350
|
+
writeFileSync(dest, readFileSync(join(skillsSource, file), 'utf8'), 'utf8')
|
|
351
|
+
written.push(file)
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
return written
|
|
355
|
+
}
|