@tienne/gestalt 0.122.0 → 0.123.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/package.json +1 -1
- package/dist/plugin/.codex-plugin/plugin.json +1 -1
- package/dist/plugin/skills/_shared/pr-base.md +169 -0
- package/dist/plugin/skills/pr/SKILL.md +20 -10
- package/dist/plugin/skills/review/SKILL.md +43 -14
- package/dist/plugin/skills/ship/SKILL.md +127 -23
- package/dist/src/architecture/ir-schema.d.ts +26 -26
- package/dist/src/core/config.d.ts +12 -12
- package/dist/src/core/types.d.ts +64 -0
- package/dist/src/core/types.d.ts.map +1 -1
- package/dist/src/digest/blocks.d.ts +34 -34
- package/dist/src/digest/comments.d.ts +6 -6
- package/dist/src/digest/lens-schema.d.ts +2 -2
- package/dist/src/digest/lint-lens-context.d.ts +4 -4
- package/dist/src/digest/schema.d.ts +55 -55
- package/dist/src/lens/attach.d.ts +10 -10
- package/dist/src/mcp/schemas.d.ts +238 -225
- package/dist/src/mcp/schemas.d.ts.map +1 -1
- package/dist/src/mcp/schemas.js +86 -16
- package/dist/src/mcp/schemas.js.map +1 -1
- package/dist/src/mcp/tools/review-passthrough.d.ts.map +1 -1
- package/dist/src/mcp/tools/review-passthrough.js +61 -2
- package/dist/src/mcp/tools/review-passthrough.js.map +1 -1
- package/dist/src/mcp/tools/status.d.ts +1 -1
- package/dist/src/research/schema.d.ts +66 -66
- package/dist/src/research/target.d.ts +6 -6
- package/dist/src/review/consensus-input.d.ts +11 -0
- package/dist/src/review/consensus-input.d.ts.map +1 -0
- package/dist/src/review/consensus-input.js +102 -0
- package/dist/src/review/consensus-input.js.map +1 -0
- package/dist/src/review/passthrough-engine.d.ts +9 -2
- package/dist/src/review/passthrough-engine.d.ts.map +1 -1
- package/dist/src/review/passthrough-engine.js +58 -10
- package/dist/src/review/passthrough-engine.js.map +1 -1
- package/dist/src/review/verification.d.ts +48 -7
- package/dist/src/review/verification.d.ts.map +1 -1
- package/dist/src/review/verification.js +109 -23
- package/dist/src/review/verification.js.map +1 -1
- package/dist/src/review/verifier-record.d.ts +55 -0
- package/dist/src/review/verifier-record.d.ts.map +1 -0
- package/dist/src/review/verifier-record.js +228 -0
- package/dist/src/review/verifier-record.js.map +1 -0
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/.mcp.json +1 -1
- package/plugin/mcp.json +1 -1
- package/plugin/skills/_shared/pr-base.md +169 -0
- package/plugin/skills/pr/SKILL.md +20 -10
- package/plugin/skills/review/SKILL.md +43 -14
- package/plugin/skills/ship/SKILL.md +127 -23
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
# PR base 고르기
|
|
2
|
+
|
|
3
|
+
이 문서는 GitHub PR을 만드는 스킬(`pr`, `ship`)이 함께 쓴다. 1~3절은 사용자가 base를 직접 주지 않았을 때 적용한다. 사용자가 준 base는 후보 고르기를 건너뛰지만 0절의 이름 검사와 4절의 비교 기준은 똑같이 거친다.
|
|
4
|
+
|
|
5
|
+
## 왜 필요한가
|
|
6
|
+
|
|
7
|
+
기능 브랜치 위에 쌓은 브랜치(스택 브랜치)를 올릴 때 base를 기본 브랜치로 잡으면 PR diff에 상위 브랜치의 커밋이 전부 섞인다. 리뷰어는 남의 변경까지 읽게 되고 머지하면 상위 브랜치보다 먼저 기본 브랜치에 들어간다. 오르카에서 상위 워크트리의 브랜치를 기준으로 하위 워크트리를 만들면 이 상황이 흔하게 생긴다.
|
|
8
|
+
|
|
9
|
+
## 0. 브랜치 이름 검사
|
|
10
|
+
|
|
11
|
+
base 이름은 뒤에서 PR 생성 명령의 `--base "<base>"`, `gh pr list --head "<후보>"`, `git rev-list "origin/<base>"..HEAD`처럼 큰따옴표 안 셸 명령에 그대로 들어간다. `$(...)`나 백틱이 든 이름이면 명령 치환이 일어나고 `-`로 시작하면 옵션으로 읽힌다. 그래서 **출처와 상관없이** 아래 세 자리의 이름을 명령에 넣기 전에 검사한다.
|
|
12
|
+
|
|
13
|
+
| 이름이 온 자리 | 걸리면 |
|
|
14
|
+
| --- | --- |
|
|
15
|
+
| 사용자가 준 base나 `target` | 멈추고 "다른 이름을 준다 / 여기서 멈춘다"로 묻는다. 그대로 진행하는 선택지는 두지 않는다 |
|
|
16
|
+
| 1절 후보 뽑기의 원격 브랜치 | 후보에서 빼고 `skip <이름>`을 표준 에러에 남긴다 (1절 스니펫이 한다) |
|
|
17
|
+
| 2절 오르카 값 | 쓰지 않는다 (2절 스니펫이 한다) |
|
|
18
|
+
|
|
19
|
+
사용자가 준 이름은 따옴표로 감싸 변수에 넣지 않는다. 이름에 `'`가 있으면 그 자리에서 따옴표가 닫힌다. 따옴표 붙은 heredoc으로 받아 글자 그대로 넣는다.
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
name=$(cat <<'BRANCH_NAME_END'
|
|
23
|
+
<사용자가 준 이름>
|
|
24
|
+
BRANCH_NAME_END
|
|
25
|
+
)
|
|
26
|
+
[[ $name =~ ^[A-Za-z0-9._][A-Za-z0-9._/-]*$ ]] && echo ok || echo "bad-name"
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
- 영문, 숫자, `.`, `_`, `/`, `-`만 받고 첫 글자에는 `/`와 `-`를 받지 않는다. `feat/한글`처럼 git은 받는 이름도 여기서는 걸린다. 그런 base면 사용자가 브랜치 이름을 바꿔야 한다
|
|
30
|
+
- `git check-ref-format`은 `feat/$(id)` 같은 이름을 통과시키므로 쓰지 않는다
|
|
31
|
+
- `[[ =~ ]]`는 bash와 zsh에서 같게 돈다. 줄바꿈이 든 값도 걸린다
|
|
32
|
+
|
|
33
|
+
## 1. 후보 뽑기
|
|
34
|
+
|
|
35
|
+
원격 브랜치마다 현재 브랜치와의 merge-base를 구하고 그 merge-base 이후에 쌓인 내 커밋 수를 센다. 이 수가 가장 작은 브랜치가 내가 실제로 갈라져 나온 브랜치다.
|
|
36
|
+
|
|
37
|
+
fetch는 따로 먼저 돌린다. 한 블록에 두면 fetch가 끊길 때 후보 계산 출력도 같이 사라진다.
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
GIT_HTTP_LOW_SPEED_LIMIT=1000 GIT_HTTP_LOW_SPEED_TIME=20 git fetch --quiet origin \
|
|
41
|
+
|| echo "fetch-failed" >&2
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
default=$(gh repo view --json defaultBranchRef --jq .defaultBranchRef.name)
|
|
46
|
+
me=$(git rev-parse --abbrev-ref HEAD)
|
|
47
|
+
head=$(git rev-parse HEAD)
|
|
48
|
+
refs=$(git for-each-ref --format='%(refname)' refs/remotes/origin | grep -vc '/HEAD$')
|
|
49
|
+
[ "$refs" -le 1 ] && echo "only-default" >&2
|
|
50
|
+
[ "$refs" -gt 200 ] && echo "capped $refs" >&2
|
|
51
|
+
|
|
52
|
+
{ echo "refs/remotes/origin/$default"
|
|
53
|
+
git for-each-ref --sort=-committerdate --count=200 --format='%(refname)' refs/remotes/origin
|
|
54
|
+
} | awk '!seen[$0]++' | while read -r ref; do
|
|
55
|
+
name=${ref#refs/remotes/origin/}
|
|
56
|
+
[ "$name" = HEAD ] || [ "$name" = "$me" ] && continue
|
|
57
|
+
[[ $name =~ ^[A-Za-z0-9._][A-Za-z0-9._/-]*$ ]] || { echo "skip $name" >&2; continue; }
|
|
58
|
+
mb=$(git merge-base HEAD "$ref") || continue
|
|
59
|
+
[ "$mb" = "$head" ] && [ "$name" != "$default" ] && continue
|
|
60
|
+
echo "$(git rev-list --count "$mb"..HEAD) $name"
|
|
61
|
+
done | sort -n -k1,1 -s | head -5
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
표준 출력은 `<내 커밋 수> <브랜치>` 줄이고 가까운 순이다. 표준 에러에는 아래 "보고 줄" 절이 쓰는 표시가 나온다.
|
|
65
|
+
|
|
66
|
+
- **기본 브랜치를 맨 앞에 넣고 안정 정렬(`-s`)한다.** 수가 같으면 기본 브랜치가 앞에 남는다. 상위 브랜치가 merge commit으로 기본 브랜치에 이미 들어갔으면 두 브랜치의 수가 같아지는데 그때는 기본 브랜치가 맞다
|
|
67
|
+
- **merge-base가 HEAD인 브랜치는 뺀다.** 내 커밋을 전부 담은 브랜치라 base가 아니다. 내 위에 쌓인 하위 브랜치나 같은 커밋을 다른 이름으로 올린 브랜치가 여기 걸린다. 기본 브랜치만은 빼지 않는다. 내 커밋이 이미 기본 브랜치에 들어갔으면 기본 브랜치가 수 0으로 남는다. 부르는 스킬은 그 수를 보고 올릴 커밋이 없다고 멈춘다
|
|
68
|
+
- **0절 이름 검사에 걸린 브랜치는 후보에서 뺀다.** 뺀 이름은 `skip <이름>`으로 표준 에러에 남긴다. 여기 이름은 `read`로 받은 변수라 검사하는 중에 치환이 일어나지는 않는다
|
|
69
|
+
- **최근 커밋 순 200개만 본다.** 원격 브랜치가 수천 개인 레포에서 merge-base를 전부 구하면 오래 걸린다. 갈라져 나온 상위 브랜치는 대개 최근에 움직인 브랜치다. 원격 브랜치가 200개를 넘으면 `capped <개수>`를 남긴다. 상위 브랜치가 200개 밖에 있으면 기본 브랜치로 정해지기 때문이다
|
|
70
|
+
- **`git fetch`를 먼저 한다.** 원격 추적 브랜치가 낡으면 이미 머지된 상위 브랜치를 후보로 고른다. HTTP 원격은 20초 동안 초당 1KB도 못 받으면 끊는다. SSH 원격은 `GIT_SSH_COMMAND`로 상한을 걸면 사용자의 `core.sshCommand`를 덮어쓰므로 걸지 않고 런타임 도구의 타임아웃에 맡긴다. 거기서 끊기면 `fetch-failed`로 보고 다음으로 간다. 실패하면 `fetch-failed`를 남기고 낡은 ref로 그대로 진행한다. 원격 추적 브랜치가 기본 브랜치 하나뿐이면(단일 브랜치 클론) `only-default`를 남긴다. 둘 다 상위 브랜치를 못 보고 기본 브랜치로 정해지는 경우다
|
|
71
|
+
|
|
72
|
+
## 2. 보조 신호
|
|
73
|
+
|
|
74
|
+
후보를 정하는 건 후보 뽑기에서 센 커밋 수다. 아래 신호는 동률을 가르거나 근거를 덧붙이는 데만 쓴다.
|
|
75
|
+
|
|
76
|
+
**열린 PR.** 1등 후보가 기본 브랜치가 아니면 그 브랜치에 걸린 PR을 본다.
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
gh pr list --head "<후보>" --state all --json number,state --jq '.[] | "\(.state) #\(.number)"'
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
| 결과 | 처리 |
|
|
83
|
+
| --- | --- |
|
|
84
|
+
| `OPEN`이 있다 | 후보로 확정한다. 근거에 PR 번호를 적는다 |
|
|
85
|
+
| `MERGED`만 있다 | 상위 브랜치가 squash 머지로 이미 들어간 경우다. 기본 브랜치를 base로 잡고 내 브랜치에 상위 브랜치의 커밋이 그대로 남아 있어 diff에 섞인다고 알린다. rebase는 사용자가 정한다 |
|
|
86
|
+
| `CLOSED`만 있다 | 상위 브랜치를 버린 경우일 수 있다. 사용자에게 묻는다 |
|
|
87
|
+
| 없다 | 후보로 확정한다. 근거에 "PR 없음"을 적는다 |
|
|
88
|
+
|
|
89
|
+
**오르카 워크트리 정보.** `orca` 명령이 있을 때만 본다. 없으면 이 신호 없이 진행한다.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
cur=$(orca worktree current --json)
|
|
93
|
+
orcaBase=$(printf '%s' "$cur" | jq -r '.result.worktree.baseRef // empty')
|
|
94
|
+
orcaBase=${orcaBase#refs/remotes/origin/}; orcaBase=${orcaBase#refs/heads/}
|
|
95
|
+
parent=$(printf '%s' "$cur" | jq -r '.result.worktree.parentWorktreeId // empty')
|
|
96
|
+
orcaParent=
|
|
97
|
+
[ -n "$parent" ] && orcaParent=$(orca worktree show --worktree "id:$parent" --json \
|
|
98
|
+
| jq -r '.result.worktree.branch // empty')
|
|
99
|
+
for kv in "orcaBase=$orcaBase" "orcaParent=$orcaParent"; do
|
|
100
|
+
v=${kv#*=}
|
|
101
|
+
[ -n "$v" ] || continue
|
|
102
|
+
[[ $v =~ ^[A-Za-z0-9._][A-Za-z0-9._/-]*$ ]] && echo "$kv" || echo "skip $v" >&2
|
|
103
|
+
done
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- `orcaBase=` 줄은 이 워크트리를 만들 때 기준으로 삼은 ref에서 `refs/remotes/origin/`이나 `refs/heads/`를 뗀 이름이다
|
|
107
|
+
- `orcaParent=` 줄은 상위 워크트리가 지금 체크아웃한 브랜치다. 상위 워크트리가 기본 브랜치 체크아웃이면 기본 브랜치가 나온다
|
|
108
|
+
- 값이 비면 그 줄은 안 찍힌다. 한 줄만 나와도 앞의 이름으로 어느 쪽인지 가린다
|
|
109
|
+
- `orca`는 `--jq`를 안 받는다. `jq`로 거른다
|
|
110
|
+
- 0절 이름 검사는 이름을 뗀 값에만 건다. 걸린 값은 찍지 않고 `skip <값>`을 표준 에러에 남긴다
|
|
111
|
+
- 두 값이 같은 동률 후보를 가리키거나 한 값만 동률 후보 안에 있으면 그 브랜치로 정한다. 두 값이 서로 다른 동률 후보를 가리키면 오르카 정보로 못 가른 것으로 보고 정하는 데 쓰지 않는다. 어느 한쪽을 앞세우지 않는 건 상위 워크트리가 브랜치를 바꿨을 때 엉뚱한 후보를 말없이 고르게 되기 때문이다. 한 값만 후보 안이고 다른 값이 후보 밖의 브랜치여도 후보 안의 값으로 정한다. 후보 밖의 값은 보고 줄 근거에 함께 적는다
|
|
112
|
+
- 후보 밖의 값은 정하는 데 쓰지 않고 보고에만 적는다. 상위 브랜치가 원격에 아직 안 올라갔거나 이미 머지된 경우다
|
|
113
|
+
|
|
114
|
+
## 3. 정하기
|
|
115
|
+
|
|
116
|
+
| 상황 | 처리 |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| 1등이 기본 브랜치다 (동률 포함) | 기본 브랜치로 진행한다 |
|
|
119
|
+
| 1등이 기본 브랜치가 아닌 브랜치 하나다 | 열린 PR 표대로 처리한다. 확정되면 그 브랜치로 진행한다 |
|
|
120
|
+
| 기본 브랜치가 아닌 브랜치가 여럿 동률이다 | 열린 PR이 있는 브랜치가 하나뿐이거나 오르카 정보가 2절 규칙대로 하나로 좁혀지면 그 브랜치로 정한다. 그래도 못 가르면 후보를 보여주고 묻는다 |
|
|
121
|
+
| 후보가 하나도 없다 | merge-base를 못 구한 경우다(기본 브랜치와 이력이 안 이어짐). 사용자에게 묻는다 |
|
|
122
|
+
|
|
123
|
+
### 보고 줄
|
|
124
|
+
|
|
125
|
+
아래 가운데 하나라도 해당하면 부르는 스킬이 사전 점검 보고에 한 줄씩 싣는다. 하나도 없으면 따로 알리지 않는다.
|
|
126
|
+
|
|
127
|
+
| 언제 | 줄 |
|
|
128
|
+
| --- | --- |
|
|
129
|
+
| 기본 브랜치가 아닌 브랜치로 정했다 | `base를 feat/parent로 잡았어요 (근거: 내 커밋 2개만 앞섬, 기본 브랜치 main은 4개, 열린 PR #123)`. 오르카 정보로 갈랐으면 근거에 `orcaBase`인지 `orcaParent`인지 적는다. 다른 값이 후보 밖이었으면 그 값도 붙인다. 예: `(근거: 내 커밋 2개만 앞섬, feat/a와 동률, orcaBase=feat/a, orcaParent=feat/x는 후보 밖)` |
|
|
130
|
+
| 상위 PR이 `MERGED`라 기본 브랜치로 돌아갔다 | `상위 브랜치 feat/parent는 #123으로 이미 머지돼서 base를 main으로 잡았어요. 상위 커밋이 diff에 섞여요` |
|
|
131
|
+
| 표준 에러에 `skip <이름>`이 있다 | `base 후보에서 뺀 브랜치: <이름> (쓸 수 없는 글자가 있음). 이 브랜치가 base면 직접 알려주세요` |
|
|
132
|
+
| 표준 에러에 `fetch-failed`나 `only-default`, `capped`가 있다 | `base 자동 선택 근거가 약해요: <fetch 실패 \| 원격 브랜치가 기본 브랜치뿐 \| 원격 브랜치 N개 중 최근 200개만 봄>` |
|
|
133
|
+
|
|
134
|
+
사용자가 이 줄을 보고 다른 base를 말하면 그 값으로 바꾸고 0절 이름 검사와 4절을 다시 돈다.
|
|
135
|
+
|
|
136
|
+
## 4. 비교 기준 ref
|
|
137
|
+
|
|
138
|
+
base가 정해지면(사용자가 줬든 1~3절로 골랐든) 커밋 수와 diff를 볼 ref를 여기서 정한다. `ship` Phase 0의 커밋 0 판정과 `pr` 2단계의 diff 수집이 같은 ref를 써야 두 스킬이 같은 판정을 한다. 0절 이름 검사를 통과한 이름만 넣는다.
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
GIT_HTTP_LOW_SPEED_LIMIT=1000 GIT_HTTP_LOW_SPEED_TIME=20 git fetch --quiet origin \
|
|
142
|
+
"+refs/heads/<base>:refs/remotes/origin/<base>" || echo "fetch-failed" >&2
|
|
143
|
+
baseRef=$(git rev-parse --verify --quiet "refs/remotes/origin/<base>" >/dev/null \
|
|
144
|
+
&& echo "origin/<base>" || echo "<base>")
|
|
145
|
+
n=$(git rev-list --count "$baseRef"..HEAD) || n=error
|
|
146
|
+
echo "baseRef=$baseRef commits=$n"
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
- `origin/<base>`가 있으면 그것을 쓴다. 로컬 브랜치는 원격보다 뒤처질 수 있고 PR은 원격 브랜치와 비교된다
|
|
150
|
+
- fetch에 refspec을 적는다. 기본 브랜치만 받은 단일 브랜치 클론은 기본 refspec이 그 브랜치만 덮는다. 그래서 이름만 주면 `origin/<base>`가 안 생기고 낡은 로컬 브랜치로 떨어진다. 0절을 통과한 이름에는 `:`와 `*`가 없어 refspec이 안 깨진다
|
|
151
|
+
- `commits`가 0이면 올릴 커밋이 없다. 부르는 스킬이 멈춘다
|
|
152
|
+
- `commits=error`면 원격에도 로컬에도 그 브랜치가 없어 비교 기준을 못 구했다는 뜻이고 커밋이 없다는 뜻이 아니다. 부르는 스킬은 git 에러를 그대로 보이고 멈춰서 base를 다시 묻는다. 답을 받으면 0절 이름 검사와 이 절을 다시 돈다. 블록 안에서 `exit`하지 않는 건 셸이 호출 사이에 이어지는 런타임에서 호출한 셸까지 닫히기 때문이다
|
|
153
|
+
- 셸 상태가 호출 사이에 안 남는 런타임이 있어서 `baseRef`를 쓰는 명령은 이 블록과 같은 호출 안에 붙여 돌린다
|
|
154
|
+
- `--base`에는 `origin/` 없이 `<base>` 이름만 넘긴다
|
|
155
|
+
|
|
156
|
+
## 확인한 상황
|
|
157
|
+
|
|
158
|
+
임시 레포에서 아래 상황을 만들어 후보 뽑기 절차를 돌려봤다.
|
|
159
|
+
|
|
160
|
+
| 상황 | 1등 | 결과 |
|
|
161
|
+
| --- | --- | --- |
|
|
162
|
+
| 상위 브랜치 위에 쌓았고 상위와 기본 브랜치가 둘 다 그 뒤로 더 나감 | 상위 (2) | 기본 브랜치는 4 |
|
|
163
|
+
| 기본 브랜치에서 바로 땄다 | 기본 (1) | 나머지는 2 |
|
|
164
|
+
| 상위가 merge commit으로 기본 브랜치에 들어감 | 기본 (2) | 상위와 동률이라 기본이 앞에 남음 |
|
|
165
|
+
| 같은 커밋을 가리키는 상위 브랜치가 둘 | 상위 둘 (2) | 정하기 표의 동률 규칙으로 감 |
|
|
166
|
+
| 내 위에 하위 브랜치가 쌓여 있음 | 영향 없음 | merge-base가 HEAD라 빠짐 |
|
|
167
|
+
| 내 커밋이 이미 기본 브랜치에 fast-forward로 들어감 | 기본 (0) | 기본 브랜치는 merge-base가 HEAD여도 남아서 부르는 스킬이 커밋 없음으로 멈춤 |
|
|
168
|
+
| 원격에 `feat/$(id)` 브랜치가 있음 | 영향 없음 | `skip feat/$(id)`가 표준 에러에 남고 후보에서 빠짐. `git check-ref-format --branch`는 이 이름을 통과시킴 |
|
|
169
|
+
| 기본 브랜치만 받은 단일 브랜치 클론 | 기본 (1) | `only-default`가 표준 에러에 남음 |
|
|
@@ -15,7 +15,7 @@ inputs:
|
|
|
15
15
|
target:
|
|
16
16
|
type: string
|
|
17
17
|
required: false
|
|
18
|
-
description: "비교
|
|
18
|
+
description: "비교 기준이자 PR base 브랜치 (생략하면 _shared/pr-base.md로 고른다)"
|
|
19
19
|
repoRoot:
|
|
20
20
|
type: string
|
|
21
21
|
required: false
|
|
@@ -42,7 +42,7 @@ routing:
|
|
|
42
42
|
## 사용 방법
|
|
43
43
|
|
|
44
44
|
```
|
|
45
|
-
/pr #
|
|
45
|
+
/pr # base를 자동으로 고른다 (스택이 아니면 기본 브랜치)
|
|
46
46
|
/pr feature/auth # 특정 브랜치
|
|
47
47
|
```
|
|
48
48
|
|
|
@@ -51,7 +51,11 @@ PR을 만드는 것까지가 범위입니다. 올리기 전에 리뷰를 통과
|
|
|
51
51
|
## 전제 조건
|
|
52
52
|
|
|
53
53
|
`repoRoot`가 주어지지 않으면 현재 작업 디렉토리를 절대 경로로 사용합니다.
|
|
54
|
-
`target`이 주어지지 않으면
|
|
54
|
+
`target`이 주어지지 않으면 [`../_shared/pr-base.md`](../_shared/pr-base.md)로 고릅니다. `main`을 그대로 쓰지 않습니다. 스택 브랜치를 기본 브랜치로 올리면 상위 브랜치의 커밋이 PR에 섞이기 때문입니다. 그 문서의 "보고 줄" 절에 해당하는 줄을 1단계 인터뷰 전에 그대로 보입니다.
|
|
55
|
+
|
|
56
|
+
`target`이 주어졌으면 그 문서의 0절 이름 검사부터 겁니다. 걸리면 멈추고 묻습니다.
|
|
57
|
+
|
|
58
|
+
정한 값이 `target`입니다. 2단계 diff 수집과 5단계 `--base`가 이 값을 씁니다.
|
|
55
59
|
|
|
56
60
|
## 이 스킬은 GitHub에만 올립니다
|
|
57
61
|
|
|
@@ -182,15 +186,20 @@ PR을 작성하기 전에 세 가지를 확인합니다. 없으면 Enter / "없
|
|
|
182
186
|
|
|
183
187
|
### 2단계: diff 수집
|
|
184
188
|
|
|
185
|
-
비교 기준(`target`) 대비 변경 내용을
|
|
189
|
+
비교 기준(`target`) 대비 변경 내용을 수집합니다. 비교에 쓰는 ref(`baseRef`)는 [`../_shared/pr-base.md`](../_shared/pr-base.md) 4절 블록으로 정합니다. `ship` Phase 0이 커밋 수를 셀 때도 같은 블록을 써서 두 스킬의 판정이 갈리지 않습니다. 셸 상태가 호출 사이에 안 남을 수 있으니 아래 명령을 그 블록 뒤에 붙여 한 번에 돌립니다.
|
|
186
190
|
|
|
187
191
|
```bash
|
|
188
|
-
|
|
189
|
-
git
|
|
190
|
-
git diff
|
|
192
|
+
# pr-base.md 4절 블록 (<base> 자리에 {target})
|
|
193
|
+
git log --oneline "$baseRef"..HEAD # 커밋 목록
|
|
194
|
+
git diff "$baseRef"...HEAD --stat # 변경 파일 통계
|
|
195
|
+
git diff "$baseRef"...HEAD # 실제 diff (핵심 변경만)
|
|
191
196
|
```
|
|
192
197
|
|
|
193
|
-
`
|
|
198
|
+
**블록이 `commits=error`를 찍었으면 아래 출력은 쓰지 않고 멈춰서 base를 다시 묻습니다.** 비교 기준 ref를 못 구했으니 커밋 목록과 diff가 비거나 에러로 나옵니다.
|
|
199
|
+
|
|
200
|
+
**커밋 목록이 비면 멈춥니다.** 올릴 변경이 없습니다. 내 커밋이 이미 `target`에 들어간 경우가 여기 걸립니다.
|
|
201
|
+
|
|
202
|
+
`changedFiles`와 커밋 목록을 수집합니다. 5단계 `--base`에는 `origin/`을 붙이지 않고 `{target}` 이름만 넘깁니다.
|
|
194
203
|
|
|
195
204
|
### 3단계: change-context-writer로 변경 분석
|
|
196
205
|
|
|
@@ -211,7 +220,7 @@ Agent {
|
|
|
211
220
|
|
|
212
221
|
시스템 프롬프트의 관점으로 아래 변경을 분석해 변경 컨텍스트 문서를 작성한다.
|
|
213
222
|
|
|
214
|
-
비교 기준: <
|
|
223
|
+
비교 기준: <2단계에서 쓴 baseRef>
|
|
215
224
|
변경 파일: <2단계 목록>
|
|
216
225
|
커밋 목록: <2단계 git log 결과>
|
|
217
226
|
작성 의도: <prIntent.purpose>
|
|
@@ -336,12 +345,13 @@ gestalt humanize-scan --file "$scanTmp/description.md" --register report
|
|
|
336
345
|
heredoc 패턴으로 실행합니다. **PR 작성자 자신을 어사인**하기 위해 `--assignee @me`를 항상 포함합니다. 명령 앞에 `GESTALT_PR=1` 표식을 붙입니다 (raw `gh pr create`를 가로채는 PreToolUse 훅이 이 스킬의 호출은 통과시키도록 하는 우회 표식):
|
|
337
346
|
|
|
338
347
|
```bash
|
|
339
|
-
GESTALT_PR=1 gh pr create --assignee @me --title "..." --body "$(cat <<'EOF'
|
|
348
|
+
GESTALT_PR=1 gh pr create --base "{target}" --assignee @me --title "..." --body "$(cat <<'EOF'
|
|
340
349
|
{description 내용}
|
|
341
350
|
EOF
|
|
342
351
|
)"
|
|
343
352
|
```
|
|
344
353
|
|
|
354
|
+
- `--base`를 반드시 붙입니다. 안 붙이면 `gh`가 기본 브랜치나 upstream으로 추론해서 스택 브랜치의 base가 엉뚱하게 잡힙니다.
|
|
345
355
|
- `GESTALT_PR=1`은 환경변수 형태의 표식일 뿐 동작에 영향을 주지 않습니다. 이미 gestalt:pr 플로우 안이므로 PR 생성 확인 프롬프트가 중복되지 않게 해줍니다.
|
|
346
356
|
- `@me`는 `gh`에 인증된 현재 사용자를 가리키므로, PR이 생성되면 작성자 본인이 자동으로 assignee로 지정됩니다.
|
|
347
357
|
- 어사인이 실패해도(권한·레포 설정 등) PR 생성 자체는 막지 않습니다. 실패 시 PR 생성 후 `gh pr edit {prUrl} --add-assignee @me`로 재시도합니다.
|
|
@@ -183,7 +183,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
|
|
|
183
183
|
| --- | --- |
|
|
184
184
|
| "사용자가 0단계에서 맥락을 다 줬으니 PR 본문은 안 읽어도 된다" | 1.15단계는 그래도 건너뛰지 않습니다. 사용자 말은 이번에 봐달라는 것이고 PR 본문은 작성자가 하려던 것입니다. 둘이 어긋나는 자리가 제일 볼 만한 자리인데 한쪽만 실으면 안 보입니다. |
|
|
185
185
|
| "리뷰어 여럿이 같은 자리를 짚었으니 제안 검증은 넘어가자" | 같은 의견이 여럿이어도 반영 결과가 맞다는 보증은 아닙니다. 라운드를 늘린 사례 87개 가운데 35개가 리뷰어 제안을 그대로 반영한 자리에서 생긴 새 문제였습니다. 3.7단계를 건너뛰는 건 이슈 초안이 하나도 없을 때와 서브에이전트가 실패했을 때뿐입니다. |
|
|
186
|
-
| "severity 낮은 이슈까지 검증할 필요는 없다" | 3.7단계는 severity와 상관없이 모든 이슈를 봅니다. 낮은 이슈의 제안도 반영하면 코드가 바뀝니다. |
|
|
186
|
+
| "severity 낮은 이슈까지 검증할 필요는 없다" | 3.7단계는 severity와 상관없이 모든 이슈를 봅니다. 낮은 이슈의 제안도 반영하면 코드가 바뀝니다. 로컬 PR은 `review_verify` 기록이 없으면 엔진이 게시를 거절합니다. |
|
|
187
187
|
| "방금 리뷰했으니 consensus는 아직 유효하다" | 유효한지는 `git rev-parse HEAD`와 PR head를 비교하면 알 수 있습니다. 그사이 커밋이 하나라도 붙었으면 stale이라 1단계부터 다시 돕니다. |
|
|
188
188
|
| "코멘트를 모아 한 번에 스캔하면 빠르다" | 한 덩어리로 스캔하면 코멘트 하나를 통째로 `>` 인용으로 감싸도 다른 코멘트 산문에 묻혀 안 걸립니다. 4.7단계 어투 검사는 코멘트마다 파일 하나로 떨궈 돌립니다. |
|
|
189
189
|
| "사용자가 리뷰를 맡겼으니 게시 확인은 생략한다" | 4.7단계 게시 확인은 코멘트를 다 쓴 뒤 한 번 묻는 자리입니다. 동의를 못 받으면 코멘트는 버리고 리포트만 보여줍니다. |
|
|
@@ -786,7 +786,7 @@ Agent {
|
|
|
786
786
|
|
|
787
787
|
**리뷰어는 `gestalt:<agent-name>` 타입으로 띄웁니다.** 플러그인 `agents/`에 리뷰어마다 Claude Code 에이전트가 있어서 AGENT.md 본문이 시스템 프롬프트로 바로 실립니다. 서브에이전트가 `ges_agent`를 따로 부르지 않고 도구도 읽기와 셸로 좁혀져 있어 첫 요청이 가볍습니다. 룰북 경로는 본문 안에 플러그인 설치 경로 기준 절대 경로로 들어 있어서 리뷰 대상 레포에서 돌아도 풀립니다. 그래도 본문만 보고 룰북을 안 연 채 판정하는 일이 있어서 룰북 읽기를 프롬프트 1번에 둡니다.
|
|
788
788
|
|
|
789
|
-
`gestalt:<agent-name>` 타입을 못 찾는다는 오류가 나면 이 에이전트가 없는 플러그인 버전이거나 Claude Code 에이전트를 못 읽는 런타임입니다. 그때는 [`agent-delegation.md`](../_shared/agent-delegation.md)의 폴백대로 `Explore`로 띄우고 프롬프트 1번 앞에 `ges_agent get`으로 시스템 프롬프트를 가져오는 줄을 넣습니다. 이 스킬의 다른 `gestalt:` 호출도 같습니다.
|
|
789
|
+
`gestalt:<agent-name>` 타입을 못 찾는다는 오류가 나면 이 에이전트가 없는 플러그인 버전이거나 Claude Code 에이전트를 못 읽는 런타임입니다. 그때는 [`agent-delegation.md`](../_shared/agent-delegation.md)의 폴백대로 `Explore`로 띄우고 프롬프트 1번 앞에 `ges_agent get`으로 시스템 프롬프트를 가져오는 줄을 넣습니다. **그 문서의 `<룰북 디렉토리>` 줄도 함께 넣고 메인이 절대 경로로 풀어 채웁니다.** `ges_agent get`이 주는 본문은 룰북을 `../` 상대 경로로 가리킵니다. 이 줄이 없으면 서브에이전트가 리뷰 대상 레포를 기준으로 경로를 풀다가 `author-voice.md` 같은 룰북을 못 찾습니다. 못 풀었으면 `없음`을 넣어 서브에이전트가 그 사실을 보고하게 합니다. 이 스킬의 다른 `gestalt:` 호출도 같습니다.
|
|
790
790
|
|
|
791
791
|
각 서브에이전트가 돌려준 JSON을 받아, 메인 세션에서 에이전트마다 한 번씩 `review_submit`을 호출합니다 (**2단계에서 정한 순서대로** 최소 4회 — `focusAreas`가 있으면 그 전문가가 먼저입니다). 세션 상태는 메인 세션 한 곳에서만 굴립니다:
|
|
792
792
|
|
|
@@ -911,7 +911,7 @@ continuityVerdict = {
|
|
|
911
911
|
|
|
912
912
|
#### 입력 준비 (메인)
|
|
913
913
|
|
|
914
|
-
1. **초안을 만듭니다.** `review_submit`으로 넣은 리뷰어 결과를 모아 중복을 합칩니다. 여러 리뷰어가 같은 자리를 같은 이유로 짚었으면 하나로 두고 `reportedBy`는 대표 하나만 적습니다.
|
|
914
|
+
1. **초안을 만듭니다.** `review_submit`으로 넣은 리뷰어 결과를 모아 중복을 합칩니다. 여러 리뷰어가 같은 자리를 같은 이유로 짚었으면 하나로 두고 `reportedBy`는 대표 하나만 적습니다. 다만 critical이거나 security category인 원본은 다른 이슈로 합치지 않고 각자 원본의 id로 남깁니다. 합치는 건 그보다 낮은 원본뿐입니다. critical이거나 security인 원본의 id가 여러 리뷰어에게 겹치면 합치지 말고 `<reportedBy>:<원래 id>` 꼴로 바꿔 각자 잇습니다. 같은 id 하나에 원본 여럿을 이으면 엔진은 막지 않으니 이 구분은 메인이 지킵니다.
|
|
915
915
|
|
|
916
916
|
3.5단계 `continuityVerdict.driftFindings` 가운데 message가 `경로 따라가기:`로 시작하는 항목도 하나씩 이슈로 더합니다. 경로 따라가기로 나온 막힘은 작성자가 고쳐야 풀리는데 `driftFindings`는 인라인 코멘트로 안 나갑니다. 이슈로 올려야 검증기가 그 전제를 다시 확인하고 4.7단계 코멘트와 로컬 PR 게시, `review_fix`까지 기존 경로로 흐릅니다.
|
|
917
917
|
|
|
@@ -949,7 +949,7 @@ continuityVerdict = {
|
|
|
949
949
|
|
|
950
950
|
`file`과 `line`은 이 PR diff의 첫 변경 파일에서 처음 추가된 줄입니다. 셋 다 특정 줄의 결함이 아니라 PR 전체를 두고 하는 말이라 자리를 하나로 정해 둡니다. 추가된 줄이 없는 PR이면 이슈로 올리지 않고 [결과 표시](#결과-표시)의 후속 작업 절에만 남깁니다.
|
|
951
951
|
|
|
952
|
-
`harness-reviewer`가 같은 참조를 `harness:forwardRef`나 `harness:backwardRef`로 올렸으면 하나로 합치고 severity는 세 상태 판정 쪽을 씁니다. 세 상태 판정은 세 커밋을 git으로 실제로 맞춰 본 결과입니다. `blocked` 판정은 이슈로 올리지 않습니다.
|
|
952
|
+
`harness-reviewer`가 같은 참조를 `harness:forwardRef`나 `harness:backwardRef`로 올렸으면 하나로 합치고 severity는 세 상태 판정 쪽을 씁니다. 다만 `harness-reviewer` 원본이 critical이면 합치지 않고 그 원본의 id로 따로 둡니다. 세 상태 판정은 세 커밋을 git으로 실제로 맞춰 본 결과입니다. `blocked` 판정은 이슈로 올리지 않습니다.
|
|
953
953
|
|
|
954
954
|
`relatedRemovesUsed`의 연관 PR 쪽 알림은 4.7단계 게시 확인에서 이번 PR 게시와 함께 동의를 받은 뒤 한 번 올립니다. 관련 레포 목록 안의 PR일 때만 올리고 본문은 파일 쓰기 도구로 만든 파일로 넘깁니다.
|
|
955
955
|
|
|
@@ -1040,6 +1040,25 @@ Agent {
|
|
|
1040
1040
|
}
|
|
1041
1041
|
```
|
|
1042
1042
|
|
|
1043
|
+
#### 검증 기록 (review_verify)
|
|
1044
|
+
|
|
1045
|
+
**검증기가 돌려준 응답을 고치지 않고 그대로 `review_verify`로 넘깁니다.** 서브에이전트가 실패해 에러 문구만 돌아왔어도 그 글을 그대로 넘깁니다. 엔진이 응답에서 판정을 직접 읽어 세션에 남깁니다. 판정을 하나도 못 읽으면 서브에이전트 실패로 기록합니다. 로컬 PR 게시(`review_publish`)는 이 기록이 없으면 거절합니다. 실패인지 아닌지는 엔진이 응답으로 가르므로 메인이 사유를 적어 보낼 칸은 없습니다.
|
|
1046
|
+
|
|
1047
|
+
**응답이 비어 돌아오면 `review_verify`가 기록하지 않고 거부합니다.** 글자나 숫자가 하나도 없는 응답도 빈 것으로 봅니다. 공백, 빈 코드펜스(언어 표시가 붙은 것도 포함), `[]`, `{}`가 해당합니다. 검증기를 한 번 다시 띄워 그 응답을 넘깁니다. 두 번째도 비었으면 넘기기 전에 사용자에게 검증기가 두 번 빈 응답을 돌려줬다고 먼저 알리고 `suggestion-verifier가 두 번 빈 응답을 돌려줬다`를 그대로 넘깁니다. 판정이 없는 글이라 실패로 기록되고 아래 결과 반영의 마지막 줄대로 갑니다.
|
|
1048
|
+
|
|
1049
|
+
```
|
|
1050
|
+
ges_execute {
|
|
1051
|
+
action: "review_verify",
|
|
1052
|
+
reviewSessionId: "<reviewSessionId>",
|
|
1053
|
+
verifierOutput: "<검증기 응답 그대로>"
|
|
1054
|
+
}
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
- **`review_submit`을 다 넣은 뒤에 부릅니다.** 기록 뒤에 `review_submit`이 더 들어오면 엔진은 그 기록을 낡은 것으로 보고 게시를 거절합니다. 검증기가 못 본 초안이 생긴 것이라서요.
|
|
1058
|
+
- **`review_consensus`보다 먼저 부릅니다.** 합의 뒤에 기록하면 그 합의에는 검증이 반영되지 않았으므로 `review_consensus`를 다시 불러야 게시됩니다.
|
|
1059
|
+
- **이슈 초안이 하나도 없어 검증기를 안 띄웠으면 이것도 부르지 않습니다.** `review_submit`에 낸 이슈도 합의 이슈도 없으면 엔진이 그걸 보고 기록 없이 게시합니다. 합의에서 이슈를 다 빼고 보내도 `review_submit`에 이슈가 있었으면 기록을 요구합니다.
|
|
1060
|
+
- 응답의 `verifier`가 `failed`이면 아래 결과 반영의 마지막 줄대로 초안 그대로 넘깁니다. 이때 `droppedIssues`나 `verification`을 붙이면 `review_consensus`가 거부합니다.
|
|
1061
|
+
|
|
1043
1062
|
#### 결과 반영 (메인)
|
|
1044
1063
|
|
|
1045
1064
|
`verifications`를 `issueId`로 초안에 되짚어 고칩니다.
|
|
@@ -1058,7 +1077,8 @@ Agent {
|
|
|
1058
1077
|
- **severity와 message는 바꾸지 않습니다.** 검증기가 다르게 돌려줘도 초안 값을 그대로 둡니다.
|
|
1059
1078
|
- **경로 따라가기에서 올라온 이슈(`continuity-judge:path-walk-<순번>`)를 drop으로 옮겼으면 짝이 되는 `driftFindings` 항목도 `continuityVerdict`에서 뺍니다.** 뺀 뒤 `driftFindings`가 비면 `coherent`를 true로 둡니다. 다른 항목이 남아 있으면 그대로 false입니다. 검증기가 전제가 틀렸다고 증거를 댄 막힘인데 정합 심급만 PR을 계속 막으면 그게 헛 Block입니다. `escalate`는 건드리지 않습니다.
|
|
1060
1079
|
- **응답에서 빠진 이슈는 `verification` 없이 둡니다.** 손으로 채우지 않습니다. 그중 critical, high는 리포트가 "검증을 거치지 않은 critical/high 이슈 N개"로 드러냅니다.
|
|
1061
|
-
- **서브에이전트가 실패하거나 JSON을 못 읽으면
|
|
1080
|
+
- **서브에이전트가 실패하거나 JSON을 못 읽으면 그 응답을 `review_verify`로 넘긴 뒤 결과 반영을 건너뜁니다.** 초안 그대로 4단계로 넘기고 사용자에게 한 줄 알립니다: "제안 검증을 못 돌려서 리뷰어 제안을 그대로 올려요." 로컬 PR에 게시하면 PR 판정에도 같은 사실이 남습니다.
|
|
1081
|
+
- **엔진은 뺀 이슈를 검증 기록과도 맞춰 봅니다.** `droppedIssues`에 넣은 id는 검증기 응답에서 `drop`이고 `evidence`가 있어야 합니다. 검증기가 keep이나 revise로 낸 이슈를 빼거나 응답에 없는 이슈를 빼면 `review_consensus`가 거부합니다. 응답의 `issueId`가 이번 이슈 id와 하나도 안 맞아도 거부합니다.
|
|
1062
1082
|
|
|
1063
1083
|
### 4단계: 합의 및 판정 (review_consensus)
|
|
1064
1084
|
|
|
@@ -1078,7 +1098,7 @@ ges_execute {
|
|
|
1078
1098
|
line: 12, // 선택 — 파일 전반이면 생략
|
|
1079
1099
|
message: "...",
|
|
1080
1100
|
suggestion: "...", // revise면 고친 제안
|
|
1081
|
-
reportedBy: "quality-reviewer", //
|
|
1101
|
+
reportedBy: "quality-reviewer", // 문자열 하나. 생략하거나 글자와 숫자가 없으면 엔진이 원본에서 채운다
|
|
1082
1102
|
verification: { // 선택 — 3.7단계를 거친 이슈만
|
|
1083
1103
|
verdict: "keep" | "revise",
|
|
1084
1104
|
reason: "...",
|
|
@@ -1088,27 +1108,33 @@ ges_execute {
|
|
|
1088
1108
|
}
|
|
1089
1109
|
],
|
|
1090
1110
|
droppedIssues: [ // 선택 — 3.7단계에서 drop된 이슈
|
|
1091
|
-
{ ...
|
|
1111
|
+
{ id: "...", dropReason: "...", dropEvidence: "..." } // 나머지 칸은 원본에서 채운다
|
|
1092
1112
|
],
|
|
1093
|
-
|
|
1094
|
-
blockedBy: [...],
|
|
1095
|
-
summary: "...",
|
|
1096
|
-
overallApproved: true | false
|
|
1113
|
+
summary: "..." // 생략하거나 글자와 숫자가 없으면 엔진이 이슈 수로 채운다
|
|
1097
1114
|
},
|
|
1098
1115
|
continuityVerdict: { ...3.5단계 산출물... }
|
|
1099
1116
|
}
|
|
1100
1117
|
```
|
|
1101
1118
|
|
|
1102
|
-
`
|
|
1119
|
+
**`reviewConsensus`에 꼭 적을 칸은 `mergedIssues` 하나입니다.** 이슈가 없으면 `[]`를 넣습니다. 비워 보내면 판정이 통과로 바뀔 수 있어서 엔진이 채우지 않고 거부합니다. `review_submit`에 critical이나 security 이슈가 있었다면 `[]`도 거부합니다. 그런 원본은 같은 id나 `<reportedBy>:<원래 id>` 꼴로 `mergedIssues`에 두고 severity는 원본 이상으로, critical이거나 security category인 채로 잇습니다. 낮춰 보내면 낮아졌다고 짚어 거부하고 3.7단계 revise 판정도 예외가 아닙니다. 여러 원본을 기존 id든 새 id든 하나로 합칠 때도 critical이나 security 원본은 다른 이슈에 접지 않고 각자 id로 따로 둡니다. 이 검사는 제안 검증 기록과 상관없이 돕니다. 같은 id 하나에 원본 여럿을 이으면 엔진은 막지 않으니 `<reportedBy>:<원래 id>` 꼴로 바꿔 각자 잇는 건 메인이 지킵니다. `mergedIssues`와 `droppedIssues`는 `reviewConsensus` 객체 안에 넣습니다. 최상위에 두면 스키마가 버리고 `reviewConsensus is required`로 거부됩니다.
|
|
1120
|
+
|
|
1121
|
+
- `mergedIssues` 항목은 `id`, `severity`, `category`, `file`, `message`, `suggestion`이 필수이고 `line`과 `verification`은 선택입니다.
|
|
1122
|
+
- **`reportedBy`는 에이전트 이름 문자열 하나**입니다. 여러 에이전트가 같은 걸 짚었으면 대표 하나만 적습니다. 생략하거나 글자와 숫자가 없으면 엔진이 같은 id나 `<reportedBy>:<원래 id>` 꼴의 `review_submit` 원본에서 채웁니다. 배열로 오면 첫 값만 씁니다. 원본이 없는 id(예: `continuity-judge:path-walk-1`)는 채울 데가 없어 그 id를 짚어 거부하니 직접 적습니다. 같은 id의 원본이 여러 리뷰어에게 있어도 엔진이 어느 쪽인지 고르지 않고 거부합니다. 3.7단계 2번대로 id를 `<reportedBy>:<원래 id>` 꼴로 바꿨으면 이 경우가 안 생깁니다. 안 바꿨으면 `reportedBy`를 직접 적습니다.
|
|
1123
|
+
- `droppedIssues` 항목은 `id`, `dropReason`, `dropEvidence`만 필수입니다. `severity`, `category`, `file`, `message`, `suggestion`은 생략하면 원본에서 채웁니다. `reportedBy`는 생략했거나 글자와 숫자가 없어도 채웁니다. 원본이 없는 id인데 칸이 비었으면 거부합니다. 같은 id의 원본이 여러 리뷰어에게 있으면 `reportedBy`를 적어야 그 리뷰어의 원본으로 채웁니다.
|
|
1124
|
+
- `verification.alsoCheck`는 배열입니다. 문자열 하나로 보내면 한 칸짜리 배열로 받습니다.
|
|
1125
|
+
- `approvedBy`와 `blockedBy`는 키를 생략했을 때만 엔진이 `review_submit`의 `approved`로 채웁니다. `[]`를 적으면 승인했거나 막은 에이전트가 없다는 뜻으로 그대로 씁니다. 같은 리뷰어가 두 번 냈으면 마지막 판정을 씁니다. 마지막 판정은 이 두 칸에만 해당하고 위의 critical이나 security 원본 대조는 재제출까지 전부 봅니다. `summary`는 생략했거나 글자와 숫자가 없으면 이슈 수로 한 줄을 채우지만 리포트와 로컬 PR 판정 본문에 그대로 실리니 직접 적는 쪽이 낫습니다.
|
|
1126
|
+
- `overallApproved`는 보내도 쓰지 않습니다. 판정은 엔진이 `mergedIssues`와 `continuityVerdict`로 냅니다.
|
|
1127
|
+
- `continuityVerdict`는 `coherent`, `driftFindings`, `escalate`, `summary`가 필수이고 `driftFindings` 항목마다 `axis`와 `message`가 필수입니다. 3.5단계 서브에이전트가 `coherent` 대신 `verdict: "pass" | "block"`을, `message` 대신 `summary`를 돌려줬으면 엔진이 제자리로 옮깁니다. `axis`는 셋 중 무엇인지 고를 근거가 없어 채우지 않으니 빠졌으면 메인이 `goal`, `consistency`, `drift` 중 하나를 적어 넘깁니다.
|
|
1103
1128
|
|
|
1104
1129
|
**`verification`과 `droppedIssues`는 생략할 수 있습니다.** 3.7단계를 건너뛰었으면 둘 다 빼고 부릅니다. 기존 동작 그대로입니다. 엔진은 `droppedIssues`를 아래 경우에 `{ error }`로 거부합니다.
|
|
1105
1130
|
|
|
1106
1131
|
- security category이거나 critical인 이슈가 들어 있다
|
|
1107
|
-
- 병합하면서 severity나 category, `reportedBy`를 바꿨어도 `review_submit` 원본이 security나 critical이다. 같은 id의 원본이 여럿이면 하나라도 걸리면
|
|
1132
|
+
- 병합하면서 severity나 category, `reportedBy`를 바꿨어도 `review_submit` 원본이 security나 critical이다. 같은 id의 원본이 여럿이면 하나라도 걸리면 거부한다. 칸을 비워 보내도 원본으로 판단한다
|
|
1108
1133
|
- `dropReason`이나 `dropEvidence`에 글자나 숫자가 하나도 없다 (공백이나 제로폭 문자만 있어도 비었다고 본다)
|
|
1109
1134
|
- 같은 id가 `mergedIssues`에도 있거나 `droppedIssues` 안에 두 번 나온다
|
|
1135
|
+
- 칸이 비었는데 같은 id의 `review_submit` 원본이 없어 채울 수 없다
|
|
1110
1136
|
|
|
1111
|
-
거부되면
|
|
1137
|
+
거부되면 뺄 수 없는 이슈는 `mergedIssues`로 되돌려 keep이나 revise로 붙입니다. 채울 수 없다고 한 칸은 직접 적어 다시 부릅니다. 거부된 호출은 세션에 아무것도 남기지 않으므로 앞서 받은 합의도 그대로입니다.
|
|
1112
1138
|
|
|
1113
1139
|
엔진이 두 심급을 합쳐 판정합니다 — **결함(critical/high)이 없고 `coherent: true`여야 통과**입니다. `continuityVerdict`를 생략하면 결함 심급만으로 판정하는 기존 동작 그대로입니다.
|
|
1114
1140
|
|
|
@@ -1411,6 +1437,7 @@ fi
|
|
|
1411
1437
|
- **11** — 어투는 깨끗하고 맞춤법만 걸렸습니다. 그것만 고칩니다.
|
|
1412
1438
|
- **12** — **검사할 산문이 없습니다.** 코멘트가 인용줄뿐이라 검사가 안 된 것이지 통과가 아닙니다. 자기 문장을 인용 밖에 두고 다시 쓰게 합니다.
|
|
1413
1439
|
- 재작성은 **한 번만** 돕니다. 두 번째도 걸리면 무엇이 남았는지 사용자에게 알리고 게시할지 묻습니다.
|
|
1440
|
+
- **스캔과 게시를 한 셸 호출에 묶지 않습니다.** 스캔 출력의 EXIT 줄을 읽고 판단한 뒤 다음 호출에서 게시합니다. `humanize-scan ... && gh api .../reviews`나 세미콜론으로 이어 붙이면 걸린 코멘트가 있어도 게시가 먼저 끝납니다. PR에 올라간 코멘트는 다시 쓸 수 없습니다.
|
|
1414
1441
|
- 여러 파일이면 파일별 EXIT가 모두 10이나 11일 때 게시합니다. 표지가 없는 단일 파일 직접 호출은 그 프로세스의 종료 코드를 씁니다. 게시가 끝나면 `rm -rf "$scanTmp"`로 그 실행 칸만 치웁니다 — 상위 `gestalt-review`나 PR 칸을 통째로 지우면 다른 세션이 쓰는 중인 파일까지 날아갑니다.
|
|
1415
1442
|
|
|
1416
1443
|
**코멘트 여러 개를 파일 하나에 모아 담으면 이 검사가 무력화됩니다.** 인용 판정이 그 파일 전체를 한 덩어리로 보기 때문에 코멘트 하나를 통째로 인용으로 감싸도 같은 파일 안 다른 코멘트의 산문에 묻혀 안 걸립니다. 파일 하나에 코멘트 하나가 이 검사가 서는 조건입니다.
|
|
@@ -1514,6 +1541,8 @@ ges_execute {
|
|
|
1514
1541
|
- **두 번 불러도 코멘트가 안 늘어납니다.** 같은 합의를 다시 옮기면 `alreadyPublished: true`로 아무것도 쓰지 않습니다. PR은 이벤트 소싱이라 한 번 붙은 코멘트를 지울 수 없습니다.
|
|
1515
1542
|
- **중간에 끊기면 그 다음부터 잇습니다.** 코멘트마다 자국을 PR에 남기므로 세션이 사라진 뒤에 다시 불러도 쓴 것을 다시 쓰지 않습니다.
|
|
1516
1543
|
|
|
1544
|
+
**3.7단계 기록이 없으면 이 액션이 거절합니다.** 코멘트를 한 건도 쓰지 않고 에러로 돌아옵니다. 에러가 말하는 대로 검증기를 돌려 `review_verify`로 넘기고 `review_consensus`를 다시 부른 뒤 게시합니다. `review_submit`에 낸 이슈도 합의 이슈도 없으면 기록 없이 게시합니다. 응답의 `verifier`는 `ran`, `failed`, `no_drafts` 가운데 하나입니다. `ran`이면 `verifierCoverage`에 합의 이슈 가운데 검증기 판정이 있는 수와 전체 수가 옵니다. 판정이 없는 이슈가 있으면 PR 판정 본문에 `이슈 M개 중 N개만 제안 검증 판정이 있다`가 붙습니다.
|
|
1545
|
+
|
|
1517
1546
|
응답의 `commentCount`와 `resumedFrom`, `prStatus`, `round`를 사용자에게 그대로 보여줍니다. `alreadyPublished`가 붙어 오면 이미 올라가 있다는 뜻이니 다시 부르지 않습니다.
|
|
1518
1547
|
|
|
1519
1548
|
라인 매핑이 불확실한 이슈는 `line`을 비워 파일 전반 코멘트가 됩니다 (`side` 개념은 로컬 PR에 없습니다). 이 액션은 `code-review-writer`를 거치지 않고 합의 이슈를 그대로 옮깁니다 — 어투를 맞춘 코멘트가 필요하면 4.5단계에서 다듬은 내용이 이미 `mergedIssues`에 들어 있어야 합니다.
|