@hegemonart/get-design-done 1.38.5 → 1.39.1
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-plugin/marketplace.json +2 -2
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +21 -0
- package/README.md +4 -0
- package/agents/design-verifier.md +1 -1
- package/agents/ds-migration-planner.md +72 -0
- package/package.json +1 -1
- package/reference/migrations/material-3-to-4.md +53 -0
- package/reference/migrations/mui-v6.md +58 -0
- package/reference/migrations/shadcn-v2.md +77 -0
- package/reference/migrations/tailwind-v4.md +73 -0
- package/reference/registry.json +28 -0
- package/scripts/lib/migration/codemod-gen.cjs +74 -0
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Get Design Done — 5-stage agent-orchestrated design pipeline with 9 connections, handoff-first workflow, bidirectional Figma write-back, 22+ specialized agents, queryable knowledge layer (intel store, dependency analysis, learnings extraction), and a self-improvement loop (reflector, frontmatter + budget feedback, global-skills layer). v1.20.0 ships the SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream, and resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) for rate-limit + 429 + context-overflow recovery. Full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation (auto-tag + GitHub Release + release-time smoke test).",
|
|
8
|
-
"version": "1.
|
|
8
|
+
"version": "1.39.1"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "get-design-done",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), Claude Design handoff, bidirectional Figma write-back, and a queryable intel store (.design/intel/) for dependency and learnings queries. Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows) and release automation. Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain.",
|
|
15
|
-
"version": "1.
|
|
15
|
+
"version": "1.39.1",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "hegemonart"
|
|
18
18
|
},
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "get-design-done",
|
|
3
3
|
"short_name": "gdd",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.39.1",
|
|
5
5
|
"description": "Agent-orchestrated 5-stage design pipeline: Brief → Explore → Plan → Design → Verify. 22+ specialized agents, 9 connections (Figma, Refero, Preview, Storybook, Chromatic, Figma Writer, Graphify, Pinterest, Claude Design), handoff-first workflow via Claude Design bundles, bidirectional Figma write-back (annotations, Code Connect), queryable intel store (`.design/intel/`) for O(1) design surface lookups, and self-improvement loop (reflector agent, frontmatter + budget feedback, global-skills layer at `~/.claude/gdd/global-skills/`). Standalone commands: style, darkmode, compare, figma-write, graphify, handoff, analyze-dependencies, skill-manifest, extract-learnings, reflect, apply-reflections. Embeds NNG heuristics, WCAG thresholds, typographic systems, motion framework, and anti-pattern catalog. Ships with a full CI/CD pipeline (Node 22/24 × Linux/macOS/Windows, lint + schema + frontmatter + stale-ref + shellcheck + gitleaks + injection-scan + blocking size-budget) and release automation (auto-tag + GitHub Release + release-time smoke test). Optimization layer (v1.0.4.1, retroactive): gdd-router + gdd-cache-manager skills, PreToolUse budget-enforcer hook, tier-aware agent frontmatter, lazy checker gates, streaming synthesizer, /gdd:warm-cache + /gdd:optimize commands, and cost telemetry at .design/telemetry/costs.jsonl — targeting 50-70% per-task token-cost reduction with no quality-floor regression. v1.20.0 SDK foundation: gdd-state MCP server (11 typed tools), lockfile-safe STATE.md mutations, event stream at .design/telemetry/events.jsonl, resilience primitives (jittered-backoff, rate-guard, error-classifier, iteration-budget) with rate-limit + 429 + context-overflow recovery, and TypeScript toolchain. v1.27.7 ships gdd-mcp (Phase 27.7): 12 read-only MCP tools for sub-3s priming. v1.28.0 (Phase 28): Foundational References Tier 2 — 5 new reference files (color-theory, composition, proportion-systems, i18n, contrast-advanced), 2 verifier i18n probes + 1 explore i18n-readiness probe, 12 additive cross-link insertions across 10 existing references, 2 orthogonal audit-scoring lens-tags (composition_alignment + i18n_readiness).",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "hegemonart",
|
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,27 @@ All notable changes to get-design-done are documented here. Versions follow [sem
|
|
|
4
4
|
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
+
## [1.39.1] - 2026-06-01
|
|
8
|
+
|
|
9
|
+
### Phase 39.1 — DS Migration Workflows
|
|
10
|
+
|
|
11
|
+
Opens the v1.39.x arc and the first half of the split Phase 39. When a design system ships a breaking major (shadcn/ui v1→v2, Tailwind v3→v4, MUI v5→v6, Material 2/3 token rename), GDD can now read the in-repo `package.json`, detect the version skew, consult a curated rule library, and produce an **impact-scored, proposal-only migration plan** with codemod scaffolds. Nothing runs automatically and no codemod engine is bundled — `codemod-gen` emits jscodeshift/ast-grep template **text** the developer reviews and runs themselves. **No new runtime dependency, no new egress.**
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- **`reference/migrations/{shadcn-v2,tailwind-v4,mui-v6,material-3-to-4}.md`** — 4 curated rule libraries. Each carries `## Detection` (package.json dep + version), a `## Migration rules` table (Rule ID · Kind · From → To · Note, where Kind ∈ `rename-class`/`rename-prop`/`remove-component`/`token-rename`/`new-default`), and `## Impact notes`. Grounded in the official upstream migration guides (Tailwind v4 browser baseline, MUI Grid2/`experimental_` removal, Material M2→M3 `--mdc-*` → `--md-sys-*` tokens). Registered.
|
|
16
|
+
- **`scripts/lib/migration/codemod-gen.cjs`** — pure, dep-free `emitCodemod(rule, { engine: 'jscodeshift' | 'ast-grep' })` → `{ ruleId, engine, kind, template }`. One template per rule kind; deterministic. Emits template **text only** — it never imports or runs jscodeshift/ast-grep.
|
|
17
|
+
- **`agents/ds-migration-planner.md`** — detects the DS + version from `package.json`, consults `reference/migrations/<ds>.md`, scores each affected component (visual-delta × usage × tests-affected), and emits codemod scaffolds to `.design/migration/` via `codemod-gen`. **Proposal-only**; long-tail DS fall back to a generic template.
|
|
18
|
+
- **`agents/design-verifier.md`** — an in-place note (net-zero, stays at the 700-line cap): when a DS migration is in flight, the verifier also asserts the migration preserved the contract (visual-diff within threshold, component API surface unchanged, tests pass) and treats an unmigrated high-impact rule as a gap.
|
|
19
|
+
|
|
20
|
+
### Notes
|
|
21
|
+
|
|
22
|
+
- **No new runtime dependency, no new egress** — `codemod-gen` is a pure text emitter; version detection is a local `package.json` read.
|
|
23
|
+
- 6-manifest lockstep at **v1.39.1** + `OFF_CADENCE_VERSIONS.add('1.39.1')` + the 30 live-pinned `manifests-version.txt` baselines forward-propagated 1.38.5 → 1.39.1.
|
|
24
|
+
- Inventory relock: registry-diff 153 → 157 (+4 rule libraries), agent-list +`ds-migration-planner` + both frontmatter-snapshots, tarball golden 694 → 700 (+6: 4 rule libraries + `codemod-gen.cjs` + `ds-migration-planner.md`). No skill/connection deltas.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
7
28
|
## [1.38.5] - 2026-06-01
|
|
8
29
|
|
|
9
30
|
### Phase 38.5 — Deployment Coordination Loop
|
package/README.md
CHANGED
|
@@ -178,6 +178,10 @@ GDD now learns **which design patterns win with users**, not just which pass lin
|
|
|
178
178
|
|
|
179
179
|
GDD now tracks a design past "PR merged" to **actually live**. [`/gdd:rollout-status`](skills/rollout-status/SKILL.md) reads the feature-flag service (the Phase 38 LaunchDarkly/Statsig/GrowthBook connections) via [`rollout-coordinator`](agents/rollout-coordinator.md) and classifies each cycle — `unrolled` / `staging-only` / `canary-N%` / `prod-100%` — surfacing **stuck** rollouts (a canary that hasn't advanced in N days). The pure [`rollout-status`](scripts/lib/rollout/rollout-status.cjs) classifier also computes a **deployed-percentage weight** that feeds the `design_arms` posterior via `verify_outcome` events — a variant that only reached 10% of users counts as weak evidence (0.1), a fully-rolled one counts 1.0. **Read-only** (GDD never advances or rolls back) and **no new runtime dependency**.
|
|
180
180
|
|
|
181
|
+
### DS migration workflows (v1.39.1)
|
|
182
|
+
|
|
183
|
+
When a design system ships a breaking major — shadcn/ui v1→v2, Tailwind v3→v4, MUI v5→v6, or the Material 2/3 token rename — GDD detects the skew from the in-repo `package.json`, consults a curated rule library ([`reference/migrations/`](reference/migrations/)), and produces an **impact-scored, proposal-only** migration plan via [`ds-migration-planner`](agents/ds-migration-planner.md). Each affected component is scored by visual-delta × usage × tests-affected, and the planner emits codemod scaffolds to `.design/migration/` through the pure [`codemod-gen`](scripts/lib/migration/codemod-gen.cjs) — which produces jscodeshift/ast-grep template **text only** (it never imports or runs a codemod engine). [`design-verifier`](agents/design-verifier.md) then treats an in-flight migration as a contract: visual-diff within threshold, component API surface unchanged, tests green, and an unmigrated high-impact rule is a gap. **Proposal-only, no new runtime dependency, no new egress.**
|
|
184
|
+
|
|
181
185
|
### Previous releases
|
|
182
186
|
|
|
183
187
|
- **v1.26.0** — Headless Model Resolver (per-runtime tier→model map, `resolved_models` router field, per-runtime price tables, `reasoning-class` runtime-neutral alias).
|
|
@@ -182,7 +182,7 @@ Allow-list seed (skip): `console\.(log|error|warn|info|debug)`, dev-only `/* */`
|
|
|
182
182
|
|
|
183
183
|
## Phase 2 — Must-Have Check
|
|
184
184
|
|
|
185
|
-
Read `.design/STATE.md` `<must_haves>`. Also read must-haves from DESIGN-PLAN.md acceptance criteria, **and the brief's `<prior-research>` findings (Phase 38)** — for each prior-research finding, assert the current design addresses it or note an explicit defer + rationale (an unaddressed `critical`/`serious` finding is a gap). For each M-XX must-have, determine verification method and verify:
|
|
185
|
+
Read `.design/STATE.md` `<must_haves>`. Also read must-haves from DESIGN-PLAN.md acceptance criteria, **and the brief's `<prior-research>` findings (Phase 38)** — for each prior-research finding, assert the current design addresses it or note an explicit defer + rationale (an unaddressed `critical`/`serious` finding is a gap). **When a DS migration is in flight** (`.design/migration/` per Phase 39.1's `ds-migration-planner`), also assert it preserved the contract — visual-diff within threshold, component API surface unchanged, tests pass — and treat an unmigrated high-impact rule as a gap. For each M-XX must-have, determine verification method and verify:
|
|
186
186
|
|
|
187
187
|
| Must-have type | Verification method |
|
|
188
188
|
|---|---|
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ds-migration-planner
|
|
3
|
+
description: Plans a design-system version migration (shadcn v1→v2, Tailwind v3→v4, MUI v5→v6, Material token migration). Detects the DS + version from package.json, consults the matching reference/migrations/<ds>.md rule library, proposes an impact-scored per-component plan (visual-delta × usage-frequency × tests-affected), and emits jscodeshift/ast-grep codemod templates via scripts/lib/migration/codemod-gen.cjs. Proposal-only — the user reviews + runs each codemod; GDD never auto-applies.
|
|
4
|
+
tools: Read, Bash, Grep, Glob
|
|
5
|
+
color: green
|
|
6
|
+
default-tier: sonnet
|
|
7
|
+
tier-rationale: "Consults a rule library + scores impact + emits codemod scaffolds via a pure helper; bounded planning, not open design judgment — sonnet-tier."
|
|
8
|
+
size_budget: M
|
|
9
|
+
size_budget_rationale: "Honest tier sized to the ~105-line body. DELEGATES the rules to reference/migrations/<ds>.md and the codemod templating to scripts/lib/migration/codemod-gen.cjs (the pdf-executor→validate-print-css precedent)."
|
|
10
|
+
parallel-safe: false
|
|
11
|
+
typical-duration-seconds: 60
|
|
12
|
+
reads-only: false
|
|
13
|
+
writes:
|
|
14
|
+
- ".design/migration/<ds>-<from>-<to>/** (plan + codemod templates, for review)"
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
@reference/shared-preamble.md
|
|
18
|
+
|
|
19
|
+
# ds-migration-planner
|
|
20
|
+
|
|
21
|
+
## Role
|
|
22
|
+
|
|
23
|
+
Turn a breaking design-system version bump into a reviewable, impact-ordered migration plan + ready-to-review codemod scaffolds. **Proposal-only (D-01)** — GDD detects, plans, and generates; the user reviews each codemod and runs it with their own tool (jscodeshift / ast-grep). GDD never auto-applies a migration.
|
|
24
|
+
|
|
25
|
+
## When invoked
|
|
26
|
+
|
|
27
|
+
When the user wants to migrate a DS across a major (or `design-context-builder` detects a dep major behind the installed one). Supported libraries: shadcn (`reference/migrations/shadcn-v2.md`), Tailwind (`tailwind-v4.md`), MUI (`mui-v6.md`), Material tokens (`material-3-to-4.md`).
|
|
28
|
+
|
|
29
|
+
## Step 1 — Detect DS + version (package.json only, D-03)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
node -e "const p=require('./package.json'); const d={...p.dependencies,...p.devDependencies}; console.log(JSON.stringify({ tailwind:d.tailwindcss, mui:d['@mui/material'], radix:Object.keys(d).filter(k=>k.startsWith('@radix-ui/')).length, material:d['@material/web']||d['@angular/material'] }))"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Resolve the DS + the from→to version boundary from the dep version. Ambiguous → ask; never guess from source.
|
|
36
|
+
|
|
37
|
+
## Step 2 — Load the rule library
|
|
38
|
+
|
|
39
|
+
Read `reference/migrations/<ds>.md`. Its `## Migration rules` table is the authoritative rule set (id · kind · from→to · note); `## Impact notes` flags high-visual-delta vs mechanical. **No matching library** (a long-tail DS) → emit a starter rule-library template for the user to author their own (D-05); do not guess rules.
|
|
40
|
+
|
|
41
|
+
## Step 3 — Impact-scored per-component plan (D-04)
|
|
42
|
+
|
|
43
|
+
For each affected component, score `impact = visual_delta × usage_frequency × tests_affected`:
|
|
44
|
+
|
|
45
|
+
- **visual_delta** — from the rule's Impact notes (high for ring/shadow/color/Grid changes; low for import renames).
|
|
46
|
+
- **usage_frequency** — `grep -rc` the component/class/token across `src/`.
|
|
47
|
+
- **tests_affected** — count touching test files.
|
|
48
|
+
|
|
49
|
+
Order the plan **highest-impact-lowest-risk first** so the user migrates the riskiest surfaces under the most scrutiny. Present the plan as a table (component · rules · impact · manual-review?).
|
|
50
|
+
|
|
51
|
+
## Step 4 — Emit codemod scaffolds (review before apply)
|
|
52
|
+
|
|
53
|
+
For each mechanical rule, emit a codemod template via the pure generator:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node -e "const {emitCodemod}=require('./scripts/lib/migration/codemod-gen.cjs'); \
|
|
57
|
+
console.log(emitCodemod({id:RULE_ID, kind:KIND, from:FROM, to:TO, note:NOTE}, {engine:'jscodeshift'}).template)"
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Write each to `.design/migration/<ds>-<from>-<to>/<RULE_ID>.{js,yml}` for the user to review + run. `new-default` rules emit a **manual-review advisory** (no auto-transform). NEVER run the codemod or write into `src/`.
|
|
61
|
+
|
|
62
|
+
## Step 5 — Hand off to verify
|
|
63
|
+
|
|
64
|
+
After the user applies codemods, `/gdd:verify` (`design-verifier`) checks the migration preserved the contract — visual-diff threshold, component API surface unchanged, tests pass. Note unresolved high-impact rules as gaps.
|
|
65
|
+
|
|
66
|
+
## Record
|
|
67
|
+
|
|
68
|
+
Emit a `## Migration plan` summary: DS, from→to, the impact-ordered component table, the emitted codemod files, and manual-review items. Close with:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
## MIGRATION PLAN COMPLETE
|
|
72
|
+
```
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hegemonart/get-design-done",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.39.1",
|
|
4
4
|
"description": "A design-quality pipeline for AI coding agents: brief, plan, implement, and verify UI work against your design system.",
|
|
5
5
|
"author": "Hegemon",
|
|
6
6
|
"homepage": "https://github.com/hegemonart/get-design-done",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Material Design Token Migration (M3 → next) — Rule Library
|
|
2
|
+
|
|
3
|
+
Honest framing: Google has **not** released a "Material Design 4" spec — Material publicly evolved M2 → M3 (Material You), and there is no public M4. This library is therefore a **forward-looking rule set for migrating a Material-token-based design system to the next Material major**, grounded entirely in the **real, well-documented M2 → M3 token migration** (the `--mdc-*` → `--md-sys-*` reshape, color-role expansion, reference-vs-system token split, typescale rename, and `mwc-*` → `md-*` component rename). Those concrete patterns are the migration *shape* the next major will most plausibly follow; nothing below invents tokens from a fictional spec. GDD never auto-applies — these rules feed `ds-migration-planner` and template `codemod-gen.cjs` output that the USER reviews and runs.
|
|
4
|
+
|
|
5
|
+
## Detection
|
|
6
|
+
|
|
7
|
+
Detect a Material-token design system from `package.json` **only** (no lockfile, no source scan at this stage):
|
|
8
|
+
|
|
9
|
+
- **Dependencies** (`dependencies` / `devDependencies` / `peerDependencies`):
|
|
10
|
+
- `@material/web` — Material Web Components (M3-era; the forward-migration target surface).
|
|
11
|
+
- `@material/*` MDC packages — `@material/button`, `@material/textfield`, `@material/theme`, `@material/typography`, etc. (M2-era MDC Web). Presence of many `@material/<component>` entries strongly signals an M2 MDC system.
|
|
12
|
+
- `material-components-web` — the MDC Web umbrella package (M2-era).
|
|
13
|
+
- `@angular/material` — read the **major** version: `<= 14` ≈ M2 theming API, `>= 15` ≈ M3 / MDC-backed components, `>= 17` ≈ M3 tokens default. Pair with `@angular/cdk` major as a cross-check.
|
|
14
|
+
- **Signal strength**: treat as a migration candidate when (a) at least one package above is present **and** (b) the manifest shows Material token usage intent — e.g. a `@material/theme` / `@material/tokens` dep, a `"material"` config block, or MDC/M3 packages pinned to a pre-target major. Manifest-only detection — defer actual token-occurrence scanning to the planner's codemod-gen pass.
|
|
15
|
+
|
|
16
|
+
## Migration rules
|
|
17
|
+
|
|
18
|
+
| Rule ID | Kind | From → To | Note |
|
|
19
|
+
|---------|------|-----------|------|
|
|
20
|
+
| MD-01 | token-rename | `--mdc-theme-primary` → `--md-sys-color-primary` | Core M2→M3 theme→system color reshape; root of most edits. |
|
|
21
|
+
| MD-02 | token-rename | `--mdc-theme-on-primary` → `--md-sys-color-on-primary` | "on-" foreground roles move under `md.sys.color`. |
|
|
22
|
+
| MD-03 | token-rename | `--mdc-theme-secondary` / `--mdc-theme-on-secondary` → `--md-sys-color-secondary` / `--md-sys-color-on-secondary` | Secondary role pair preserved across majors. |
|
|
23
|
+
| MD-04 | token-rename | `--mdc-theme-surface` → `--md-sys-color-surface` | Surface base role; precondition for the surface-container set (MD-05). |
|
|
24
|
+
| MD-05 | new-default | *(no M2 equivalent)* → `--md-sys-color-surface-container` / `-surface-container-high` / `-surface-container-highest` / `-surface-container-low` / `-surface-container-lowest` | M3 tonal surface-container roles; replace ad-hoc elevation overlays. High visual delta. |
|
|
25
|
+
| MD-06 | token-rename | `--mdc-theme-outline` (or hand-rolled border token) → `--md-sys-color-outline` | Outline promoted to a first-class system color role. |
|
|
26
|
+
| MD-07 | new-default | *(no M2 equivalent)* → `--md-sys-color-outline-variant` | Low-emphasis dividers/borders; previously a faded outline or custom value. |
|
|
27
|
+
| MD-08 | token-rename | `md.ref.palette.primary40` (reference tokens) → `md.sys.color.primary` (system tokens) | Enforce ref→sys indirection: components consume `md.sys.*`, never `md.ref.palette.*` directly. |
|
|
28
|
+
| MD-09 | token-rename | `--mdc-typography-headline6-*` → `--md-sys-typescale-title-large-*` | Typescale rename + remap (M2 named sizes → M3 role-based scale). Verify per-role size/weight mapping. |
|
|
29
|
+
| MD-10 | token-rename | `--mdc-typography-body1-*` / `body2-*` → `--md-sys-typescale-body-large-*` / `body-medium-*` | Body typescale remap; line-height and tracking values also shift. |
|
|
30
|
+
| MD-11 | token-rename | `--mdc-elevation-*` / elevation z-values → `--md-sys-elevation-level0..level5` | Numeric dp elevations become a 6-step token scale; pair with surface-container roles. |
|
|
31
|
+
| MD-12 | token-rename | `--mdc-shape-medium` / corner radii → `--md-sys-shape-corner-medium` (`-small` / `-large` / `-extra-large` / `-full`) | Shape scale renamed and bucketed into corner roles. |
|
|
32
|
+
| MD-13 | new-default | *(opt-in)* → dynamic color enabled (`md.sys.color.*` derived from a seed/source color) | M3 default theming model; replaces a static brand palette. Highest visual delta. |
|
|
33
|
+
| MD-14 | remove-component | `mwc-button` → `md-filled-button` (and `md-outlined-button` / `md-text-button` / `md-tonal-button`) | `@material/web` element rename; one MWC element fans out to explicit M3 variants — no clean 1:1 transform, pick a variant by hand (manual TODO). |
|
|
34
|
+
| MD-15 | remove-component | `mwc-textfield` → `md-filled-text-field` / `md-outlined-text-field` | `mwc-*` → `md-*` rename; one element fans out to 2 variants — appearance now encoded in the element name, choose by hand (manual TODO). |
|
|
35
|
+
| MD-16 | rename-prop | `?outlined` / `?raised` boolean attrs → variant element (per MD-14/MD-15) | Style-toggle props removed; pick the right `md-*` element instead of toggling a flag. |
|
|
36
|
+
| MD-17 | remove-component | `mwc-icon-button-toggle` → *(removed)* — compose `md-icon-button` + `toggle` selected state | Dedicated toggle element dropped; rebuild with base icon-button + selected state. |
|
|
37
|
+
| MD-18 | rename-class | `.mdc-button` / `.mdc-card` (BEM CSS classes) → web-component element + part styling | M3 Web moves from BEM-class theming to custom-element `::part()` / token styling. |
|
|
38
|
+
|
|
39
|
+
## Impact notes
|
|
40
|
+
|
|
41
|
+
**High visual delta — design + QA review required, do not ship blind:**
|
|
42
|
+
- **Dynamic color (MD-13)**: switching from a fixed brand palette to seed-derived `md.sys.color.*` changes nearly every on-screen color. Confirm seed source, contrast pairs (`on-*` roles), and light/dark tonal output before adopting.
|
|
43
|
+
- **Surface-container roles (MD-05, MD-07)**: replacing elevation-overlay surfaces with tonal containers visibly alters card/sheet/menu backgrounds and divider weight. Audit layering after migration.
|
|
44
|
+
- **Typescale (MD-09, MD-10)**: M2 named styles do not map 1:1 to M3 role sizes — size, weight, line-height, and tracking all move. Expect reflow; review dense text and truncation.
|
|
45
|
+
|
|
46
|
+
**Mechanical — codemod-templatable, low judgment per occurrence:**
|
|
47
|
+
- Pure custom-property renames (MD-01–MD-04, MD-06, MD-08, MD-11, MD-12) are string-substitution-shaped. `codemod-gen.cjs` can template these as find/replace with a token map; still diff-review for false positives in comments, strings, and unrelated `--mdc-*` namespaces.
|
|
48
|
+
|
|
49
|
+
**Manual-review items — never auto-apply:**
|
|
50
|
+
- **Component renames + fan-out (MD-14, MD-15)**: one source element maps to several target variants; the correct target depends on the original prop set — a human (or planner) must choose. Generated templates should emit a *candidate* mapping plus a TODO, not a committed rename.
|
|
51
|
+
- **Removed components (MD-17)** and **prop→variant collapse (MD-16)**: require reconstructing behavior (selected/toggle state, layout) — flag for hand-editing.
|
|
52
|
+
- **Class→element migration (MD-18)**: BEM-class theming → custom-element `::part()` is a structural rewrite, not a rename; scope as its own task.
|
|
53
|
+
- **Angular major bumps**: when detection keys off `@angular/material`, the framework upgrade (DI, theming mixins, component API) dwarfs the token rename — sequence the framework migration first, tokens second.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# MUI (Material UI) v5 → v6 — Migration Rule Library
|
|
2
|
+
|
|
3
|
+
A proposal-only rule library that `agents/ds-migration-planner.md` reads to draft a v5→v6 migration plan, and that the pure `scripts/lib/migration/codemod-gen.cjs` uses to template codemods the USER reviews and runs. GDD never auto-applies any rule here — every entry is a *proposal* with a human in the loop. v6 is mostly mechanical (import + prefix renames) with two high-visual-delta areas: the `Grid` overhaul and the CSS-theme-variables adoption.
|
|
4
|
+
|
|
5
|
+
## Detection
|
|
6
|
+
|
|
7
|
+
Detect MUI and the v5→v6 boundary from `package.json` only — no lockfile read, no source scan. The planner classifies the project from the dependency graph alone:
|
|
8
|
+
|
|
9
|
+
- **Is MUI?** `dependencies` / `devDependencies` contains `@mui/material`. Treat the presence of any of these companion packages as corroborating evidence: `@mui/system`, `@mui/icons-material`, `@mui/lab`, `@mui/styled-engine` (or the styled-components variant `@mui/styled-engine-sc`), `@mui/base`.
|
|
10
|
+
- **On v5 (migration applies)?** The `@mui/material` semver range resolves to a `^5` / `5.x` major. Treat `~5`, `>=5 <6`, and a pinned `5.x.y` the same way. A `^6` / `6.x` range means the project is already on v6 — emit no rules. A `^7` range means it has moved past the scope of this library (defer to a v6→v7 library).
|
|
11
|
+
- **Companion-major skew (flag, do not migrate):** if `@mui/material` is `^5` but `@mui/system` or `@mui/styled-engine` resolves to `^6` (or vice-versa), the install is half-upgraded. Surface this as a blocker for the planner — every rule below assumes a coherent v5 baseline, and a mixed graph produces unreliable codemod output.
|
|
12
|
+
- **Peer majors to record** (planner notes, not rules in this library): `@mui/x-data-grid`, `@mui/x-date-pickers`, `@mui/x-charts`, `@mui/x-tree-view`. MUI X tracks its own v5→v6/v7 line on a separate cadence, but a Core v6 bump usually forces an X major bump too. Flag the Core/X pair so the planner sequences both migrations together rather than leaving the graph inconsistent.
|
|
13
|
+
- **Styled engine:** record whether `@mui/styled-engine-sc` (styled-components) is present — it changes how MUI6-19 (`styled`/`deepmerge`) is reviewed versus the default Emotion engine.
|
|
14
|
+
- **Runtime gates:** read `engines.node` and the `typescript` devDependency. v6 requires Node 14+ and TypeScript 4.7+ and drops IE 11 (see MUI6-17); a lower floor in `package.json` is itself a migration task.
|
|
15
|
+
|
|
16
|
+
## Migration rules
|
|
17
|
+
|
|
18
|
+
Kinds are constrained to: `rename-class`, `rename-prop`, `remove-component`, `token-rename`, `new-default`.
|
|
19
|
+
|
|
20
|
+
| Rule ID | Kind | From → To | Note |
|
|
21
|
+
|---|---|---|---|
|
|
22
|
+
| MUI6-01 | rename-class | `import { Unstable_Grid2 as Grid2 } from '@mui/material'` → `import { Grid2 } from '@mui/material'` | The `Unstable_` prefix is dropped; `Grid2` is now stable and is slated to become the default `Grid` in the next major. |
|
|
23
|
+
| MUI6-02 | rename-prop | `<Grid item xs={12} sm={6}>` → `<Grid size={{ xs: 12, sm: 6 }}>` | Grid2's per-breakpoint props collapse into one object `size` prop; the standalone `item` prop is gone (every non-container is implicitly an item). |
|
|
24
|
+
| MUI6-03 | rename-prop | `<Grid xs={6}>` → `<Grid size={6}>` | When the value is identical across breakpoints, `size` takes a single number instead of an object — shorthand for the MUI6-02 object form. |
|
|
25
|
+
| MUI6-04 | rename-prop | `xsOffset={2} smOffset={3}` → `offset={{ xs: 2, sm: 3 }}` | Per-breakpoint `*Offset` props collapse into one object `offset` prop (single-value form `offset={2}` also allowed). |
|
|
26
|
+
| MUI6-05 | new-default | `<Grid xs>` (boolean auto-grow) → `<Grid size="grow">` | The boolean `true` size value is renamed to the string `"grow"`; a bare boolean breakpoint prop no longer compiles. |
|
|
27
|
+
| MUI6-06 | new-default | `<Grid2 disableEqualOverflow>` → (prop removed) | `disableEqualOverflow` is deleted; v6 Grid2 lays out spacing with CSS `gap`, so the old negative-margin overflow workaround is unnecessary. |
|
|
28
|
+
| MUI6-07 | rename-class | `experimental_extendTheme` → `extendTheme` (from `@mui/material/styles`) | The CSS-vars theme factory leaves experimental status; import the stable name. The `experimental_` alias is removed, not deprecated-with-shim. |
|
|
29
|
+
| MUI6-08 | rename-class | `Experimental_CssVarsProvider as CssVarsProvider` → `CssVarsProvider` | Drop the `Experimental_` prefix. Preferred end-state: v6 `ThemeProvider` now subsumes every CssVarsProvider feature, so consolidating onto a single `ThemeProvider` is the recommended target. |
|
|
30
|
+
| MUI6-09 | new-default | `createTheme({ ... })` → `createTheme({ cssVariables: true, ... })` | Opt-in flag that emits CSS custom properties for the theme; required to read `theme.vars.*` and to get SSR-safe, flash-free dark mode. Off by default — adding it is a proposal, never automatic. |
|
|
31
|
+
| MUI6-10 | token-rename | `theme.palette.primary.main` (in `styled`/`sx`) → `theme.vars.palette.primary.main` | Under `cssVariables: true`, read palette tokens through `theme.vars.*` (CSS-var references) so values resolve per color scheme at runtime instead of being baked in at build time. Plain `theme.palette.*` still returns a value but won't switch schemes via CSS. |
|
|
32
|
+
| MUI6-11 | token-rename | `theme.palette.mode === 'dark' ? a : b` → `theme.applyStyles('dark', { ... })` | Replace color-mode ternaries in `styled`/`sx` with `applyStyles`; ternaries break under CSS-vars dark mode because both schemes are emitted into one stylesheet and the JS branch resolves only once. |
|
|
33
|
+
| MUI6-12 | remove-component | `import { LoadingButton } from '@mui/lab'` → `import { Button } from '@mui/material'` + `loading` / `loadingPosition` props | `LoadingButton` is merged into the core `Button`; the `@mui/lab` export is deprecated. Move `loading`, `loadingPosition`, and `loadingIndicator` onto `Button`. |
|
|
34
|
+
| MUI6-13 | remove-component | `<ListItem button autoFocus disabled selected>` → `<ListItem disablePadding><ListItemButton autoFocus disabled selected>…</ListItemButton></ListItem>` | The interactive `button` behavior and its `autoFocus` / `disabled` / `selected` props are removed from `ListItem`; wrap the content in `ListItemButton`. |
|
|
35
|
+
| MUI6-14 | rename-class | `listItemClasses` (e.g. `.Mui-selected`, `.Mui-focusVisible` keys) → `listItemButtonClasses` | Companion to MUI6-13: the interactive state class keys move from the `ListItem` class object to `ListItemButton`, so any `styleOverrides` or CSS targeting those keys must be re-pointed. |
|
|
36
|
+
| MUI6-15 | new-default | `<Divider orientation="vertical" />` renders an `hr` element → renders a `div` | A vertical `Divider` now emits a `div` carrying a separator ARIA role (an `hr` cannot be vertical/inline). Update any CSS or test selector that assumed an `hr` element. |
|
|
37
|
+
| MUI6-16 | new-default | `AccordionSummary` content is bare → wrapped in an `h3` heading element | The summary is wrapped in a heading by default; override the level via `slotProps={{ heading: { component: 'h4' } }}`. Affects heading-order audits and heading-based selectors/queries. |
|
|
38
|
+
| MUI6-17 | new-default | Node 12 / TS 3.5 / IE 11 baseline → Node 14+, TS 4.7+, IE 11 dropped | Raise `engines.node`, bump the `typescript` devDependency, and remove IE-11 polyfills / transpile targets. Pure compatibility surface — no component code shape change. |
|
|
39
|
+
| MUI6-18 | remove-component | UMD bundle (`@mui/material/umd`, CDN `<script>` usage) → (removed) | The pre-bundled UMD build is dropped (~2.5 MB saved). Move CDN/`<script>` consumers to a bundler or native ESM; pure-ESM package `exports` are enforced, so deep internal `lib/` import paths also stop resolving. |
|
|
40
|
+
| MUI6-19 | token-rename | `import { deepmerge } from '@mui/utils'`; deep-spread of `theme.palette` in `styleOverrides` → re-verify against `@mui/utils` v6 | `styled` and `deepmerge` internals were reworked for the CSS-vars theme. Custom `styleOverrides` that mutated or deep-merged `theme.palette` should be re-tested (especially alongside MUI6-10). Flag for manual review; do not auto-rewrite — behavior, not just the import path, may differ. |
|
|
41
|
+
| MUI6-20 | rename-prop | `components={{ ... }}` / `componentsProps={{ ... }}` (on slotted components) → `slots={{ ... }}` / `slotProps={{ ... }}` | v6 finalizes the slot-API standardization: the legacy `components`/`componentsProps` pair is deprecated in favor of `slots`/`slotProps` across Modal, Popper, Autocomplete, Badge, Tooltip, and friends. Note that slot *names* are lowercased (e.g. `Backdrop` → `backdrop`). |
|
|
42
|
+
| MUI6-21 | remove-component | `import { Hidden } from '@mui/material'` → `sx` display utilities or `useMediaQuery` | The `Hidden` helper is removed; replace `<Hidden smDown>` with responsive `sx={{ display: { xs: 'none', sm: 'block' } }}` or a `useMediaQuery` guard. There is no drop-in component — this is a manual rewrite. |
|
|
43
|
+
| MUI6-22 | rename-class | `createMuiTheme` / `adaptV4Theme` (lingering v4 aliases) → `createTheme` | Any remaining v4-era `createMuiTheme` alias and the `adaptV4Theme` shim are removed in v6; collapse onto `createTheme`. Surfaces mainly in codebases that did a partial v4→v5 pass and left the aliases in place. |
|
|
44
|
+
|
|
45
|
+
## Impact notes
|
|
46
|
+
|
|
47
|
+
High visual delta — these require human review of the rendered result, not just a green diff:
|
|
48
|
+
|
|
49
|
+
- **Grid overhaul (MUI6-01…06):** moving off the `item xs={}` layout model to `size={{}}` *plus* the switch to CSS `gap` spacing can shift column widths, gutters, and wrap behavior. Generate the codemod proposal, then require a side-by-side visual diff before accepting. The official `v6.0.0/grid-v2-props` codemod covers the prop rename but not bespoke spacing/negative-margin math.
|
|
50
|
+
- **CSS theme variables (MUI6-09…11):** enabling `cssVariables: true` together with the `theme.vars.*` and `applyStyles` migration changes how *every* themed color resolves and how dark mode flips. This touches the whole surface at once — stage it behind a feature flag and review color and contrast holistically, not file-by-file.
|
|
51
|
+
- **Structural DOM rules (MUI6-13…16):** `ListItemButton` nesting, the vertical `Divider` → `div`, and the Accordion `h3` wrap all change the rendered DOM and heading order. Re-run accessibility and heading-order audits and fix brittle element/heading selectors in tests.
|
|
52
|
+
|
|
53
|
+
Mechanical — low risk, but still proposal-only (the USER reviews every generated patch):
|
|
54
|
+
|
|
55
|
+
- **Import / prefix / class renames** (MUI6-01, MUI6-07, MUI6-08, MUI6-12, MUI6-14) and the **engine bumps** (MUI6-17) are find-and-replace–class changes that `codemod-gen.cjs` can template safely. MUI6-18 is mechanical to detect but its *fix* (re-tooling a CDN consumer onto a bundler) is a build-system change, so route it to manual.
|
|
56
|
+
- **MUI6-19** looks mechanical (one import) but hides a behavior change — always tag it manual-review, never auto-apply.
|
|
57
|
+
|
|
58
|
+
Sequencing: do the prefix/import/class renames first (smallest blast radius), then the Grid pass, then the CSS-vars pass last (largest blast radius, and it interacts with MUI6-19). Resolve any companion-major skew and the peer MUI X majors surfaced in Detection *before* starting — a Core v6 bump typically drags an `@mui/x-*` major along with it.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# shadcn/ui v1 → v2 — Migration Rule Library
|
|
2
|
+
|
|
3
|
+
This library encodes the concrete, mechanical changes a project must make when moving a shadcn/ui codebase from the v1 era (Tailwind v3, HSL CSS variables, Radix `forwardRef`) to the v2 era (Tailwind v4 CSS-first theming, OKLCH variables, React 19 ref-as-prop). It is consumed by `agents/ds-migration-planner.md` to scope a migration and by `scripts/lib/migration/codemod-gen.cjs` to emit jscodeshift / ast-grep templates.
|
|
4
|
+
|
|
5
|
+
Every rule below is **proposal-only**. GDD never auto-applies a migration: the codemod generator emits reviewable templates, and the user inspects and runs each one before it touches the working tree. Treat the rules as a checklist the planner reasons over, not an executable patch — the planner's job is to scope, order, and impact-score, not to mutate code.
|
|
6
|
+
|
|
7
|
+
## Detection
|
|
8
|
+
|
|
9
|
+
shadcn/ui is **CLI-managed copy-in source**, not a versioned runtime dependency — the component code lives in the repo (`components/ui/*`), so there is no `shadcn` line in `dependencies` to read a version from. The planner therefore infers the era from `package.json` plus the project's `components.json`.
|
|
10
|
+
|
|
11
|
+
Primary boundary signals, strongest first:
|
|
12
|
+
|
|
13
|
+
- **`tailwindcss` major in `package.json`** is the single strongest signal: `^3.x` → v1 era; `^4.x` → v2 era. Almost everything else follows from this.
|
|
14
|
+
- **`components.json` shape** — its presence confirms a shadcn project. A non-empty `tailwind.config` key (a path to a JS/TS config) signals v1; an empty `tailwind.config` string plus a `tailwind.css` pointing at a file that opens with `@import "tailwindcss"` signals v2.
|
|
15
|
+
- **`react` / `react-dom` major** — `^19` indicates the project can use ref-as-prop and is on the v2 track; `^18` means `forwardRef` shims in the component source are still load-bearing.
|
|
16
|
+
|
|
17
|
+
Supporting (corroborating, not decisive) signals:
|
|
18
|
+
|
|
19
|
+
- **`@radix-ui/*` versions** — v1 pins per-primitive packages (e.g. `@radix-ui/react-dialog`); late-v2 projects often consolidate onto the unified `radix-ui` package. Either pattern is acceptable; the version floor matters more than the package name.
|
|
20
|
+
- **Animation dep** — `tailwindcss-animate` present → v1; `tw-animate-css` → v2.
|
|
21
|
+
- **`tailwind-merge` major** — `^2` → v1; `^3` → v2.
|
|
22
|
+
- **Neutral deps** — `next-themes`, `lucide-react`, `class-variance-authority`, `clsx` appear in both eras and are **not** boundary signals.
|
|
23
|
+
|
|
24
|
+
Version detection is **from `package.json` only** — never execute the shadcn CLI, hit a network registry, or shell out to resolve versions. If `tailwindcss` is absent or unparseable, the planner reports "era undetermined" rather than guessing.
|
|
25
|
+
|
|
26
|
+
## Migration rules
|
|
27
|
+
|
|
28
|
+
Each row is one codemod target. `Kind` is drawn from the `codemod-gen.cjs` enum: `rename-class` | `rename-prop` | `remove-component` | `token-rename` | `new-default`. Rule IDs are stable — the planner references them in its impact report and the generator names emitted templates after them.
|
|
29
|
+
|
|
30
|
+
| Rule ID | Kind | From → To | Note |
|
|
31
|
+
|---|---|---|---|
|
|
32
|
+
| SHADCN-01 | new-default | `tailwind.config.{js,ts}` `theme.extend` colors → `@theme` block in the CSS entry (after `@import "tailwindcss"`) | Tailwind v4 is CSS-first: theme tokens move out of JS config into an `@theme` directive. Structural move; the JS config is largely retired (only kept for plugins/content edge cases). |
|
|
33
|
+
| SHADCN-02 | token-rename | HSL triplet vars `--background: 0 0% 100%` → OKLCH `--background: oklch(1 0 0)` | Default theme variables switch from space-separated HSL channels to complete `oklch()` values across `:root` and `.dark`. Color values change representation, not (intended) appearance. |
|
|
34
|
+
| SHADCN-03 | rename-class | `hsl(var(--token))` wrappers & channel-opacity `bg-background/[alpha]` hacks → direct `var(--token)` / `bg-background` | v1 wrapped every var in `hsl(var(--x))`; v2 stores complete color functions, so the `hsl(...)` wrapper and the channel-based opacity workaround are removed. |
|
|
35
|
+
| SHADCN-04 | new-default | hand-rolled `@layer base { :root { … } }` tokens → `@theme inline { --color-*: var(--*) }` mapping | v2 exposes semantic tokens to the utility namespace via `@theme inline`, bridging raw `--background` vars to the `--color-background` utility Tailwind generates classes from. |
|
|
36
|
+
| SHADCN-05 | rename-prop | `React.forwardRef<T, P>((props, ref) => …)` → ref-as-prop `({ ref, ...props }: P & { ref?: Ref<T> })` | React 19 passes `ref` as a normal prop. v2 component source drops `forwardRef`; the trailing `Component.displayName = …` assignments become unnecessary. Mechanical, but touches every primitive file. |
|
|
37
|
+
| SHADCN-06 | new-default | component part JSX → add `data-slot="<component-part>"` attribute on each rendered element | v2 tags every primitive part with a stable `data-slot` for styling/targeting (e.g. `data-slot="card-header"`). Net-new attribute across every component file; near-zero visual delta. |
|
|
38
|
+
| SHADCN-07 | remove-component | `components/ui/toast.tsx` + `hooks/use-toast.ts` + Radix `<Toaster/>` → `sonner` `<Toaster/>` + `toast` import | The Radix-based toast and the `useToast` hook are removed in favor of Sonner. Imports of `useToast` / `@/components/ui/use-toast` must be rewritten to `import { toast } from "sonner"`. Behavioral + public-API change. |
|
|
39
|
+
| SHADCN-08 | rename-prop | `useToast()` call sites `toast({ title, description, variant })` → sonner `toast(title, { description })` / `toast.error(…)` | Sonner's signature differs from the v1 hook object form; `variant: "destructive"` maps to `toast.error`. Every call site needs reshaping. Pairs with SHADCN-07. |
|
|
40
|
+
| SHADCN-09 | token-rename | `tailwindcss-animate` plugin + dep → `tw-animate-css` via `@import "tw-animate-css"` in the CSS entry | The animation-utilities package is renamed. Remove the old devDependency and its plugin entry, add the new CSS import. Utility class names (`animate-in`, `fade-in-0`, `zoom-in-95`) are preserved. |
|
|
41
|
+
| SHADCN-10 | new-default | `components.json` `"style": "default"` → `"style": "new-york"` | The `default` style is removed in v2; `new-york` is the only remaining style, so any re-added component pulls new-york source. Some spacing, border, and icon-size defaults shift — moderate visual delta. |
|
|
42
|
+
| SHADCN-11 | rename-class | paired `h-4 w-4`, `h-6 w-6` icon sizing → `size-4`, `size-6` | v2 source adopts the `size-*` shorthand (Tailwind ≥3.4). Safe equivalent of matched `h-*`/`w-*`; mechanical, no visual delta. Only collapse pairs where the two values are equal. |
|
|
43
|
+
| SHADCN-12 | rename-prop | `cn()` relying on `tailwind-merge@^2` → `^3` | `tailwind-merge` v3 changes some merge-conflict resolution internals. Bump the dependency and re-verify `cn()` output where custom classes overlap component defaults. The `lib/utils.ts` `cn` body itself is unchanged. |
|
|
44
|
+
| SHADCN-13 | remove-component | per-primitive `@radix-ui/react-*` imports → unified `radix-ui` namespace imports | Optional in v2: `import * as DialogPrimitive from "@radix-ui/react-dialog"` → `import { Dialog as DialogPrimitive } from "radix-ui"`. Removes many small deps; not required for correctness, so flag as opt-in only. |
|
|
45
|
+
| SHADCN-14 | rename-class | `focus:ring-2 ring-offset-2` focus styles → `focus-visible:ring-[3px] focus-visible:ring-ring/50` | v2 components standardize on `focus-visible` plus a softened ring (`ring/50`, 3px). Visible focus-state change — moderate visual delta, and accessibility-relevant, so it cannot be silently dropped. |
|
|
46
|
+
| SHADCN-15 | token-rename | add v2 chart + sidebar tokens (`--chart-1..5`, `--sidebar`, `--sidebar-foreground`, …) absent in v1 themes | v2 ships extra semantic variables. If the project uses the Chart or Sidebar components, the `@theme` / `:root` block must gain these tokens or those components render unstyled. No-op for projects that use neither. |
|
|
47
|
+
| SHADCN-16 | new-default | `tailwind.config` `darkMode: ["class"]` → CSS `@custom-variant dark (&:is(.dark *))` | v4 expresses the dark variant in CSS rather than JS config; the `darkMode` array key is dropped and replaced by a `@custom-variant` declaration in the stylesheet entry. |
|
|
48
|
+
|
|
49
|
+
## Impact notes
|
|
50
|
+
|
|
51
|
+
The planner scores each rule as **visual-delta × usage × tests** to order the migration and decide what needs human eyes before the user runs the codemod.
|
|
52
|
+
|
|
53
|
+
**High visual-delta — review with screenshots / visual diff:**
|
|
54
|
+
|
|
55
|
+
- SHADCN-02 + SHADCN-03 — the whole HSL→OKLCH color move. Intended to be visually neutral, but it rewrites every color value, so it must be *proven* neutral against a baseline, not assumed.
|
|
56
|
+
- SHADCN-10 — `default`→`new-york` shifts spacing, radius, and icon defaults across the component set.
|
|
57
|
+
- SHADCN-14 — the focus-ring restyle changes a visible interaction state.
|
|
58
|
+
- SHADCN-15 — missing chart/sidebar tokens make those components render wrong (unstyled), which reads as a large visual delta where they're used.
|
|
59
|
+
|
|
60
|
+
**High behavioral / API delta — manual review required:**
|
|
61
|
+
|
|
62
|
+
- SHADCN-07 + SHADCN-08 — toast → Sonner changes a public API and runtime behavior. Call-site reshaping cannot be fully mechanical (variant mapping, return values), so every notification flow should be re-tested by hand.
|
|
63
|
+
- SHADCN-12 — `tailwind-merge` v3 can silently change which class wins in a merge. Review anywhere custom classes are layered over component defaults.
|
|
64
|
+
|
|
65
|
+
**Structural, low visual-delta — codemod-friendly, light review:**
|
|
66
|
+
|
|
67
|
+
- SHADCN-01, SHADCN-04, SHADCN-16 relocate theming from JS config into CSS. Mechanical once the target `@theme` shape is fixed, but they touch the build entry — a single broken import fails the whole stylesheet, so verify the app boots rather than diffing pixels.
|
|
68
|
+
|
|
69
|
+
**Purely mechanical — auto-generatable templates, spot-check only:**
|
|
70
|
+
|
|
71
|
+
- SHADCN-05 (`forwardRef`→ref-as-prop), SHADCN-06 (`data-slot`), SHADCN-09 (`tailwindcss-animate`→`tw-animate-css`), SHADCN-11 (`h-*/w-*`→`size-*`). Deterministic AST rewrites with no intended visual change. Weight **usage** (file count) heavily here, since these fan out across every component file and dominate the diff size.
|
|
72
|
+
|
|
73
|
+
**Opt-in — never blocking:**
|
|
74
|
+
|
|
75
|
+
- SHADCN-13 (Radix package consolidation) is dependency hygiene only. Gate it behind explicit user opt-in; never hold up a migration on it.
|
|
76
|
+
|
|
77
|
+
**Test signal:** rules touching files that already have component tests (often the primitives in SHADCN-05/06/11) score lower review-risk, because the suite catches regressions. Rules touching theme/CSS (SHADCN-01/02/04/15/16) are rarely unit-tested, score higher, and must lean on visual verification instead of the test gate.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# Tailwind CSS v3 → v4 — Migration Rule Library
|
|
2
|
+
|
|
3
|
+
This is a **proposal-only** rule library consumed by `agents/ds-migration-planner.md` and the pure `scripts/lib/migration/codemod-gen.cjs`. It encodes the concrete, mechanical changes between Tailwind CSS v3 and v4 so the planner can template codemods. GDD never auto-applies any rule below — every codemod is surfaced as a reviewable diff the USER inspects and runs.
|
|
4
|
+
|
|
5
|
+
A note the planner should carry into any plan it writes: v4 raised its browser baseline (it targets modern Safari / Chrome / Firefox and leans on native CSS cascade layers, `@property`, and `color-mix()`). A project that must support older browsers is **not** a clean migration target — the planner should surface that as a blocker rather than emitting codemods, since no class rename below can recover the dropped baseline.
|
|
6
|
+
|
|
7
|
+
## Detection
|
|
8
|
+
|
|
9
|
+
Detect Tailwind and the v3→v4 boundary from `package.json` **alone** — no file-content scanning is required for the gate. The planner reads `dependencies` + `devDependencies` and classifies:
|
|
10
|
+
|
|
11
|
+
- **Tailwind present** — a `tailwindcss` key appears under `dependencies` or `devDependencies`. If absent, the project is out of scope and no rules apply.
|
|
12
|
+
|
|
13
|
+
- **On v3** — the `tailwindcss` semver range resolves to `^3` / `~3` / `3.x`. Corroborating signals:
|
|
14
|
+
- a `tailwind.config.js` (or `.cjs` / `.mjs` / `.ts`) referenced by the build config, and
|
|
15
|
+
- a PostCSS pipeline that lists the bare `tailwindcss` plugin (in v3 the PostCSS plugin ships *inside* the core package).
|
|
16
|
+
|
|
17
|
+
- **On v4** — the `tailwindcss` range resolves to `^4` / `4.x`, **and/or** one of the dedicated build packages is present:
|
|
18
|
+
- `@tailwindcss/postcss` — the PostCSS plugin, which **moved out of core** into its own package in v4 (a high-confidence v4 marker), or
|
|
19
|
+
- `@tailwindcss/vite` — the first-party Vite plugin (used instead of PostCSS in Vite projects).
|
|
20
|
+
|
|
21
|
+
- **Migration candidate** — `tailwindcss@^3` with **no** `@tailwindcss/postcss` and **no** `@tailwindcss/vite`. A `tailwind.config.js` that survives the version bump is the signal that the JS-config → CSS-first migration (TW4-02) has not yet run.
|
|
22
|
+
|
|
23
|
+
- **Build-tool target** — a `vite` dependency steers the planner toward the `@tailwindcss/vite` install target; otherwise assume the `@tailwindcss/postcss` PostCSS target. Do **not** infer Tailwind from a lockfile alone — require the manifest entry so the gate stays deterministic.
|
|
24
|
+
|
|
25
|
+
Reading the semver range: treat a leading `^3` / `~3` / `3` / `3.x` as v3, and `^4` / `4` / `4.x` as v4. A wildcard (`*`) or git/`file:` specifier is **ambiguous** — the planner should fall back to the corroborating signals above (presence of `@tailwindcss/postcss`, a surviving `tailwind.config.js`) and, if still unresolved, mark detection as inconclusive rather than guessing. Detection is package.json-only by design; the per-class renames in the table are confirmed later against source by the codemod step, not by this gate.
|
|
26
|
+
|
|
27
|
+
## Migration rules
|
|
28
|
+
|
|
29
|
+
Rule IDs are stable. `Kind ∈ rename-class | rename-prop | remove-component | token-rename | new-default`.
|
|
30
|
+
|
|
31
|
+
| Rule ID | Kind | From → To | Note |
|
|
32
|
+
|---------|------|-----------|------|
|
|
33
|
+
| TW4-01 | rename-prop | `@tailwind base; @tailwind components; @tailwind utilities;` → `@import "tailwindcss";` | The three v3 directives collapse into a single CSS import. Fully mechanical; affects the one entry CSS file. |
|
|
34
|
+
| TW4-02 | token-rename | `tailwind.config.js` (JS `theme`) → `@theme { … }` (CSS-first) | Theme moves into CSS custom properties under `@theme`. The JS config is still loadable via an `@config "./tailwind.config.js";` bridge, but the native v4 target is CSS. Highest-effort rule in the set. |
|
|
35
|
+
| TW4-03 | token-rename | `theme(colors.red.500)` in CSS → `var(--color-red-500)` | `@theme` tokens are emitted as real CSS variables, so `theme()` lookups and arbitrary values resolve to `var(--…)` references instead. |
|
|
36
|
+
| TW4-04 | rename-class | `shadow-sm` → `shadow-xs` | Box-shadow scale shifted one step down. **High visual delta** — every `shadow-sm` renders visibly smaller after the bump. |
|
|
37
|
+
| TW4-05 | rename-class | `shadow` → `shadow-sm` | The bare `shadow` alias is removed; the old default maps to the new explicit `shadow-sm`. |
|
|
38
|
+
| TW4-06 | rename-class | `drop-shadow` → `drop-shadow-sm` | Same bare-alias removal as TW4-05, applied across the `drop-shadow-*` filter scale. |
|
|
39
|
+
| TW4-07 | rename-class | `blur-sm` → `blur-xs` | Blur scale shifted one step down, mirroring the shadow scale. |
|
|
40
|
+
| TW4-08 | rename-class | `blur` → `blur-sm` | Bare `blur` alias removed; the old default becomes the explicit `blur-sm`. |
|
|
41
|
+
| TW4-09 | rename-class | `rounded-sm` → `rounded-xs` | Border-radius scale shifted one step down. |
|
|
42
|
+
| TW4-10 | rename-class | `rounded` → `rounded-sm` | Bare `rounded` alias removed; the old default becomes the explicit `rounded-sm`. |
|
|
43
|
+
| TW4-11 | rename-class | `ring` → `ring-3` | The default ring **width changed 3px → 1px** in v4. To preserve the v3 look the bare `ring` must become `ring-3`. **High visual delta** if skipped. |
|
|
44
|
+
| TW4-12 | new-default | bare `ring` color `blue-500` → `currentColor` | v4's bare `ring` now derives from `currentColor`, not `blue-500`. Where the blue was intentional, add an explicit `ring-blue-500`. |
|
|
45
|
+
| TW4-13 | rename-class | `outline-none` → `outline-hidden` | `outline-none` now emits a real `outline: none` (it was a transparent-outline a11y shim in v3). The old shim behavior is renamed `outline-hidden`; audit focus-visible styling when applying. |
|
|
46
|
+
| TW4-14 | rename-class | `bg-opacity-50` / `text-opacity-*` / `border-opacity-*` → `bg-black/50` (slash) | The `*-opacity-*` utilities are removed in favor of color/opacity slash syntax (`bg-black/50`, `text-white/70`). The numeric opacity value carries over directly. |
|
|
47
|
+
| TW4-15 | rename-class | `flex-shrink-0` / `flex-grow` → `shrink-0` / `grow` | The `flex-` prefix on shrink/grow was already deprecated in v3 and is removed in v4. Pure prefix strip, no value change. |
|
|
48
|
+
| TW4-16 | new-default | default border / divide color `gray-200` → `currentColor` | v4 borders and dividers default to `currentColor`, not `gray-200`. To preserve the v3 look, set `@theme { --color-border: …; }` or add explicit `border-gray-200`. **High visual delta.** |
|
|
49
|
+
| TW4-17 | rename-prop | `@layer utilities { … }` (custom utility) → `@utility name { … }` | Registering custom utilities now uses the `@utility` API; plain `@layer utilities` blocks no longer register sortable utilities. `@layer base` for resets still works. |
|
|
50
|
+
| TW4-18 | rename-prop | `corePlugins` / `safelist` (JS config) → `@source inline(…)` + CSS theme | v4 has no `corePlugins` toggle and content detection is automatic; explicit safelisting moves to `@source inline(...)`. Flag for manual handling — not a 1:1 token swap. |
|
|
51
|
+
|
|
52
|
+
## Impact notes
|
|
53
|
+
|
|
54
|
+
**High visual delta — require manual review and a screenshot diff before accepting:**
|
|
55
|
+
|
|
56
|
+
- **Scale shifts (TW4-04 through TW4-10).** Shadow, blur, and radius scales all shifted one step down. A blanket find/replace is correct *only if* the project used the default scale. Any project that overrode these in `tailwind.config.js` must reconcile against TW4-02 first, or the rename compounds with a custom scale and yields wrong sizes. Treat these seven rules as one coordinated set, not independent edits.
|
|
57
|
+
- **Ring width + color (TW4-11, TW4-12).** The default ring went from 3px/blue to 1px/currentColor. Focus rings are an accessibility surface, so both the width (`ring-3`) and color (`ring-blue-500`) rewrites should be reviewed against focus-visible states rather than applied silently.
|
|
58
|
+
- **Default border / divide color (TW4-16) and outline (TW4-13).** The `currentColor` border default and the `outline-none` → `outline-hidden` rename change visible chrome and focus behavior across the whole app. These are the two rules most likely to look "broken" immediately post-migration; verify against a representative page set.
|
|
59
|
+
|
|
60
|
+
**Mechanical — low risk, still proposal-only:**
|
|
61
|
+
|
|
62
|
+
- **Import directive (TW4-01)** and **flex-prefix strip (TW4-15)** are deterministic textual swaps with no visual consequence — safe to batch, but still emitted as a reviewable diff.
|
|
63
|
+
- **Opacity slash syntax (TW4-14)** is mechanical per-utility, but the codemod must pair the correct color literal with the opacity value — confirm it preserves the original color when expanding `bg-opacity-50` into `bg-<color>/50`.
|
|
64
|
+
|
|
65
|
+
**Needs a human design decision — not auto-templatable:**
|
|
66
|
+
|
|
67
|
+
- **CSS-first theme (TW4-02), `theme()` rewrites (TW4-03), custom-utility migration (TW4-17), and config-feature removal (TW4-18)** depend on the project's actual theme shape and custom utilities. The planner should emit a scaffold (an `@theme` block skeleton plus an `@config` bridge note) and hand the remainder to the USER rather than attempting a full automatic translation. The `@config` directive is the recommended interim bridge so a project can adopt the v4 build packages *before* fully porting its JS theme.
|
|
68
|
+
|
|
69
|
+
**Application order and idempotency (for `codemod-gen.cjs`):**
|
|
70
|
+
|
|
71
|
+
- The shadow, blur, and radius scales each contain a **shift hazard**: renaming the bare alias up one step (e.g. TW4-05 `shadow` → `shadow-sm`) and shifting the named step down (TW4-04 `shadow-sm` → `shadow-xs`) target overlapping tokens. A naive two-pass run can double-apply — `shadow` becomes `shadow-sm` becomes `shadow-xs`. Templates must rename in a **single atomic pass** (match the original token, map to its final value) so each utility is rewritten exactly once. The same hazard applies to the `blur-*` (TW4-07/08) and `rounded-*` (TW4-09/10) pairs.
|
|
72
|
+
- Run the entry-CSS rules (**TW4-01**, then the **TW4-02 / TW4-03 / TW4-17** theme scaffold) before the class renames, so the build is parseable under v4 before utility classes change. Class renames (TW4-04 through TW4-16) operate on template/markup files and are independent of each other once the single-pass rule above is respected.
|
|
73
|
+
- Every generated codemod should be **idempotent** — re-running it on already-migrated source must be a no-op. The planner surfaces the full set as one reviewable changeset; the USER applies it, not GDD.
|
package/reference/registry.json
CHANGED
|
@@ -993,6 +993,34 @@
|
|
|
993
993
|
"type": "heuristic",
|
|
994
994
|
"phase": 38.5,
|
|
995
995
|
"description": "Phase 38.5 rollout-coordination contract: the <rollout_status> STATE block (unrolled/staging-only/canary-N%/prod-100%), stuck detection (default 14 days), linear deployed_pct weighting feeding design_arms via verify_outcome (a 10%-rolled variant counts 0.1), and the rollout_started/advanced/stuck/verify_outcome events. Classifier scripts/lib/rollout/rollout-status.cjs; agent agents/rollout-coordinator.md; skill /gdd:rollout-status. Read-only — GDD never drives the rollout."
|
|
996
|
+
},
|
|
997
|
+
{
|
|
998
|
+
"name": "shadcn-v2",
|
|
999
|
+
"path": "reference/migrations/shadcn-v2.md",
|
|
1000
|
+
"type": "heuristic",
|
|
1001
|
+
"phase": 39.1,
|
|
1002
|
+
"description": "Phase 39.1 migration rule library — shadcn/ui v1→v2 (Tailwind v4 alignment, OKLCH CSS vars, sonner, Radix React-19 refs). Migration-rules table (id/kind/from→to) + Detection + Impact; consumed by agents/ds-migration-planner.md + scripts/lib/migration/codemod-gen.cjs (proposal-only)."
|
|
1003
|
+
},
|
|
1004
|
+
{
|
|
1005
|
+
"name": "tailwind-v4",
|
|
1006
|
+
"path": "reference/migrations/tailwind-v4.md",
|
|
1007
|
+
"type": "heuristic",
|
|
1008
|
+
"phase": 39.1,
|
|
1009
|
+
"description": "Phase 39.1 migration rule library — Tailwind CSS v3→v4 (@import directive, CSS-first @theme, renamed shadow/blur/radius/ring scale, opacity slash syntax, default border color). Rules + Detection + Impact; codemod-gen-consumable."
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
"name": "mui-v6",
|
|
1013
|
+
"path": "reference/migrations/mui-v6.md",
|
|
1014
|
+
"type": "heuristic",
|
|
1015
|
+
"phase": 39.1,
|
|
1016
|
+
"description": "Phase 39.1 migration rule library — MUI (Material UI) v5→v6 (Grid2-as-default, cssVariables theme, extendTheme/CssVarsProvider de-prefixing, theme.vars). Rules + Detection + Impact; codemod-gen-consumable."
|
|
1017
|
+
},
|
|
1018
|
+
{
|
|
1019
|
+
"name": "material-3-to-4",
|
|
1020
|
+
"path": "reference/migrations/material-3-to-4.md",
|
|
1021
|
+
"type": "heuristic",
|
|
1022
|
+
"phase": 39.1,
|
|
1023
|
+
"description": "Phase 39.1 migration rule library — Material Design token migration (M3→next), grounded in the real M2→M3 token-system patterns (md.sys.color/typescale roles, @material/web mwc-→md-) — no fabricated M4 spec. Rules + Detection + Impact; codemod-gen-consumable."
|
|
996
1024
|
}
|
|
997
1025
|
]
|
|
998
1026
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* scripts/lib/migration/codemod-gen.cjs — Phase 39.1 codemod template generator.
|
|
4
|
+
*
|
|
5
|
+
* Pure + dep-free (D-02): zero `require`. Given a migration rule from a
|
|
6
|
+
* reference/migrations/<ds>.md rule library, emit a STARTING-POINT codemod template (jscodeshift
|
|
7
|
+
* or ast-grep) that the USER reviews + runs with their own tool — GDD never imports or runs
|
|
8
|
+
* jscodeshift / ast-grep, and never auto-applies (D-01). Deterministic: same rule → same template
|
|
9
|
+
* (hermetic tests). The template is a scaffold the user adapts, not a guaranteed-correct transform.
|
|
10
|
+
*
|
|
11
|
+
* Rule shape: { id, kind, from, to, note? }
|
|
12
|
+
* kind ∈ 'rename-class' | 'rename-prop' | 'remove-component' | 'token-rename' | 'new-default'
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const KINDS = ['rename-class', 'rename-prop', 'remove-component', 'token-rename', 'new-default'];
|
|
16
|
+
|
|
17
|
+
const esc = (s) => String(s == null ? '' : s).replace(/\\/g, '\\\\').replace(/'/g, "\\'");
|
|
18
|
+
const reEsc = (s) => String(s == null ? '' : s).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
19
|
+
|
|
20
|
+
function jscodeshift(rule) {
|
|
21
|
+
const { id, kind, from, to, note } = rule;
|
|
22
|
+
const head = `// codemod ${id} (${kind}) — review before running: jscodeshift -t ${id}.js <path>\n` +
|
|
23
|
+
`// ${note ? note.replace(/\n/g, ' ') : `${from} -> ${to}`}\nmodule.exports = function (file, api) {\n const j = api.jscodeshift;\n const root = j(file.source);\n`;
|
|
24
|
+
const tail = `\n return root.toSource();\n};\n`;
|
|
25
|
+
switch (kind) {
|
|
26
|
+
case 'rename-prop':
|
|
27
|
+
return head + ` root.find(j.JSXAttribute, { name: { name: '${esc(from)}' } })\n .forEach((p) => { p.node.name.name = '${esc(to)}'; });` + tail;
|
|
28
|
+
case 'remove-component':
|
|
29
|
+
return head + ` // '${esc(from)}' is removed in the target version${to ? ` (use '${esc(to)}')` : ''}.\n` +
|
|
30
|
+
` root.findJSXElements('${esc(from)}').forEach((p) => {\n j(p).insertBefore(j.commentLine(' TODO(${id}): migrate <${esc(from)}>${to ? ` -> <${esc(to)}>` : ''}'));\n });` + tail;
|
|
31
|
+
case 'rename-class':
|
|
32
|
+
case 'token-rename':
|
|
33
|
+
return head + ` // replace the ${kind === 'rename-class' ? 'class' : 'token'} '${esc(from)}' -> '${esc(to)}' in string/template literals\n` +
|
|
34
|
+
` const RE = /\\b${reEsc(from)}\\b/g;\n` +
|
|
35
|
+
` root.find(j.Literal).filter((p) => typeof p.node.value === 'string' && RE.test(p.node.value))\n .forEach((p) => { p.node.value = p.node.value.replace(RE, '${esc(to)}'); });\n` +
|
|
36
|
+
` root.find(j.TemplateElement).filter((p) => p.node.value && RE.test(p.node.value.raw))\n .forEach((p) => { const v = p.node.value.raw.replace(RE, '${esc(to)}'); p.node.value = { raw: v, cooked: v }; });` + tail;
|
|
37
|
+
case 'new-default':
|
|
38
|
+
return `// codemod ${id} (new-default) — NO automatic transform.\n` +
|
|
39
|
+
`// A default changed (${from || '—'} -> ${to || '—'}). Manual review required: ${note ? note.replace(/\n/g, ' ') : 'audit affected usages + restore the old default explicitly if needed.'}\n` +
|
|
40
|
+
`module.exports = function (file) { return file.source; };\n`;
|
|
41
|
+
default:
|
|
42
|
+
throw new RangeError(`codemod-gen: unknown kind '${kind}' (expected one of ${KINDS.join(', ')})`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function astGrep(rule) {
|
|
47
|
+
const { id, kind, from, to, note } = rule;
|
|
48
|
+
const header = `# codemod ${id} (${kind}) — review before running: ast-grep scan -r ${id}.yml\n# ${note ? note.replace(/\n/g, ' ') : `${from} -> ${to}`}\nid: ${id}\nlanguage: tsx\n`;
|
|
49
|
+
switch (kind) {
|
|
50
|
+
case 'rename-prop':
|
|
51
|
+
return header + `rule:\n pattern: ${from}=$VAL\nfix: ${to}=$VAL\n`;
|
|
52
|
+
case 'remove-component':
|
|
53
|
+
return header + `rule:\n pattern: <${from} $$$PROPS />\n# '${from}' removed${to ? ` — migrate to <${to}>` : ''}; no auto-fix (manual)\n`;
|
|
54
|
+
case 'rename-class':
|
|
55
|
+
case 'token-rename':
|
|
56
|
+
return header + `rule:\n regex: '\\b${reEsc(from)}\\b'\nfix: '${to}'\n`;
|
|
57
|
+
case 'new-default':
|
|
58
|
+
return header + `# NO auto-fix — a default changed (${from || '—'} -> ${to || '—'}); manual review.\nrule:\n pattern: $X\nconstraints: {}\n`;
|
|
59
|
+
default:
|
|
60
|
+
throw new RangeError(`codemod-gen: unknown kind '${kind}' (expected one of ${KINDS.join(', ')})`);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** emitCodemod(rule, { engine }) → { ruleId, engine, kind, template }. */
|
|
65
|
+
function emitCodemod(rule, opts = {}) {
|
|
66
|
+
if (!rule || typeof rule !== 'object') throw new TypeError('emitCodemod: rule object required');
|
|
67
|
+
if (!KINDS.includes(rule.kind)) throw new RangeError(`emitCodemod: invalid kind '${rule.kind}'`);
|
|
68
|
+
const engine = opts.engine || 'jscodeshift';
|
|
69
|
+
if (engine !== 'jscodeshift' && engine !== 'ast-grep') throw new RangeError(`emitCodemod: engine must be jscodeshift|ast-grep`);
|
|
70
|
+
const template = engine === 'jscodeshift' ? jscodeshift(rule) : astGrep(rule);
|
|
71
|
+
return { ruleId: rule.id, engine, kind: rule.kind, template };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
module.exports = { emitCodemod, KINDS };
|