mikeneko-ui 1.0.1 → 1.2.0

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/README.md CHANGED
@@ -104,7 +104,7 @@ AI ツール(Claude Code, Cursor 等)が以下のツールを使える:
104
104
  ### Typography
105
105
 
106
106
  ```
107
- Font: Inter, Hiragino Sans, Hiragino Kaku Gothic ProN, Noto Sans JP, sans-serif
107
+ Font: Noto Sans JP, sans-serif
108
108
  Body: 18px / line-height 2.0 / letter-spacing 0.02em
109
109
  Heading: line-height 1.4 / letter-spacing 0.01em
110
110
  ```
package/dist/mcp.js CHANGED
@@ -27,7 +27,24 @@ export async function startMcp() {
27
27
  return null;
28
28
  return readFileSync(full, "utf-8");
29
29
  }
30
- const server = new Server({ name: "mikeneko-ui-mcp", version: "1.0.0" }, { capabilities: { tools: {} } });
30
+ /**
31
+ * Load prohibition rules from rules.json (SSoT) and flatten into patterns.
32
+ * Falls back to bundled template.
33
+ */
34
+ function loadProhibitionRules() {
35
+ const rulesData = loadJSON("metadata/rules.json") ?? loadJSON(resolve(TEMPLATES, "rules.json"));
36
+ if (!rulesData)
37
+ return [];
38
+ const flat = [];
39
+ for (const rule of rulesData.rules) {
40
+ const patterns = rule.patterns ?? (rule.pattern ? [rule.pattern] : []);
41
+ for (const p of patterns) {
42
+ flat.push({ pattern: p, reason: rule.reason, alternative: rule.alternative });
43
+ }
44
+ }
45
+ return flat;
46
+ }
47
+ const server = new Server({ name: "mikeneko-ui-cli-mcp", version: "2.0.0" }, { capabilities: { tools: {} } });
31
48
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
32
49
  tools: [
33
50
  {
@@ -57,7 +74,7 @@ export async function startMcp() {
57
74
  },
58
75
  {
59
76
  name: "get_prohibited",
60
- description: "mikeneko UI 禁止パターンを取得。",
77
+ description: "mikeneko UI 禁止パターンを取得(rules.json SSoT対応)。",
61
78
  inputSchema: {
62
79
  type: "object",
63
80
  properties: {
@@ -65,6 +82,20 @@ export async function startMcp() {
65
82
  },
66
83
  },
67
84
  },
85
+ {
86
+ name: "check_rule",
87
+ description: "Tailwind クラスを禁止ルールに照合。違反があれば理由と代替を返す。",
88
+ inputSchema: {
89
+ type: "object",
90
+ properties: {
91
+ classes: {
92
+ type: "string",
93
+ description: "スペース区切りの Tailwind クラス (例: 'text-black shadow-lg')",
94
+ },
95
+ },
96
+ required: ["classes"],
97
+ },
98
+ },
68
99
  {
69
100
  name: "get_quick_reference",
70
101
  description: "CLAUDE.md (AI Quick Reference) を返す。",
@@ -73,6 +104,20 @@ export async function startMcp() {
73
104
  properties: {},
74
105
  },
75
106
  },
107
+ {
108
+ name: "search",
109
+ description: "トークンとコンポーネントをキーワード横断検索。",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {
113
+ query: {
114
+ type: "string",
115
+ description: "検索キーワード",
116
+ },
117
+ },
118
+ required: ["query"],
119
+ },
120
+ },
76
121
  ],
77
122
  }));
