deel-local-cli 1.15.1 → 1.16.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/src/agent/loop.js CHANGED
@@ -5,7 +5,7 @@ import { 그림메시지 } from '../backend/vision.js';
5
5
  import { 어떻게할까 } from '../safety/policy.js';
6
6
  import { toolSchemas, runTool, TOOLS, 파일현황 } from '../tools/index.js';
7
7
  import { isMutating } from '../safety/guard.js';
8
- import { effortFor, tokensFor, fullCap, wasCut, shiftLevel, 자동강도, 인사인가 as 인사말인가 } from './effort.js';
8
+ import { effortFor, tokensFor, fullCap, wasCut, shiftLevel, 자동강도, 천장고르기, 인사인가 as 인사말인가 } from './effort.js';
9
9
  import { 배울전선, 카드고치기, 카드저장꼴, 전선붙이기 } from '../backend/wire.js';
10
10
  import { 살린쓰기 } from './salvage.js';
11
11
  import { 배울것, 길이문제인가 } from '../backend/learn.js';
@@ -407,7 +407,15 @@ export async function* run(session, ctx, userText, { signal = null, 깊이 = 0,
407
407
  * 한 번 정하면 이 턴 안에서는 안 바꾼다. while 바깥에서 정하는 것이 그
408
408
  * 뜻이다 — 걸음마다 흔들리면 그것이 곧 캐시가 매번 새로 엮인다는 말이다.
409
409
  */
410
- const 천장 = session.thinkSet ? session.think : (모드.think ?? session.think);
410
+ /*
411
+ * 모드가 정한 강도는 **가벼운 말에만** 낮춘다 (effort.js 의 천장고르기).
412
+ *
413
+ * 묻기 모드는 low 다. "이 함수 뭐야?" 에는 맞는 값이다. 그런데 무엇이
414
+ * 묻기로 가는지는 낱말이 정하고(route.js), "…설명해줘" 한 마디면 간다.
415
+ * 그래서 파일 열 개를 읽고 경합 조건을 짚어야 하는 일까지 low 로 돌았다 —
416
+ * 생각 블록이 0자로 나가고, 답이 그만큼 얕았다.
417
+ */
418
+ const 천장 = session.thinkSet ? session.think : 천장고르기(모드.think, session.think, userText);
411
419
  const think = 자동강도(userText, 천장, {
412
420
  대화크기: session.messages.length,
413
421
  켜짐: session.autoThink !== false,
@@ -50,6 +50,53 @@ const 확인 = ['Verify'];
50
50
  */
51
51
  const 쪼개기 = ['Task'];
52
52
 
53
+ /*
54
+ * ── 남의 코드를 읽는 법 ─────────────────────────────────────────────────
55
+ *
56
+ * 모드마다 따로 적지 않는다. 따로 적으면 언젠가 한쪽만 고쳐지고, 그때부터
57
+ * 「모드에 따라 읽는 법이 다르다」 는 없던 규칙이 생긴다.
58
+ *
59
+ * 프롬프트에는 **한 번에 한 모드의 글만** 실리므로, 여기 모아 둬도 전선에
60
+ * 나가는 양은 똑같다.
61
+ *
62
+ * ── 왜 이 두 줄인가 ─────────────────────────────────────────────────────
63
+ *
64
+ * 같은 프로젝트에 같은 질문을 두 벌 돌려서 재 봤다. 갈린 자리가 여기였다.
65
+ *
66
+ * 통째로 읽은 쪽: 도구 결과 10개, 평균 14,000자. 한 번은 상한(30,000자)에
67
+ * 걸려 잘렸다. 자른 것은 아무도 안 말해 준다.
68
+ * 좁게 읽은 쪽: 도구 결과 30여 개, 평균 5,000자. 같은 자리를 두 번 읽은
69
+ * 적이 없다.
70
+ *
71
+ * 창에 부어 넣은 양은 앞쪽이 세 배인데, 답이 얕았다. 많이 넣는 것과 많이
72
+ * 아는 것은 다르다 — 앞엣것이 접혀 나가면 안 읽은 것과 같아진다.
73
+ *
74
+ * 두 번째 줄(나란히 부르기)은 값이 아니라 **시간**이다. 읽기 도구는 같이
75
+ * 돌게 되어 있는데(loop.js 의 묶기), 한 개씩 부르면 그 수만큼 왕복이 는다.
76
+ */
77
+ const 읽는법 = [
78
+ '- 남의 코드는 Outline 으로 모양부터 본다. Grep 으로 그 말이 닿는 자리를 다 모은 뒤,',
79
+ ' **짚은 자리 앞뒤만** Read 한다 (offset·limit). 통째로 읽지 마라 — 한 번에 다',
80
+ ' 부어 넣으면 뒤로 갈수록 앞엣것이 접혀 나가고, 잊은 채로 「없다」 고 답하게 된다.',
81
+ '- 서로 상관없는 읽기는 **한 번에 같이 부른다.** 읽기 도구는 나란히 돈다.',
82
+ ' 한 개씩 부르면 그 수만큼 왕복이 늘고, 그게 그대로 사람이 기다리는 시간이다.',
83
+ ];
84
+ const 읽는법짧게 = [
85
+ '- 남의 코드는 Outline → Grep → **짚은 자리 앞뒤만** Read (offset·limit). 통째로 읽지 마라.',
86
+ '- 상관없는 읽기는 한 번에 같이 부른다 — 나란히 돈다.',
87
+ ];
88
+ const 읽는법En = [
89
+ '- For code you did not write, get the shape with Outline. Use Grep to gather every place it touches,',
90
+ ' then Read **only around the lines you found** (offset/limit). Do not read whole files - pour it all',
91
+ ' in at once and the earlier turns fold away, and you answer having forgotten them.',
92
+ '- Fire unrelated reads **together in one message.** Read tools run side by side; one call at a time',
93
+ ' adds that many round trips, and that is the waiting the person feels.',
94
+ ];
95
+ const 읽는법짧게En = [
96
+ '- Code you did not write: Outline -> Grep -> Read **only around what you found** (offset/limit). Never whole files.',
97
+ '- Fire unrelated reads together in one message - they run side by side.',
98
+ ];
99
+
53
100
  import { 언어, 지시말 } from '../i18n/index.js';
54
101
 
55
102
  export const MODES = {
@@ -64,8 +111,8 @@ export const MODES = {
64
111
  auto: {
65
112
  id: 'auto',
66
113
  hintEn: "picks the right mode for what you ask",
67
- sayEn: "You are in **Auto** mode. What comes next is not fixed.\n\n- Work out what kind of job this is first, then do it that way.\n If it is an edit, read before editing. If it is a diagnosis, confirm before concluding.\n If it is an explanation, back it with the files.\n- For a large job, break it into steps with TodoWrite and **finish everything you wrote down.**\n Do not write the list and then ask. If there are several chunks, hand them off with Task —\n a subtask runs in its own window, so yours does not fill up.\n- For code you did not write, look at the shape with Outline first. Do not Read whole files.\n- Making several files: one Write call (files array). Do not call it once per file.\n Several places to edit: one Edit call the same way (edits array).\n- Commands that never end (dev servers, watch) need background: true on Bash. Called plainly\n they die on timeout. After starting one, read its output with Jobs, and end it with Jobs when done.\n- Verify what can be verified — call Verify. Do not call something done that you did not check.\n- Do what the job needs. Do not start work the job did not ask for.",
68
- say짧게En: "**Auto** mode. What comes next is not fixed.\n- Work out what kind of job this is, then do it that way.\n- Large job: break it up with TodoWrite and **finish it all.** Several chunks: hand off with Task.\n- Code you did not write: Outline for the shape first. Do not Read whole files.\n- Several files: one Write (files array). Several edits: one Edit (edits array).\n- Commands that never end (dev server, watch): background: true on Bash. Read with Jobs, end with Jobs.\n- Verify before you finish. Do not call something done that you did not check.",
114
+ sayEn: "You are in **Auto** mode. What comes next is not fixed.\n\n- Work out what kind of job this is first, then do it that way.\n If it is an edit, read before editing. If it is a diagnosis, confirm before concluding.\n If it is an explanation, back it with the files.\n- For a large job, break it into steps with TodoWrite and **finish everything you wrote down.**\n Do not write the list and then ask. If there are several chunks, hand them off with Task —\n a subtask runs in its own window, so yours does not fill up.\n- For code you did not write, get the shape with Outline. Use Grep to gather every place it\n touches, then Read **only around the lines you found** (offset/limit). Do not read whole\n files - pour it all in at once and the earlier turns fold away, and you answer having\n forgotten them.\n- Fire unrelated reads **together in one message.** Read tools run side by side; one call at\n a time adds that many round trips, and that is the waiting the person feels.\n- Making several files: one Write call (files array). Do not call it once per file.\n Several places to edit: one Edit call the same way (edits array).\n- Commands that never end (dev servers, watch) need background: true on Bash. Called plainly\n they die on timeout. After starting one, read its output with Jobs, and end it with Jobs when done.\n- Verify what can be verified — call Verify. Do not call something done that you did not check.\n- Do what the job needs. Do not start work the job did not ask for.",
115
+ say짧게En: "**Auto** mode. What comes next is not fixed.\n- Work out what kind of job this is, then do it that way.\n- Large job: break it up with TodoWrite and **finish it all.** Several chunks: hand off with Task.\n- Code you did not write: Outline -> Grep -> Read **only around what you found** (offset/limit).\n- Fire unrelated reads together in one message - they run side by side.\n- Several files: one Write (files array). Several edits: one Edit (edits array).\n- Commands that never end (dev server, watch): background: true on Bash. Read with Jobs, end with Jobs.\n- Verify before you finish. Do not call something done that you did not check.",
69
116
  name: '종합',
70
117
  en: 'Auto',
71
118
  glyph: '◎',
@@ -78,7 +125,7 @@ export const MODES = {
78
125
  '**종합** 모드다. 무슨 일이 올지 정해져 있지 않다.',
79
126
  '- 무슨 일인지 먼저 가늠하고 그에 맞게 해라.',
80
127
  '- 큰 일이면 TodoWrite 로 쪼개 적고 **다 끝낸다.** 덩이가 여럿이면 Task 로 떼어 준다.',
81
- '- 남의 코드는 Outline 으로 모양부터. 통째로 Read 하지 마라.',
128
+ ...읽는법짧게,
82
129
  '- 파일 여러 개는 Write 한 번에 (files 배열), 고칠 자리 여럿은 Edit 한 번에 (edits 배열).',
83
130
  '- 끝내지 않는 명령(dev 서버·watch)은 Bash 에 background: true. Jobs 로 읽고 끝낸다.',
84
131
  '- 끝내기 전에 Verify. 확인 못 한 것을 됐다고 하지 마라.',
@@ -91,7 +138,7 @@ export const MODES = {
91
138
  ' 설명하는 일이면 근거를 파일에서 대라.',
92
139
  '- 큰 일이면 TodoWrite 로 쪼개 적고 **적은 것을 다 끝낸다.** 적어 놓고 묻지 마라.',
93
140
  ' 덩이가 여럿이면 Task 로 떼어 준다 — 하위는 제 창에서 돌아 네 창이 안 찬다.',
94
- '- 남의 코드는 Outline 으로 모양부터 본다. 통째로 Read 하지 마라.',
141
+ ...읽는법,
95
142
  '- 파일을 여러 개 만들 때는 Write 한 번에 (files 배열). 한 개씩 부르지 마라.',
96
143
  ' 고칠 자리가 여러 군데일 때도 마찬가지로 Edit 한 번에 (edits 배열).',
97
144
  '- 끝나지 않는 명령(dev 서버·watch)은 Bash 에 background: true 를 준다. 그냥 부르면',
@@ -104,8 +151,8 @@ export const MODES = {
104
151
  code: {
105
152
  id: 'code',
106
153
  hintEn: "edits and builds",
107
- sayEn: "This is **implementation**. Follow this order.\n\n1. For code you did not write, look at the **shape first** with Outline. Do not Read whole\n files — a folder through Outline is a fraction of the size.\n Pick the places to change there, then Read **only those files**.\n2. Always Read a file before editing it. The tool refuses an edit to a file you have not read.\n3. Follow the conventions of the surrounding code — naming, error handling, comment density.\n Do not import a new convention. Do what this code already does.\n4. Make and change every file the job needs. Do not touch one file and stop.\n For something new, lay out the folder structure first and create **several files in one Write**\n (files array). Several places to change go in **one Edit** (edits array).\n One call per item adds that many round trips, and minutes go with them.\n5. If the work splits into separate strands, hand a chunk off with Task.\n A subtask runs in its own window and returns only a summary — your window does not fill up.\n6. **Call Verify before you finish.** A file existing and a file working are different things.\n Fix what comes back and call it again. Say \"I could not verify this\" for anything you did not check.\n For things you only learn by running (dev servers, watch), give Bash **background: true**.\n Called plainly they never end and die on timeout. Read output with Jobs, and always end it\n with Jobs when done — otherwise that server keeps holding the port.\n7. When done, say what you changed and why in a line or two. Do not paste the code back.",
108
- say짧게En: "**Implementation.** Follow this order.\n1. Code you did not write: Outline for the shape. Pick the places, then Read **only those files**.\n2. Always Read a file before editing it.\n3. Follow the surrounding conventions. Do not import a new one.\n4. Several files: one Write (files array). Several edits: one Edit (edits array).\n5. Several strands: hand off with Task.\n6. **Verify before you finish.** Fix what comes back and call it again.\n If it must be run, Bash with background: true — called plainly it dies on timeout. Read with Jobs, end with Jobs.\n7. Say what changed and why in a line or two. Do not paste code.",
154
+ sayEn: "This is **implementation**. Follow this order.\n\n1. For code you did not write, look at the **shape first** with Outline - a folder through\n Outline is a fraction of the size. Grep to gather every place the thing you are changing\n touches, then Read **only around the lines you found** (offset/limit). Never whole files.\n Fire unrelated reads **together in one message** - read tools run side by side.\n2. Always Read a file before editing it. The tool refuses an edit to a file you have not read.\n3. Follow the conventions of the surrounding code — naming, error handling, comment density.\n Do not import a new convention. Do what this code already does.\n4. Make and change every file the job needs. Do not touch one file and stop.\n For something new, lay out the folder structure first and create **several files in one Write**\n (files array). Several places to change go in **one Edit** (edits array).\n One call per item adds that many round trips, and minutes go with them.\n5. If the work splits into separate strands, hand a chunk off with Task.\n A subtask runs in its own window and returns only a summary — your window does not fill up.\n6. **Call Verify before you finish.** A file existing and a file working are different things.\n Fix what comes back and call it again. Say \"I could not verify this\" for anything you did not check.\n For things you only learn by running (dev servers, watch), give Bash **background: true**.\n Called plainly they never end and die on timeout. Read output with Jobs, and always end it\n with Jobs when done — otherwise that server keeps holding the port.\n7. When done, say what you changed and why in a line or two. Do not paste the code back.",
155
+ say짧게En: "**Implementation.** Follow this order.\n1. Code you did not write: Outline -> Grep -> Read **only around what you found** (offset/limit).\n Fire unrelated reads together in one message - they run side by side.\n2. Always Read a file before editing it.\n3. Follow the surrounding conventions. Do not import a new one.\n4. Several files: one Write (files array). Several edits: one Edit (edits array).\n5. Several strands: hand off with Task.\n6. **Verify before you finish.** Fix what comes back and call it again.\n If it must be run, Bash with background: true — called plainly it dies on timeout. Read with Jobs, end with Jobs.\n7. Say what changed and why in a line or two. Do not paste code.",
109
156
  name: '코드',
110
157
  en: 'Code',
111
158
  glyph: '◆',
@@ -116,7 +163,8 @@ export const MODES = {
116
163
 
117
164
  say짧게: [
118
165
  '**구현**이다. 이 순서를 지켜라.',
119
- '1. 남의 코드면 Outline 으로 모양부터. 고칠 자리를 고른 **그 파일만** Read.',
166
+ '1. 남의 코드면 Outline Grep **짚은 자리 앞뒤만** Read (offset·limit). 통째로 읽지 마라.',
167
+ ' 상관없는 읽기는 한 번에 같이 부른다 — 나란히 돈다.',
120
168
  '2. 고칠 파일은 반드시 먼저 Read.',
121
169
  '3. 주변 코드의 관례를 따른다. 새 관례를 들여오지 마라.',
122
170
  '4. 파일 여럿은 Write 한 번에 (files 배열). 고칠 자리 여럿은 Edit 한 번에 (edits 배열).',
@@ -129,9 +177,10 @@ export const MODES = {
129
177
  say: [
130
178
  "지금 하는 일은 **구현**이다. 아래 순서를 지켜라.",
131
179
  "",
132
- "1. 남의 코드를 만지는 일이면 Outline 으로 **모양부터** 본다. 파일을 통째로",
133
- " Read 하지 마라 폴더 하나가 Outline 으로는 몇십 분의 일이다.",
134
- " 거기서 고칠 자리를 고른 다음, **그 파일만** Read 한다.",
180
+ "1. 남의 코드를 만지는 일이면 Outline 으로 **모양부터** 본다. 폴더 하나가",
181
+ " Outline 으로는 몇십 분의 일이다. Grep 으로 고칠 말이 닿는 자리를 다 모으고,",
182
+ " **짚은 자리 앞뒤만** Read 한다 (offset·limit). 파일을 통째로 읽지 마라.",
183
+ " 서로 상관없는 읽기는 **한 번에 같이 부른다** — 읽기 도구는 나란히 돈다.",
135
184
  "2. 고칠 파일은 반드시 먼저 Read 한다. 안 읽고 고치려 하면 도구가 거절한다.",
136
185
  "3. 주변 코드의 관례를 따른다 — 이름 짓는 법, 오류 다루는 법, 주석 밀도까지.",
137
186
  " 새 관례를 들여오지 마라. 이 코드가 이미 하고 있는 대로 한다.",
@@ -153,7 +202,7 @@ export const MODES = {
153
202
  architect: {
154
203
  id: 'architect',
155
204
  hintEn: "shapes the structure · touches no files",
156
- sayEn: "This is **design**. You have not been given the tools that change files.\n\nRead first. Designing without knowing the current structure is imagining, not designing.\n - Start with Outline for the shape of the folder. Narrow with Glob/Grep,\n then actually Read only the files you need\n - Work out what depends on what, and in which direction\n\nThen answer in this order.\n 1. Current structure — files, their roles, where the boundaries are (point with path:line)\n 2. What is wrong — why the current shape does not hold\n 3. Two or three options — what each gains, what each costs, how much work it is\n 4. One recommendation with the reason, and why you dropped the others\n 5. The files this affects\n\nFind the answer inside the conventions this code already uses. Bringing in a new framework is\nthe last resort, and if you go there, first say why the existing conventions cannot do it.",
205
+ sayEn: "This is **design**. You have not been given the tools that change files.\n\nRead first. Designing without knowing the current structure is imagining, not designing.\n - Start with Outline for the shape of the folder. Narrow with Glob/Grep, then Read\n **only around the lines you found** (offset/limit). Read whole files and the earlier\n ones fold away after a handful - then you have not seen the structure, only the last file\n - Fire unrelated reads together in one message - they run side by side\n - Work out what depends on what, and in which direction\n\nThen answer in this order.\n 1. Current structure — files, their roles, where the boundaries are (point with path:line)\n 2. What is wrong — why the current shape does not hold\n 3. Two or three options — what each gains, what each costs, how much work it is\n 4. One recommendation with the reason, and why you dropped the others\n 5. The files this affects\n\nFind the answer inside the conventions this code already uses. Bringing in a new framework is\nthe last resort, and if you go there, first say why the existing conventions cannot do it.",
157
206
  name: '설계',
158
207
  en: 'Architect',
159
208
  glyph: '◈',
@@ -166,7 +215,9 @@ export const MODES = {
166
215
  "",
167
216
  "먼저 읽어라. 지금 구조를 모르면 설계가 아니라 상상이다.",
168
217
  " - Outline 으로 폴더 모양부터 본다. 그 다음 Glob/Grep 으로 좁히고,",
169
- " 필요한 파일만 Read 실제로 읽는다",
218
+ " **짚은 자리 앞뒤만** Read 한다 (offset·limit). 통째로 읽으면 파일 몇 개 만에",
219
+ " 앞엣것이 접혀 나가고, 그러면 구조를 본 것이 아니라 마지막 파일만 본 것이 된다",
220
+ " - 서로 상관없는 읽기는 한 번에 같이 부른다 — 나란히 돈다",
170
221
  " - 무엇이 무엇에 기대고 있는지(의존 방향)를 파악한다",
171
222
  "",
172
223
  "그 다음 이 차례로 답한다.",
@@ -197,15 +248,119 @@ export const MODES = {
197
248
  "",
198
249
  "- 근거를 파일에서 대라. 경로와 줄 번호를 같이 적어라 (src/a.js:42).",
199
250
  "- 모르면 모른다고 하라. 그럴듯한 답을 지어내지 마라.",
200
- "- 짧게, 물어본 것에만 답하라.",
251
+ "- **답의 크기를 물음의 크기에 맞춰라.** 한 줄로 물으면 한 줄로 답하고,",
252
+ " 여러 가지를 물으면 물은 것을 하나도 빼지 말고 다 답한다. 짧은 것이 목적이 아니다.",
253
+ "- 읽어야 답이 나오는 물음이면 읽어라 — Outline → Grep → **짚은 자리 앞뒤만** Read.",
254
+ " 상관없는 읽기는 한 번에 같이 부른다.",
201
255
  "- 고치라는 말이 없으면 고칠 것을 제안하지 마라. 시키지 않은 일을 벌이지 마라.",
202
256
  ].join('\n'),
203
257
  },
204
258
 
259
+ /*
260
+ * ── 왜 이 모드가 따로 있나 ─────────────────────────────────────────────
261
+ *
262
+ * 「이 기능을 훑어서 동시에 고칠 때 데이터가 날아갈 자리를 찾아 줘. 근거가
263
+ * 있는 것만. 고치지는 말고. 다섯 개까지만.」
264
+ *
265
+ * 이 말이 갈 데가 없었다. 묻기(ask)로 갔다 — 「설명해줘」 가 들어 있어서다.
266
+ * 그런데 묻기는 **짧게 답하는 모드**이고 강도가 low 이고 걸음이 열두 번까지다.
267
+ * 읽어야 할 파일이 열 개인 일에 그 셋이 한꺼번에 걸리면 이렇게 된다 —
268
+ *
269
+ * 생각 0자 · 도구 10번 · 「짧게 답하라」 · 그러고 답이 나온다
270
+ *
271
+ * 답이 틀린 것은 아니다. **덜 본 것**이다. 그리고 덜 봤다는 말은 아무 데도
272
+ * 안 적힌다 — 사람은 그게 전부인 줄 안다.
273
+ *
274
+ * 설계(architect)도 아니다. 설계는 「어떻게 바꿀까」 이고 이건 「지금 무엇이
275
+ * 잘못됐나」 다. 디버그도 아니다. 디버그는 증상이 하나 있고 그 원인을 좁혀
276
+ * 가지만, 이건 증상이 아직 없고 **날 수 있는 자리**를 찾는 일이다.
277
+ *
278
+ * 그래서 따로 둔다. 읽기만 하고, 깊게 보고, 걸음을 넉넉히 준다.
279
+ */
280
+ inspect: {
281
+ id: 'inspect',
282
+ hintEn: "finds defects · touches no files",
283
+ sayEn: [
284
+ "This is an **inspection**. You have not been given the tools that change files.",
285
+ "",
286
+ "Read wide first, then narrow.",
287
+ " 1. Outline for the shape. Grep to gather **every** place the thing touches - a defect that",
288
+ " only shows up between two files is invisible if you read just one of them.",
289
+ " 2. Then Read **only around the lines you found** (offset/limit). Do not read whole files -",
290
+ " pour it all in at once and the earlier turns fold away, and you answer having forgotten them.",
291
+ " 3. Fire unrelated reads together in one message. Read tools run side by side.",
292
+ "",
293
+ "Write each finding in this shape.",
294
+ " - What - in one line",
295
+ " - Where - path:line, and the function name",
296
+ " - When it happens - the **order of events** that triggers it (while A is doing X, B does Y)",
297
+ " - Why - which line of code makes it so",
298
+ " - What it costs - is data lost, or is only the screen briefly out of step",
299
+ "",
300
+ "Hold to this.",
301
+ "- **Only what the code shows.** No guesses. If there is somewhere you could not look,",
302
+ " write that you did not - unsaid means you looked.",
303
+ "- If you were given a count, spend it on the **worst ones first.** Do not pad to reach the",
304
+ " number. Asked for five and found three? Give three.",
305
+ "- Do not fix anything. A fix goes in one line, and only if you were asked for one.",
306
+ "- If there is nothing, say there is nothing. Manufacture one finding and nobody trusts the rest.",
307
+ ].join('\n'),
308
+ say짧게En: [
309
+ "**Inspection.** You change nothing.",
310
+ "1. Outline -> Grep every place it touches -> Read **only around what you found** (offset/limit).",
311
+ " Fire unrelated reads together - they run side by side.",
312
+ "2. Per finding: what - where (path:line + function) - the order of events that triggers it - why - what it costs.",
313
+ "3. **Only what the code shows.** No guesses. Say what you could not look at.",
314
+ "4. Given a count? Worst first, no padding. Found fewer? Give fewer.",
315
+ "5. Do not fix anything. If there is nothing, say so.",
316
+ ].join('\n'),
317
+ name: '점검',
318
+ en: 'Inspect',
319
+ glyph: '◍',
320
+ hint: '결함을 찾는다 · 파일은 안 건드림',
321
+ tools: [...읽기, ...계획],
322
+ effort: 'deep',
323
+ think: 'high',
324
+ say짧게: [
325
+ '**점검**이다. 아무것도 바꾸지 않는다.',
326
+ '1. Outline → 그 말이 닿는 자리를 Grep 으로 **다** 모으기 → 짚은 자리 앞뒤만 Read (offset·limit).',
327
+ ' 상관없는 읽기는 한 번에 같이 부른다 — 나란히 돈다.',
328
+ '2. 찾은 것마다: 무엇 · 어디(경로:줄 과 함수) · 일어나는 차례 · 왜 · 무엇을 잃나.',
329
+ '3. **코드에 있는 것만.** 짐작은 적지 마라. 못 본 자리는 못 봤다고 적어라.',
330
+ '4. 개수를 정해 줬으면 **제일 아픈 것부터.** 수를 채우려고 약한 것을 넣지 마라.',
331
+ '5. 없으면 없다고 해라. 고치지는 마라.',
332
+ ].join('\n'),
333
+ say: [
334
+ '지금 하는 일은 **점검**이다. 파일을 바꾸는 도구는 주어지지 않았다.',
335
+ '',
336
+ '먼저 넓게 보고, 그 다음 좁게 읽어라.',
337
+ ' 1. Outline 으로 모양부터. 그 다음 Grep 으로 그 기능이 닿는 자리를 **다** 모은다.',
338
+ ' 두 파일 사이에서만 나는 결함은 한쪽만 읽으면 아예 안 보인다.',
339
+ ' 2. 그러고 나서 **짚은 자리 앞뒤만** Read 한다 (offset·limit). 통째로 읽지 마라 —',
340
+ ' 한 번에 다 부어 넣으면 뒤로 갈수록 앞엣것이 접혀 나가고, 잊은 채로 답하게 된다.',
341
+ ' 3. 서로 상관없는 읽기는 **한 번에 같이 부른다.** 읽기 도구는 나란히 돈다.',
342
+ '',
343
+ '찾은 것은 하나씩 이 꼴로 적어라.',
344
+ ' · 무엇 — 한 줄로',
345
+ ' · 어디 — 경로:줄 과 함수 이름',
346
+ ' · 언제 나나 — 그 일이 일어나는 **차례**를 적어라 (A 가 …하는 사이에 B 가 …하면)',
347
+ ' · 왜 그런가 — 코드의 어느 줄이 그렇게 만드는지',
348
+ ' · 무엇을 잃나 — 자료가 사라지는가, 화면만 잠깐 어긋나는가',
349
+ '',
350
+ '지키는 것:',
351
+ '- **코드에 근거가 있는 것만.** 짐작은 적지 마라. 못 본 자리가 있으면',
352
+ ' "여기는 못 봤다" 고 적어라 — 안 적으면 다 본 것으로 읽힌다.',
353
+ '- 개수를 정해 줬으면 그 안에서 **제일 아픈 것부터** 고른다.',
354
+ ' 수를 채우려고 약한 것을 끼워 넣지 마라. 다섯을 시켰는데 셋이면 셋만 낸다.',
355
+ '- 고치지 마라. 고칠 방법은 한 줄까지만, 그것도 물어봤을 때만.',
356
+ '- 없으면 없다고 해라. 억지로 찾아내면 그 뒤로 이 말을 아무도 안 믿는다.',
357
+ ].join('\n'),
358
+ },
359
+
205
360
  debug: {
206
361
  id: 'debug',
207
362
  hintEn: "finds the cause",
208
- sayEn: "This is **finding the cause**. Do not fix by guessing.\n\nFollow this order.\n 1. Restate the symptom in one sentence — what happens when you do what.\n 2. Get a reproduction. If there is none, build one. Without it you cannot tell whether you fixed it.\n 3. Form two or three hypotheses. For each, write down what you should see if it is true.\n 4. Check them one at a time, for real — read the logs, run something small, print the value.\n Only what you checked is fact. What you did not check is still a hypothesis.\n 5. When you name the cause, bring the evidence. A cause that starts with \"probably\" is not a cause.\n 6. After fixing, run the reproduction from step 2 again. If it is not fixed, go back to step 3.\n\nDo not change several places at once. You will not know which one fixed it.",
363
+ sayEn: "This is **finding the cause**. Do not fix by guessing.\n\nFollow this order.\n 1. Restate the symptom in one sentence — what happens when you do what.\n 2. Get a reproduction. If there is none, build one. Without it you cannot tell whether you fixed it.\n 3. Form two or three hypotheses. For each, write down what you should see if it is true.\n 4. Check them one at a time, for real — read the logs, run something small, print the value.\n Only what you checked is fact. What you did not check is still a hypothesis.\n If several hypotheses turn on reading alone, read for all of them in one message.\n 5. When you name the cause, bring the evidence. A cause that starts with \"probably\" is not a cause.\n 6. After fixing, run the reproduction from step 2 again. If it is not fixed, go back to step 3.\n\nDo not change several places at once. You will not know which one fixed it.",
209
364
  name: '디버그',
210
365
  en: 'Debug',
211
366
  glyph: '◉',
@@ -222,6 +377,7 @@ export const MODES = {
222
377
  " 3. 가설을 2~3개 세운다. 각각 \"이게 맞다면 무엇이 보여야 하는가\" 를 같이 적는다.",
223
378
  " 4. 하나씩 실제로 확인한다 — 로그를 보고, 작게 돌려 보고, 값을 찍어 본다.",
224
379
  " 확인한 것만 사실이다. 확인 안 한 것은 아직 가설이다.",
380
+ " 읽기만으로 가리는 가설이 여럿이면 **한 번에 같이 읽는다** — 나란히 돈다.",
225
381
  " 5. 원인을 짚을 때는 증거를 같이 댄다. \"아마\" 로 시작하는 원인은 원인이 아니다.",
226
382
  " 6. 고친 뒤 2번의 재현 절차로 다시 확인한다. 안 고쳐졌으면 3번으로 돌아간다.",
227
383
  "",
@@ -232,7 +388,7 @@ export const MODES = {
232
388
  plan: {
233
389
  id: 'plan',
234
390
  hintEn: "plan first · run it after approval",
235
- sayEn: "This is **planning**. You have not been given the tools that change files.\nDo not try to edit code. Produce a plan and stop.\n\nConfirm first — a plan built without knowing the current state is a wish, not a plan.\n Start with Outline for the shape, narrow with Glob/Grep, then Read only what you must.\n\nThen write it in this order.\n 1. Goal — what does \"done\" look like (as a sentence you can check)\n 2. Current state — the files involved and what they do now (point with path:line)\n 3. What changes — per file, what and why\n 4. Order — step by step. Each step small enough to check on its own\n 5. Risks — what could break, and how to get back if it does\n 6. How to check — what do you run to know it worked\n\nWrite the steps into TodoWrite as well. After approval you continue straight from them.\n The number of steps is not fixed — match it to the size of the job. Do not force it to three.\n A small job ends in two or three; a large one lists all ten or more.\nIf something is unknown, do not invent it — write \"this needs to be confirmed\".\n\nEnd with \"Shall I go ahead with this?\". Once approved, switch to /code and run it.",
391
+ sayEn: "This is **planning**. You have not been given the tools that change files.\nDo not try to edit code. Produce a plan and stop.\n\nConfirm first — a plan built without knowing the current state is a wish, not a plan.\n Start with Outline for the shape, narrow with Glob/Grep, then Read **only around the lines\n you found** (offset/limit). Fire unrelated reads together in one message.\n\nThen write it in this order.\n 1. Goal — what does \"done\" look like (as a sentence you can check)\n 2. Current state — the files involved and what they do now (point with path:line)\n 3. What changes — per file, what and why\n 4. Order — step by step. Each step small enough to check on its own\n 5. Risks — what could break, and how to get back if it does\n 6. How to check — what do you run to know it worked\n\nWrite the steps into TodoWrite as well. After approval you continue straight from them.\n The number of steps is not fixed — match it to the size of the job. Do not force it to three.\n A small job ends in two or three; a large one lists all ten or more.\nIf something is unknown, do not invent it — write \"this needs to be confirmed\".\n\nEnd with \"Shall I go ahead with this?\". Once approved, switch to /code and run it.",
236
392
  name: '계획',
237
393
  en: 'Plan',
238
394
  glyph: '☰',
@@ -245,7 +401,8 @@ export const MODES = {
245
401
  "코드를 고치려 들지 마라. 계획을 내고 멈춘다.",
246
402
  "",
247
403
  "먼저 확인하라 — 지금 무엇이 어떻게 되어 있는지 모르면 계획이 아니라 희망이다.",
248
- " Outline 으로 모양부터 보고, Glob/Grep 으로 좁힌 뒤, 필요한 것만 Read 해라.",
404
+ " Outline 으로 모양부터 보고, Glob/Grep 으로 좁힌 뒤, **짚은 자리 앞뒤만** Read 해라",
405
+ " (offset·limit). 상관없는 읽기는 한 번에 같이 부른다 — 나란히 돈다.",
249
406
  "",
250
407
  "그 다음 이 차례로 적어라.",
251
408
  " 1. 목표 — 무엇이 끝나면 다 된 것인가 (확인할 수 있는 문장으로)",
@@ -306,7 +463,7 @@ export const MODES = {
306
463
  },
307
464
  };
308
465
 
309
- export const ORDER = ['auto', 'code', 'plan', 'architect', 'debug', 'ask', 'orchestrator'];
466
+ export const ORDER = ['auto', 'code', 'plan', 'architect', 'debug', 'inspect', 'ask', 'orchestrator'];
310
467
  export const DEFAULT = 'auto';
311
468
 
312
469
  /** 이름을 관대하게 받는다. 한글·영문·줄임말 다 통한다. */
@@ -321,6 +478,11 @@ export function normalize(v) {
321
478
  '설계': 'architect', '아키': 'architect', 'arch': 'architect', 'a': 'architect',
322
479
  '디버그': 'debug', '버그': 'debug', 'd': 'debug',
323
480
  '묻기': 'ask', '질문': 'ask', '일상': 'ask', 'q': 'ask',
481
+ // 점검: '리뷰' 도 여기로 받는다. 슬래시 `/review` 는 **방금 바꾼 것**을 보는
482
+ // 다른 일이지만(agent/review.js), `/work 리뷰` 라고 친 사람이 원하는 것은
483
+ // 이쪽이다 — 그 둘을 가르는 것은 우리 몫이지 사람 몫이 아니다.
484
+ '점검': 'inspect', '감사': 'inspect', '검토': 'inspect', '리뷰': 'inspect',
485
+ 'inspect': 'inspect', 'audit': 'inspect', 'review': 'inspect', 'i': 'inspect',
324
486
  '총괄': 'orchestrator', '오케': 'orchestrator', 'orch': 'orchestrator', 'o': 'orchestrator',
325
487
  };
326
488
  return 별명[s] ?? null;
@@ -31,6 +31,9 @@ const 표 = {
31
31
  [/error/i, 3], [/\bexception\b|\bcrash|\bfail(s|ed|ing)?\b|\bbroken\b/i, 3],
32
32
  [/고장/, 3], [/왜\s*이(래|러|렇게\s*나)/, 2],
33
33
  [/왜\s*(못|안)/, 3], [/뭐\s*때문(이|에)/, 3],
34
+ // 원인을 짚으라는 말은 점검이 아니라 디버그다 — 증상이 이미 있다는 뜻이다.
35
+ // 이게 없으면 "오류 원인 분석해줘" 가 둘 사이에서 비겨 종합에 그대로 남는다.
36
+ [/원인(을|이|은)?\s*(찾|짚|파악|밝|알아|분석|살펴|봐)/, 4],
34
37
  ],
35
38
 
36
39
  plan: [
@@ -56,9 +59,50 @@ const 표 = {
56
59
  [/\bdesign\b|\bstructure\b|\brefactor(ing)?\s*(방향|plan)/i, 3],
57
60
  ],
58
61
 
62
+ /*
63
+ * 점검 — 「지금 무엇이 잘못돼 있나」 를 찾는 일.
64
+ *
65
+ * 이 갈래가 없던 동안 이런 말이 전부 묻기(ask)로 갔다. 「설명해줘」 한 마디가
66
+ * 5점이라 그것만으로 문턱을 넘기 때문이다. 그런데 묻기는 짧게 답하고, 얕게
67
+ * 생각하고(low), 열두 걸음에서 멈춘다 — 파일 열 개를 훑어야 하는 일에는
68
+ * 셋 다 틀린 값이다. 실제로 그렇게 돌아 본 턴이 있고, 같은 질문을 깊게 본
69
+ * 쪽과 견주면 답의 깊이가 눈에 띄게 달랐다.
70
+ *
71
+ * 여기 모은 낱말은 하나같이 **읽어야 답이 나오는** 말이다. 잘못 고르면
72
+ * 파일을 못 고쳐서 사람이 막히므로, 문턱은 읽기 전용답게 높인다.
73
+ */
74
+ inspect: [
75
+ [/점검(해|좀|을|이)/, 5], [/분석(해|좀|을|이)/, 5],
76
+ [/검토(해|좀|를)/, 5], [/리뷰(해|좀)?/, 5],
77
+ // '감사합니다' 는 안 걸린다 — '합' 은 '해·하' 가 아니다.
78
+ [/감사(를)?\s*(해|하)/, 4], [/훑어\s*(봐|보고|줘)/, 4],
79
+ [/취약(점|한|성)/, 5], [/보안\s*(점검|검토|문제|취약|구멍)/, 5],
80
+ [/경합|레이스\s*컨디션|race\s*condition/i, 5],
81
+ [/데이터\s*(손실|유실)|덮어쓰(기|면)|유실/, 4],
82
+ [/동시\s*(편집|접근|요청|쓰기|성)/, 4],
83
+ [/충돌/, 3], [/불일치|어긋/, 3], [/누락(된|되는|이|을)/, 3],
84
+ [/위험(한|이|을|해)/, 3], [/결함/, 3],
85
+ [/가능성(이|을)?\s*있는/, 3],
86
+ [/문제(가|를|점)?\s*(있|찾|없|만)/, 3],
87
+ [/찾아\s*(줘|주세요|봐|보)/, 2],
88
+ [/\baudit\b|\breview\b|\banaly[sz]e\b|\binspect\b|\bvulnerab/i, 4],
89
+ [/\bdata\s*loss\b|\bdeadlock\b|\blost\s*update\b/i, 4],
90
+ ],
91
+
59
92
  ask: [
60
93
  // 설명은 아무것도 안 바꾸는 모드라 잘못 고르면 답답해진다. 신호를 엄격히 본다.
61
94
  [/뭐(야|예요|입니까|지)\s*\??$/m, 5], [/무엇(인가|입니까|이야)/, 5],
95
+ /*
96
+ * 「뭐 하는 파일이야?」 — 터미널에서 돌려 보다 걸린 자리다.
97
+ *
98
+ * 윗줄은 「뭐」 바로 뒤에 「야」 가 붙어야 걸린다. 그런데 사람이 실제로
99
+ * 치는 말은 「이거 뭐 하는 파일이야?」 처럼 **사이에 말이 낀다.** 그래서
100
+ * 제일 흔한 물음 하나가 통째로 안 걸렸고, 종합 모드에 그대로 남았다.
101
+ * 막히지는 않지만 물어본 것에 답만 하면 되는 턴이 파일을 고칠 수 있는
102
+ * 모드로 돌았다는 뜻이다.
103
+ */
104
+ [/뭐\s*하는\s*(파일|함수|코드|모듈|클래스|녀석|건|거|것)/, 5],
105
+ [/뭐\s*하(는|나|니|냐)/, 3], [/무슨\s*(일|역할)/, 3],
62
106
  [/설명(해|좀|을)/, 5], [/알려\s*(줘|주세요|줄래)/, 3],
63
107
  [/어떻게\s*(동작|작동|돌아가)/, 5],
64
108
  [/무슨\s*(뜻|의미)|의미(가|는)\s*뭐/, 5],
@@ -107,6 +151,7 @@ const 문턱 = {
107
151
  orchestrator: 4,
108
152
  plan: 5,
109
153
  architect: 5,
154
+ inspect: 5,
110
155
  ask: 5,
111
156
  };
112
157
 
@@ -135,6 +180,45 @@ const 계획먼저 = new RegExp(`(${계획말})\\s*(?:을|를)?\\s*(?:${잇는
135
180
  // 영어도 같이 본다 — "plan and then build it" 처럼 붙여 넣는 일이 있다.
136
181
  const 영어겹침 = /\b(plan|design|outline|draft)\b[\s\S]{0,60}?\b(?:and|then|,)\s*(?:then\s*)?\b(build|implement|create|write|make|code)\b/i;
137
182
 
183
+ /*
184
+ * ── 두 말이 **얼마나 붙어 있어야** 겹침인가 ─────────────────────────────
185
+ *
186
+ * 예순 자다. 영어 쪽은 처음부터 `[\s\S]{0,60}?` 로 거리를 두고 봤는데
187
+ * 한국어 쪽만 「그 뒤 어디든」 이었다. 그 차이가 긴 글에서 그대로 터졌다.
188
+ *
189
+ * 실제로 겪은 것 — 1,300자짜리 지시문이었다. 「…배포 전 점검한다고 가정하고
190
+ * 전체적으로 검토해줘 … 실제로 개선하는 것이다 … 코드 수정만 수행해라 …
191
+ * 중간에 멈춰서 확인을 요청하지 말고 스스로 진행해라」.
192
+ *
193
+ * 여기서 800자 떨어진 두 조각 — 「검토해서」(9번째 문단)와 「추가하」(보고
194
+ * 항목 이름) — 이 짝지어져 겹침으로 잡혔다. 그래서 **계획 모드**로 갔다.
195
+ * 계획은 파일을 바꾸는 도구가 없는 모드이고, 그 지시문은 「고쳐라 · 테스트를
196
+ * 만들어라 · 멈추지 마라」 였다. 사람은 고쳐 달라고 했는데 계획서 한 장을
197
+ * 받고, 마지막에 「이대로 진행할까요?」 를 봤다 — 묻지 말라고 적어 둔 바로
198
+ * 그 자리에서.
199
+ *
200
+ * 이 규칙이 잡으려던 것은 「정리해서 만들어줘」 같은 **한 문장**이다. 한 문장
201
+ * 안에서 두 말은 예순 자를 안 넘는다. 그러니 거리를 재는 것이 규칙을 좁히는
202
+ * 것이 아니라 **원래 뜻대로 되돌리는 것**이다.
203
+ */
204
+ const 겹침거리 = 60;
205
+
206
+ /*
207
+ * ── 「묻지 말고 끝까지」 라고 이미 적어 둔 사람 ─────────────────────────
208
+ *
209
+ * 겹침의 값은 「계획을 보여 주고 승인을 받는다」 다. 그런데 승인을 받지
210
+ * 말라고 **글로 적어 둔** 사람에게 그 값은 손해뿐이다. 계획 모드는 계획을
211
+ * 내고 멈추므로, 그 사람은 시킨 일의 절반도 못 받는다.
212
+ *
213
+ * 이건 짐작이 아니라 사람이 직접 쓴 말이다. 쓴 말이 규칙을 이긴다.
214
+ */
215
+ const 멈추지마 = /(?:중간에\s*)?(?:멈추지|묻지|물어보지|확인\s*요청하지|되묻지)\s*(?:는)?\s*(?:말|마)|끝까지\s*(?:수행|진행|해)|스스로\s*진행|알아서\s*(?:진행|해)|승인\s*없이|\bwithout\s+(?:asking|stopping|confirmation)\b|\bdon'?t\s+(?:stop|ask)\b/i;
216
+
217
+ /** 사람이 「묻지 말고 끝까지 하라」 고 적어 뒀나. */
218
+ export function 묻지말라했나(text) {
219
+ return 멈추지마.test(String(text ?? ''));
220
+ }
221
+
138
222
  /**
139
223
  * 계획과 실행이 한 말에 같이 들었나.
140
224
  *
@@ -142,14 +226,23 @@ const 영어겹침 = /\b(plan|design|outline|draft)\b[\s\S]{0,60}?\b(?:and|then|
142
226
  */
143
227
  export function 겹친요청(text) {
144
228
  const s = String(text ?? '');
229
+ // 묻지 말라고 적어 둔 사람에게 승인 창을 띄우지 않는다. 위 머리말 참고.
230
+ if (묻지말라했나(s)) return { 겹침: false, why: '' };
231
+
145
232
  const 영어 = s.match(영어겹침);
146
233
  if (영어) return { 겹침: true, why: `'${영어[1]}' 과 '${영어[2]}'` };
147
234
 
148
235
  const 앞 = s.match(계획먼저);
149
236
  if (!앞) return { 겹침: false, why: '' };
150
- // 실행말이 계획말 **뒤에** 와야 한다. "만들고 나서 정리해줘" 는 겹침이 아니다 —
151
- // 그건 만드는 일이 먼저고, 계획을 미리 볼 것이 없다.
152
- const 뒤 = s.slice(앞.index + 앞[0].length).match(실행말);
237
+ /*
238
+ * 실행말이 계획말 **바로 뒤에** 와야 한다.
239
+ *
240
+ * 「뒤에」 였지 「글 어디든」 이 아니었다 — 거리를 안 재면 긴 지시문에서는
241
+ * 언제나 짝이 하나쯤 걸린다(위 겹침거리 머리말). "만들고 나서 정리해줘" 가
242
+ * 안 걸리는 것도 그대로다. 그건 만드는 일이 먼저고, 미리 볼 계획이 없다.
243
+ */
244
+ const 남은것 = s.slice(앞.index + 앞[0].length, 앞.index + 앞[0].length + 겹침거리);
245
+ const 뒤 = 남은것.match(실행말);
153
246
  if (!뒤) return { 겹침: false, why: '' };
154
247
  return { 겹침: true, why: `'${앞[0].trim()}' 뒤에 '${뒤[0].trim()}'` };
155
248
  }
@@ -173,13 +266,31 @@ export function 겹친요청(text) {
173
266
  * 하나는 읽고 말하라는 말이고 하나는 고치라는 말이다. 그래서 어미가 아니라
174
267
  * **동사 목록**으로 본다. 목록에 없으면 손대라는 말이 아니다.
175
268
  */
176
- const 손대는동사 = '만들|고쳐|고치|바꿔|바꾸|추가|넣어|지워|삭제|옮겨|나눠|나누|합쳐|정리|개선|구현|작성|적용|설치|배포|리팩터링|리팩터|분리|통일|세팅|갈아|손봐|손보';
177
- // 동사 바로 뒤에 시킴꼴이 붙어야 한다. 빈칸을 안 넘으므로 "나누는 설계만 봐줘"
178
- // 걸린다 그건 나누라는 말이 아니라 나누는 안을 보자는 말이다.
179
- const 손대라는말 = new RegExp(`(${손대는동사})\\S{0,3}(줘|주세요|주라|해라|하라|하자|해다오|해줄래|할래)`);
269
+ const 손대는동사 = '만들|고쳐|고치|바꿔|바꾸|추가|넣어|지워|삭제|옮겨|나눠|나누|합쳐|정리|개선|구현|작성|적용|설치|배포|리팩터링|리팩터|분리|통일|세팅|갈아|손봐|손보|수정';
270
+ /*
271
+ * 동사 바로 뒤에 시킴꼴이 붙어야 한다. 빈칸을 안 넘으므로 "나누는 설계만 봐줘"
272
+ * 걸린다 — 그건 나누라는 말이 아니라 나누는 안을 보자는 말이다.
273
+ *
274
+ * ── 해라체가 통째로 빠져 있었다 ─────────────────────────────────────────
275
+ *
276
+ * 끝맺음 목록이 전부 **부탁하는 말**이었다(줘 · 주세요 · 해라 · 할래). 그래서
277
+ * 시키는 말투로 쓴 사람은 이 검사에 한 번도 안 걸렸다 —
278
+ *
279
+ * 「재현 검사를 먼저 만들어라 … 최소 범위의 코드 수정만 수행해라
280
+ * … 중간에 멈추지 말고 스스로 진행해라」
281
+ *
282
+ * 이 지시문이 「고치라는 말이 아니다」 로 읽혀서 읽기 전용 모드로 갔다.
283
+ * 사람은 고쳐 달라고 세 번 적었는데 아무것도 안 바뀐 화면을 받는다.
284
+ *
285
+ * 「어라 · 아라」 와 맨 「라」 가 그 문을 연다. 맨 라 는 낱말 끝일 때만 본다 —
286
+ * 「지워라」 는 지우라는 말이지만 「바꾸라는 말」 은 아니다. 뒤에 무엇이
287
+ * 붙어 있나로 그 둘이 갈린다.
288
+ * 「만들어라」 는 언제나 만들라는 말이다.
289
+ */
290
+ const 손대라는말 = new RegExp(`(${손대는동사})\\S{0,3}(줘|주세요|주라|해라|하라|하자|해다오|해줄래|할래|어라|아라|여라|라(?=[\s.!?~,]|$))`);
180
291
 
181
292
  // 파일을 못 고치는 모드. modes.js 에서 쓰기 도구를 안 주는 것들과 같아야 한다.
182
- const 읽기만하는모드 = new Set(['architect', 'plan', 'ask']);
293
+ const 읽기만하는모드 = new Set(['architect', 'plan', 'ask', 'inspect']);
183
294
 
184
295
  /** 파일을 손대라는 말이 들었나. */
185
296
  export function 손대라했나(text) {
@@ -26,9 +26,53 @@ export function endpoint(shape) {
26
26
  return '/chat/completions';
27
27
  }
28
28
 
29
+ /*
30
+ * ── 이 대화가 한 덩어리라고 **머리에** 적는다 ───────────────────────────
31
+ *
32
+ * 몸통에도 같은 것을 적는 자리가 있다(buildBody 의 세션이름). 그런데 그 자리는
33
+ * 규격이 받는 칸이 있을 때만 쓴다 — OpenAI 직통은 `user`, Anthropic 은
34
+ * `metadata.user_id`. 그 둘이 아닌 곳, 즉 **게이트웨이 뒤**에는 아무것도 안
35
+ * 나갔다. 문서에 없는 칸을 몸통에 실으면 400 이고, 그 400 은 화면에서 열쇠가
36
+ * 틀린 것과 구별이 안 되기 때문이다(backend/wire.js 의 세션자리).
37
+ *
38
+ * 그래서 몸통이 아니라 **머리**에 적는다. 머리는 모르면 그냥 무시된다 —
39
+ * 400 이 날 자리가 아니다. 그러니 창구를 가릴 이유도 없고, 「이 창구만 예외」
40
+ * 라는 갈래가 하나도 안 는다.
41
+ *
42
+ * ── 이게 없으면 무슨 일이 나나 ──────────────────────────────────────────
43
+ *
44
+ * 게이트웨이는 이름이 없는 요청마다 **새 대화를 하나씩 연다.** 대시보드에
45
+ * 한 대화가 아홉 줄로 흩어지는 것은 눈에 보이는 쪽이고, 안 보이는 쪽이 훨씬
46
+ * 비싸다 — 모델 하나 뒤에 창구가 여럿이면(지역이 다른 Bedrock 프로파일 같은
47
+ * 것) 요청마다 다른 창구로 갈린다. 프롬프트 캐시는 **창구마다 따로** 있으므로,
48
+ * 갈리는 순간 앞머리가 통째로 다시 나간다. 캐시가 90% 를 넘다가 갑자기 20%
49
+ * 로 떨어지는 것이 그 모양이다.
50
+ *
51
+ * 이름이 있으면 게이트웨이가 그 대화를 첫 요청이 닿은 창구에 붙여 둔다
52
+ * (LiteLLM 의 session affinity). 그러면 캐시가 한자리에 쌓인다.
53
+ *
54
+ * ── 왜 이름을 둘로 적나 ─────────────────────────────────────────────────
55
+ *
56
+ * x-litellm-session-id 문서에 이름이 그대로 적힌 칸이다.
57
+ * x-deel-session-id `x-<우리이름>-session-id` 라는 **일반 규칙**으로도
58
+ * 읽어 주는 게이트웨이가 있다. 우리 이름으로 적어 두면
59
+ * 남의 도구 이름을 사칭하지 않고도 그 규칙에 걸린다.
60
+ *
61
+ * 값은 같다. 둘 다 대화 번호 하나뿐이고 경로·주소·열쇠는 안 들어간다
62
+ * (backend/wire.js 의 세션이름짓기).
63
+ */
64
+ function 세션머리(세션이름) {
65
+ const 이름 = String(세션이름 ?? '').trim();
66
+ if (!이름) return {};
67
+ return { 'x-litellm-session-id': 이름, 'x-deel-session-id': 이름 };
68
+ }
69
+
29
70
  /** 이 규격이 더 요구하는 머리. 없으면 빈 것. */
30
- export function 더할머리(shape) {
31
- return shape === 'anthropic' ? { 'anthropic-version': ANTHROPIC_VERSION } : {};
71
+ export function 더할머리(shape, 세션이름 = null) {
72
+ return {
73
+ ...(shape === 'anthropic' ? { 'anthropic-version': ANTHROPIC_VERSION } : {}),
74
+ ...세션머리(세션이름),
75
+ };
32
76
  }
33
77
 
34
78
  /**
@@ -856,11 +900,18 @@ export function toolMessage(shape, { callId, name, content }) {
856
900
  * 안 붙는다. 못 받았다는 것은 부르는 쪽이 onAuth 로 듣고 화면에 적는다.
857
901
  */
858
902
  async function 머리말짓기(conn, opts, { 다시 = false } = {}) {
903
+ /*
904
+ * 대화 이름은 **세 갈래가 다 지나는 자리**에서 한 번만 얹는다.
905
+ *
906
+ * 갈래마다 손으로 적게 두면 언젠가 한 곳이 빠지고, 그 한 곳만 새 대화로
907
+ * 잡힌다 — 하필 열쇠를 새로 받은 뒤의 요청이 그 자리다.
908
+ */
909
+ const 이름 = opts.세션이름 ?? conn.세션이름 ?? null;
859
910
  const 설정 = conn.열쇠받기 ?? null;
860
911
  const 판단 = 쓸수있나(설정, { auth: conn.auth });
861
912
  if (!설정 || !판단.된다) {
862
913
  if (설정 && 판단.왜) opts.onAuth?.({ ok: false, 왜: 판단.왜, 안부름: true });
863
- return headersFor(conn.auth, conn.key ?? '', 더할머리(conn.kind));
914
+ return headersFor(conn.auth, conn.key ?? '', 더할머리(conn.kind, 이름));
864
915
  }
865
916
  const r = await 열쇠받아오기(설정, {
866
917
  다시, signal: opts.signal ?? null,
@@ -869,10 +920,10 @@ async function 머리말짓기(conn, opts, { 다시 = false } = {}) {
869
920
  });
870
921
  if (!r.ok) {
871
922
  opts.onAuth?.({ ...r, ok: false });
872
- return headersFor(conn.auth, conn.key ?? '', 더할머리(conn.kind));
923
+ return headersFor(conn.auth, conn.key ?? '', 더할머리(conn.kind, 이름));
873
924
  }
874
925
  if (!r.그대로) opts.onAuth?.({ ok: true, 만료: r.만료, ms: r.ms });
875
- return headersFor(conn.auth, r.token, { ...더할머리(conn.kind), ...r.headers });
926
+ return headersFor(conn.auth, r.token, { ...더할머리(conn.kind, 이름), ...r.headers });
876
927
  }
877
928
 
878
929
  /*
package/src/cmdnames.js CHANGED
@@ -33,6 +33,7 @@ export const 명령들 = {
33
33
  architect: { arg: false },
34
34
  debug: { arg: false },
35
35
  ask: { arg: false },
36
+ inspect: { arg: false },
36
37
  orchestrator: { arg: false },
37
38
  undo: { arg: true },
38
39
  diff: { arg: true },