@tienne/gestalt 0.81.0 → 0.81.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.81.0",
3
+ "version": "0.81.1",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "repository": {
6
6
  "type": "git",
@@ -56,7 +56,7 @@ git diff --word-diff=porcelain -- <파일>
56
56
  **정적 탐지를 먼저 돌립니다.** 정규식으로 잡히는 룰은 사람이 눈으로 세는 것보다 정확합니다.
57
57
 
58
58
  ```bash
59
- pnpm tsx bin/gestalt.ts humanize-check --before <파일> --after <파일> --register doc --json
59
+ gestalt humanize-check --before <파일> --after <파일> --register doc --json
60
60
  ```
61
61
 
62
62
  같은 파일을 `--before`와 `--after`에 둘 다 넘기면 윤문 전후 비교가 아니라 그 파일에 남아 있는 S1 패턴을 세어 줍니다. 잡히는 룰은 21개뿐이니 나머지는 읽고 판정합니다.
@@ -0,0 +1,45 @@
1
+ # gestalt CLI를 어떻게 부르는지 먼저 판정한다
2
+
3
+ 이 문서는 여러 스킬이 공유하는 규칙이다. `gestalt` CLI(`humanize-scan`, `pr`, `review-loop`, `setup` 등)를 셸로 부르는 단계에서 적용한다.
4
+
5
+ ## 왜 필요한가
6
+
7
+ 플러그인으로 설치되면 호스트가 복사해 가는 건 `plugin/` 아래뿐이다. `bin/`도 `package.json`도 `node_modules`도 그 안에 없다. 그래서 스킬 본문에 `pnpm tsx bin/gestalt.ts ...`만 적혀 있으면 게슈탈트 레포 밖에서는 그 명령이 원리상 안 돈다.
8
+
9
+ 그때 읽는 쪽이 하는 말이 "의존성이 안 깔려 있어서 못 돌립니다"다. 실제로는 전역 `gestalt`가 깔려 있어 돌릴 수 있는데도 스킬이 적어준 형태 하나가 막혀서 포기한다. 어투 검사처럼 **필수 검사인 단계가 이렇게 조용히 건너뛰어지면** 막으려던 문장이 그대로 나간다.
10
+
11
+ 그래서 호출 형태를 스킬마다 박아두지 않고 한 번 판정해서 쓴다.
12
+
13
+ ## 판정
14
+
15
+ CLI를 처음 부르기 전에 한 번 돌린다.
16
+
17
+ ```bash
18
+ if gestalt --version >/dev/null 2>&1; then
19
+ echo "OK gestalt"
20
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
21
+ echo "OK pnpm"
22
+ else
23
+ echo "MISSING"
24
+ fi
25
+ ```
26
+
27
+ - **`OK gestalt`** — 스킬 본문의 예시를 그대로 쓴다. 본문은 전부 `gestalt ...` 형태로 적혀 있다.
28
+ - **`OK pnpm`** — 게슈탈트 레포 안이라는 뜻이다. 그 뒤로 모든 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 쓴다.
29
+ - **`MISSING`** — 아래 "없을 때"를 따른다.
30
+
31
+ 한 번 확인하고 그 뒤로는 같은 형태를 쓴다. 단계마다 다시 찌르지 않는다.
32
+
33
+ **쓰려는 하위 명령이 최근에 생긴 것이면 그 하위 명령으로 찌른다.** `--version`은 설치본이 뒤처져 하위 명령만 없는 경우를 못 잡는다. 그 상태로 라운드를 돌다 `unknown option`으로 죽으면 앞선 단계가 통째로 헛돈다.
34
+
35
+ **명령을 변수에 담아 함수로 넘기지 않는다.** zsh는 인용 안 한 변수 전개에서 단어 분리를 안 해 `$cmd`가 통째로 명령 이름이 된다. 실제로 깔려 있어도 `MISSING`이 나온다. 같은 코드가 bash에서는 도는 탓에 눈으로는 안 걸린다.
36
+
37
+ ## 없을 때
38
+
39
+ `MISSING`이면 **그 단계가 필수 검사인지로 갈린다.**
40
+
41
+ - **필수 검사면 멈춘다.** 어투 검사(`humanize-scan`)가 그렇다. 검사를 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같다. 무엇을 못 돌렸는지와 `npm i -g @tienne/gestalt`를 함께 알리고 사용자가 그대로 진행할지 정하게 둔다.
42
+ - **판정에 쓰는 수를 그 명령이 내면 멈춘다.** `review-loop`처럼 모든 단계가 한 명령에 매달린 자리다.
43
+ - **그 밖에는 알리고 건너뛴다.** 없다고 스킬 전체를 막을 이유는 없다. 대신 완료 보고에 어느 단계를 안 돌렸는지 적는다.
44
+
45
+ 어느 쪽이든 **직접 흉내내지 않는다.** 스캔 결과를 지어내거나 룰북을 눈으로 훑어 "깨끗합니다"로 답하지 않는다 ([`tool-availability.md`](./tool-availability.md)와 같은 규칙이다).
@@ -56,7 +56,7 @@ outputs:
56
56
 
57
57
  git 저장소이기만 하면 된다. 인증도 원격도 안 본다.
58
58
 
59
- 명령은 `gestalt pr ...`이다. 게슈탈트 레포 안에서 돌 때는 전역 설치가 없을 수 있으므로 `pnpm tsx bin/gestalt.ts pr ...`로 부른다. 한 번 확인하고 그 뒤로는 같은 형태를 쓴다.
59
+ 명령은 `gestalt pr ...`이다. 부르는 형태는 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 따른다 — 게슈탈트 레포 안에서는 전역 설치가 없을 수 있어 형태가 갈린다. 한 번 확인하고 그 뒤로는 같은 형태를 쓴다.
60
60
 
61
61
  `--json`을 붙이면 객체만 나온다. 에이전트가 값을 읽어야 하는 자리에서는 이쪽을 쓴다.
62
62
 
@@ -30,6 +30,9 @@ outputs:
30
30
  # PR Skill
31
31
 
32
32
  > **에이전트 tier로 모델 고르기** → [`../_shared/agent-model.md`](../_shared/agent-model.md)
33
+ >
34
+ > **`gestalt` CLI를 부르는 형태** → [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)
35
+ > 본문의 셸 예시는 `gestalt ...`입니다. 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts ...`로 바꿔 부릅니다. description 어투 검사가 이 CLI에 매달려 있고 **그건 필수 검사라 못 돌리면 멈춥니다.**
33
36
  레포의 PR 규칙을 먼저 탐색하고 미니 인터뷰로 컨텍스트를 수집한 뒤, diff를 분석해 레포 규칙에 맞는 PR description을 생성하고 `gh pr create`로 제출합니다.
34
37
 
35
38
  ## 사용 방법
@@ -287,7 +290,21 @@ echo "$scanTmp"
287
290
  윤문된 제목과 본문을 합쳐 `"$scanTmp/description.md"`에 씁니다.
288
291
 
289
292
  ```bash
290
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/description.md" --register report
293
+ if gestalt --version >/dev/null 2>&1; then
294
+ echo "OK gestalt"
295
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
296
+ echo "OK pnpm"
297
+ else
298
+ echo "MISSING"
299
+ fi
300
+ ```
301
+
302
+ `OK pnpm`이면 아래 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 부릅니다.
303
+
304
+ **`MISSING`이면 검사를 건너뛰지 않고 멈춥니다.** 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같습니다. `npm i -g @tienne/gestalt`를 함께 알리고 그대로 진행할지 사용자가 정하게 둡니다.
305
+
306
+ ```bash
307
+ gestalt humanize-scan --file "$scanTmp/description.md" --register report
291
308
  ```
292
309
 
293
310
  - **10 / 11** — 통과입니다. 11이면 어투는 안 걸렸고 맞춤법만 고칩니다.
@@ -59,6 +59,9 @@ execute 세션 없이 PR, 브랜치, 커밋의 변경사항을 직접 리뷰 파
59
59
  >
60
60
  > **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
61
61
  >
62
+ > **`gestalt` CLI를 부르는 형태** → [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)
63
+ > 본문의 셸 예시는 전부 `gestalt ...`입니다. 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts ...`로 바꿔 부릅니다. 어투 검사(1.5, 4.5, 4.7단계)가 이 CLI에 매달려 있고 **그 셋은 필수 검사라 못 돌리면 멈춥니다.**
64
+ >
62
65
  > **에이전트 tier로 모델 고르기** → [`../_shared/agent-model.md`](../_shared/agent-model.md)
63
66
  >
64
67
  > **에이전트를 서브에이전트로 위임하기** → [`../_shared/agent-delegation.md`](../_shared/agent-delegation.md)
@@ -95,7 +98,7 @@ execute 세션 없이 PR, 브랜치, 커밋의 변경사항을 직접 리뷰 파
95
98
  `gestalt pr list`에는 브랜치 필터가 없습니다. `--status`만 받습니다. `headRef`도 못 믿습니다 — 워크트리에서 detached로 만든 PR은 거기에 브랜치 이름이 아니라 sha가 들어갑니다. 그래서 이름이 아니라 커밋으로 가릅니다.
96
99
 
97
100
  ```bash
98
- pnpm tsx bin/gestalt.ts pr --json list
101
+ gestalt pr --json list
99
102
  git merge-base --is-ancestor <PR의 headSha> HEAD # 종료 코드 0이면 내 브랜치의 PR
100
103
  ```
101
104
 
@@ -128,7 +131,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
128
131
  **명시 지정이 가장 셉니다. 그다음이 안 끝난 로컬 PR입니다.** 아래 순서대로 훑어 처음 걸리는 것을 택하고 나머지는 보지 않습니다.
129
132
 
130
133
  1. **로컬 지정** — `local` 입력이 true거나(`--local` 플래그) `target`이 로컬 PR id 형식(`gestalt pr list`에 뜨는 id)인 경우입니다. 이때는 로컬 PR을 두 수단으로 찾습니다.
131
- - `target`에 id가 있으면 먼저 `pnpm tsx bin/gestalt.ts pr --json show <id>`로 실제 존재를 확인합니다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따집니다.
134
+ - `target`에 id가 있으면 먼저 `gestalt pr --json show <id>`로 실제 존재를 확인합니다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따집니다.
132
135
  - id가 없거나(`--local`만 준 경우) `show`가 빈 결과를 내면 위의 가리는 법으로 현재 브랜치의 로컬 PR을 찾습니다.
133
136
  - 둘 중 하나로 찾으면 `local`입니다. 어느 쪽으로도 못 찾으면 그 사실을 한 줄 알리고 2번으로 내려갑니다.
134
137
 
@@ -226,7 +229,7 @@ git diff --name-only <commit>^ <commit>
226
229
  **`target`이 로컬 PR id 형식(8자리 16진수)이면 git이 그 값을 리비전으로 못 읽습니다.** `git diff main...<id>`는 `fatal: ambiguous argument`로 죽습니다. 이 경우는 PR에서 sha를 받아 옵니다.
227
230
 
228
231
  ```bash
229
- pnpm tsx bin/gestalt.ts pr --json show <id> # baseSha, headSha
232
+ gestalt pr --json show <id> # baseSha, headSha
230
233
  git diff --name-only <baseSha>..<headSha> # 점 두 개 — pr diff와 같은 범위
231
234
  ```
232
235
 
@@ -263,14 +266,14 @@ git diff --name-only <baseSha>..<headSha> # 점 두 개 — pr diff
263
266
  리뷰어의 워크트리는 자기 브랜치에 올라타 있어서 PR 코드가 거기 없습니다. 떼어냅니다.
264
267
 
265
268
  ```bash
266
- pnpm tsx bin/gestalt.ts pr checkout <id> --json # { path, created, headSha }
269
+ gestalt pr checkout <id> --json # { path, created, headSha }
267
270
  ```
268
271
 
269
272
  `path`로 옮겨 가 테스트를 돌리고 핵심 줄을 일부러 깨봅니다. 같은 PR을 두 번 불러도 워크트리는
270
273
  하나이고 그 안의 변경은 살아남습니다. 끝나면 정리합니다.
271
274
 
272
275
  ```bash
273
- pnpm tsx bin/gestalt.ts pr checkout <id> --remove --json
276
+ gestalt pr checkout <id> --remove --json
274
277
  ```
275
278
 
276
279
  정리 결과는 `status`로 분기합니다 — 산문 `reason`을 부분 문자열로 긁지 않습니다.
@@ -306,7 +309,7 @@ pnpm tsx bin/gestalt.ts pr checkout <id> --remove --json
306
309
  - `local` — 1단계나 판별 1번의 `pr --json show <id>` 결과에 `title`과 `body`가 이미 들어 있습니다(`PullRequest` 전체가 그대로 나옵니다). 조회를 안 거쳐 왔으면 여기서 한 번 부릅니다.
307
310
 
308
311
  ```bash
309
- pnpm tsx bin/gestalt.ts pr --json show <id> # title, body
312
+ gestalt pr --json show <id> # title, body
310
313
  ```
311
314
 
312
315
  - `none` — 이 단계를 건너뛰고 `prContext`를 `"(없음)"`으로 둡니다. 브랜치나 커밋 범위 리뷰에는 본문이랄 게 없습니다.
@@ -413,7 +416,21 @@ echo "$scanTmp"
413
416
  작성된 컨텍스트 문서를 `"$scanTmp/change-context.md"`에 쓴다 (파일 쓰기 도구 사용, 셸로 넘기지 않는다).
414
417
 
415
418
  ```bash
416
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/change-context.md" --register report
419
+ if gestalt --version >/dev/null 2>&1; then
420
+ echo "OK gestalt"
421
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
422
+ echo "OK pnpm"
423
+ else
424
+ echo "MISSING"
425
+ fi
426
+ ```
427
+
428
+ `OK pnpm`이면 아래 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 부릅니다. 한 번 확인하고 그 뒤로는(4.5, 4.7단계 포함) 같은 형태를 씁니다.
429
+
430
+ **`MISSING`이면 검사를 건너뛰지 않고 멈춥니다.** 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같습니다. `npm i -g @tienne/gestalt`를 함께 알리고 그대로 진행할지 사용자가 정하게 둡니다.
431
+
432
+ ```bash
433
+ gestalt humanize-scan --file "$scanTmp/change-context.md" --register report
417
434
  ```
418
435
 
419
436
  - **10 / 11** — 통과. 11이면 어투는 두고 맞춤법만 고친다.
@@ -658,7 +675,7 @@ echo "$scanTmp"
658
675
  윤문 전 리포트 전문을 `"$scanTmp/consensus.md"`에 쓰고 스캔합니다.
659
676
 
660
677
  ```bash
661
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/consensus.md" --register report
678
+ gestalt humanize-scan --file "$scanTmp/consensus.md" --register report
662
679
  ```
663
680
 
664
681
  출력 전문을 그대로 아래 윤문 프롬프트의 `사전 스캔` 자리에 싣습니다. 종료 코드로 걸러 내지 않습니다 — 0건일 때의 출력도 "무엇을 직접 확인해야 하는가"를 담고 있어서 윤문하는 쪽이 읽을 값이 있습니다.
@@ -717,7 +734,7 @@ Agent {
717
734
  윤문된 리포트 전문을 사전 스캔이 출력한 경로 아래 `report.md`에 씁니다.
718
735
 
719
736
  ```bash
720
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/report.md" --register report
737
+ gestalt humanize-scan --file "$scanTmp/report.md" --register report
721
738
  ```
722
739
 
723
740
  - **10 / 11** — 통과입니다. 11이면 어투는 안 걸렸고 맞춤법만 고칩니다.
@@ -752,7 +769,7 @@ git rev-parse HEAD && git status --porcelain
752
769
  gh pr view <target> --json headRefOid
753
770
 
754
771
  # local: PR head도 함께 확인
755
- pnpm tsx bin/gestalt.ts pr --json show <id> # headSha 필드로 비교
772
+ gestalt pr --json show <id> # headSha 필드로 비교
756
773
  ```
757
774
 
758
775
  판단 기준:
@@ -773,7 +790,7 @@ pnpm tsx bin/gestalt.ts pr --json show <id> # headSha 필드로 비교
773
790
  gh pr view <number> --json number,headRefName,baseRefName,url 2>/dev/null
774
791
 
775
792
  # local
776
- pnpm tsx bin/gestalt.ts pr --json show <id> 2>/dev/null
793
+ gestalt pr --json show <id> 2>/dev/null
777
794
  ```
778
795
 
779
796
  여기서 PR이 사라졌으면 게시하지 않고 그 사실을 알립니다. 판별을 다시 돌려 다른 자리에 옮겨 붙이지 않습니다.
@@ -868,7 +885,7 @@ echo "파일 $found 개"
868
885
  # 파일마다 프로세스를 새로 띄우지 않고 한 번의 호출에 전부 실어 보냅니다.
869
886
  # humanize-scan 은 넘겨받은 파일 각각을 읽고 스캔을 따로 돌리므로 결과는 파일별로 갈립니다.
870
887
  find "$scanTmp" -maxdepth 1 -name '*.md' | sort | sed 's/^/--file /' \
871
- | xargs pnpm tsx bin/gestalt.ts humanize-scan --register chat
888
+ | xargs gestalt humanize-scan --register chat
872
889
  ```
873
890
 
874
891
  `found` 가 코멘트 수와 다르면 **검사 실패입니다.** 그 자리에서 멈추고 무엇이 어긋났는지 알립니다. 파일 쓰기가 어긋났는데 조용히 넘어가면 이 검사가 막으려던 상황이 그대로 재현됩니다.
@@ -202,13 +202,12 @@ gh repo view --json owner,name,nameWithOwner
202
202
 
203
203
  **판정에 쓰는 수를 전부 이 명령이 낸다.** 없으면 첫 단계부터 아무것도 못 한다 — 설치본이 뒤처져 있으면 `unknown option` 으로 죽는데, 그걸 라운드를 돌다가 알면 늦다.
204
204
 
205
+ 먼저 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 돌려 `gestalt`를 어떤 형태로 부르는지 정한다. 그다음 그 형태로 하위 명령을 찌른다.
206
+
205
207
  ```bash
206
208
  if gestalt review-loop parse 1 >/dev/null 2>&1 \
207
209
  && gestalt review-loop dir >/dev/null 2>&1; then
208
- echo "OK gestalt"
209
- elif pnpm tsx bin/gestalt.ts review-loop parse 1 >/dev/null 2>&1 \
210
- && pnpm tsx bin/gestalt.ts review-loop dir >/dev/null 2>&1; then
211
- echo "OK pnpm"
210
+ echo "OK"
212
211
  else
213
212
  echo "MISSING"
214
213
  exit 1
@@ -217,7 +216,7 @@ fi
217
216
 
218
217
  **명령을 변수에 담아 함수로 넘기지 않는다.** zsh는 인용 안 한 변수 전개에서 단어 분리를 안 해 `$cmd`가 통째로 명령 이름이 된다. 그러면 실제로 깔려 있어도 `MISSING`이 나온다. 같은 코드가 bash에서는 도는 탓에 눈으로는 안 걸린다.
219
218
 
220
- `OK gestalt`면 아래 예시를 그대로 쓴다. `OK pnpm`이면 게슈탈트 레포 안이라는 뜻이라 모든 호출을 `pnpm tsx bin/gestalt.ts review-loop ...`로 바꾼다. **`MISSING`이면 라운드를 시작하지 않는다.**
219
+ `OK`면 아래 예시를 앞의 판정에서 정한 형태로 쓴다. **`MISSING`이면 라운드를 시작하지 않는다.**
221
220
 
222
221
  **하위 명령을 둘 다 찔러본다.** 하나만 보면 그것만 있고 나머지가 없는 중간 설치본이 통과해 라운드 중간에 죽는다. 1.1이 입력 하나가 아니라 셋을 보는 이유와 같다.
223
222
 
@@ -532,7 +531,7 @@ gestalt humanize-scan --file "$stateDir/verdict-r<N>.md" --register chat
532
531
  echo "EXIT=$?"
533
532
  ```
534
533
 
535
- 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts humanize-scan ...`이다. 한 번 확인하고 그 뒤로는 같은 형태를 쓴다.
534
+ 부르는 형태는 위 "조회 명령이 있는지 먼저 본다"에서 판정한 것을 그대로 쓴다.
536
535
 
537
536
  | 종료 코드 | 무엇 |
538
537
  | --- | --- |
@@ -49,6 +49,9 @@ outputs:
49
49
  >
50
50
  > **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
51
51
  >
52
+ > **`gestalt` CLI를 부르는 형태** → [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)
53
+ > 본문의 셸 예시는 전부 `gestalt ...`다. 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts ...`로 바꿔 부른다. 답글 어투 검사가 이 CLI에 매달려 있고 **그건 필수 검사라 못 돌리면 멈춘다.**
54
+ >
52
55
  > **에이전트 tier로 모델 고르기** → [`../_shared/agent-model.md`](../_shared/agent-model.md)
53
56
  > GitHub PR은 `gh` CLI(REST + GraphQL)에, 로컬 PR은 `gestalt pr` CLI에 의존한다 (대상 판별은 아래). 둘 다 실패하면 거기서 멈추고 알린다. 스레드 목록을 손으로 지어내지 않는다.
54
57
 
@@ -82,7 +85,7 @@ outputs:
82
85
  `gestalt pr list`에는 브랜치 필터가 없다. `--status`만 받는다. `headRef`도 못 믿는다 — 워크트리에서 detached로 만든 PR은 거기에 브랜치 이름이 아니라 sha가 들어간다. 그래서 이름이 아니라 커밋으로 가른다.
83
86
 
84
87
  ```bash
85
- pnpm tsx bin/gestalt.ts pr --json list
88
+ gestalt pr --json list
86
89
  git merge-base --is-ancestor <PR의 headSha> HEAD # 종료 코드 0이면 내 브랜치의 PR
87
90
  ```
88
91
 
@@ -115,7 +118,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
115
118
  **명시 지정이 가장 세다. 그다음이 안 끝난 로컬 PR이다.** 아래 순서대로 훑어 처음 걸리는 것을 택한다.
116
119
 
117
120
  1. **로컬 지정** — `local` 입력이 true거나(`--local` 플래그) `target`이 로컬 PR id 형식(`gestalt pr list`에 뜨는 id)인 경우다. 이때는 로컬 PR을 두 수단으로 찾는다.
118
- - `target`에 id가 있으면 먼저 `pnpm tsx bin/gestalt.ts pr --json show <id>`로 실제 존재를 확인한다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따진다.
121
+ - `target`에 id가 있으면 먼저 `gestalt pr --json show <id>`로 실제 존재를 확인한다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따진다.
119
122
  - id가 없거나(`--local`만 준 경우) `show`가 빈 결과를 내면 위의 가리는 법으로 현재 브랜치의 로컬 PR을 찾는다.
120
123
  - 둘 중 하나로 찾으면 `local`. 어느 쪽으로도 못 찾으면 그 사실을 한 줄 알리고 2번으로 내려간다.
121
124
 
@@ -168,7 +171,7 @@ gh api user --jq .login
168
171
  **local**: 대상 PR은 판별에서 이미 정해졌다. 여기서 다시 조회하지 않고 그때 잡은 id를 그대로 쓴다. 본문과 상태가 필요하면 `show` 한 번이면 된다.
169
172
 
170
173
  ```bash
171
- pnpm tsx bin/gestalt.ts pr --json show <판별에서 잡은 id>
174
+ gestalt pr --json show <판별에서 잡은 id>
172
175
  ```
173
176
 
174
177
  `prTarget`이 `none`이면 0단계에 들어오지 않는다. 판별 4번에서 이미 멈춘 뒤다.
@@ -238,7 +241,7 @@ gh api --paginate 'repos/<owner>/<repo>/issues/<number>/comments?per_page=100' \
238
241
  로컬 PR에는 GraphQL이 없다. `gestalt pr comments`가 resolved 여부를 이미 필드로 준다 — 페이지네이션 걱정도 없다(단일 프로세스, SQLite 기반이라 상한이 없다).
239
242
 
240
243
  ```bash
241
- pnpm tsx bin/gestalt.ts pr --json comments <id> --unresolved
244
+ gestalt pr --json comments <id> --unresolved
242
245
  ```
243
246
 
244
247
  반환된 코멘트를 스레드로 재구성한다. `replyTo`로 이어지는 코멘트를 한 체인으로 묶는다. 체인의 마지막 작성자가 나 자신이면 제외한다(이미 답했음). `line`이 `null`인 항목이 PR 전반 코멘트다 — 별도 API가 없다. `isOutdated` 개념은 로컬 PR에 없으므로 그 필터는 건너뛴다.
@@ -390,13 +393,27 @@ echo "$scanTmp"
390
393
 
391
394
  **본문만 쓴다** — 표지 줄을 붙이면 그 줄이 산문으로 세어져 인용 판정이 어긋난다.
392
395
 
396
+ ```bash
397
+ if gestalt --version >/dev/null 2>&1; then
398
+ echo "OK gestalt"
399
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
400
+ echo "OK pnpm"
401
+ else
402
+ echo "MISSING"
403
+ fi
404
+ ```
405
+
406
+ `OK pnpm`이면 아래 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 부른다.
407
+
408
+ **`MISSING`이면 검사를 건너뛰지 않고 멈춘다.** 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같다. `npm i -g @tienne/gestalt`를 함께 알리고 그대로 진행할지 사용자가 정하게 둔다.
409
+
393
410
  ```bash
394
411
  # 개수를 먼저 센다. 글롭으로 바로 돌면 파일이 0개일 때 셸마다 다르게 죽는다
395
412
  found=$(find "$scanTmp" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')
396
413
  echo "파일 $found 개"
397
414
 
398
415
  find "$scanTmp" -maxdepth 1 -name '*.md' | sort | while read -r f; do
399
- pnpm tsx bin/gestalt.ts humanize-scan --file "$f" --register chat
416
+ gestalt humanize-scan --file "$f" --register chat
400
417
  echo "$(basename "$f" .md) EXIT=$?"
401
418
  done
402
419
  ```
@@ -456,7 +473,7 @@ gh api repos/<owner>/<repo>/issues/<number>/comments -f body="..."
456
473
  **local**:
457
474
 
458
475
  ```bash
459
- pnpm tsx bin/gestalt.ts pr comment <id> \
476
+ gestalt pr comment <id> \
460
477
  --path "<원 코멘트의 path>" \
461
478
  --line <원 코멘트의 line — 없으면 생략> \
462
479
  --reply-to <원 코멘트 id> \
@@ -487,7 +504,7 @@ mutation($threadId:ID!) {
487
504
  **local**:
488
505
 
489
506
  ```bash
490
- pnpm tsx bin/gestalt.ts pr resolve <id> <commentId>
507
+ gestalt pr resolve <id> <commentId>
491
508
  ```
492
509
 
493
510
  종료하더라도 `accept`·`alternate`만 종료한다. `defer`·`clarify`는 대화가 남아 있으므로 열어둔다. (github·local 공통)
@@ -64,14 +64,10 @@ multiSelect: true
64
64
  **파일이 없으면**: Bash로 생성한다.
65
65
 
66
66
  ```bash
67
- ! pnpm tsx bin/gestalt.ts setup
67
+ ! gestalt setup
68
68
  ```
69
69
 
70
- 또는 npx로 설치된 환경이면:
71
-
72
- ```bash
73
- ! npx @tienne/gestalt setup
74
- ```
70
+ 부르는 형태는 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 따른다. 전역 설치도 게슈탈트 레포도 아니면 `npx @tienne/gestalt setup`으로 부른다.
75
71
 
76
72
  **파일이 이미 존재하면**: 사용자에게 덮어쓸지 확인 후 진행한다.
77
73
 
@@ -107,14 +103,10 @@ ges_code_graph({ action: "build", repoRoot: "<현재 디렉토리 절대경로>"
107
103
  ### Step 4 — post-commit 훅 설치 (선택된 경우)
108
104
 
109
105
  ```bash
110
- ! pnpm tsx bin/gestalt.ts init --skip-graph
106
+ ! gestalt init --skip-graph
111
107
  ```
112
108
 
113
- 또는:
114
-
115
- ```bash
116
- ! npx @tienne/gestalt init --skip-graph
117
- ```
109
+ 부르는 형태는 위 Step 2와 같다.
118
110
 
119
111
  성공 시 출력:
120
112
  ```
@@ -351,7 +351,7 @@ Phase 0에서 4번(사용자가 직접 준 명령)으로 정했으면 `verify-sr
351
351
 
352
352
  ### 명령 형태
353
353
 
354
- 게슈탈트 레포 안에서는 전역 설치가 없을 수 있으므로 `pnpm tsx bin/gestalt.ts pr ...`이고 밖에서는 `gestalt pr ...`이다. 확인하고 그 뒤로는 같은 형태를 쓴다.
354
+ 예시는 `gestalt pr ...`이다. 부르는 형태는 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 따른다 — 게슈탈트 레포 안에서는 전역 설치가 없을 수 있어 형태가 갈린다. 확인하고 그 뒤로는 같은 형태를 쓴다.
355
355
 
356
356
  ## Phase 1 — 로컬 PR 확보
357
357
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.81.0",
3
+ "version": "0.81.1",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt",
3
- "version": "0.81.0",
3
+ "version": "0.81.1",
4
4
  "description": "Gestalt psychology-driven AI development harness. Transforms scattered requirements into structured, validated specifications through interactive interviews.",
5
5
  "author": {
6
6
  "name": "tienne"
package/plugin/.mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "gestalt": {
4
4
  "command": "npx",
5
- "args": ["-y", "@tienne/gestalt@0.81.0", "serve"],
5
+ "args": ["-y", "@tienne/gestalt@0.81.1", "serve"],
6
6
  "startup_timeout_sec": 180,
7
7
  "tool_timeout_sec": 900
8
8
  }
package/plugin/mcp.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "mcpServers": {
3
3
  "gestalt": {
4
4
  "command": "npx",
5
- "args": ["-y", "@tienne/gestalt@0.81.0", "serve"],
5
+ "args": ["-y", "@tienne/gestalt@0.81.1", "serve"],
6
6
  "startup_timeout_sec": 180,
7
7
  "tool_timeout_sec": 900
8
8
  }
@@ -56,7 +56,7 @@ git diff --word-diff=porcelain -- <파일>
56
56
  **정적 탐지를 먼저 돌립니다.** 정규식으로 잡히는 룰은 사람이 눈으로 세는 것보다 정확합니다.
57
57
 
58
58
  ```bash
59
- pnpm tsx bin/gestalt.ts humanize-check --before <파일> --after <파일> --register doc --json
59
+ gestalt humanize-check --before <파일> --after <파일> --register doc --json
60
60
  ```
61
61
 
62
62
  같은 파일을 `--before`와 `--after`에 둘 다 넘기면 윤문 전후 비교가 아니라 그 파일에 남아 있는 S1 패턴을 세어 줍니다. 잡히는 룰은 21개뿐이니 나머지는 읽고 판정합니다.
@@ -0,0 +1,45 @@
1
+ # gestalt CLI를 어떻게 부르는지 먼저 판정한다
2
+
3
+ 이 문서는 여러 스킬이 공유하는 규칙이다. `gestalt` CLI(`humanize-scan`, `pr`, `review-loop`, `setup` 등)를 셸로 부르는 단계에서 적용한다.
4
+
5
+ ## 왜 필요한가
6
+
7
+ 플러그인으로 설치되면 호스트가 복사해 가는 건 `plugin/` 아래뿐이다. `bin/`도 `package.json`도 `node_modules`도 그 안에 없다. 그래서 스킬 본문에 `pnpm tsx bin/gestalt.ts ...`만 적혀 있으면 게슈탈트 레포 밖에서는 그 명령이 원리상 안 돈다.
8
+
9
+ 그때 읽는 쪽이 하는 말이 "의존성이 안 깔려 있어서 못 돌립니다"다. 실제로는 전역 `gestalt`가 깔려 있어 돌릴 수 있는데도 스킬이 적어준 형태 하나가 막혀서 포기한다. 어투 검사처럼 **필수 검사인 단계가 이렇게 조용히 건너뛰어지면** 막으려던 문장이 그대로 나간다.
10
+
11
+ 그래서 호출 형태를 스킬마다 박아두지 않고 한 번 판정해서 쓴다.
12
+
13
+ ## 판정
14
+
15
+ CLI를 처음 부르기 전에 한 번 돌린다.
16
+
17
+ ```bash
18
+ if gestalt --version >/dev/null 2>&1; then
19
+ echo "OK gestalt"
20
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
21
+ echo "OK pnpm"
22
+ else
23
+ echo "MISSING"
24
+ fi
25
+ ```
26
+
27
+ - **`OK gestalt`** — 스킬 본문의 예시를 그대로 쓴다. 본문은 전부 `gestalt ...` 형태로 적혀 있다.
28
+ - **`OK pnpm`** — 게슈탈트 레포 안이라는 뜻이다. 그 뒤로 모든 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 쓴다.
29
+ - **`MISSING`** — 아래 "없을 때"를 따른다.
30
+
31
+ 한 번 확인하고 그 뒤로는 같은 형태를 쓴다. 단계마다 다시 찌르지 않는다.
32
+
33
+ **쓰려는 하위 명령이 최근에 생긴 것이면 그 하위 명령으로 찌른다.** `--version`은 설치본이 뒤처져 하위 명령만 없는 경우를 못 잡는다. 그 상태로 라운드를 돌다 `unknown option`으로 죽으면 앞선 단계가 통째로 헛돈다.
34
+
35
+ **명령을 변수에 담아 함수로 넘기지 않는다.** zsh는 인용 안 한 변수 전개에서 단어 분리를 안 해 `$cmd`가 통째로 명령 이름이 된다. 실제로 깔려 있어도 `MISSING`이 나온다. 같은 코드가 bash에서는 도는 탓에 눈으로는 안 걸린다.
36
+
37
+ ## 없을 때
38
+
39
+ `MISSING`이면 **그 단계가 필수 검사인지로 갈린다.**
40
+
41
+ - **필수 검사면 멈춘다.** 어투 검사(`humanize-scan`)가 그렇다. 검사를 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같다. 무엇을 못 돌렸는지와 `npm i -g @tienne/gestalt`를 함께 알리고 사용자가 그대로 진행할지 정하게 둔다.
42
+ - **판정에 쓰는 수를 그 명령이 내면 멈춘다.** `review-loop`처럼 모든 단계가 한 명령에 매달린 자리다.
43
+ - **그 밖에는 알리고 건너뛴다.** 없다고 스킬 전체를 막을 이유는 없다. 대신 완료 보고에 어느 단계를 안 돌렸는지 적는다.
44
+
45
+ 어느 쪽이든 **직접 흉내내지 않는다.** 스캔 결과를 지어내거나 룰북을 눈으로 훑어 "깨끗합니다"로 답하지 않는다 ([`tool-availability.md`](./tool-availability.md)와 같은 규칙이다).
@@ -56,7 +56,7 @@ outputs:
56
56
 
57
57
  git 저장소이기만 하면 된다. 인증도 원격도 안 본다.
58
58
 
59
- 명령은 `gestalt pr ...`이다. 게슈탈트 레포 안에서 돌 때는 전역 설치가 없을 수 있으므로 `pnpm tsx bin/gestalt.ts pr ...`로 부른다. 한 번 확인하고 그 뒤로는 같은 형태를 쓴다.
59
+ 명령은 `gestalt pr ...`이다. 부르는 형태는 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 따른다 — 게슈탈트 레포 안에서는 전역 설치가 없을 수 있어 형태가 갈린다. 한 번 확인하고 그 뒤로는 같은 형태를 쓴다.
60
60
 
61
61
  `--json`을 붙이면 객체만 나온다. 에이전트가 값을 읽어야 하는 자리에서는 이쪽을 쓴다.
62
62
 
@@ -30,6 +30,9 @@ outputs:
30
30
  # PR Skill
31
31
 
32
32
  > **에이전트 tier로 모델 고르기** → [`../_shared/agent-model.md`](../_shared/agent-model.md)
33
+ >
34
+ > **`gestalt` CLI를 부르는 형태** → [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)
35
+ > 본문의 셸 예시는 `gestalt ...`입니다. 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts ...`로 바꿔 부릅니다. description 어투 검사가 이 CLI에 매달려 있고 **그건 필수 검사라 못 돌리면 멈춥니다.**
33
36
  레포의 PR 규칙을 먼저 탐색하고 미니 인터뷰로 컨텍스트를 수집한 뒤, diff를 분석해 레포 규칙에 맞는 PR description을 생성하고 `gh pr create`로 제출합니다.
34
37
 
35
38
  ## 사용 방법
@@ -287,7 +290,21 @@ echo "$scanTmp"
287
290
  윤문된 제목과 본문을 합쳐 `"$scanTmp/description.md"`에 씁니다.
288
291
 
289
292
  ```bash
290
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/description.md" --register report
293
+ if gestalt --version >/dev/null 2>&1; then
294
+ echo "OK gestalt"
295
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
296
+ echo "OK pnpm"
297
+ else
298
+ echo "MISSING"
299
+ fi
300
+ ```
301
+
302
+ `OK pnpm`이면 아래 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 부릅니다.
303
+
304
+ **`MISSING`이면 검사를 건너뛰지 않고 멈춥니다.** 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같습니다. `npm i -g @tienne/gestalt`를 함께 알리고 그대로 진행할지 사용자가 정하게 둡니다.
305
+
306
+ ```bash
307
+ gestalt humanize-scan --file "$scanTmp/description.md" --register report
291
308
  ```
292
309
 
293
310
  - **10 / 11** — 통과입니다. 11이면 어투는 안 걸렸고 맞춤법만 고칩니다.
@@ -59,6 +59,9 @@ execute 세션 없이 PR, 브랜치, 커밋의 변경사항을 직접 리뷰 파
59
59
  >
60
60
  > **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
61
61
  >
62
+ > **`gestalt` CLI를 부르는 형태** → [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)
63
+ > 본문의 셸 예시는 전부 `gestalt ...`입니다. 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts ...`로 바꿔 부릅니다. 어투 검사(1.5, 4.5, 4.7단계)가 이 CLI에 매달려 있고 **그 셋은 필수 검사라 못 돌리면 멈춥니다.**
64
+ >
62
65
  > **에이전트 tier로 모델 고르기** → [`../_shared/agent-model.md`](../_shared/agent-model.md)
63
66
  >
64
67
  > **에이전트를 서브에이전트로 위임하기** → [`../_shared/agent-delegation.md`](../_shared/agent-delegation.md)
@@ -95,7 +98,7 @@ execute 세션 없이 PR, 브랜치, 커밋의 변경사항을 직접 리뷰 파
95
98
  `gestalt pr list`에는 브랜치 필터가 없습니다. `--status`만 받습니다. `headRef`도 못 믿습니다 — 워크트리에서 detached로 만든 PR은 거기에 브랜치 이름이 아니라 sha가 들어갑니다. 그래서 이름이 아니라 커밋으로 가릅니다.
96
99
 
97
100
  ```bash
98
- pnpm tsx bin/gestalt.ts pr --json list
101
+ gestalt pr --json list
99
102
  git merge-base --is-ancestor <PR의 headSha> HEAD # 종료 코드 0이면 내 브랜치의 PR
100
103
  ```
101
104
 
@@ -128,7 +131,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
128
131
  **명시 지정이 가장 셉니다. 그다음이 안 끝난 로컬 PR입니다.** 아래 순서대로 훑어 처음 걸리는 것을 택하고 나머지는 보지 않습니다.
129
132
 
130
133
  1. **로컬 지정** — `local` 입력이 true거나(`--local` 플래그) `target`이 로컬 PR id 형식(`gestalt pr list`에 뜨는 id)인 경우입니다. 이때는 로컬 PR을 두 수단으로 찾습니다.
131
- - `target`에 id가 있으면 먼저 `pnpm tsx bin/gestalt.ts pr --json show <id>`로 실제 존재를 확인합니다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따집니다.
134
+ - `target`에 id가 있으면 먼저 `gestalt pr --json show <id>`로 실제 존재를 확인합니다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따집니다.
132
135
  - id가 없거나(`--local`만 준 경우) `show`가 빈 결과를 내면 위의 가리는 법으로 현재 브랜치의 로컬 PR을 찾습니다.
133
136
  - 둘 중 하나로 찾으면 `local`입니다. 어느 쪽으로도 못 찾으면 그 사실을 한 줄 알리고 2번으로 내려갑니다.
134
137
 
@@ -226,7 +229,7 @@ git diff --name-only <commit>^ <commit>
226
229
  **`target`이 로컬 PR id 형식(8자리 16진수)이면 git이 그 값을 리비전으로 못 읽습니다.** `git diff main...<id>`는 `fatal: ambiguous argument`로 죽습니다. 이 경우는 PR에서 sha를 받아 옵니다.
227
230
 
228
231
  ```bash
229
- pnpm tsx bin/gestalt.ts pr --json show <id> # baseSha, headSha
232
+ gestalt pr --json show <id> # baseSha, headSha
230
233
  git diff --name-only <baseSha>..<headSha> # 점 두 개 — pr diff와 같은 범위
231
234
  ```
232
235
 
@@ -263,14 +266,14 @@ git diff --name-only <baseSha>..<headSha> # 점 두 개 — pr diff
263
266
  리뷰어의 워크트리는 자기 브랜치에 올라타 있어서 PR 코드가 거기 없습니다. 떼어냅니다.
264
267
 
265
268
  ```bash
266
- pnpm tsx bin/gestalt.ts pr checkout <id> --json # { path, created, headSha }
269
+ gestalt pr checkout <id> --json # { path, created, headSha }
267
270
  ```
268
271
 
269
272
  `path`로 옮겨 가 테스트를 돌리고 핵심 줄을 일부러 깨봅니다. 같은 PR을 두 번 불러도 워크트리는
270
273
  하나이고 그 안의 변경은 살아남습니다. 끝나면 정리합니다.
271
274
 
272
275
  ```bash
273
- pnpm tsx bin/gestalt.ts pr checkout <id> --remove --json
276
+ gestalt pr checkout <id> --remove --json
274
277
  ```
275
278
 
276
279
  정리 결과는 `status`로 분기합니다 — 산문 `reason`을 부분 문자열로 긁지 않습니다.
@@ -306,7 +309,7 @@ pnpm tsx bin/gestalt.ts pr checkout <id> --remove --json
306
309
  - `local` — 1단계나 판별 1번의 `pr --json show <id>` 결과에 `title`과 `body`가 이미 들어 있습니다(`PullRequest` 전체가 그대로 나옵니다). 조회를 안 거쳐 왔으면 여기서 한 번 부릅니다.
307
310
 
308
311
  ```bash
309
- pnpm tsx bin/gestalt.ts pr --json show <id> # title, body
312
+ gestalt pr --json show <id> # title, body
310
313
  ```
311
314
 
312
315
  - `none` — 이 단계를 건너뛰고 `prContext`를 `"(없음)"`으로 둡니다. 브랜치나 커밋 범위 리뷰에는 본문이랄 게 없습니다.
@@ -413,7 +416,21 @@ echo "$scanTmp"
413
416
  작성된 컨텍스트 문서를 `"$scanTmp/change-context.md"`에 쓴다 (파일 쓰기 도구 사용, 셸로 넘기지 않는다).
414
417
 
415
418
  ```bash
416
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/change-context.md" --register report
419
+ if gestalt --version >/dev/null 2>&1; then
420
+ echo "OK gestalt"
421
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
422
+ echo "OK pnpm"
423
+ else
424
+ echo "MISSING"
425
+ fi
426
+ ```
427
+
428
+ `OK pnpm`이면 아래 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 부릅니다. 한 번 확인하고 그 뒤로는(4.5, 4.7단계 포함) 같은 형태를 씁니다.
429
+
430
+ **`MISSING`이면 검사를 건너뛰지 않고 멈춥니다.** 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같습니다. `npm i -g @tienne/gestalt`를 함께 알리고 그대로 진행할지 사용자가 정하게 둡니다.
431
+
432
+ ```bash
433
+ gestalt humanize-scan --file "$scanTmp/change-context.md" --register report
417
434
  ```
418
435
 
419
436
  - **10 / 11** — 통과. 11이면 어투는 두고 맞춤법만 고친다.
@@ -658,7 +675,7 @@ echo "$scanTmp"
658
675
  윤문 전 리포트 전문을 `"$scanTmp/consensus.md"`에 쓰고 스캔합니다.
659
676
 
660
677
  ```bash
661
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/consensus.md" --register report
678
+ gestalt humanize-scan --file "$scanTmp/consensus.md" --register report
662
679
  ```
663
680
 
664
681
  출력 전문을 그대로 아래 윤문 프롬프트의 `사전 스캔` 자리에 싣습니다. 종료 코드로 걸러 내지 않습니다 — 0건일 때의 출력도 "무엇을 직접 확인해야 하는가"를 담고 있어서 윤문하는 쪽이 읽을 값이 있습니다.
@@ -717,7 +734,7 @@ Agent {
717
734
  윤문된 리포트 전문을 사전 스캔이 출력한 경로 아래 `report.md`에 씁니다.
718
735
 
719
736
  ```bash
720
- pnpm tsx bin/gestalt.ts humanize-scan --file "$scanTmp/report.md" --register report
737
+ gestalt humanize-scan --file "$scanTmp/report.md" --register report
721
738
  ```
722
739
 
723
740
  - **10 / 11** — 통과입니다. 11이면 어투는 안 걸렸고 맞춤법만 고칩니다.
@@ -752,7 +769,7 @@ git rev-parse HEAD && git status --porcelain
752
769
  gh pr view <target> --json headRefOid
753
770
 
754
771
  # local: PR head도 함께 확인
755
- pnpm tsx bin/gestalt.ts pr --json show <id> # headSha 필드로 비교
772
+ gestalt pr --json show <id> # headSha 필드로 비교
756
773
  ```
757
774
 
758
775
  판단 기준:
@@ -773,7 +790,7 @@ pnpm tsx bin/gestalt.ts pr --json show <id> # headSha 필드로 비교
773
790
  gh pr view <number> --json number,headRefName,baseRefName,url 2>/dev/null
774
791
 
775
792
  # local
776
- pnpm tsx bin/gestalt.ts pr --json show <id> 2>/dev/null
793
+ gestalt pr --json show <id> 2>/dev/null
777
794
  ```
778
795
 
779
796
  여기서 PR이 사라졌으면 게시하지 않고 그 사실을 알립니다. 판별을 다시 돌려 다른 자리에 옮겨 붙이지 않습니다.
@@ -868,7 +885,7 @@ echo "파일 $found 개"
868
885
  # 파일마다 프로세스를 새로 띄우지 않고 한 번의 호출에 전부 실어 보냅니다.
869
886
  # humanize-scan 은 넘겨받은 파일 각각을 읽고 스캔을 따로 돌리므로 결과는 파일별로 갈립니다.
870
887
  find "$scanTmp" -maxdepth 1 -name '*.md' | sort | sed 's/^/--file /' \
871
- | xargs pnpm tsx bin/gestalt.ts humanize-scan --register chat
888
+ | xargs gestalt humanize-scan --register chat
872
889
  ```
873
890
 
874
891
  `found` 가 코멘트 수와 다르면 **검사 실패입니다.** 그 자리에서 멈추고 무엇이 어긋났는지 알립니다. 파일 쓰기가 어긋났는데 조용히 넘어가면 이 검사가 막으려던 상황이 그대로 재현됩니다.
@@ -202,13 +202,12 @@ gh repo view --json owner,name,nameWithOwner
202
202
 
203
203
  **판정에 쓰는 수를 전부 이 명령이 낸다.** 없으면 첫 단계부터 아무것도 못 한다 — 설치본이 뒤처져 있으면 `unknown option` 으로 죽는데, 그걸 라운드를 돌다가 알면 늦다.
204
204
 
205
+ 먼저 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 돌려 `gestalt`를 어떤 형태로 부르는지 정한다. 그다음 그 형태로 하위 명령을 찌른다.
206
+
205
207
  ```bash
206
208
  if gestalt review-loop parse 1 >/dev/null 2>&1 \
207
209
  && gestalt review-loop dir >/dev/null 2>&1; then
208
- echo "OK gestalt"
209
- elif pnpm tsx bin/gestalt.ts review-loop parse 1 >/dev/null 2>&1 \
210
- && pnpm tsx bin/gestalt.ts review-loop dir >/dev/null 2>&1; then
211
- echo "OK pnpm"
210
+ echo "OK"
212
211
  else
213
212
  echo "MISSING"
214
213
  exit 1
@@ -217,7 +216,7 @@ fi
217
216
 
218
217
  **명령을 변수에 담아 함수로 넘기지 않는다.** zsh는 인용 안 한 변수 전개에서 단어 분리를 안 해 `$cmd`가 통째로 명령 이름이 된다. 그러면 실제로 깔려 있어도 `MISSING`이 나온다. 같은 코드가 bash에서는 도는 탓에 눈으로는 안 걸린다.
219
218
 
220
- `OK gestalt`면 아래 예시를 그대로 쓴다. `OK pnpm`이면 게슈탈트 레포 안이라는 뜻이라 모든 호출을 `pnpm tsx bin/gestalt.ts review-loop ...`로 바꾼다. **`MISSING`이면 라운드를 시작하지 않는다.**
219
+ `OK`면 아래 예시를 앞의 판정에서 정한 형태로 쓴다. **`MISSING`이면 라운드를 시작하지 않는다.**
221
220
 
222
221
  **하위 명령을 둘 다 찔러본다.** 하나만 보면 그것만 있고 나머지가 없는 중간 설치본이 통과해 라운드 중간에 죽는다. 1.1이 입력 하나가 아니라 셋을 보는 이유와 같다.
223
222
 
@@ -532,7 +531,7 @@ gestalt humanize-scan --file "$stateDir/verdict-r<N>.md" --register chat
532
531
  echo "EXIT=$?"
533
532
  ```
534
533
 
535
- 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts humanize-scan ...`이다. 한 번 확인하고 그 뒤로는 같은 형태를 쓴다.
534
+ 부르는 형태는 위 "조회 명령이 있는지 먼저 본다"에서 판정한 것을 그대로 쓴다.
536
535
 
537
536
  | 종료 코드 | 무엇 |
538
537
  | --- | --- |
@@ -49,6 +49,9 @@ outputs:
49
49
  >
50
50
  > **도구가 없을 때** → [`../_shared/tool-availability.md`](../_shared/tool-availability.md)
51
51
  >
52
+ > **`gestalt` CLI를 부르는 형태** → [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)
53
+ > 본문의 셸 예시는 전부 `gestalt ...`다. 게슈탈트 레포 안에서는 `pnpm tsx bin/gestalt.ts ...`로 바꿔 부른다. 답글 어투 검사가 이 CLI에 매달려 있고 **그건 필수 검사라 못 돌리면 멈춘다.**
54
+ >
52
55
  > **에이전트 tier로 모델 고르기** → [`../_shared/agent-model.md`](../_shared/agent-model.md)
53
56
  > GitHub PR은 `gh` CLI(REST + GraphQL)에, 로컬 PR은 `gestalt pr` CLI에 의존한다 (대상 판별은 아래). 둘 다 실패하면 거기서 멈추고 알린다. 스레드 목록을 손으로 지어내지 않는다.
54
57
 
@@ -82,7 +85,7 @@ outputs:
82
85
  `gestalt pr list`에는 브랜치 필터가 없다. `--status`만 받는다. `headRef`도 못 믿는다 — 워크트리에서 detached로 만든 PR은 거기에 브랜치 이름이 아니라 sha가 들어간다. 그래서 이름이 아니라 커밋으로 가른다.
83
86
 
84
87
  ```bash
85
- pnpm tsx bin/gestalt.ts pr --json list
88
+ gestalt pr --json list
86
89
  git merge-base --is-ancestor <PR의 headSha> HEAD # 종료 코드 0이면 내 브랜치의 PR
87
90
  ```
88
91
 
@@ -115,7 +118,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
115
118
  **명시 지정이 가장 세다. 그다음이 안 끝난 로컬 PR이다.** 아래 순서대로 훑어 처음 걸리는 것을 택한다.
116
119
 
117
120
  1. **로컬 지정** — `local` 입력이 true거나(`--local` 플래그) `target`이 로컬 PR id 형식(`gestalt pr list`에 뜨는 id)인 경우다. 이때는 로컬 PR을 두 수단으로 찾는다.
118
- - `target`에 id가 있으면 먼저 `pnpm tsx bin/gestalt.ts pr --json show <id>`로 실제 존재를 확인한다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따진다.
121
+ - `target`에 id가 있으면 먼저 `gestalt pr --json show <id>`로 실제 존재를 확인한다. 사용자가 id를 짚었으면 그 PR이 현재 브랜치 것인지는 안 따진다.
119
122
  - id가 없거나(`--local`만 준 경우) `show`가 빈 결과를 내면 위의 가리는 법으로 현재 브랜치의 로컬 PR을 찾는다.
120
123
  - 둘 중 하나로 찾으면 `local`. 어느 쪽으로도 못 찾으면 그 사실을 한 줄 알리고 2번으로 내려간다.
121
124
 
@@ -168,7 +171,7 @@ gh api user --jq .login
168
171
  **local**: 대상 PR은 판별에서 이미 정해졌다. 여기서 다시 조회하지 않고 그때 잡은 id를 그대로 쓴다. 본문과 상태가 필요하면 `show` 한 번이면 된다.
169
172
 
170
173
  ```bash
171
- pnpm tsx bin/gestalt.ts pr --json show <판별에서 잡은 id>
174
+ gestalt pr --json show <판별에서 잡은 id>
172
175
  ```
173
176
 
174
177
  `prTarget`이 `none`이면 0단계에 들어오지 않는다. 판별 4번에서 이미 멈춘 뒤다.
@@ -238,7 +241,7 @@ gh api --paginate 'repos/<owner>/<repo>/issues/<number>/comments?per_page=100' \
238
241
  로컬 PR에는 GraphQL이 없다. `gestalt pr comments`가 resolved 여부를 이미 필드로 준다 — 페이지네이션 걱정도 없다(단일 프로세스, SQLite 기반이라 상한이 없다).
239
242
 
240
243
  ```bash
241
- pnpm tsx bin/gestalt.ts pr --json comments <id> --unresolved
244
+ gestalt pr --json comments <id> --unresolved
242
245
  ```
243
246
 
244
247
  반환된 코멘트를 스레드로 재구성한다. `replyTo`로 이어지는 코멘트를 한 체인으로 묶는다. 체인의 마지막 작성자가 나 자신이면 제외한다(이미 답했음). `line`이 `null`인 항목이 PR 전반 코멘트다 — 별도 API가 없다. `isOutdated` 개념은 로컬 PR에 없으므로 그 필터는 건너뛴다.
@@ -390,13 +393,27 @@ echo "$scanTmp"
390
393
 
391
394
  **본문만 쓴다** — 표지 줄을 붙이면 그 줄이 산문으로 세어져 인용 판정이 어긋난다.
392
395
 
396
+ ```bash
397
+ if gestalt --version >/dev/null 2>&1; then
398
+ echo "OK gestalt"
399
+ elif pnpm tsx bin/gestalt.ts --version >/dev/null 2>&1; then
400
+ echo "OK pnpm"
401
+ else
402
+ echo "MISSING"
403
+ fi
404
+ ```
405
+
406
+ `OK pnpm`이면 아래 호출의 `gestalt`를 `pnpm tsx bin/gestalt.ts`로 바꿔 부른다.
407
+
408
+ **`MISSING`이면 검사를 건너뛰지 않고 멈춘다.** 못 돌린 걸 통과로 읽으면 검사가 없는 것과 같다. `npm i -g @tienne/gestalt`를 함께 알리고 그대로 진행할지 사용자가 정하게 둔다.
409
+
393
410
  ```bash
394
411
  # 개수를 먼저 센다. 글롭으로 바로 돌면 파일이 0개일 때 셸마다 다르게 죽는다
395
412
  found=$(find "$scanTmp" -maxdepth 1 -name '*.md' | wc -l | tr -d ' ')
396
413
  echo "파일 $found 개"
397
414
 
398
415
  find "$scanTmp" -maxdepth 1 -name '*.md' | sort | while read -r f; do
399
- pnpm tsx bin/gestalt.ts humanize-scan --file "$f" --register chat
416
+ gestalt humanize-scan --file "$f" --register chat
400
417
  echo "$(basename "$f" .md) EXIT=$?"
401
418
  done
402
419
  ```
@@ -456,7 +473,7 @@ gh api repos/<owner>/<repo>/issues/<number>/comments -f body="..."
456
473
  **local**:
457
474
 
458
475
  ```bash
459
- pnpm tsx bin/gestalt.ts pr comment <id> \
476
+ gestalt pr comment <id> \
460
477
  --path "<원 코멘트의 path>" \
461
478
  --line <원 코멘트의 line — 없으면 생략> \
462
479
  --reply-to <원 코멘트 id> \
@@ -487,7 +504,7 @@ mutation($threadId:ID!) {
487
504
  **local**:
488
505
 
489
506
  ```bash
490
- pnpm tsx bin/gestalt.ts pr resolve <id> <commentId>
507
+ gestalt pr resolve <id> <commentId>
491
508
  ```
492
509
 
493
510
  종료하더라도 `accept`·`alternate`만 종료한다. `defer`·`clarify`는 대화가 남아 있으므로 열어둔다. (github·local 공통)
@@ -64,14 +64,10 @@ multiSelect: true
64
64
  **파일이 없으면**: Bash로 생성한다.
65
65
 
66
66
  ```bash
67
- ! pnpm tsx bin/gestalt.ts setup
67
+ ! gestalt setup
68
68
  ```
69
69
 
70
- 또는 npx로 설치된 환경이면:
71
-
72
- ```bash
73
- ! npx @tienne/gestalt setup
74
- ```
70
+ 부르는 형태는 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 따른다. 전역 설치도 게슈탈트 레포도 아니면 `npx @tienne/gestalt setup`으로 부른다.
75
71
 
76
72
  **파일이 이미 존재하면**: 사용자에게 덮어쓸지 확인 후 진행한다.
77
73
 
@@ -107,14 +103,10 @@ ges_code_graph({ action: "build", repoRoot: "<현재 디렉토리 절대경로>"
107
103
  ### Step 4 — post-commit 훅 설치 (선택된 경우)
108
104
 
109
105
  ```bash
110
- ! pnpm tsx bin/gestalt.ts init --skip-graph
106
+ ! gestalt init --skip-graph
111
107
  ```
112
108
 
113
- 또는:
114
-
115
- ```bash
116
- ! npx @tienne/gestalt init --skip-graph
117
- ```
109
+ 부르는 형태는 위 Step 2와 같다.
118
110
 
119
111
  성공 시 출력:
120
112
  ```
@@ -351,7 +351,7 @@ Phase 0에서 4번(사용자가 직접 준 명령)으로 정했으면 `verify-sr
351
351
 
352
352
  ### 명령 형태
353
353
 
354
- 게슈탈트 레포 안에서는 전역 설치가 없을 수 있으므로 `pnpm tsx bin/gestalt.ts pr ...`이고 밖에서는 `gestalt pr ...`이다. 확인하고 그 뒤로는 같은 형태를 쓴다.
354
+ 예시는 `gestalt pr ...`이다. 부르는 형태는 [`../_shared/cli-launcher.md`](../_shared/cli-launcher.md)의 판정을 따른다 — 게슈탈트 레포 안에서는 전역 설치가 없을 수 있어 형태가 갈린다. 확인하고 그 뒤로는 같은 형태를 쓴다.
355
355
 
356
356
  ## Phase 1 — 로컬 PR 확보
357
357