@tienne/gestalt 0.99.0 → 0.101.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.
Files changed (157) hide show
  1. package/CLAUDE.md +6 -1
  2. package/dist/package.json +5 -3
  3. package/dist/plugin/research-sources/amplitude/SOURCE.md +28 -0
  4. package/dist/plugin/research-sources/app-store-reviews/SOURCE.md +25 -0
  5. package/dist/plugin/research-sources/app-store-search/SOURCE.md +25 -0
  6. package/dist/plugin/research-sources/tech-blog-search/SOURCE.md +27 -0
  7. package/dist/plugin/research-sources/uibowl/SOURCE.md +26 -0
  8. package/dist/plugin/research-sources/web-search/SOURCE.md +24 -0
  9. package/dist/plugin/skills/_shared/agent-delegation.md +30 -7
  10. package/dist/plugin/skills/_shared/agent-model.md +3 -1
  11. package/dist/plugin/skills/_shared/proactive-routing.md +5 -0
  12. package/dist/plugin/skills/brief/SKILL.md +5 -7
  13. package/dist/plugin/skills/explain/SKILL.md +4 -5
  14. package/dist/plugin/skills/jira-create/SKILL.md +3 -4
  15. package/dist/plugin/skills/market-scan/SKILL.md +61 -0
  16. package/dist/plugin/skills/pr/SKILL.md +4 -6
  17. package/dist/plugin/skills/presentation/SKILL.md +18 -16
  18. package/dist/plugin/skills/research/SKILL.md +438 -0
  19. package/dist/plugin/skills/research/references/design.md +938 -0
  20. package/dist/plugin/skills/research/references/output-template.md +366 -0
  21. package/dist/plugin/skills/research/scripts/pii-vision.swift +327 -0
  22. package/dist/plugin/skills/research/scripts/slide-extract.mjs +223 -0
  23. package/dist/plugin/skills/reverse-plan/SKILL.md +52 -0
  24. package/dist/plugin/skills/review/SKILL.md +20 -25
  25. package/dist/plugin/skills/review-reply/SKILL.md +4 -5
  26. package/dist/plugin/skills/screen-ref/SKILL.md +63 -0
  27. package/dist/plugin/skills/slack-send/SKILL.md +3 -4
  28. package/dist/plugin/skills/ux-research/SKILL.md +51 -0
  29. package/dist/src/core/config.d.ts +3 -0
  30. package/dist/src/core/config.d.ts.map +1 -1
  31. package/dist/src/core/config.js +4 -0
  32. package/dist/src/core/config.js.map +1 -1
  33. package/dist/src/mcp/schemas.d.ts +93 -0
  34. package/dist/src/mcp/schemas.d.ts.map +1 -1
  35. package/dist/src/mcp/schemas.js +110 -0
  36. package/dist/src/mcp/schemas.js.map +1 -1
  37. package/dist/src/mcp/server.d.ts.map +1 -1
  38. package/dist/src/mcp/server.js +11 -1
  39. package/dist/src/mcp/server.js.map +1 -1
  40. package/dist/src/mcp/tools/research-passthrough.d.ts +21 -0
  41. package/dist/src/mcp/tools/research-passthrough.d.ts.map +1 -0
  42. package/dist/src/mcp/tools/research-passthrough.js +566 -0
  43. package/dist/src/mcp/tools/research-passthrough.js.map +1 -0
  44. package/dist/src/research/canvas-view.d.ts +5 -0
  45. package/dist/src/research/canvas-view.d.ts.map +1 -0
  46. package/dist/src/research/canvas-view.js +274 -0
  47. package/dist/src/research/canvas-view.js.map +1 -0
  48. package/dist/src/research/evidence.d.ts +145 -0
  49. package/dist/src/research/evidence.d.ts.map +1 -0
  50. package/dist/src/research/evidence.js +50 -0
  51. package/dist/src/research/evidence.js.map +1 -0
  52. package/dist/src/research/figma-plan.d.ts +80 -0
  53. package/dist/src/research/figma-plan.d.ts.map +1 -0
  54. package/dist/src/research/figma-plan.js +527 -0
  55. package/dist/src/research/figma-plan.js.map +1 -0
  56. package/dist/src/research/figma-script.d.ts +19 -0
  57. package/dist/src/research/figma-script.d.ts.map +1 -0
  58. package/dist/src/research/figma-script.js +363 -0
  59. package/dist/src/research/figma-script.js.map +1 -0
  60. package/dist/src/research/index.d.ts +8 -0
  61. package/dist/src/research/index.d.ts.map +1 -0
  62. package/dist/src/research/index.js +8 -0
  63. package/dist/src/research/index.js.map +1 -0
  64. package/dist/src/research/pii-core.d.ts +16 -0
  65. package/dist/src/research/pii-core.d.ts.map +1 -0
  66. package/dist/src/research/pii-core.js +345 -0
  67. package/dist/src/research/pii-core.js.map +1 -0
  68. package/dist/src/research/pii-dom.d.ts +13 -0
  69. package/dist/src/research/pii-dom.d.ts.map +1 -0
  70. package/dist/src/research/pii-dom.js +208 -0
  71. package/dist/src/research/pii-dom.js.map +1 -0
  72. package/dist/src/research/pii-image.d.ts +71 -0
  73. package/dist/src/research/pii-image.d.ts.map +1 -0
  74. package/dist/src/research/pii-image.js +163 -0
  75. package/dist/src/research/pii-image.js.map +1 -0
  76. package/dist/src/research/pii.d.ts +21 -0
  77. package/dist/src/research/pii.d.ts.map +1 -0
  78. package/dist/src/research/pii.js +47 -0
  79. package/dist/src/research/pii.js.map +1 -0
  80. package/dist/src/research/product-profile.d.ts +108 -0
  81. package/dist/src/research/product-profile.d.ts.map +1 -0
  82. package/dist/src/research/product-profile.js +104 -0
  83. package/dist/src/research/product-profile.js.map +1 -0
  84. package/dist/src/research/render-html.d.ts +203 -0
  85. package/dist/src/research/render-html.d.ts.map +1 -0
  86. package/dist/src/research/render-html.js +901 -0
  87. package/dist/src/research/render-html.js.map +1 -0
  88. package/dist/src/research/schema-base.d.ts +5 -0
  89. package/dist/src/research/schema-base.d.ts.map +1 -0
  90. package/dist/src/research/schema-base.js +10 -0
  91. package/dist/src/research/schema-base.js.map +1 -0
  92. package/dist/src/research/schema.d.ts +5142 -0
  93. package/dist/src/research/schema.d.ts.map +1 -0
  94. package/dist/src/research/schema.js +991 -0
  95. package/dist/src/research/schema.js.map +1 -0
  96. package/dist/src/research/source-create.d.ts +79 -0
  97. package/dist/src/research/source-create.d.ts.map +1 -0
  98. package/dist/src/research/source-create.js +226 -0
  99. package/dist/src/research/source-create.js.map +1 -0
  100. package/dist/src/research/source-registry.d.ts +29 -0
  101. package/dist/src/research/source-registry.d.ts.map +1 -0
  102. package/dist/src/research/source-registry.js +74 -0
  103. package/dist/src/research/source-registry.js.map +1 -0
  104. package/dist/src/research/source-schema.d.ts +158 -0
  105. package/dist/src/research/source-schema.d.ts.map +1 -0
  106. package/dist/src/research/source-schema.js +215 -0
  107. package/dist/src/research/source-schema.js.map +1 -0
  108. package/dist/src/research/source-status.d.ts +40 -0
  109. package/dist/src/research/source-status.d.ts.map +1 -0
  110. package/dist/src/research/source-status.js +109 -0
  111. package/dist/src/research/source-status.js.map +1 -0
  112. package/dist/src/research/store.d.ts +24 -0
  113. package/dist/src/research/store.d.ts.map +1 -0
  114. package/dist/src/research/store.js +60 -0
  115. package/dist/src/research/store.js.map +1 -0
  116. package/dist/src/research/target-view.d.ts +33 -0
  117. package/dist/src/research/target-view.d.ts.map +1 -0
  118. package/dist/src/research/target-view.js +133 -0
  119. package/dist/src/research/target-view.js.map +1 -0
  120. package/dist/src/research/target.d.ts +883 -0
  121. package/dist/src/research/target.d.ts.map +1 -0
  122. package/dist/src/research/target.js +323 -0
  123. package/dist/src/research/target.js.map +1 -0
  124. package/dist/src/research/video-transfer.d.ts +28 -0
  125. package/dist/src/research/video-transfer.d.ts.map +1 -0
  126. package/dist/src/research/video-transfer.js +100 -0
  127. package/dist/src/research/video-transfer.js.map +1 -0
  128. package/package.json +5 -3
  129. package/plugin/.codex-plugin/plugin.json +1 -1
  130. package/plugin/.mcp.json +1 -1
  131. package/plugin/mcp.json +1 -1
  132. package/plugin/research-sources/amplitude/SOURCE.md +28 -0
  133. package/plugin/research-sources/app-store-reviews/SOURCE.md +25 -0
  134. package/plugin/research-sources/app-store-search/SOURCE.md +25 -0
  135. package/plugin/research-sources/tech-blog-search/SOURCE.md +27 -0
  136. package/plugin/research-sources/uibowl/SOURCE.md +26 -0
  137. package/plugin/research-sources/web-search/SOURCE.md +24 -0
  138. package/plugin/skills/_shared/agent-delegation.md +30 -7
  139. package/plugin/skills/_shared/agent-model.md +3 -1
  140. package/plugin/skills/_shared/proactive-routing.md +5 -0
  141. package/plugin/skills/brief/SKILL.md +5 -7
  142. package/plugin/skills/explain/SKILL.md +4 -5
  143. package/plugin/skills/jira-create/SKILL.md +3 -4
  144. package/plugin/skills/market-scan/SKILL.md +61 -0
  145. package/plugin/skills/pr/SKILL.md +4 -6
  146. package/plugin/skills/presentation/SKILL.md +18 -16
  147. package/plugin/skills/research/SKILL.md +438 -0
  148. package/plugin/skills/research/references/design.md +938 -0
  149. package/plugin/skills/research/references/output-template.md +366 -0
  150. package/plugin/skills/research/scripts/pii-vision.swift +327 -0
  151. package/plugin/skills/research/scripts/slide-extract.mjs +223 -0
  152. package/plugin/skills/reverse-plan/SKILL.md +52 -0
  153. package/plugin/skills/review/SKILL.md +20 -25
  154. package/plugin/skills/review-reply/SKILL.md +4 -5
  155. package/plugin/skills/screen-ref/SKILL.md +63 -0
  156. package/plugin/skills/slack-send/SKILL.md +3 -4
  157. package/plugin/skills/ux-research/SKILL.md +51 -0
