@tienne/gestalt 0.72.0 → 0.72.2

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/CLAUDE.md CHANGED
@@ -108,13 +108,35 @@ plugin/.mcp.json Grok MCP (plugin/mcp.json과 동일)
108
108
  ```
109
109
 
110
110
  - Codex는 마켓플레이스 매니페스트를 `.agents/plugins/marketplace.json`에서만 찾는다. `.codex-plugin/marketplace.json`은 인식하지 않는다.
111
- - Grok은 `.grok-plugin/marketplace.json`이 정본이다. source는 반드시 `./plugin`이다. Claude 매니페스트(`source: "./"`)를 바꾸지 말 것.
111
+ - Grok은 `.grok-plugin/marketplace.json`만 읽는다. 마켓플레이스를 고칠 일이 있으면 여기를 고친다. source는 반드시 `./plugin`이다. Claude 매니페스트(`source: "./"`)를 바꾸지 말 것.
112
112
  - Grok은 `plugin/.mcp.json`(점 파일)을 읽는다. `plugin/mcp.json`과 내용을 같게 유지한다.
113
113
  - Codex는 `path`가 가리킨 디렉토리를 통째로 복사한다. 레포 루트를 가리키면 `.git`과 `node_modules`까지 딸려가 1.6GB가 되므로 반드시 `plugin/`으로 좁힌다.
114
114
  - Codex는 심링크를 따라가지 않는다. 자산은 실물 파일로 `plugin/` 안에 있어야 한다.
115
115
  - `plugin/skills/review/SKILL.md`가 `../../role-agents/`를 참조한다. 스킬과 에이전트를 함께 옮겨야 이 상대 깊이가 유지된다.
116
116
  - 자산 디렉토리 기본값은 `src/core/config.ts`에 `skillsDir`, `agentsDir`, `roleAgentsDir`, `reviewAgentsDir`, `personasDir` 다섯 개로 있다. 경로를 바꾸면 전부 함께 고친다.
117
117
 
118
+ ### MCP 기동 경로
119
+
120
+ 클라이언트마다 서버를 띄우는 방식이 다르다.
121
+
122
+ ```
123
+ .mcp.json Claude — sh로 scripts/mcp-serve.sh를 찾아 실행
124
+ .claude-plugin/.mcp.json .mcp.json과 내용 동일 (해석 기준 디렉토리가 모호해 양쪽에 둔다)
125
+ plugin/mcp.json Codex — npx, 버전 핀
126
+ plugin/.mcp.json Grok(배포) — plugin/mcp.json과 동일
127
+ .grok/config.toml Grok(이 레포 개발용) — scripts/grok-mcp-serve.sh
128
+ ```
129
+
130
+ - `npx`는 버전을 박아도 기동할 때마다 레지스트리를 조회한다. 캐시가 비면 20초, 레지스트리에 못 닿으면 70초를 매달린다. Claude Code의 기동 제한은 30초라 둘 다 `Connection closed`로 끊긴다.
131
+ - `startup_timeout_sec`와 `tool_timeout_sec`는 Codex 키다. Claude Code는 안 읽고 `MCP_TIMEOUT` 환경변수만 본다. Claude 매니페스트에 넣어봐야 무시된다.
132
+ - `scripts/mcp-serve.sh`가 그 셋을 처리한다. nvm, fnm, Volta, Homebrew에서 Node >= 20을 찾는다 (GUI 세션은 PATH에 버전 매니저가 없다). 전역 `gestalt`가 있으면 그걸 쓰고 없으면 `npx --offline`으로 캐시에서 해석한다.
133
+ - 그 스크립트는 npx로 서버를 띄우지 않고 bin 경로만 받아와 직접 exec한다. npx가 cwd의 로컬 패키지를 먼저 보기 때문에, node_modules 없는 gestalt 체크아웃 안에서는 `gestalt: command not found`로 죽는다. 그래서 해석은 `cd /`에서 한다.
134
+ - 전역 `gestalt`가 깔려 있으면 핀보다 그게 이긴다. 누가 `npm i -g`를 했다는 건 이 체크아웃이 번들한 것보다 구체적인 선택이라서다. 대신 어느 쪽을 썼는지 stderr에 적어 버전이 어긋났을 때 로그에서 보이게 한다.
135
+ - 매니페스트의 `sh -c`는 `${CLAUDE_PLUGIN_ROOT}`를 먼저 본다. 거기서 스크립트를 찾으면 `GESTALT_LAUNCHER`는 아예 안 본다. 플러그인으로 설치된 상태에서는 그 변수가 안 걸린다는 뜻이다. 플러그인 없이 이 레포만 연 경우에만 차례가 온다. 그때도 **절대 경로만** 받는다 — 상대 경로를 허용하면 남의 레포를 열었을 때 거기 있는 동명 실행 파일이 서버 대신 도는 자리가 된다.
136
+ - 그 `sh -c`의 최후 폴백도 버전이 핀되어 있다. 거기까지 왔으면 스크립트를 못 찾은 것이다. 스크립트가 없으면 `package.json`도 없어 런타임에 버전을 못 읽는다. 그래서 그 자리만은 `sync-version.ts`가 문자열에 직접 박는다.
137
+ - 네 매니페스트의 버전 핀을 `scripts/sync-version.ts`가 릴리즈마다 함께 갱신한다. `plugin/*`는 인자 하나가 통째로 스펙이고 Claude 쪽은 `sh` 문자열 안에 박혀 있는데, 같은 정규식으로 둘 다 친다.
138
+ - `command: "sh"`라서 Windows 호스트에서는 안 뜬다. 그쪽은 전역 설치 후 `command: "gestalt"`로 안내한다.
139
+
118
140
  ## Conventions
119
141
  - MCP 서버에서 `console.log` 금지 → `log()` stderr 유틸 사용
120
142
  - `noUncheckedIndexedAccess` 환경 → 배열 인덱스·regex 캡처그룹에 `!` 단언 필수
package/README.ko.md CHANGED
@@ -161,14 +161,18 @@ claude plugin install gestalt@gestalt
161
161
 
162
162
  ### 옵션 2: Claude Code Desktop
163
163
 
164
- Claude Code Desktop 설정에서 `settings.json` (또는 `claude_desktop_config.json`)에 추가하세요:
164
+ 먼저 전역 설치한 다음, 설정이 바이너리를 가리키게 하세요:
165
+
166
+ ```bash
167
+ npm install -g @tienne/gestalt
168
+ ```
165
169
 
166
170
  ```json
167
171
  {
168
172
  "mcpServers": {
169
173
  "gestalt": {
170
- "command": "npx",
171
- "args": ["-y", "@tienne/gestalt"]
174
+ "command": "gestalt",
175
+ "args": ["serve"]
172
176
  }
173
177
  }
174
178
  }
@@ -176,13 +180,15 @@ Claude Code Desktop 설정에서 `settings.json` (또는 `claude_desktop_config.
176
180
 
177
181
  Claude Code Desktop을 재시작하면 MCP 도구가 즉시 사용 가능해요. 슬래시 커맨드는 플러그인 설치 또는 별도 스킬 설정이 필요해요.
178
182
 
183
+ `npx -y @tienne/gestalt`도 되긴 하는데, 그 전에 [기동 타임아웃](#기동-타임아웃)을 읽어보세요. 데스크톱 앱이 이 문제에 제일 잘 걸립니다.
184
+
179
185
  ---
180
186
 
181
187
  ### 옵션 3: Claude Code CLI
182
188
 
183
189
  ```bash
184
- # claude CLI로 추가
185
- claude mcp add gestalt -- npx -y @tienne/gestalt
190
+ npm install -g @tienne/gestalt
191
+ claude mcp add gestalt -- gestalt serve
186
192
  ```
187
193
 
188
194
  또는 `~/.claude/settings.json`을 직접 편집하세요:
@@ -191,8 +197,8 @@ claude mcp add gestalt -- npx -y @tienne/gestalt
191
197
  {
192
198
  "mcpServers": {
193
199
  "gestalt": {
194
- "command": "npx",
195
- "args": ["-y", "@tienne/gestalt"]
200
+ "command": "gestalt",
201
+ "args": ["serve"]
196
202
  }
197
203
  }
198
204
  }
@@ -200,6 +206,26 @@ claude mcp add gestalt -- npx -y @tienne/gestalt
200
206
 
201
207
  ---
202
208
 
209
+ ### 기동 타임아웃
210
+
211
+ 서버가 붙을 때는 붙고 어떨 때는 `Connection closed`로 끊긴다면, 원인은 대개 Gestalt가 아니라 `npx`입니다. 알아둘 게 셋입니다.
212
+
213
+ **npx는 기동할 때마다 레지스트리를 조회합니다.** 버전을 정확히 박아도 마찬가지예요. npm 상대로 재봤더니 캐시가 데워졌을 때 1.9초, 비었을 때 20초, 레지스트리에 못 닿으면 70초를 매달린 뒤 실패했습니다. Claude Code는 stdio 서버에 `initialize` 응답까지 30초를 주므로 콜드 상태와 오프라인 상태가 둘 다 끊긴 연결로 나타납니다. 전역 설치해서 `gestalt serve`를 직접 부르면 이 과정이 통째로 사라집니다.
214
+
215
+ **GUI로 띄운 세션은 PATH가 다릅니다.** 터미널 밖에서 시작한 것들 — 데스크톱 앱, 런처, launchd — 은 버전 매니저가 빠진 PATH를 물려받아서 `npx`를 아예 못 찾고 즉시 죽습니다. `command`에 절대 경로를 주거나 서버 항목에 `env.PATH`를 지정하세요.
216
+
217
+ **Claude Code에서 `startup_timeout_sec`는 아무 일도 안 합니다.** 그건 Codex 키예요. Claude Code는 `MCP_TIMEOUT` 환경변수(밀리초)를 읽으므로 `settings.json`에 넣어야 Claude Code 프로세스까지 전달됩니다:
218
+
219
+ ```json
220
+ {
221
+ "env": { "MCP_TIMEOUT": "180000" }
222
+ }
223
+ ```
224
+
225
+ 플러그인 설치(옵션 1)는 앞의 둘을 `scripts/mcp-serve.sh`가 알아서 처리해요. Node는 nvm, fnm, Volta, Homebrew 밑에서 찾고요. 전역 설치된 `gestalt`가 있으면 그걸 씁니다. 없으면 핀된 버전을 npm 캐시에서 바로 해석하고요.
226
+
227
+ ---
228
+
203
229
  ### 옵션 4: OpenAI Codex 플러그인
204
230
 
205
231
  Claude Code 플러그인과 똑같이 MCP 서버랑 워크플로 스킬 19개를 한 번에 받아요.
@@ -246,7 +272,7 @@ Codex는 호스트 패스스루로 동작해요 — Gestalt가 프롬프트와
246
272
 
247
273
  ### 옵션 6: Grok Build 플러그인
248
274
 
249
- Grok Build TUI/CLI용으로 MCP 서버랑 워크플로 스킬을 한 번에 받아요. 마켓플레이스는 `.grok-plugin/marketplace.json`이 정본이에요. Claude 마켓플레이스는 레포 루트를 복사하니 여기서 쓰지 마세요.
275
+ Grok Build TUI/CLI용으로 MCP 서버랑 워크플로 스킬을 한 번에 받아요. Grok은 `.grok-plugin/marketplace.json`을 읽어요. Claude 마켓플레이스는 레포 루트를 복사하니 여기서 쓰지 마세요.
250
276
 
251
277
  ```bash
252
278
  grok plugin marketplace add tienne/gestalt
package/README.md CHANGED
@@ -132,14 +132,18 @@ What you get:
132
132
 
133
133
  ### Option 2: Claude Code Desktop
134
134
 
135
- Add this to your `settings.json` (or `claude_desktop_config.json`) and restart:
135
+ Install once, then point the config at the installed binary:
136
+
137
+ ```bash
138
+ npm install -g @tienne/gestalt
139
+ ```
136
140
 
137
141
  ```json
138
142
  {
139
143
  "mcpServers": {
140
144
  "gestalt": {
141
- "command": "npx",
142
- "args": ["-y", "@tienne/gestalt"]
145
+ "command": "gestalt",
146
+ "args": ["serve"]
143
147
  }
144
148
  }
145
149
  }
@@ -147,12 +151,15 @@ Add this to your `settings.json` (or `claude_desktop_config.json`) and restart:
147
151
 
148
152
  MCP tools are available immediately after restart. Slash commands require the plugin or manual skills setup.
149
153
 
154
+ `npx -y @tienne/gestalt` works too, but read [Startup timeouts](#startup-timeouts) first — the desktop app is the setup most likely to hit them.
155
+
150
156
  ---
151
157
 
152
158
  ### Option 3: Claude Code CLI
153
159
 
154
160
  ```bash
155
- claude mcp add gestalt -- npx -y @tienne/gestalt
161
+ npm install -g @tienne/gestalt
162
+ claude mcp add gestalt -- gestalt serve
156
163
  ```
157
164
 
158
165
  Or add directly to `~/.claude/settings.json`:
@@ -161,8 +168,8 @@ Or add directly to `~/.claude/settings.json`:
161
168
  {
162
169
  "mcpServers": {
163
170
  "gestalt": {
164
- "command": "npx",
165
- "args": ["-y", "@tienne/gestalt"]
171
+ "command": "gestalt",
172
+ "args": ["serve"]
166
173
  }
167
174
  }
168
175
  }
@@ -170,6 +177,26 @@ Or add directly to `~/.claude/settings.json`:
170
177
 
171
178
  ---
172
179
 
180
+ ### Startup timeouts
181
+
182
+ If the server connects sometimes and reports `Connection closed` other times, the cause is almost always `npx`, not Gestalt. Three things are worth knowing.
183
+
184
+ **npx reaches the registry on every start.** Pinning an exact version does not change that. Measured against npm: 1.9s on a warm cache, 20s on a cold one, and a 70s hang before failing when the registry is unreachable. Claude Code gives a stdio server 30s to answer `initialize`, so the cold and offline cases both surface as a closed connection. Installing the package globally and calling `gestalt serve` skips all of it.
185
+
186
+ **GUI-launched sessions have a different PATH.** Anything started outside a terminal — the desktop app, a launcher, launchd — inherits a PATH with no version manager on it, so `npx` is not found and the server dies instantly. Give `command` an absolute path, or set `env.PATH` on the server entry.
187
+
188
+ **`startup_timeout_sec` does nothing in Claude Code.** That key belongs to Codex. Claude Code reads the `MCP_TIMEOUT` environment variable (milliseconds) instead, so it goes in `settings.json`, where it lands in the Claude Code process:
189
+
190
+ ```json
191
+ {
192
+ "env": { "MCP_TIMEOUT": "180000" }
193
+ }
194
+ ```
195
+
196
+ The plugin install (Option 1) already handles the first two through `scripts/mcp-serve.sh`: it finds Node under nvm, fnm, Volta, or Homebrew, prefers a globally installed `gestalt`, and otherwise resolves the pinned version from the npm cache, falling back to the network only when the cache misses.
197
+
198
+ ---
199
+
173
200
  ### Option 4: OpenAI Codex Plugin
174
201
 
175
202
  Bundles the MCP server and all 19 workflow skills, the same way the Claude Code plugin does.
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.72.0",
3
+ "version": "0.72.2",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -72,7 +72,6 @@
72
72
  |---|---|
73
73
  | 규칙·정의가 적힌 문서 | 기준 문서 |
74
74
  | 비교 대상이 되는 수치 | 기준값 (목표 대비, 전기 대비 등 — impact-writer 용법) |
75
- | 여러 사본 중 원본 하나 | 정본 |
76
75
  | 조회·대조용 참조 자료 | 참조 기준 |
77
76
  | 데이터가 처음 만들어지는 곳 | 원천 데이터 |
78
77
  | 값을 담고 있는 데이터 자체 | 기준 데이터 |
@@ -85,6 +84,8 @@
85
84
  "룰북과 에이전트 문서가 갈라졌는지 본다"가 "룰북 기준 문서와…"보다 낫다.
86
85
  명사를 고르기 전에 동사로 풀 수 있는지 먼저 본다 — "A가 기준이다", "A를 기준으로 삼는다".
87
86
 
87
+ **여러 사본 중 원본 하나를 가리킬 때가 특히 그렇다.** 이 자리에 명사를 붙이면 뭉개진다. "이 파일만 읽는다", "고칠 일이 있으면 여기를 고친다", "표와 본문이 어긋나 보이면 본문을 따른다"처럼 적는다.
88
+
88
89
  ### 음차를 옮길 때 — 한 단어로 정하지 않는다
89
90
 
90
91
  같은 영어 단어라도 가리키는 게 다르면 다른 말이 된다. 대체어 하나를 정해놓고 전부 치환하면
@@ -98,6 +99,23 @@
98
99
  | layer | 동시에 처리되는 한 덩어리 | 묶음 |
99
100
  | layer | 순서대로 밟는 구간 | 단계 |
100
101
  | layer | 책임이 갈리는 구분 | 역할 |
102
+ | surface | 에이전트나 사람이 접근하는 창구 | 그 이름을 부른다 (CLI, MCP, 웹) |
103
+ | surface | 그 셋을 아울러야 할 때 | 호출하는 쪽 |
104
+ | surface | 이름 붙이기 애매한 접근 지점 | 자리 |
105
+ | surface | 값이 찍히는 화면 | 화면 |
106
+ | surface | 사용자에게 나가는 응답 | 사용자에게 보이는 / 나가는 |
107
+ | surface | 노출 영역 전체를 개념으로 부를 때 | 사용자 노출 |
108
+ | surface | 탐지기가 걸리는 넓이 | 걸리는 범위 |
109
+ | deep | 게슈탈트 용어를 써도 되는 곳 | 게슈탈트 내부 |
110
+ | deep | LLM 지시 프롬프트를 가리킬 때 | LLM에게만 가는 프롬프트 |
111
+
112
+ **이 표는 한국어 산문에서 음차로 쓴 자리만 다룬다.** 세 가지는 대상이 아니다.
113
+
114
+ - **코드 식별자.** `sanitizeSurfaceContext`, `DEEP_PROMPT_KEYS`, `gateway`는 그대로 둔다. 심볼을 바꾸면 호출부가 전부 딸려 온다.
115
+ - **그 도메인의 정착 용어.** 디자인 시스템의 `surface`와 `onSurface`(Material Design 색 토큰), CI의 gate, API gateway가 그렇다. **이 파일은 플러그인으로 배포돼 다른 레포에서도 읽히므로** 프론트엔드 레포에서 색 토큰을 "표면"으로 옮기라는 말이 되지 않게 조심한다.
116
+ - **이미 나간 CHANGELOG와 릴리즈 노트.** 그때 낸 문장이 아니게 된다.
117
+
118
+ **대비쌍은 짝을 함께 옮긴다.** `surface`와 `deep`처럼 둘이 맞물려 뜻을 이루는 말은 한쪽만 풀면 남은 쪽이 무엇의 반대인지 가리킬 데를 잃는다. 실제로 "표면"만 걷었다가 "심층"이 짝 없이 남아 한 문단 안에서 이름이 둘로 갈렸다.
101
119
 
102
120
  **일괄 치환은 조사를 깨뜨린다.** 받침 유무가 바뀌면 뒤따르는 조사도 바뀐다.
103
121
  `레이어라면` → `계층이라면`, `레이어(…)는` → `계층(…)은`. 실제로 이 자리에서 두 번 깨졌다.
@@ -180,7 +180,7 @@ gestalt pr prune --checkouts # 체크아웃 자국까지
180
180
  - 닫힌 PR은 아무것도 안 놓는다.
181
181
  - 체크아웃 자국은 기본으로 안 놓는다. 어느 이력에도 없는 커밋이라 놓으면 영영 사라진다. `--checkouts`로 뜻을 밝혀야 하고 그 PR이 이미 머지되거나 닫혔을 때만 놓는다.
182
182
 
183
- `prune`은 CLI에만 있다. 되돌릴 수 없게 놓는 자리라 도구 표면에 안 뒀다.
183
+ `prune`은 CLI에만 있다. 되돌릴 수 없게 놓는 자리라 MCP 도구로는 안 뒀다.
184
184
 
185
185
  ## 여러 워커로 나눌 때
186
186
 
@@ -134,7 +134,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
134
134
  5. `gh pr view <target>`이 성공하면(GitHub PR이 실제로 존재) → `github`입니다.
135
135
  6. 여기까지 아무 데도 안 걸렸으면(GitHub에도 로컬에도 대응하는 PR이 없는 브랜치나 커밋 범위 리뷰) → `none`입니다. 4.7단계 전체를 건너뜁니다.
136
136
 
137
- **갈리는 건 본문이 정본입니다.** 아래 표는 본문 1~6번을 그대로 펼친 것뿐입니다. 표와 본문이 어긋나 보이면 본문을 따르고 표를 고칩니다. 각 행은 자기 위의 행에 안 걸린 경우입니다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻입니다.
137
+ **아래 표는 본문 1~6번을 그대로 펼친 것뿐입니다.** 표와 본문이 어긋나 보이면 본문을 따르고 표를 고칩니다. 각 행은 자기 위의 행에 안 걸린 경우입니다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻입니다.
138
138
 
139
139
  "로컬 PR 조회" 열은 1번의 두 수단(`show <id>` 또는 위의 가리는 법)이 대상 PR을 찾았는지입니다. 앞쪽은 브랜치를 안 따지고 뒤쪽만 따집니다. `안 봄`은 1번도 3번도 조회할 일이 없어 CLI를 아예 안 부른 경우입니다.
140
140
 
@@ -133,7 +133,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
133
133
 
134
134
  gh 인증이 되고 원격도 있는데 로컬 PR을 지정했으면 1번이 먼저 잡는다. 로컬 PR id 형식이 아닌 값(PR 번호, URL, 브랜치명)은 1번을 그냥 지나친다.
135
135
 
136
- **갈리는 건 본문이 정본이다.** 아래 표는 본문 1~5번을 그대로 펼친 것뿐이다. 표와 본문이 어긋나 보이면 본문을 따르고 표를 고친다. 각 행은 자기 위의 행에 안 걸린 경우다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻이다.
136
+ **아래 표는 본문 1~5번을 그대로 펼친 것뿐이다.** 표와 본문이 어긋나 보이면 본문을 따르고 표를 고친다. 각 행은 자기 위의 행에 안 걸린 경우다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻이다.
137
137
 
138
138
  "로컬 PR 조회" 열은 1번의 두 수단(`show <id>` 또는 위의 가리는 법)이 대상 PR을 찾았는지다. 앞쪽은 브랜치를 안 따지고 뒤쪽만 따진다. `안 봄`은 1번도 3번도 조회할 일이 없어 CLI를 아예 안 부른 경우다.
139
139
 
@@ -2,7 +2,7 @@ import type { PullRequest } from '../../local-pr/types.js';
2
2
  /**
3
3
  * `gestalt pr` — 로컬 PR CLI.
4
4
  *
5
- * 에이전트가 부르는 정본 표면이다. MCP가 없거나 끊긴 런타임에서도 돌아야 해서
5
+ * MCP가 없거나 끊긴 런타임에서도 에이전트가 부를 수 있는 자리다.
6
6
  * 셸만 있으면 되는 이 경로를 먼저 만든다.
7
7
  */
