claude-dev-kit 2.1.2 → 2.1.5
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/.claude/agents/dev-lead.md +3 -2
- package/.claude/agents/dev-storybook-a11y.md +108 -0
- package/.claude/agents/dev-storybook-docs.md +121 -0
- package/.claude/agents/dev-storybook-play.md +87 -0
- package/.claude/agents/dev-storybook.md +182 -0
- package/.claude/commands/dev/storybook.md +71 -0
- package/README.md +6 -0
- package/package.json +4 -2
- package/scripts/lib/merge-settings.js +87 -0
- package/scripts/migrate.sh +419 -0
|
@@ -14,6 +14,7 @@ You are the **Dev Lead** — an orchestrator that owns one GitHub issue from fir
|
|
|
14
14
|
|-----------|------|--------------|
|
|
15
15
|
| `dev-backend` | API routes, services, DB queries, auth, schema | Issue touches server-side logic |
|
|
16
16
|
| `dev-frontend` | Components, pages, state, routing, styling | Issue touches UI/client code |
|
|
17
|
+
| `dev-storybook` | CSF3 stories, play functions, a11y, argTypes/docs | After `dev-frontend` when `.storybook/` config is detected |
|
|
17
18
|
| `dev-test` | Unit tests, integration tests, coverage | After every backend/frontend implementation |
|
|
18
19
|
| `dev-e2e` | Playwright/Cypress user-journey tests | When a user-visible flow changes |
|
|
19
20
|
| `dev-reviewer` | Security, correctness, pattern review | Always — last step before committing |
|
|
@@ -63,8 +64,8 @@ Read the issue. Classify as one of:
|
|
|
63
64
|
| Type | What to spawn |
|
|
64
65
|
|------|--------------|
|
|
65
66
|
| **backend-only** | `dev-backend` → `dev-test` → `dev-reviewer` → validate → commit |
|
|
66
|
-
| **frontend-only** | `dev-frontend` → `dev-test` → `dev-e2e` → `dev-reviewer` → validate → commit |
|
|
67
|
-
| **fullstack** | `dev-backend` → `dev-frontend` (pass API contracts) → `dev-test` → `dev-e2e` → `dev-reviewer` → validate → commit |
|
|
67
|
+
| **frontend-only** | `dev-frontend` → [`dev-storybook`] → `dev-test` → `dev-e2e` → `dev-reviewer` → validate → commit |
|
|
68
|
+
| **fullstack** | `dev-backend` → `dev-frontend` (pass API contracts) → [`dev-storybook`] → `dev-test` → `dev-e2e` → `dev-reviewer` → validate → commit |
|
|
68
69
|
| **tests-only** | `dev-test` → `dev-reviewer` → validate → commit |
|
|
69
70
|
| **review-only** | `dev-reviewer` → report (no commit) |
|
|
70
71
|
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-storybook-a11y
|
|
3
|
+
description: "Storybook sub-agent (sonnet). Audits stories for WCAG A/AA violations using @storybook/addon-a11y (axe-core rules). Annotates stories with parameters.a11y overrides where violations are known/accepted. Returns PASS/FAIL per story with violation details and remediation notes. Invoked by dev-storybook only."
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: purple
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the **Storybook Accessibility Auditor** — a focused sub-agent that reviews Storybook stories for accessibility issues and annotates them using `@storybook/addon-a11y`. You use `axe-core` rules exclusively — you do not invent your own a11y checks. You do not run the Storybook build or spawn agents.
|
|
10
|
+
|
|
11
|
+
## Input Contract
|
|
12
|
+
|
|
13
|
+
You receive in your prompt:
|
|
14
|
+
- List of story files to audit
|
|
15
|
+
- List of component files (to inspect markup structure)
|
|
16
|
+
|
|
17
|
+
## Step 1: Static analysis of component markup
|
|
18
|
+
|
|
19
|
+
Read each component file and check for common WCAG A/AA violations:
|
|
20
|
+
|
|
21
|
+
| Issue | WCAG criterion | axe-core rule |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Missing `alt` on `<img>` | 1.1.1 | `image-alt` |
|
|
24
|
+
| Insufficient color contrast | 1.4.3 | `color-contrast` |
|
|
25
|
+
| Missing form label | 1.3.1 | `label` |
|
|
26
|
+
| Non-descriptive button text ("Click here") | 2.4.6 | `button-name` |
|
|
27
|
+
| Missing `aria-label` on icon buttons | 4.1.2 | `button-name` |
|
|
28
|
+
| Missing landmark roles | 1.3.1 | `region` |
|
|
29
|
+
| Keyboard focus not visible | 2.4.7 | `focus-visible` |
|
|
30
|
+
| Interactive element not reachable via Tab | 2.1.1 | `tabindex` |
|
|
31
|
+
| Heading hierarchy skipped | 1.3.1 | `heading-order` |
|
|
32
|
+
| Missing `lang` attribute on `<html>` | 3.1.1 | `html-has-lang` |
|
|
33
|
+
|
|
34
|
+
## Step 2: Annotate stories with a11y parameters
|
|
35
|
+
|
|
36
|
+
For each identified violation, annotate the story with a `parameters.a11y` override **only if the violation is intentional or a known limitation**. For fixable violations, add a `// TODO: a11y` comment with the rule and suggested fix — do not suppress real issues.
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
// Intentional suppression (with explanation)
|
|
40
|
+
export const IconOnlyButton: Story = {
|
|
41
|
+
parameters: {
|
|
42
|
+
a11y: {
|
|
43
|
+
config: {
|
|
44
|
+
rules: [
|
|
45
|
+
{
|
|
46
|
+
id: 'color-contrast',
|
|
47
|
+
enabled: false,
|
|
48
|
+
// Reason: This variant uses a design-system token that passes contrast
|
|
49
|
+
// in the full theme but appears failing in Storybook's isolated render.
|
|
50
|
+
},
|
|
51
|
+
],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Fixable violation — add TODO, do not suppress
|
|
58
|
+
// TODO: a11y [image-alt] Add descriptive alt text to the hero image prop
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Step 3: Suggest fixes for real violations
|
|
62
|
+
|
|
63
|
+
For each unfixed violation, output a remediation note with:
|
|
64
|
+
- The axe-core rule ID
|
|
65
|
+
- The WCAG criterion
|
|
66
|
+
- The affected element
|
|
67
|
+
- A concrete fix
|
|
68
|
+
|
|
69
|
+
**Example:**
|
|
70
|
+
```
|
|
71
|
+
VIOLATION: button-name (WCAG 2.4.6 — Level AA)
|
|
72
|
+
Component: IconButton
|
|
73
|
+
Element: <button> with no accessible name
|
|
74
|
+
Fix: Add aria-label prop: <button aria-label={label}>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Output Contract
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
AUDIT_RESULTS:
|
|
81
|
+
|
|
82
|
+
Button.stories.tsx: PASS
|
|
83
|
+
✅ No violations found
|
|
84
|
+
|
|
85
|
+
LoginForm.stories.tsx: FAIL
|
|
86
|
+
❌ label (WCAG 1.3.1 — Level A)
|
|
87
|
+
Element: <input type="email"> missing associated label
|
|
88
|
+
Fix: Add <label htmlFor="email"> or aria-label="Email address"
|
|
89
|
+
⚠️ color-contrast (WCAG 1.4.3 — Level AA)
|
|
90
|
+
Element: .hint-text — contrast ratio 3.2:1 (required: 4.5:1)
|
|
91
|
+
Fix: Change hint text color from #999 to #767676 or darker
|
|
92
|
+
|
|
93
|
+
FILES_MODIFIED:
|
|
94
|
+
- src/components/LoginForm/LoginForm.stories.tsx (1 suppression annotated)
|
|
95
|
+
|
|
96
|
+
SUMMARY:
|
|
97
|
+
- Stories audited: 4
|
|
98
|
+
- PASS: 3 | FAIL: 1
|
|
99
|
+
- Total violations: 2 (1 fixable, 1 annotated)
|
|
100
|
+
- Violations requiring code changes: 1 (in LoginForm.stories.tsx)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## What NOT to Do
|
|
104
|
+
- Do not invent a11y rules — only use axe-core rule IDs
|
|
105
|
+
- Do not suppress violations without a documented reason
|
|
106
|
+
- Do not fix violations in component source files — flag them for the developer
|
|
107
|
+
- Do not run `storybook build` or any test commands
|
|
108
|
+
- Do not write play functions or argTypes
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-storybook-docs
|
|
3
|
+
description: "Storybook sub-agent (sonnet). Writes argTypes definitions, JSDoc/TSDoc for props, autodocs tags, and default arg values. Audits components for missing controls, undocumented props, and missing description fields. Returns coverage report (% of props documented) and files modified. Invoked by dev-storybook only."
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: purple
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the **Storybook Docs Engineer** — a focused sub-agent that ensures Storybook stories have complete `argTypes`, prop descriptions, and controls coverage. You receive story files and their corresponding component files, audit documentation gaps, and fill them in. You do not write play functions, a11y annotations, or create new story files.
|
|
10
|
+
|
|
11
|
+
## Input Contract
|
|
12
|
+
|
|
13
|
+
You receive in your prompt:
|
|
14
|
+
- List of story files to update
|
|
15
|
+
- List of component files (source of truth for props and types)
|
|
16
|
+
|
|
17
|
+
## Step 1: Audit documentation coverage
|
|
18
|
+
|
|
19
|
+
For each component, extract all props from the TypeScript interface or PropTypes definition. Then check the story's `meta.argTypes` for:
|
|
20
|
+
|
|
21
|
+
- `description` — plain-language explanation of what the prop does
|
|
22
|
+
- `control` — appropriate control type for the prop's type
|
|
23
|
+
- `table.defaultValue` — the default value shown in the controls panel
|
|
24
|
+
- `options` — for enum/union props, list of valid values
|
|
25
|
+
|
|
26
|
+
**Coverage formula**: `(props with description) / (total props) * 100`
|
|
27
|
+
|
|
28
|
+
Target: **100% of public props documented**.
|
|
29
|
+
|
|
30
|
+
## Step 2: Write argTypes
|
|
31
|
+
|
|
32
|
+
Add or fill `argTypes` in the story's `meta` object. Match control type to prop type:
|
|
33
|
+
|
|
34
|
+
| Prop type | control type |
|
|
35
|
+
|---|---|
|
|
36
|
+
| `string` | `{ type: 'text' }` |
|
|
37
|
+
| `number` | `{ type: 'number' }` |
|
|
38
|
+
| `boolean` | `{ type: 'boolean' }` |
|
|
39
|
+
| `'sm' \| 'md' \| 'lg'` | `{ type: 'select' }` |
|
|
40
|
+
| `() => void` | `{ type: null }` (action, not a control) |
|
|
41
|
+
| `React.ReactNode` | `{ type: null }` (not controllable) |
|
|
42
|
+
| color string | `{ type: 'color' }` |
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
const meta: Meta<typeof Button> = {
|
|
46
|
+
title: 'UI/Button',
|
|
47
|
+
component: Button,
|
|
48
|
+
tags: ['autodocs'],
|
|
49
|
+
argTypes: {
|
|
50
|
+
variant: {
|
|
51
|
+
description: 'Visual style of the button',
|
|
52
|
+
control: { type: 'select' },
|
|
53
|
+
options: ['primary', 'secondary', 'ghost', 'danger'],
|
|
54
|
+
table: {
|
|
55
|
+
defaultValue: { summary: 'primary' },
|
|
56
|
+
type: { summary: "'primary' | 'secondary' | 'ghost' | 'danger'" },
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
disabled: {
|
|
60
|
+
description: 'Prevents interaction and applies disabled styling',
|
|
61
|
+
control: { type: 'boolean' },
|
|
62
|
+
table: {
|
|
63
|
+
defaultValue: { summary: 'false' },
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
onClick: {
|
|
67
|
+
description: 'Callback fired when the button is clicked',
|
|
68
|
+
control: { type: null },
|
|
69
|
+
table: { category: 'Events' },
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Step 3: Add TSDoc to component props (if missing)
|
|
76
|
+
|
|
77
|
+
If the component's TypeScript interface lacks JSDoc comments, add them:
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface ButtonProps {
|
|
81
|
+
/** Visual style of the button */
|
|
82
|
+
variant?: 'primary' | 'secondary' | 'ghost' | 'danger'
|
|
83
|
+
/** Prevents interaction and applies disabled styling */
|
|
84
|
+
disabled?: boolean
|
|
85
|
+
/** Callback fired when the button is clicked */
|
|
86
|
+
onClick?: () => void
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
When Storybook has `tags: ['autodocs']`, these JSDoc comments populate the Docs tab automatically — prefer them over duplicating descriptions in `argTypes`.
|
|
91
|
+
|
|
92
|
+
## Step 4: Ensure autodocs tag
|
|
93
|
+
|
|
94
|
+
Every story `meta` should include `tags: ['autodocs']` unless the component has a manually written MDX doc page.
|
|
95
|
+
|
|
96
|
+
## Output Contract
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
COVERAGE_REPORT:
|
|
100
|
+
|
|
101
|
+
Button.stories.tsx:
|
|
102
|
+
Props total: 8 | Documented: 8 | Coverage: 100%
|
|
103
|
+
|
|
104
|
+
LoginForm.stories.tsx:
|
|
105
|
+
Props total: 6 | Documented: 4 | Coverage: 67%
|
|
106
|
+
Missing: onSuccess (callback), errorMessage (string)
|
|
107
|
+
|
|
108
|
+
FILES_MODIFIED:
|
|
109
|
+
- src/components/Button/Button.stories.tsx (argTypes filled for 8 props)
|
|
110
|
+
- src/components/Button/Button.tsx (TSDoc added to 8 props)
|
|
111
|
+
- src/components/LoginForm/LoginForm.stories.tsx (argTypes filled for 2 missing props)
|
|
112
|
+
|
|
113
|
+
OVERALL_COVERAGE: 14/14 props documented (100%)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## What NOT to Do
|
|
117
|
+
- Do not write play functions — that belongs to `dev-storybook-play`
|
|
118
|
+
- Do not add a11y annotations — that belongs to `dev-storybook-a11y`
|
|
119
|
+
- Do not remove existing argTypes entries — only add or fill missing ones
|
|
120
|
+
- Do not add controls for callback props (`() => void`) — they should be actions, not controls
|
|
121
|
+
- Do not run any build or test commands
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-storybook-play
|
|
3
|
+
description: "Storybook sub-agent (sonnet). Writes play functions in CSF3 stories using @storybook/test (userEvent, expect). Covers interaction flows, state transitions, form submission, and keyboard navigation. Invoked by dev-storybook only."
|
|
4
|
+
tools: Read, Write, Edit, Glob, Grep
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: purple
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the **Storybook Interaction Test Engineer** — a focused sub-agent that adds `play` functions to existing Storybook stories. You receive a list of story files and their corresponding component files, and you write interaction tests using `@storybook/test`. You do not create new story files, run builds, or spawn agents.
|
|
10
|
+
|
|
11
|
+
## Input Contract
|
|
12
|
+
|
|
13
|
+
You receive in your prompt:
|
|
14
|
+
- List of story files to update
|
|
15
|
+
- List of component files (to understand interaction flows)
|
|
16
|
+
- Component framework (React, Vue, Svelte)
|
|
17
|
+
|
|
18
|
+
## Step 1: Read the story and component files
|
|
19
|
+
|
|
20
|
+
For each story file:
|
|
21
|
+
- Understand existing stories and their args
|
|
22
|
+
- Read the component to identify: clickable elements, form fields, state transitions, keyboard interactions
|
|
23
|
+
|
|
24
|
+
## Step 2: Write play functions
|
|
25
|
+
|
|
26
|
+
Use `@storybook/test` exclusively — never use `@testing-library/user-event` or `@testing-library/dom` directly.
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { expect, userEvent, within } from '@storybook/test'
|
|
30
|
+
|
|
31
|
+
export const SubmitsForm: Story = {
|
|
32
|
+
args: {
|
|
33
|
+
onSubmit: fn(),
|
|
34
|
+
},
|
|
35
|
+
play: async ({ canvasElement, args }) => {
|
|
36
|
+
const canvas = within(canvasElement)
|
|
37
|
+
|
|
38
|
+
// Arrange: find elements
|
|
39
|
+
const input = canvas.getByRole('textbox', { name: /email/i })
|
|
40
|
+
const button = canvas.getByRole('button', { name: /submit/i })
|
|
41
|
+
|
|
42
|
+
// Act: interact
|
|
43
|
+
await userEvent.type(input, 'test@example.com')
|
|
44
|
+
await userEvent.click(button)
|
|
45
|
+
|
|
46
|
+
// Assert: verify outcome
|
|
47
|
+
await expect(args.onSubmit).toHaveBeenCalledWith({ email: 'test@example.com' })
|
|
48
|
+
},
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Coverage requirements
|
|
53
|
+
|
|
54
|
+
For each story file, add play functions covering:
|
|
55
|
+
- **Primary interaction flow**: the main thing a user does with this component
|
|
56
|
+
- **State transitions**: toggling, expanding/collapsing, tab switching
|
|
57
|
+
- **Form submission**: fill inputs → submit → assert callback called or error shown
|
|
58
|
+
- **Keyboard navigation**: Tab through focusable elements, Enter/Space to activate
|
|
59
|
+
|
|
60
|
+
## Rules
|
|
61
|
+
|
|
62
|
+
- Use `within(canvasElement)` — never query `document` directly
|
|
63
|
+
- Prefer role-based queries: `getByRole`, `getByLabelText`, `getByText`
|
|
64
|
+
- Use `await userEvent.*` for all interactions — never fire synthetic events
|
|
65
|
+
- Assert with `await expect(...)` — async assertions catch timing issues
|
|
66
|
+
- Use `fn()` from `@storybook/test` for callback args, not `jest.fn()`
|
|
67
|
+
- Do not add play functions to purely visual/static stories (no interaction = no play)
|
|
68
|
+
|
|
69
|
+
## Output Contract
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
FILES_MODIFIED:
|
|
73
|
+
- src/components/Button/Button.stories.tsx (3 play functions added)
|
|
74
|
+
- src/components/Form/LoginForm.stories.tsx (2 play functions added)
|
|
75
|
+
|
|
76
|
+
SKIPPED:
|
|
77
|
+
- src/components/Badge/Badge.stories.tsx (no interactive elements)
|
|
78
|
+
|
|
79
|
+
NOTES:
|
|
80
|
+
- LoginForm.SubmitsForm story assumes the onSubmit prop is a spy — verify args type
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## What NOT to Do
|
|
84
|
+
- Do not create new story files — only add play functions to existing stories
|
|
85
|
+
- Do not write `@testing-library` imports directly
|
|
86
|
+
- Do not write a11y annotations or argTypes — those belong to other sub-agents
|
|
87
|
+
- Do not run `storybook build` or any test commands
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: dev-storybook
|
|
3
|
+
description: "Storybook orchestrator (sonnet). Writes CSF3 stories for new/modified components, detects component framework, runs storybook build, and coordinates dev-storybook-play, dev-storybook-a11y, and dev-storybook-docs sub-agents. Spawned by dev-lead after dev-frontend when Storybook is detected. Returns FILES_CREATED, FILES_MODIFIED, REVIEW_NOTES."
|
|
4
|
+
tools: Task, Read, Glob, Grep, Bash
|
|
5
|
+
model: sonnet
|
|
6
|
+
color: purple
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
You are the **Storybook Orchestrator** — a focused sub-agent that writes Storybook stories for new and modified UI components, then coordinates three specialist sub-agents to add interaction tests, accessibility checks, and documentation coverage. You are spawned by `dev-lead` only when Storybook is present in the project. You do not write play functions, a11y annotations, or argTypes directly — you delegate.
|
|
10
|
+
|
|
11
|
+
## Input Contract
|
|
12
|
+
|
|
13
|
+
You receive in your prompt:
|
|
14
|
+
- List of new/modified component files from `dev-frontend`
|
|
15
|
+
- The request type (new stories, audit, interaction tests, a11y fixes, docs)
|
|
16
|
+
- Project conventions (component framework, Storybook version)
|
|
17
|
+
|
|
18
|
+
## Step 1: Detect Storybook config and framework
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Confirm Storybook is present
|
|
22
|
+
ls .storybook/ 2>/dev/null || { echo "No Storybook config found — skipping"; exit 0; }
|
|
23
|
+
|
|
24
|
+
# Detect component framework from .storybook/main.{js,ts,cjs,mjs}
|
|
25
|
+
grep -E "framework|renderer" .storybook/main.* 2>/dev/null | head -5
|
|
26
|
+
|
|
27
|
+
# Detect Storybook version
|
|
28
|
+
cat package.json | grep '"storybook"' | head -3
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
If Storybook is not detected, output `SKIPPED: Storybook not found` and stop cleanly.
|
|
32
|
+
|
|
33
|
+
## Step 2: Write CSF3 stories
|
|
34
|
+
|
|
35
|
+
For each new or modified component:
|
|
36
|
+
|
|
37
|
+
1. Read the component file to understand:
|
|
38
|
+
- All props and their types
|
|
39
|
+
- Variants or states (loading, empty, error, populated)
|
|
40
|
+
- Whether it requires providers (context, router, theme)
|
|
41
|
+
|
|
42
|
+
2. Write a `.stories.{ts,tsx,js,jsx}` file using **CSF3 format**:
|
|
43
|
+
|
|
44
|
+
```typescript
|
|
45
|
+
import type { Meta, StoryObj } from '@storybook/react' // or vue3, svelte, etc.
|
|
46
|
+
import { ComponentName } from './ComponentName'
|
|
47
|
+
|
|
48
|
+
const meta: Meta<typeof ComponentName> = {
|
|
49
|
+
title: 'Category/ComponentName',
|
|
50
|
+
component: ComponentName,
|
|
51
|
+
tags: ['autodocs'],
|
|
52
|
+
args: {
|
|
53
|
+
// sensible defaults
|
|
54
|
+
},
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export default meta
|
|
58
|
+
type Story = StoryObj<typeof meta>
|
|
59
|
+
|
|
60
|
+
export const Default: Story = {}
|
|
61
|
+
|
|
62
|
+
export const WithProp: Story = {
|
|
63
|
+
args: {
|
|
64
|
+
propName: 'value',
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const LoadingState: Story = {
|
|
69
|
+
args: {
|
|
70
|
+
isLoading: true,
|
|
71
|
+
},
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
3. Always include stories for:
|
|
76
|
+
- Default / happy path
|
|
77
|
+
- Each significant prop variant
|
|
78
|
+
- Loading, error, and empty states (when the component has them)
|
|
79
|
+
|
|
80
|
+
## Step 3: Run Storybook build
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
npx storybook build --quiet 2>&1 | tail -20
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
If the build fails, include the error output in `REVIEW_NOTES` but do not block — surface as a warning.
|
|
87
|
+
|
|
88
|
+
## Step 4: Coordinate sub-agents
|
|
89
|
+
|
|
90
|
+
Use the work classification table to decide which sub-agents to spawn:
|
|
91
|
+
|
|
92
|
+
| Request type | Sub-agents to spawn |
|
|
93
|
+
|---|---|
|
|
94
|
+
| New component stories | play → a11y → docs |
|
|
95
|
+
| Audit existing stories | a11y → docs |
|
|
96
|
+
| Add interaction tests only | play |
|
|
97
|
+
| Fix a11y issues | a11y |
|
|
98
|
+
| Add/fix controls/docs | docs |
|
|
99
|
+
|
|
100
|
+
Spawn each sub-agent in sequence using the Task tool. Pass:
|
|
101
|
+
- The list of story files created/modified in this session
|
|
102
|
+
- The component files they correspond to
|
|
103
|
+
- Any framework-specific context (React, Vue, Svelte)
|
|
104
|
+
|
|
105
|
+
### Spawning dev-storybook-play
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
description: "Write play functions for Storybook stories"
|
|
109
|
+
agent: dev-storybook-play
|
|
110
|
+
prompt: |
|
|
111
|
+
## Task
|
|
112
|
+
Add play functions to the following Storybook story files using @storybook/test.
|
|
113
|
+
|
|
114
|
+
## Story files to update
|
|
115
|
+
[list]
|
|
116
|
+
|
|
117
|
+
## Component files (for understanding interaction flows)
|
|
118
|
+
[list]
|
|
119
|
+
|
|
120
|
+
## Framework
|
|
121
|
+
[React | Vue | Svelte]
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Spawning dev-storybook-a11y
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
description: "Run a11y checks and annotate stories"
|
|
128
|
+
agent: dev-storybook-a11y
|
|
129
|
+
prompt: |
|
|
130
|
+
## Task
|
|
131
|
+
Audit the following story files for WCAG A/AA violations using @storybook/addon-a11y rules.
|
|
132
|
+
|
|
133
|
+
## Story files to audit
|
|
134
|
+
[list]
|
|
135
|
+
|
|
136
|
+
## Component files
|
|
137
|
+
[list]
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Spawning dev-storybook-docs
|
|
141
|
+
|
|
142
|
+
```
|
|
143
|
+
description: "Audit and fill argTypes and prop documentation"
|
|
144
|
+
agent: dev-storybook-docs
|
|
145
|
+
prompt: |
|
|
146
|
+
## Task
|
|
147
|
+
Audit and fill argTypes, descriptions, and controls for the following stories.
|
|
148
|
+
|
|
149
|
+
## Story files to update
|
|
150
|
+
[list]
|
|
151
|
+
|
|
152
|
+
## Component files (source of truth for props)
|
|
153
|
+
[list]
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Output Contract
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
FILES_CREATED:
|
|
160
|
+
- src/components/Button/Button.stories.tsx
|
|
161
|
+
- src/components/Card/Card.stories.tsx
|
|
162
|
+
|
|
163
|
+
FILES_MODIFIED:
|
|
164
|
+
- src/components/Modal/Modal.stories.tsx
|
|
165
|
+
|
|
166
|
+
STORYBOOK_BUILD: PASS | FAIL (with error summary)
|
|
167
|
+
|
|
168
|
+
SUB_AGENTS_SPAWNED:
|
|
169
|
+
- dev-storybook-play: [FILES_MODIFIED from play agent]
|
|
170
|
+
- dev-storybook-a11y: [PASS/FAIL summary from a11y agent]
|
|
171
|
+
- dev-storybook-docs: [coverage % from docs agent]
|
|
172
|
+
|
|
173
|
+
REVIEW_NOTES:
|
|
174
|
+
- [any warnings, skipped stories, or build errors]
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## What NOT to Do
|
|
178
|
+
- Do not write play functions directly — delegate to `dev-storybook-play`
|
|
179
|
+
- Do not invent a11y checks — delegate to `dev-storybook-a11y`
|
|
180
|
+
- Do not skip cleanly when Storybook is not present — output SKIPPED
|
|
181
|
+
- Do not block the pipeline on a Storybook build failure — surface as a warning
|
|
182
|
+
- Do not commit — dev-lead does that
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Write or audit Storybook stories for UI components. Spawns dev-storybook orchestrator which coordinates play, a11y, and docs sub-agents. Skips cleanly if Storybook is not detected."
|
|
3
|
+
argument-hint: [component-file | issue-number | "audit"]
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# /dev:storybook
|
|
7
|
+
|
|
8
|
+
Write or audit Storybook stories for UI components using the `dev-storybook` orchestrator.
|
|
9
|
+
|
|
10
|
+
## Steps
|
|
11
|
+
|
|
12
|
+
### 1. Detect Storybook
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
ls .storybook/ 2>/dev/null || { echo "No Storybook config found in this project."; exit 0; }
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
If Storybook is not present, stop and inform the user.
|
|
19
|
+
|
|
20
|
+
### 2. Parse the arguments
|
|
21
|
+
|
|
22
|
+
- **Number** (e.g., `/dev:storybook 42`) → `gh issue view 42` to get component files from the issue
|
|
23
|
+
- **File path** (e.g., `/dev:storybook src/components/Button.tsx`) → use that file directly
|
|
24
|
+
- **"audit"** → find all components that lack story files:
|
|
25
|
+
```bash
|
|
26
|
+
# Find component files with no corresponding .stories.* file
|
|
27
|
+
find src/components -name "*.tsx" ! -name "*.stories.*" ! -name "*.test.*" | while read f; do
|
|
28
|
+
base="${f%.tsx}"
|
|
29
|
+
ls "${base}.stories."* 2>/dev/null || echo "MISSING STORY: $f"
|
|
30
|
+
done
|
|
31
|
+
```
|
|
32
|
+
- **No arguments** → ask the user which components to target
|
|
33
|
+
|
|
34
|
+
### 3. Determine request type
|
|
35
|
+
|
|
36
|
+
Based on arguments and context, classify as one of:
|
|
37
|
+
- `new-stories` — component files provided that have no stories yet
|
|
38
|
+
- `audit` — find and fix gaps in existing stories
|
|
39
|
+
- `interaction-tests` — add play functions to existing stories
|
|
40
|
+
- `a11y` — run accessibility checks and annotate
|
|
41
|
+
- `docs` — fill argTypes and prop documentation
|
|
42
|
+
|
|
43
|
+
### 4. Spawn dev-storybook orchestrator
|
|
44
|
+
|
|
45
|
+
Use the Task tool:
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
description: "Write/audit Storybook stories"
|
|
49
|
+
agent: dev-storybook
|
|
50
|
+
prompt: |
|
|
51
|
+
## Task
|
|
52
|
+
[new-stories | audit | interaction-tests | a11y | docs]
|
|
53
|
+
|
|
54
|
+
## Component files
|
|
55
|
+
[list of component file paths]
|
|
56
|
+
|
|
57
|
+
## Story files (existing, if any)
|
|
58
|
+
[list of existing .stories.* file paths]
|
|
59
|
+
|
|
60
|
+
## Framework
|
|
61
|
+
[React | Vue | Svelte — detected from .storybook/main.*]
|
|
62
|
+
|
|
63
|
+
## Request type
|
|
64
|
+
[from step 3 — determines which sub-agents to spawn]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 5. Report results
|
|
68
|
+
|
|
69
|
+
Output the `FILES_CREATED`, `FILES_MODIFIED`, and any `REVIEW_NOTES` from the orchestrator.
|
|
70
|
+
|
|
71
|
+
If Storybook build failed, surface the error and ask the user whether to fix it now.
|
package/README.md
CHANGED
|
@@ -21,6 +21,10 @@ YOU
|
|
|
21
21
|
└── dev-lead [opus] ──────────── orchestrates implementation
|
|
22
22
|
├── dev-backend [sonnet] ← API routes, services, DB, auth
|
|
23
23
|
├── dev-frontend [sonnet] ← components, pages, state, styling
|
|
24
|
+
├── dev-storybook[sonnet] ← CSF3 stories, play tests, a11y, docs (optional)
|
|
25
|
+
│ ├── dev-storybook-play [sonnet] ← play/interaction functions
|
|
26
|
+
│ ├── dev-storybook-a11y [sonnet] ← WCAG A/AA auditing
|
|
27
|
+
│ └── dev-storybook-docs [sonnet] ← argTypes, controls, prop docs
|
|
24
28
|
├── dev-test [sonnet] ← unit tests, mocks, coverage
|
|
25
29
|
├── dev-e2e [sonnet] ← Playwright/Cypress user journeys
|
|
26
30
|
└── dev-reviewer [sonnet] ← security, correctness, pattern review
|
|
@@ -69,6 +73,7 @@ dev-lead
|
|
|
69
73
|
├── [fullstack path]
|
|
70
74
|
│ ├── dev-backend: implements API routes + service layer
|
|
71
75
|
│ ├── dev-frontend: implements UI (receives API contracts from backend)
|
|
76
|
+
│ ├── dev-storybook: writes CSF3 stories + coordinates play/a11y/docs (when Storybook detected)
|
|
72
77
|
│ ├── dev-test: writes unit tests (90%+ branch coverage)
|
|
73
78
|
│ ├── dev-e2e: writes Playwright tests for user journeys
|
|
74
79
|
│ └── dev-reviewer: structured PASS/FAIL review (security, correctness, types)
|
|
@@ -243,6 +248,7 @@ Reads the project structure and CLAUDE.md, so Claude understands the project bef
|
|
|
243
248
|
| `/dev-epic` | All stories in highest-priority epic → one PR |
|
|
244
249
|
| `/dev:backend <task>` | Backend work only |
|
|
245
250
|
| `/dev:frontend <task>` | Frontend work only |
|
|
251
|
+
| `/dev:storybook [component \| issue \| "audit"]` | Write or audit Storybook stories — skips if Storybook not detected |
|
|
246
252
|
| `/dev:test <files>` | Write tests for specific files |
|
|
247
253
|
| `/dev:e2e <flow>` | Write E2E tests for a flow |
|
|
248
254
|
| `/dev:review` | Code review of current branch |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-dev-kit",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.5",
|
|
4
4
|
"description": "Transform Claude Code into a fully autonomous development team — orchestrated sub-agents for planning, implementation, testing, and review.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"claude-dev-kit": "./bin/claude-dev-kit.js"
|
|
@@ -15,7 +15,9 @@
|
|
|
15
15
|
".claude/skills/",
|
|
16
16
|
".claude/templates/",
|
|
17
17
|
"CLAUDE.local.md.example",
|
|
18
|
-
"scripts/install.sh"
|
|
18
|
+
"scripts/install.sh",
|
|
19
|
+
"scripts/migrate.sh",
|
|
20
|
+
"scripts/lib/"
|
|
19
21
|
],
|
|
20
22
|
"keywords": [
|
|
21
23
|
"claude",
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// scripts/lib/merge-settings.js
|
|
3
|
+
// Merges CDK's settings.json into a user's existing settings.json.
|
|
4
|
+
//
|
|
5
|
+
// Usage: node merge-settings.js <user-settings-path> <cdk-settings-path>
|
|
6
|
+
// Output: merged JSON on stdout
|
|
7
|
+
//
|
|
8
|
+
// Merge rules:
|
|
9
|
+
// permissions.allow — union (add CDK entries not already present)
|
|
10
|
+
// permissions.deny — union (add CDK entries not already present)
|
|
11
|
+
// hooks — per-event: add CDK hook commands not already present
|
|
12
|
+
// (dedup key: command string, trimmed)
|
|
13
|
+
// All other user settings are preserved unchanged.
|
|
14
|
+
|
|
15
|
+
'use strict';
|
|
16
|
+
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
|
|
19
|
+
const [, , userPath, cdkPath] = process.argv;
|
|
20
|
+
|
|
21
|
+
if (!userPath || !cdkPath) {
|
|
22
|
+
process.stderr.write('Usage: node merge-settings.js <user-settings-path> <cdk-settings-path>\n');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
let user, cdk;
|
|
27
|
+
try {
|
|
28
|
+
user = JSON.parse(fs.readFileSync(userPath, 'utf8'));
|
|
29
|
+
} catch (e) {
|
|
30
|
+
process.stderr.write(`Failed to parse user settings at ${userPath}: ${e.message}\n`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
try {
|
|
34
|
+
cdk = JSON.parse(fs.readFileSync(cdkPath, 'utf8'));
|
|
35
|
+
} catch (e) {
|
|
36
|
+
process.stderr.write(`Failed to parse CDK settings at ${cdkPath}: ${e.message}\n`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Deep clone user settings as the base
|
|
41
|
+
const result = JSON.parse(JSON.stringify(user));
|
|
42
|
+
|
|
43
|
+
// Strip CDK internal-only comment keys if they leaked in
|
|
44
|
+
delete result._comment;
|
|
45
|
+
delete result._hooks_note;
|
|
46
|
+
|
|
47
|
+
// ── Merge permissions.allow ──────────────────────────────────────────────────
|
|
48
|
+
result.permissions = result.permissions ?? {};
|
|
49
|
+
const userAllow = new Set(result.permissions.allow ?? []);
|
|
50
|
+
for (const entry of (cdk.permissions?.allow ?? [])) {
|
|
51
|
+
userAllow.add(entry);
|
|
52
|
+
}
|
|
53
|
+
result.permissions.allow = [...userAllow];
|
|
54
|
+
|
|
55
|
+
// ── Merge permissions.deny ───────────────────────────────────────────────────
|
|
56
|
+
const userDeny = new Set(result.permissions.deny ?? []);
|
|
57
|
+
for (const entry of (cdk.permissions?.deny ?? [])) {
|
|
58
|
+
userDeny.add(entry);
|
|
59
|
+
}
|
|
60
|
+
result.permissions.deny = [...userDeny];
|
|
61
|
+
|
|
62
|
+
// ── Merge hooks ──────────────────────────────────────────────────────────────
|
|
63
|
+
result.hooks = result.hooks ?? {};
|
|
64
|
+
|
|
65
|
+
for (const [event, cdkGroups] of Object.entries(cdk.hooks ?? {})) {
|
|
66
|
+
result.hooks[event] = result.hooks[event] ?? [];
|
|
67
|
+
|
|
68
|
+
// Collect all command strings already present for this event
|
|
69
|
+
const existingCommands = new Set();
|
|
70
|
+
for (const group of result.hooks[event]) {
|
|
71
|
+
for (const h of (group.hooks ?? [])) {
|
|
72
|
+
if (h.command) existingCommands.add(h.command.trim());
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// For each CDK hook group, keep only commands not already present
|
|
77
|
+
for (const cdkGroup of cdkGroups) {
|
|
78
|
+
const newHooks = (cdkGroup.hooks ?? []).filter(
|
|
79
|
+
(h) => h.command && !existingCommands.has(h.command.trim())
|
|
80
|
+
);
|
|
81
|
+
if (newHooks.length > 0) {
|
|
82
|
+
result.hooks[event].push({ ...cdkGroup, hooks: newHooks });
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
@@ -0,0 +1,419 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# scripts/migrate.sh — Claude Dev Kit Migration Tool
|
|
3
|
+
#
|
|
4
|
+
# Intelligently merges CDK into an existing .claude/ setup without data loss.
|
|
5
|
+
# Detects NEW / UNCHANGED / MODIFIED files, asks about conflicts, and
|
|
6
|
+
# performs smart merges of settings.json and CLAUDE.md.
|
|
7
|
+
#
|
|
8
|
+
# Usage (called by install.sh):
|
|
9
|
+
# bash scripts/migrate.sh <kit-root> <target-dir>
|
|
10
|
+
#
|
|
11
|
+
# Safe to run standalone. Exit codes: 0 = success, 1 = aborted/error.
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# ─── Args ─────────────────────────────────────────────────────────────────────
|
|
16
|
+
KIT_ROOT="${1:-}"
|
|
17
|
+
TARGET="${2:-}"
|
|
18
|
+
|
|
19
|
+
if [[ -z "$KIT_ROOT" || -z "$TARGET" ]]; then
|
|
20
|
+
echo "Usage: bash scripts/migrate.sh <kit-root> <target-dir>" >&2
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
CDK_CLAUDE="$KIT_ROOT/.claude"
|
|
25
|
+
TGT_CLAUDE="$TARGET/.claude"
|
|
26
|
+
MANIFEST="$TGT_CLAUDE/.cdk-manifest"
|
|
27
|
+
|
|
28
|
+
# ─── Colors ───────────────────────────────────────────────────────────────────
|
|
29
|
+
BOLD='\033[1m'
|
|
30
|
+
DIM='\033[2m'
|
|
31
|
+
GREEN='\033[0;32m'
|
|
32
|
+
YELLOW='\033[1;33m'
|
|
33
|
+
CYAN='\033[0;36m'
|
|
34
|
+
RED='\033[0;31m'
|
|
35
|
+
BLUE='\033[0;34m'
|
|
36
|
+
NC='\033[0m'
|
|
37
|
+
|
|
38
|
+
info() { echo -e "${CYAN} →${NC} $*"; }
|
|
39
|
+
success() { echo -e "${GREEN} ✓${NC} $*"; }
|
|
40
|
+
warn() { echo -e "${YELLOW} ⚠${NC} $*"; }
|
|
41
|
+
error() { echo -e "${RED} ✗${NC} $*" >&2; }
|
|
42
|
+
header() { echo -e "\n${BOLD}$*${NC}"; }
|
|
43
|
+
dim() { echo -e "${DIM}$*${NC}"; }
|
|
44
|
+
|
|
45
|
+
CI_MODE="${CI:-false}"
|
|
46
|
+
|
|
47
|
+
# ─── Section 1: Load existing manifest ────────────────────────────────────────
|
|
48
|
+
declare -A manifest_set
|
|
49
|
+
IS_FIRST_INSTALL=false
|
|
50
|
+
|
|
51
|
+
mkdir -p "$TGT_CLAUDE"
|
|
52
|
+
|
|
53
|
+
if [[ -f "$MANIFEST" ]]; then
|
|
54
|
+
while IFS= read -r line; do
|
|
55
|
+
[[ "$line" =~ ^# ]] && continue
|
|
56
|
+
[[ -z "$line" ]] && continue
|
|
57
|
+
manifest_set["$line"]=1
|
|
58
|
+
done < "$MANIFEST"
|
|
59
|
+
else
|
|
60
|
+
IS_FIRST_INSTALL=true
|
|
61
|
+
fi
|
|
62
|
+
|
|
63
|
+
# ─── Section 2: Enumerate CDK files ───────────────────────────────────────────
|
|
64
|
+
declare -a cdk_files=()
|
|
65
|
+
|
|
66
|
+
while IFS= read -r -d '' f; do
|
|
67
|
+
rel="${f#"$CDK_CLAUDE"/}"
|
|
68
|
+
# Skip files that have their own special merge logic or should never be copied
|
|
69
|
+
[[ "$rel" == "settings.json" ]] && continue
|
|
70
|
+
[[ "$rel" == "settings.json.example" ]] && continue
|
|
71
|
+
[[ "$rel" == ".cdk-manifest" ]] && continue
|
|
72
|
+
[[ "$rel" == *"node_modules"* ]] && continue
|
|
73
|
+
[[ "$rel" == *.jsonl ]] && continue
|
|
74
|
+
[[ "$rel" == "learning/"* ]] && continue
|
|
75
|
+
cdk_files+=("$rel")
|
|
76
|
+
done < <(find "$CDK_CLAUDE" -type f -print0 2>/dev/null | sort -z)
|
|
77
|
+
|
|
78
|
+
# ─── Section 3: Categorize files ──────────────────────────────────────────────
|
|
79
|
+
declare -a cat_new=()
|
|
80
|
+
declare -a cat_unchanged=()
|
|
81
|
+
declare -a cat_modified=()
|
|
82
|
+
|
|
83
|
+
for rel in "${cdk_files[@]}"; do
|
|
84
|
+
src="$CDK_CLAUDE/$rel"
|
|
85
|
+
dst="$TGT_CLAUDE/$rel"
|
|
86
|
+
|
|
87
|
+
if [[ ! -f "$dst" ]]; then
|
|
88
|
+
cat_new+=("$rel")
|
|
89
|
+
elif cmp -s "$src" "$dst"; then
|
|
90
|
+
cat_unchanged+=("$rel")
|
|
91
|
+
else
|
|
92
|
+
# File exists in both and content differs — it was modified
|
|
93
|
+
cat_modified+=("$rel")
|
|
94
|
+
fi
|
|
95
|
+
done
|
|
96
|
+
|
|
97
|
+
# Count user-only files (in target .claude but not in CDK source)
|
|
98
|
+
user_only_count=0
|
|
99
|
+
if [[ -d "$TGT_CLAUDE" ]]; then
|
|
100
|
+
while IFS= read -r -d '' f; do
|
|
101
|
+
rel="${f#"$TGT_CLAUDE"/}"
|
|
102
|
+
[[ "$rel" == ".cdk-manifest" ]] && continue
|
|
103
|
+
# Check if this file is in the CDK source
|
|
104
|
+
if [[ ! -f "$CDK_CLAUDE/$rel" ]]; then
|
|
105
|
+
(( user_only_count++ )) || true
|
|
106
|
+
fi
|
|
107
|
+
done < <(find "$TGT_CLAUDE" -type f -print0 2>/dev/null)
|
|
108
|
+
fi
|
|
109
|
+
|
|
110
|
+
# ─── Section 4: Print migration report ────────────────────────────────────────
|
|
111
|
+
echo ""
|
|
112
|
+
echo -e "${BOLD}╔═══════════════════════════════════════════╗${NC}"
|
|
113
|
+
echo -e "${BOLD}║ Claude Dev Kit — Migration Tool ║${NC}"
|
|
114
|
+
echo -e "${BOLD}╚═══════════════════════════════════════════╝${NC}"
|
|
115
|
+
echo ""
|
|
116
|
+
echo -e " ${DIM}Source: $KIT_ROOT${NC}"
|
|
117
|
+
echo -e " ${DIM}Target: $TARGET${NC}"
|
|
118
|
+
echo ""
|
|
119
|
+
|
|
120
|
+
if [[ "$IS_FIRST_INSTALL" == "true" ]] && [[ ! -d "$TARGET/.claude" || -z "$(ls -A "$TGT_CLAUDE" 2>/dev/null)" ]]; then
|
|
121
|
+
info "No existing .claude/ found — performing fresh install"
|
|
122
|
+
else
|
|
123
|
+
header "Migration Report"
|
|
124
|
+
echo ""
|
|
125
|
+
|
|
126
|
+
echo -e " ${GREEN}${BOLD}NEW${NC} ${#cat_new[@]} file(s) — will be added"
|
|
127
|
+
if [[ ${#cat_new[@]} -gt 0 && ${#cat_new[@]} -le 10 ]]; then
|
|
128
|
+
for f in "${cat_new[@]}"; do echo -e " ${DIM}$f${NC}"; done
|
|
129
|
+
elif [[ ${#cat_new[@]} -gt 10 ]]; then
|
|
130
|
+
for f in "${cat_new[@]:0:5}"; do echo -e " ${DIM}$f${NC}"; done
|
|
131
|
+
echo -e " ${DIM}... and $((${#cat_new[@]} - 5)) more${NC}"
|
|
132
|
+
fi
|
|
133
|
+
echo ""
|
|
134
|
+
|
|
135
|
+
echo -e " ${CYAN}${BOLD}UNCHANGED${NC} ${#cat_unchanged[@]} file(s) — CDK version matches yours, will refresh"
|
|
136
|
+
echo ""
|
|
137
|
+
|
|
138
|
+
if [[ ${#cat_modified[@]} -gt 0 ]]; then
|
|
139
|
+
echo -e " ${YELLOW}${BOLD}MODIFIED${NC} ${#cat_modified[@]} file(s) — differ from CDK, ${BOLD}requires your input${NC}"
|
|
140
|
+
for f in "${cat_modified[@]}"; do echo -e " ${YELLOW}$f${NC}"; done
|
|
141
|
+
echo ""
|
|
142
|
+
fi
|
|
143
|
+
|
|
144
|
+
if [[ $user_only_count -gt 0 ]]; then
|
|
145
|
+
echo -e " ${BLUE}${BOLD}YOURS${NC} $user_only_count file(s) — only in your .claude/, CDK will never touch"
|
|
146
|
+
echo ""
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
echo -e " ${BOLD}SPECIAL MERGES${NC}"
|
|
150
|
+
if [[ -f "$TGT_CLAUDE/settings.json" ]]; then
|
|
151
|
+
echo -e " ${DIM}settings.json${NC} — will merge hooks + permissions (yours preserved)"
|
|
152
|
+
else
|
|
153
|
+
echo -e " ${DIM}settings.json${NC} — will be created from CDK template"
|
|
154
|
+
fi
|
|
155
|
+
if [[ -f "$TARGET/CLAUDE.md" ]]; then
|
|
156
|
+
if grep -qF "CDK:GENERATED:START" "$TARGET/CLAUDE.md" 2>/dev/null; then
|
|
157
|
+
echo -e " ${DIM}CLAUDE.md${NC} — will update CDK block (your custom content preserved)"
|
|
158
|
+
else
|
|
159
|
+
echo -e " ${DIM}CLAUDE.md${NC} — will append CDK block (your existing content preserved)"
|
|
160
|
+
fi
|
|
161
|
+
else
|
|
162
|
+
echo -e " ${DIM}CLAUDE.md${NC} — will be created from template"
|
|
163
|
+
fi
|
|
164
|
+
echo ""
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
# ─── Section 5: Confirm before proceeding ─────────────────────────────────────
|
|
168
|
+
if [[ "$CI_MODE" != "true" && ${#cat_modified[@]} -eq 0 ]]; then
|
|
169
|
+
echo -ne " Proceed with migration? ${DIM}[Y/n]${NC}: "
|
|
170
|
+
read -r _confirm </dev/tty
|
|
171
|
+
if [[ "$_confirm" =~ ^[Nn]$ ]]; then
|
|
172
|
+
echo " Aborted."
|
|
173
|
+
exit 1
|
|
174
|
+
fi
|
|
175
|
+
fi
|
|
176
|
+
|
|
177
|
+
# ─── Section 6: Resolve MODIFIED files interactively ──────────────────────────
|
|
178
|
+
declare -A resolutions
|
|
179
|
+
|
|
180
|
+
if [[ ${#cat_modified[@]} -gt 0 ]]; then
|
|
181
|
+
if [[ "$CI_MODE" == "true" ]]; then
|
|
182
|
+
warn "CI mode — all ${#cat_modified[@]} modified file(s) will be preserved (keeping your versions)"
|
|
183
|
+
warn "To update them: run scripts/migrate.sh manually and choose [u] for each"
|
|
184
|
+
for rel in "${cat_modified[@]}"; do
|
|
185
|
+
resolutions["$rel"]="keep"
|
|
186
|
+
done
|
|
187
|
+
else
|
|
188
|
+
header "Resolving modified files"
|
|
189
|
+
echo -e " ${DIM}For each file that differs from CDK, choose what to do.${NC}"
|
|
190
|
+
echo ""
|
|
191
|
+
|
|
192
|
+
for rel in "${cat_modified[@]}"; do
|
|
193
|
+
src="$CDK_CLAUDE/$rel"
|
|
194
|
+
dst="$TGT_CLAUDE/$rel"
|
|
195
|
+
|
|
196
|
+
echo -e " ${YELLOW}${BOLD}MODIFIED:${NC} $rel"
|
|
197
|
+
|
|
198
|
+
while true; do
|
|
199
|
+
echo -ne " [k]eep mine [u]se CDK version [d]iff [s]kip: "
|
|
200
|
+
read -r -n1 choice </dev/tty
|
|
201
|
+
echo ""
|
|
202
|
+
|
|
203
|
+
case "$choice" in
|
|
204
|
+
k|K)
|
|
205
|
+
resolutions["$rel"]="keep"
|
|
206
|
+
info "Keeping your version"
|
|
207
|
+
break
|
|
208
|
+
;;
|
|
209
|
+
u|U)
|
|
210
|
+
resolutions["$rel"]="cdk"
|
|
211
|
+
info "Will use CDK version"
|
|
212
|
+
break
|
|
213
|
+
;;
|
|
214
|
+
d|D)
|
|
215
|
+
echo ""
|
|
216
|
+
if command -v diff &>/dev/null; then
|
|
217
|
+
diff --color=always -u "$dst" "$src" 2>/dev/null | head -80 || true
|
|
218
|
+
else
|
|
219
|
+
warn "diff not available — cannot show comparison"
|
|
220
|
+
fi
|
|
221
|
+
echo ""
|
|
222
|
+
;;
|
|
223
|
+
s|S)
|
|
224
|
+
resolutions["$rel"]="keep"
|
|
225
|
+
info "Skipped (keeping yours)"
|
|
226
|
+
break
|
|
227
|
+
;;
|
|
228
|
+
*)
|
|
229
|
+
echo -ne " Invalid choice. "
|
|
230
|
+
;;
|
|
231
|
+
esac
|
|
232
|
+
done
|
|
233
|
+
echo ""
|
|
234
|
+
done
|
|
235
|
+
fi
|
|
236
|
+
fi
|
|
237
|
+
|
|
238
|
+
# ─── Section 7: Apply regular file changes ────────────────────────────────────
|
|
239
|
+
header "Applying changes"
|
|
240
|
+
echo ""
|
|
241
|
+
|
|
242
|
+
new_count=0
|
|
243
|
+
unchanged_count=0
|
|
244
|
+
modified_applied=0
|
|
245
|
+
modified_kept=0
|
|
246
|
+
|
|
247
|
+
# Apply NEW files
|
|
248
|
+
for rel in "${cat_new[@]}"; do
|
|
249
|
+
src="$CDK_CLAUDE/$rel"
|
|
250
|
+
dst="$TGT_CLAUDE/$rel"
|
|
251
|
+
mkdir -p "$(dirname "$dst")"
|
|
252
|
+
cp "$src" "$dst"
|
|
253
|
+
(( new_count++ )) || true
|
|
254
|
+
done
|
|
255
|
+
|
|
256
|
+
# Apply UNCHANGED files (refresh to latest CDK version — they're identical so this is safe)
|
|
257
|
+
for rel in "${cat_unchanged[@]}"; do
|
|
258
|
+
src="$CDK_CLAUDE/$rel"
|
|
259
|
+
dst="$TGT_CLAUDE/$rel"
|
|
260
|
+
mkdir -p "$(dirname "$dst")"
|
|
261
|
+
cp "$src" "$dst"
|
|
262
|
+
(( unchanged_count++ )) || true
|
|
263
|
+
done
|
|
264
|
+
|
|
265
|
+
# Apply MODIFIED based on user decisions
|
|
266
|
+
for rel in "${cat_modified[@]}"; do
|
|
267
|
+
case "${resolutions[$rel]:-keep}" in
|
|
268
|
+
cdk)
|
|
269
|
+
mkdir -p "$(dirname "$TGT_CLAUDE/$rel")"
|
|
270
|
+
cp "$CDK_CLAUDE/$rel" "$TGT_CLAUDE/$rel"
|
|
271
|
+
(( modified_applied++ )) || true
|
|
272
|
+
;;
|
|
273
|
+
keep)
|
|
274
|
+
(( modified_kept++ )) || true
|
|
275
|
+
;;
|
|
276
|
+
esac
|
|
277
|
+
done
|
|
278
|
+
|
|
279
|
+
[[ $new_count -gt 0 ]] && success "$new_count new file(s) added"
|
|
280
|
+
[[ $unchanged_count -gt 0 ]] && success "$unchanged_count unchanged file(s) refreshed"
|
|
281
|
+
[[ $modified_applied -gt 0 ]] && success "$modified_applied modified file(s) updated to CDK version"
|
|
282
|
+
[[ $modified_kept -gt 0 ]] && info "$modified_kept modified file(s) kept as your version"
|
|
283
|
+
|
|
284
|
+
# ─── Section 8: Merge settings.json ───────────────────────────────────────────
|
|
285
|
+
merge_settings() {
|
|
286
|
+
local tgt_settings="$TGT_CLAUDE/settings.json"
|
|
287
|
+
local cdk_settings="$CDK_CLAUDE/settings.json"
|
|
288
|
+
local merge_script="$KIT_ROOT/scripts/lib/merge-settings.js"
|
|
289
|
+
|
|
290
|
+
if [[ ! -f "$cdk_settings" ]]; then
|
|
291
|
+
warn "settings.json: CDK source not found — skipping"
|
|
292
|
+
return
|
|
293
|
+
fi
|
|
294
|
+
|
|
295
|
+
if [[ ! -f "$tgt_settings" ]]; then
|
|
296
|
+
# No existing settings — install CDK's (strip internal comment keys)
|
|
297
|
+
node -e "
|
|
298
|
+
const s = JSON.parse(require('fs').readFileSync('$cdk_settings', 'utf8'));
|
|
299
|
+
delete s._comment; delete s._hooks_note;
|
|
300
|
+
process.stdout.write(JSON.stringify(s, null, 2) + '\n');
|
|
301
|
+
" > "$tgt_settings"
|
|
302
|
+
success "settings.json: created from CDK template"
|
|
303
|
+
return
|
|
304
|
+
fi
|
|
305
|
+
|
|
306
|
+
local tmp
|
|
307
|
+
tmp=$(mktemp /tmp/cdk-settings-XXXXXX.json)
|
|
308
|
+
# shellcheck disable=SC2064
|
|
309
|
+
trap "rm -f '$tmp'" RETURN
|
|
310
|
+
|
|
311
|
+
if node "$merge_script" "$tgt_settings" "$cdk_settings" > "$tmp" 2>&1; then
|
|
312
|
+
# Validate output is parseable JSON before overwriting
|
|
313
|
+
if node -e "JSON.parse(require('fs').readFileSync('$tmp', 'utf8'))" 2>/dev/null; then
|
|
314
|
+
cp "$tmp" "$tgt_settings"
|
|
315
|
+
success "settings.json: merged (permissions + hooks updated)"
|
|
316
|
+
else
|
|
317
|
+
warn "settings.json: merge produced invalid JSON — your original preserved"
|
|
318
|
+
warn "Manual merge reference: $CDK_CLAUDE/settings.json.example"
|
|
319
|
+
fi
|
|
320
|
+
else
|
|
321
|
+
warn "settings.json: merge failed — your original preserved"
|
|
322
|
+
warn "Manual merge reference: $CDK_CLAUDE/settings.json.example"
|
|
323
|
+
fi
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
merge_settings
|
|
327
|
+
|
|
328
|
+
# ─── Section 9: Merge CLAUDE.md ───────────────────────────────────────────────
|
|
329
|
+
merge_claude_md() {
|
|
330
|
+
local tgt_md="$TARGET/CLAUDE.md"
|
|
331
|
+
local cdk_template="$CDK_CLAUDE/templates/claude-md-template.md"
|
|
332
|
+
local START_MARKER="CDK:GENERATED:START"
|
|
333
|
+
local END_MARKER="CDK:GENERATED:END"
|
|
334
|
+
|
|
335
|
+
if [[ ! -f "$cdk_template" ]]; then
|
|
336
|
+
warn "CLAUDE.md: template not found at $cdk_template — skipping"
|
|
337
|
+
return
|
|
338
|
+
fi
|
|
339
|
+
|
|
340
|
+
if [[ ! -f "$tgt_md" ]]; then
|
|
341
|
+
cp "$cdk_template" "$tgt_md"
|
|
342
|
+
success "CLAUDE.md: created from template"
|
|
343
|
+
return
|
|
344
|
+
fi
|
|
345
|
+
|
|
346
|
+
if grep -qF "$START_MARKER" "$tgt_md" 2>/dev/null; then
|
|
347
|
+
# Has CDK markers — replace only the block between markers (inclusive)
|
|
348
|
+
local tmp
|
|
349
|
+
tmp=$(mktemp /tmp/cdk-claude-md-XXXXXX.md)
|
|
350
|
+
# shellcheck disable=SC2064
|
|
351
|
+
trap "rm -f '$tmp'" RETURN
|
|
352
|
+
|
|
353
|
+
# Extract CDK block from template
|
|
354
|
+
local cdk_block
|
|
355
|
+
cdk_block=$(awk "/<!-- ${START_MARKER}/,/<!-- ${END_MARKER} -->/" "$cdk_template")
|
|
356
|
+
|
|
357
|
+
# Build merged file: content before START + cdk block + content after END
|
|
358
|
+
awk -v block="$cdk_block" \
|
|
359
|
+
-v start="$START_MARKER" \
|
|
360
|
+
-v end="$END_MARKER" \
|
|
361
|
+
'BEGIN { in_block=0; printed=0 }
|
|
362
|
+
index($0, start) > 0 {
|
|
363
|
+
if (!printed) { print block; printed=1 }
|
|
364
|
+
in_block=1; next
|
|
365
|
+
}
|
|
366
|
+
in_block && index($0, end) > 0 { in_block=0; next }
|
|
367
|
+
!in_block { print }
|
|
368
|
+
' "$tgt_md" > "$tmp"
|
|
369
|
+
|
|
370
|
+
cp "$tmp" "$tgt_md"
|
|
371
|
+
success "CLAUDE.md: CDK block updated (your custom content preserved)"
|
|
372
|
+
else
|
|
373
|
+
# No CDK markers — append the CDK block at the end
|
|
374
|
+
local cdk_block
|
|
375
|
+
cdk_block=$(awk "/<!-- ${START_MARKER}/,/<!-- ${END_MARKER} -->/" "$cdk_template")
|
|
376
|
+
{
|
|
377
|
+
echo ""
|
|
378
|
+
echo "---"
|
|
379
|
+
echo ""
|
|
380
|
+
printf '%s\n' "$cdk_block"
|
|
381
|
+
} >> "$tgt_md"
|
|
382
|
+
success "CLAUDE.md: CDK block appended (your existing content preserved)"
|
|
383
|
+
info "Tip: you can move the appended block anywhere in the file — the markers let future migrations update it in place"
|
|
384
|
+
fi
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
merge_claude_md
|
|
388
|
+
|
|
389
|
+
# ─── Section 10: Write manifest ───────────────────────────────────────────────
|
|
390
|
+
write_manifest() {
|
|
391
|
+
{
|
|
392
|
+
echo "# CDK Manifest — generated by scripts/migrate.sh"
|
|
393
|
+
echo "# Lists all files installed by claude-dev-kit."
|
|
394
|
+
echo "# Do not edit manually. Used to distinguish CDK-owned from user-created files."
|
|
395
|
+
echo "#"
|
|
396
|
+
for rel in "${cdk_files[@]}"; do
|
|
397
|
+
echo "$rel"
|
|
398
|
+
done
|
|
399
|
+
} > "$MANIFEST"
|
|
400
|
+
success "Manifest written (.claude/.cdk-manifest — tracks CDK-owned files for future migrations)"
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
write_manifest
|
|
404
|
+
|
|
405
|
+
# ─── Done ─────────────────────────────────────────────────────────────────────
|
|
406
|
+
echo ""
|
|
407
|
+
success "Migration complete"
|
|
408
|
+
echo ""
|
|
409
|
+
echo -e " ${DIM}Next steps:${NC}"
|
|
410
|
+
if [[ ! -f "$TARGET/CLAUDE.md" ]] || ! grep -qF "CDK:GENERATED" "$TARGET/CLAUDE.md" 2>/dev/null; then
|
|
411
|
+
echo -e " ${DIM} 1. Run \`/init\` inside Claude Code to configure CLAUDE.md for your stack${NC}"
|
|
412
|
+
echo -e " ${DIM} 2. Copy CLAUDE.local.md.example → CLAUDE.local.md for personal preferences${NC}"
|
|
413
|
+
echo -e " ${DIM} 3. Run \`/primer\` to verify Claude understands the project${NC}"
|
|
414
|
+
else
|
|
415
|
+
echo -e " ${DIM} 1. Copy CLAUDE.local.md.example → CLAUDE.local.md for personal preferences${NC}"
|
|
416
|
+
echo -e " ${DIM} 2. Review any modified files you chose to keep — ensure they're still compatible${NC}"
|
|
417
|
+
echo -e " ${DIM} 3. Run \`/primer\` to verify Claude understands the project${NC}"
|
|
418
|
+
fi
|
|
419
|
+
echo ""
|