@@ -0,0 +1,938 @@
1
+ # 리서치 기능 설계
2
+
3
+ 경쟁사와 시장 레퍼런스를 조사하고 역기획까지 하는 research 기능의 설계 문서입니다. 2026-10-16 기준의 합의 상태를 적었습니다. 결과물의 섹션별 규칙은 [output-template.md](./output-template.md)가 갖고 이 문서는 그 바깥의 구조, 도구, 운영 규칙을 다룹니다.
4
+
5
+ ## 상태 표기
6
+
7
+ 절마다 아래 세 라벨 중 하나를 답니다. 한 절 안에서 라벨이 갈리면 항목마다 따로 답니다.
8
+
9
+ | 라벨 | 뜻 |
10
+ | ---- | ----------------------------------------------- |
11
+ | 정함 | 사용자와 합의가 끝났습니다 |
12
+ | 제안 | 제가 추천했고 사용자가 아직 확인하지 않았습니다 |
13
+ | 열림 | 선택지만 있고 정하지 않았습니다 |
14
+
15
+ ## 1. 목적
16
+
17
+ 상태: 정함
18
+
19
+ 경쟁사와 시장 레퍼런스를 조사하고 역기획까지 해서 기획 판단의 근거를 만듭니다. 목표는 동료가 스킬 하나로 한 번에 만든 Figma 페이지 수준의 결과물입니다.
20
+
21
+ 기준 결과물은 [output-template.md](./output-template.md)로 뽑았습니다. 영상 기반 리서치 내용은 템플릿에 합쳐서도 쓰고 따로도 쓸 수 있게 했습니다([8.5절](#85-합치기와-분할)).
22
+
23
+ ## 2. 리서치 종류와 구성
24
+
25
+ ### 2.1 경우 5개
26
+
27
+ 상태: 정함
28
+
29
+ | 경우 | 단계 | 채우는 섹션 |
30
+ | -------------- | ---------------------------------------------- | ----------- |
31
+ | UX 리서치만 | `ux-research` | 4 |
32
+ | 경쟁사 화면만 | 화면 수집 | 관찰 자료 |
33
+ | 역기획 | 화면 수집, `reverse-plan` | 5 |
34
+ | 제품 지형 조사 | `market-scan`([3절](#3-제품-찾기와-화면-지도)) | 1 |
35
+ | 영상 분석만 | 영상 처리([8절](#8-영상-처리)) | 영상 분석 |
36
+
37
+ 경쟁사 화면만 모으는 경우에도 목표 지표는 먼저 받습니다. 지표에 따라 모을 화면이 달라지기 때문입니다. 전환율이면 결제 화면까지, 탐색이면 목록과 검색 화면까지 봅니다.
38
+
39
+ 단계마다 스킬과 에이전트를 따로 두고 최상위 `research` 스킬이 사용자의 의도에 따라 조합합니다. 캡처 방식은 앱 서비스냐 웹 서비스냐에 따라 달라집니다([6절](#6-화면-출처-순서)).
40
+
41
+ ### 2.2 조합 확인
42
+
43
+ 상태: 정함
44
+
45
+ research는 단계를 돌리기 전에 고른 조합을 사용자에게 보여주고 확인을 받습니다. 의도를 잘못 읽으면 캡처와 분석 비용이 통째로 낭비되기 때문입니다. 11절의 현재 추천대로 이 방식으로 갑니다. 3절의 제품 목록과 화면 지도 확인([3.3절](#33-사용자-확인))도 이 자리에서 같이 받습니다.
46
+
47
+ ### 2.3 첫 버전의 범위
48
+
49
+ 상태: 정함
50
+
51
+ 첫 버전은 `research` 스킬 하나가 0번부터 8번까지 한 번에 돌립니다. 단계 스킬 4개(`market-scan`, `screen-ref`, `ux-research`, `reverse-plan`)는 그 뒤에 분리했습니다. 원래 계획은 원본 페이지와 비교한 뒤 분리하는 것이었습니다. 비교 평가를 돌리지 못한 채 분리했고([9.8절](#98-원본-페이지와-구조-비교)) 이 사실은 PR 본문에도 적습니다.
52
+
53
+ 기존 `researcher` 역할 에이전트(`plugin/role-agents/researcher`)는 관점 전용이라 그대로 둡니다. 화면 수집이나 영상 처리는 이 에이전트의 일이 아닙니다.
54
+
55
+ ## 3. 제품 찾기와 화면 지도
56
+
57
+ 상태: 제안. 앱스토어 검색 API, 앱스토어 리뷰 RSS, 기술 블로그 검색만 확인됨(9.4절, 9.5절).
58
+
59
+ 리서치는 두 단계로 대상을 좁힙니다. 1단계에서 어떤 제품을 볼지 정하고 2단계에서 그 제품 안의 어떤 화면을 볼지 정합니다. 여기서 잘못 고르면 뒤 단계인 캡처, 비교, 역기획이 전부 헛돕니다. 사용자는 이 절을 리서치의 또 다른 핵심으로 봅니다. 아직 사용자가 확정하지 않았습니다.
60
+
61
+ ### 3.1 1단계 제품 찾기
62
+
63
+ 대상은 두 축으로 찾습니다.
64
+
65
+ | 축 | 뜻 | 예 |
66
+ | --------- | ----------------------------------- | ------------------------------------------------------------- |
67
+ | 같은 시장 | 직접 경쟁 제품 | 상품권 목록 과제라면 서비스 B의 선물 기능, 서비스 C, 서비스 F |
68
+ | 같은 문제 | 업종은 달라도 같은 문제를 푸는 화면 | 서비스 A의 순위 리스트, 브랜드가 많은 목록을 정리한 화면 |
69
+
70
+ [output-template.md](./output-template.md)의 1번이 레퍼런스를 "A. 서비스 A 기준: 순위 리스트와 브랜드 목록형"처럼 패턴 유형으로 고르는 이유가 두 번째 축입니다. 직접 경쟁사만 보면 비슷한 화면만 나옵니다.
71
+
72
+ 찾는 출처는 아래와 같습니다. 출처마다 상태가 다릅니다.
73
+
74
+ | 출처 | 잘 찾는 것 | 비고 | 상태 |
75
+ | ----------------------------- | ----------------------------------- | ------------------------------------------------------------------------------------------------------------ | ---------- |
76
+ | 사용자 입력 | 이미 아는 경쟁사 | 제일 먼저 묻습니다 | 제안 |
77
+ | 앱스토어 검색 API | 사용자가 기능을 부르는 말로 찾는 앱 | 검색어를 넓히면 큰 앱도 잡힙니다 | 확인됨 |
78
+ | 앱스토어 리뷰 RSS | 스토어 검색에 안 나오는 경쟁 상대 | 리뷰에 언급된 다른 서비스 이름을 셉니다 | 확인됨 |
79
+ | 회사 기술 블로그 | 큰 앱 안의 기능 | 도메인만 대상으로 웹 검색합니다 | 확인됨 |
80
+ | Google Play | 안드로이드 쪽 같은 시장 앱 | 공식 검색 API가 없어서 웹 검색 페이지를 브라우저로 봅니다 | 확인 안 함 |
81
+ | 유아이볼 패턴과 컴포넌트 검색 | 같은 문제 축 | 업종과 상관없이 찾고 MAU가 같이 나옵니다. 앱 존재 여부는 사이트맵으로 확인합니다([7.2절](#72-브라우저-경로)) | 제안 |
82
+ | 계정 기반 서비스 | 규모(MAU) 확인 | 계정이 있을 때만 씁니다 | 확인 안 함 |
83
+
84
+ #### 앱스토어 검색
85
+
86
+ 앱스토어 검색 API는 키 없이 호출됩니다.
87
+
88
+ ```text
89
+ https://itunes.apple.com/search?term=<검색어>&country=kr&entity=software
90
+ ```
91
+
92
+ 결과에 장르, 평점 수(`userRatingCount`), `bundleId`가 있습니다. 평점 수로 규모를 가늠하고 `bundleId`는 이후 앱을 실행할 때 번들 ID로 씁니다.
93
+
94
+ 검색어를 넓히면 큰 앱도 잡힙니다. 사용자가 그 기능을 부르는 말로 검색하면 됩니다. 2026-10-16에 검색어를 바꿔 다시 돌린 결과입니다.
95
+
96
+ | 검색어 | 새로 나온 것 |
97
+ | ---------- | ------------------------------------------ |
98
+ | 상품권 | 앱 6개(상품권 전문 앱과 결제 앱 포함) |
99
+ | 기프티콘 | 앱 3개(거래 앱과 사진첩 쿠폰 스캔 앱 포함) |
100
+ | 모바일쿠폰 | 앱 3개 |
101
+ | 선물하기 | 큰 플랫폼 앱 2개 |
102
+
103
+ 큰 플랫폼 앱이 나와도 그 기능이 앱 어디에 있는지는 검색 결과로 알 수 없습니다. 그건 [3.2절](#32-2단계-화면-지도)의 화면 지도에서 찾습니다.
104
+
105
+ 그래도 큰 앱 4곳(서비스 G, H, I, J) 안의 선물 기능은 어떤 검색어로도 안 나왔습니다. 이 빈자리는 아래 기술 블로그로 메웁니다.
106
+
107
+ 노이즈도 섞입니다. 리워드 앱, 게임 앱, 프랜차이즈 매장 앱, 폰케이스 쇼핑 앱 같은 앱이 나왔습니다. 장르와 앱 설명에 키워드가 있는지로 먼저 거르고 남은 건 LLM이 판단합니다.
108
+
109
+ #### 리뷰에서 경쟁사 뽑기
110
+
111
+ 앱스토어 리뷰 RSS는 키 없이 열립니다.
112
+
113
+ ```text
114
+ https://itunes.apple.com/kr/rss/customerreviews/page=1/id=<앱ID>/sortby=mostrecent/json
115
+ ```
116
+
117
+ 서비스 K의 최근 리뷰에서 다른 서비스 이름을 세어보니 상품권 앱 2곳, 대형 플랫폼 1곳, 중고거래 앱 2곳이 나왔습니다. 사용자가 기프티콘을 중고거래 앱에서도 사고판다는 뜻이라, 스토어 검색으로는 안 나오는 경쟁 상대입니다.
118
+
119
+ 수집은 반복합니다. 상위 후보의 리뷰에서 새 이름을 뽑고 그 앱의 리뷰를 다시 봅니다. 새 이름이 안 나오면 멈춥니다. 한계는 표본입니다. 한 번에 최근 리뷰 50개 정도만 줘서 작습니다.
120
+
121
+ #### 기술 블로그
122
+
123
+ 회사 기술 블로그 도메인 목록을 두고 그 도메인만 대상으로 웹 검색합니다. "선물하기 상품권 기프티콘"으로 한 번 검색했더니 아래 글이 나왔습니다. 앱스토어 검색에 하나도 안 나온 곳들입니다.
124
+
125
+ | 회사 | 나온 글 |
126
+ | -------- | ---------------------------------------------------------------------------------------------------------------- |
127
+ | 서비스 G | 선물 기능의 재고 관리 설계, AI 선물 메시지, 앱 A에서 받은 교환권을 앱 B 주문으로 이어주는 흐름, 시즌 이벤트(4편) |
128
+ | 서비스 H | 선물 기능 회고 |
129
+ | 서비스 I | 선물 기능 개발기 |
130
+ | 서비스 J | 선물 기능 UX 개선 |
131
+
132
+ 글 주소는 이 문서에 싣지 않습니다.
133
+
134
+ 기술 블로그는 제품 찾기, 역기획 근거, 화면 지도에 쓰입니다.
135
+
136
+ - 제품 찾기에서는 큰 앱 안의 기능을 찾을 때 가장 잘 맞습니다.
137
+ - 역기획 근거로는 이유를 줍니다. 화면은 무엇을 했는지만 보여주고 블로그에는 왜 했는지가 있습니다. 예를 들어 회사 글에 추천 문구를 그대로 쓴 비율이 실려 있을 수 있습니다. 이런 수치는 [5.2절](#52-근거-상태)의 회사 발언으로 받고 글 날짜를 같이 적습니다. 세미나와 같은 취급입니다.
138
+ - 화면 지도에서는 앱 사이를 건너가는 흐름을 줍니다. "앱 A에서 받은 교환권을 앱 B 주문으로 이어주는 흐름" 같은 것은 앱 하나만 탐색해서는 못 찾습니다.
139
+
140
+ RSS는 주제로 찾는 데 안 맞습니다. 확인한 회사 블로그 9곳 중 5곳은 최근 10개에서 20개만 줬고 1곳은 전체 글을 줬습니다. 3곳은 짐작한 주소가 안 열렸습니다.
141
+
142
+ 도메인 목록의 출처 후보는 아래와 같습니다.
143
+
144
+ | 후보 | 비고 |
145
+ | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------- |
146
+ | awesome-devblog (`https://github.com/awesome-devblog/awesome-devblog`) | `db.yml`은 대부분 개인 블로그이고 RSS가 1,735개입니다. 회사 목록 위치는 확인 안 함 |
147
+ | velopers | 확인 안 함 |
148
+ | techblogposts.com/ko | 확인 안 함 |
149
+ | Engineering.fyi | 검색 API가 있는지 확인 안 함 |
150
+
151
+ 조심할 점이 있습니다. 글이 오래됐을 수 있으니 날짜를 꼭 적고 지금 화면과 다르면 표시합니다. 회사에 유리한 얘기가 많아서 수치는 회사 발언으로만 받습니다. 기술 블로그는 백엔드 글이 많으므로 디자인 블로그와 미디엄 디자인 매체도 도메인 목록에 넣습니다.
152
+
153
+ #### 계정 기반 서비스
154
+
155
+ 유아이볼처럼 계정이 붙어 있을 때만 쓰는 출처입니다. 웹 검색 결과를 정리한 것이고 직접 써보지는 않았습니다. 상태는 확인 안 함입니다.
156
+
157
+ | 서비스 | 찾을 수 있는 것 | 걸리는 점 |
158
+ | -------------------------------------------------------------------- | -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
159
+ | 와이즈앱 (`https://guide.wiseapp.co.kr/wiseapp`) | 한국인이 많이 쓰는 앱 8,000여 개의 사용 데이터, 표본 약 8만 명 | 무료로 쓸 만하다는 커뮤니티 의견이 있습니다 |
160
+ | 모바일인덱스(아이지에이웍스) | 경쟁 앱 비교와 사용 현황. 구글플레이, 앱스토어, 원스토어 통합 | 요금을 못 찾았습니다 |
161
+ | 혁신의숲 | 스타트업 15,000여 곳을 키워드와 카테고리로 찾기, 36개월 MAU | 스타트업 위주입니다 |
162
+ | Sensor Tower | 앱 시장 데이터 | 연 $29,500에서 $115,000, API는 따로 연 $5,000에서 $20,000입니다(creatify.ai 2026 가격 가이드, Vendr와 SpendHound 인용). 한국 데이터가 약하다는 의견이 있습니다 |
163
+ | AppTweak | 앱 스토어 데이터 | 커뮤니티가 만든 MCP 서버가 있지만 유료 API 키가 필요하고 경쟁사를 처음부터 찾는 도구는 없습니다 |
164
+ | Similarweb (`https://developers.similarweb.com/docs/similarweb-mcp`) | 웹 서비스 리서치. 공식 MCP(`mcp.similarweb.com`)가 있습니다 | API를 포함한 유료 플랜이 필요합니다 |
165
+
166
+ 이 서비스들은 후보를 늘리는 쪽보다 규모(MAU)를 확인하는 데 쓸모가 큽니다. 앱스토어 평점 수는 규모를 대충 짐작하는 정도라서입니다. 해외 유료 도구는 이번 용도에 과합니다.
167
+
168
+ 이 표의 서비스는 사람마다 계정이 있을 수도 없을 수도 있어서 [4절](#4-출처-정의와-추가)의 출처 구조로 붙입니다. 계정이 있는 사람이 SOURCE.md를 추가하면 규모 확인에 쓰이고 없으면 건너뜁니다.
169
+
170
+ #### 제안 흐름
171
+
172
+ 1. 사용자 입력과 과제 설명에서 검색어를 여러 개 만듭니다. 같은 기능을 부르는 말과 사용자 표현을 함께 씁니다.
173
+ 2. 검색어마다 앱스토어 검색을 돌려 결과를 합칩니다.
174
+ 3. 장르와 앱 설명으로 노이즈를 거릅니다.
175
+ 4. 상위 후보의 리뷰에서 경쟁사 이름을 뽑습니다. 새 이름이 안 나올 때까지 반복합니다.
176
+ 5. 기술 블로그 검색으로 큰 앱 안의 기능 후보를 보탭니다.
177
+ 6. 유아이볼로 같은 문제 축 후보를 보탭니다. 계정이 있으면 와이즈앱이나 모바일인덱스로 규모를 확인합니다.
178
+ 7. 후보마다 찾은 경로(검색어, 리뷰 언급 횟수, 블로그 글, 유아이볼)와 규모를 붙여 3개에서 5개로 줄여서 보여줍니다.
179
+
180
+ 후보는 0번 전제의 목표 지표로 거릅니다. 템플릿대로 3개에서 5개만 남기고 고른 이유를 한 줄씩 붙입니다.
181
+
182
+ ### 3.2 2단계 화면 지도
183
+
184
+ 제품을 정해도 관련 화면이 앱 어디에 있는지 모르면 캡처할 수 없습니다. 그래서 제품마다 화면 지도를 먼저 만듭니다.
185
+
186
+ | 출처 | 알아내는 것 |
187
+ | ------------------------------------- | ------------------------------------------------------------------------------- |
188
+ | 유아이볼 `/name/<앱>?patterns=<패턴>` | 이미 수집된 관련 화면 |
189
+ | 앱 직접 탐색(에뮬레이터나 실기기) | 앱 안 검색창에 키워드를 넣고, 전체 메뉴를 열어 진입 경로를 찾습니다 |
190
+ | `site:` 검색(웹 서비스) | 관련 페이지 URL |
191
+ | 유튜브 "<앱> <기능> 사용법" | 사용자가 실제로 지나가는 흐름 |
192
+ | 스토어 스크린샷 | 회사가 내세우는 핵심 화면 |
193
+ | 회사 기술 블로그 | 앱 사이를 건너가는 흐름(예: 앱 A에서 받은 교환권을 앱 B 주문으로 이어주는 흐름) |
194
+
195
+ 화면 지도는 아래 꼴입니다. 예시는 꼴을 보이려고 지어낸 것이고 실제 서비스 C의 경로를 확인한 결과가 아닙니다.
196
+
197
+ | 화면 | 진입 경로 | 출처 | 캡처 |
198
+ | ----------- | -------------------- | -------- | ------------------ |
199
+ | 상품권 홈 | 홈, 전체메뉴, 상품권 | 앱 탐색 | 함 |
200
+ | 브랜드 목록 | 상품권 홈, 브랜드 탭 | 유아이볼 | 유아이볼 이미지 |
201
+ | 구매 확인 | 상세, 구매하기 | 없음 | 결제 직전이라 멈춤 |
202
+
203
+ 앱 탐색으로 찾은 경로는 Maestro flow로 남겨서 다음 리서치 때 같은 화면을 다시 찍습니다. 앱이 업데이트되면 flow가 깨지는데, 깨진 것 자체가 화면이 바뀌었다는 신호입니다. 캡처 출처는 [6절](#6-화면-출처-순서)의 순서를 따릅니다.
204
+
205
+ ### 3.3 사용자 확인
206
+
207
+ 1단계의 제품 목록과 2단계의 화면 지도를 묶어서 "이 제품들의 이 화면들을 보겠다"로 캡처 전에 한 번 확인받습니다. [2.2절](#22-조합-확인)의 조합 확인과 같은 자리에서 한 번에 받습니다.
208
+
209
+ ## 4. 출처 정의와 추가
210
+
211
+ 상태: 제안. 기존 구조 설명(4.1절)만 확인됨이고 나머지는 설계입니다.
212
+
213
+ 유료 플랜이나 계정 기반 출처(모바일인덱스, 와이즈앱, Similarweb 같은 것)는 사람마다 있을 수도 없을 수도 있습니다. 그래서 게슈탈트 에이전트처럼 사용자가 출처를 직접 추가할 수 있게 설계합니다. 이 방향은 사용자가 승인했습니다. 3.1절의 [계정 기반 서비스](#계정-기반-서비스) 표는 이 구조로 붙이는 출처의 후보 목록입니다.
214
+
215
+ 유아이볼은 "MCP가 있으면 MCP, 없으면 브라우저"로 쓰는 내장 출처의 대표 예시입니다([7절](#7-유아이볼)). 화면 캡처 출처의 순서는 [6절](#6-화면-출처-순서)에 있고 이 절은 그 앞에서 제품 후보, 규모, 화면 지도, 근거를 찾는 출처를 다룹니다.
216
+
217
+ ### 4.1 위치와 읽는 순서
218
+
219
+ 출처는 기존 역할 에이전트 구조를 따릅니다. `src/agent/role-agent-registry.ts`의 `RoleAgentRegistry.loadAll()`은 내장 디렉토리를 먼저 읽고 커스텀 디렉토리를 나중에 읽습니다. 이름이 같으면 나중에 읽은 쪽이 덮어씁니다. 에이전트 하나는 `<이름>/AGENT.md` 파일 하나이고 frontmatter에 메타데이터, 본문에 지시문이 있습니다. 새 에이전트는 `ges_create_agent`로 만듭니다.
220
+
221
+ | 순서 | 위치 | 들어가는 것 | 상태 |
222
+ | ---- | --------------------------------------------------- | ----------------------------------------------------------------- | ---- |
223
+ | 1 | 내장 `plugin/research-sources/<이름>/SOURCE.md` | 앱스토어 검색, 앱스토어 리뷰, 기술 블로그 검색, 유아이볼, 웹 검색 | 제안 |
224
+ | 2 | 개인 `~/.gestalt/research-sources/<이름>/SOURCE.md` | 계정이 사람마다 달라서 레포 밖에 둡니다 | 정함 |
225
+ | 3 | 팀 공용 `<레포>/.gestalt/research-sources/` | 다음 버전으로 미룹니다(4.6절의 보안 이유, 11절) | 정함 |
226
+
227
+ 이름이 같으면 개인 쪽이 이깁니다. 첫 버전은 내장과 개인만 둡니다.
228
+
229
+ ### 4.2 SOURCE.md 형식
230
+
231
+ frontmatter 필드는 아래와 같습니다.
232
+
233
+ | 필드 | 뜻 | 예 |
234
+ | ------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
235
+ | `name` | 출처 이름 | `mobileindex` |
236
+ | `description` | 한 줄 설명 | 국내 앱 MAU와 경쟁 앱 사용 현황 |
237
+ | `stage` | 결과가 들어가는 단계입니다. 여러 개를 고를 수 있습니다 | `product-discovery`(3.1절의 후보), `scale`(규모), `screen-map`(3.2절의 화면 지도), `evidence`(근거) |
238
+ | `axis` | 어느 축을 찾는지 | `same-market` 또는 `same-problem` |
239
+ | `access` | 접근 방법 목록입니다. 위에서부터 시도합니다 | `mcp`(서버 이름과 도구), `browser`(사이트와 로그인 주체 human), `http`(키 없는 공개 API), `env`(API 키가 든 환경변수 이름만 적음) |
240
+ | `requires` | 쓸 수 있는지 판정하는 조건입니다. MCP 서버 연결, 환경변수 유무, 브라우저 세션 로그인 여부를 봅니다 | `browserLogin: mobileindex.com` |
241
+ | `evidence` | 결과를 받을 근거 상태입니다([5.2절](#52-근거-상태)) | `observation`(관찰), `company-statement`(회사 발언), `estimate`(추정) |
242
+ | `limits` | 요금제나 속도 제한입니다. 결과물 0번 전제의 근거 한계로 옮겨 적습니다([5.3절](#53-0번-전제의-근거-한계)) | 요금제에 따라 볼 수 있는 앱 수가 다름 |
243
+
244
+ `estimate`는 규모처럼 제3자가 낸 추정치입니다. 근거 상태 표에는 따로 두지 않습니다. SOURCE.md에서는 받아 주되 읽을 때 해석으로 바꿉니다(11절).
245
+
246
+ 본문에는 두 절을 씁니다. "찾는 법"에는 단계별 절차와 URL 패턴을 적고 "멈추는 곳"에는 결제, 플랜 업그레이드, 로그인 화면을 적습니다. 운영 규칙(사람 속도로 보기, 결제 전에 멈추기, 로그인은 사람이 하기)은 [10절](#10-운영-규칙)에 있고 SOURCE.md 본문에도 그대로 적용됩니다.
247
+
248
+ 아래는 모바일인덱스 SOURCE.md 예시입니다. 실제로 써본 게 아니라 꼴을 보이려고 만든 예시입니다.
249
+
250
+ ```markdown
251
+ ---
252
+ name: mobileindex
253
+ description: 국내 앱 MAU와 경쟁 앱 사용 현황
254
+ stage: [scale, product-discovery]
255
+ axis: same-market
256
+ access:
257
+ - browser: { site: mobileindex.com, login: human }
258
+ requires:
259
+ browserLogin: mobileindex.com
260
+ evidence: estimate
261
+ limits: '요금제에 따라 볼 수 있는 앱 수가 다름'
262
+ ---
263
+
264
+ ## 찾는 법
265
+
266
+ 1. 앱 이름으로 검색해 상세 페이지를 연다.
267
+ 2. 최근 3개월 MAU와 함께 쓰는 앱 목록을 읽는다.
268
+
269
+ ## 멈추는 곳
270
+
271
+ 결제나 플랜 업그레이드 화면이 나오면 멈춘다.
272
+ ```
273
+
274
+ ### 4.3 리서치를 시작할 때
275
+
276
+ `ges_research action=sources`가 출처 목록과 상태를 보여줍니다. 예시는 아래와 같습니다.
277
+
278
+ | 출처 | 단계 | 상태 |
279
+ | ------------- | --------------- | ------------------------- |
280
+ | 앱스토어 검색 | 후보 | 사용 가능 |
281
+ | 유아이볼 | 후보, 화면 지도 | MCP 미연결이라 브라우저로 |
282
+ | 모바일인덱스 | 규모 | 로그인 안 됨이라 건너뜀 |
283
+
284
+ 리서치 스킬은 단계마다 쓸 수 있는 출처만 돌립니다. 건너뛴 출처는 0번 전제의 근거 한계에 자동으로 적힙니다. 예를 들어 "모바일인덱스 미연결이라 규모는 앱스토어 평점 수로 짐작"처럼 적습니다. 그래서 사람마다 결과가 왜 다른지가 결과물에 남습니다. 이 기록은 [5.3절](#53-0번-전제의-근거-한계)의 근거 한계 목록에 한 줄로 들어갑니다.
285
+
286
+ ### 4.4 출처 추가하기
287
+
288
+ 추가하는 방법은 두 가지입니다.
289
+
290
+ - SOURCE.md를 직접 씁니다.
291
+ - `ges_research action=create_source`로 몇 가지를 묻고 SOURCE.md를 만듭니다. `ges_create_agent`와 같은 방식입니다. 사이트 주소, 로그인 방법, 찾을 수 있는 것, 멈춰야 하는 화면을 묻습니다. 만든 뒤 한 번 시험으로 돌려서 상태가 "사용 가능"으로 뜨는지 확인합니다.
292
+
293
+ 구현은 아래와 같이 동작합니다.
294
+
295
+ - `source` 없이 부르면 물어볼 목록(`questions`)과 예시(`example`)를 돌려주고 아무것도 쓰지 않습니다. 세션이 사용자에게 묻고 답을 `source`에 담아 다시 부릅니다.
296
+ - `source`를 주면 SOURCE.md 문자열을 만든 뒤 읽는 쪽과 같은 검사기(`parseSourceMd`)로 다시 읽어 확인합니다. 이름 형식, 공개 호스트, 비밀번호와 키 값, 필수 절(찾는 법, 멈추는 곳)이 여기서 걸립니다. 줄바꿈으로 본문 구조를 끼워 넣는 입력은 한 줄로 합칩니다.
297
+ - 쓰는 곳은 `~/.gestalt/research-sources/<name>/SOURCE.md`뿐입니다. 같은 이름의 개인 출처나 내장 출처가 있으면 `overwrite`를 켜기 전에는 거절합니다. 내장 출처를 덮어쓰면 결과의 `overridesBuiltin`과 경고에 적습니다.
298
+ - 쓰고 나면 레지스트리를 다시 읽어 `usable`, `fallback`, `skipped`를 판정합니다. 접근 방법에 `http`와 `probe`가 있으면 그 주소를 한 번 GET합니다. https만 부르고 호스트가 사설 주소로 풀리면 부르지 않습니다. 리다이렉트는 따라가지 않고 본문도 읽지 않습니다. probe가 실패해도 파일은 남기고 경고로 알립니다.
299
+ - DNS를 확인한 뒤 fetch가 다시 이름을 푸는 사이에 주소가 바뀌는 경우는 막지 못합니다. 개인 디렉토리의 파일을 본인이 쓰는 도구라서 받아들였습니다. 팀 공용 디렉토리를 열 때는 다시 봐야 합니다.
300
+
301
+ ### 4.5 결과 꼴
302
+
303
+ 어느 출처든 후보 하나를 같은 꼴로 돌려줍니다. 이름, 플랫폼 ID, 찾은 경로, 규모(있으면), 근거 링크입니다. 그래서 `research.json`과 렌더러는 출처가 늘어나도 안 바뀝니다. [12절](#12-구현-순서) 1번의 제품 후보 필드와 맞춘 꼴입니다.
304
+
305
+ ### 4.6 보안
306
+
307
+ - 비밀번호나 API 키는 SOURCE.md에 안 넣습니다. 환경변수 이름이나 MCP 서버 이름만 적습니다.
308
+ - 팀 공용 디렉토리는 레포 안에 들어 있으므로 남의 레포를 열면 그 레포의 SOURCE.md가 같이 읽힙니다. 그래서 팀 공용을 넣을 때는 `http`와 `browser`만 허용하고 셸 명령을 돌리는 방식은 막습니다. 매니페스트의 `GESTALT_LAUNCHER`가 절대 경로만 받는 것과 같은 이유입니다(루트 `CLAUDE.md`의 MCP 기동 경로 절). 첫 버전에서 팀 공용을 미루는 이유가 이것입니다.
309
+ - 출처는 `access`에 적힌 방법만 씁니다. 첫 버전에는 셸 명령 종류가 아예 없습니다.
310
+
311
+ ## 5. 결과물
312
+
313
+ ### 5.1 섹션 0부터 8까지
314
+
315
+ 상태: 정함
316
+
317
+ 섹션 구성과 섹션별 규칙은 [output-template.md](./output-template.md)를 따릅니다. 이 문서는 섹션 내용을 반복하지 않습니다.
318
+
319
+ ### 5.2 근거 상태
320
+
321
+ 관찰과 해석을 나누는 규칙은 템플릿에 있습니다. 여기서는 출처 표기와 회사 발언 상태를 더합니다.
322
+
323
+ | 근거 상태 | 표기 | 상태 |
324
+ | --------- | --------------------------------------------------------------------------- | ---- |
325
+ | 관찰 | 출처를 붙입니다. `(화면 03)`, `(영상 12.3초)`, `(프레임 NN)`, 유아이볼 링크 | 정함 |
326
+ | 해석 | 문장 끝에 `(해석)` | 정함 |
327
+ | 확인 못함 | 확인하지 못했다고 적습니다 | 정함 |
328
+ | 회사 발언 | `(회사 컨퍼런스 발표 14:32)` 꼴로 출처를 적습니다 | 정함 |
329
+
330
+ 회사 발언은 세미나와 기술 블로그 같은 자리에서 회사가 직접 한 말입니다. 블로그 글은 `(회사 기술 블로그 2025-05)`처럼 글 날짜를 적습니다(날짜는 예시입니다). 쓰는 규칙은 아래와 같습니다.
331
+
332
+ - 발표 연도나 글 날짜를 적습니다. 지금 앱 화면과 다르면 그 차이를 짚습니다.
333
+ - 발표나 글에 나온 숫자는 회사 주장이지 직접 확인한 값이 아닙니다. 관찰처럼 쓰지 않습니다.
334
+ - 5번 "레퍼런스가 그렇게 한 이유"는 `(해석)` 대신 회사 발언으로 채울 수 있습니다. 회사가 직접 밝힌 이유가 추정보다 근거가 강하기 때문입니다.
335
+
336
+ 회사 발언 상태는 11절의 현재 추천대로 도입했습니다.
337
+
338
+ 위 표의 네 줄은 스키마에서 다섯 상태로 나뉩니다. 비교표 칸과 와우, 페인 발견이 `basis`를 가집니다.
339
+
340
+ | `basis` | 뜻 | 스키마가 거절하는 경우 |
341
+ | --- | --- | --- |
342
+ | `observation` | 본 것 | `evidence`가 비어 있음 |
343
+ | `company-statement` | 회사가 직접 말한 것 | `evidence`가 비어 있음 |
344
+ | `interpretation` | 우리 해석 | 없음 |
345
+ | `unconfirmed` | 못 봐서 비워 둔 칸 | `note`(이유)가 없음 |
346
+ | `absent` | 없다고 확인한 것 | `note`(확인한 방법)가 없음 |
347
+
348
+ `evidence`는 `capture`, `video`, `internal`, `url` 넷 중 하나로 걸고 문서 안에 없는 캡처나 영상 id는 거절합니다. 이전 스키마(1.0.0)로 쓴 문서는 새 필드가 모두 선택이라 그대로 읽힙니다. `validate`는 "확인 못함" 문구인데 상태가 다른 칸, 비교 행에 빠진 레퍼런스 칸, `basis` 없는 칸 수를 경고합니다.
349
+
350
+ ### 5.3 0번 전제의 근거 한계
351
+
352
+ 상태: 정함
353
+
354
+ 0번 전제에는 이번 리서치가 어떤 도구 한계 안에서 돌았는지를 적습니다. 읽는 사람이 관찰의 범위를 가늠하는 근거가 됩니다.
355
+
356
+ - 유아이볼 계정 등급. 무료면 MCP 검색당 3개, 웹 앱당 8장만 봅니다.
357
+ - 유아이볼 MCP가 연결되지 않았으면 그 사실.
358
+ - iOS 화면을 못 봤으면 그 사실.
359
+ - 계정이 없거나 로그인이 안 돼서 건너뛴 출처와 대신 쓴 경로([4.3절](#43-리서치를-시작할-때)). 예: "모바일인덱스 미연결이라 규모는 앱스토어 평점 수로 짐작".
360
+ - 직접 캡처한 화면과 레퍼런스 DB 이미지의 구분(7.4절의 출처 표기).
361
+
362
+ ### 5.4 렌더링 구조
363
+
364
+ 상태: 정함
365
+
366
+ research는 `ges_architecture`와 같은 원칙으로 렌더링합니다. LLM은 HTML을 손으로 그리지 않고 구조화된 중간 표현(IR)만 만듭니다. HTML은 코드가 그립니다.
367
+
368
+ ```text
369
+ LLM이 research.json 작성 (zod 스키마 IR)
370
+ → MCP 도구 ges_research가 검증하고 렌더
371
+ → HTML (이미지는 base64 JPEG로 넣은 단일 파일, 개인용과 공유용)
372
+ → Figma (5.5절)
373
+ ```
374
+
375
+ architecture 스킬이 IR을 파일로 쓰고 `irPath`로 넘기는 방식(이미지를 컨텍스트에 올리지 않기 위한 규칙)을 그대로 씁니다. 개인용과 공유용 HTML은 둘 다 쓰고 `audience`로 열 쪽을 고릅니다.
376
+
377
+ HTML은 문서 보기와 캔버스 보기를 오갈 수 있습니다. 캔버스 보기는 섹션 하나를 보드 하나로 펼쳐 놓고 피그마처럼 끌고 확대해서 봅니다. 주소 끝에 `#canvas`를 붙이면 캔버스로 바로 열립니다. 보드 배치는 브라우저가 열 때 정하므로 IR에는 좌표가 없습니다.
378
+
379
+ ### 5.5 Figma 경로
380
+
381
+ 상태: 정함. 실행기 후보는 항목마다 확인 여부를 따로 적었습니다.
382
+
383
+ Figma 쓰기는 중립 레이아웃 파일을 한 번 거칩니다.
384
+
385
+ ```text
386
+ research.json → figma-plan.json (중립 레이아웃)
387
+ → Plugin API 실행 스크립트
388
+ → 실행기 (선택)
389
+ ```
390
+
391
+ 실행기를 하나로 강제하지 않습니다. 같은 스크립트를 붙어 있는 실행기에서 돌립니다.
392
+
393
+ | 실행기 | 조건 | 확인 여부 |
394
+ | ---------------------------------- | ------------------------------------------------------------ | ------------- |
395
+ | 브라우저(aside 등) | 편집 권한이 있는 파일에서 `window.figma` 전역을 씁니다 | 확인됨(9.1절) |
396
+ | claude.ai Figma 커넥터 `use_figma` | 코드가 5만 자까지라 이미지는 `upload_assets`로 따로 올립니다 | 확인됨(9.9절) |
397
+ | 로컬 개발 플러그인 | 개발 플러그인으로 로드합니다 | 미확인 |
398
+ | Figma REST API | 노드 생성이 안 되는 걸로 알고 있어 읽기와 검증용으로 봅니다 | 확인 필요 |
399
+ | 로컬 figma dev-mode MCP | 읽기 전용이고 활성 탭에 로드된 페이지만 읽습니다 | 확인됨 |
400
+
401
+ 구현은 `ges_research action=figma_plan`입니다. research.json을 읽어 아래 파일을 `.gestalt/research/<slug>/`에 씁니다.
402
+
403
+ | 파일 | 내용 |
404
+ | ----------------- | ------------------------------------------------------------------------------------------- |
405
+ | `figma-plan.json` | 섹션 0부터 8까지를 텍스트, 표, 이미지 묶음으로 나눈 중립 레이아웃과 열 배치(`board`). 이미지 바이트는 없습니다 |
406
+ | `figma-render.js` | 계획을 새 Figma 페이지로 그리는 `async () => {...}` 스크립트 |
407
+ | `figma-verify.js` | 쓴 뒤 이미지 fill이 남았는지 보는 스크립트. `__PAGE_ID__` 자리에 반환된 pageId를 넣습니다 |
408
+ | `figma-fit.js` | 이미지를 올린 뒤 회색 칸의 높이를 이미지 비율에 맞추고 캡션의 "이미지 없음" 줄을 지웁니다 |
409
+
410
+ - 배치는 `figmaLayout`이 정합니다. 기본 `board`는 가로 3열이고 `page`는 한 페이지에 세로로 쌓습니다. 세로로 길게 이어지면 훑기 어려워서 기본을 가로로 뒀습니다.
411
+ - 왼쪽 열은 화면 캡처입니다. 제품마다 캡처 줄과 그 옆의 메모 카드를 한 덩어리로 묶습니다. 메모 카드에는 와우와 페인(인사이트와 설계 힌트도 있으면)을 색 라벨과 함께 적습니다. 메모는 그 제품의 화면이나 캡처에 걸린 발견에서 가져옵니다. 걸린 발견이 없으면 레퍼런스의 와우와 페인 목록으로 채웁니다.
412
+ - 가운데 열은 결론입니다. 제목 블록, 종합, 6번 판단(초록), 2번 시안, 4번 와우와 페인(주황), 7번 검증 제안(파랑), 8번 열린 질문 순입니다.
413
+ - 오른쪽 열은 근거입니다. 0번 전제, 1번 레퍼런스 선정, 3번 비교표, 5번 이유 순입니다. 세 열의 높이가 비슷해지도록 구획을 나눠 뒀습니다.
414
+ - 색을 입힌 구획 안의 표는 흰 바탕에 모서리를 둥글게 해서 배경과 갈립니다. 캡처가 없는 조사는 캡처 열을 만들지 않고 두 열로 그립니다.
415
+ - `board`에서는 캡처가 `figma-plan.json`의 `board.groups`에 들어가고 1번 구획에는 캡처 표가 남지 않습니다. 캡처 카드는 `page`보다 넓은 320으로 그립니다. 표 본문 글자는 한눈에 읽히도록 16으로 그립니다.
416
+ - 스크립트는 기존 페이지를 건드리지 않고 새 페이지를 만듭니다. 폰트는 Pretendard를 먼저 쓰고 없으면 Inter로 내려갑니다.
417
+ - 이미지는 `withImages`를 켰을 때만 스크립트에 base64로 넣습니다. PNG, JPEG, GIF만 받고 합계 8MB를 넘으면 건너뜁니다. 건너뛴 자리는 회색 칸과 "이미지 없음" 캡션으로 남습니다.
418
+ - `runner`는 스크립트 모양을 정합니다. 기본 `function`은 `async () => {...}` 한 덩어리이고 `body`는 함수 껍데기 없이 최상위 `await`와 `return`으로 쓴 본문입니다. `use_figma`는 `body`만 실행합니다.
419
+ - `use_figma`는 코드가 5만 자까지라 `withImages`로 이미지를 넣은 스크립트는 들어가지 않습니다. 이 경로에서는 `withImages`를 끄고 렌더 결과의 `imageSlots`(이미지 자리의 노드 ID)를 `upload_assets`의 `nodeIds`에 넣어 응답의 `imageFiles`를 올린 뒤 `figma-fit.js`와 `figma-verify.js`를 돌립니다.
420
+ - `audience`는 HTML과 같습니다. 공유용은 계획 메모를 빼고 pii가 unchecked인 캡처가 있으면 만들지 않습니다.
421
+ - 파일을 못 읽는 실행기(aside)는 `inline`으로 본문을 응답에 받습니다. 200KB를 넘으면 싣지 않습니다.
422
+ - 테스트는 가짜 `figma` 객체로 스크립트가 끝까지 도는지 봅니다. 실제 Figma 파일에서 돌려본 결과는 9.9절에 있습니다.
423
+
424
+ ### 5.6 저장 위치
425
+
426
+ 상태: 정함
427
+
428
+ research.json, 캡처, Figma 스크립트는 `.gestalt/research/<slug>/`에 둡니다. 이 디렉토리는 gitignore 대상입니다. 캡처에는 경쟁사 서비스의 화면이 들어 있어서 레포로 올라가면 안 됩니다. 개인정보는 캡처를 저장하기 전에 가짜 값으로 바꾸므로(10.1절) 치환 전 원본은 이 디렉토리에도 없습니다. 팀에 공유할 때는 `audience`를 공유용으로 렌더한 HTML이나 Figma 결과를 씁니다.
429
+
430
+ ## 6. 화면 출처 순서
431
+
432
+ 상태: 정함
433
+
434
+ 붙어 있는 도구를 씁니다. 특정 MCP에 묶지 않습니다. 위에서부터 시도하고 안 되면 다음으로 내려갑니다. 제품 후보와 규모 같은 정보 출처는 사용자가 추가할 수 있습니다. 그 구조는 [4절](#4-출처-정의와-추가)에 있습니다.
435
+
436
+ | 순서 | 출처 | 방법과 조건 |
437
+ | ---- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
438
+ | 1 | 레퍼런스 DB | 국내는 유아이볼, 해외는 Mobbin. MCP가 붙어 있으면 MCP를 쓰고 없으면 브라우저로 봅니다 |
439
+ | 2 | 웹 서비스 | aside로 직접 캡처합니다 |
440
+ | 3 | Android | 에뮬레이터와 Maestro. Play Store가 있는 AVD를 씁니다 |
441
+ | 4 | iOS | 실기기와 mobile-mcp(USB 연결, 신뢰, Xcode, WebDriverAgent가 필요하고 무료 Apple ID 서명은 7일 뒤 만료됩니다). 또는 iPhone 미러링과 computer-use(설정은 없고 스크린샷만 얻습니다) |
442
+ | 5 | 공개 영상과 스토어 | yt-dlp로 받은 영상, 스토어 스크린샷 |
443
+ | 6 | 확인 못함 | 0번 전제에 한계로 적습니다 |
444
+
445
+ ### 6.1 제약
446
+
447
+ 경쟁사 iOS 앱은 자동화로 보기가 어렵습니다. 막힌 길은 아래와 같습니다.
448
+
449
+ | 도구 | 못 쓰는 이유 |
450
+ | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------ |
451
+ | iOS 시뮬레이터 | 앱스토어 앱을 설치할 수 없습니다 |
452
+ | BrowserStack App Automate, LambdaTest, AWS Device Farm | 앱 파일을 업로드하는 방식입니다. App Store 앱은 FairPlay로 암호화돼 있어 경쟁사 앱은 못 올립니다 |
453
+ | BrowserStack App Live | 실기기에서 App Store 설치는 되지만 사람이 조작해야 하고 유료입니다 |
454
+ | Firebase Robo | APK가 필요해서 iOS는 해당이 없습니다 |
455
+
456
+ ### 6.2 우리 서비스 화면
457
+
458
+ 경쟁사 화면만 보면 "우리는 어디가 다른가"가 비어 있습니다. 우리 서비스를 제품 후보에 `own-service` 축으로 한 개 더하고 세 길로 화면을 받습니다.
459
+
460
+ | 길 | 캡처 `kind` | 개인정보 처리 |
461
+ |---|---|---|
462
+ | 앱 캡처 이미지(사용자가 줌) | `own-app` | `redact_image` |
463
+ | 웹(브라우저로 직접) | `own-web` | `redact_script`, 로그인은 사람 |
464
+ | 피그마 링크 | `figma` | Figma MCP로 읽고 `redact_image`. 노드 링크를 `figmaUrl`에 남김 |
465
+
466
+ 우리 서비스는 레퍼런스처럼 가져올 대상이 아니라서 종합 표에서 따로 갈립니다. 우리 서비스의 와우는 "지킬 우리 와우", 페인은 "고칠 우리 페인"으로 나오고 가져올 와우와 피할 페인에는 섞이지 않습니다. 비교표에는 같은 구좌의 열로 들어가므로 경쟁사와 한 줄에서 견줍니다. 피그마는 읽기만 하고 사용자 파일에 쓰지 않습니다.
467
+
468
+ `validate`는 우리 서비스 제품이 둘 이상일 때, 캡처의 `kind`와 제품 축이 어긋날 때, `figma` 캡처에 `figmaUrl`이 없을 때 경고합니다.
469
+
470
+ ## 7. 유아이볼
471
+
472
+ 상태: 정함
473
+
474
+ 유아이볼은 [4절](#4-출처-정의와-추가)의 출처 구조에서 `access`에 `mcp`를 먼저 적고 `browser`를 다음에 적는 내장 출처입니다. MCP가 있으면 MCP를 쓰고 없으면 브라우저로 봅니다.
475
+
476
+ ### 7.1 MCP
477
+
478
+ - 주소는 `https://uibowl.io/api/mcp`입니다.
479
+ - 인증은 OAuth입니다. API 키는 없고 구글 계정으로 로그인합니다.
480
+ - 조회 전용입니다. 결과는 이미지(일부는 영상)와 유아이볼 화면 링크로 옵니다.
481
+ - 화면 유형, 사용자 흐름, 화면 속 문구, 컴포넌트로 찾고 이미지 유사 검색도 됩니다.
482
+ - 무료는 검색당 최신 3개만 줍니다. 멤버십은 페이지를 넘길 수 있습니다.
483
+ - 도구당 기기는 2대입니다.
484
+
485
+ 등록은 각자 user 범위로 합니다. gestalt 플러그인에 번들하지 않습니다. 계정은 사람마다 다르고 약관상 공유할 수 없기 때문입니다.
486
+
487
+ ```bash
488
+ claude mcp add --transport http -s user uibowl https://uibowl.io/api/mcp
489
+ ```
490
+
491
+ 등록한 뒤 `/mcp`에서 인증합니다.
492
+
493
+ ### 7.2 브라우저 경로
494
+
495
+ MCP가 없거나 막히면 로그인된 브라우저 세션으로 봅니다.
496
+
497
+ | URL | 대상 |
498
+ | ------------------------------------------ | ----------------------------- |
499
+ | `/name/<앱이름>` | 앱 약 581개 |
500
+ | `/website/<이름>` | 웹 약 247개 |
501
+ | `?patterns=`, `?components=`, `?category=` | 패턴, 컴포넌트, 카테고리 필터 |
502
+
503
+ - 화면은 클라이언트에서 렌더링돼서 curl로는 이미지가 나오지 않습니다. 로그인된 브라우저 세션(aside)이 필요합니다.
504
+ - 앱이 있는지는 로그인 없이 `https://uibowl.io/server-sitemap.xml`에서 확인합니다. 2026-10-16 기준으로 서비스 A와 서비스 R은 있고 서비스 D는 웹만 있고 서비스 C는 없습니다.
505
+ - 이미지 alt에 패턴 이름이 들어 있고 페이지에 MAU가 표시됩니다.
506
+
507
+ ### 7.3 약관과 접근 속도
508
+
509
+ 약관 검토는 하지 않기로 사용자가 결정했습니다. 브라우저로 볼 때는 계정이 막히지 않도록 사람 속도로, 필요한 화면만 엽니다.
510
+
511
+ ### 7.4 출처 표기
512
+
513
+ 유아이볼 이미지는 수집 시점이 지금이 아닐 수 있습니다. `(유아이볼, 수집 시점 확인 못함)`처럼 따로 적어서 직접 캡처한 화면과 구분합니다.
514
+
515
+ ## 8. 영상 처리
516
+
517
+ 상태: 설계. 일부만 9절에서 확인했습니다.
518
+
519
+ ### 8.1 종류 가름
520
+
521
+ 먼저 영상 종류를 가릅니다. 코드가 1차로 판정하고 애매하면 LLM이 썸네일을 보고 정합니다.
522
+
523
+ | 종류 | 특징 | 처리 |
524
+ | ------------- | ---------------------------------- | --------------------- |
525
+ | 화면 녹화 | 세로, 상태바, 말소리가 적음 | 8.2절 |
526
+ | 세미나나 발표 | 가로, 말소리, 슬라이드가 멈춰 있음 | 8.3절 |
527
+ | 섞임 | 발표 안에 데모가 들어 있는 경우 | 구간을 잘라 각각 처리 |
528
+
529
+ ### 8.2 화면 녹화
530
+
531
+ 코드가 처리하고 LLM은 뽑힌 이미지만 읽습니다.
532
+
533
+ 1. `ffprobe`로 길이, 해상도, 프레임 수를 읽습니다.
534
+ 2. 분석용으로 폭 390px로 줄입니다. 결과 이미지는 원본에서 뽑습니다.
535
+ 3. 구간을 나눕니다. 멈춤, 스크롤, 화면 전환, 움직임 네 가지로 분류합니다.
536
+ 4. 구간마다 아래처럼 뽑습니다.
537
+
538
+ | 구간 | 뽑는 것 |
539
+ | --------- | -------------------------------- |
540
+ | 멈춤 | 가장 선명한 한 장 |
541
+ | 스크롤 | 이어붙인 긴 이미지와 N장 |
542
+ | 화면 전환 | 직전과 직후 두 장, 전환 시각 |
543
+ | 움직임 | 3장에서 5장 묶음과 걸린 시간(ms) |
544
+
545
+ 5. 중복을 지웁니다.
546
+ 6. 소리가 있으면 whisper로 받아 적습니다.
547
+
548
+ LLM은 뽑힌 이미지를 읽고 제품별 흐름, 구좌별, 유형별, 종합 순서로 묶습니다. 출처는 `(영상 N.N초)`로 적습니다. 파일 이름은 `<제품>-NN.jpg`입니다.
549
+
550
+ 직접 녹화할 때는 Android의 "탭 표시"를 켜서 누른 위치를 영상에 남깁니다. 남이 올린 영상은 화면 전환의 원인을 알 수 없으니 `(해석)`으로 적습니다.
551
+
552
+ ### 8.3 세미나
553
+
554
+ 1. 자막을 먼저 구합니다. 사람이 단 유튜브 자막이 우선이고 없으면 whisper를 씁니다. 기존 `video-summarizer` 에이전트를 재사용합니다.
555
+ 2. 슬라이드가 바뀔 때마다 한 장을 뽑고 발표자만 나오는 구간은 버립니다.
556
+ 3. 슬라이드와 그 시각에 한 말을 맞춥니다.
557
+ 4. 데모 구간은 잘라서 8.2절의 화면 녹화 처리로 넘깁니다.
558
+ 5. 결과는 회사 발언(5.2절)으로 기록합니다.
559
+
560
+ 기술 설명은 본문에 섞지 않고 "기술 메모"로 분리합니다. 긴 발표는 썸네일 격자로 먼저 훑고 관련 구간만 자세히 봅니다.
561
+
562
+ 2번과 3번은 `scripts/slide-extract.mjs`가 합니다. ffmpeg로 2초마다 64x36 흑백 썸네일을 뽑아 인접 표본의 평균 차이로 화면 전환을 찾습니다. 4초보다 짧게 머문 화면은 전환 효과로 보고 버립니다. 앞서 뽑은 슬라이드와 거의 같은 화면도 중복으로 버립니다. 구간의 마지막 표본을 쓰기 때문에 불릿이 하나씩 나타나는 슬라이드는 다 나타난 모습이 뽑힙니다. 자막(VTT나 SRT)을 주면 유튜브 자동 자막의 되풀이 줄을 걷고 각 슬라이드의 구간에 나온 말을 `slides.json`에 붙입니다. 의존성은 ffmpeg뿐이고 Node 내장 모듈만 씁니다.
563
+
564
+ 한계도 있습니다. 발표자만 나오는 구간을 따로 가려내지는 않아서 카메라가 발표자로 넘어가면 그 화면이 슬라이드로 뽑힐 수 있습니다. 슬라이드 안의 작은 변화(불릿 한 줄)는 임계값 아래라 따로 안 뽑습니다. 프레임에는 발표자 얼굴이 남으므로 뽑은 뒤 `redact_image`로 넣습니다.
565
+
566
+ ### 8.4 LLM에 넘기는 이미지
567
+
568
+ JPEG 품질 80으로 줄여서 넘깁니다. 컨텍스트에 큰 이미지를 한 번 올리면 호출마다 다시 실려 비용이 쌓이기 때문입니다.
569
+
570
+ ### 8.5 합치기와 분할
571
+
572
+ 상태: 정함
573
+
574
+ 영상 분석은 본문에 합쳐서도 쓰고 따로도 씁니다. 두 경우 모두 같은 `video` 블록을 쓰고 둘을 오가는 연산이 있습니다.
575
+
576
+ | 구분 | `meta.kind` | 그려지는 모양 |
577
+ | --- | --- | --- |
578
+ | 합침 | 본문 그대로(`ux-research` 등) | 0번부터 8번 뒤에 "9. 영상 분석"이 붙습니다. `video`가 없으면 기존 모양 그대로입니다 |
579
+ | 분할 | `video-analysis` | 0번 전제, 1번 레퍼런스 선정(캡처), "2. 영상 분석"만 그립니다. 시안, 비교표, 판단 같은 본문 섹션은 쓰지 않습니다 |
580
+
581
+ `video` 블록은 영상 목록(`clips`)과 발견(`findings`)으로 이뤄집니다.
582
+
583
+ - 영상: 제품, 제목, 종류(화면 녹화, 세미나, 섞임), 출처(공개 영상, 직접 녹화), 주소(https), 길이.
584
+ - 발견: 영상, 묶음(제품별 흐름, 구좌별, 유형별, 종합), 주제, 내용, 시각(초), 프레임 캡처, 근거 상태, 방향성이나 관례, 상시나 분기나 단독.
585
+ - 관찰과 회사 발언은 시각이 없으면 스키마가 거절합니다. 해석은 시각 없이 받습니다.
586
+ - 발견의 프레임은 같은 제품의 캡처만 가리킬 수 있습니다. 이미지는 1번 섹션 캡처 갤러리에 한 번만 실립니다.
587
+ - 목표 지표(`premise.goalMetric`)는 분할 문서에서도 필수입니다. 2.1절의 규칙과 같습니다.
588
+
589
+ `ges_research`에 두 액션을 뒀습니다.
590
+
591
+ | 액션 | 하는 일 |
592
+ | --- | --- |
593
+ | `split_video` | 본문(`slug`)의 영상 블록을 `<slug>-video`(`videoSlug`로 바꿈) 문서로 떼어냅니다. 영상이 가리키는 제품, 프레임 캡처, 그 캡처의 화면 지도 항목만 따라가고 이미지를 복사합니다. 본문은 그대로 두고 `removeFromMain`을 켜면 영상 블록만 지웁니다(캡처와 제품은 남습니다). 같은 slug의 영상 문서가 있으면 `overwrite` 없이는 거절합니다 |
594
+ | `merge_video` | 영상 문서의 영상 블록, 제품, 화면 지도, 캡처를 본문에 합치고 이미지를 복사합니다. 이미 있는 항목은 하나로 보고 두 번 합쳐도 겹치지 않습니다 |
595
+
596
+ 같은 id가 다른 내용이면(제품 이름, 캡처 경로, 영상 정의가 다름) 어느 쪽도 덮어쓰지 않고 멈추며 어떤 id가 겹쳤는지 알려줍니다. 원본 이미지가 없으면 `missingImages`에 올립니다.
597
+
598
+ 언제 어느 쪽으로 쓸지는 이렇게 정합니다. 영상이 과제의 근거 일부이면 본문에 합칩니다. 영상이 주된 자료이거나 여러 과제에서 같이 보게 될 것이면 따로 둡니다. 한쪽으로 시작해도 나중에 `split_video`나 `merge_video`로 바꿀 수 있습니다.
599
+
600
+ ## 9. 스파이크 결과
601
+
602
+ 2026-10-16에 돌렸고 코드는 `_workspace/spikes/`에 있습니다(gitignore 대상).
603
+
604
+ ### 9.1 Figma 브라우저 쓰기
605
+
606
+ 상태: 확인됨
607
+
608
+ - 편집 권한이 있는 파일에서만 `window.figma` 전역이 있습니다. 보기 전용 파일에는 없습니다.
609
+ - 페이지, 프레임, 오토레이아웃, Pretendard 한글 폰트, 이미지 fill이 모두 됩니다.
610
+ - 이미지 업로드가 끝나기 전에 탭을 닫으면 이미지가 빠진 채 저장됩니다.
611
+ - `exportAsync`로 검증용 이미지를 뽑을 수 있습니다.
612
+
613
+ 여기서 나온 규칙은 아래와 같습니다.
614
+
615
+ | 시점 | 규칙 |
616
+ | ------- | -------------------------------------------------------------------------- |
617
+ | 쓰기 전 | 편집 권한을 확인합니다. 없으면 새 드래프트를 만들거나 다른 실행기를 씁니다 |
618
+ | 쓴 뒤 | 파일을 다시 열어 이미지가 남았는지 검증합니다. 빠졌으면 다시 넣습니다 |
619
+
620
+ ### 9.2 스크롤 이어붙이기
621
+
622
+ 상태: 확인됨. 한계는 아래에 따로 적었습니다.
623
+
624
+ 의존성 없이 ffmpeg와 plain TS로 만들었습니다. 방식은 아래와 같습니다.
625
+
626
+ - 행마다 16px 블록 평균 디스크립터를 만들고 인접 프레임과 SAD를 비교해 세로 이동량을 추정합니다. 가운데 70%만 봅니다.
627
+ - 변하지 않은 위아래 행은 고정 헤더와 푸터로 뺍니다.
628
+ - 누적 캔버스와 ±12px 범위로 다시 대조해서 오차가 쌓이지 않게 합니다.
629
+ - 행마다 medoid를 골라 스티키 요소, 압축 번짐, 토스트를 지웁니다.
630
+ - 프레임마다 엇갈리는 행 묶음(캐러셀)은 한 프레임에서 통째로 가져옵니다.
631
+
632
+ 결과는 아래와 같습니다.
633
+
634
+ | 항목 | 값 |
635
+ | --------------------------------------- | ------------------------------- |
636
+ | 합성 영상의 위치 오차 | 최대 0.6px |
637
+ | 높이 오차 | 0px |
638
+ | N | 4.19 (정답 4.20) |
639
+ | 통과한 조건 | h264 압축, 플릭, 캐러셀, 스티키 |
640
+ | 화면 2개짜리 영상 | 정확히 2장으로 분리 |
641
+ | 실제 페이지(국내 서비스 모바일 홈, MDN) | 오차 0px |
642
+ | 속도 | 160프레임에 약 2초 |
643
+
644
+ 화면 전환은 맞는 행의 비율이 30% 미만일 때로 판정합니다. 정상 스크롤은 44% 이상이고 전환은 17% 이하라 여유가 크지 않습니다.
645
+
646
+ 남은 위험은 아래와 같습니다.
647
+
648
+ - 똑같은 행이 반복되는 화면(스켈레톤)은 속도 가정으로 버팁니다.
649
+ - 스티키 요소가 오래 멈춰 있으면 다수결이 뒤집힐 수 있습니다.
650
+ - 캐러셀은 어느 시점의 모습이 들어갈지 정해지지 않습니다.
651
+ - lazy-load 화면은 검증하지 않았습니다.
652
+ - 실기기의 고해상도 영상은 축소해서 분석하고 원본으로 합성해야 합니다.
653
+ - JPEG 높이 한도가 65535px라 원본 해상도 기준으로 약 25장이 상한입니다.
654
+
655
+ ffmpeg가 없으면 이어붙이지 않고 낱장으로 넘깁니다. `sharp`는 전이 의존성이라 직접 import할 수 없어서 새 의존성은 쓰지 않았습니다.
656
+
657
+ ### 9.3 유아이볼 브라우저
658
+
659
+ 상태: 확인됨
660
+
661
+ 로그인된 세션에서 `/name/<앱이름>`이 열렸습니다. 무료 계정에서는 화면 8장과 영상 2개가 보이고 그 아래는 멤버십 안내로 막혀 있습니다.
662
+
663
+ ### 9.4 앱스토어 검색 API와 리뷰 RSS
664
+
665
+ 상태: 확인됨
666
+
667
+ 검색 API는 키 없이 호출되고 한국 스토어 결과가 나옵니다. 장르, 평점 수, `bundleId`까지 받습니다. 처음에는 "상품권" 검색만 돌려서 앱 이름 단위라 큰 앱 안의 기능은 못 잡는다고 봤습니다. 검색어를 "기프티콘", "모바일쿠폰", "선물하기"로 넓혀 다시 돌렸더니 큰 플랫폼 앱도 나왔습니다. 그래도 큰 앱 4곳 안의 선물 기능은 어떤 검색어로도 안 나왔습니다(3.1절).
668
+
669
+ 리뷰 RSS도 키 없이 열립니다. 서비스 K의 최근 리뷰에서 다른 서비스 이름을 뽑았습니다. 한 번에 최근 50개 정도만 주는 점이 한계입니다.
670
+
671
+ ### 9.5 기술 블로그 검색
672
+
673
+ 상태: 확인됨. 검색 한 번으로 본 결과입니다.
674
+
675
+ "선물하기 상품권 기프티콘"으로 한 번 검색해서 큰 앱 4곳의 선물 기능 관련 글을 찾았습니다. 앱스토어 검색에 안 나온 곳들입니다. RSS는 주제 검색에는 안 맞습니다. 회사 블로그 9곳 중 5곳은 최근 10개에서 20개만, 1곳은 전체 글을 줬고 3곳은 짐작한 주소가 열리지 않았습니다(3.1절).
676
+
677
+ ### 9.6 개인정보 치환
678
+
679
+ 상태: 확인됨. 가짜 값만 넣은 샘플로 한 번씩 돌렸습니다.
680
+
681
+ 앱 화면 이미지는 macOS Vision으로 읽습니다. tesseract는 영어 데이터만 설치돼 있어 한글을 못 읽었습니다. Vision은 한글 신뢰도가 1.0으로 나왔습니다. 샘플은 받는 분 이름, 전화번호, 이메일, 주문 번호, 카드 번호, 주소, 쿠폰 번호를 한 줄씩 적은 이미지입니다. `ges_research action=redact_image`로 일곱 곳이 모두 가짜 값으로 바뀌었고 결제 금액 같은 개인정보가 아닌 글자는 그대로 남았습니다. 바코드는 가짜 페이로드(`0000000007163`)로 다시 그려졌고 Vision이 그 값을 정상으로 읽었습니다.
682
+
683
+ 웹 화면은 `redact_script`가 주는 스크립트를 aside로 열어 둔 로컬 페이지에서 실행했습니다. 글자 6곳, 입력칸 2곳, 프로필 이미지 1개, 바코드 요소 1개가 바뀌었습니다. 아바타 이미지의 `alt`에 이름이 남는 것을 이 실행에서 찾아 고쳤습니다.
684
+
685
+ 걸린 시간은 첫 실행이 약 55초(Vision 워밍업)였고 이후는 4초에서 6초입니다.
686
+
687
+ 확인한 한계는 다음과 같습니다.
688
+
689
+ - 라벨과 값 사이 공백이 좁아집니다. 값 칸에 맞춰 가짜 값을 그리기 때문입니다. 글자 크기도 원본보다 조금 작습니다.
690
+ - 라벨 없는 이름은 성씨로 시작하고 `님`이 붙은 것만 잡습니다. 이름만 덩그러니 있는 화면은 놓칩니다.
691
+ - 얼굴을 아바타로 덮는 경로를 공개 샘플 이미지 한 장(정면 얼굴)으로 돌려봤습니다. Vision이 얼굴 하나를 잡았고 그 자리가 아바타로 덮였습니다. 얼굴 사각형만 덮어서 이마와 머리카락은 남습니다. 작은 얼굴이나 옆모습, 한 장에 여럿이 나오는 경우는 시험하지 않았습니다.
692
+ - aside의 샌드박스는 로컬 파일을 못 읽어서 `redact_script`를 `inline`으로 받아야 합니다. chrome-devtools는 `sourcePath`로 파일을 바로 읽습니다.
693
+
694
+ ### 9.7 아직 안 한 것
695
+
696
+ - 멈춤 구간에서 선명한 프레임 고르기
697
+ - 움직임 구간의 시간 재기
698
+ - 중복 제거(화면 녹화 쪽. 세미나 슬라이드는 8.3절 스크립트가 한다)
699
+ - 유아이볼 MCP 반복 사용(2026-10-16에 재인증해 "상품권" 화면 검색을 한 번 돌렸습니다. 무료 한도로 결과 3건만 봤고 다른 검색어나 필터는 안 썼습니다)
700
+ - Google Play 검색
701
+ - 앱 안 탐색으로 화면 지도 만들기
702
+ - Google Play "비슷한 앱" 목록
703
+ - 회사 기술 블로그 도메인 목록 만들기
704
+ - 리뷰 반복 수집이 어디서 멈추는지 확인하기
705
+
706
+ ### 9.8 원본 페이지와 구조 비교
707
+
708
+ 상태: 확인됨. 구조만 읽기 전용으로 봤고 템플릿의 평가 기준을 실행으로 검증한 것은 아닙니다.
709
+
710
+ 12절 5번은 같은 과제를 새로 돌려 원본과 나란히 놓는 일입니다. 아래 이유로 돌리지 못했습니다.
711
+
712
+ - Figma dev-mode MCP는 읽기 전용이라 `figma_plan` 스크립트를 실행할 수 없습니다. 쓰기는 claude.ai Figma 커넥터로 새 빈 파일에서만 했습니다(9.9절).
713
+ - 원본 과제의 화면 출처와 계정은 이 환경에 없습니다. 유아이볼로 모은 다른 과제(상품권)로만 끝까지 돌렸습니다.
714
+ - 실제 앱을 조작해 끝까지 도는 일은 테스트 기기와 계정 같은 사용자 쪽 준비가 먼저 필요합니다.
715
+
716
+ 원본과 같은 과제로는 못 돌려서 원본 리서치 결과 페이지 하나를 읽기 전용으로 읽고 구조만 템플릿 0번부터 8번과 맞춰봤습니다. 원본은 서비스마다 같은 순서를 반복합니다. 결론 한 문장, 채택할 것, 검토할 것, 그 외(범위에서 뺄 것), 영상이 확인해 준 것, 부록, 서비스 요약, 페인 포인트 순입니다. 템플릿에 없거나 다르게 나뉜 자리와 정한 내용은 아래와 같습니다.
717
+
718
+ - "그 외" 칸이 없었습니다. 범위에서 뺀 것과 그 이유를 적는 자리입니다. 6번 판단의 확신에 `out`(제외)을 더해 받기로 했습니다. 제외는 `holdUntil` 없이 쓰고 이유는 근거에 적습니다.
719
+ - "부록" 칸이 없습니다. 본문에 안 쓴 캡처를 모아 두는 자리인데 1번 섹션의 캡처가 모든 캡처를 모아 보여주므로 칸을 더하지 않습니다.
720
+ - "서비스 요약"의 수익 모델 칸이 없습니다. 서비스가 어떻게 돈을 버는지 적는 자리입니다. 5번 섹션 `reasons`의 `business`가 본업을 적는 자리라 칸을 더하지 않고 수익 모델도 거기에 적도록 안내를 더했습니다.
721
+ - 나누는 기준이 다릅니다. 원본의 채택과 검토는 레퍼런스별이고 템플릿의 6번 판단은 화면 영역별로 유력, 시험, 보류, 제외를 나눕니다. 이 차이는 구조라서 그대로 둡니다.
722
+
723
+ 원본의 서비스 이름과 내용은 이 문서에 옮기지 않았습니다.
724
+
725
+ #### 품질 비교
726
+
727
+ 결과물의 형식은 맞는데 알맹이가 비어 있다는 평가를 받고 원본 네 곳(시장조사, 시안, 마이페이지 비교 리서치, 역기획 워크샵 파일)을 읽기 전용으로 다시 봤습니다. 원본이 가진 것과 이 양식에 없던 것은 아래와 같습니다.
728
+
729
+ | 원본이 하는 일 | 이 양식에 없던 것 | 처리 |
730
+ | --- | --- | --- |
731
+ | 구좌를 세로, 제품을 가로로 둔 격자와 구좌마다 한 줄 정의 | 비교 행의 정의와 패턴 | `definition`, `pattern`으로 받음 |
732
+ | 문장 끝에 "(프레임 08)", "(영상 109.7초)" 같은 출처 | 칸 단위 출처 | `basis`와 `evidence`로 받음 |
733
+ | 못 본 칸에 "영상에서 확인 못함" | 못 본 칸의 이유 | `unconfirmed`와 `note`로 받음 |
734
+ | 와우와 페인을 제품마다 적고 페인에 조작 횟수와 수치 | 발견 단위 | `wowPain.findings`와 `concrete`로 받음 |
735
+ | 질문을 확인 주체별 색으로 가름 | 확인 주체 | `owner`로 받음 |
736
+ | 캡처 아래 캡션 한 줄 | 캡처 설명 | `caption`, `frameNo`로 받음 |
737
+ | 비교표와 종합 순위 | 우선순위 | 판단의 `rank`, `todo`로 받음 |
738
+ | 역기획의 화면 노드, 전이, 번호 마커, BAD와 INSIGHT 카드 | 화면 흐름 표현 | `flow`와 화면 주석으로 받음(12절 10번). 그림은 못 그리고 표로만 나옴 |
739
+
740
+ 원본에도 없는 것이 있습니다. 시안 A부터 E까지 강점과 약점이 어느 와우나 페인에서 나왔는지 잇는 선이 없습니다. 검증 가설에도 표본과 기간, 성공 기준이 없습니다. 이 양식은 앞의 것을 `adoptions`로 받았고 뒤의 것은 아직 받지 않았습니다.
741
+
742
+ 도구가 만들 수 없는 것도 있습니다. 내부 실적, PG 회신, 인터뷰 유형은 사람이 입력해야 하고 `internal` 출처로만 겁니다. 값이 없으면 "내부 데이터 없음"이라고 적도록 SKILL.md에 안내했습니다.
743
+
744
+ ### 9.9 시험 실행
745
+
746
+ 상태: 확인됨. 원본과 다른 과제(상품권 구매 화면 모으기)로 한 번 끝까지 돌렸습니다. 원본과 나란히 놓은 평가가 아니라 파이프라인이 끝까지 도는지 본 것입니다.
747
+
748
+ 흐름은 아래와 같습니다. 코드로 직접 부른 핸들러는 `ges_research`와 같은 것입니다.
749
+
750
+ 1. 유아이볼 "상품권" 검색으로 서비스 3개의 화면 10장을 받았습니다(무료 한도로 4건 중 3건).
751
+ 2. `sources`로 쓸 수 있는 출처 5개를 확인했습니다.
752
+ 3. `redact_image`로 10장을 치환했습니다. 9장은 탐지 0건이었고 1장은 텍스트 1건을 가짜 값으로 바꿨습니다.
753
+ 4. `research.json`을 쓰고 `validate`를 돌려 경고 0건을 확인했습니다. 제품 후보와 화면 지도는 유아이볼 응답이 아니라 화면을 직접 보고 채웠습니다. 응답의 화면 설명이 비어 있었기 때문입니다.
754
+ 5. `render`로 개인용과 공유용 HTML을 만들고 브라우저로 표와 캡처 10장이 보이는지 열어봤습니다.
755
+ 6. `figma_plan`으로 스크립트를 만들고 개인 플랜에 새로 만든 빈 Figma 파일에서 실행했습니다. 원본 리서치가 있는 파일에는 쓰지 않았습니다.
756
+
757
+ Figma에서 확인한 것과 고친 것은 아래와 같습니다.
758
+
759
+ - 표 6개, 섹션 9개, 캡처 카드 10개가 한 번에 만들어졌고 `figma-verify.js`가 이미지 10장 모두 남았다고 답했습니다.
760
+ - `withImages`로 이미지를 넣은 스크립트는 1.8MB라 `use_figma`의 코드 한도(5만 자)에 들어가지 않았습니다. 이미지 없는 본문은 13KB였습니다. 그래서 `runner: "body"`, `imageSlots`, `imageFiles`, `figma-fit.js`를 더했습니다. 이전 방식은 브라우저처럼 파일을 읽는 실행기에서만 쓸 수 있었습니다.
761
+ - 이미지를 올리고 나면 회색 칸이 240 높이 그대로이고 캡션에 "이미지 없음"이 남는 문제가 있었습니다. `figma-fit.js`가 올린 이미지의 비율로 높이를 맞추고 그 줄을 지웁니다. 생성된 스크립트를 실제 파일에서 돌려 높이가 240에서 519로 바뀌고 캡션이 정리되는 것까지 봤습니다.
762
+ - 이미지 업로드는 `upload_assets`가 준 주소에 원문 바이트를 보내면 됩니다. 10장 모두 200으로 올라갔습니다.
763
+
764
+ 치환에서 걸린 것은 아래와 같습니다.
765
+
766
+ - 한 장에서 하단 사업자 정보의 주소만 가짜 값으로 바뀌었고 같은 줄의 대표자 이름과 사업자등록번호는 그대로였습니다. 탐지기가 라벨 목록에 있는 항목만 바꾸는데 대표자와 사업자 번호 라벨이 없었기 때문입니다. 사업자 정보 줄은 통째로 바꾸기로 정하고 대표자, 대표이사, 사업자등록번호, 법인등록번호, 통신판매업 신고번호 라벨을 더했습니다. 공시 정보라 개인정보는 아니지만 서비스를 유추할 수 있는 정보이기도 해서 반쪽만 바꾸는 쪽이 가장 나쁘다고 봤습니다. 이미 저장한 캡처는 다시 치환해야 반영됩니다.
767
+ - `none`은 보증이 아닙니다. 10.1절에 적은 한계가 그대로 남아 있습니다.
768
+
769
+ ### 9.10 전체 조합 시험
770
+
771
+ 상태: 확인됨. 9.9절의 시험은 화면만 모으는 조합(`screens-only`)이라 4번부터 7번까지가 비어 있었습니다. 같은 상품권 데이터에 목표 지표 "상품권 구매 전환율 20%"를 정해 0번부터 8번까지 모두 채워 다시 돌렸습니다.
772
+
773
+ - `ux-research` 스킬은 4번만 채우는 단계라 5번부터 7번은 전체 research 흐름을 손으로 따라 채웠습니다. 현재 세션에 `ges_research` MCP 도구가 없어서 핸들러를 직접 불렀습니다. 스킬 호출 경로로는 돌리지 못했습니다.
774
+ - `validate` 경고는 0건이었고 개인용과 공유용 HTML 모두 0번부터 8번이 렌더됐습니다.
775
+ - `figma_plan` 본문 스크립트는 16,308자라 `use_figma`의 5만 자 한도 안에 들어갔습니다. 표 14개와 이미지 자리 10개가 만들어졌고 이미지 10장을 올린 뒤 맞춤 스크립트까지 돌렸습니다.
776
+
777
+ 돌려서 찾은 결함과 고친 것은 아래와 같습니다.
778
+
779
+ - 시안이 비어 있을 때 2번 섹션 문구가 "시안 링크가 없습니다."였습니다. SKILL.md는 머리말 한 문장을 쓰라고 했지만 쓸 스키마 필드가 없었습니다. 렌더가 "시안이 없는 조사라 3번 비교표의 열은 시안 대신 레퍼런스 화면이다."를 적도록 바꾸고 SKILL.md의 안내도 맞췄습니다.
780
+ - 판단 보류 문구가 "~ 뒤에 정함"이라 보류 조건과 겹쳐 비문이 됐습니다. "~ 결정"으로 바꿨습니다.
781
+ - Figma 표에서 같은 행의 셀 높이가 제각각이라 테두리가 어긋났습니다. 단계나 사용자 관점 같은 짧은 헤더 열은 글자가 세로로 꺾이기도 했습니다. 셀 세로 크기를 `FILL`로 두고 열 최소 가중치를 6에서 12로 올렸습니다. 연습 파일에서 다시 렌더해 테두리가 맞고 짧은 헤더가 한 줄로 나오는 것을 확인했습니다.
782
+
783
+ 남은 한계도 있습니다. 레퍼런스 B는 목록 화면 두 장뿐이라 구매 단계 세 행의 칸이 비어 있고 HTML과 Figma 모두 `-`로 나옵니다. 목표 20%는 시험을 위해 사용자가 정한 값이라 근거 자료가 없습니다.
784
+
785
+ ### 9.11 실제 영상 시험
786
+
787
+ 상태: 확인됨. 8.5절까지 만든 영상 모듈을 공개 영상 두 개로 처음 돌렸습니다. 화면 녹화 하나(2분 18초)와 세미나 하나(35분 52초)입니다. 서비스 이름은 "서비스 A"와 "서비스 B"로만 적었습니다. 영상 파일과 프레임은 `.gestalt/research/` 아래에만 두었습니다.
788
+
789
+ - 영상 받기, 구간 프레임 뽑기, 자막과 장면 맞추기, `research.json` 작성, `validate`, 개인용과 공유용 `render`, `figma_plan` 순으로 돌렸습니다. `validate` 경고는 0건이었고 공유용 렌더도 막히지 않았습니다.
790
+ - 클립 2개, 발견 13개, 프레임 캡처 9개를 채웠습니다. 단독 문서는 0번 전제, 1번 레퍼런스 선정, 2번 영상 분석으로 그려졌습니다.
791
+ - 앞선 상품권 시험 본문의 복사본에 `merge_video`를 하니 클립 2개, 발견 13개, 이미지 9장이 옮겨졌고 9번 절로 그려졌습니다. `split_video`로 다시 떼어내자 영상 블록이 처음 문서와 JSON 비교로 같았습니다.
792
+
793
+ 돌려서 찾은 일과 고친 것은 아래와 같습니다.
794
+
795
+ - 앱이 가운데를 별표로 가린 이름(성과 한 글자만 남은 꼴 뒤에 `님`)을 탐지기가 못 잡았습니다. 이름 패턴이 한글 바로 뒤에 `님`이 오는 꼴만 봐서 별표가 끼면 빠졌습니다. 별표와 ○, ●가 낀 이름이 성씨로 시작하면 가명으로 바꾸도록 `pii-core.ts`와 단위 테스트를 고쳤습니다. 홈 화면 프레임 두 장이 `none`에서 `replaced`로 바뀌었습니다.
796
+ - 세미나 프레임에는 발표자 얼굴이 들어옵니다. 이 실행에서는 발표자 얼굴이 `none`으로 나왔습니다. 얼굴 검출 요청은 있지만 프레임 속 작은 얼굴을 잡는지는 확인하지 못해서 사람이 나오는 프레임은 뽑을 때 가립니다. 이번에는 프레임을 뽑을 때 발표자 자리를 흰 상자로 덮었습니다. SKILL.md 영상 절에 사람이 나오는 프레임은 뽑을 때 가리라고 적었습니다.
797
+ - `redact_image`는 결과를 `captures/<captureId>.<확장자>`로 저장합니다. 프레임 파일 이름과 캡처 id를 따로 지으면 경로가 어긋나서, 프레임은 `source/frames/`에 두고 캡처 id를 파일 이름으로 쓰도록 SKILL.md에 적었습니다.
798
+ - 이미지 치환은 macOS Vision 스크립트가 있어야 돕니다. 핸들러를 스크립트로 직접 부를 때 `visionScript`를 안 넘기면 `unchecked`로 남습니다. MCP 서버 경로는 이미 넘깁니다.
799
+ - yt-dlp 기본 설정은 403으로 끊겼습니다. 720p는 8MB쯤에서, 360p 통합 포맷도 시작하자마자 막혔습니다. 처음에는 `player_client`를 `mweb`으로 바꿔 360p만 받았는데 슬라이드와 앱 화면의 작은 글자가 읽기 어려웠습니다. `web_embedded`로 바꾸고 `-f "136+140"`을 주니 720p가 받아졌습니다(화면 녹화 25MB, 세미나 111MB). 프레임을 720p로 다시 뽑아 치환부터 합치기와 분할까지 다시 돌렸고 결과는 같았습니다. 1080p는 시험하지 않았습니다.
800
+ - 자막은 유튜브 자동 자막을 썼습니다. 화면 녹화 쪽은 오인식이 많아 장면과 맞춰 읽었습니다. 세미나는 슬라이드와 맞추면 숫자를 확인할 수 있었습니다.
801
+ - 세미나는 정산과 발행을 다루는 백엔드 도메인 발표라 구매 화면 근거는 거의 없었고 발행 형태와 사용처 유형 같은 구조 근거만 얻었습니다. 영상 후보를 고를 때 자막에서 화면이나 구매 단어가 나오는지 먼저 보면 이런 어긋남을 줄일 수 있습니다.
802
+ - 영상 단독 문서에도 `plan.confirmedAt` 경고가 뜹니다. 영상 조합을 사용자가 확인한 시각을 적으면 사라져서 규칙은 그대로 두었습니다.
803
+
804
+ 남은 한계도 있습니다. 영상 두 개로 한 번 돌린 결과입니다. 이 실행에서 세미나 슬라이드는 자막 시각으로 프레임을 직접 골랐습니다. 자동 추출은 이후에 8.3절 스크립트로 만들었습니다. 같은 35분 영상에서 18초에 64장이 나왔습니다. 격자로 묶어 눈으로 보니 슬라이드 전환마다 한 장씩 맞게 뽑혔고 발표자만 나오는 구간은 없었습니다. 영상이 하나뿐이라 다른 발표 형식에서 임계값이 맞는지는 모릅니다. 화면 녹화는 남이 손으로 찍은 영상이라 전환 원인은 해석으로만 적었습니다.
805
+
806
+ ### 9.12 품질 작업 뒤 실제 출처 시험
807
+
808
+ 상태: 일부 확인됨. 근거 구조와 구좌 격자와 화면 흐름을 만든 뒤 앞선 상품권 시험 데이터로 다시 돌렸습니다. 레퍼런스 DB에서 받은 화면 10장과 제품 3개입니다. 제품 이름은 적지 않았고 산출물은 `.gestalt/research/` 아래에만 있습니다.
809
+
810
+ - 새 구조로 올린 내용은 비교 행의 정의와 패턴, 칸마다 근거 상태와 출처, 발견 10개, 판단의 우선순위와 할 일, 질문의 확인 주체, 화면 전이 2개, 마커 3개, 시도 기록 2개입니다. `validate` 경고는 0건이었고 개인용 렌더가 통과했습니다.
811
+ - 근거 표기가 "(프레임 08)(프레임 09)"처럼 괄호가 이어 붙어 나왔습니다. 근거 여러 개를 한 괄호로 묶고 이어지는 프레임은 접두어를 한 번만 쓰도록 `evidenceGroupLabel`을 두었습니다. HTML과 Figma 계획이 같은 함수를 씁니다.
812
+ - 못 본 칸은 "확인 못함" 칩 옆에 "확인 못함"이라는 글이 붙어 같은 말이 두 번 읽혔습니다. 글에는 어디서 못 봤는지를 쓰도록 SKILL.md와 output-template.md에 적었습니다.
813
+
814
+ 이 시험에서 원본과의 큰 차이 둘을 이어서 메웠습니다.
815
+
816
+ - 와우와 페인이 구좌 격자 밖에만 있었습니다. 발견의 `slot`이 비교표 기준 이름과 같으면 그 칸 안에도 붙입니다. 시험 데이터는 구좌 이름을 "금액 선택"처럼 줄여 적어서 처음에는 하나도 안 붙었습니다. 그래서 기준 이름과 똑같이 적으라는 안내를 문서에 넣었습니다. 이름이 다르면 `validate`가 알려줍니다. 고친 뒤에는 발견 10개가 모두 칸에 붙었습니다.
817
+ - 종합 리포트가 없었습니다. 0번 앞에 표 네 개를 붙입니다. 기준별 점수, 우선순위, 가져올 와우, 피할 페인입니다. 모두 비교표 기호와 판단 순위와 발견에서 코드가 모아서 새 주장이 없습니다. 점수는 칸 수 대비 비율이고 기호가 없는 칸은 빼서 못 본 칸이 점수를 깎지 않습니다.
818
+
819
+ 원본과 아직 다른 점은 아래와 같습니다.
820
+
821
+
822
+ - 화면 전이는 표로만 나옵니다. 화살표 그림은 사람이 Figma에서 그립니다.
823
+ - 시안이 없는 시험이라 시안 채택 표(`adoptions`)는 돌리지 못했습니다.
824
+ - 내부 실적, PG 회신, 인터뷰 유형은 사람이 입력해야 채워집니다.
825
+ - 검증 가설에 표본, 기간, 성공 기준을 받는 칸이 없습니다.
826
+ - 12열로 바꾼 Figma 열 너비는 실제 Figma에서 다시 확인하지 못했습니다.
827
+ - 우리 서비스 열(6.2절)은 단위 시험까지만 했고 실제 화면으로는 돌려보지 못했습니다.
828
+
829
+ ## 10. 운영 규칙
830
+
831
+ 상태: 정함
832
+
833
+ - 결제 직전에 멈춥니다.
834
+ - 로그인은 사람이 합니다.
835
+ - 캡처에 담긴 개인정보는 가리지 않고 가짜 값으로 바꿉니다(10.1절).
836
+ - 외부 검색어(유아이볼 MCP 등)에 공개 전인 기획 내용을 넣지 않습니다.
837
+ - 캡처는 리서치 결과물 폴더 한 곳에 모읍니다.
838
+ - 브라우저를 자동으로 열 때는 사람 속도로 움직입니다.
839
+
840
+ ### 10.1 개인정보는 가짜 값으로 바꿉니다
841
+
842
+ 가린 화면은 화면이 어떻게 생겼는지를 읽기 어렵게 합니다. 그래서 가리지 않고 같은 꼴의 가짜 값을 넣습니다.
843
+
844
+ | 종류 | 바꾸는 값 |
845
+ | ----------------------------------- | --------------------------------------------------------------- |
846
+ | 전화번호 | `010-0000-0000` 꼴. 끝 네 자리는 값에서 정한 숫자 |
847
+ | 이메일 | `user123@example.com` 꼴 |
848
+ | 이름, 대표자 이름 | 홍길동 같은 가명 목록에서 글자 수를 맞춰 고르고 `님`은 남깁니다 |
849
+ | 주소 | `서울특별시 샘플구 예시로 N` |
850
+ | 카드 번호, 계좌 번호, 주문 번호 | 자릿수와 구분자를 지키고 끝 네 자리 말고는 0으로 |
851
+ | 사업자등록번호, 통신판매업 신고번호 | 같은 꼴로 끝 네 자리 말고는 0으로 |
852
+ | 생년월일, 주민등록번호 | 같은 형식의 가짜 날짜 |
853
+ | 상품권 바코드, 핀 번호, 쿠폰 번호 | 같은 길이의 가짜 숫자. 바코드 이미지는 가짜로 다시 그립니다 |
854
+ | 프로필 사진 | 기본 아바타 |
855
+
856
+ 같은 리서치 안에서 같은 값은 같은 가명이 됩니다. 화면 여러 장에 같은 사람이 나와도 같은 사람으로 읽히게 하려는 것입니다. 가명은 리서치(slug)마다 서버 메모리에만 둔 난수와 값을 섞어 해시로 정합니다. 실제 값과 가명의 대응표는 어디에도 저장하지 않습니다. 난수를 디스크에 쓰지 않으므로 서버를 다시 띄우면 같은 값이 다른 가명이 됩니다. 한 리서치를 서버 재시작을 넘겨 이어 하면 같은 사람이 다른 이름으로 보일 수 있습니다.
857
+
858
+ 바꾸는 시점은 캡처를 디스크에 저장하기 전이고 LLM에 넘기기 전입니다.
859
+
860
+ - 웹 화면은 스크린샷 전에 `ges_research action=redact_script`가 주는 스크립트를 실행해 DOM의 글자, 입력칸, 속성, 프로필 이미지, 바코드를 바꿉니다. 스크립트는 바꾼 개수만 돌려주고 값은 돌려주지 않습니다.
861
+ - 앱 화면과 영상 프레임은 `action=redact_image`가 Vision으로 글자 위치를 찾아 배경색으로 덮고 그 자리에 가짜 값을 그립니다. 원본은 기본으로 지우지 않고 경고합니다. `deleteSource`를 켜면 치환이 끝난 뒤 지웁니다.
862
+
863
+ 캡처마다 `pii` 상태를 적습니다.
864
+
865
+ | 상태 | 뜻 |
866
+ | ----------- | -------------------------------------- |
867
+ | `replaced` | 탐지기가 돌았고 가짜 값으로 바꿨습니다 |
868
+ | `none` | 탐지기가 돌았고 0건이었습니다 |
869
+ | `unchecked` | 탐지기를 못 돌렸습니다 |
870
+
871
+ `none`은 개인정보가 없다는 보증이 아닙니다. 라벨 없는 이름처럼 탐지기가 못 잡는 것이 있습니다. 웹 캡처의 상태는 세션이 research.json에 적는 값이라 서버가 맞는지 검증하지 못합니다. 스크립트를 돌린 뒤 화면을 눈으로 확인하고 적습니다. 닫힌 shadow root, 다른 출처의 iframe, canvas 속 글자는 스크립트가 못 건드리므로 화면에 있으면 `unchecked`로 남깁니다.
872
+
873
+ Vision을 못 쓰는 환경(macOS가 아니거나 swift가 없는 곳)에서는 이미지를 치환하지 못하고 전부 `unchecked`입니다. 공유용 HTML은 `unchecked` 캡처가 하나라도 있으면 만들지 않습니다.
874
+
875
+ ## 11. 열린 결정
876
+
877
+ | 무엇 | 선택지 | 현재 추천 | 상태 |
878
+ | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------- | ---- |
879
+ | 시안 범위 | v1은 기존 시안 링크만 쓰거나, 시안 생성까지 v1에 넣습니다 | v1은 기존 시안 링크만, 시안 생성은 v2 | 정함 |
880
+ | 영상 기반 리서치 페이지 내용을 템플릿에 합칠지 | 합치거나, 영상 전용 양식을 따로 둡니다 | 둘 다 지원. 본문에 `video` 블록으로 합쳐 9번에 그리거나 `video-analysis` 문서로 따로 쓰고 `split_video`와 `merge_video`로 오갑니다(8.5절) | 정함 |
881
+ | 회사 발언 근거 상태 | 도입하거나, 기존 관찰과 해석만 씁니다 | 도입(5.2절) | 정함 |
882
+ | 조합 확인 단계 | 돌리기 전에 사용자 확인을 받거나, 바로 돌립니다 | 확인을 받음(2.2절) | 정함 |
883
+ | 규모 출처 | 사내에 모바일인덱스나 와이즈앱 계정이 있으면 1순위 규모 출처로 쓰고, 없으면 무료 경로(앱스토어 평점 수, 유아이볼 MAU)만 씁니다 | 개인 SOURCE.md로 붙이면 됨(4.1절). 계정이 없는 사람은 무료 경로만 | 제안 |
884
+ | 팀 공용 출처 디렉토리 | `<레포>/.gestalt/research-sources/`를 다음 버전에 열 때 허용할 `access` 종류를 정합니다 | 이번에 열지 않음. 다음 버전에 열 때는 `http`와 `browser`만(4.6절) | 정함 |
885
+ | `evidence` 값 `estimate` | 근거 상태 표에 "추정"을 더하거나, 기존 해석 표기로 받습니다 | 기존 해석 표기로 받음(4.2절) | 정함 |
886
+ | 저장 위치 | gitignore 대상 `.gestalt/research/<slug>/`, 또는 홈 디렉토리 아래 | `.gestalt/research/<slug>/`(5.6절) | 정함 |
887
+ | 원본과의 구조 차이를 템플릿에 반영할지 | 그 외, 부록, 수익 모델 칸을 더하거나 현행을 유지합니다 | 그 외는 판단의 제외로, 수익 모델은 5번 안내로 받고 부록은 더하지 않음(9.8절) | 정함 |
888
+ | 근거 구조 | 관찰과 해석 두 가지만 쓰거나, 다섯 상태와 칸 단위 출처를 받습니다 | 다섯 상태와 출처를 받음(5.2절). 기존 문서는 그대로 읽힘 | 정함 |
889
+ | 화면 흐름 표현 | 표로만 받거나, 화면 노드와 화살표 그림까지 그립니다 | 표로만 받음. 그림은 Figma에서 사람이 손으로 | 정함 |
890
+ | 시안과 리서치의 연결 | 연결 없이 두거나, `adoptions`로 시안마다 가져온 것과 푸는 페인을 겁니다 | `adoptions`로 받음. 안 푸는 페인은 경고 | 정함 |
891
+ | 사용자 측 할 일 | 아래 목록 | 사용자가 처리 | 열림 |
892
+
893
+ 영상 기반 리서치 페이지에서 템플릿에 합칠 후보 7개는 아래처럼 처리했습니다.
894
+
895
+ | 후보 | 처리 |
896
+ | --- | --- |
897
+ | 구좌별과 유형별 분석 단계 | `video.findings[].group`(`flow`, `slot`, `type`, `summary`)으로 받음 |
898
+ | 방향성과 관례의 구분 | `nature`(`direction`, `convention`)로 받음 |
899
+ | 상시, 분기, 단독 구분 | `cadence`(`always`, `seasonal`, `exclusive`)로 받음 |
900
+ | 영상 시각 인용 | `at`(초)로 받고 렌더가 `(영상 N.N초)`를 붙임. 관찰과 회사 발언은 필수 |
901
+ | 이어붙인 스크롤 이미지 | 새로 더하지 않음. 이미 캡처 이미지(9.2절)로 받음 |
902
+ | 색으로 나눈 질문 | `owner`(`immediate`, `data`, `other-team`)로 받음. 처음에는 더하지 않기로 했다가 원본을 다시 읽고 뒤집었습니다 |
903
+ | 내부 데이터 인용 | `internal` 출처 이름만 필드로 받음. 숫자는 사람이 문장에 적을 때만 들어가고 공유용 HTML에는 자료 이름만 실립니다. 처음 결정(더하지 않음)을 뒤집었습니다 |
904
+
905
+ 사용자 측 할 일은 아래와 같습니다.
906
+
907
+ - QA팀 테스트용 아이폰 확보
908
+ - BrowserStack 라이선스 보유 여부 확인
909
+
910
+ ## 12. 구현 순서
911
+
912
+ 상태: 정함. 진행 상태는 항목마다 적었습니다.
913
+
914
+ 1. `research.json` 스키마(zod)를 정합니다. 제품 후보(선정 이유, 출처, 축, 찾은 경로, 규모)와 화면 지도(화면, 진입 경로, 출처, 캡처 상태)를 일급 항목으로 둡니다. 구현을 마쳤습니다.
915
+ 2. 출처 레지스트리를 만듭니다. 내장과 개인 디렉토리를 읽고 `requires`로 쓸 수 있는지 판정하고 `ges_research action=sources`로 보여줍니다(4절). 구현을 마쳤습니다.
916
+ 3. `ges_research`를 만듭니다. HTML 렌더가 먼저이고 Figma 렌더가 뒤입니다. 둘 다 구현했고 Figma는 실제 파일에서도 돌려봤습니다(9.9절).
917
+ 4. `research` SKILL.md를 씁니다. 0번부터 8번까지 한 번에 돌립니다. 구현을 마쳤습니다.
918
+ 5. 같은 과제로 돌려서 원본 Figma 페이지와 나란히 놓고 비교합니다. 평가 기준은 템플릿의 평가 절에 있습니다. 원본과 같은 과제로는 못 돌렸습니다. 구조 비교(9.8절)와 다른 과제로 끝까지 돌린 시험 실행(9.9절)만 했습니다.
919
+ 6. `ges_research action=create_source`를 만듭니다. 질문으로 SOURCE.md를 만들고 시험 실행까지 확인합니다(4.4절). 구현을 마쳤습니다.
920
+ 7. 단계 스킬로 분리합니다. `market-scan`, `screen-ref`, `ux-research`, `reverse-plan`을 만들었습니다. 5번 평가가 끝나기 전에 분리했고 이 사실을 PR 본문에 적습니다.
921
+ 8. 영상 분석을 본문에 합치거나 따로 쓸 수 있게 합니다. `video` 블록, 9번 섹션, `video-analysis` 문서, `split_video`와 `merge_video`를 만들었습니다(8.5절). 단위 시험에 더해 공개 영상 두 개로 한 번 돌려봤습니다(9.11절).
922
+ 9. 근거 구조와 구좌 격자를 넣습니다. `basis`와 `evidence`, `findings`, `adoptions`, `owner`를 스키마와 `validate` 경고에 더하고 HTML과 Figma 렌더에 격자, 발견 표, 시안 환류 표를 그립니다. 구현을 마쳤습니다(5.2절, 9.8절).
923
+ 10. 역기획의 화면 흐름을 받습니다. 화면 플랫폼, 전이(행동과 분기 조건), 번호 마커, 시도 기록, 사용자 유형을 `flow`로 스키마에 더하고 HTML과 Figma 렌더에 표로 그립니다. 구현을 마쳤습니다. 화살표 그림과 화면 위 마커 겹침은 만들지 않았습니다.
924
+ 11. 구좌 격자 안의 와우와 페인, 종합 표를 넣습니다. 발견의 `slot`을 기준 이름과 맞춰 칸에 붙이고 `validate`가 안 맞는 이름을 경고합니다. 0번 앞에 기준별 점수, 우선순위, 가져올 와우, 피할 페인을 코드가 모아 그립니다. 구현을 마쳤습니다(9.12절).
925
+ 12. 우리 서비스 화면을 받습니다. `own-service` 축, 앱 캡처와 웹과 피그마 세 길의 캡처 종류, `figmaUrl`, 종합의 지킬 우리 와우와 고칠 우리 페인 표를 스키마와 렌더에 더했습니다. 구현을 마쳤습니다. 피그마 캡처 네 장으로 한 번 돌려봤고(개인정보 판정은 전부 `none`), 운영 화면으로는 아직 안 돌렸습니다(6.2절).
926
+ 13. 시안이 얹히는 자리를 받습니다. 레퍼런스에서 가져온 시안이 아니라 "어디에 넣을지"를 고르는 안이면 `adoptions`의 `placement`에 적습니다. 레퍼런스도 자리도 없는 시안은 `validate`가 경고합니다. 자리가 하나라도 있으면 HTML과 Figma의 시안 표에 자리 열이 생깁니다. 구현을 마쳤습니다.
927
+ 14. 검증 가설에 성공 기준, 표본, 기간을 받습니다. `verification`에 `successCriteria`와 `sample`, `period`를 선택 필드로 더했습니다. 성공 기준이 없는 가설은 `validate`가 경고하고 HTML 가설 카드에 적힌 칸만 나옵니다. Figma 검증 표는 셋 중 하나라도 있을 때만 열이 생깁니다. 구현을 마쳤습니다.
928
+ 15. 세미나 슬라이드를 자동으로 뽑습니다. `scripts/slide-extract.mjs`가 화면 전환을 찾아 슬라이드를 한 장씩 뽑고 자막을 시각대로 붙입니다(8.3절). MCP 액션이 아니라 스크립트로 두었고 SKILL.md 영상 절에서 셸로 부릅니다. 구현을 마쳤습니다.
929
+ 16. 시안에서 뺀 페인을 표시합니다. 발견에 `excludedReason`을 선택 필드로 더했고 이유가 적힌 페인은 `validate`의 안 풀린 페인 경고에서 빠집니다. 푸는 시안이 있는데 이유도 적혀 있으면 따로 경고합니다. HTML과 Figma 발견 표에 뺀 이유가 같이 나옵니다. 구현을 마쳤습니다.
930
+ 17. 목표 지표를 받을 때 분모와 목표치를 같이 묻습니다. 상품권 리서치에서 "구매 전환율 20%"만 받았더니 분모를 몰라 검증 가설 세 개의 성공 기준이 전부 비었습니다. SKILL.md Step 0만 고쳤고 스키마는 그대로입니다.
931
+ 18. 타사 지표 사례를 받습니다. `metricCases`에 회사, 지표, 분모, 값, 바꾼 것, 시점을 근거와 함께 적고 검증 가설은 `metricCaseIds`로 근거 사례를 가리킵니다. 가설이 있는데 사례가 없거나, 비율 사례에 분모가 없거나, 쓸 만한 사례가 있는데 성공 기준에 안 걸었으면 `validate`가 경고합니다. HTML과 Figma 7번에 사례 표와 근거 사례가 나옵니다. 구현을 마쳤고 실제 리서치로는 아직 안 돌렸습니다.
932
+ 19. 지표가 같이 공개된 사례를 레퍼런스로 먼저 고릅니다. SKILL.md Step 2에서 화면과 지표를 같이 공개한 사례부터 찾습니다. 같은 해법을 대표하는 후보가 둘이면 지표가 있는 쪽을 남깁니다. 지표 사례가 걸린 제품은 HTML과 Figma의 레퍼런스 표와 제품 후보 표에 "지표 사례" 열로 나옵니다. 못 찾은 사례는 열에 올리지 않습니다. 고르는 기준이라 `validate` 경고는 두지 않았습니다.
933
+ 20. 목표치를 우리 데이터에서 세웁니다. 18번의 타사 지표 사례를 상품권 리서치로 찾아보니 공개 사례가 거의 없었고 찾은 것도 업종과 분모가 달랐습니다. 그래서 주 근거를 우리 분석 데이터로 옮기고 타사 사례와 시장 자료는 견주는 데만 씁니다. 최상위에 `target`(개선과 신규, 기준값, 하루 진입 수, 주별 변동, 자연 추세, 목표 범위, 계산 단계, 데이터 출처, 이벤트 목록)을 두고 검증 가설에 `expectedLiftPp`와 `events`를 더했습니다. `validate`가 두 비율 비교 공식(양측 α 0.05, 검정력 0.8)으로 가설마다 군당 표본과 걸리는 일수를 계산해 8주가 넘으면 경고합니다. 기대 효과의 합이 목표에 못 닿거나 기대 효과가 주별 변동보다 작거나 잴 이벤트가 없어도 경고합니다. 기준값이 우리 데이터로 채워져 있으면 타사 사례가 없다는 경고는 끕니다. HTML과 Figma 0번 아래에 목표치 근거 표와 가설별 표본이, 7번 아래에 심어야 할 이벤트 표가 나옵니다. 구현을 마쳤습니다.
934
+ 21. 제품 코드에서 분석 이벤트를 먼저 찾아 둡니다. 분석 도구의 프로젝트와 이벤트 이름은 팀마다 달라서 이름을 짐작해 질의하면 틀립니다. 제품 코드에서 SDK 초기화와 이벤트 호출을 훑어 `.gestalt/research/_products/<제품>.json`에 제품 단위로 두고 `ges_research action=profile`로 저장하고 읽습니다. 저장한 커밋이 지금 HEAD와 다르면 알려줍니다. 키처럼 보이는 값은 스키마가 막습니다. 로컬에만 둡니다. 구현을 마쳤습니다.
935
+ 22. Amplitude 출처를 더합니다. `baseline` 단계를 새로 두고 `amplitude` SOURCE.md에는 프로젝트나 이벤트 이름 없이 찾는 법만 적었습니다. 프로필을 읽고 프로젝트를 사람이 고른 뒤 저장된 차트를 먼저 보고 없으면 퍼널을 질의합니다. MCP 서버 이름은 사람마다 달라서(`claude.ai Amplitude`, `amplitude`, `amp`) 출처에 서버 이름을 박지 않고 그 MCP에만 있는 도구 이름(`tools`)으로 알아봅니다. 세션이 `available.mcpTools`에 도구 이름을 `mcp__<서버>__<도구>` 꼴로 넘기면 `sources` 응답이 이 세션에서 부를 서버(`mcpServer`)를 알려줍니다. 서버 이름으로 적는 출처를 위해 이름 규칙은 공백 하나를 받게 고쳤습니다. 같은 종류의 접근 방법이 여럿이면 연결 이름만 다른 같은 경로라 대체 경로(`fallback`)로 치지 않습니다. 구현을 마쳤고 상품권 리서치로 한 번 돌렸습니다.
936
+ 23. 기능이 붙는 흐름을 Step 0에서 묻습니다. 상품권 리서치에서 대리 퍼널을 상품 종류가 비슷한 픽업 결제로 골랐다가 다이닝 예약으로 바꿨습니다. 예약에 쓰는 상품권이라 붙는 흐름이 예약이었기 때문입니다. `premise.attachTo`(흐름, 도메인)와 제품 후보의 `domainFit`(같은, 이웃, 다른 도메인)을 더했습니다. 가설이나 목표치가 있는데 붙는 흐름이 비었거나 우리 서비스가 아닌 후보 중에 같은 도메인이 하나도 없으면 `validate`가 경고합니다. HTML과 Figma의 0번 전제 표에 붙는 흐름이, 제품 후보 표의 축 칸에 도메인 일치가 나옵니다. 구현을 마쳤습니다.
937
+ 24. 가설 기대 효과에 근거를 답니다. 표본과 걸리는 일수, 목표 도달이 전부 `expectedLiftPp`에서 나오는데 상품권 리서치에서는 이 값을 짐작으로 채웠습니다. `verification.liftBasis`(우리 지난 실험, 타사 사례, 시장 자료, 가정과 근거 상태)를 더했습니다. 근거가 없거나 가정이거나 확인 못한 근거면 `validate`가 경고합니다. HTML과 Figma 가설별 표에 기대 효과 근거 열이 생깁니다. 구현을 마쳤습니다.
938
+ 25. 가설로 목표에 못 닿으면 사용자와 대응을 고릅니다. 상품권 리서치에서 가설 합이 5%p라 기대 목표 20%에 못 닿았는데 경고만 뜨고 다음 할 일이 없었습니다. `target.shortfall`(가설 추가, 효과 큰 가설로 교체, 목표 낮춤, 그대로 둠과 메모)을 더했습니다. 목표를 낮추면 처음 기대 목표를 함께 남겨야 스키마가 받습니다. 대응을 안 골랐으면 고를 대응을 함께 알리고 그대로 둠이 아닌데 여전히 못 닿으면 다시 경고합니다. SKILL.md Step 7에서 AskUserQuestion으로 묻습니다. 구현을 마쳤습니다.