sparkle-design-cli 2.4.2 → 2.5.0-beta.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.
@@ -6,8 +6,9 @@ import { setupAssistant } from '../lib/setup.js';
6
6
  import { runStopHook } from '../lib/stop-hook.js';
7
7
  import { PLUGIN_SPEC_MARKDOWN } from '../lib/plugin-api.js';
8
8
  import { loadAntiPatternPlugins } from '../lib/load-plugins.js';
9
+ import { runRules } from '../lib/rules-report.js';
9
10
 
10
- const SUBCOMMANDS = new Set(['generate', 'check', 'setup', 'stop-hook', 'plugin-spec']);
11
+ const SUBCOMMANDS = new Set(['generate', 'check', 'rules', 'setup', 'stop-hook', 'plugin-spec']);
11
12
 
12
13
  function requireOptionValue(args, index, flags) {
13
14
  const value = args[index + 1];
@@ -134,6 +135,37 @@ function parseSetupOptions(args) {
134
135
  return options;
135
136
  }
136
137
 
138
+ /**
139
+ * `rules` のオプション解析。
140
+ *
141
+ * 値の検証を省くと、`--format jsno` やタイプミスが**黙ってテキスト出力に
142
+ * フォールバックして exit 0** になる。JSON を期待している CI や AI は、
143
+ * エラーではなく解析できない stdout を受け取ることになる。他のサブコマンドと
144
+ * 同じく、未知のフラグと不正な値はその場で失敗させる。
145
+ * en: Without validation a typo like `--format jsno` would silently emit text
146
+ * and exit 0, handing non-JSON stdout to a caller that asked for JSON.
147
+ */
148
+ function parseRulesOptions(args) {
149
+ const options = { format: 'text' };
150
+ const FORMATS = new Set(['text', 'json']);
151
+
152
+ for (let i = 0; i < args.length; i += 1) {
153
+ const arg = args[i];
154
+ if (arg === '--format') {
155
+ const value = requireOptionValue(args, i, '--format');
156
+ if (!FORMATS.has(value)) {
157
+ throw new Error(`Unsupported format for --format: ${value} (text | json)`);
158
+ }
159
+ options.format = value;
160
+ i += 1;
161
+ continue;
162
+ }
163
+ throw new Error(`Unknown option for rules: ${arg}`);
164
+ }
165
+
166
+ return options;
167
+ }
168
+
137
169
  function showHelp() {
138
170
  console.log(`
139
171
  Sparkle Design CLI
@@ -144,8 +176,11 @@ Sparkle Design CLI
144
176
  Commands:
145
177
  generate sparkle.config.json から CSS を生成
146
178
  check Sparkle Design のアンチパターンを検査
179
+ rules 現在有効なアンチパターンルールを一覧表示(プラグイン由来のものも含む)
147
180
  setup Sparkle Design プロジェクトをセットアップ(パッケージ導入 + 初期ファイル + AI ガード + generate)
148
- stop-hook AI assistant の Stop hook 用 internal subcommand(findings 検出時に exit 2 で 1 度だけ停止をブロック / 再発火は自動回避)
181
+ stop-hook AI assistant の Stop hook 用 internal subcommand(severity=error の findings か、
182
+ 実行に失敗して未検査のルールがあるときに exit 2 で 1 度だけ停止をブロック / 再発火は自動回避。
183
+ warning / info はブロックせず件数の要約を stderr に出す)
149
184
  plugin-spec アンチパターン拡張プラグインの契約仕様を出力(--list で導入済みプラグインを表示)
150
185
 
151
186
  Generate:
@@ -164,6 +199,10 @@ Check:
164
199
  sparkle-design-cli check src --format json
165
200
  sparkle-design-cli check src/components src/features
166
201
 
202
+ Rules:
203
+ sparkle-design-cli rules # 有効なルールを severity 別に一覧表示
204
+ sparkle-design-cli rules --format json # CI / AI 向け
205
+
167
206
  Plugin spec:
168
207
  sparkle-design-cli plugin-spec # アンチパターン拡張プラグインの契約仕様を出力
169
208
  sparkle-design-cli plugin-spec --list # 現在のプロジェクトで発見されたプラグインを表示
@@ -229,7 +268,8 @@ sparkle.config.json の設定フィールド:
229
268
 
230
269
  Check options:
231
270
  -h, --help このヘルプメッセージを表示
232
- --strict 違反があれば exit code 1 で終了
271
+ --strict severity=error の違反があれば exit code 1 で終了
272
+ (warning / info は報告のみで exit code に影響しない)
233
273
  --format <text|json> 出力形式 (default: text)
234
274
 
235
275
  Setup options:
@@ -252,23 +292,12 @@ Setup の動作:
252
292
  4. AI アシスタントのガードブロックを指定ファイルに挿入(存在しなければ新規作成)
253
293
  5. sparkle-design-cli generate を実行し、sparkle-design.css と SparkleHead.tsx を生成
254
294
 
255
- Check で検出する主なパターン:
256
- - フォーム入力に Dialog を使っている
257
- - DialogCancel / DialogAction を <Button> で二重ラップしている
258
- - children なしの Button に prefixIcon / suffixIcon を使っている
259
- - Material Symbols を className 直書きしている
260
- - shadcn/ui 既定 token(text-muted-foreground 等)を持ち込んでいる
261
- - Tailwind デフォルト typography(text-sm, font-medium 等)を使っている
262
- - CardTitle に typography を上書きしている
263
- - CardControl に Button / IconButton 以外を入れている
264
- - CardHeader / CardContent の padding を上書きしている
265
- - asChild と prefixIcon / suffixIcon / isLoading を併用している
266
- - disabled を isDisabled の代わりに使っている
267
- - Button の prefixIcon に JSX を渡している
268
- - Icon の children にテキストを渡している
269
- - <Card> を <button> / <a> / role="button" でラップしている(ClickableCard を使う)
270
- - エントリ CSS にフォント @import が残っている(SparkleHead への移行が必要)
271
- - CSP ヘッダーで Google Fonts がブロックされている可能性
295
+ 検出するルールの一覧:
296
+ sparkle-design-cli rules
297
+
298
+ ここに列挙しないのは、有効なルールがプロジェクトごとに違うため。プラグインは
299
+ コンシューマの package.json から自動発見されるので、固定の一覧はプラグインを
300
+ 入れた利用者にとって最初から不正確になる。
272
301
 
273
302
  JSON 出力では manualReviewReminders も返すため、AI から実行する場合は --format json を推奨
274
303
 
@@ -353,11 +382,14 @@ async function main() {
353
382
  process.exit(0);
354
383
  }
355
384
 
356
- const hasFindings = await checkProject(options.targets, {
385
+ // checkProject の戻り値は「findings があるか」ではなく
386
+ // 「exit code を 1 にすべきか」(error severity か、実行に失敗したルールがあるか)。
387
+ // en: The return value means "should this fail", not "were there findings".
388
+ const hasBlockingIssues = await checkProject(options.targets, {
357
389
  strict: options.strict,
358
390
  format: options.format,
359
391
  });
360
- if (hasFindings && options.strict) {
392
+ if (hasBlockingIssues && options.strict) {
361
393
  process.exit(1);
362
394
  }
363
395
  return;
@@ -385,6 +417,11 @@ async function main() {
385
417
  process.exit(exitCode);
386
418
  }
387
419
 
420
+ if (command === 'rules') {
421
+ await runRules(parseRulesOptions(args.slice(1)));
422
+ return;
423
+ }
424
+
388
425
  if (command === 'plugin-spec') {
389
426
  await runPluginSpec(args.slice(1));
390
427
  return;
@@ -0,0 +1,92 @@
1
+ # アンチパターン検査の詳細
2
+
3
+ `sparkle-design-cli check` が検出する内容の背景と、個別ルールの扱いをまとめています。
4
+
5
+ **現在有効なルールの一覧は `npx sparkle-design-cli rules` で確認してください。** ここには一覧を置きません。プラグインで増減するため、書いた時点で古くなるからです。
6
+
7
+ ## 抑制する
8
+
9
+ 特定の箇所だけ検査から外すには、ESLint と同じ形のコメントを使います。
10
+
11
+ ```tsx
12
+ {/* sparkle-disable-next-line legacy-color-token */}
13
+ <div className="bg-primary-600" />
14
+
15
+ <div className="bg-primary-600" /> {/* sparkle-disable-line legacy-color-token */}
16
+ ```
17
+
18
+ ルール ID はカンマ区切りで複数指定できます。ID は `rules` コマンドの出力と `check` の指摘に出ているものです。
19
+
20
+ ## 新セマンティックトークンへの移行(beta 期間中)
21
+
22
+ 移行ルールは検出するだけでなく、**マッチ 1 件ごとに移行先を提示**します。
23
+
24
+ ```text
25
+ src/Button.tsx:12 [warning] [legacy-color-token] 旧セマンティックカラーのユーティリティクラスを使っています
26
+ Recommendation: [自動変換可] bg-primary-600 → bg-surface-primary-high-enabled
27
+
28
+ src/Card.tsx:8 [warning] [legacy-color-token] 旧セマンティックカラーのユーティリティクラスを使っています
29
+ Recommendation: [要判断] bg-primary-200 の移行先候補: surface-primary-high-disabled / surface-primary-middle-active。…
30
+ ```
31
+
32
+ 新体系は「用途 × 意味 × 強度 × **状態**」で、状態がトークン名に内包されます。そのため**同じ旧クラスでも文脈によって移行先が変わり**、機械的に決まらないものは `[要判断]` として候補の列挙に留めます(推測で置換しません)。
33
+
34
+ ユーティリティ接頭辞がそのまま用途に対応します。
35
+
36
+ | 接頭辞 | 用途 | 例 |
37
+ | -------------------------------------------- | -------------------- | ---------------------------------------------------- |
38
+ | `bg-` | surface(背景) | `bg-primary-600` → `bg-surface-primary-high-enabled` |
39
+ | `text-` | text(文字) | `text-primary-600` → `text-text-primary-enabled` |
40
+ | `border-` / `divide-` / `outline-` / `ring-` | border(枠線) | `border-primary-300` → `border-border-primary-high` |
41
+ | `fill-` / `stroke-` | object(アイコン等) | `fill-primary-600` → `fill-object-primary-enabled` |
42
+
43
+ `secondary` は `neutral` に統合されました。旧トークンは beta 期間中はテンプレートに併存するので、警告が出ていても即座には壊れません。beta 終了時に旧トークンを削除するタイミングで `error` に昇格します。
44
+
45
+ 移行ルールは `.tsx` などのソースに加えて **`.css` も検査します**(`var(--color-ring-normal)` のような CSS 変数の直接参照は `.css` にこそ書かれ、しかも beta 終了で消えたときにビルドエラーにならず見た目だけ壊れるため)。
46
+
47
+ 他のルール(`dialog-form` などの構造ルール)は従来どおりソースファイルのみが対象で、CSS 検査の追加による影響はありません。
48
+
49
+ **報告しないもの**(いずれも「移行が必要な使用箇所」ではないため):
50
+
51
+ - **そのファイル自身が宣言している変数への参照**。CLI が生成した CSS は旧トークンを「定義している側」なので、これで丸ごと除外されます。ファイル名ではなく内容で判定しているため、`generate --scope`(`-o` が必須で出力名が任意)の生成物にも効きます。自前で `:root` に定義を持つプロジェクトの theme ファイルも同じ理由で対象外になります(自分で定義している以上、beta 終了で壊れません)
52
+ - **行頭から始まるブロックコメントの中身**。「旧 `--color-ring-normal` の移行先」のような説明文を拾わないためです。行末コメントは対象外にしていません(任意位置の `/*` をコメント開始とみなすと、`matcher: ['/dashboard/*']` のような文字列リテラルを起点に実コードを黙って検査対象から外してしまうため)。逆に、複数行文字列(テンプレートリテラル)の中に行頭から `/*` を書いた場合は、閉じるまでの範囲が検査されません
53
+
54
+ > 移行ルールはクラス名らしき文字列に対する正規表現マッチのため、`url(/img/fill-base-100.svg)` のようにクラス名でない文字列を拾うことがあります。`warning` 止まりで exit code には影響しませんが、抑制したい箇所には `sparkle-disable-line legacy-color-token` のコメントを使えます。
55
+ >
56
+ > 旧→新の対応表は `lib/token-migration.js` に一元化してあり、`test/token-migration.test.js` が**実際に生成した CSS を読んで**「表に書いた移行先が実在し、旧トークンと同じ実値に解決される」ことを検証しています。表とトークン定義が食い違ったらテストが落ちるので、案内が嘘になりません。検出パターンがマッチする文字列は必ず対応表で解決できることも全数検証しています(片方だけ更新すると「検出したのに黙って捨てる」穴になるため)。
57
+
58
+ ## 余白のスケール(`use-figma-spacing-scale`)
59
+
60
+ Figma の余白は 17 段(0 / 2 / 4 / 6 / 8 / 12 / 16 / 20 / 24 / 32 / 40 / 48 / 56 / 72 / 88 / 104 / 120 px)です。Tailwind の 1 ステップは 4px なので、`p-4` = 16px、`p-6` = 24px が対応します。このスケールから外れた値を `info` で報告し、前後の候補を提示します。
61
+
62
+ ```text
63
+ src/Popover.stories.tsx:194 [info] [use-figma-spacing-scale] Figma の Spacing: Primitives に無い余白ステップを使っています
64
+ Recommendation: [要判断] gap-y-20 は 80px で、Figma の Spacing: Primitives に無い値です。近いステップは gap-y-18(72px) / gap-y-22(88px)。…
65
+ ```
66
+
67
+ **Tailwind の数字と Figma の数字は一致しません。** Figma の `padding/16` は 16px、Tailwind の `p-16` は 64px で 4 倍違います。Figma を見ながら同じ数字を書くと 4 倍の余白になります。
68
+
69
+ | Figma | px | Tailwind |
70
+ | ------------- | ----- | -------- |
71
+ | `padding/8` | 8px | `p-2` |
72
+ | `padding/16` | 16px | `p-4` |
73
+ | `padding/24` | 24px | `p-6` |
74
+ | `padding/120` | 120px | `p-30` |
75
+
76
+ このスケールは **CSS 変数として出力していません**。`--spacing-16` のような名前付きキーを定義すると Tailwind の `p-16` がその値に差し替わり、既存コードの余白がビルドも警告も通ったまま変わってしまうためです。だから「変数を使う」ではなく「使ってよい値を守る」という形の制約になっています(`sparkle-variables` 側でも `--spacing-*` を定義していないことを検査しています)。
77
+
78
+ 対象は `p` / `m` / `gap` / `space` 系の数値ユーティリティで、`.tsx` などのソースに加えて `.css` の `@apply` も見ます。Figma のコレクション名は `padding/*` ですが、`gap-7` のようにスケールから外れた値は padding でなくても同じだけレイアウトを崩すため、margin / gap / space も対象にしています。**これは Figma がそう定義しているのではなく運用上の判断**なので、`severity` は `info` です。
79
+
80
+ 提示する代替は、variant 連鎖・負のマージンの `-`・important の `!` をすべて保ったまま数字だけを差し替えます(`hover:p-7!` → `hover:p-6!` / `hover:p-8!`)。`[&>*]:` のような角括弧だけの arbitrary variant も保持します。案内どおりに書き換えた結果 important が消えていた、という事故を避けるためです。
81
+
82
+ `p-[13px]` のような arbitrary **value** は対象外です(`calc()` など正当な用途と機械的に区別できないため)。
83
+
84
+ Tailwind では `w-*` / `h-*` / `inset-*` / `translate-*` / `scroll-p-*` なども同じ spacing スケールから値を引きますが、**このルールの対象外**です。Figma のコレクションが定義しているのは余白(`padding/*`)で、サイズや位置まで同じ制約に含めると別の判断が混ざるためです。
85
+
86
+ クラス名の直前に `/` が来る文字列(`https://example.com/v1/p-7` や `import x from './gap-7'`)は誤検出を避けるため対象外にしています。
87
+
88
+ デザイン上どうしてもスケール外の値が必要な場合は Figma 側にステップを追加するのが本筋ですが、個別に外すなら `sparkle-disable-next-line use-figma-spacing-scale` を使えます。
89
+
90
+ ---
91
+
92
+ [← README に戻る](../README.md)(docs/anti-patterns.md)
package/docs/config.md ADDED
@@ -0,0 +1,108 @@
1
+ # 設定ファイルの拡張(`extend`)
2
+
3
+ Figma プラグインが出力する基本4項目に加えて、プロジェクト固有の拡張を `extend` セクションにまとめます。`extend` はオブジェクト直書き(推奨)またはファイルパスを指定できます。
4
+
5
+ ```json
6
+ {
7
+ "primary": "blue",
8
+ "font-pro": "Montserrat",
9
+ "font-mono": "Roboto Mono",
10
+ "radius": "md",
11
+ "extend": {
12
+ "fonts": {
13
+ "pro": [
14
+ { "family": "Montserrat", "weights": [500, 600, 700] },
15
+ { "family": "Noto Sans JP", "weights": [400, 500, 600, 700] }
16
+ ],
17
+ "mono": [{ "family": "Roboto Mono", "weights": [400, 700] }]
18
+ },
19
+ "source-packages": ["@goodpatch/sparkle-design-internal"],
20
+ "custom-css": "./src/app/custom-tokens.css"
21
+ }
22
+ }
23
+ ```
24
+
25
+ ファイル参照も可能です:
26
+
27
+ ```json
28
+ {
29
+ "primary": "blue",
30
+ "font-pro": "Montserrat",
31
+ "font-mono": "Roboto Mono",
32
+ "radius": "md",
33
+ "extend": "./sparkle.extend.json"
34
+ }
35
+ ```
36
+
37
+ ## extend.fonts
38
+
39
+ フォントごとにウェイトを個別指定。`extend.fonts` がない場合は `font-pro` / `font-mono` + デフォルトウェイト `[400, 700]` が使われます。`extend.fonts` が存在しても `fonts.pro` / `fonts.mono` のどちらかが未指定なら、そのスロットのみ `font-pro` / `font-mono` にフォールバックします。同じフォントファミリーが `pro` と `mono` で重複する場合、ウェイトはマージされ import は 1 行に統合されます。
40
+
41
+ ## extend.source-packages
42
+
43
+ 既知のデザインシステムパッケージ(`sparkle-design` など)は `package.json` から**自動検出**されるので、通常は指定不要です。クライアント固有のパッケージなど自動検出の対象外を追加したいときだけ指定してください。指定分は自動検出分とマージされ、Tailwind エントリ CSS(自動検出)に `@source` ディレクティブとして挿入されます。両方とも空のときだけ `@source` を出力しません。
44
+
45
+ ## extend.custom-css
46
+
47
+ プロジェクト固有のカスタムトークンの CSS ファイルパス。Tailwind エントリ CSS に `@import` が自動挿入されます。`sparkle-design.css` に直接追加すると `generate` 実行時に上書きされるため、必ず別ファイルに分離してください。
48
+
49
+ この `@import` は `sparkle-design.css` の直後に挿入されるため、`custom-css` 側で書いた `:root { --color-xxx: ... }` は CSS の cascade(後勝ち)でそのまま `sparkle-design.css` のトークンを上書きします。属性セレクタや `!important` を使う必要はありません。
50
+
51
+ ### カスタムブランドカラーを primary にしたい場合
52
+
53
+ `primary` は 7 色のいずれかしか受け付けません(前述の Core セクション参照)。7 色にないブランドカラーを使いたい場合は、`primary` は 7 色から見た目が近いものを仮に選んだ上で、`extend.custom-css` で `--color-primary-*` と `--color-gray-*` をブランドカラー基準の値で丸ごと再定義してください。`primary` の選択自体は `custom-css` 側の定義で完全に上書きされるため実質的な意味を持たなくなりますが、フィールドとしては有効な値を入れておく必要があります。
54
+
55
+ ```css
56
+ /* custom-tokens.css */
57
+ :root {
58
+ /* ブランドカラー基準の primary パレット(50〜900) */
59
+ --color-primary-50: oklch(97% 0.02 250);
60
+ /* ... */
61
+ --color-primary-500: oklch(55% 0.18 250);
62
+ /* ... */
63
+ --color-primary-900: oklch(20% 0.08 250);
64
+
65
+ /* primary に合わせた gray パレット(50〜900)も必ず一緒に定義する。
66
+ gray だけ 7 色デフォルトのままだと、primary だけ浮いて見えるトーンずれが起きる */
67
+ --color-gray-50: oklch(98% 0.005 250);
68
+ /* ... */
69
+ --color-gray-900: oklch(22% 0.01 250);
70
+ }
71
+ ```
72
+
73
+ `primary` だけを差し替えて `gray` を既定のままにすると、コンポーネントの枠線・背景・テキストに使われる gray 系トークンと primary のトーンが揃わなくなるため、gray も必ずセットで再定義してください。
74
+
75
+ ### `character-*` に無いウェイト(SemiBold 等)を使いたい場合
76
+
77
+ `character-*` utility の `font-weight` は Regular(400) / Bold(700) の 2 値のみで、SemiBold(600) や Medium(500) に対応する `character-*` は存在しません(Sparkle Design のプリミティブトークンである `fontWeights.text.regular` / `fontWeights.text.bold` がこの 2 値のみを持つため)。
78
+
79
+ 日本語見出しでは Bold(700) が視覚的に重すぎると判断して SemiBold(600) を使いたい、といったプロジェクト固有のウェイト方針がある場合は、`character-*` に SemiBold バリアントの追加を待つのではなく、`extend.custom-css` で独自のユーティリティクラスを定義してください。**`className="character-3-regular-pro font-semibold"` のように Tailwind の `font-semibold` を併用する方法は機能しません**。`sparkle-design.css` は Tailwind の後・`@layer utilities` 内に読み込まれるよう意図的に設計されており(同一詳細度なら後着のルールが勝つ cascade layers の仕様上)、`character-*` 側の `font-weight` が常に優先されるためです。
80
+
81
+ ```css
82
+ /* custom-typography.css */
83
+ @layer utilities {
84
+ /* character-3(16px/24px)の SemiBold バリアント。
85
+ font-family / font-size / letter-spacing / line-height は
86
+ character-* と同じプリミティブトークンをそのまま流用し、
87
+ font-weight だけプロジェクト独自の 600 に差し替える */
88
+ .character-3-semibold-pro {
89
+ font-family: var(--font-family-pro);
90
+ font-size: var(--font-size-16);
91
+ font-weight: 600;
92
+ letter-spacing: var(--letter-spacing-wider);
93
+ line-height: var(--line-height-24);
94
+ }
95
+ }
96
+ ```
97
+
98
+ `character-*-semibold-*` は既存の `character-*-regular-*` / `character-*-bold-*` と別名のクラスなので、cascade の競合は起きません。`extend.custom-css` は `sparkle-design.css` の直後に `@import` されるため、上記のように新規クラスを追記するだけで有効になります(上書きではないので `!important` や記述順の工夫も不要です)。
99
+
100
+ 使用するフォントファミリーが実際に 600 ウェイトのファイルを読み込んでいるかも確認してください。`extend.fonts.pro` / `extend.fonts.mono` の `weights` に `600` を含めていないと、`font-weight: 600` を指定してもブラウザによる疑似太字(faux bold)にフォールバックし、正しい SemiBold の字形になりません。
101
+
102
+ 将来 Sparkle Design 本体が正式に `character-N-semibold-*` token を追加した場合は、cascade 上は consumer 側の `custom-css` 定義が勝ち続けるため、その時点で自前定義は削除して本体の token に乗り換えてください(放置すると Sparkle 側の値が更新されても気づかず古い定義が使われ続けます)。
103
+
104
+ なお `check` の `tailwind-typography` ルールは `font-semibold` / `font-medium` を検出対象に含めていますが、`character-*-semibold-*` のような独自クラスに置き換えた行は検出パターンに一致しないため、`sparkle-disable-line` を付けなくても違反として検出されなくなります。
105
+
106
+ ---
107
+
108
+ [← README に戻る](../README.md)(docs/config.md)
@@ -0,0 +1,117 @@
1
+ # 手動セットアップ(Next.js / Vite 以外のプロジェクト向け)
2
+
3
+ `setup` コマンドは **Next.js App Router** と **Vite** を前提に scaffold / entry CSS の配置を自動判定します。Remix / Astro / TanStack Start など他のフレームワークや、独自ディレクトリ構成のプロジェクトでは自動判定が外れるため、以下の手順で手動セットアップすることを推奨します。
4
+
5
+ ## 手順
6
+
7
+ **1. パッケージをインストール**
8
+
9
+ ```bash
10
+ # 本体
11
+ pnpm add sparkle-design # or npm install / yarn add / bun add
12
+
13
+ # Tailwind v4
14
+ pnpm add -D tailwindcss @tailwindcss/postcss
15
+ ```
16
+
17
+ **2. `sparkle.config.json` をプロジェクトルートに作成**
18
+
19
+ ```json
20
+ {
21
+ "primary": "blue",
22
+ "font-pro": "Inter",
23
+ "font-mono": "JetBrains Mono",
24
+ "radius": "md"
25
+ }
26
+ ```
27
+
28
+ 選択肢の詳細は [README の「設定オプション」](../README.md#設定オプション)、`extend` の各項目は [docs/config.md](config.md) を参照してください。
29
+
30
+ **3. `postcss.config.mjs` をプロジェクトルートに作成**
31
+
32
+ ```js
33
+ export default {
34
+ plugins: {
35
+ '@tailwindcss/postcss': {},
36
+ },
37
+ };
38
+ ```
39
+
40
+ **4. Tailwind エントリ CSS を自前で用意**
41
+
42
+ 既存プロジェクトに組み込む場合、`@import "tailwindcss";` を含む CSS ファイル(例: `src/styles/app.css`)があればそれを使います。無ければ作成してアプリケーションのエントリから import します。
43
+
44
+ **5. `generate` を実行**
45
+
46
+ ```bash
47
+ npx --yes sparkle-design-cli generate
48
+ ```
49
+
50
+ これで `sparkle-design.css` と `SparkleHead.tsx` が生成されます。**出力先はエントリ CSS の場所で変わります** — `extend.globals-path`(または `--globals-path`)を指定していればそのディレクトリ、未指定なら既定の `src/app/` です。上の例のように `src/styles/app.css` を使う場合は、この手順の前に `extend.globals-path` を指定してください(指定しないと `src/app/` に出ます)。`-o/--output` で直接指定することもできます。
51
+
52
+ ```json
53
+ {
54
+ "primary": "blue",
55
+ "extend": {
56
+ "globals-path": "src/styles/app.css"
57
+ }
58
+ }
59
+ ```
60
+
61
+ **6. フォントの `<link>` タグを手動で配置**
62
+
63
+ Next.js / Vite 以外ではアプリケーションフレームワークごとに「`<head>` 相当」の配置方法が異なります。自動生成される `SparkleHead.tsx` はそのまま使えるはずですが、使えない場合は中身(`preconnect` + Google Fonts の `<link>` タグ群)を自前のレイアウトにコピーしてください。
64
+
65
+ たとえば Astro なら `src/layouts/BaseLayout.astro` の `<head>` に直接書きます:
66
+
67
+ ```html
68
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
69
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
70
+ <link
71
+ rel="stylesheet"
72
+ href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:FILL,wght@0..1,500&display=block"
73
+ />
74
+ <!-- sparkle.config.json の font-pro / font-mono に合わせた Google Fonts URL -->
75
+ <link
76
+ rel="stylesheet"
77
+ href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&display=swap"
78
+ />
79
+ <link
80
+ rel="stylesheet"
81
+ href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;700&display=swap"
82
+ />
83
+ ```
84
+
85
+ **7. アンチパターン検査を package.json に追加(任意)**
86
+
87
+ ```json
88
+ {
89
+ "scripts": {
90
+ "lint:sparkle": "npx --yes sparkle-design-cli check src --strict",
91
+ "lint:sparkle:json": "npx --yes sparkle-design-cli check src --format json"
92
+ }
93
+ }
94
+ ```
95
+
96
+ **8. AI エージェントを使うなら Guard ブロックと hook を手動で配置**
97
+
98
+ AI ガード(`CLAUDE.md` / `AGENTS.md`)と hook 設定(`.claude/settings.json` / `.cursor/hooks.json` / `.codex/hooks.json`)は `setup` に任せるのが最も楽です。Cursor 用 Guard は rc.4 から AGENTS.md に統一しました(`.cursor/rules/*.mdc` は条件付き読み込みのため)。
99
+
100
+ ```bash
101
+ # ガード追加・hook 設定のみ(パッケージインストールや scaffold はスキップ)
102
+ npx --yes sparkle-design-cli setup --assistant claude --skip-install --skip-scaffold --skip-generate
103
+ ```
104
+
105
+ この `--skip-*` 組み合わせは手動セットアップ済みのプロジェクトに対して Guard + hook だけ差し込むのに使えます。
106
+
107
+ ## 既知の未対応ケース
108
+
109
+ - **モノレポ(workspaces)**: `generate` は実行した cwd からパッケージマネージャーを遡及検出しないため、サブパッケージで直接実行するのが確実です。
110
+ - **CSS 以外のエントリ(CSS-in-JS / vanilla-extract 等)**: Sparkle Design は Tailwind v4 の `@source` + CSS variable 前提で作られているため、ビルドパイプラインの外で CSS を扱うソリューションは対象外です。
111
+ - **Tailwind v3**: `@source` ディレクティブ依存のため v4 必須です。
112
+
113
+ Next.js / Vite 以外で導入したい方で困ったときは Issue で教えていただけると助かります。
114
+
115
+ ---
116
+
117
+ [← README に戻る](../README.md)(docs/manual-setup.md)
@@ -0,0 +1,120 @@
1
+ # アンチパターン検知の拡張(プラグイン)
2
+
3
+ 他のコンポーネントライブラリ(社内拡張など)に固有のアンチパターン検知を、`sparkle-design-cli` 本体に変更を加えず追加できる拡張機構があります。
4
+
5
+ ## 仕組み
6
+
7
+ プラグインを提供するパッケージは、自身の `package.json` に検知ルールのエントリを宣言します:
8
+
9
+ ```json
10
+ {
11
+ "name": "@your-org/your-design-extensions",
12
+ "sparkleCli": {
13
+ "antiPatterns": "./anti-patterns/index.js"
14
+ }
15
+ }
16
+ ```
17
+
18
+ エントリファイルは `defineAntiPatternPlugin` を使ってプラグインを default export します:
19
+
20
+ ```js
21
+ import { defineAntiPatternPlugin } from 'sparkle-design-cli/plugin';
22
+
23
+ export default defineAntiPatternPlugin({
24
+ groups: [
25
+ {
26
+ id: 'your-org-foocard-nesting',
27
+ check: {
28
+ // 省略時は 'error'(--strict で exit 1)。'warning' / 'info' は報告のみ。
29
+ severity: 'error',
30
+ // 省略時は ['source'](.js/.jsx/.ts/.tsx のみ)。CSS も見たいなら 'css' を足す。
31
+ targets: ['source'],
32
+ description: 'FooCard を別の FooCard でラップしないでください。',
33
+ recommendation: '入れ子表示が必要なら FooStack を使ってください。',
34
+ pattern: /<FooCard[^>]*>[\s\S]*?<FooCard/g,
35
+ },
36
+ },
37
+ ],
38
+ manualReviewReminders: [
39
+ {
40
+ id: 'your-org-foocard-color',
41
+ message: 'FooCard の color token が brand に揃っているか確認してください。',
42
+ },
43
+ ],
44
+ });
45
+ ```
46
+
47
+ `severity` を省略すると `error` になります(severity 導入前と同じ挙動)。未知の値を渡した場合は警告を出して `error` にフォールバックします — タイポで意図せず CI をすり抜ける/落とすのを防ぐためです。
48
+
49
+ ## 複雑な検出(`match` とヘルパー)
50
+
51
+ 単純な正規表現では安全に書けない検出(accessible name の有無、prop の組み合わせ、JSX 式を意識した走査など)には、`check.pattern` の代わりに `check.match` を使います。`match` は `(content, helpers)` で呼ばれ、第 2 引数の `helpers` に JSX パースヘルパーが **CLI から注入** されます。各パッケージがパース処理を再実装して同じ false-positive / negative を踏むのを防ぐ仕組みです。
52
+
53
+ `match` が返す要素に `recommendation` を含めると、**マッチ 1 件ごとに違う推奨**を出せます(省略時はルール既定の `check.recommendation`)。移行ルールのように「置換先が箇所ごとに変わる」検出で使います。
54
+
55
+ ```js
56
+ // sparkle-design-cli を import しない(plain object を default export)
57
+ export default {
58
+ groups: [
59
+ {
60
+ id: 'your-org-avatar-accessible-name',
61
+ check: {
62
+ description: 'Avatar に accessible name がありません。',
63
+ recommendation: 'aria-label か alt を指定してください。',
64
+ // helpers = { matchOpeningTags, hasProp, isMultipleTypeProp, findOpeningTagEnd }
65
+ match: (content, { matchOpeningTags, hasProp }) =>
66
+ matchOpeningTags(
67
+ content,
68
+ 'Avatar',
69
+ (props) =>
70
+ !hasProp(props, 'src') &&
71
+ !hasProp(props, 'aria-label') &&
72
+ !hasProp(props, 'aria-labelledby')
73
+ ),
74
+ },
75
+ },
76
+ ],
77
+ };
78
+ ```
79
+
80
+ 注入されるヘルパー(`check.match` の第 2 引数):
81
+
82
+ | ヘルパー | 用途 |
83
+ | ----------------------------------------------- | ------------------------------------------------------------------------------------ |
84
+ | `matchOpeningTags(content, tagName, predicate)` | `<Tag ...>` / `<Tag ... />` を走査し predicate が真のものを `{ index, text }` で返す |
85
+ | `hasProp(propsBlock, propName)` | prop が指定されているかを厳密判定(`aria-*` / `data-*` を誤検出しない) |
86
+ | `isMultipleTypeProp(propsBlock)` | `type` が静的に `"multiple"` か判定(動的式は `false`) |
87
+ | `findOpeningTagEnd(content, startIdx)` | 低レベル: 開きタグの閉じ `>` のオフセット(文字列 / JSX 式を考慮)。無ければ `-1` |
88
+
89
+ > 💡 `match` を使うプラグインは(上の `pattern` 例の `defineAntiPatternPlugin` import と違い)`sparkle-design-cli` を **import せず** plain object として default export してください。`helpers` はランタイムで CLI が注入するため import は不要で、生 JS のまま配布され npx 経由で実行される consumer 環境でも確実に解決されます。`pattern` と `match` は排他で、どちらか一方のみ指定します。
90
+
91
+ ## 自動 discovery
92
+
93
+ `sparkle-design-cli check` 実行時、CLI は consumer プロジェクトの `package.json` の `dependencies` / `devDependencies` / `peerDependencies` / `optionalDependencies` を走査し、`sparkleCli.antiPatterns` を宣言しているパッケージを発見次第ロードしてビルトインのルールにマージします。
94
+
95
+ - プラグインパッケージが install されていなければ何も起きません
96
+ - ロードや評価で失敗したプラグインは warn を出してスキップし、残りのルールで check は継続されます
97
+ - ルール ID はビルトイン・他プラグインと名前空間を共有するため、`your-org-` のようなプレフィックスを付けて衝突を避けてください
98
+
99
+ > ⚠️ **信頼境界**: プラグインのエントリファイルは `import()` で **任意の JavaScript を実行** します。これは Node.js の通常のパッケージ依存と同じ性質ですが、`sparkle-design-cli check` 実行時に走るコードが増えるという点で意識しておく必要があります。プラグインは信頼できる org / 著者のパッケージのみインストールしてください。
100
+
101
+ ## 契約仕様の確認
102
+
103
+ 最新の契約仕様は CLI から直接出力できます:
104
+
105
+ ```bash
106
+ # 契約 shape のドキュメントを出力
107
+ npx --yes sparkle-design-cli plugin-spec
108
+
109
+ # 現在のプロジェクトで discover されたプラグインを一覧
110
+ npx --yes sparkle-design-cli plugin-spec --list
111
+ ```
112
+
113
+ 完全な型定義は `node_modules/sparkle-design-cli/lib/plugin-api.js` の JSDoc を参照してください。
114
+
115
+ > [!TIP]
116
+ > 契約仕様の最新版は `npx sparkle-design-cli plugin-spec` が出力します。導入済みプラグインは `plugin-spec --list`、そのプラグインが実際に追加したルールは `rules` で確認できます。
117
+
118
+ ---
119
+
120
+ [← README に戻る](../README.md)(docs/plugins.md)