@kood/claude-code 0.7.6 → 0.7.8

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.
Files changed (31) hide show
  1. package/dist/index.js +215 -39
  2. package/package.json +1 -1
  3. package/templates/.claude/skills/design/SKILL.md +199 -0
  4. package/templates/.claude/skills/design/references/accessibility.md +106 -0
  5. package/templates/.claude/skills/design/references/checklist.md +41 -0
  6. package/templates/.claude/skills/design/references/core-principles.md +66 -0
  7. package/templates/.claude/skills/design/references/global-uiux.md +216 -0
  8. package/templates/.claude/skills/design/references/korea-uiux.md +227 -0
  9. package/templates/.claude/skills/design/references/patterns.md +250 -0
  10. package/templates/.claude/skills/design/references/responsive.md +248 -0
  11. package/templates/.claude/skills/design/references/safe-area.md +202 -0
  12. package/templates/.claude/skills/design/references/systems.md +116 -0
  13. package/templates/.claude/skills/design/references/trends.md +75 -0
  14. package/templates/.claude/skills/global-uiux-design/AGENTS.md +0 -320
  15. package/templates/.claude/skills/global-uiux-design/SKILL.md +0 -1089
  16. package/templates/.claude/skills/global-uiux-design/references/accessibility.md +0 -401
  17. package/templates/.claude/skills/global-uiux-design/references/color-system.md +0 -275
  18. package/templates/.claude/skills/global-uiux-design/references/design-philosophy.md +0 -206
  19. package/templates/.claude/skills/global-uiux-design/references/design-systems.md +0 -446
  20. package/templates/.claude/skills/korea-uiux-design/AGENTS.md +0 -310
  21. package/templates/.claude/skills/korea-uiux-design/SKILL.md +0 -980
  22. package/templates/.claude/skills/korea-uiux-design/references/accessibility.md +0 -298
  23. package/templates/.claude/skills/korea-uiux-design/references/checklist.md +0 -107
  24. package/templates/.claude/skills/korea-uiux-design/references/color-system.md +0 -156
  25. package/templates/.claude/skills/korea-uiux-design/references/design-philosophy.md +0 -25
  26. package/templates/.claude/skills/korea-uiux-design/references/icon-guide.md +0 -180
  27. package/templates/.claude/skills/korea-uiux-design/references/micro-interactions.md +0 -259
  28. package/templates/.claude/skills/korea-uiux-design/references/responsive-patterns.md +0 -115
  29. package/templates/.claude/skills/korea-uiux-design/references/service-patterns.md +0 -206
  30. package/templates/.claude/skills/korea-uiux-design/references/state-patterns.md +0 -320
  31. package/templates/.claude/skills/korea-uiux-design/references/typography.md +0 -70
package/dist/index.js CHANGED
@@ -22,8 +22,8 @@ var banner = () => {
22
22
  };
23
23
 
24
24
  // src/commands/init.ts
25
- import fs8 from "fs-extra";
26
- import os from "os";
25
+ import fs9 from "fs-extra";
26
+ import os2 from "os";
27
27
 
28
28
  // src/features/templates/template-path-resolver.ts
29
29
  import path2 from "path";
@@ -688,16 +688,50 @@ async function promptScopeSelection(options) {
688
688
  }
689
689
  return { scope: response.value };
690
690
  }
691
+ async function promptCodexSync(options) {
692
+ const { providedSyncCodex } = options;
693
+ if (providedSyncCodex !== void 0) {
694
+ return { syncCodex: providedSyncCodex };
695
+ }
696
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
697
+ return { syncCodex: false };
698
+ }
699
+ logger.blank();
700
+ const result = await promptConfirm(
701
+ "Also sync with Codex now? (~/.codex/skills/)",
702
+ false
703
+ );
704
+ return { syncCodex: result.confirmed };
705
+ }
691
706
 
692
707
  // src/shared/gitignore-manager.ts
693
708
  import fs7 from "fs-extra";
694
709
  import path9 from "path";
695
710
  var CLAUDE_GENERATED_FOLDERS = [
711
+ ".claude/brainstorms/",
712
+ ".claude/crawler/",
713
+ ".claude/first-principles/",
714
+ ".claude/genius-ideas/",
696
715
  ".claude/plan/",
716
+ ".claude/plans/",
697
717
  ".claude/ralph/",
698
718
  ".claude/refactor/",
699
- ".claude/prd/"
719
+ ".claude/prd/",
720
+ ".claude/research/",
721
+ ".claude/validation-results/"
700
722
  ];
