$npx -y skills add kwakseongjae/oh-my-design --skill omd-learn.omd/preferences.md의 status:pending 항목을 DESIGN.md에 정식 merge하고 status를 applied로 플립. '프리퍼런스 정리해줘', 'fold preferences', 'apply all corrections', 「好みをDESIGN.mdに反映」, 「套用偏好」류의 요청에 트리거. 단발성 교정 기록은 omd:remember.
| 1 | <!-- omd:installed-skill — managed by `omd install-skills`. Do not edit; rerun the command to refresh. --> |
| 2 | |
| 3 | |
| 4 | # omd:learn — Preference Fold into DESIGN.md |
| 5 | |
| 6 | `.omd/preferences.md`에 누적된 `status: pending` 교정사항을 DESIGN.md에 반영하고, 반영된 엔트리의 상태를 `applied`로 플립한다. **CLI 호출 없음** — Read/Edit 툴로 직접 처리. |
| 7 | |
| 8 | ## Phase 1 — 검토 |
| 9 | |
| 10 | `Read .omd/preferences.md` → frontmatter + 엔트리들 파싱: |
| 11 | |
| 12 | - 엔트리 분리: `## ` heading 기준 split |
| 13 | - 각 엔트리의 `omd-meta` 코드블록에서 `id`, `scope`, `status` 추출 |
| 14 | - `status: pending`만 필터 |
| 15 | |
| 16 | scope별로 그룹화해서 사용자에게 요약: |
| 17 | |
| 18 | ``` |
| 19 | components.button (3 pending): |
| 20 | - CTAs never uppercase (pref_xxx, pref_yyy) |
| 21 | - primary fill should be brand-500 not 600 (pref_zzz) |
| 22 | |
| 23 | spacing (1 pending): |
| 24 | - 8pt grid, not 4pt (pref_aaa) |
| 25 | ``` |
| 26 | |
| 27 | 엔트리당 한 줄이 아니라 **scope당 2-3줄로 의도 정리**. |
| 28 | |
| 29 | ## Phase 2 — 사용자 확인 |
| 30 | |
| 31 | "이 교정들을 DESIGN.md에 반영할까요?" 묻기. 동의 → Phase 3. |
| 32 | |
| 33 | 거부 → 어떤 scope를 reject할지 묻고 Phase 4 reject 분기로. |
| 34 | |
| 35 | ## Phase 3 — DESIGN.md 적용 |
| 36 | |
| 37 | 1. `Read DESIGN.md` 로드 |
| 38 | 2. scope별로 묶어서 **하나의 coherent edit** 생성 (엔트리당 하나가 아니라 한 scope의 교정들을 종합) |
| 39 | 3. Edit 툴로 DESIGN.md의 해당 섹션 수정: |
| 40 | - `components.button` → DESIGN.md §8 (Components → Button) 또는 §13 (Components 상세) |
| 41 | - `color` → §2 (Color Palette) |
| 42 | - `typography` → §3 |
| 43 | - `spacing` → §4 (Spacing scale) |
| 44 | - `voice` → §10 (Voice & Tone) |
| 45 | - `motion` → §15 (Motion & Easing) |
| 46 | - `visualTheme` → §1 (Visual Theme) |
| 47 | 4. **voice/내러티브 수정 시 DESIGN.md의 기존 문체 preserve** — 교정 내용만 반영, 문장 스타일/길이/톤 유지 |
| 48 | 5. **§10-15 (Brand Philosophy 레이어)는 reference voice 보존이 우선** — preference가 §10-15 본문 자체를 다시 쓰라고 하지 않는 한 본문은 건드리지 않고 §1-9의 axes만 수정 |
| 49 | |
| 50 | ## Phase 4 — 상태 플립 |
| 51 | |
| 52 | 반영한 엔트리: 해당 엔트리의 omd-meta 블록을 Edit 툴로: |
| 53 | - `status: pending` → `status: applied` |
| 54 | - `applied_at: <ISO timestamp>` 라인 추가 |
| 55 | - (선택) `applied_design_md_hash: <DESIGN.md sha256>` 추가. hash 계산: |
| 56 | ```bash |
| 57 | node -e "console.log(require('crypto').createHash('sha256').update(require('fs').readFileSync('DESIGN.md')).digest('hex').slice(0,12))" |
| 58 | ``` |
| 59 | |
| 60 | 거부한 엔트리: |
| 61 | - `status: pending` → `status: rejected` |
| 62 | - `rejected_reason: "<짧은 이유>"` 라인 추가 |
| 63 | |
| 64 | 상위 엔트리가 누적된 작은 교정을 통합·대체했으면: |
| 65 | - 작은 엔트리들은 `status: superseded` |
| 66 | - `superseded_by: <상위 pref_id>` 추가 |
| 67 | |
| 68 | ## Phase 5 — 결과 요약 |
| 69 | |
| 70 | 한 문단: |
| 71 | - 반영된 교정 수 (scope별) |
| 72 | - 거부된 교정 수 + 이유 |
| 73 | - 사용자에게 `.omd/preferences.md` 직접 확인 안내 |
| 74 | |
| 75 | ``` |
| 76 | 4 preferences applied to DESIGN.md |
| 77 | - components.button: CTAs never uppercase, primary brand-500 |
| 78 | - spacing: 8pt grid |
| 79 | 1 rejected (conflicts with base reference radius) |
| 80 | |
| 81 | Review .omd/preferences.md for details. |
| 82 | ``` |
| 83 | |
| 84 | ## Fold-in 제안에서 호출된 경우 (`.omd/foldin-proposal.json`) |
| 85 | |
| 86 | SessionStart 컨텍스트의 OMD FOLD-IN PROPOSAL → AskUserQuestion 승인 경로로 호출되었으면 Phase 2 확인은 이미 끝난 것 — 다시 묻지 말 것. |
| 87 | |
| 88 | 제안 없이 사용자가 직접 omd:learn을 부른 경우에도 `.omd/foldin-proposal.json`이 |
| 89 | `"status": "proposed"`로 존재하면: 그 scopes를 이번 폴드 대상에 포함할지 Phase 2에서 |
| 90 | 함께 확인하고, 처리 후 아래와 동일하게 status를 갱신한다 (proposed인 채로 방치 금지 — |
| 91 | 다음 세션이 또 물어본다). |
| 92 | |
| 93 | - **승인된 scope만** Phase 3-4로 처리. 미승인 scope의 pending 엔트리는 건드리지 않는다 |
| 94 | - 처리 후 `.omd/foldin-proposal.json`의 status를 Edit 툴로 갱신: |
| 95 | - 전부 반영 → `"status": "applied"` + `"applied_at": "<ISO timestamp>"` 필드 추가 |
| 96 | - 일부만 반영 → `"status": "partial"` + `scopes` 배열을 **남은(미승인) scope만**으로 갱신 |
| 97 | - 전부 거절("나중에") → `"status": "snoozed"` + `"snoozed_at": "<ISO timestamp>"` 필드 추가 |
| 98 | - status 값은 **JSON 계약상 영문 고정** (`proposed`/`applied`/`partial`/`snoozed`) — |
| 99 | 번역·한글화 금지 (훅이 문자열 비교로 읽는다) |
| 100 | |
| 101 | ## 옵션 패턴 |
| 102 | |
| 103 | 사용자가 특정 작업만 요청하는 경우: |
| 104 | |
| 105 | - **"pending만 보여줘"** → Phase 1만, Phase 2-5 생략 |
| 106 | - **"X scope만 반영"** → 해당 scope만 Phase 3에서 처리 |
| 107 | - **"<pref_id>를 applied로 표시"** → Phase 4의 single-entry 플립만 |
| 108 | - **"<pref_id>를 rejected로 표시 + 이유"** → 동일 |
| 109 | - 플립 전 현재 status를 먼저 Read로 확인: 이미 같은 값이면 no-op 보고, |
| 110 | `superseded`/`rejected` → `applied` 전환은 **금지** (이력 오염 — 사용자에게 |
| 111 | "이 항목은 X 상태예요. 되살리려면 omd:remember로 재캡처하세요"라고 안내) |
| 112 | |
| 113 | ## 금지 |
| 114 | |
| 115 | - LLM으로 엔트리별 개별 diff를 생성하지 말 것 — scope별 합쳐서 하나의 coherent edit |
| 116 | - DESIGN.md의 section heading 계층을 바꾸지 말 것 |
| 117 | - 교정과 관계없는 부분을 "개선"하지 말 것 |
| 118 | - pending을 건너뛰지 말 것 — 모든 pending에 applied/rejected/superseded 중 하나로 플립 |
| 119 | - omd-meta 블록 외부 (body) 수정 금지 — 교정 본문은 영구 기록 |