@uzysjung/agent-harness 26.146.0 → 26.147.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-OEIGA5IL.js → chunk-GPH5AWLX.js} +450 -53
- package/dist/chunk-GPH5AWLX.js.map +1 -0
- package/dist/index.js +607 -530
- package/dist/index.js.map +1 -1
- package/dist/trust-tier-drift.js +1 -1
- package/package.json +4 -2
- package/templates/CLAUDE.md +14 -5
- package/templates/agents/implementer.md +1 -1
- package/templates/codex/AGENTS.md.template +11 -0
- package/templates/opencode/AGENTS.md.template +8 -1
- package/templates/opencode/opencode.json.template +0 -1
- package/templates/rules/cli-development.md +2 -2
- package/templates/rules/doc-governance.md +3 -1
- package/templates/rules/test-policy.md +5 -6
- package/templates/scripts/check-absence.sh +205 -0
- package/templates/settings.json +0 -9
- package/templates/skills/gh-issue-workflow/ISSUE.template.md +111 -35
- package/templates/skills/gh-issue-workflow/SKILL.md +268 -138
- package/templates/skills/gh-issue-workflow/references/hierarchy.md +261 -0
- package/templates/skills/gh-issue-workflow/references/operating-model.md +132 -0
- package/templates/skills/gh-issue-workflow/references/readiness.md +121 -0
- package/templates/skills/ui-visual-review/SKILL.md +17 -3
- package/dist/chunk-OEIGA5IL.js.map +0 -1
- package/templates/commands/ecc/checkpoint.md +0 -32
- package/templates/commands/ecc/e2e.md +0 -105
- package/templates/commands/ecc/eval.md +0 -88
- package/templates/commands/ecc/evolve.md +0 -7
- package/templates/commands/ecc/harness-audit.md +0 -73
- package/templates/commands/ecc/instinct-status.md +0 -8
- package/templates/commands/ecc/promote.md +0 -10
- package/templates/commands/ecc/security-scan.md +0 -10
- package/templates/hooks/mcp-pre-exec.sh +0 -122
- package/templates/mcp-allowlist.example +0 -24
- package/templates/rules/playwright-launch.md +0 -16
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
# Hierarchy — 무엇이 실제로 되는가
|
|
2
|
+
|
|
3
|
+
`SKILL.md` §계층의 상세. **먼저 재고, 그 다음에 쓴다** — 이 문서의 절반은 "이 기능이 이 저장소에
|
|
4
|
+
있는가"를 판정하는 절차다.
|
|
5
|
+
|
|
6
|
+
## 1. 가용성 프로브 (읽기 전용, 착수 전 1회)
|
|
7
|
+
|
|
8
|
+
```bash
|
|
9
|
+
# ① gh 버전 — 하위 이슈 명령은 2.94+ 에만 있다
|
|
10
|
+
gh --version
|
|
11
|
+
|
|
12
|
+
# ② 조직 리포인가 — issue type 은 조직 전용
|
|
13
|
+
gh repo view --json nameWithOwner,isInOrganization
|
|
14
|
+
|
|
15
|
+
# ③ issue type 이 실재하는가 (404 면 그 축은 없다)
|
|
16
|
+
gh api repos/:owner/:repo/issues/types
|
|
17
|
+
|
|
18
|
+
# ④ Projects 스코프가 있는가 (없으면 error 로 알려준다)
|
|
19
|
+
gh project list --owner <owner>
|
|
20
|
+
|
|
21
|
+
# ⑤ 라벨·마일스톤 재고
|
|
22
|
+
gh label list --limit 100
|
|
23
|
+
gh api repos/:owner/:repo/milestones --jq '.[] | "\(.number) \(.title)"'
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**빈 결과를 "없음"으로 읽지 마라.** ③④는 실패할 때 메시지를 낸다 — `2>/dev/null` 로 지우면
|
|
27
|
+
"기능 없음"과 "명령이 틀림"이 구분되지 않는다. stderr 를 남기고 메시지를 읽는다.
|
|
28
|
+
|
|
29
|
+
프로브 결과를 사용자에게 한 줄로 보고하고 그에 맞춰 축을 고른다. 예:
|
|
30
|
+
|
|
31
|
+
> issue type 은 개인 리포라 없습니다(404) → 라벨로 대신합니다. Projects 는 토큰 스코프가 없어
|
|
32
|
+
> 건너뜁니다 — 쓰시려면 `gh auth refresh -s project` 를 직접 실행해 주세요.
|
|
33
|
+
|
|
34
|
+
## 2. 4단 매핑과 명령
|
|
35
|
+
|
|
36
|
+
### Task (하위 이슈) 만들기
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 부모를 지정해 새로 만든다
|
|
40
|
+
gh issue create --title "<제목>" --body-file <초안.md> --parent <EPIC> --label "<label>" # WRITE
|
|
41
|
+
|
|
42
|
+
# 이미 있는 이슈를 하위로 붙인다 (여러 개 한 번에)
|
|
43
|
+
gh issue edit <EPIC> --add-sub-issue 101,102,103 # WRITE
|
|
44
|
+
|
|
45
|
+
# 뗀다 — 이슈는 지워지지 않는다
|
|
46
|
+
gh issue edit <EPIC> --remove-sub-issue 102 # WRITE
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Epic (부모 이슈)
|
|
50
|
+
|
|
51
|
+
에픽은 **평범한 이슈**다. 다른 점은 본문이 `## 적용 대상 / AC` 대신 **하위 목록과 순서**를 담고,
|
|
52
|
+
직접 구현되지 않는다는 것뿐이다. 템플릿의 Epic 변형을 쓴다.
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
gh issue view <EPIC> --json subIssues,subIssuesSummary
|
|
56
|
+
gh issue edit <N> --parent <EPIC> # WRITE — 자식 쪽에서 붙이기
|
|
57
|
+
gh issue edit <N> --remove-parent # WRITE
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Milestone
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gh api repos/:owner/:repo/milestones -f title="<이름>" -f description="<한 줄>" \
|
|
64
|
+
-f due_on="YYYY-MM-DDT00:00:00Z" # WRITE — 마일스톤 생성
|
|
65
|
+
gh issue edit <N> -m "<이름>" # WRITE — 붙이기
|
|
66
|
+
gh issue edit <N> --remove-milestone # WRITE — 떼기
|
|
67
|
+
gh issue list --milestone "<이름>" --state all
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
마일스톤은 **진척 막대**를 준다(열림/닫힘 비율). 사용자가 남은 양을 한눈에 보는 유일한 기본 화면이라,
|
|
71
|
+
"이번 묶음"이 생기면 라벨보다 마일스톤이 낫다.
|
|
72
|
+
|
|
73
|
+
### Project (Projects v2)
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
gh project list --owner <owner>
|
|
77
|
+
gh project item-add <번호> --owner <owner> --url <issue-url> # WRITE — 보드 지정이 선행돼야 한다
|
|
78
|
+
gh project item-list <번호> --owner <owner> --format json
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**어느 보드를 쓸지 사용자가 지정하기 전에는 보드를 건드리지 않는다** — `docs/SPEC.md` 의
|
|
82
|
+
`github_project: <URL>` 또는 그 세션의 명시 지정이 선행 조건이다. 스코프가 없으면 여기서 멈추고
|
|
83
|
+
사용자에게 넘긴다. **`gh auth refresh` 를 대신 실행하지 않는다** —
|
|
84
|
+
토큰 권한 확대는 사용자의 결정이다.
|
|
85
|
+
|
|
86
|
+
### Issue type (조직 리포만)
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
gh issue create --type "Bug" ... # WRITE
|
|
90
|
+
gh issue edit <N> --type "Feature" # WRITE
|
|
91
|
+
gh issue edit <N> --remove-type # WRITE
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
개인 리포에서는 이 축이 존재하지 않는다. 라벨 `bug`/`enhancement` 로 대신하고, 나중에 조직으로
|
|
95
|
+
옮기면 그때 승격한다.
|
|
96
|
+
|
|
97
|
+
## 3. 의존 관계 — blocked-by / blocking
|
|
98
|
+
|
|
99
|
+
순서를 "라벨"이 아니라 **관계**로 표현하고 싶을 때 쓴다. 계층(부모-자식)과 다른 축이다:
|
|
100
|
+
부모-자식은 *구성*이고, blocked-by 는 *순서*다.
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
gh issue create --blocked-by 101 --blocking 105 ... # WRITE
|
|
104
|
+
gh issue edit <N> --add-blocked-by 101 # WRITE
|
|
105
|
+
gh issue edit <N> --remove-blocked-by 101 # WRITE
|
|
106
|
+
gh issue edit <N> --add-blocking 105 # WRITE
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
착수 후보를 고를 때 `blockedBy.totalCount == 0` 인 것만 남기면 **순서** 축의 착수 후보가 걸러진다.
|
|
110
|
+
**본문 `## 전제` 체크박스는 이 필터에 안 잡힌다** — 그 축은 `SKILL.md` §5 가 따로 본다.
|
|
111
|
+
|
|
112
|
+
## 4. 읽기 — JSON 필드
|
|
113
|
+
|
|
114
|
+
`gh issue view` / `gh issue list` 가 계층을 JSON 으로 준다. 파싱으로 순서를 정할 때 쓴다.
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
gh issue list --state open --limit 100 \
|
|
118
|
+
--json number,title,labels,milestone,parent,subIssues,blockedBy,state
|
|
119
|
+
|
|
120
|
+
gh issue view <N> --json number,title,body,comments,labels,milestone,\
|
|
121
|
+
parent,subIssues,subIssuesSummary,blockedBy,blocking,issueType,state
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
착수 후보 정렬 (막힌 것 제외 → `P0 > P1 > P2 > 무라벨`):
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
gh issue list --state open --json number,title,labels,blockedBy \
|
|
128
|
+
--jq '[.[] | select((.blockedBy.totalCount // 0) == 0)]
|
|
129
|
+
| sort_by([.labels[].name] as $l
|
|
130
|
+
| if $l|index("P0") then 0
|
|
131
|
+
elif $l|index("P1") then 1
|
|
132
|
+
elif $l|index("P2") then 2
|
|
133
|
+
else 3 end)
|
|
134
|
+
| .[] | "\(.number)\t\(.title)"'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
**세 값을 다 다뤄야 한다.** `index("P0") // 9` 한 줄로 쓰면 P1·P2·무라벨이 전부 같은 키가 돼
|
|
138
|
+
입력 순서만 남는다 — 광고한 우선순위 정렬이 아니다. `index()` 는 **위치**를 반환하므로
|
|
139
|
+
`// 9` 형태는 라벨이 많을 때 P0 가 무라벨과 동률이 되는 문제도 있다.
|
|
140
|
+
|
|
141
|
+
`// 0` 을 붙인 이유: **GitHub Enterprise Server 3.19 미만에는 relationships 자체가 없다.**
|
|
142
|
+
필드가 null 로 오는지 질의가 실패하는지는 이 저장소에서 검증하지 못했다 — 어느 쪽이든
|
|
143
|
+
`// 0` 이 `select` 가 전건을 조용히 버려 "착수 가능한 이슈 0건"을 내는 것을 막는다.
|
|
144
|
+
|
|
145
|
+
## 5. GitHub 쪽 한계 (문서 값)
|
|
146
|
+
|
|
147
|
+
| 항목 | 한계 |
|
|
148
|
+
|---|---|
|
|
149
|
+
| 하위 이슈 중첩 깊이 | **8단계** |
|
|
150
|
+
| 부모 1개당 하위 이슈 | **100개** |
|
|
151
|
+
| Projects 계층 보기 | Hierarchy view (표 뷰에서 그룹·정렬·필터하며 계층 유지) |
|
|
152
|
+
|
|
153
|
+
100개 한계는 실무에선 거의 안 닿지만, 닿는다면 그건 에픽이 너무 크다는 신호다 — 중간 에픽으로
|
|
154
|
+
한 겹 나눈다.
|
|
155
|
+
|
|
156
|
+
## 6. 재조정 시퀀스 (누락 없이)
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
# ① 지금 묶음의 전량을 먼저 뽑는다 — 기억으로 하지 않는다
|
|
160
|
+
gh issue list --milestone "<이번 묶음>" --state all --json number,title,state
|
|
161
|
+
|
|
162
|
+
# ② 뺄 것: 마일스톤·부모만 떼고 이슈는 OPEN 으로 남긴다
|
|
163
|
+
gh issue edit <N> --remove-milestone # WRITE
|
|
164
|
+
gh issue edit <EPIC> --remove-sub-issue <N> # WRITE
|
|
165
|
+
gh issue comment <N> --body "이번 묶음에서 제외 — <사유>. 이슈는 열어 둔다." # WRITE
|
|
166
|
+
|
|
167
|
+
# ③ 다시 셌는지 확인: 뺀 수 + 남은 수 = ① 의 수
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
③ 이 이 절의 요점이다. **"정리했다"는 보고는 개수가 맞을 때만 참이다.**
|
|
171
|
+
|
|
172
|
+
## 7. 라벨 부트스트랩 (없을 때만)
|
|
173
|
+
|
|
174
|
+
기본 라벨은 `bug` `documentation` `duplicate` `enhancement` `good first issue` `help wanted`
|
|
175
|
+
`invalid` `question` `wontfix` 9종뿐이다. 상태·우선순위 축은 만들어야 있다 — **만들 것인지
|
|
176
|
+
사용자에게 먼저 묻는다.**
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
gh label create "P0" --color B60205 --description "먼저 한다" # WRITE
|
|
180
|
+
gh label create "P1" --color D93F0B --description "다음" # WRITE
|
|
181
|
+
gh label create "P2" --color FBCA04 --description "여유 있을 때" # WRITE
|
|
182
|
+
gh label create "decision-pending" --color 5319E7 --description "방향성 OPEN — 착수 차단" # WRITE
|
|
183
|
+
gh label create "ready" --color 0E8A16 --description "방향성 확정 — 착수 가능" # WRITE
|
|
184
|
+
gh label create "blocked" --color B60205 --description "전제 미충족" # WRITE
|
|
185
|
+
gh label create "in-progress" --color 1D76DB --description "PR 열림" # WRITE
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
축은 셋으로 충분하다. 라벨을 늘리면 필터가 아니라 장식이 된다.
|
|
189
|
+
|
|
190
|
+
## 8. wiki 미러 (조건 충족 시에만)
|
|
191
|
+
|
|
192
|
+
`gh` 에 **wiki 서브커맨드가 없다** — 전부 git 으로 한다. wiki 는 별도 git 리포다.
|
|
193
|
+
|
|
194
|
+
### 8-1. 조건 판정 — 대조군으로
|
|
195
|
+
|
|
196
|
+
`git ls-remote` 의 exit **128 은 "리포 없음"과 "인증·네트워크 실패"에 똑같이 나온다.** 그 코드만
|
|
197
|
+
보고 "wiki 가 없다"고 결론내면 틀린다. 대조군을 강제하는 도구로 판정한다:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
gh api repos/:owner/:repo --jq .has_wiki # false 면 여기서 끝. 아무것도 하지 않는다
|
|
201
|
+
URL="$(gh repo view --json url --jq .url)"
|
|
202
|
+
bash .uzys-agent-harness/check-absence.sh \
|
|
203
|
+
--control "git ls-remote ${URL}.git" \
|
|
204
|
+
--subject "git ls-remote ${URL}.wiki.git"
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
| 도구 exit | 뜻 | 다음 |
|
|
208
|
+
|---|---|---|
|
|
209
|
+
| `1` | wiki 리포가 **있다** | 8-2 로 |
|
|
210
|
+
| `0` | wiki 리포가 **없다** | 첫 페이지 생성을 사용자에게 안내하고 끝 |
|
|
211
|
+
| `2` | **판정 불가** — 대조군도 실패했다 | 인증·네트워크를 먼저 본다. "없음"으로 읽지 마라 |
|
|
212
|
+
| **그 밖(`127` 등)** | **도구 자체가 없다** — 판정이 아니다 | 아래 수동 경로로. `0` 으로 읽으면 안 된다 |
|
|
213
|
+
|
|
214
|
+
`127` 은 하네스를 설치하지 않은 저장소에서 실제로 난다. **`0` 이 아니라는 것만으로 "wiki 가 있다"로
|
|
215
|
+
읽지 마라** — 도구가 실행조차 안 된 것은 판정이 아니다(그 도구 자신이 126/127 을 무효로 처리하는
|
|
216
|
+
것과 같은 규율이다).
|
|
217
|
+
|
|
218
|
+
도구가 없는 환경이면 두 명령을 직접 돌린다 — **`2>/dev/null` 을 붙이지 않는다.** `remote:
|
|
219
|
+
Repository not found.` 라는 진단 문자열이 128 의 원인을 가르는 유일한 재료다.
|
|
220
|
+
|
|
221
|
+
`0` 이면 여기서 멈추고 사용자에게 넘긴다 — 첫 페이지는 **웹 UI 에서 사람이** 만든다
|
|
222
|
+
(리포 → Wiki 탭 → Create the first page). 에이전트가 할 수 있는 경로가 없다.
|
|
223
|
+
|
|
224
|
+
### 8-2. 미러 — 여기부터 전부 `WRITE`
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
# 변수는 새 셸에서 다시 만든다 — 앞 블록의 것이 살아 있다고 가정하면 빈 경로로 clone 하고
|
|
228
|
+
# 그 실패가 exit 128 이라 8-1 의 "리포 없음"과 구분되지 않는다
|
|
229
|
+
URL="$(gh repo view --json url --jq .url)"
|
|
230
|
+
SRC="$(git rev-parse --short HEAD)"
|
|
231
|
+
D="$(mktemp -d)" # 고정 경로 금지
|
|
232
|
+
git clone "${URL}.wiki.git" "$D" # WRITE 구간 시작 — 승인 없이 push 하지 않는다
|
|
233
|
+
|
|
234
|
+
# 페이지 파일명은 제목이 곧 파일명이다. 공백은 `-` (사이드바를 두려면 `_Sidebar.md`)
|
|
235
|
+
{
|
|
236
|
+
printf '> 이 페이지는 `%s` 의 사본이다. **편집은 저장소에서** 하고 여기는 미러만 한다.\n' \
|
|
237
|
+
"docs/NORTH_STAR.md"
|
|
238
|
+
printf '> 미러한 커밋: `%s`\n\n' "$SRC"
|
|
239
|
+
cat docs/NORTH_STAR.md
|
|
240
|
+
} > "$D/North-Star.md"
|
|
241
|
+
|
|
242
|
+
git -C "$D" add North-Star.md
|
|
243
|
+
git -C "$D" commit -m "docs: mirror docs/NORTH_STAR.md at ${SRC}"
|
|
244
|
+
git -C "$D" push # WRITE — 사용자 승인 후에만
|
|
245
|
+
|
|
246
|
+
# 올렸다는 것을 증거로 만든다. push 가 실패했으면 **작업 디렉터리를 지우지 않는다** —
|
|
247
|
+
# 지우면 커밋한 것이 사라져 clone 부터 다시 해야 한다
|
|
248
|
+
if git -C "$D" status -sb | grep -q '\[ahead'; then
|
|
249
|
+
echo "push 미완 — $D 를 남긴다" >&2
|
|
250
|
+
else
|
|
251
|
+
git -C "$D" log --oneline -1 # 이슈/보고에 붙일 증거
|
|
252
|
+
rm -rf "$D"
|
|
253
|
+
fi
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
배너는 **첫 두 줄**이어야 한다. 이게 없으면 다음 사람이 wiki 를 원본으로 편집하고, 그때부터
|
|
257
|
+
원본이 둘이 된다. 커밋 해시는 `git rev-parse --short HEAD` 로 **미러 시점의 본체 HEAD** 를 쓴다 —
|
|
258
|
+
날짜만 적으면 어느 상태의 사본인지 알 수 없다.
|
|
259
|
+
|
|
260
|
+
미러 대상은 **목표층뿐**이다 — 움직이는 것(계획·백로그·ADR)을 미러하면 미러가 항상 틀린다.
|
|
261
|
+
계획이 바뀐 사유는 이슈나 ADR 에 있고, wiki 는 그 번호를 가리킨다.
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# 운영 원칙 — 무엇이 고정이고 무엇이 움직이는가
|
|
2
|
+
|
|
3
|
+
목표는 안 바뀌고, 묶음과 순서는 계속 바뀐다. 그 둘을 같은 자리에 두면 순서를 바꿀 때마다 목표가
|
|
4
|
+
흔들리거나, 목표를 지키려고 순서를 못 바꾸게 된다. 아래 일곱 줄은 **그 분리를 어디에 두는가**에
|
|
5
|
+
대한 것이고, 각 줄은 측정된 사실을 근거로 한다.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## P1. 층은 성질로 가른다 — 이력이 쌓이는 층엔 순서를 두지 않는다
|
|
10
|
+
|
|
11
|
+
| 층 | 실물 | 성질 | 바뀔 때 |
|
|
12
|
+
|---|---|---|---|
|
|
13
|
+
| **목표(북극성)** | 문서. 이슈가 아니다 | **고정** | 바뀌면 그건 다른 제품이다 |
|
|
14
|
+
| **이슈**(에픽·태스크) | Issue + 하위 이슈 | **축적** — 지우지 않는다 | 떼되 남긴다 |
|
|
15
|
+
| **묶음**(마일스톤·프로젝트) | Milestone / Projects v2 | **재편 자유** | 흔적 없이 바꾼다 |
|
|
16
|
+
|
|
17
|
+
**핵심은 세 번째 줄이 왜 자유로운가다: 거기에 이력이 없기 때문이다.** 마일스톤 설명란에
|
|
18
|
+
"이렇게 하기로 했다"를 적는 순간 그 마일스톤은 못 지우는 물건이 되고, 재편의 자유가 사라진다.
|
|
19
|
+
|
|
20
|
+
→ **결정은 이슈 본문에, 순서는 묶음에.** 묶음에는 이름·기한·소속만 둔다.
|
|
21
|
+
|
|
22
|
+
## P2. 본문 이력은 보존되지만 **보이지 않는다** — 보존과 실효는 다르다
|
|
23
|
+
|
|
24
|
+
실측(2026-08-17, 본문을 통째로 교체한 이슈 1건 대상):
|
|
25
|
+
|
|
26
|
+
| 조회 경로 | 이전 본문이 나오는가 |
|
|
27
|
+
|---|---|
|
|
28
|
+
| `gh issue view` · 웹 기본 화면 | ✗ 현재 판만 |
|
|
29
|
+
| REST 타임라인 (`/issues/N/timeline`) | ✗ **이벤트 0건** (`labeled`·`sub_issue_added`·`cross-referenced` 는 남는데 본문 편집은 안 남는다) |
|
|
30
|
+
| REST `/issues/N/edits` | ✗ 404 |
|
|
31
|
+
| GraphQL `issue.userContentEdits` | ✅ **전 판 전문**(`totalCount: 2`, `diff` 가 이전 본문 전체) |
|
|
32
|
+
|
|
33
|
+
기술적으로는 남는다. 그러나 **아무도 GraphQL 을 치지 않는다** — 리뷰어도, 다음 세션의 에이전트도,
|
|
34
|
+
6개월 뒤의 본인도. 그래서 실무 규율은 "보존된다"가 아니라 이것이다:
|
|
35
|
+
|
|
36
|
+
→ **본문을 교체할 때, 사라지면 안 되는 것은 본문 안에 남기거나 코멘트로 먼저 옮긴다.**
|
|
37
|
+
특히 ⓐ 사용자 원문 요청 ⓑ 기각된 대안과 그 이유. 둘 다 나중에 "왜 이렇게 됐지"의 답이다.
|
|
38
|
+
|
|
39
|
+
## P3. 링크는 **도구가 만드는 것만** 산다
|
|
40
|
+
|
|
41
|
+
측정된 감쇠:
|
|
42
|
+
|
|
43
|
+
| 측정 | 값 | 출처 |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 커밋이 이슈에 연결된 비율 (오픈소스 6개 평균) | **60%** | *Traceability in the Wild* — https://arxiv.org/abs/1804.02433 |
|
|
46
|
+
| 커밋 메시지 안 링크 중 접근 불가 | **34~43%** (distinct 기준 약 70%) | *18 Million Links in Commit Messages* — https://arxiv.org/abs/2305.16591 |
|
|
47
|
+
| 릴리스 아티팩트 중 추적 링크 없음 | **47%** (+ 12% 깨짐) | https://arxiv.org/abs/2511.18187 |
|
|
48
|
+
|
|
49
|
+
손으로 적은 URL 은 감쇠 대상이고, GitHub 이 **이벤트로 남기는** 형태는 아니다. 실측한 살아남는 형태:
|
|
50
|
+
|
|
51
|
+
| 남는 것 (타임라인 이벤트) | 만드는 법 |
|
|
52
|
+
|---|---|
|
|
53
|
+
| `sub_issue_added` | `gh issue create --parent N` · `gh issue edit E --add-sub-issue N` |
|
|
54
|
+
| `cross-referenced` | 다른 이슈·PR 본문/코멘트에 `#N` |
|
|
55
|
+
| `closed` (PR 연결 포함) | PR body 의 `Closes #N` |
|
|
56
|
+
| `labeled` · `milestoned` · `assigned` | 해당 gh 플래그 |
|
|
57
|
+
|
|
58
|
+
→ **추적성을 원하면 문장이 아니라 이벤트를 만들어라.** 본문에 "관련: <URL>" 을 적는 것은
|
|
59
|
+
추적성이 아니라 메모다.
|
|
60
|
+
|
|
61
|
+
## P4. 순서는 세 축으로 나눠 적는다 — 셋은 **수명이 다르다**
|
|
62
|
+
|
|
63
|
+
| 축 | 무엇을 뜻하나 | 수명 | 재정렬 비용 |
|
|
64
|
+
|---|---|---|---|
|
|
65
|
+
| 라벨 `P0`/`P1`/`P2` | **오늘의 판단** | 짧다 | 0 — 마음껏 바꾼다 |
|
|
66
|
+
| 마일스톤 | **이번 묶음의 경계** | 중간 | 0 — 떼면 된다 |
|
|
67
|
+
| `blocked-by` | **사실** (A 없이는 B 를 판정할 수 없다) | 길다 | 높다 — 바뀌면 뭘 잘못 안 것이다 |
|
|
68
|
+
|
|
69
|
+
두 방향의 오용이 있고 둘 다 조용히 아프다:
|
|
70
|
+
|
|
71
|
+
- **우선순위를 `blocked-by` 로** 표현 → 재정렬할 때마다 의존 그래프를 수리해야 한다
|
|
72
|
+
- **진짜 의존을 라벨로** 표현 → 재정렬이 순서를 깨뜨려도 아무도 모른다
|
|
73
|
+
|
|
74
|
+
→ `gh issue list --json number,blockedBy` 에서 `blockedBy.totalCount == 0` 인 것만 남기면
|
|
75
|
+
**순서** 축의 후보가 걸러진다. 이게 성립하려면 `blocked-by` 에 취향이 섞이면 안 된다.
|
|
76
|
+
본문 `## 전제` 체크박스는 이 필터에 안 잡힌다 — 그 축은 `SKILL.md` §5 가 따로 본다.
|
|
77
|
+
|
|
78
|
+
## P5. 재편의 단위는 **떼기**이지 지우기가 아니다 — 그리고 개수로 확인한다
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
gh issue list --milestone "<이번 묶음>" --state all --json number | jq length # ① 먼저 센다
|
|
82
|
+
gh issue edit <N> --remove-milestone # WRITE
|
|
83
|
+
gh issue edit <EPIC> --remove-sub-issue <N> # WRITE
|
|
84
|
+
gh issue comment <N> --body "이번 묶음에서 제외 — <사유>. 이슈는 열어 둔다." # WRITE
|
|
85
|
+
# ③ 뺀 수 + 남은 수 == ① ← 이게 맞을 때만 "정리했다"가 참이다
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**"누락 없이 정리했다"는 개수가 맞을 때만 참인 문장이다.** 기억으로 하지 않는다.
|
|
89
|
+
|
|
90
|
+
## P6. 크기 — 계층도 백로그도 상한이 있다
|
|
91
|
+
|
|
92
|
+
| 상한 | 값 | 출처 |
|
|
93
|
+
|---|---|---|
|
|
94
|
+
| 하위 이슈 중첩 깊이 | **8단계** | GitHub |
|
|
95
|
+
| 부모 1개당 하위 이슈 | **100개** | GitHub |
|
|
96
|
+
| 활성 백로그 크기 | **50 내외** — 넘으면 하나 빼고 넣는다 | ⚠ **실무 경험칙(측정치 아님)** — 제품 관리 실무에서 널리 쓰이는 상한. 근거 연구 없음 |
|
|
97
|
+
|
|
98
|
+
100 에 닿았다면 한계가 문제가 아니라 **에픽이 너무 크다**는 신호다 — 중간 에픽으로 한 겹 나눈다.
|
|
99
|
+
|
|
100
|
+
참고 실측(GitHub 저장소 34곳 · 이슈 약 118,000건 — *Characterizing Issue Management in Runtime
|
|
101
|
+
Systems*, https://arxiv.org/abs/2310.15971): 라벨은 **82.65%** 에 붙지만 assignee 는 **28.30%**
|
|
102
|
+
뿐이고, **90.65%** 가 코멘트를 최소 1개 갖는다(닫히기까지 중앙값 **58일**). 모집단이 JVM 계열
|
|
103
|
+
런타임 저장소라 다른 생태계로 그대로 옮기지 못한다.
|
|
104
|
+
|
|
105
|
+
→ **판정 축은 라벨에 두고 assignee 에 기대지 마라.** 1인 + 에이전트 구성에서는 assignee 가
|
|
106
|
+
아예 없는 축이다. 그리고 논의가 코멘트에 모인다는 것은 **결정도 거기 묻힌다**는 뜻이다(P2).
|
|
107
|
+
|
|
108
|
+
## P7. 묶음 이름은 **날짜가 아니라 확신도**로 짓는다
|
|
109
|
+
|
|
110
|
+
순서가 바뀔 것을 전제하면 날짜 이름은 곧 거짓이 된다. 그렇다고 이름을 고치면
|
|
111
|
+
`gh issue edit -m "<name>"` 이 **이름으로** 붙이므로 기존 스크립트·문서가 함께 깨진다.
|
|
112
|
+
|
|
113
|
+
확신도로 나누는 형태(Now / Next / Later)가 이 문제를 피한다:
|
|
114
|
+
|
|
115
|
+
| 묶음 | 뜻 | 안에 있는 것 |
|
|
116
|
+
|---|---|---|
|
|
117
|
+
| `now` | 해법까지 확정 | 방향성 `YYYY-MM-DD 확정` 인 이슈만 |
|
|
118
|
+
| `next` | 문제는 확정, 해법 미정 | 방향성 `OPEN` 이어도 된다 |
|
|
119
|
+
| `later` | 방향만 | AC 없이 배경만 있어도 된다 |
|
|
120
|
+
|
|
121
|
+
이름이 세 개뿐이면 재정렬은 **소속을 옮기는 일**이 되고 이름은 영원히 참이다.
|
|
122
|
+
|
|
123
|
+
---
|
|
124
|
+
|
|
125
|
+
## 이 원칙들이 서로 맞물리는 방식
|
|
126
|
+
|
|
127
|
+
목표는 안 움직이고(P1) · 이슈는 지워지지 않고(P5) · 그 사이의 묶음만 자유롭게 재편된다(P1·P7).
|
|
128
|
+
재편이 안전한 이유는 이력이 이슈 쪽에 이벤트로 박혀 있기 때문이고(P3), 그 이력이 실제로 읽히려면
|
|
129
|
+
결정이 본문에 있어야 한다(P2). 순서를 표현하는 세 축의 수명을 섞지 않으면(P4) 재정렬이
|
|
130
|
+
의존 관계를 조용히 깨뜨리지 않는다.
|
|
131
|
+
|
|
132
|
+
**한 줄로**: 바뀌는 것을 바뀌어도 되는 층에 두고, 바뀌면 안 되는 것은 이벤트로 박아 둔다.
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# Readiness — 이슈가 에이전트에게 작동하는가
|
|
2
|
+
|
|
3
|
+
`SKILL.md` §준비도의 상세. 이 문서가 있는 이유는 "좋은 이슈를 쓰자"가 취향 논쟁으로 끝나기
|
|
4
|
+
때문이다 — **어떤 속성이 실제로 결과를 바꾸는지 측정된 값**이 있으면 논쟁이 필요 없다.
|
|
5
|
+
|
|
6
|
+
## 1. 근거
|
|
7
|
+
|
|
8
|
+
주 출처는 **에이전트 PR 3,180건**과 그에 대응하는 이슈를 짝지어, 이슈를 32개 기준으로 채점하고
|
|
9
|
+
**머지 여부를 예측**한 연구다(Random Forest, 중앙값 AUC 72% · precision 69.1% · recall 77.4%).
|
|
10
|
+
아래 수치는 각 축이 높을 때의 **머지율 차이**다.
|
|
11
|
+
|
|
12
|
+
- *What Makes a GitHub Issue Ready for Copilot?* — https://arxiv.org/abs/2512.21426
|
|
13
|
+
- GitHub 공식 지침 — https://docs.github.com/copilot/how-tos/agents/copilot-coding-agent/best-practices-for-using-copilot-to-work-on-tasks
|
|
14
|
+
- 이슈 품질의 고전 연구(사람 대상, 재현 절차·스택트레이스·테스트케이스가 가장 유용하나 가장 쓰기
|
|
15
|
+
어렵다) — Bettenburg et al., *What Makes a Good Bug Report?*
|
|
16
|
+
|
|
17
|
+
**해석 주의**: 이 수치는 상관이지 인과가 아니고, 모집단은 공개 저장소의 Copilot PR 이다. 방향을
|
|
18
|
+
정하는 데는 충분하지만 "8.38%p 올랐다"를 이 저장소에서 재현할 수는 없다.
|
|
19
|
+
|
|
20
|
+
## 2. 올리는 축 (높을수록 머지율 ↑)
|
|
21
|
+
|
|
22
|
+
| 축 | 차이 | 이슈에서 이렇게 보인다 |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| **자족성** (self-contained) | **+16.7%p** | 링크를 안 따라가도 착수 가능. 필요한 요지가 본문에 있다 |
|
|
25
|
+
| **범위** (well-scoped) | **+16.4%p** | 한 PR 로 닫힌다. "그리고"로 이어붙은 목표가 없다 |
|
|
26
|
+
| **익숙한 작업** | +13.1%p | 이 코드베이스·생태계의 흔한 형태. 신규 발명이 아니다 |
|
|
27
|
+
| **기대·경계 동작 명시** | +10.7%p | 정상 경로 외에 예외·edge 를 최소 1개 적었다 |
|
|
28
|
+
| **모호성 회피** | +8.4%p | "개선"·"정리"·"최적화" 같은 판정 불가 동사가 없다 |
|
|
29
|
+
| **실행 가능한 입도** | +7.6%p | 단계로 쪼개져 있어 어디부터 손댈지 보인다 |
|
|
30
|
+
| **좌표 제공** | +6.4%p | 파일·모듈·심볼이 적혀 있다 |
|
|
31
|
+
| **해법 방향** | +6.1%p | "이렇게 가면 된다" 한 줄. 강제가 아니라 출발점 |
|
|
32
|
+
|
|
33
|
+
상위 둘이 나머지를 합친 것만큼 크다. **쪼개기와 자족성이 다른 무엇보다 먼저다.**
|
|
34
|
+
|
|
35
|
+
## 3. 내리는 축
|
|
36
|
+
|
|
37
|
+
| 축 | 차이 | 실제 의미 · 단서 |
|
|
38
|
+
|---|---|---|
|
|
39
|
+
| **성능 고려** | −33.8%p | 가장 큰 수치지만 **가장 약한 근거**다 — 표본 **21건**이고, 원저자가 *"거절 이유를 찾지 못했다 · 추가 연구가 필요하다"* 고 **직접 유보**를 달았다(전체 이슈의 92.8%가 이 축 점수 없음). 규칙이 아니라 신호로만 쓴다 |
|
|
40
|
+
| **환경·설정 서술** | −9.4%p | 재현에 특수 환경이 필요하다 = 에이전트가 그 환경을 못 만든다 |
|
|
41
|
+
| **외부 의존 강조** | −6.9%p | 외부 API·마이그레이션이 걸리면 저장소 안에서 안 끝난다 |
|
|
42
|
+
| **재현 절차** | **−11.5%p** | 아래 단서 참조 — 이 스킬은 이 축을 **따르지 않는다** |
|
|
43
|
+
| **본문 길이** | 예측확률 **진폭 0.09**, 단조 감소 | 모델에서 **가장 중요한 피처(rank 1)**. 길수록 나쁘다 |
|
|
44
|
+
|
|
45
|
+
**수치 읽는 법 주의**: 원문의 *amplitude* 는 *"피처를 변화시킬 때 관측된 **최대·최소 예측확률의
|
|
46
|
+
차이**"* 이지 단위당 기울기가 아니다. 그리고 같은 표에서 **제목 길이는 방향이 반대(↗, 진폭 0.15)**
|
|
47
|
+
다 — "길면 나쁘다"가 길이 전반에 성립하지는 않는다. 본문에만 해당한다.
|
|
48
|
+
|
|
49
|
+
**앞의 셋은 글이 아니라 작업의 신호다 — 글에서 지우는 것으로 대응하지 마라.** 그건 *작업이 무겁다*는 진단이다 — 대응은
|
|
50
|
+
**쪼개거나 사람 레인으로 보내는 것**이다. 성능 얘기를 지운 이슈는 준비된 이슈가 아니라 함정이다.
|
|
51
|
+
|
|
52
|
+
**재현 절차 축은 이 스킬이 의도적으로 거스른다.** 주 출처는 재현 절차 점수가 높을수록 머지율이
|
|
53
|
+
11.5%p 낮다고 보고했다(같은 축이 전체 이슈 품질과 Spearman 72.56%로 강한 양의 상관인데도).
|
|
54
|
+
그런데 `ISSUE.template.md` 는 `## 근거` 칸을 재현 명령·출력·대조군으로 **크게** 잡는다. 이유는
|
|
55
|
+
모집단이 다르기 때문이다 — 그 연구의 종속변수는 *"에이전트 PR 이 머지됐는가"* 이고, 이 스킬이
|
|
56
|
+
지키려는 것은 *"사람이 나중에 판정할 수 있는가"* 다. 사람 대상 고전 연구(Bettenburg)는 재현
|
|
57
|
+
절차·스택트레이스·테스트케이스를 **가장 유용한 정보**로 꼽는다. **상충을 알고 후자를 택했다.**
|
|
58
|
+
|
|
59
|
+
길이만 글의 문제다. 필요한 축을 채우고 **남는 서술을 지운다** — 축을 지워서 짧게 만들면
|
|
60
|
+
자족성이 같이 떨어진다.
|
|
61
|
+
|
|
62
|
+
## 4. 등록 전 체크 (SKILL.md 의 8줄 확장)
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
[ ] 범위 한 PR 로 닫히는가. "그리고"가 목표에 있으면 쪼갠다
|
|
66
|
+
[ ] 자족 이 본문만 읽고 착수 가능한가. 링크 뒤 요지는 옮겨 적는다
|
|
67
|
+
[ ] 모호성 판정 불가 동사(개선/정리/최적화/고도화)가 0인가
|
|
68
|
+
[ ] AC When → Then 이 pass/fail 로 갈리는가. "잘 동작함"은 AC 가 아니다
|
|
69
|
+
[ ] 좌표 건드릴 파일·모듈·심볼이 적혀 있는가
|
|
70
|
+
[ ] 경계 예외·edge 를 최소 1개 적었는가
|
|
71
|
+
[ ] 길이 위를 채운 뒤 남는 서술을 지웠는가
|
|
72
|
+
[ ] 무게 성능·환경·외부 의존이 본문의 축이면 → 쪼개거나 사람 레인
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
여덟 줄을 못 채우면 그건 **이슈가 아니라 메모**다. 메모는 메모라고 적고 에픽 본문의
|
|
76
|
+
`## 후속 작업` 에 한 줄로 남긴다 — 반쯤 채운 이슈를 양산하면 목록이 신뢰를 잃는다.
|
|
77
|
+
|
|
78
|
+
## 5. 작업 종류별 적합도
|
|
79
|
+
|
|
80
|
+
GitHub 공식 지침이 밝힌 갈림. **잘 맞는 것**: 버그 수정 · 테스트 추가 · 문서 갱신 · 접근성 개선 ·
|
|
81
|
+
좁은 UI 변경 · 작은 기술부채. **사람이 낫다**: 저장소를 넘나드는 변경 · 깊은 도메인 판단 ·
|
|
82
|
+
큰 리팩터(일관성 설계가 필요) · 보안/인증/장애 대응 · 요구가 열려 있어 반복 탐색이 필요한 일 ·
|
|
83
|
+
**배우려고 하는 일**.
|
|
84
|
+
|
|
85
|
+
마지막 항목은 품질 문제가 아니다 — 이해를 얻는 게 목적이면 위임이 목적을 없앤다.
|
|
86
|
+
|
|
87
|
+
## 6. 나쁜 이슈 → 준비된 이슈 (같은 내용)
|
|
88
|
+
|
|
89
|
+
**전**
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
## 배경
|
|
93
|
+
로그인이 가끔 안 됨. 확인해서 고쳐주세요.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
준비도: 범위 ✗ · 자족 ✗ · 모호성 ✗ · AC ✗ · 좌표 ✗ · 경계 ✗ → 착수 불가.
|
|
97
|
+
|
|
98
|
+
**후**
|
|
99
|
+
|
|
100
|
+
```
|
|
101
|
+
## 배경
|
|
102
|
+
세션 만료 직후 로그인하면 401 이 뜨고 로그인 화면으로 되돌아온다. 재시도하면 성공한다.
|
|
103
|
+
만료 갱신 경로에서 옛 토큰을 한 번 더 쓰는 것으로 보인다.
|
|
104
|
+
|
|
105
|
+
## 전제 (Given)
|
|
106
|
+
- [x] 재현: 세션 TTL 을 60s 로 낮추고 만료 후 로그인
|
|
107
|
+
|
|
108
|
+
## 방향성 (2026-08-17 확정)
|
|
109
|
+
- 갱신 실패 시 **저장된 토큰을 먼저 폐기**하고 재인증한다 (재시도 루프 추가는 기각 — 증상만 가린다)
|
|
110
|
+
|
|
111
|
+
## 적용 대상 / AC
|
|
112
|
+
- `src/auth/session.ts` 의 `refresh()` · `src/auth/client.ts` 의 인터셉터
|
|
113
|
+
- [ ] 만료 토큰으로 로그인 → 401 없이 1회에 성공 (When 만료 후 로그인 → Then 200)
|
|
114
|
+
- [ ] 갱신 중 네트워크 실패 → 저장 토큰이 남지 않는다 (경계)
|
|
115
|
+
- [ ] 위 둘을 고정하는 회귀 테스트가 변경 없이는 red
|
|
116
|
+
|
|
117
|
+
## 후속 작업
|
|
118
|
+
- [ ] 동시 갱신 요청 합치기는 #M 으로 분리 — 이번 범위 밖
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
바뀐 것은 분량이 아니라 **판정 가능성**이다. 두 번째는 다 읽고 나면 무엇을 하면 끝인지 안다.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ui-visual-review
|
|
3
|
-
description: "Captures screenshots of key UI flows after E2E tests pass, runs an agent-side first-pass diff (regressions, console errors, layout shifts), then surfaces a checklist for the user's final approval. Also owns the browser-launch
|
|
3
|
+
description: "Captures screenshots of key UI flows after E2E tests pass, runs an agent-side first-pass diff (regressions, console errors, layout shifts), then surfaces a checklist for the user's final approval. Also owns the browser-launch rules AND procedure — read it BEFORE opening a browser by any means (Playwright, chrome-devtools MCP, a launcher script), not after something feels slow: the prohibitions here exist because attaching to the user's live Chrome or using a throwaway context degrades the session in ways that are expensive to undo. Fire it for manual E2E checks, UX/fidelity comparison against a reference product, a one-time OAuth login, automated capture, and whenever the user reports the browser is laggy. Use after E2E tests pass on a UI track (csr-*, ssr-*, full)."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# UI Visual Review
|
|
@@ -33,8 +33,22 @@ E2E 테스트가 PASS 했어도 시각적 회귀(layout shift, 색상/간격 변
|
|
|
33
33
|
|
|
34
34
|
## 브라우저를 띄우는 법 (영속 profile)
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
36
|
+
브라우저를 띄워 **사람이 직접 쓰는** 모든 상황(E2E 수동 확인, UX 비교, fidelity audit, OAuth
|
|
37
|
+
로그인)에 적용한다. **브라우저를 여는 어떤 방법이든 그 전에** 이 절을 읽는다 — 아래 금지는 어기고
|
|
38
|
+
나서 되돌리는 비용이 크다(재로그인·세션 소실·사용자 입력 지연).
|
|
39
|
+
|
|
40
|
+
### 절대 금지
|
|
41
|
+
|
|
42
|
+
- **사용자 활성 Chrome 에 attach** (`mcp__chrome-devtools__*`) — 사용자 입력이 느려진다.
|
|
43
|
+
- **일회성 browser context** (Playwright MCP, `browser.newContext()`) — 영속 profile dir 이 없어
|
|
44
|
+
cookie · IndexedDB · Service Worker 가 휘발한다. 매 launch 재로그인 + 버벅임.
|
|
45
|
+
- **사용자가 키를 입력하는 동안 자동화 process 동시 실행** — CDP latency.
|
|
46
|
+
- **reference SaaS(Linear/Notion/Jira 등) 측 `page.goto()` · `page.reload()`** — sidebar click only.
|
|
47
|
+
|
|
48
|
+
일회성 context 나 devtools attach 를 발견하면 즉시 닫고 아래 영속 profile launcher 로 재기동한다.
|
|
49
|
+
**사용자가 "버벅인다"고 하면 이 절의 위반 확인이 1순위다.**
|
|
50
|
+
|
|
51
|
+
### 절차
|
|
38
52
|
|
|
39
53
|
핵심은 셋이다. ⓐ **영속 profile dir** — 프로젝트별로 분리해 매 iteration 재사용한다. cookie ·
|
|
40
54
|
IndexedDB · Service Worker 가 보존돼야 재로그인이 사라진다. ⓑ **Chrome for Testing 별도 binary**
|