78
123
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -81,7 +126,6 @@ export async function startMcp() {
81
126
  const text = (s) => ({ content: [{ type: "text", text: s }] });
82
127
  switch (name) {
83
128
  case "get_token": {
84
- // Try project-local tokens first, then bundled template
85
129
  const tokens = loadJSON("tokens/tokens.json") ?? loadJSON(resolve(TEMPLATES, "tokens.json"));
86
130
  if (!tokens)
87
131
  return text("tokens.json not found");
@@ -100,6 +144,15 @@ export async function startMcp() {
100
144
  return comp ? text(JSON.stringify(comp, null, 2)) : text(`Not found: ${a.name}`);
101
145
  }
102
146
  case "get_prohibited": {
147
+ // Try rules.json first (SSoT), fall back to prohibited.md
148
+ const rulesData = loadJSON("metadata/rules.json") ?? loadJSON(resolve(TEMPLATES, "rules.json"));
149
+ if (rulesData) {
150
+ if (!a.section)
151
+ return text(JSON.stringify(rulesData, null, 2));
152
+ const filtered = rulesData.rules.filter((r) => r.category.toLowerCase().includes(a.section.toLowerCase()));
153
+ return text(JSON.stringify(filtered, null, 2));
154
+ }
155
+ // Fallback to prohibited.md
103
156
  const content = loadFile("foundations/prohibited.md") ?? loadTemplate("prohibited.md");
104
157
  if (!content)
105
158
  return text("prohibited.md not found");
@@ -109,15 +162,46 @@ export async function startMcp() {
109
162
  const match = content.match(regex);
110
163
  return text(match ? match[1].trim() : `Section "${a.section}" not found`);
111
164
  }
165
+ case "check_rule": {
166
+ const rules = loadProhibitionRules();
167
+ const classList = a.classes.split(/\s+/).filter(Boolean);
168
+ const violations = [];
169
+ for (const cls of classList) {
170
+ for (const rule of rules) {
171
+ if (cls.includes(rule.pattern)) {
172
+ violations.push({ class: cls, reason: rule.reason, alternative: rule.alternative });
173
+ }
174
+ }
175
+ }
176
+ return violations.length === 0
177
+ ? text("✅ No violations found.")
178
+ : text(JSON.stringify({ violationCount: violations.length, violations }, null, 2));
179
+ }
112
180
  case "get_quick_reference": {
113
181
  const content = loadFile("CLAUDE.md") ?? loadTemplate("CLAUDE.md");
114
182
  return text(content ?? "CLAUDE.md not found");
115
183
  }
184
+ case "search": {
185
+ const tokens = loadJSON("tokens/tokens.json") ?? loadJSON(resolve(TEMPLATES, "tokens.json"));
186
+ const components = loadJSON("metadata/components.json") ?? loadJSON(resolve(TEMPLATES, "components.json"));
187
+ const q = a.query.toLowerCase();
188
+ const results = [];
189
+ // Search components
190
+ if (components) {
191
+ for (const comp of components.components) {
192
+ const searchable = [comp.id, comp.name, comp.description, comp.category].join(" ").toLowerCase();
193
+ if (searchable.includes(q)) {
194
+ results.push({ type: "component", id: comp.id, name: comp.name, data: comp });
195
+ }
196
+ }
197
+ }
198
+ return text(JSON.stringify(results, null, 2));
199
+ }
116
200
  default:
117
201
  return text(`Unknown tool: ${name}`);
118
202
  }
119
203
  });
120
204
  const transport = new StdioServerTransport();
121
205
  await server.connect(transport);
122
- console.error("mikeneko UI MCP Server running on stdio");
206
+ console.error("mikeneko UI CLI MCP Server v2.0.0 running on stdio");
123
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mikeneko-ui",
3
- "version": "1.0.1",
3
+ "version": "1.2.0",
4
4
  "description": "mikeneko UI — AI-ready design system CLI. Injects theme + rules into your React/Next.js project.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,4 @@
1
- # mikeneko UI — AI Quick Reference
1
+ # melta UI — AI Quick Reference
2
2
 
3
3
  > ShadCN/ui ベースのデザインシステム。Primary: Blue (#2b70ef)、日本語ファーストのタイポグラフィ。
4
4
 
@@ -17,7 +17,7 @@ Danger: #ef4444
17
17
 
18
18
  ### Typography
19
19
  ```
20
- Font: Inter, Hiragino Sans, Hiragino Kaku Gothic ProN, Noto Sans JP, sans-serif
20
+ Font: Noto Sans JP, sans-serif
21
21
  H1: 32px/1.4 bold (text-3xl)
22
22
  H2: 26px/1.4 bold (text-2xl)
23
23
  H3: 22px/1.4 semibold (text-xl)
@@ -41,13 +41,14 @@ Easing: ease-in-out (default)
41
41
 
42
42
  ---
43
43
 
44
- ## 設計原則(5つ)
44
+ ## 設計原則(6つ)
45
45
 
46
46
  1. **Layered** — Background → Surface → Text/Object の3層でUIを構成する
47
47
  2. **Contrast** — テキストは背景に対してWCAG 2.1準拠(4.5:1以上)
48
48
  3. **Semantic** — 色は用途で指定する(`bg-primary` ≠ 生の `bg-blue-500`)
49
49
  4. **Minimal** — 1つのViewに使う色は3色まで(背景・アクセント・テキスト)
50
50
  5. **Grid** — スペーシングは4の倍数を基本、8の倍数を推奨する
51
+ 6. **State-Complete** — 新規コンポーネントは全状態(default / hover / focus / active / disabled / error / loading 等)を網羅して作る
51
52
 
52
53
  ---
53
54
 
@@ -149,15 +150,48 @@ Easing: ease-in-out (default)
149
150
  | 色だけで情報伝達 | アイコン/テキストを併用 |
150
151
  | 300ms超のアニメーション | 150〜300ms に制限 |
151
152
  | `<th>` の `scope` 省略 | Table コンポーネント使用 |
153
+ | **Button** の右寄せ(`ml-auto` / `justify-end` / `text-right` 等)※ Button 以外の要素には適用しない。カードヘッダーの `justify-between` 等は許可 | 左寄せ(デフォルト)または中央寄せ |
154
+ | アイコンをテキストの **後** に配置(シェブロン・外部リンク等の Trailing 許可リスト以外) | アイコンはテキストの **前(Leading)** に配置する |
155
+ | `flex-row-reverse` でアイコン位置制御 | DOM順 = 視覚順にする |
156
+ | 定義外アイコンサイズ(`w-3 h-3` / `w-7 h-7` 等) | 4段階のみ: 16/20/24/32px |
152
157
 
153
- > 全禁止パターン(76項目): `foundations/prohibited.md` 参照
158
+ > 全禁止パターン: `foundations/prohibited.md` 参照
159
+ > アイコン配置ルール詳細: `foundations/icons.md` 参照
160
+
161
+ ---
162
+
163
+ ## HTML要素 → コンポーネント マッピング(必須)
164
+
165
+ > 素のHTML要素を使わず、必ず対応する shadcn/ui コンポーネントを使用すること。
166
+ > `components/ui/` 内のコンポーネント実装は例外。
167
+ > ESLint: `eslint-plugin-melta` の `melta/no-raw-html-elements` ルールで自動検出。
168
+
169
+ | HTML要素 | melta UI コンポーネント | インポート元 |
170
+ |----------|----------------------|-------------|
171
+ | `<button>` | `<Button>` | `@/components/ui/button` |
172
+ | `<input>` | `<Input>` | `@/components/ui/input` |
173
+ | `<input type="checkbox">` | `<Checkbox>` | `@/components/ui/checkbox` |
174
+ | `<input type="radio">` | `<RadioGroupItem>` | `@/components/ui/radio-group` |
175
+ | `<textarea>` | `<Textarea>` | `@/components/ui/textarea` |
176
+ | `<select>` | `<Select>` + `<SelectTrigger>` + `<SelectContent>` | `@/components/ui/select` |
177
+ | `<option>` | `<SelectItem>` | `@/components/ui/select` |
178
+ | `<label>` | `<Label>` | `@/components/ui/label` |
179
+ | `<table>` | `<Table>` | `@/components/ui/table` |
180
+ | `<thead>` | `<TableHeader>` | `@/components/ui/table` |
181
+ | `<tbody>` | `<TableBody>` | `@/components/ui/table` |
182
+ | `<tr>` | `<TableRow>` | `@/components/ui/table` |
183
+ | `<th>` | `<TableHead>` | `@/components/ui/table` |
184
+ | `<td>` | `<TableCell>` | `@/components/ui/table` |
185
+ | `<dialog>` | `<Dialog>` | `@/components/ui/dialog` |
186
+ | `<progress>` | `<Progress>` | `@/components/ui/progress` |
187
+ | `<hr>` | `<Separator>` | `@/components/ui/separator` |
154
188
 
155
189
  ---
156
190
 
157
191
  ## ファイル構成
158
192
 
159
193
  ```
160
- mikeneko-ui/
194
+ melta-ui/
161
195
  ├── CLAUDE.md ← このファイル (AI Quick Reference)
162
196
  ├── tokens/
163
197
  │ └── tokens.json ← デザイントークン (SSOT)
@@ -181,6 +215,8 @@ mikeneko-ui/
181
215
  │ └── mcp/ ← MCP Server
182
216
  │ ├── src/index.ts
183
217
  │ └── dist/index.js
218
+ ├── packages/
219
+ │ └── eslint-plugin-melta/ ← 素のHTML要素検出 ESLint プラグイン
184
220
  ├── app/ ← Next.js + shadcn/ui 実装
185
221
  │ ├── src/
186
222
  │ │ ├── app/globals.css ← テーマ (CSS変数)
@@ -207,7 +243,7 @@ mikeneko-ui/
207
243
  ```json
208
244
  {
209
245
  "mcpServers": {
210
- "mikeneko-ui": {
246
+ "melta-ui": {
211
247
  "command": "node",
212
248
  "args": ["./ai/mcp/dist/index.js"]
213
249
  }
@@ -226,7 +262,7 @@ mikeneko-ui/
226
262
  | ダークモード対応 | + foundations/theme.md → foundations/color.md |
227
263
  | フォーム画面 | + patterns/form.md → Input / Select / Checkbox / Button |
228
264
  | データ一覧 | + Table → Pagination → Badge |
229
- | ダッシュボード | + Card / Table / Progress / Chart / Badge |
265
+ | ダッシュボード | + **patterns/dashboard.md** → Card / Table / Progress / Chart / Badge |
230
266
  | 設定画面 | + Tabs → Switch / Select / RadioGroup |
231
267
  | モーダル / 確認 | + Dialog / AlertDialog → Button |
232
268
  | Loading / 空状態 | + Skeleton → interaction-states.md |
@@ -79,6 +79,8 @@
79
79
  |------|------|------|
80
80
  | カード上部/左部のカラーバー(`border-t-4` や色付き `div`) | AI生成UIの典型パターン。装飾過剰で汎用性が低い | ボーダー(`border border-slate-200`)のみでカードを構成する |
81
81
  | 左端/上端のカラーストライプ(`border-l-4 border-*-500`) | Alert含め全コンポーネントで禁止 | `border border-*-200 rounded-lg` で全周ボーダー |
82
+ | アイコンをタイトル/ラベルの **後(右側)** に配置 | AI が "Content First" を「テキストを先に書く」と解釈し、アイコンがタイトルの反対側に出る | アイコンはテキストの **前(Leading)** に配置。Trailing 許可はシェブロン・外部リンク等に限定(`foundations/icons.md` 参照) |
83
+ | `flex-row-reverse` でアイコン位置を調整 | DOM順と視覚順が逆転し、スクリーンリーダーの読み上げ順序が壊れる | DOM順をそのまま視覚順にする |
82
84
 
83
85
  ---
84
86
 
@@ -92,6 +94,9 @@
92
94
  | Lighted Buttonの単独使用 | トグル状態の対比がないと意味不明 | Neutralとペアで使用する |
93
95
  | `aria-label` なしのアイコンボタン | 操作内容がスクリーンリーダーに伝わらない | `aria-label="閉じる"` 等を付与 |
94
96
  | 44px未満のタップ領域 | 操作困難(インラインテキストリンクを除く) | パディングでタップ領域を確保 |
97
+ | ボタンの右寄せ配置(`ml-auto` / `justify-end` / `text-right` / `self-end` / `float-right`) | 右端のボタンは視線動線(左→右の Z パターン)から外れやすく、特にモバイルでは親指が届きにくい。また右寄せは「補助的な操作」という暗黙の意味を持ち、主要アクションの発見性が低下する | ボタンは左寄せ(デフォルト)または中央寄せで配置する。フォーム送信ボタンはフォーム幅に合わせて `w-full` またはコンテンツ左寄せ |
98
+
99
+ > **注意**: この禁止は `<Button>` コンポーネントの配置のみが対象。カードヘッダーでタイトルとアイコンを `justify-between` で左右に配置するなど、ボタン以外の要素のレイアウトには適用しない。
95
100
 
96
101
  ### フォーム
97
102
 
@@ -185,6 +190,29 @@
185
190
 
186
191
  ---
187
192
 
193
+ ## 素のHTML要素の使用禁止
194
+
195
+ > shadcn/ui コンポーネントが存在するHTML要素を素で使うことを禁止する。素のHTML要素はデザイントークン・アクセシビリティ・一貫したスタイリングが適用されず、melta UI の品質基準を満たせない。
196
+
197
+ | 禁止 | 理由 | 代替 |
198
+ |------|------|------|
199
+ | `<button>` | DSのスタイル・バリアント・サイズが適用されない | `<Button>` (`@/components/ui/button`) |
200
+ | `<input>` | ボーダー・フォーカスリング・エラー状態が未適用 | `<Input>` (`@/components/ui/input`) |
201
+ | `<input type="checkbox">` | チェックボックスのスタイル・アニメーションが未適用 | `<Checkbox>` (`@/components/ui/checkbox`) |
202
+ | `<input type="radio">` | ラジオボタンのスタイル・グループ制御が未適用 | `<RadioGroupItem>` (`@/components/ui/radio-group`) |
203
+ | `<textarea>` | フォーム入力のスタイル統一が崩れる | `<Textarea>` (`@/components/ui/textarea`) |
204
+ | `<select>` / `<option>` | ブラウザネイティブUIでスタイル制御不可 | `<Select>` + `<SelectItem>` (`@/components/ui/select`) |
205
+ | `<label>` | フォームラベルのスタイル・間隔が未適用 | `<Label>` (`@/components/ui/label`) |
206
+ | `<table>` / `<thead>` / `<tbody>` / `<tr>` / `<th>` / `<td>` | テーブルのスタイル・レスポンシブ対応が未適用 | `<Table>` / `<TableHeader>` / `<TableBody>` / `<TableRow>` / `<TableHead>` / `<TableCell>` (`@/components/ui/table`) |
207
+ | `<dialog>` | フォーカストラップ・オーバーレイ・アニメーションが未適用 | `<Dialog>` (`@/components/ui/dialog`) |
208
+ | `<progress>` | プログレスバーのスタイル・アニメーションが未適用 | `<Progress>` (`@/components/ui/progress`) |
209
+ | `<hr>` | 区切り線のスタイル・セマンティクスが未適用 | `<Separator>` (`@/components/ui/separator`) |
210
+
211
+ > **例外**: `components/ui/` 内の shadcn/ui コンポーネント実装では素のHTML要素を使用してよい。
212
+ > **ESLint**: `eslint-plugin-melta` の `melta/no-raw-html-elements` ルールで自動検出される。
213
+
214
+ ---
215
+
188
216
  ## アクセシビリティ(全般)
189
217
 
190
218
  | 禁止 | 理由 | 代替 |
@@ -1,6 +1,6 @@
1
1
  /* ============================================
2
2
  mikeneko UI Theme — Primary: #2b70ef (Blue)
3
- Font: Inter + Hiragino Sans + Noto Sans JP
3
+ Font: Noto Sans JP
4
4
  ============================================ */
5
5
 
6
6
  :root {
@@ -107,7 +107,7 @@
107
107
 
108
108
  @layer base {
109
109
  body {
110
- font-family: "Inter", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans JP", sans-serif;
110
+ font-family: "Noto Sans JP", sans-serif;
111
111
  letter-spacing: 0.02em;
112
112
  line-height: 2.0;
113
113
  }
@@ -70,7 +70,7 @@
70
70
  "typography": {
71
71
  "fontFamily": {
72
72
  "sans": {
73
- "value": ["Inter", "Hiragino Sans", "Hiragino Kaku Gothic ProN", "Noto Sans JP", "sans-serif"],
73
+ "value": ["Noto Sans JP", "sans-serif"],
74
74
  "tailwind": "font-sans"
75
75
  },
76
76
  "mono": {