8
8
  export interface PrCommonOptions {
@@ -1,18 +1,18 @@
1
1
  import { GestaltPrinciple } from '../core/types.js';
2
2
  /**
3
- * 표면/심층 분리 (Figure-Ground)에서 표면 문자열을 정의하는 단 한 곳.
3
+ * 사용자에게 보이는 문자열을 정의하는 단 한 곳. 게슈탈트 내부와의 경계다 (Figure-Ground).
4
4
  *
5
5
  * 코드 내부는 게슈탈트 원리(GestaltPrinciple enum)와 에이전트 식별자를 그대로 쓰지만,
6
- * 사용자에게 돌려주는 표면 문자열에는 게슈탈트 용어가 새어 나가면 안 된다.
6
+ * 사용자에게 돌려주는 문자열에는 게슈탈트 용어가 새어 나가면 안 된다.
7
7
  * 이 모듈이 내부 식별자를 평범한 한국어·영어 문구로 잇는 유일한 매핑 지점이다.
8
8
  *
9
- * 심층 (README, docs, LLM 시스템 프롬프트, 내부 타입) 이 모듈을 거치지 않고
10
- * 게슈탈트 어휘를 그대로 유지한다.
9
+ * 게슈탈트 내부(README, docs, LLM 시스템 프롬프트, 내부 타입) 이 모듈을 거치지 않고
10
+ * 어휘를 그대로 유지한다.
11
11
  */
12
12
  export type SurfaceLang = 'ko' | 'en';
13
13
  /**
14
14
  * 원리가 담당하는 단계를 평범한 말로 설명한 문구를 돌려준다.
15
- * currentPrinciple/principleStrategy/gestaltFocus 등 표면 노출 자리에 사용한다.
15
+ * currentPrinciple/principleStrategy/gestaltFocus 등 사용자에게 나가는 자리에 쓴다.
16
16
  * 알 수 없는 값('next' 등)은 중립적 기본 문구로 대체한다.
17
17
  */
18
18
  export declare function getStageLabel(principle: GestaltPrinciple | string, lang?: SurfaceLang): string;
@@ -27,27 +27,27 @@ export declare function getAgentDisplayName(agentName: string, lang?: SurfaceLan
27
27
  export declare function toDisplayAgentNames(agentNames: string[], lang?: SurfaceLang): string[];
28
28
  export declare function getConsistencyHint(lang?: SurfaceLang): string;
29
29
  /**
30
- * 표면 문자열에 절대 나타나면 안 되는 게슈탈트 금지어.
31
- * 회귀 테스트(LeakTest)가 이 목록으로 사용자 표면 응답을 검사한다.
30
+ * 사용자에게 나가는 문자열에 절대 나타나면 안 되는 게슈탈트 금지어.
31
+ * 회귀 테스트(LeakTest)가 이 목록으로 사용자에게 나가는 응답을 검사한다.
32
32
  * 원리 이름 6종(figure-ground는 두 표기 모두)만 담는다 — 에이전트 접미사(completer 등)는
33
33
  * 게슈탈트 용어가 아니므로 중립 표시 이름에 그대로 쓸 수 있어 제외한다.
34
34
  */
35
35
  export declare const BANNED_SURFACE_TERMS: readonly string[];
36
36
  /**
37
- * MCP 도구 응답에서 심층 쪽(LLM 지시 프롬프트) 필드 키.
38
- * 이 필드들은 게슈탈트 어휘를 담은 채 유지되므로 표면 누수 검사 대상에서 제외한다.
37
+ * MCP 도구 응답에서 LLM에게만 가는 프롬프트 필드 키.
38
+ * 이 필드들은 게슈탈트 어휘를 담은 채 유지되므로 누수 검사 대상에서 제외한다.
39
39
  */
40
40
  export declare const DEEP_PROMPT_KEYS: readonly string[];
41
41
  /**
42
42
  * 도구 응답에 통째로 실리는 컨텍스트 객체(gestaltContext/specContext/executeContext)에서
43
- * 게슈탈트 용어가 새는 메타 필드를 중립적 표면 값으로 치환한다.
43
+ * 게슈탈트 용어가 새는 메타 필드를 중립적인 값으로 치환한다.
44
44
  *
45
45
  * - currentPrinciple(원리 enum 값) → currentStage(평범한 단계 설명)
46
- * - principleStrategy(원리 용어가 박힌 전략 문구) → 표면에서 제거 (프롬프트에 이미 포함)
46
+ * - principleStrategy(원리 용어가 박힌 전략 문구) → 사용자 응답에서 제거 (프롬프트에 이미 포함)
47
47
  * - activeAgents(에이전트 식별자) → 중립적 표시 이름
48
48
  * - allRounds[].gestaltFocus(원리 enum 값) → stage(평범한 단계 설명)
49
49
  *
50
- * 심층 프롬프트 필드(systemPrompt 등)와 나머지 필드는 그대로 둔다.
50
+ * LLM에게만 가는 프롬프트 필드(systemPrompt 등)와 나머지 필드는 그대로 둔다.
51
51
  */
52
52
  export declare function sanitizeSurfaceContext<T>(ctx: T, lang?: SurfaceLang): T;
53
53
  //# sourceMappingURL=surface-labels.d.ts.map
@@ -39,7 +39,7 @@ const AGENT_DISPLAY_NAMES = {
39
39
  };
40
40
  /**
41
41
  * 원리가 담당하는 단계를 평범한 말로 설명한 문구를 돌려준다.
42
- * currentPrinciple/principleStrategy/gestaltFocus 등 표면 노출 자리에 사용한다.
42
+ * currentPrinciple/principleStrategy/gestaltFocus 등 사용자에게 나가는 자리에 쓴다.
43
43
  * 알 수 없는 값('next' 등)은 중립적 기본 문구로 대체한다.
44
44
  */
45
45
  export function getStageLabel(principle, lang = 'ko') {
@@ -62,7 +62,7 @@ export function toDisplayAgentNames(agentNames, lang = 'ko') {
62
62
  return agentNames.map((name) => getAgentDisplayName(name, lang));
63
63
  }
64
64
  /**
65
- * 실행 단계에서 caller에게 주는 "일관성 유지" 힌트의 평범한 표면 문구.
65
+ * 실행 단계에서 caller에게 주는 "일관성 유지" 힌트로 사용자에게 보이는 평범한 문구.
66
66
  * Similarity 원리를 노출하던 similarityStrategy 필드를 이 문구로 치환한다.
67
67
  */
68
68
  const CONSISTENCY_HINT = {
@@ -73,8 +73,8 @@ export function getConsistencyHint(lang = 'ko') {
73
73
  return CONSISTENCY_HINT[lang];
74
74
  }
75
75
  /**
76
- * 표면 문자열에 절대 나타나면 안 되는 게슈탈트 금지어.
77
- * 회귀 테스트(LeakTest)가 이 목록으로 사용자 표면 응답을 검사한다.
76
+ * 사용자에게 나가는 문자열에 절대 나타나면 안 되는 게슈탈트 금지어.
77
+ * 회귀 테스트(LeakTest)가 이 목록으로 사용자에게 나가는 응답을 검사한다.
78
78
  * 원리 이름 6종(figure-ground는 두 표기 모두)만 담는다 — 에이전트 접미사(completer 등)는
79
79
  * 게슈탈트 용어가 아니므로 중립 표시 이름에 그대로 쓸 수 있어 제외한다.
80
80
  */
@@ -88,8 +88,8 @@ export const BANNED_SURFACE_TERMS = [
88
88
  'gestalt',
89
89
  ];
90
90
  /**
91
- * MCP 도구 응답에서 심층 쪽(LLM 지시 프롬프트) 필드 키.
92
- * 이 필드들은 게슈탈트 어휘를 담은 채 유지되므로 표면 누수 검사 대상에서 제외한다.
91
+ * MCP 도구 응답에서 LLM에게만 가는 프롬프트 필드 키.
92
+ * 이 필드들은 게슈탈트 어휘를 담은 채 유지되므로 누수 검사 대상에서 제외한다.
93
93
  */
94
94
  export const DEEP_PROMPT_KEYS = [
95
95
  'systemPrompt',
@@ -102,14 +102,14 @@ export const DEEP_PROMPT_KEYS = [
102
102
  ];
103
103
  /**
104
104
  * 도구 응답에 통째로 실리는 컨텍스트 객체(gestaltContext/specContext/executeContext)에서
105
- * 게슈탈트 용어가 새는 메타 필드를 중립적 표면 값으로 치환한다.
105
+ * 게슈탈트 용어가 새는 메타 필드를 중립적인 값으로 치환한다.
106
106
  *
107
107
  * - currentPrinciple(원리 enum 값) → currentStage(평범한 단계 설명)
108
- * - principleStrategy(원리 용어가 박힌 전략 문구) → 표면에서 제거 (프롬프트에 이미 포함)
108
+ * - principleStrategy(원리 용어가 박힌 전략 문구) → 사용자 응답에서 제거 (프롬프트에 이미 포함)
109
109
  * - activeAgents(에이전트 식별자) → 중립적 표시 이름
110
110
  * - allRounds[].gestaltFocus(원리 enum 값) → stage(평범한 단계 설명)
111
111
  *
112
- * 심층 프롬프트 필드(systemPrompt 등)와 나머지 필드는 그대로 둔다.
112
+ * LLM에게만 가는 프롬프트 필드(systemPrompt 등)와 나머지 필드는 그대로 둔다.
113
113
  */
114
114
  export function sanitizeSurfaceContext(ctx, lang = 'ko') {
115
115
  if (!ctx || typeof ctx !== 'object')
@@ -58,7 +58,7 @@ function proseOnly(text, options = {}) {
58
58
  * 길이도 자른다 — D-9의 (?:\S+\s+)? 처럼 임의 토큰을 삼키는 탐지기가 있어서
59
59
  * 매치 하나가 수백 자로 늘어날 수 있다.
60
60
  *
61
- * 어투 탐지기와 맞춤법 검사가 같이 지나는 자리에 둔다. 한쪽만 누르면 표면이 넓은
61
+ * 어투 탐지기와 맞춤법 검사가 같이 지나는 자리에 둔다. 한쪽만 누르면 걸리는 범위가 넓은
62
62
  * 쪽이 그대로 새는데, 실제로 맞춤법만 누른 판이 그랬다.
63
63
  */
64
64
  function clampSample(sample) {
@@ -3,16 +3,16 @@ import type { Actor, Comment, PullRequest, ReviewVerdict } from './types.js';
3
3
  /**
4
4
  * 로컬 PR의 판단 규칙.
5
5
  *
6
- * 표면이 셋(CLI, MCP,)이고 이 규칙을 산문으로 옮겨 적는 스킬 문서가 셋 더 있다.
7
- * 규칙을 표면마다 다시 구현하면 하나가 반드시 뒤처진다 — 실제로 미해결 수를 스레드로
6
+ * CLI MCP셋이 이 규칙을 쓴다. 산문으로 옮겨 적는 스킬 문서도 셋 더 있다.
7
+ * 규칙을 호출하는 쪽마다 다시 구현하면 하나가 반드시 뒤처진다 — 실제로 미해결 수를 스레드로
8
8
  * 고친 뒤에도 웹 UI만 옛 계산으로 남아 같은 PR이 CLI에서 2, 웹에서 4로 보였다.
9
- * 표면은 여기서 값을 가져다 쓰고 스스로 세지 않는다.
9
+ * 호출하는 쪽은 여기서 값을 가져다 쓰고 스스로 세지 않는다.
10
10
  */
11
11
  /**
12
12
  * 아직 안 닫힌 코멘트.
13
13
  *
14
14
  * 미해결을 가르는 술어가 여기 하나뿐이다. 아래 두 함수도 이 목록에서 나온다 —
15
- * 표면이 헤아릴 때와 늘어놓을 때를 따로 계산하면 같은 화면에서 수가 갈린다.
15
+ * 호출하는 쪽이 헤아릴 때와 늘어놓을 때를 따로 계산하면 같은 화면에서 수가 갈린다.
16
16
  * 실제로 `pr show`가 머리글에 스레드 수를, 바로 아래 목록에 코멘트 수를 찍어
17
17
  * "미해결 1"이라고 해놓고 세 줄을 늘어놓았다.
18
18
  */
@@ -24,7 +24,7 @@ export interface Thread {
24
24
  /** 그중 안 닫힌 것만. 전부 닫혔으면 빈 배열이다 */
25
25
  unresolved: Comment[];
26
26
  /**
27
- * 표면이 한 줄로 접을 때 보여줄 코멘트. 안 닫힌 것 중 첫 번째다.
27
+ * 한 줄로 접을 때 보여줄 코멘트. 안 닫힌 것 중 첫 번째다.
28
28
  * 전부 닫혔으면 맨 처음 코멘트가 온다.
29
29
  */
30
30
  head: Comment;
@@ -43,7 +43,7 @@ export declare function threadsOf(pr: PullRequest): Thread[];
43
43
  /** 안 닫힌 스레드 하나 */
44
44
  export interface OpenThread {
45
45
  /**
46
- * 표면이 한 줄로 접을 때 보여줄 코멘트. 스레드에서 아직 안 닫힌 것 중 첫 번째다.
46
+ * 한 줄로 접을 때 보여줄 코멘트. 스레드에서 아직 안 닫힌 것 중 첫 번째다.
47
47
  * 뿌리가 닫히고 답글만 열려 있으면 그 답글이 여기 온다.
48
48
  */
49
49
  root: Comment;
@@ -51,7 +51,7 @@ export interface OpenThread {
51
51
  comments: Comment[];
52
52
  }
53
53
  export declare function openThreads(pr: PullRequest): OpenThread[];
54
- /** 안 닫힌 스레드 수. 표면의 "미해결 N"이 전부 이 값이다 */
54
+ /** 안 닫힌 스레드 수. 화면에 찍히는 "미해결 N"이 전부 이 값이다 */
55
55
  export declare function unresolvedCount(pr: PullRequest): number;
56
56
  /**
57
57
  * 누가 했는지.
@@ -1 +1 @@
1
- {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/local-pr/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7E;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,EAAE,CAE7D;AAED,0BAA0B;AAC1B,MAAM,WAAW,MAAM;IACrB,gCAAgC;IAChC,GAAG,EAAE,OAAO,EAAE,CAAC;IACf,iCAAiC;IACjC,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,EAAE,CAYnD;AAED,kBAAkB;AAClB,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,WAAW,GAAG,UAAU,EAAE,CAIzD;AAED,wCAAwC;AACxC,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAE,KAAqB,GAAG,KAAK,CAEtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EAAE,EACrB,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAIT;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EAAE,EACrB,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAAC,aAAa,EAAE,SAAS,GAAG,iBAAiB,CAAC,CAEvD"}
1
+ {"version":3,"file":"policy.d.ts","sourceRoot":"","sources":["../../../src/local-pr/policy.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE7E;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,WAAW,GAAG,OAAO,EAAE,CAE7D;AAED,0BAA0B;AAC1B,MAAM,WAAW,MAAM;IACrB,gCAAgC;IAChC,GAAG,EAAE,OAAO,EAAE,CAAC;IACf,iCAAiC;IACjC,UAAU,EAAE,OAAO,EAAE,CAAC;IACtB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,+CAA+C;IAC/C,IAAI,EAAE,OAAO,CAAC;CACf;AAED;;;;;;;GAOG;AACH,wBAAgB,SAAS,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,EAAE,CAYnD;AAED,kBAAkB;AAClB,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,IAAI,EAAE,OAAO,CAAC;IACd,oCAAoC;IACpC,QAAQ,EAAE,OAAO,EAAE,CAAC;CACrB;AAED,wBAAgB,WAAW,CAAC,EAAE,EAAE,WAAW,GAAG,UAAU,EAAE,CAIzD;AAED,4CAA4C;AAC5C,wBAAgB,eAAe,CAAC,EAAE,EAAE,WAAW,GAAG,MAAM,CAEvD;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,QAAQ,GAAE,KAAqB,GAAG,KAAK,CAEtF;AAED;;;;;;;;;GASG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,WAAW,EAAE,EACrB,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAIT;AAED,uDAAuD;AACvD,wBAAgB,gBAAgB,CAC9B,MAAM,EAAE,WAAW,EAAE,EACrB,iBAAiB,CAAC,EAAE,iBAAiB,GACpC,OAAO,CAAC,aAAa,EAAE,SAAS,GAAG,iBAAiB,CAAC,CAEvD"}
@@ -1,16 +1,16 @@
1
1
  /**
2
2
  * 로컬 PR의 판단 규칙.
3
3
  *
4
- * 표면이 셋(CLI, MCP,)이고 이 규칙을 산문으로 옮겨 적는 스킬 문서가 셋 더 있다.
5
- * 규칙을 표면마다 다시 구현하면 하나가 반드시 뒤처진다 — 실제로 미해결 수를 스레드로
4
+ * CLI MCP셋이 이 규칙을 쓴다. 산문으로 옮겨 적는 스킬 문서도 셋 더 있다.
5
+ * 규칙을 호출하는 쪽마다 다시 구현하면 하나가 반드시 뒤처진다 — 실제로 미해결 수를 스레드로
6
6
  * 고친 뒤에도 웹 UI만 옛 계산으로 남아 같은 PR이 CLI에서 2, 웹에서 4로 보였다.
7
- * 표면은 여기서 값을 가져다 쓰고 스스로 세지 않는다.
7
+ * 호출하는 쪽은 여기서 값을 가져다 쓰고 스스로 세지 않는다.
8
8
  */
9
9
  /**
10
10
  * 아직 안 닫힌 코멘트.
11
11
  *
12
12
  * 미해결을 가르는 술어가 여기 하나뿐이다. 아래 두 함수도 이 목록에서 나온다 —
13
- * 표면이 헤아릴 때와 늘어놓을 때를 따로 계산하면 같은 화면에서 수가 갈린다.
13
+ * 호출하는 쪽이 헤아릴 때와 늘어놓을 때를 따로 계산하면 같은 화면에서 수가 갈린다.
14
14
  * 실제로 `pr show`가 머리글에 스레드 수를, 바로 아래 목록에 코멘트 수를 찍어
15
15
  * "미해결 1"이라고 해놓고 세 줄을 늘어놓았다.
16
16
  */
@@ -44,7 +44,7 @@ export function openThreads(pr) {
44
44
  .filter((t) => t.open)
45
45
  .map((t) => ({ root: t.head, comments: t.unresolved }));
46
46
  }
47
- /** 안 닫힌 스레드 수. 표면의 "미해결 N"이 전부 이 값이다 */
47
+ /** 안 닫힌 스레드 수. 화면에 찍히는 "미해결 N"이 전부 이 값이다 */
48
48
  export function unresolvedCount(pr) {
49
49
  return openThreads(pr).length;
50
50
  }
@@ -1 +1 @@
1
- {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/local-pr/policy.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAe;IAChD,OAAO,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAChD,CAAC;AAiBD;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,EAAe;IACvC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC9C,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;YAC5B,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAClD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IAC1F,CAAC,CAAC,CAAC;AACL,CAAC;AAaD,MAAM,UAAU,WAAW,CAAC,EAAe;IACzC,OAAO,SAAS,CAAC,EAAE,CAAC;SACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,wCAAwC;AACxC,MAAM,UAAU,eAAe,CAAC,EAAe;IAC7C,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAiB,EAAE,WAAkB,aAAa;IAC7E,OAAO,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,QAAQ,CAAC;AAC9D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAqB,EACrB,iBAAqC;IAErC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC;IACzF,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IACjF,OAAO,CAAC,SAAS,IAAI,CAAC,gBAAgB,CAAC;AACzC,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,gBAAgB,CAC9B,MAAqB,EACrB,iBAAqC;IAErC,OAAO,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC;AACxF,CAAC"}
1
+ {"version":3,"file":"policy.js","sourceRoot":"","sources":["../../../src/local-pr/policy.ts"],"names":[],"mappings":"AAGA;;;;;;;GAOG;AAEH;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB,CAAC,EAAe;IAChD,OAAO,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;AAChD,CAAC;AAiBD;;;;;;;GAOG;AACH,MAAM,UAAU,SAAS,CAAC,EAAe;IACvC,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAqB,CAAC;IAC9C,KAAK,MAAM,OAAO,IAAI,EAAE,CAAC,QAAQ,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,MAAM;YAAE,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;;YAC5B,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACjD,CAAC;IAED,OAAO,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QACxC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC;QAClD,OAAO,EAAE,GAAG,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,CAAE,EAAE,IAAI,EAAE,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;IAC1F,CAAC,CAAC,CAAC;AACL,CAAC;AAaD,MAAM,UAAU,WAAW,CAAC,EAAe;IACzC,OAAO,SAAS,CAAC,EAAE,CAAC;SACjB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;SACrB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED,4CAA4C;AAC5C,MAAM,UAAU,eAAe,CAAC,EAAe;IAC7C,OAAO,WAAW,CAAC,EAAE,CAAC,CAAC,MAAM,CAAC;AAChC,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,QAAiB,EAAE,WAAkB,aAAa;IAC7E,OAAO,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,IAAI,QAAQ,CAAC;AAC9D,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,mBAAmB,CACjC,MAAqB,EACrB,iBAAqC;IAErC,MAAM,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,UAAU,IAAI,CAAC,CAAC,QAAQ,KAAK,MAAM,CAAC,CAAC;IACzF,MAAM,gBAAgB,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAAC,iBAAiB,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IACjF,OAAO,CAAC,SAAS,IAAI,CAAC,gBAAgB,CAAC;AACzC,CAAC;AAED,uDAAuD;AACvD,MAAM,UAAU,gBAAgB,CAC9B,MAAqB,EACrB,iBAAqC;IAErC,OAAO,mBAAmB,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,iBAAiB,CAAC;AACxF,CAAC"}
@@ -18,7 +18,7 @@ export declare class PrWebEngine {
18
18
  * 프로세스 하나가 서버를 하나만 띄우도록 공유하는 인스턴스.
19
19
  *
20
20
  * 아직 부르는 자리가 없다. `pr serve`는 `new PrWebEngine()`으로 직접 만들고 MCP는
21
- * 이 모듈을 안 가져간다. 여러 표면이 서버를 공유해야 할 때를 위해 남겨 둔 진입점이다.
21
+ * 이 모듈을 안 가져간다. CLI와 MCP가 같은 서버 인스턴스를 공유해야 할 때를 위해 남겨 둔 진입점이다.
22
22
  */
23
23
  export declare function getPrWebEngine(): PrWebEngine;
24
24
  //# sourceMappingURL=engine.d.ts.map
@@ -93,7 +93,7 @@ let _instance = null;
93
93
  * 프로세스 하나가 서버를 하나만 띄우도록 공유하는 인스턴스.
94
94
  *
95
95
  * 아직 부르는 자리가 없다. `pr serve`는 `new PrWebEngine()`으로 직접 만들고 MCP는
96
- * 이 모듈을 안 가져간다. 여러 표면이 서버를 공유해야 할 때를 위해 남겨 둔 진입점이다.
96
+ * 이 모듈을 안 가져간다. CLI와 MCP가 같은 서버 인스턴스를 공유해야 할 때를 위해 남겨 둔 진입점이다.
97
97
  */
98
98
  export function getPrWebEngine() {
99
99
  if (!_instance)
@@ -111,7 +111,7 @@ export function errorKind(exitCode) {
111
111
  * update-ref를 쓰고 워크트리를 만든다. `close`는 ref를 지운다. `checkout_remove --force`는
112
112
  * 경로를 재귀로 지운다. 인자 하나를 바꿔 부르면 이 머신의 아무 git 레포나 그 대상이
113
113
  * 된다. 레지스트리가 `pr create`의 등록 경로를 막은 것과 같은 축인데, 정작 git을
114
- * 변형하는 표면이 열려 있었다.
114
+ * 변형하는 자리가 열려 있었다.
115
115
  *
116
116
  * 그래서 지금 자리와 **같은 레포**일 때만 받는다. 판정은 공용 git 디렉토리로 하므로
117
117
  * 워크트리와 하위 디렉토리는 그대로 통과한다 — 이 인자가 원래 있는 이유가 그거다.
@@ -257,7 +257,7 @@ function actorOfAgent(name) {
257
257
  * PR은 이벤트 소싱이라 그렇게 붙은 중복은 지울 수 없고 사람이 손으로 닫아야 한다.
258
258
  * 그래서 어디까지 썼는지를 PR 자신에게도 남긴다 — 재기동해도 여기서 되짚는다.
259
259
  *
260
- * 코멘트의 `marker` 필드에 넣는다. 본문에 실었더니 어느 표면에서도 보이지 않았다
260
+ * 코멘트의 `marker` 필드에 넣는다. 본문에 실었더니 CLI에서도 웹에서도 그대로 보였다
261
261
  * 웹은 본문을 이스케이프해서 `<!-- ... -->`를 화면에 그대로 찍고 CLI는 평문으로
262
262
  * 내보낸다. 사람이 읽을 이유가 없는 해시 줄이 리뷰 코멘트마다 붙었다.
263
263
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tienne/gestalt",
3
- "version": "0.72.0",
3
+ "version": "0.72.2",
4
4
  "description": "TypeScript AI Development Harness - Gestalt psychology-driven requirement clarification",
5
5
  "type": "module",
6
6
  "main": "./dist/src/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gestalt",
3
- "version": "0.72.0",
3
+ "version": "0.72.2",
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", "serve"],
5
+ "args": ["-y", "@tienne/gestalt@0.72.2", "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", "serve"],
5
+ "args": ["-y", "@tienne/gestalt@0.72.2", "serve"],
6
6
  "startup_timeout_sec": 180,
7
7
  "tool_timeout_sec": 900
8
8
  }
@@ -72,7 +72,6 @@
72
72
  |---|---|
73
73
  | 규칙·정의가 적힌 문서 | 기준 문서 |
74
74
  | 비교 대상이 되는 수치 | 기준값 (목표 대비, 전기 대비 등 — impact-writer 용법) |
75
- | 여러 사본 중 원본 하나 | 정본 |
76
75
  | 조회·대조용 참조 자료 | 참조 기준 |
77
76
  | 데이터가 처음 만들어지는 곳 | 원천 데이터 |
78
77
  | 값을 담고 있는 데이터 자체 | 기준 데이터 |
@@ -85,6 +84,8 @@
85
84
  "룰북과 에이전트 문서가 갈라졌는지 본다"가 "룰북 기준 문서와…"보다 낫다.
86
85
  명사를 고르기 전에 동사로 풀 수 있는지 먼저 본다 — "A가 기준이다", "A를 기준으로 삼는다".
87
86
 
87
+ **여러 사본 중 원본 하나를 가리킬 때가 특히 그렇다.** 이 자리에 명사를 붙이면 뭉개진다. "이 파일만 읽는다", "고칠 일이 있으면 여기를 고친다", "표와 본문이 어긋나 보이면 본문을 따른다"처럼 적는다.
88
+
88
89
  ### 음차를 옮길 때 — 한 단어로 정하지 않는다
89
90
 
90
91
  같은 영어 단어라도 가리키는 게 다르면 다른 말이 된다. 대체어 하나를 정해놓고 전부 치환하면
@@ -98,6 +99,23 @@
98
99
  | layer | 동시에 처리되는 한 덩어리 | 묶음 |
99
100
  | layer | 순서대로 밟는 구간 | 단계 |
100
101
  | layer | 책임이 갈리는 구분 | 역할 |
102
+ | surface | 에이전트나 사람이 접근하는 창구 | 그 이름을 부른다 (CLI, MCP, 웹) |
103
+ | surface | 그 셋을 아울러야 할 때 | 호출하는 쪽 |
104
+ | surface | 이름 붙이기 애매한 접근 지점 | 자리 |
105
+ | surface | 값이 찍히는 화면 | 화면 |
106
+ | surface | 사용자에게 나가는 응답 | 사용자에게 보이는 / 나가는 |
107
+ | surface | 노출 영역 전체를 개념으로 부를 때 | 사용자 노출 |
108
+ | surface | 탐지기가 걸리는 넓이 | 걸리는 범위 |
109
+ | deep | 게슈탈트 용어를 써도 되는 곳 | 게슈탈트 내부 |
110
+ | deep | LLM 지시 프롬프트를 가리킬 때 | LLM에게만 가는 프롬프트 |
111
+
112
+ **이 표는 한국어 산문에서 음차로 쓴 자리만 다룬다.** 세 가지는 대상이 아니다.
113
+
114
+ - **코드 식별자.** `sanitizeSurfaceContext`, `DEEP_PROMPT_KEYS`, `gateway`는 그대로 둔다. 심볼을 바꾸면 호출부가 전부 딸려 온다.
115
+ - **그 도메인의 정착 용어.** 디자인 시스템의 `surface`와 `onSurface`(Material Design 색 토큰), CI의 gate, API gateway가 그렇다. **이 파일은 플러그인으로 배포돼 다른 레포에서도 읽히므로** 프론트엔드 레포에서 색 토큰을 "표면"으로 옮기라는 말이 되지 않게 조심한다.
116
+ - **이미 나간 CHANGELOG와 릴리즈 노트.** 그때 낸 문장이 아니게 된다.
117
+
118
+ **대비쌍은 짝을 함께 옮긴다.** `surface`와 `deep`처럼 둘이 맞물려 뜻을 이루는 말은 한쪽만 풀면 남은 쪽이 무엇의 반대인지 가리킬 데를 잃는다. 실제로 "표면"만 걷었다가 "심층"이 짝 없이 남아 한 문단 안에서 이름이 둘로 갈렸다.
101
119
 
102
120
  **일괄 치환은 조사를 깨뜨린다.** 받침 유무가 바뀌면 뒤따르는 조사도 바뀐다.
103
121
  `레이어라면` → `계층이라면`, `레이어(…)는` → `계층(…)은`. 실제로 이 자리에서 두 번 깨졌다.
@@ -180,7 +180,7 @@ gestalt pr prune --checkouts # 체크아웃 자국까지
180
180
  - 닫힌 PR은 아무것도 안 놓는다.
181
181
  - 체크아웃 자국은 기본으로 안 놓는다. 어느 이력에도 없는 커밋이라 놓으면 영영 사라진다. `--checkouts`로 뜻을 밝혀야 하고 그 PR이 이미 머지되거나 닫혔을 때만 놓는다.
182
182
 
183
- `prune`은 CLI에만 있다. 되돌릴 수 없게 놓는 자리라 도구 표면에 안 뒀다.
183
+ `prune`은 CLI에만 있다. 되돌릴 수 없게 놓는 자리라 MCP 도구로는 안 뒀다.
184
184
 
185
185
  ## 여러 워커로 나눌 때
186
186
 
@@ -134,7 +134,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
134
134
  5. `gh pr view <target>`이 성공하면(GitHub PR이 실제로 존재) → `github`입니다.
135
135
  6. 여기까지 아무 데도 안 걸렸으면(GitHub에도 로컬에도 대응하는 PR이 없는 브랜치나 커밋 범위 리뷰) → `none`입니다. 4.7단계 전체를 건너뜁니다.
136
136
 
137
- **갈리는 건 본문이 정본입니다.** 아래 표는 본문 1~6번을 그대로 펼친 것뿐입니다. 표와 본문이 어긋나 보이면 본문을 따르고 표를 고칩니다. 각 행은 자기 위의 행에 안 걸린 경우입니다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻입니다.
137
+ **아래 표는 본문 1~6번을 그대로 펼친 것뿐입니다.** 표와 본문이 어긋나 보이면 본문을 따르고 표를 고칩니다. 각 행은 자기 위의 행에 안 걸린 경우입니다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻입니다.
138
138
 
139
139
  "로컬 PR 조회" 열은 1번의 두 수단(`show <id>` 또는 위의 가리는 법)이 대상 PR을 찾았는지입니다. 앞쪽은 브랜치를 안 따지고 뒤쪽만 따집니다. `안 봄`은 1번도 3번도 조회할 일이 없어 CLI를 아예 안 부른 경우입니다.
140
140
 
@@ -133,7 +133,7 @@ id를 직접 주면 아래 1번의 첫 수단이 브랜치를 안 따지고 잡
133
133
 
134
134
  gh 인증이 되고 원격도 있는데 로컬 PR을 지정했으면 1번이 먼저 잡는다. 로컬 PR id 형식이 아닌 값(PR 번호, URL, 브랜치명)은 1번을 그냥 지나친다.
135
135
 
136
- **갈리는 건 본문이 정본이다.** 아래 표는 본문 1~5번을 그대로 펼친 것뿐이다. 표와 본문이 어긋나 보이면 본문을 따르고 표를 고친다. 각 행은 자기 위의 행에 안 걸린 경우다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻이다.
136
+ **아래 표는 본문 1~5번을 그대로 펼친 것뿐이다.** 표와 본문이 어긋나 보이면 본문을 따르고 표를 고친다. 각 행은 자기 위의 행에 안 걸린 경우다. "—"는 앞 행에서 이미 갈려 볼 필요가 없다는 뜻이다.
137
137
 
138
138
  "로컬 PR 조회" 열은 1번의 두 수단(`show <id>` 또는 위의 가리는 법)이 대상 PR을 찾았는지다. 앞쪽은 브랜치를 안 따지고 뒤쪽만 따진다. `안 봄`은 1번도 3번도 조회할 일이 없어 CLI를 아예 안 부른 경우다.
139
139