oh-my-design-cli 1.7.1 → 1.7.2

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.
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ // Shared parser for .omd/preferences.md — the CANONICAL format written by the
3
+ // omd:remember skill (skills/omd-remember/SKILL.md). The remember format is the
4
+ // single source of truth; these hooks read it, they never change it.
5
+ //
6
+ // Canonical entry shape:
7
+ //
8
+ // ## 2026-04-30T17:48:00.000Z — ctas-never-uppercase
9
+ //
10
+ // ```omd-meta
11
+ // id: pref_lqxk2_a3f9c1d4
12
+ // timestamp: 2026-04-30T17:48:00.000Z
13
+ // scope: components.button
14
+ // signal: user-statement
15
+ // confidence: explicit
16
+ // status: pending
17
+ // source_agent: claude-code
18
+ // source_context: "src/components/Button.tsx"
19
+ // ```
20
+ //
21
+ // CTAs are never uppercase
22
+ //
23
+ // Entries are `## <heading>` sections; metadata lives in a fenced ```omd-meta
24
+ // block of `key: value` lines (NOT dash-lists, NOT `### ` headings).
25
+
26
+ 'use strict';
27
+
28
+ /** Parse the key/value lines inside an ```omd-meta fenced block. */
29
+ function parseOmdMeta(block) {
30
+ const m = /```omd-meta\s*\n([\s\S]*?)\n```/.exec(block);
31
+ if (!m) return null;
32
+ const meta = {};
33
+ for (const line of m[1].split('\n')) {
34
+ const kv = /^\s*([a-z_]+):\s*(.*)$/i.exec(line);
35
+ if (!kv) continue;
36
+ let val = kv[2].trim();
37
+ // Strip surrounding quotes (source_context is JSON.stringify'd).
38
+ if (
39
+ (val.startsWith('"') && val.endsWith('"')) ||
40
+ (val.startsWith("'") && val.endsWith("'"))
41
+ ) {
42
+ val = val.slice(1, -1);
43
+ }
44
+ meta[kv[1].toLowerCase()] = val;
45
+ }
46
+ return meta;
47
+ }
48
+
49
+ /**
50
+ * Parse the canonical preferences.md text into structured entries.
51
+ * Returns [{ heading, scope, status, timestamp(ms|NaN), confidence, raw }].
52
+ * Robust to a missing/garbled meta block (entry is skipped, never throws).
53
+ */
54
+ function parsePreferences(text) {
55
+ if (!text || typeof text !== 'string') return [];
56
+ // Split on `## ` headings (entry boundary). slice(1) drops the file
57
+ // frontmatter + `# Preference Log` preamble before the first entry.
58
+ const sections = text.split(/^## /m).slice(1);
59
+ const entries = [];
60
+ for (const section of sections) {
61
+ const heading = section.split('\n', 1)[0].trim();
62
+ const meta = parseOmdMeta(section);
63
+ if (!meta) continue;
64
+ const tsRaw = meta.timestamp || '';
65
+ entries.push({
66
+ heading,
67
+ scope: meta.scope || '',
68
+ status: meta.status || 'pending',
69
+ confidence: meta.confidence || '',
70
+ timestamp: tsRaw ? new Date(tsRaw).getTime() : NaN,
71
+ raw: meta,
72
+ });
73
+ }
74
+ return entries;
75
+ }
76
+
77
+ /** Count entries with `status: pending`. */
78
+ function countPending(text) {
79
+ return parsePreferences(text).filter((e) => e.status === 'pending').length;
80
+ }
81
+
82
+ module.exports = { parsePreferences, parseOmdMeta, countPending };
@@ -28,15 +28,15 @@ process.stdin.on('end', () => {
28
28
  if (!['Edit', 'Write', 'MultiEdit'].includes(toolName)) {
29
29
  process.exit(0);
30
30
  }
31
- const filePath = payload.toolInput?.file_path || payload.toolInput?.filePath || '';
31
+ // Claude Code sends snake_case `tool_input`; keep camelCase `toolInput` as
32
+ // a fallback for other channels / older payloads.
33
+ const toolInput = payload.tool_input || payload.toolInput || {};
34
+ const filePath = toolInput.file_path || toolInput.filePath || '';
32
35
  if (!/\.(tsx|jsx|ts|js|css|scss)$/i.test(filePath)) {
33
36
  process.exit(0);
34
37
  }
35
38
 
36
- const newText =
37
- payload.toolInput?.content ||
38
- payload.toolInput?.new_string ||
39
- '';
39
+ const newText = toolInput.content || toolInput.new_string || '';
40
40
  if (!newText) process.exit(0);
41
41
 
42
42
  // Normalize a hex to canonical 6-char lowercase form (#abc → #aabbcc)
@@ -93,7 +93,15 @@ process.stdin.on('end', () => {
93
93
  '',
94
94
  ];
95
95
 
96
- // Hook contract: write to additionalContext via JSON stdout
97
- process.stdout.write(JSON.stringify({ additionalContext: lines.join('\n') }));
96
+ // PostToolUse hook contract: additionalContext must be nested under
97
+ // hookSpecificOutput — a top-level { additionalContext } is dropped.
98
+ process.stdout.write(
99
+ JSON.stringify({
100
+ hookSpecificOutput: {
101
+ hookEventName: 'PostToolUse',
102
+ additionalContext: lines.join('\n'),
103
+ },
104
+ }),
105
+ );
98
106
  });
99
107
 
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require('node:fs');
9
9
  const path = require('node:path');
10
+ const { parsePreferences } = require('./lib/preferences-parser.cjs');
10
11
 
11
12
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
12
13
  const preferencesMd = path.join(projectDir, '.omd', 'preferences.md');
@@ -34,26 +35,22 @@ try {
34
35
  process.exit(0);
35
36
  }
36
37
 
37
- // Parse pending entries (very rough)
38
- const blocks = prefText.split(/^### /m).slice(1);
38
+ // Parse the CANONICAL omd:remember format (## heading + ```omd-meta block).
39
39
  const now = Date.now();
40
40
  const windowMs = config.recurrence_window_days * 24 * 3600 * 1000;
41
41
  const byScope = new Map();
42
42
 
43
- for (const block of blocks) {
44
- const tsMatch = /^- ts:\s*(.+)$/m.exec(block);
45
- const scopeMatch = /^- scope:\s*(.+)$/m.exec(block);
46
- const statusMatch = /^- status:\s*(\w+)$/m.exec(block);
47
- const importanceMatch = /^- importance:\s*([1-5])$/m.exec(block);
48
- if ((statusMatch?.[1] || 'pending') !== 'pending') continue;
49
- if (!tsMatch || !scopeMatch) continue;
50
- const ts = new Date(tsMatch[1]).getTime();
43
+ for (const entry of parsePreferences(prefText)) {
44
+ if (entry.status !== 'pending') continue;
45
+ if (!entry.scope) continue;
46
+ const ts = entry.timestamp;
51
47
  if (Number.isNaN(ts) || now - ts > windowMs) continue;
52
- const scope = scopeMatch[1];
53
- const importance = parseInt(importanceMatch?.[1] || '3', 10);
54
- const list = byScope.get(scope) || [];
48
+ // The canonical format carries no numeric importance; derive a 1-5 weight
49
+ // from confidence (explicit statements weigh more than inferred).
50
+ const importance = entry.confidence === 'inferred' ? 2 : 4;
51
+ const list = byScope.get(entry.scope) || [];
55
52
  list.push({ ts, importance });
56
- byScope.set(scope, list);
53
+ byScope.set(entry.scope, list);
57
54
  }
58
55
 
59
56
  const proposals = [];
@@ -7,6 +7,7 @@
7
7
 
8
8
  const fs = require('node:fs');
9
9
  const path = require('node:path');
10
+ const { countPending } = require('./lib/preferences-parser.cjs');
10
11
 
11
12
  const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
12
13
  const stateMd = path.join(projectDir, '.omd', 'state.md');
@@ -32,9 +33,9 @@ if (fs.existsSync(stateMd)) {
32
33
  lines.push('');
33
34
  }
34
35
  } else if (fs.existsSync(preferencesMd)) {
35
- // Best-effort fallback — count pending entries
36
+ // Best-effort fallback — count pending entries (canonical omd:remember format).
36
37
  const text = safeRead(preferencesMd) || '';
37
- const pendingCount = (text.match(/^- status:\s*pending\b/gm) || []).length;
38
+ const pendingCount = countPending(text);
38
39
  if (pendingCount > 0) {
39
40
  lines.push('## OMD ENVIRONMENT STATE');
40
41
  lines.push('');
@@ -35,9 +35,11 @@ const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
35
35
  // "is the user asking for UI work?" sniff test, not a full taxonomy.
36
36
  // If you find yourself wanting to add 50 entries here, that's a sign
37
37
  // the description on the relevant SKILL.md should grow instead.
38
+ // `색` → `색상`: the bare `색` substring-matched 검색("search"), 검색창, etc.,
39
+ // firing the gate on non-UI prompts. `색상` ("color") is the real cue.
38
40
  const UI_CUES = [
39
41
  // Korean
40
- '디자인', '레이아웃', '화면', '컴포넌트', '버튼', '카드', '색', '색상',
42
+ '디자인', '레이아웃', '화면', '컴포넌트', '버튼', '카드', '색상',
41
43
  '폰트', '스타일', '브랜드', '리팩토링', '랜딩', '페이지',
42
44
  // English
43
45
  'design', 'layout', 'component', 'button', 'card', 'color', 'colour',
@@ -49,17 +51,38 @@ const UI_CUES = [
49
51
  '設計', '佈局', '元件', '按鈕', '風格', '顏色', '字體', '品牌', '落地頁',
50
52
  ];
51
53
 
52
- let prompt = '';
54
+ // Latin-alphabet cues use word boundaries so e.g. "card" doesn't match
55
+ // "discard"/"cardinal" and "style" doesn't match "stylesheet" inside a path.
56
+ // CJK cues have no ASCII word boundaries, so for them we keep substring match.
57
+ const isLatin = (s) => /^[a-z]+$/i.test(s);
58
+ function cueMatches(cue, prompt, lowerPrompt) {
59
+ if (isLatin(cue)) {
60
+ return new RegExp(`\\b${cue}\\b`, 'i').test(prompt);
61
+ }
62
+ return lowerPrompt.includes(cue.toLowerCase()) || prompt.includes(cue);
63
+ }
64
+
65
+ let input = '';
53
66
  process.stdin.setEncoding('utf8');
54
- process.stdin.on('data', (chunk) => (prompt += chunk));
67
+ process.stdin.on('data', (chunk) => (input += chunk));
55
68
  process.stdin.on('end', () => {
69
+ // Never crash on malformed stdin — exit 0 silently.
70
+ let prompt;
71
+ try {
72
+ const payload = JSON.parse(input || '{}');
73
+ // Match against the user's prompt ONLY — not cwd/transcript_path, which
74
+ // contain repo paths like ".../oh-my-design/..." that falsely tripped the gate.
75
+ prompt = typeof payload.prompt === 'string' ? payload.prompt : '';
76
+ } catch {
77
+ process.exit(0);
78
+ }
79
+ if (!prompt) process.exit(0);
80
+
56
81
  const designMdPath = path.join(projectDir, 'DESIGN.md');
57
82
  if (fs.existsSync(designMdPath)) process.exit(0);
58
83
 
59
84
  const lower = prompt.toLowerCase();
60
- const looksLikeUiWork = UI_CUES.some(
61
- (k) => lower.includes(k.toLowerCase()) || prompt.includes(k),
62
- );
85
+ const looksLikeUiWork = UI_CUES.some((k) => cueMatches(k, prompt, lower));
63
86
  if (!looksLikeUiWork) process.exit(0);
64
87
 
65
88
  const lines = [
@@ -71,5 +94,15 @@ process.stdin.on('end', () => {
71
94
  ' with brand context.',
72
95
  '',
73
96
  ];
74
- process.stdout.write(JSON.stringify({ additionalContext: lines.join('\n') }));
97
+ // UserPromptSubmit contract: structured context must sit under
98
+ // hookSpecificOutput — a top-level { additionalContext } parses as JSON
99
+ // with no recognized fields and the gate message is silently dropped.
100
+ process.stdout.write(
101
+ JSON.stringify({
102
+ hookSpecificOutput: {
103
+ hookEventName: 'UserPromptSubmit',
104
+ additionalContext: lines.join('\n'),
105
+ },
106
+ }),
107
+ );
75
108
  });
package/README.ja.md CHANGED
@@ -5,19 +5,16 @@
5
5
  <h1 align="center">oh-my-design</h1>
6
6
 
7
7
  <p align="center">
8
- <strong>107 社の実在する企業デザインシステムから DESIGN.md を生成。</strong>インタラクティブウィザード。AI 呼び出しゼロ。
9
- </p>
10
-
11
- <p align="center">
12
- <strong>新機能: OmD v0.1 Philosophy Layer。</strong>Voice・Narrative・Principles・Personas・States・Motion — Claude Code が AI のデフォルトではなく、あなたのブランドに合わせて出力します。
8
+ <strong>AI コーディングエージェントのためのスキル駆動デザイン コマンド 1 回でブートストラップ。</strong>221 社の実在する企業デザインシステム。インストールに AI 呼び出しゼロ。あとはエージェントに話しかけるだけ。
13
9
  </p>
14
10
 
15
11
  <p align="center">
12
+ <a href="https://www.npmjs.com/package/oh-my-design-cli"><img src="https://img.shields.io/npm/v/oh-my-design-cli?style=flat-square&color=cb3837" alt="npm version" /></a>
13
+ <a href="https://www.npmjs.com/package/oh-my-design-cli"><img src="https://img.shields.io/npm/dm/oh-my-design-cli?style=flat-square&color=cb3837" alt="npm downloads" /></a>
16
14
  <a href="LICENSE"><img src="https://img.shields.io/github/license/kwakseongjae/oh-my-design?style=flat-square" alt="License" /></a>
17
15
  <a href="https://github.com/kwakseongjae/oh-my-design/stargazers"><img src="https://img.shields.io/github/stars/kwakseongjae/oh-my-design?style=social" alt="GitHub Stars" /></a>
18
- <img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square" alt="PRs Welcome" />
19
- <img src="https://img.shields.io/badge/AI%20calls-zero-blue?style=flat-square" alt="Zero AI" />
20
- <img src="https://img.shields.io/badge/references-107-7c5cfc?style=flat-square" alt="107 References" />
16
+ <img src="https://img.shields.io/badge/references-221-7c5cfc?style=flat-square" alt="221 References" />
17
+ <img src="https://img.shields.io/badge/CLI%20commands-1-blue?style=flat-square" alt="One CLI command" />
21
18
  </p>
22
19
 
23
20
  <p align="center">
@@ -28,118 +25,50 @@
28
25
 
29
26
  ## oh-my-design とは?
30
27
 
31
- **oh-my-design (OmD)** は、AI コーディングエージェントに「ブランドらしい UI」を生成させるのに十分なブランドコンテキストを供給するためのオープン仕様です。
32
-
33
- [Google が提案した](https://stitch.withgoogle.com/docs/design-md/overview/) `DESIGN.md` は本質的に**トークン文書** — 色・タイポグラフィ・コンポーネントの集合です。必要ですが、十分ではありません。トークンだけで UI を作ると形は整っても「誰のブランドでもない」出力になります — Inter-on-white、紫グラデーション、意味のない絵文字といった AI のデフォルトに収束します。OmD v0.1 はその上に**ブランド哲学レイヤー**を重ねます: **Voice・Narrative・Principles・Personas・States・Motion**。OmD フォーマットの `DESIGN.md` をプロジェクトルートに置くと、エージェントの出力はジェネリックではなく「あなたのもの」になります。
34
-
35
- 3 つの構成要素:
36
-
37
- 1. **[仕様](spec/omd-v0.1.md)** — バージョン管理された Google Stitch 拡張、MIT ライセンス。
38
- 2. **[Claude Code スキル](.claude/skills/omd/SKILL.md)** — 仕様をハード制約として自動適用。
39
- 3. **[107 のリファレンス](references/)** — 実在企業の `DESIGN.md` をフォークし、ビルダーでカスタマイズしてそのまま導入。
40
-
41
- **API キー不要。AI 呼び出しゼロ。全てクライアントサイドで完結。**
42
-
43
- ## OmD v0.1 Philosophy Layer
44
-
45
- Google Stitch の 9 セクションの上に OmD が追加する 6 セクション:
46
-
47
- | セクション | 役割 |
48
- |---|---|
49
- | **10. Voice & Tone** | マイクロコピー制約 — ボタン文言、エラーメッセージ、オンボーディング |
50
- | **11. Brand Narrative** | 「なぜ」 — ブランドが拒否するもの、変えようとしているカテゴリ |
51
- | **12. Principles** | トークンでは解けないケースを決する 5〜10 の第一原理 |
52
- | **13. Personas** | 2〜4 人の具体的なユーザー。エージェントの出力を実際の使用文脈に grounded させる |
53
- | **14. States** | Empty / loading / error / skeleton パターン — ジェネリックな「データなし」を防ぐ |
54
- | **15. Motion & Easing** | 命名された duration + easing トークン — Stitch の 9 セクションが抜けている次元 |
55
-
56
- **現在、10 のリファレンスが完全な Philosophy Layer とともに提供されています:**
57
- Toss · Claude · Line · Stripe · Linear · Vercel · Notion · Airbnb · Apple · Figma — それぞれ voice, narrative, principles, personas, states, motion まで公開ソースに基づいて書かれています。
28
+ **oh-my-design (OmD)** AI コーディングエージェントのためのデザインシステムです。Claude Code / Codex / OpenCode / Cursor を、あなたのブランドを記憶したシニアプロダクトデザイナーに変えます。一度インストールすれば、あとは欲しいものを説明するだけ — コンポーネント、画面、コピー、アセット、チャート — エージェントがプロジェクトのデザインシステムを適用して出力します。`DESIGN.md` がブランド仕様([Google Stitch](https://stitch.withgoogle.com/docs/design-md/overview/) トークン + ブランド哲学レイヤー: Voice / Narrative / Principles / Personas / States / Motion)で、221 社の実在企業の DESIGN.md がパッケージに同梱されています。**API キー不要。外部インフラ不要。すべて既存の CLI セッション内で動作します。**
58
29
 
59
- 完全な OmD v0.1 の例は [references/toss/DESIGN.md](references/toss/DESIGN.md) を参照。
30
+ ## インストール
60
31
 
61
- ## 主な機能
62
-
63
- - **ビルダー** — リファレンスを選び、カラー / radius / ダークモードを調整し、コンポーネントを選択して Export。**Philosophy** フィルターで完全なブランド哲学を持つ 10 件に絞り込めます。
64
- - **デザインシステムディレクトリ** ([oh-my-design.kr/design-systems](https://oh-my-design.kr/design-systems)) — 107 リファレンス中 34 件は公式のデザインシステムまたはブランドガイドラインページを持っており、ディレクトリからライブサムネイル付きで直接アクセスできます。
65
- - **Personal Curation** ([oh-my-design.kr/curation](https://oh-my-design.kr/curation)) — MBTI 風の短いクイズであなたのデザイン傾向を 107 リファレンスのいずれかとマッチングし、そのリファレンスが事前選択されたビルダーへ直接移動します。
66
-
67
- ## サポートされる 107 のリファレンス
68
-
69
- | カテゴリ | 企業 |
70
- |----------|------|
71
- | **AI & LLM** | Claude, Cohere, ElevenLabs, Minimax, Mistral AI, Ollama, OpenCode AI, Replicate, RunwayML, Together AI, VoltAgent, xAI |
72
- | **デザインツール** | Airtable, Clay, Figma, Framer, Miro, Webflow |
73
- | **開発者ツール** | Cursor, Expo, Lovable, Raycast, Superhuman, Vercel, Warp |
74
- | **生産性** | Cal.com, freee, Intercom, Linear, Mintlify, Notion, Resend, Zapier |
75
- | **コンシューマテック** | Airbnb, Apple, Baemin, Dcard, IBM, Kakao, Karrot, LINE, Mercari, NVIDIA, Pinkoi, Pinterest, SpaceX, Spotify, Uber |
76
- | **フィンテック** | Coinbase, Kraken, Revolut, Stripe, Toss, Wise |
77
- | **バックエンド & DevOps** | ClickHouse, Composio, Hashicorp, MongoDB, PostHog, Sanity, Sentry, Supabase |
78
- | **自動車** | BMW, Ferrari, Lamborghini, Renault, Tesla |
79
- | **マーケティング** | Semrush |
80
-
81
- > ビルダーの**国フィルター**で地域別に絞り込めます (韓国、台湾、日本、フランス、イタリア、ドイツ、イギリス、アメリカ)。
82
-
83
- ## エクスポートされる DESIGN.md
84
-
85
- [Google Stitch DESIGN.md フォーマット](https://stitch.withgoogle.com/docs/design-md/overview/)ベース — セクション 1〜9 + OmD v0.1 Philosophy Layer (セクション 10〜15、オプション):
32
+ ```bash
33
+ npx oh-my-design-cli install-skills
34
+ ```
86
35
 
87
- **ベース (Google Stitch)**
88
- 1. Visual Theme & Atmosphere
89
- 2. Color Palette & Roles
90
- 3. Typography Rules
91
- 4. Component Stylings
92
- 5. Layout Principles
93
- 6. Depth & Elevation
94
- 7. Do's and Don'ts
95
- 8. Responsive Behavior
96
- 9. Agent Prompt Guide
36
+ インストール後、エージェントを再起動してください (Claude Code は Cmd+Q → 再起動) — 新しいスキル + エージェントが読み込まれます。
97
37
 
98
- **OmD v0.1 Philosophy Layer (追加)**
38
+ 実行する CLI コマンドはこれだけです。あとはすべてエージェントへの自然言語です。
99
39
 
100
- 10. Voice & Tone
101
- 11. Brand Narrative
102
- 12. Principles
103
- 13. Personas
104
- 14. States
105
- 15. Motion & Easing
40
+ ## サポートされるエージェント
106
41
 
107
- その他: Style Preferences, Included Components, Iconography & SVG Guidelines, Document Policies。
42
+ | エージェント | チャネル | インストールされるもの |
43
+ |---|---|---|
44
+ | **Claude Code** | `--agent claude-code` (デフォルト) | フルバンドル — `.claude/` 配下のスキル、16 サブエージェント、hooks、data |
45
+ | **Codex** | `--agent codex` | `.agents/skills/` スキルバンドル (公式 discovery パス) |
46
+ | **OpenCode** | `--agent opencode` | `.opencode/skills/` スキルバンドル |
47
+ | **Cursor** | `--agent cursor` | 正式な rules チャネル — `.cursor/rules/omd-design.mdc` shim + 共有 `.claude/data` カタログ (スキル/フックなし) |
108
48
 
109
- ## プロジェクト構成
49
+ デフォルトでは検出されたすべてのエージェントにインストールします; 単一チャネルのみなら `--agent <name>`。
110
50
 
111
- ```
112
- oh-my-design/
113
- spec/ OmD v0.1 仕様 (正本)
114
- .claude/skills/omd/ Claude Code スキルバンドル
115
- references/ 107 社分の DESIGN.md ファイル
116
- src/ CLI コア (TypeScript)
117
- web/ Next.js ウェブビルダー
118
- src/app/ Landing + Builder + Directory ページ
119
- src/components/ Wizard, Preview, Export
120
- test/ CLI Vitest スイート (unit/, integration/, scripts/)
121
- ```
51
+ ## パッケージの中身
122
52
 
123
- ウェブのテストはソースファイルと並置されています (`web/src/**/*.test.ts`)。
53
+ **16 スキル · 16 サブエージェント · 221 の検証済みリファレンス · 活性化 hooks** — 上記コマンド 1 回ですべてインストールされます。
124
54
 
125
- ## テスト
55
+ すべてのリファレンスは `oh-my-design.kr/design-systems/<id>.md` から raw markdown としても取得でき、エージェントが直接 fetch できます。スキル・エージェントごとの詳細リファレンス: **[oh-my-design.kr/docs](https://oh-my-design.kr/docs)**。
126
56
 
127
- 2 つのスイートがあり、いずれも Vitest で動作し、いずれも合格する必要があります:
57
+ ## アップグレード
128
58
 
129
59
  ```bash
130
- npm test # CLI: 370 テスト — unit + リファレンス全件のスモーク
131
- cd web && npm test # Web: 107 テスト — generate-css, config-hash, survey
60
+ npx oh-my-design-cli@latest install-skills
132
61
  ```
133
62
 
134
- 統合スイート (`test/integration/all-references.test.ts`) はすべての `references/<id>/DESIGN.md` に対して生成パイプライン全体を実行するため、リファレンスの破損は PR レビューでリファレンスごとの失敗として可視化されます。フォルダ規約とモジュール別カバレッジマップは [test/README.md](test/README.md) を参照してください。
135
-
136
- ## 謝辞
63
+ Idempotent。`<!-- omd:installed-skill -->` マーカー付きの管理ファイルは in-place で更新され、ユーザーが編集したファイルはそのまま残ります (`--force` で上書き)。再実行後はエージェントを再起動してください。
137
64
 
138
- - [VoltAgent/awesome-design-md](https://github.com/VoltAgent/awesome-design-md) — 本プロジェクトの出発点となった上流の DESIGN.md コレクション。
139
- - [kzhrknt/awesome-design-md-jp](https://github.com/kzhrknt/awesome-design-md-jp) — 日本市場のデザインシステムリファレンス。
65
+ ## リンク
140
66
 
141
- oh-my-design はこれらのコレクションにインタラクティブなカスタマイズウィザード、A/B スタイル選択、コンポーネント選択、デザインシステムディレクトリ、CLI エクスポートパイプラインを追加して拡張しています。
67
+ - **カタログ** — [oh-my-design.kr/design-systems](https://oh-my-design.kr/design-systems)
68
+ - **コレクション** — [oh-my-design.kr/collections](https://oh-my-design.kr/collections)
69
+ - **ドキュメント** — [oh-my-design.kr/docs](https://oh-my-design.kr/docs)
70
+ - **チェンジログ** — [CHANGELOG.md](CHANGELOG.md)
142
71
 
143
72
  ## ライセンス
144
73
 
145
- [MIT](LICENSE)
74
+ MIT — [LICENSE](LICENSE) を参照。リファレンスは各企業に帰属し、教育的参照のために再構成されています。