723
+ function normalizeIgnorePattern(pattern) {
724
+ return pattern.replace(/\\/g, "/").replace(/^\.?\//, "").replace(/\/+$/, "").trim();
725
+ }
726
+ function parseIgnoreLine(line) {
727
+ const trimmed = line.trim();
728
+ if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("!")) {
729
+ return null;
730
+ }
731
+ const [rawPattern] = trimmed.split(/\s+#/u, 1);
732
+ const normalized = normalizeIgnorePattern(rawPattern);
733
+ return normalized || null;
734
+ }
701
735
  async function updateGitignore(targetDir) {
702
736
  const gitignorePath = path9.join(targetDir, ".gitignore");
703
737
  const sectionComment = "# Claude Code generated files";
@@ -709,43 +743,33 @@ async function updateGitignore(targetDir) {
709
743
  } catch {
710
744
  content = "";
711
745
  }
712
- const linesToAdd = [];
713
- const existingLines = content.split("\n").map((line) => line.trim());
714
- for (const folder of CLAUDE_GENERATED_FOLDERS) {
715
- if (!existingLines.includes(folder)) {
716
- linesToAdd.push(folder);
717
- }
718
- }
746
+ const eol = content.includes("\r\n") ? "\r\n" : "\n";
747
+ const lines = content ? content.split(/\r?\n/u) : [];
748
+ const existingPatterns = new Set(
749
+ lines.map((line) => parseIgnoreLine(line)).filter((pattern) => Boolean(pattern))
750
+ );
751
+ const linesToAdd = CLAUDE_GENERATED_FOLDERS.filter(
752
+ (folder) => !existingPatterns.has(normalizeIgnorePattern(folder))
753
+ );
719
754
  if (linesToAdd.length === 0) {
720
755
  logger.info(".gitignore already contains all Claude Code patterns");
721
756
  return;
722
757
  }
723
- const needsSection = !content.includes(sectionComment);
724
- let newContent = content;
725
- if (newContent && !newContent.endsWith("\n")) {
726
- newContent += "\n";
727
- }
758
+ const sectionIndex = lines.findIndex(
759
+ (line) => line.trim() === sectionComment
760
+ );
761
+ const needsSection = sectionIndex === -1;
728
762
  if (needsSection) {
729
- if (newContent) {
730
- newContent += "\n";
763
+ if (lines.length > 0 && lines[lines.length - 1] !== "") {
764
+ lines.push("");
731
765
  }
732
- newContent += sectionComment + "\n";
733
- newContent += linesToAdd.join("\n") + "\n";
766
+ lines.push(sectionComment, ...linesToAdd);
734
767
  } else {
735
- const lines = newContent.split("\n");
736
- const sectionIndex = lines.findIndex(
737
- (line) => line.includes(sectionComment)
738
- );
739
- if (sectionIndex !== -1) {
740
- lines.splice(sectionIndex + 1, 0, ...linesToAdd);
741
- newContent = lines.join("\n");
742
- } else {
743
- if (newContent) {
744
- newContent += "\n";
745
- }
746
- newContent += sectionComment + "\n";
747
- newContent += linesToAdd.join("\n") + "\n";
748
- }
768
+ lines.splice(sectionIndex + 1, 0, ...linesToAdd);
769
+ }
770
+ let newContent = lines.join(eol);
771
+ if (!newContent.endsWith(eol)) {
772
+ newContent += eol;
749
773
  }
750
774
  await fs7.writeFile(gitignorePath, newContent, "utf-8");
751
775
  if (hasGitignore) {
@@ -755,6 +779,126 @@ async function updateGitignore(targetDir) {
755
779
  }
756
780
  }
757
781
 
782
+ // src/features/codex-sync/codex-sync.ts
783
+ import fs8 from "fs-extra";
784
+ import os from "os";
785
+ import path10 from "path";
786
+ function resolveCodexHome() {
787
+ const envCodexHome = process.env.CODEX_HOME?.trim();
788
+ if (envCodexHome) {
789
+ return path10.resolve(envCodexHome);
790
+ }
791
+ return path10.join(os.homedir(), ".codex");
792
+ }
793
+ function normalizeLineEndings(content) {
794
+ return content.replace(/\r\n/g, "\n");
795
+ }
796
+ function yamlQuote(value) {
797
+ return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
798
+ }
799
+ function extractDescriptionFromFrontmatter(markdown) {
800
+ const frontmatterMatch = markdown.match(/^---\n([\s\S]*?)\n---\n?/);
801
+ if (!frontmatterMatch) {
802
+ return void 0;
803
+ }
804
+ const lines = frontmatterMatch[1].split("\n");
805
+ for (const rawLine of lines) {
806
+ const line = rawLine.trim();
807
+ if (!line.startsWith("description:")) {
808
+ continue;
809
+ }
810
+ const value = line.slice("description:".length).trim();
811
+ if (!value) {
812
+ return void 0;
813
+ }
814
+ return value.replace(/^['"]|['"]$/g, "");
815
+ }
816
+ return void 0;
817
+ }
818
+ function buildCommandSkillContent(commandPath, commandRaw) {
819
+ const commandName = path10.basename(commandPath, ".md");
820
+ const normalizedCommand = normalizeLineEndings(commandRaw).trim();
821
+ const sourcePath = path10.resolve(commandPath);
822
+ const description = extractDescriptionFromFrontmatter(normalizedCommand) || `${commandName} command imported from .claude/commands`;
823
+ return `---
824
+ name: ${yamlQuote(commandName)}
825
+ description: ${yamlQuote(description)}
826
+ ---
827
+
828
+ Imported command source: ${sourcePath}
829
+
830
+ Use this as a command playbook. If scripts are referenced with .claude-relative paths,
831
+ resolve them from the current workspace first; if missing, ask for the intended repo root.
832
+
833
+ ---
834
+
835
+ ${normalizedCommand}
836
+ `;
837
+ }
838
+ async function syncSkills(claudeSkillsDir, codexSkillsDir) {
839
+ if (!await fs8.pathExists(claudeSkillsDir)) {
840
+ return 0;
841
+ }
842
+ const entries = await fs8.readdir(claudeSkillsDir, { withFileTypes: true });
843
+ let syncedSkills = 0;
844
+ for (const entry of entries) {
845
+ if (!entry.isDirectory()) {
846
+ continue;
847
+ }
848
+ if (entry.name === ".system") {
849
+ continue;
850
+ }
851
+ const src = path10.join(claudeSkillsDir, entry.name);
852
+ const dest = path10.join(codexSkillsDir, entry.name);
853
+ if (await fs8.pathExists(dest)) {
854
+ await fs8.remove(dest);
855
+ }
856
+ await copyRecursive(src, dest, { files: 0, directories: 0 });
857
+ syncedSkills++;
858
+ }
859
+ return syncedSkills;
860
+ }
861
+ async function syncCommands(claudeCommandsDir, codexCommandsDir) {
862
+ if (!await fs8.pathExists(claudeCommandsDir)) {
863
+ return 0;
864
+ }
865
+ const entries = await fs8.readdir(claudeCommandsDir, { withFileTypes: true });
866
+ const commandFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith(".md")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
867
+ if (commandFiles.length === 0) {
868
+ return 0;
869
+ }
870
+ await fs8.remove(codexCommandsDir);
871
+ await fs8.ensureDir(codexCommandsDir);
872
+ for (const commandFile of commandFiles) {
873
+ const commandPath = path10.join(claudeCommandsDir, commandFile);
874
+ const commandRaw = await fs8.readFile(commandPath, "utf8");
875
+ const commandName = path10.basename(commandFile, ".md");
876
+ const skillDir = path10.join(codexCommandsDir, commandName);
877
+ const skillPath = path10.join(skillDir, "SKILL.md");
878
+ await fs8.ensureDir(skillDir);
879
+ await fs8.writeFile(skillPath, buildCommandSkillContent(commandPath, commandRaw));
880
+ }
881
+ return commandFiles.length;
882
+ }
883
+ async function syncWithCodex(targetDir) {
884
+ const codexHome = resolveCodexHome();
885
+ const codexSkillsDir = path10.join(codexHome, "skills");
886
+ const codexCommandsDir = path10.join(codexSkillsDir, "claude-commands");
887
+ await fs8.ensureDir(codexSkillsDir);
888
+ const claudeRootDir = path10.join(targetDir, ".claude");
889
+ const claudeSkillsDir = path10.join(claudeRootDir, "skills");
890
+ const claudeCommandsDir = path10.join(claudeRootDir, "commands");
891
+ const syncedSkills = await syncSkills(claudeSkillsDir, codexSkillsDir);
892
+ const syncedCommands = await syncCommands(claudeCommandsDir, codexCommandsDir);
893
+ return {
894
+ codexHome,
895
+ codexSkillsDir,
896
+ codexCommandsDir,
897
+ syncedSkills,
898
+ syncedCommands
899
+ };
900
+ }
901
+
758
902
  // src/commands/init.ts
759
903
  var TEMPLATE_DESCRIPTIONS = {
760
904
  "tanstack-start": "TanStack Start + React \uD480\uC2A4\uD0DD \uD504\uB85C\uC81D\uD2B8",
@@ -764,7 +908,7 @@ var TEMPLATE_DESCRIPTIONS = {
764
908
  };
765
909
  async function validateTargetDirectory(targetDir) {
766
910
  try {
767
- const stat = await fs8.stat(targetDir);
911
+ const stat = await fs9.stat(targetDir);
768
912
  if (!stat.isDirectory()) {
769
913
  logger.error(`Target is not a directory: ${targetDir}`);
770
914
  process.exit(1);
@@ -778,7 +922,7 @@ async function validateTargetDirectory(targetDir) {
778
922
  process.exit(1);
779
923
  }
780
924
  try {
781
- await fs8.access(targetDir, fs8.constants.W_OK);
925
+ await fs9.access(targetDir, fs9.constants.W_OK);
782
926
  } catch {
783
927
  logger.error(`No write permission for: ${targetDir}`);
784
928
  process.exit(1);
@@ -905,7 +1049,7 @@ var init = async (options) => {
905
1049
  const { scope } = await promptScopeSelection({
906
1050
  providedScope: options.scope
907
1051
  });
908
- const targetDir = scope === "user" ? os.homedir() : projectDir;
1052
+ const targetDir = scope === "user" ? os2.homedir() : projectDir;
909
1053
  const isUserScope = scope === "user";
910
1054
  if (isUserScope) {
911
1055
  logger.blank();
@@ -960,15 +1104,46 @@ var init = async (options) => {
960
1104
  );
961
1105
  }
962
1106
  }
1107
+ const { syncCodex } = await promptCodexSync({
1108
+ providedSyncCodex: options.syncCodex
1109
+ });
1110
+ if (!syncCodex) {
1111
+ return;
1112
+ }
1113
+ logger.blank();
1114
+ logger.info("Syncing .claude skills/commands to Codex...");
1115
+ try {
1116
+ const result = await syncWithCodex(targetDir);
1117
+ if (result.syncedSkills > 0) {
1118
+ logger.step(`Skills synced: ${result.syncedSkills}`);
1119
+ }
1120
+ if (result.syncedCommands > 0) {
1121
+ logger.step(`Commands synced: ${result.syncedCommands}`);
1122
+ }
1123
+ if (result.syncedSkills === 0 && result.syncedCommands === 0) {
1124
+ logger.warn(
1125
+ "Nothing was synced. .claude/skills and .claude/commands were not found."
1126
+ );
1127
+ } else {
1128
+ logger.success(`Codex sync complete: ${result.codexSkillsDir}`);
1129
+ }
1130
+ } catch (error) {
1131
+ logger.warn(
1132
+ `Codex sync failed: ${error instanceof Error ? error.message : "Unknown error"}`
1133
+ );
1134
+ }
963
1135
  };
964
1136
 
965
1137
  // src/index.ts
966
1138
  var program = new Command();
967
- program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.6");
1139
+ program.name("claude-code").description("Claude Code documentation installer for projects").version("0.7.8");
968
1140
  program.option(
969
1141
  "-t, --template <names>",
970
1142
  "template names (comma-separated: tanstack-start,hono)"
971
- ).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").option("-s, --skills", "install skills to .claude/skills/").option("-c, --commands", "install commands to .claude/commands/").option("-a, --agents", "install agents to .claude/agents/").option("--scope <scope>", "installation scope (project|user)").action(async (options) => {
1143
+ ).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").option("-s, --skills", "install skills to .claude/skills/").option("-c, --commands", "install commands to .claude/commands/").option("-a, --agents", "install agents to .claude/agents/").option(
1144
+ "--sync-codex",
1145
+ "sync installed .claude skills/commands to Codex (~/.codex/skills/)"
1146
+ ).option("--scope <scope>", "installation scope (project|user)").action(async (options) => {
972
1147
  banner();
973
1148
  if (options.list) {
974
1149
  const templates = await listAvailableTemplates();
@@ -984,7 +1159,8 @@ program.option(
984
1159
  scope: options.scope,
985
1160
  skills: options.skills,
986
1161
  commands: options.commands,
987
- agents: options.agents
1162
+ agents: options.agents,
1163
+ syncCodex: options.syncCodex
988
1164
  });
989
1165
  });
990
1166
  program.parse();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kood/claude-code",
3
- "version": "0.7.6",
3
+ "version": "0.7.8",
4
4
  "description": "Claude Code documentation installer for projects",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
@@ -0,0 +1,199 @@
1
+ ---
2
+ name: design
3
+ description: UI/UX 디자인 전문 스킬. 사용자 중심 디자인, 접근성, 반응형 레이아웃, 디자인 시스템 구축. Nielsen 휴리스틱 및 WCAG 2.2 기반.
4
+ user-invocable: true
5
+ ui-only: true
6
+ ---
7
+
8
+ @../../instructions/agent-patterns/read-parallelization.md
9
+ @../../instructions/agent-patterns/agent-teams-usage.md
10
+ @../../instructions/validation/forbidden-patterns.md
11
+ @../../instructions/validation/required-behaviors.md
12
+
13
+ # Design Skill - UI/UX 전문 스킬
14
+
15
+ <context>
16
+
17
+ **Purpose:** 사용자 중심의 UI/UX 디자인 구현. 접근성, 반응형 디자인, 디자인 시스템 구축.
18
+
19
+ **Trigger:** UI/UX 디자인 구현, 랜딩 페이지 제작, 대시보드 개발, 모바일 앱 디자인, 폼 디자인
20
+
21
+ **Key Features:**
22
+ - Nielsen 10가지 휴리스틱 기반 UX
23
+ - WCAG 2.2 AA 접근성 준수
24
+ - 반응형 디자인 (Mobile-First)
25
+ - 디자인 심리학 적용 (인지부하 최소화, Fitts/Hick/Miller 법칙)
26
+ - 2024-2026 트렌드 반영 (AI 통합, 마이크로인터랙션, 다크모드)
27
+
28
+ </context>
29
+
30
+ ---
31
+
32
+ <quick_reference>
33
+
34
+ ## 빠른 참조
35
+
36
+ ### Core UX 원칙 (Nielsen)
37
+
38
+ | 원칙 | 적용 |
39
+ |------|------|
40
+ | **시스템 상태 가시성** | Loading, Progress, 피드백 |
41
+ | **일관성** | 패턴 재사용, 플랫폼 규칙 준수 |
42
+ | **오류 예방** | 확인 다이얼로그, 입력 제약 |
43
+ | **인지부하 최소화** | 7±2 항목, 단순 메뉴 |
44
+
45
+ ### 접근성 (WCAG 2.2 AA)
46
+
47
+ | 요구사항 | 기준 |
48
+ |---------|------|
49
+ | **색상 대비** | 4.5:1 (일반), 3:1 (큰 텍스트) |
50
+ | **터치 타겟** | 44x44px 최소 |
51
+ | **키보드** | Tab, Enter, Esc 지원 |
52
+ | **ARIA** | role, label, live 속성 |
53
+
54
+ ### 레이아웃 패턴
55
+
56
+ | 패턴 | 용도 |
57
+ |------|------|
58
+ | **F-패턴** | 콘텐츠 중심 (블로그, 기사) |
59
+ | **Z-패턴** | 랜딩 페이지 (히어로 → CTA) |
60
+ | **12열 그리드** | 반응형 레이아웃 |
61
+
62
+ </quick_reference>
63
+
64
+ ---
65
+
66
+ <parallel_agent_execution>
67
+
68
+ ### ⚠️ Agent Teams 우선 원칙
69
+
70
+ > **복잡한 병렬 작업 시 Agent Teams를 기본으로 사용**
71
+ > - Agent Teams 가용 → TeamCreate → 팀원 spawn → 병렬 협업
72
+ > - Agent Teams 미가용 → Task 병렬 호출 (폴백)
73
+
74
+ **Agent Teams 모드 (기본)**:
75
+ ```typescript
76
+ TeamCreate({ team_name: "design-team", description: "UI/UX 디자인 프로젝트" })
77
+ Task(subagent_type="designer", team_name="design-team", name="designer", ...)
78
+ ```
79
+
80
+ **수명주기 관리:**
81
+ - 팀원 태스크 완료 → 즉시 `shutdown_request` 전송
82
+ - 종료 후 `TaskList`로 다음 태스크 확인
83
+ - 모든 작업 완료 → `TeamDelete`로 팀 해산
84
+
85
+ ---
86
+
87
+ ## 병렬 에이전트 실행
88
+
89
+ ### 핵심 원칙
90
+
91
+ | 원칙 | 실행 방법 | 디자인 적용 |
92
+ |------|----------|------------|
93
+ | **PARALLEL** | 단일 메시지에서 여러 Tool 동시 호출 | 여러 페이지 동시 디자인 |
94
+ | **DELEGATE** | 전문 에이전트에게 즉시 위임 | designer (UI), code-reviewer (접근성) |
95
+ | **SMART MODEL** | 복잡도별 모델 선택 | haiku (탐색), sonnet (구현), opus (디자인 시스템) |
96
+
97
+ ### 패턴 1: 랜딩 페이지 섹션 병렬 디자인
98
+
99
+ ```typescript
100
+ // ✅ 5개 섹션 동시 구현
101
+ Task(subagent_type="designer", model="sonnet",
102
+ prompt="히어로 섹션: 헤드라인 + CTA + 이미지")
103
+ Task(subagent_type="designer", model="sonnet",
104
+ prompt="소셜 프루프 섹션: 로고, 추천사, 통계")
105
+ Task(subagent_type="designer", model="sonnet",
106
+ prompt="기능 섹션: 3열 그리드, 아이콘, 설명")
107
+ Task(subagent_type="designer", model="sonnet",
108
+ prompt="가격 테이블: 3개 티어, 비교 기능")
109
+ Task(subagent_type="designer", model="sonnet",
110
+ prompt="CTA 섹션: 최종 전환 유도")
111
+ ```
112
+
113
+ **예상 시간:** 순차 300초 → 병렬 60초 (5배 향상)
114
+
115
+ ### 패턴 2: 반응형 Breakpoint 병렬
116
+
117
+ ```typescript
118
+ // ✅ Mobile/Tablet/Desktop 동시 구현
119
+ Task(subagent_type="designer", model="sonnet",
120
+ prompt="Mobile (320-767px): 세로 스택, 44px 터치 타겟")
121
+ Task(subagent_type="designer", model="sonnet",
122
+ prompt="Tablet (768-1023px): 2열 그리드")
123
+ Task(subagent_type="designer", model="sonnet",
124
+ prompt="Desktop (1024px+): 3열 그리드, 호버 효과")
125
+ ```
126
+
127
+ ### 패턴 3: 접근성 검증 병렬
128
+
129
+ ```typescript
130
+ // ✅ WCAG/ARIA/키보드 동시 검증
131
+ Task(subagent_type="code-reviewer", model="opus",
132
+ prompt="WCAG 2.2 AA: 색상 대비, 포커스, 텍스트 크기")
133
+ Task(subagent_type="code-reviewer", model="opus",
134
+ prompt="ARIA 속성: role, label, live")
135
+ Task(subagent_type="code-reviewer", model="opus",
136
+ prompt="키보드 네비게이션: Tab, Enter, Esc")
137
+ ```
138
+
139
+ </parallel_agent_execution>
140
+
141
+ ---
142
+
143
+ <detailed_references>
144
+
145
+ ## 상세 참고 문서
146
+
147
+ 각 주제별 상세 가이드는 references 폴더를 참조하세요.
148
+
149
+ | 파일 | 내용 |
150
+ |------|------|
151
+ | **core-principles.md** | Nielsen 휴리스틱, Don Norman 원칙, Gestalt 원칙, 디자인 심리학 |
152
+ | **accessibility.md** | POUR 원칙, 색상 대비, 키보드, ARIA, 스크린 리더 |
153
+ | **patterns.md** | 랜딩 페이지, 대시보드, 폼, 모바일 앱 패턴 |
154
+ | **systems.md** | 타이포그래피, 컬러, 그리드, 반응형 디자인 |
155
+ | **responsive.md** | Container Queries, Fluid CSS, Modern CSS, 반응형 이미지 |
156
+ | **korea-uiux.md** | 한국형 UI/UX: 토스/카카오/배민/당근 디자인 시스템, 타이포(Pretendard), 결제 UX |
157
+ | **global-uiux.md** | 글로벌 UI/UX: Material 3, Apple HIG (Liquid Glass), Fluent 2, Carbon, SLDS |
158
+ | **safe-area.md** | Safe Area: iOS/Android Insets, Dynamic Island, Foldable, React Native, CSS env() |
159
+ | **checklist.md** | DO/DON'T 항목 |
160
+ | **trends.md** | 2024-2026 디자인 트렌드 |
161
+
162
+ **사용 방법:**
163
+ ```bash
164
+ # 디자인 원칙 확인
165
+ Read references/core-principles.md
166
+
167
+ # 접근성 가이드 확인
168
+ Read references/accessibility.md
169
+
170
+ # 랜딩 페이지 패턴 확인
171
+ Read references/patterns.md
172
+ ```
173
+
174
+ </detailed_references>
175
+
176
+ ---
177
+
178
+ <references>
179
+
180
+ ## 외부 참고 자료
181
+
182
+ | 분류 | 자료 |
183
+ |------|------|
184
+ | **UX 원칙** | Nielsen Norman Group, Don Norman "The Design of Everyday Things" |
185
+ | **접근성** | WCAG 2.2, W3C WAI, WebAIM |
186
+ | **글로벌 디자인 시스템** | Material Design 3 (Expressive), Apple HIG (Liquid Glass), Fluent 2, Carbon, SLDS |
187
+ | **한국 디자인 시스템** | 토스 TDS, 카카오 Krane, 쿠팡 RDS, 배민, 당근 |
188
+ | **심리학** | Laws of UX, Gestalt 원칙, 인지부하 이론 |
189
+ | **트렌드** | Awwwards, Dribbble, Behance, Smashing Magazine |
190
+
191
+ ### 외부 링크
192
+
193
+ - [Nielsen 10 Usability Heuristics](https://www.nngroup.com/articles/ten-usability-heuristics/)
194
+ - [WCAG 2.2 Guidelines](https://www.w3.org/WAI/standards-guidelines/wcag/)
195
+ - [Material Design 3](https://m3.material.io/)
196
+ - [Apple Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/)
197
+ - [Laws of UX](https://lawsofux.com/)
198
+
199
+ </references>
@@ -0,0 +1,106 @@
1
+ # Accessibility (WCAG 2.2 AA)
2
+
3
+ <pour_principles>
4
+
5
+ ## POUR 원칙
6
+
7
+ | 원칙 | 설명 | 구현 |
8
+ |------|------|------|
9
+ | **Perceivable** | 인지 가능 | 대체 텍스트, 자막, 색상 대비 |
10
+ | **Operable** | 조작 가능 | 키보드, 충분한 시간 |
11
+ | **Understandable** | 이해 가능 | 명확한 언어, 일관된 UI |
12
+ | **Robust** | 견고성 | 시맨틱 HTML, ARIA |
13
+
14
+ </pour_principles>
15
+
16
+ ---
17
+
18
+ <color_contrast>
19
+
20
+ ## 색상 대비 (WCAG 2.2)
21
+
22
+ | 유형 | 최소 비율 | 예시 |
23
+ |------|----------|------|
24
+ | **일반 텍스트** | 4.5:1 | 14px body text |
25
+ | **큰 텍스트** | 3:1 | 18pt+ 또는 14pt bold+ |
26
+ | **Interactive 요소** | 3:1 | 버튼, 아이콘 |
27
+
28
+ ```tsx
29
+ // ✅ 충분한 대비
30
+ <button className="bg-blue-600 text-white">Save</button>
31
+
32
+ // ❌ 불충분한 대비
33
+ <button className="bg-gray-100 text-gray-300">Save</button>
34
+ ```
35
+
36
+ </color_contrast>
37
+
38
+ ---
39
+
40
+ <keyboard_navigation>
41
+
42
+ ## 키보드 네비게이션
43
+
44
+ | 키 | 동작 | 적용 |
45
+ |---|------|------|
46
+ | **Tab** | 다음 요소 | tabindex 순서 |
47
+ | **Enter/Space** | 활성화 | 버튼, 링크 |
48
+ | **Esc** | 취소/닫기 | 모달, 드롭다운 |
49
+ | **↑↓** | 선택 이동 | 드롭다운, 슬라이더 |
50
+
51
+ ```tsx
52
+ // ✅ 키보드 접근 가능
53
+ <button
54
+ tabIndex={0}
55
+ onKeyDown={(e) => {
56
+ if (e.key === 'Enter' || e.key === ' ') {
57
+ handleClick()
58
+ }
59
+ }}
60
+ >
61
+ Click me
62
+ </button>
63
+ ```
64
+
65
+ </keyboard_navigation>
66
+
67
+ ---
68
+
69
+ <aria_attributes>
70
+
71
+ ## ARIA 속성
72
+
73
+ | 속성 | 용도 | 예시 |
74
+ |------|------|------|
75
+ | **role** | 역할 명시 | `role="navigation"` |
76
+ | **aria-label** | 레이블 제공 | `aria-label="Close menu"` |
77
+ | **aria-labelledby** | 레이블 ID 참조 | `aria-labelledby="title-id"` |
78
+ | **aria-expanded** | 확장 상태 | `aria-expanded={isOpen}` |
79
+ | **aria-live** | 동적 업데이트 | `aria-live="polite"` |
80
+
81
+ ```tsx
82
+ // ✅ ARIA 속성 사용
83
+ <button
84
+ aria-label="Close menu"
85
+ aria-expanded={isOpen}
86
+ aria-controls="menu-id"
87
+ >
88
+ <svg className="w-6 h-6" aria-hidden="true" />
89
+ </button>
90
+ ```
91
+
92
+ </aria_attributes>
93
+
94
+ ---
95
+
96
+ <screen_readers>
97
+
98
+ ## 스크린 리더 테스트
99
+
100
+ | 도구 | 플랫폼 | 용도 |
101
+ |------|--------|------|
102
+ | **VoiceOver** | macOS/iOS | Safari |
103
+ | **NVDA** | Windows | Firefox |
104
+ | **JAWS** | Windows | Chrome |
105
+
106
+ </screen_readers>
@@ -0,0 +1,41 @@
1
+ # Design Checklist
2
+
3
+ <do>
4
+
5
+ ## DO
6
+
7
+ | 항목 | 설명 |
8
+ |------|------|
9
+ | **사용자 중심** | Nielsen 휴리스틱, Don Norman 원칙 적용 |
10
+ | **접근성 우선** | WCAG 2.2 AA, 키보드/스크린 리더 지원 |
11
+ | **일관성** | 디자인 시스템, 패턴 재사용 |
12
+ | **반응형** | Mobile-First, 모든 Breakpoint 테스트 |
13
+ | **피드백** | Loading, Success, Error 상태 명확히 |
14
+ | **여백** | 8px 그리드, 충분한 breathing room |
15
+ | **대비** | 4.5:1 (일반), 3:1 (큰 텍스트) |
16
+ | **터치 타겟** | 44x44px 최소 (모바일) |
17
+ | **오류 예방** | 확인 다이얼로그, 입력 제약 |
18
+ | **테스트** | 실제 사용자, A/B 테스트 |
19
+
20
+ </do>
21
+
22
+ ---
23
+
24
+ <dont>
25
+
26
+ ## DON'T
27
+
28
+ | 항목 | 이유 |
29
+ |------|------|
30
+ | **색상만으로 정보 전달** | 색맹 사용자 고려 |
31
+ | **너무 많은 선택지** | Hick의 법칙 (7±2) |
32
+ | **작은 터치 타겟** | 손가락으로 클릭 어려움 |
33
+ | **자동 재생 미디어** | 접근성, 사용자 경험 저해 |
34
+ | **일관성 없는 패턴** | 학습 비용 증가 |
35
+ | **모호한 에러 메시지** | "오류 발생" 대신 구체적 설명 |
36
+ | **과도한 애니메이션** | 멀미, 성능 저하 |
37
+ | **플레이스홀더를 레이블로** | 입력 시 사라짐 |
38
+ | **지나친 혁신** | 표준 패턴 무시 |
39
+ | **접근성 무시** | 법적 문제, 사용자 배제 |
40
+
41
+ </dont>