deel-local-cli 1.0.2 → 1.1.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/README.en.md +381 -7
- package/README.md +493 -9
- package/bin/deel.js +266 -234
- package/package.json +4 -3
- package/src/agent/budget.js +167 -0
- package/src/agent/grade.js +207 -0
- package/src/agent/loop.js +778 -574
- package/src/agent/modes.js +109 -28
- package/src/agent/project.js +171 -0
- package/src/agent/route.js +55 -3
- package/src/agent/session.js +131 -12
- package/src/backend/http.js +41 -4
- package/src/backend/learn.js +46 -4
- package/src/commands.js +178 -3
- package/src/oneshot.js +390 -327
- package/src/preview/serve.js +326 -0
- package/src/repl.js +1184 -887
- package/src/safety/audit.js +3 -1
- package/src/skills/builtin//352/262/200/354/202/254-/353/250/274/354/240/200/SKILL.md +64 -0
- package/src/skills/builtin//352/271/212/354/235/264/354/236/210/352/262/214-/353/247/214/353/223/244/352/270/260/SKILL.md +78 -0
- package/src/skills/builtin//353/201/235/352/271/214/354/247/200-/355/225/230/352/270/260/SKILL.md +65 -0
- package/src/skills/builtin//354/212/244/354/212/244/353/241/234-/352/262/200/355/206/240/SKILL.md +74 -0
- package/src/skills/builtin//354/260/224/353/237/254/353/263/264/352/270/260/SKILL.md +59 -0
- package/src/skills/builtin//354/260/250/352/267/274/354/260/250/352/267/274-/353/224/224/353/262/204/352/271/205/SKILL.md +73 -0
- package/src/skills/builtin//354/275/224/353/223/234-/354/244/204/354/235/264/352/270/260/SKILL.md +66 -0
- package/src/skills/discover.js +17 -2
- package/src/tools/index.js +500 -107
- package/src/tools/jobs.js +670 -0
- package/src/tools/outline.js +331 -0
- package/src/tools/task.js +153 -0
- package/src/tools/verify.js +306 -0
- package/src/tools/webfetch.js +152 -16
- package/src/ui/inputbox.js +102 -7
- package/src/ui/motion.js +212 -0
- package/src/ui/screen.js +51 -3
- package/src/ui/status.js +69 -4
- package/src/ui/working.js +8 -1
package/src/agent/modes.js
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
// 읽기만 하는 도구. 무엇을 바꾸지 않는다.
|
|
14
14
|
// Recall 은 지난 대화를 찾는다 — 파일은 안 건드리므로 읽기 쪽이다.
|
|
15
15
|
// 묻기 모드에도 준다: "저번에 이거 어떻게 했더라" 가 딱 묻기 모드의 일이다.
|
|
16
|
-
const 읽기 = ['Read', 'Glob', 'Grep', 'WebFetch', 'Skill', 'Recall'];
|
|
16
|
+
const 읽기 = ['Read', 'Outline', 'Glob', 'Grep', 'WebFetch', 'Skill', 'Recall'];
|
|
17
17
|
// 계획을 적는 도구. 파일을 안 건드리므로 읽기 전용 모드에서도 준다.
|
|
18
18
|
//
|
|
19
19
|
// Remember 도 여기 있다. 기억은 사용자의 소스를 안 건드리고 .deel/memory.md
|
|
@@ -23,7 +23,25 @@ const 계획 = ['TodoWrite', 'Remember'];
|
|
|
23
23
|
// 바꾸는 도구.
|
|
24
24
|
// Append 는 Write 와 짝이다 — 출력 상한이 작은 모델이 큰 파일을 나눠 쓰는 길이다.
|
|
25
25
|
// Write 를 주는 자리에는 반드시 같이 준다. 하나만 주면 나눠 쓸 방법이 없어진다.
|
|
26
|
-
|
|
26
|
+
// Jobs 는 Bash 와 짝이다. Bash 를 주는 자리에는 반드시 같이 준다 —
|
|
27
|
+
// background 로 띄워 놓고 읽을 길이 없으면 띄운 것이 그냥 유령이 된다.
|
|
28
|
+
const 쓰기 = ['Write', 'Append', 'Edit', 'Bash', 'Jobs'];
|
|
29
|
+
// 만든 것을 확인하는 도구. 아무것도 안 바꾸지만 **쓰는 모드에만** 준다 —
|
|
30
|
+
// 안 만든 모드에서 확인할 것이 없고, 도구 정의로 나가는 자리만 먹는다.
|
|
31
|
+
const 확인 = ['Verify'];
|
|
32
|
+
/*
|
|
33
|
+
* 일을 쪼개는 도구.
|
|
34
|
+
*
|
|
35
|
+
* 파일을 바꾸는 모드에만 준다. 읽기만 하는 모드(설계·계획·묻기)는 한 창 안에서
|
|
36
|
+
* 답을 내는 것이 일이고, 거기에 하위 작업을 얹으면 얻는 것보다 도구 정의로
|
|
37
|
+
* 나가는 자리가 더 아깝다.
|
|
38
|
+
*
|
|
39
|
+
* 안전 쪽으로도 그렇다. 하위 작업은 제 모드를 스스로 고르는데, 읽기 전용
|
|
40
|
+
* 모드에 이걸 쥐여 주면 "파일을 안 바꾼다" 는 약속을 하위가 깨고 나갈 길이
|
|
41
|
+
* 하나 생긴다. 그 길은 loop.js 와 task.js 에서 두 겹으로 막아 두었지만,
|
|
42
|
+
* 애초에 안 주는 것이 제일 확실하다.
|
|
43
|
+
*/
|
|
44
|
+
const 쪼개기 = ['Task'];
|
|
27
45
|
|
|
28
46
|
export const MODES = {
|
|
29
47
|
// 처음에는 여기서 시작한다.
|
|
@@ -40,19 +58,34 @@ export const MODES = {
|
|
|
40
58
|
en: 'Auto',
|
|
41
59
|
glyph: '◎',
|
|
42
60
|
hint: '요청에 따라 알맞은 모드로',
|
|
43
|
-
tools: [...읽기, ...계획,
|
|
61
|
+
tools: [...읽기, ...계획, ...쓰기, ...확인, ...쪼개기],
|
|
44
62
|
effort: 'save',
|
|
45
63
|
think: null,
|
|
46
|
-
|
|
64
|
+
// 작은 창용 짧은 판. 빠진 규칙은 없고 설득하는 문장만 없다 — 아래 말() 참고.
|
|
65
|
+
say짧게: [
|
|
66
|
+
'**종합** 모드다. 무슨 일이 올지 정해져 있지 않다.',
|
|
67
|
+
'- 무슨 일인지 먼저 가늠하고 그에 맞게 해라.',
|
|
68
|
+
'- 큰 일이면 TodoWrite 로 쪼개 적고 **다 끝낸다.** 덩이가 여럿이면 Task 로 떼어 준다.',
|
|
69
|
+
'- 남의 코드는 Outline 으로 모양부터. 통째로 Read 하지 마라.',
|
|
70
|
+
'- 파일 여러 개는 Write 한 번에 (files 배열), 고칠 자리 여럿은 Edit 한 번에 (edits 배열).',
|
|
71
|
+
'- 끝내지 않는 명령(dev 서버·watch)은 Bash 에 background: true. Jobs 로 읽고 끝낸다.',
|
|
72
|
+
'- 끝내기 전에 Verify. 확인 못 한 것을 됐다고 하지 마라.',
|
|
73
|
+
].join('\n'),
|
|
47
74
|
say: [
|
|
48
75
|
'지금은 **종합** 모드다. 무슨 일이 올지 정해져 있지 않다.',
|
|
49
76
|
'',
|
|
50
77
|
'- 시키는 일이 무엇인지 먼저 가늠하고, 그에 맞는 방식으로 해라.',
|
|
51
78
|
' 고치는 일이면 읽고 나서 고치고, 원인을 찾는 일이면 확인부터 하고,',
|
|
52
79
|
' 설명하는 일이면 근거를 파일에서 대라.',
|
|
53
|
-
'- 큰 일이면 TodoWrite 로 쪼개 적고
|
|
54
|
-
'
|
|
55
|
-
'-
|
|
80
|
+
'- 큰 일이면 TodoWrite 로 쪼개 적고 **적은 것을 다 끝낸다.** 적어 놓고 묻지 마라.',
|
|
81
|
+
' 덩이가 여럿이면 Task 로 떼어 준다 — 하위는 제 창에서 돌아 네 창이 안 찬다.',
|
|
82
|
+
'- 남의 코드는 Outline 으로 모양부터 본다. 통째로 Read 하지 마라.',
|
|
83
|
+
'- 파일을 여러 개 만들 때는 Write 한 번에 (files 배열). 한 개씩 부르지 마라.',
|
|
84
|
+
' 고칠 자리가 여러 군데일 때도 마찬가지로 Edit 한 번에 (edits 배열).',
|
|
85
|
+
'- 끝나지 않는 명령(dev 서버·watch)은 Bash 에 background: true 를 준다. 그냥 부르면',
|
|
86
|
+
' 시간 초과로 죽는다. 띄운 뒤에는 Jobs 로 출력을 읽고, 일이 끝나면 Jobs 로 끝낸다.',
|
|
87
|
+
'- 확인할 수 있는 것은 확인해라 — Verify 를 부른다. 확인 못 한 것을 됐다고 하지 마라.',
|
|
88
|
+
'- 시킨 일을 해내는 데 필요한 것은 한다. 그것과 상관없는 일을 벌이지 마라.',
|
|
56
89
|
].join('\n'),
|
|
57
90
|
},
|
|
58
91
|
|
|
@@ -62,20 +95,43 @@ export const MODES = {
|
|
|
62
95
|
en: 'Code',
|
|
63
96
|
glyph: '◆',
|
|
64
97
|
hint: '고치고 만든다',
|
|
65
|
-
tools: [...읽기, ...계획,
|
|
98
|
+
tools: [...읽기, ...계획, ...쓰기, ...확인, ...쪼개기],
|
|
66
99
|
effort: 'save', // 첫 판단만 세게, 이어가기는 얕게
|
|
67
100
|
think: null, // 사용자가 정한 값을 그대로 쓴다
|
|
68
|
-
|
|
101
|
+
|
|
102
|
+
say짧게: [
|
|
103
|
+
'**구현**이다. 이 순서를 지켜라.',
|
|
104
|
+
'1. 남의 코드면 Outline 으로 모양부터. 고칠 자리를 고른 뒤 **그 파일만** Read.',
|
|
105
|
+
'2. 고칠 파일은 반드시 먼저 Read.',
|
|
106
|
+
'3. 주변 코드의 관례를 따른다. 새 관례를 들여오지 마라.',
|
|
107
|
+
'4. 파일 여럿은 Write 한 번에 (files 배열). 고칠 자리 여럿은 Edit 한 번에 (edits 배열).',
|
|
108
|
+
'5. 갈래가 여럿이면 Task 로 떼어 준다.',
|
|
109
|
+
'6. **끝내기 전에 Verify.** 탈이 나오면 고치고 다시 부른다.',
|
|
110
|
+
' 띄워 봐야 하면 Bash 에 background: true — 그냥 부르면 시간 초과로 죽는다. Jobs 로 읽고 Jobs 로 끝낸다.',
|
|
111
|
+
'7. 무엇을 왜 바꿨는지 한두 줄로. 코드를 붙여넣지 마라.',
|
|
112
|
+
].join('\n'),
|
|
113
|
+
|
|
69
114
|
say: [
|
|
70
115
|
"지금 하는 일은 **구현**이다. 아래 순서를 지켜라.",
|
|
71
116
|
"",
|
|
72
|
-
"1.
|
|
73
|
-
"
|
|
117
|
+
"1. 남의 코드를 만지는 일이면 Outline 으로 **모양부터** 본다. 파일을 통째로",
|
|
118
|
+
" Read 하지 마라 — 폴더 하나가 Outline 으로는 몇십 분의 일이다.",
|
|
119
|
+
" 거기서 고칠 자리를 고른 다음, **그 파일만** Read 한다.",
|
|
120
|
+
"2. 고칠 파일은 반드시 먼저 Read 한다. 안 읽고 고치려 하면 도구가 거절한다.",
|
|
121
|
+
"3. 주변 코드의 관례를 따른다 — 이름 짓는 법, 오류 다루는 법, 주석 밀도까지.",
|
|
74
122
|
" 새 관례를 들여오지 마라. 이 코드가 이미 하고 있는 대로 한다.",
|
|
75
|
-
"
|
|
76
|
-
"
|
|
77
|
-
"
|
|
78
|
-
"
|
|
123
|
+
"4. 필요한 파일을 다 만들고 다 고친다. 한 파일만 건드려 놓고 멈추지 마라.",
|
|
124
|
+
" 새로 만드는 일이면 폴더 구조부터 잡고, **Write 한 번에 여러 파일**을 만든다",
|
|
125
|
+
" (files 배열). 고칠 자리가 여러 군데면 **Edit 한 번에** 보낸다 (edits 배열).",
|
|
126
|
+
" 한 번에 하나씩 부르면 그 수만큼 왕복이 늘어 몇 분이 그냥 간다.",
|
|
127
|
+
"5. 파일을 여러 갈래로 나눠 만들어야 하면 Task 로 덩이를 떼어 준다.",
|
|
128
|
+
" 하위 작업은 제 창에서 돌고 너에게는 요약만 온다 — 네 창이 안 찬다.",
|
|
129
|
+
"6. **끝내기 전에 Verify 를 부른다.** 파일이 있다는 것과 되는 것은 다르다.",
|
|
130
|
+
" 탈이 나오면 고치고 다시 부른다. 확인 못 한 것은 \"확인 못 했다\" 고 말한다.",
|
|
131
|
+
" 실제로 띄워 봐야 아는 일이면 (dev 서버·watch) **Bash 에 background: true** 를 준다.",
|
|
132
|
+
" 그냥 부르면 끝나지 않아서 시간 초과로 죽는다. 띄운 뒤 Jobs 로 출력을 읽고,",
|
|
133
|
+
" 일이 끝나면 Jobs 로 반드시 끝낸다 — 안 끝내면 그 서버가 계속 포트를 문다.",
|
|
134
|
+
"7. 끝나면 무엇을 왜 바꿨는지 한두 줄로 말한다. 코드를 통째로 붙여넣지 마라.",
|
|
79
135
|
].join('\n'),
|
|
80
136
|
},
|
|
81
137
|
|
|
@@ -88,12 +144,12 @@ export const MODES = {
|
|
|
88
144
|
tools: [...읽기, ...계획],
|
|
89
145
|
effort: 'deep',
|
|
90
146
|
think: 'high',
|
|
91
|
-
steps: 20,
|
|
92
147
|
say: [
|
|
93
148
|
"지금 하는 일은 **설계**다. 파일을 바꾸는 도구는 주어지지 않았다.",
|
|
94
149
|
"",
|
|
95
150
|
"먼저 읽어라. 지금 구조를 모르면 설계가 아니라 상상이다.",
|
|
96
|
-
" -
|
|
151
|
+
" - Outline 으로 폴더 모양부터 본다. 그 다음 Glob/Grep 으로 좁히고,",
|
|
152
|
+
" 꼭 필요한 파일만 Read 로 실제로 읽는다",
|
|
97
153
|
" - 무엇이 무엇에 기대고 있는지(의존 방향)를 파악한다",
|
|
98
154
|
"",
|
|
99
155
|
"그 다음 이 차례로 답한다.",
|
|
@@ -117,7 +173,6 @@ export const MODES = {
|
|
|
117
173
|
tools: [...읽기],
|
|
118
174
|
effort: 'even',
|
|
119
175
|
think: 'low',
|
|
120
|
-
steps: 8,
|
|
121
176
|
say: [
|
|
122
177
|
"지금 하는 일은 **설명**이다. 아무것도 바꾸지 않는다.",
|
|
123
178
|
"",
|
|
@@ -134,10 +189,9 @@ export const MODES = {
|
|
|
134
189
|
en: 'Debug',
|
|
135
190
|
glyph: '◉',
|
|
136
191
|
hint: '원인을 찾는다',
|
|
137
|
-
tools: [...읽기, ...계획,
|
|
192
|
+
tools: [...읽기, ...계획, ...쓰기, ...확인, ...쪼개기],
|
|
138
193
|
effort: 'deep',
|
|
139
194
|
think: 'high',
|
|
140
|
-
steps: 32, // 원인 찾기는 왔다 갔다 하므로 여유를 준다
|
|
141
195
|
say: [
|
|
142
196
|
"지금 하는 일은 **원인 찾기**다. 짐작으로 고치지 마라.",
|
|
143
197
|
"",
|
|
@@ -163,13 +217,12 @@ export const MODES = {
|
|
|
163
217
|
tools: [...읽기, ...계획],
|
|
164
218
|
effort: 'deep',
|
|
165
219
|
think: 'high',
|
|
166
|
-
steps: 16,
|
|
167
220
|
say: [
|
|
168
221
|
"지금 하는 일은 **계획 세우기**다. 파일을 바꾸는 도구는 주어지지 않았다.",
|
|
169
222
|
"코드를 고치려 들지 마라. 계획을 내고 멈춘다.",
|
|
170
223
|
"",
|
|
171
224
|
"먼저 확인하라 — 지금 무엇이 어떻게 되어 있는지 모르면 계획이 아니라 희망이다.",
|
|
172
|
-
" Glob/Grep 으로
|
|
225
|
+
" Outline 으로 모양부터 보고, Glob/Grep 으로 좁힌 뒤, 꼭 필요한 것만 Read 해라.",
|
|
173
226
|
"",
|
|
174
227
|
"그 다음 이 차례로 적어라.",
|
|
175
228
|
" 1. 목표 — 무엇이 끝나면 다 된 것인가 (확인할 수 있는 문장으로)",
|
|
@@ -192,19 +245,32 @@ export const MODES = {
|
|
|
192
245
|
en: 'Orchestrator',
|
|
193
246
|
glyph: '❋',
|
|
194
247
|
hint: '큰 일을 쪼개서 끝까지',
|
|
195
|
-
tools: [...읽기, ...계획,
|
|
248
|
+
tools: [...읽기, ...계획, ...쓰기, ...확인, ...쪼개기],
|
|
196
249
|
effort: 'save',
|
|
197
250
|
think: null,
|
|
198
|
-
|
|
251
|
+
say짧게: [
|
|
252
|
+
'**큰 일을 끝까지 끌고 가기**다.',
|
|
253
|
+
'1. 시작하자마자 TodoWrite 로 전체를 단계로 쪼개 적어라.',
|
|
254
|
+
'2. **단계마다 Task 로 떼어 줘라.** 직접 다 하면 네 창이 차서 하던 일을 잊는다.',
|
|
255
|
+
' 하위는 이 대화를 못 본다 — 배경·정한 것·파일 경로를 다 적어 줘라.',
|
|
256
|
+
'3. 한 번에 하나만 진행 중으로. 끝나면 바로 표시하고 다음으로.',
|
|
257
|
+
'4. 단계 끝마다 Verify.',
|
|
258
|
+
'5. 막히면 멈추고 보고해라. 우회로를 몰래 타지 마라.',
|
|
259
|
+
'다 끝나면 한 것과 못 한 것을 같이 요약해라. 못 한 것을 빼지 마라.',
|
|
260
|
+
].join('\n'),
|
|
199
261
|
say: [
|
|
200
262
|
"지금 하는 일은 **큰 일을 끝까지 끌고 가기**다.",
|
|
201
263
|
"",
|
|
202
264
|
" 1. 시작하자마자 TodoWrite 로 전체를 단계로 쪼개 적어라. 머릿속에만 두지 마라.",
|
|
203
265
|
" 단계는 각각 따로 확인할 수 있는 크기여야 한다.",
|
|
204
|
-
" 2.
|
|
205
|
-
"
|
|
206
|
-
"
|
|
207
|
-
"
|
|
266
|
+
" 2. **단계 하나하나를 Task 로 떼어 줘라.** 이게 이 모드의 핵심이다 —",
|
|
267
|
+
" 네가 직접 다 하면 파일 내용이 전부 네 창에 쌓여서, 서너 단계째에",
|
|
268
|
+
" 앞엣말이 접혀 나가고 무엇을 하던 중이었는지 잊는다.",
|
|
269
|
+
" 하위에게는 배경·정한 것·파일 경로를 다 적어 줘라. 하위는 이 대화를 못 본다.",
|
|
270
|
+
" 3. 한 번에 하나만 진행 중으로 둬라. 끝나면 바로 표시하고 다음으로 넘어가라.",
|
|
271
|
+
" 4. 각 단계 끝에서 Verify 로 확인하라. 확인 없이 넘어가면 어디서 어긋났는지 못 찾는다.",
|
|
272
|
+
" 5. 막히면 멈추고 무엇에 막혔는지 보고하라. 우회로를 몰래 타지 마라.",
|
|
273
|
+
" 6. 계획이 틀린 것을 알게 되면 목록을 고쳐라. 틀린 계획을 끝까지 밀지 마라.",
|
|
208
274
|
"",
|
|
209
275
|
"다 끝나면 무엇을 했는지, 무엇을 못 했는지 같이 요약하라.",
|
|
210
276
|
"못 한 것을 빼고 요약하지 마라.",
|
|
@@ -236,6 +302,21 @@ export function get(id) {
|
|
|
236
302
|
return MODES[normalize(id) ?? DEFAULT];
|
|
237
303
|
}
|
|
238
304
|
|
|
305
|
+
/**
|
|
306
|
+
* 이 창 크기에 맞는 모드 설명.
|
|
307
|
+
*
|
|
308
|
+
* 24k 아래에서는 짧은 판을 쓴다 — 도구 설명(budget.js 의 설명길이)과 기본
|
|
309
|
+
* 규칙(session.js)이 줄어드는 자리와 같은 경계다. 셋이 같이 움직여야
|
|
310
|
+
* '작은 창에서는 고정 몫을 줄인다' 가 흩어진 세 결정이 아니라 한 결정이 된다.
|
|
311
|
+
*
|
|
312
|
+
* 짧은 판이 없는 모드는 원래 짧은 것들이다(묻기 125토큰). 그냥 그대로 쓴다.
|
|
313
|
+
*/
|
|
314
|
+
export function 말(id, ctx) {
|
|
315
|
+
const m = get(id);
|
|
316
|
+
const 좁은가 = Number(ctx) > 0 && Number(ctx) < 24000;
|
|
317
|
+
return (좁은가 && m.say짧게) ? m.say짧게 : m.say;
|
|
318
|
+
}
|
|
319
|
+
|
|
239
320
|
/** Ctrl+O 로 돌릴 때 다음 모드. (Shift+Tab 은 승인 방식이 가져갔다) */
|
|
240
321
|
export function next(id) {
|
|
241
322
|
const i = ORDER.indexOf(normalize(id) ?? DEFAULT);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* 이 폴더가 무슨 프로젝트인가 — 켤 때 한 번 읽어 프롬프트에 넣는다.
|
|
3
|
+
*
|
|
4
|
+
* 왜 필요한가:
|
|
5
|
+
* 남의 코드가 있는 폴더에서 켜면 모델은 아무것도 모르는 채로 시작했다.
|
|
6
|
+
* 그래서 매번 같은 세 걸음을 다시 밟는다 — Glob 으로 위쪽을 훑고,
|
|
7
|
+
* package.json 을 읽고, 검사를 어떻게 돌리는지 찾는다. 로컬 모델은 한 걸음이
|
|
8
|
+
* 20~40초라 **일을 시작하기도 전에 2분**이 간다. 그 세 걸음의 답은 켤 때
|
|
9
|
+
* 이미 다 알 수 있는 것이다.
|
|
10
|
+
*
|
|
11
|
+
* 더 나쁜 쪽도 있다. 모델이 그 세 걸음을 **안 밟고** 그냥 시작하는 경우다.
|
|
12
|
+
* 그러면 이 프로젝트가 이미 쓰는 것을 모른 채 제 관례로 파일을 만든다 —
|
|
13
|
+
* npm 프로젝트에 requirements.txt 를 만들어 놓는 식이다.
|
|
14
|
+
*
|
|
15
|
+
* 무엇을 넣나 (값이 큰 순서):
|
|
16
|
+
* 1. 돌릴 수 있는 명령 — npm scripts 가 곧 '이 프로젝트에서 되는 일' 이다
|
|
17
|
+
* 2. 위쪽 생김새 — Glob 한 번을 아낀다
|
|
18
|
+
* 3. 무슨 갈래인가 — node·python·go·rust·java·c#
|
|
19
|
+
* 4. 지금 git 가지 — 어디에 커밋하게 되는지
|
|
20
|
+
*
|
|
21
|
+
* 무엇을 안 하나:
|
|
22
|
+
* git 을 **띄우지 않는다.** .git/HEAD 를 그냥 읽는다. 켤 때 자식 프로세스를
|
|
23
|
+
* 부르면 큰 저장소에서 몇 초가 걸리고, 그 몇 초는 화면이 멈춘 채로 간다.
|
|
24
|
+
* 안 올린 변경 개수 같은 것은 모델이 필요하면 제 손으로 git 을 부르면 된다.
|
|
25
|
+
*
|
|
26
|
+
* 폴더를 훑지도 않는다. 위쪽 한 겹만 읽는다. 하위까지 내려가면 큰 저장소에서
|
|
27
|
+
* 느려지고, 어차피 프롬프트에 넣을 양은 한 줄뿐이다.
|
|
28
|
+
*
|
|
29
|
+
* 이 글도 **고정 몫**이다 — 매 요청에 통째로 나간다. 그래서 창 크기에 맞춘다.
|
|
30
|
+
* 8k 에서는 두 줄, 큰 창에서는 네 줄. 늘리면 test/compact.test.js 가 먼저 빨개진다.
|
|
31
|
+
*/
|
|
32
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
33
|
+
import { join } from 'node:path';
|
|
34
|
+
|
|
35
|
+
// 위쪽에 있어도 사람에게 아무 말도 안 해 주는 것들. 적어 봐야 자리만 먹는다.
|
|
36
|
+
const 안적을것 = new Set([
|
|
37
|
+
'node_modules', '.git', '.deel', '.claude', '.vscode', '.idea', '__pycache__',
|
|
38
|
+
'.venv', 'venv', 'dist', 'build', 'target', 'out', '.next', '.cache', '.pytest_cache',
|
|
39
|
+
'coverage', '.DS_Store', 'Thumbs.db',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
// 파일 하나로 갈래가 정해지는 것들. 위에 있는 것이 먼저 이긴다.
|
|
43
|
+
const 표식 = [
|
|
44
|
+
{ 파일: 'package.json', 갈래: 'node' },
|
|
45
|
+
{ 파일: 'pyproject.toml', 갈래: 'python' },
|
|
46
|
+
{ 파일: 'requirements.txt', 갈래: 'python' },
|
|
47
|
+
{ 파일: 'go.mod', 갈래: 'go' },
|
|
48
|
+
{ 파일: 'Cargo.toml', 갈래: 'rust' },
|
|
49
|
+
{ 파일: 'pom.xml', 갈래: 'java (maven)' },
|
|
50
|
+
{ 파일: 'build.gradle', 갈래: 'java (gradle)' },
|
|
51
|
+
{ 파일: 'build.gradle.kts', 갈래: 'kotlin (gradle)' },
|
|
52
|
+
{ 파일: 'Gemfile', 갈래: 'ruby' },
|
|
53
|
+
{ 파일: 'composer.json', 갈래: 'php' },
|
|
54
|
+
{ 파일: 'CMakeLists.txt', 갈래: 'c/c++ (cmake)' },
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
/** 위쪽 한 겹. 폴더는 뒤에 / 를 붙여 파일과 구별한다. */
|
|
58
|
+
function 위쪽(root, 상한) {
|
|
59
|
+
let 것들;
|
|
60
|
+
try { 것들 = readdirSync(root); } catch { return { 목록: [], 더: 0 }; }
|
|
61
|
+
const 폴더 = [];
|
|
62
|
+
const 파일 = [];
|
|
63
|
+
for (const n of 것들) {
|
|
64
|
+
if (안적을것.has(n) || n.startsWith('.')) continue;
|
|
65
|
+
try { (statSync(join(root, n)).isDirectory() ? 폴더 : 파일).push(n); }
|
|
66
|
+
catch { /* 읽는 중에 사라졌으면 없는 셈 */ }
|
|
67
|
+
}
|
|
68
|
+
// 폴더가 먼저다. 구조를 먼저 보여 주는 편이 훑을 때 빠르다.
|
|
69
|
+
const 다 = [...폴더.sort().map((n) => `${n}/`), ...파일.sort()];
|
|
70
|
+
return { 목록: 다.slice(0, 상한), 더: Math.max(0, 다.length - 상한) };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* 돌릴 수 있는 명령.
|
|
75
|
+
*
|
|
76
|
+
* 이름을 지어내지 않는다 — 적힌 것만 그대로 옮긴다. 없는 명령을 알려 주면
|
|
77
|
+
* 모델이 그걸 부르고, 실패하고, 다시 찾느라 걸음을 더 쓴다.
|
|
78
|
+
*/
|
|
79
|
+
function 명령들(root, 갈래, 상한) {
|
|
80
|
+
if (갈래 !== 'node') return [];
|
|
81
|
+
try {
|
|
82
|
+
const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'));
|
|
83
|
+
const s = pkg?.scripts;
|
|
84
|
+
if (!s || typeof s !== 'object') return [];
|
|
85
|
+
// 사람이 실제로 부르는 것부터. 나머지는 알파벳순으로 채운다.
|
|
86
|
+
const 앞선것 = ['dev', 'start', 'test', 'build', 'lint', 'typecheck'];
|
|
87
|
+
const 있는것 = Object.keys(s);
|
|
88
|
+
const 골라 = [
|
|
89
|
+
...앞선것.filter((n) => 있는것.includes(n)),
|
|
90
|
+
...있는것.filter((n) => !앞선것.includes(n)).sort(),
|
|
91
|
+
];
|
|
92
|
+
return 골라.slice(0, 상한).map((n) => (n === 'test' || n === 'start' ? `npm ${n}` : `npm run ${n}`));
|
|
93
|
+
} catch { return []; }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** package.json 의 이름. 폴더 이름과 다를 때가 많아 따로 본다. */
|
|
97
|
+
function 이름(root, 갈래) {
|
|
98
|
+
if (갈래 !== 'node') return null;
|
|
99
|
+
try {
|
|
100
|
+
const n = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8'))?.name;
|
|
101
|
+
return typeof n === 'string' && n ? n : null;
|
|
102
|
+
} catch { return null; }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* 지금 git 가지.
|
|
107
|
+
*
|
|
108
|
+
* git 을 안 띄우고 .git/HEAD 를 읽는다. 떨어져 나온 머리(detached HEAD)면
|
|
109
|
+
* 가지 이름 대신 커밋 앞자리가 들어 있는데, 그때는 '가지 없음' 이라고 말한다 —
|
|
110
|
+
* 커밋 해시를 가지 이름처럼 적으면 모델이 그 이름으로 checkout 을 시도한다.
|
|
111
|
+
*/
|
|
112
|
+
export function git가지(root) {
|
|
113
|
+
try {
|
|
114
|
+
const head = readFileSync(join(root, '.git', 'HEAD'), 'utf8').trim();
|
|
115
|
+
const m = head.match(/^ref:\s*refs\/heads\/(.+)$/);
|
|
116
|
+
if (m) return m[1];
|
|
117
|
+
return head ? '가지에 안 붙어 있음 (detached)' : null;
|
|
118
|
+
} catch { return null; }
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* 이 폴더의 지문.
|
|
123
|
+
*
|
|
124
|
+
* @param 창 모델 컨텍스트 길이. 여기에 맞춰 몇 줄까지 적을지 정한다.
|
|
125
|
+
* @returns {string|null} 프롬프트에 넣을 토막. 적을 것이 없으면 null.
|
|
126
|
+
*/
|
|
127
|
+
export function 지문(root, 창 = null) {
|
|
128
|
+
// 좁을수록 적게. 이건 접히지 않는 고정 몫이라, 창의 몇 %를 쓸지가 곧 손해다.
|
|
129
|
+
const n = Number(창) || 0;
|
|
130
|
+
const 넉넉한가 = n >= 32000;
|
|
131
|
+
const 아주좁은가 = n > 0 && n < 12000;
|
|
132
|
+
const 위쪽상한 = 아주좁은가 ? 10 : 넉넉한가 ? 24 : 16;
|
|
133
|
+
const 명령상한 = 아주좁은가 ? 4 : 넉넉한가 ? 8 : 6;
|
|
134
|
+
|
|
135
|
+
let 갈래 = null;
|
|
136
|
+
for (const t of 표식) {
|
|
137
|
+
if (existsSync(join(root, t.파일))) { 갈래 = t.갈래; break; }
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const 줄들 = [];
|
|
141
|
+
const 이 = 이름(root, 갈래);
|
|
142
|
+
const 가지 = git가지(root);
|
|
143
|
+
|
|
144
|
+
const 머리 = [
|
|
145
|
+
갈래 ? `${갈래} 프로젝트${이 ? ` (${이})` : ''}` : null,
|
|
146
|
+
가지 ? `git ${가지}` : null,
|
|
147
|
+
].filter(Boolean).join(' · ');
|
|
148
|
+
if (머리) 줄들.push(머리);
|
|
149
|
+
|
|
150
|
+
const 명 = 명령들(root, 갈래, 명령상한);
|
|
151
|
+
// 명령은 제일 값이 크다. 좁은 창에서 무엇 하나를 남긴다면 이것이다 —
|
|
152
|
+
// '이 프로젝트에서 검사를 어떻게 돌리나' 가 여기 다 들어 있다.
|
|
153
|
+
if (명.length) 줄들.push(`돌릴 수 있는 것: ${명.join(' · ')}`);
|
|
154
|
+
|
|
155
|
+
const w = 위쪽(root, 위쪽상한);
|
|
156
|
+
if (w.목록.length) {
|
|
157
|
+
줄들.push(`위쪽: ${w.목록.join(' ')}${w.더 ? ` (그 밖에 ${w.더}개)` : ''}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (!줄들.length) return null;
|
|
161
|
+
/*
|
|
162
|
+
* 마지막 한 줄이 중요하다.
|
|
163
|
+
*
|
|
164
|
+
* 이 토막만 보고 "다 알았다" 고 넘어가면 안 된다. 여기 적힌 것은 위쪽 한 겹과
|
|
165
|
+
* package.json 뿐이고, 하위 폴더 안은 아무것도 안 봤다. 그 사실을 안 적으면
|
|
166
|
+
* 모델은 이걸 프로젝트 전체 지도로 여기고 Outline 을 안 부른다 —
|
|
167
|
+
* 그러면 이 토막이 오히려 손해가 된다.
|
|
168
|
+
*/
|
|
169
|
+
줄들.push('위쪽 한 겹만 본 것이다. 안을 알아야 하면 Outline 을 불러라.');
|
|
170
|
+
return `\n--- 이 폴더 ---\n${줄들.join('\n')}`;
|
|
171
|
+
}
|
package/src/agent/route.js
CHANGED
|
@@ -110,15 +110,66 @@ const 문턱 = {
|
|
|
110
110
|
ask: 5,
|
|
111
111
|
};
|
|
112
112
|
|
|
113
|
+
/*
|
|
114
|
+
* ── 겹친 요청 ───────────────────────────────────────────────────────────
|
|
115
|
+
*
|
|
116
|
+
* "계획해주고 만들어줘" 처럼 **계획과 실행이 한 말에 같이** 든 것.
|
|
117
|
+
*
|
|
118
|
+
* 이건 위의 점수표로는 못 고른다. '만들어' 가 code 3점 문턱을 그냥 넘어서
|
|
119
|
+
* 곧장 코드 모드로 가 버리고, 사람이 보자고 한 계획은 안 나온다.
|
|
120
|
+
* 실제로 겪었다 — "정리해서 만들어줘" 가 계획 한 줄 없이 파일부터 만들었다.
|
|
121
|
+
*
|
|
122
|
+
* 반대로 plan 으로 보내면 계획만 내고 멈춘다. 그것도 시킨 일의 절반이다.
|
|
123
|
+
* 그래서 따로 알아본다. 계획을 내고 → 승인을 받고 → 그대로 실행까지 잇는다.
|
|
124
|
+
*
|
|
125
|
+
* 좁게 잡는다. 사용자는 자율 실행을 원했고, 승인 창은 그 결정을 갉아먹는
|
|
126
|
+
* 쪽이다. '고쳐줘' 한마디에 창이 뜨면 그게 더 나쁘다.
|
|
127
|
+
* 그래서 **계획말 + 잇는말** 이 앞에 있고 그 **뒤에** 실행말이 와야만 겹침이다.
|
|
128
|
+
*/
|
|
129
|
+
const 계획말 = '계획|플랜|설계|구상|기획|정리|검토|조사|파악|살펴|알아보|분석|로드맵|방향|순서|밑그림|초안';
|
|
130
|
+
// '정리하고' 처럼 잇는 꼴이어야 한다. 그냥 '정리' 하나로는 겹침이 아니다 —
|
|
131
|
+
// "코드 정리해줘" 는 계획이 아니라 그 자체가 할 일이다.
|
|
132
|
+
const 잇는말 = '해\\s*주고|해\\s*놓고|하고\\s*나서|하고서|한\\s*다음|한\\s*뒤|한\\s*후|하고|해서|잡고|짜고|세우고|낸\\s*뒤|낸\\s*다음';
|
|
133
|
+
const 실행말 = /만들어|만들자|만들어라|구현|작성|추가(해|하)|생성|써\s*(줘|주세요)|짜\s*(줘|주세요)|고쳐|세팅|설치|배포/;
|
|
134
|
+
const 계획먼저 = new RegExp(`(${계획말})\\s*(?:을|를)?\\s*(?:${잇는말})`);
|
|
135
|
+
// 영어도 같이 본다 — "plan and then build it" 처럼 붙여 넣는 일이 있다.
|
|
136
|
+
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
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 계획과 실행이 한 말에 같이 들었나.
|
|
140
|
+
*
|
|
141
|
+
* @returns {{겹침: boolean, why: string}} 겹침이 아니면 why 는 빈 글.
|
|
142
|
+
*/
|
|
143
|
+
export function 겹친요청(text) {
|
|
144
|
+
const s = String(text ?? '');
|
|
145
|
+
const 영어 = s.match(영어겹침);
|
|
146
|
+
if (영어) return { 겹침: true, why: `'${영어[1]}' 과 '${영어[2]}'` };
|
|
147
|
+
|
|
148
|
+
const 앞 = s.match(계획먼저);
|
|
149
|
+
if (!앞) return { 겹침: false, why: '' };
|
|
150
|
+
// 실행말이 계획말 **뒤에** 와야 한다. "만들고 나서 정리해줘" 는 겹침이 아니다 —
|
|
151
|
+
// 그건 만드는 일이 먼저고, 계획을 미리 볼 것이 없다.
|
|
152
|
+
const 뒤 = s.slice(앞.index + 앞[0].length).match(실행말);
|
|
153
|
+
if (!뒤) return { 겹침: false, why: '' };
|
|
154
|
+
return { 겹침: true, why: `'${앞[0].trim()}' 뒤에 '${뒤[0].trim()}'` };
|
|
155
|
+
}
|
|
156
|
+
|
|
113
157
|
/**
|
|
114
158
|
* 이 한마디가 무슨 일인지 고른다.
|
|
115
159
|
*
|
|
116
160
|
* @param {string} text 사용자가 친 말
|
|
117
|
-
* @returns {{ mode: string|null, score: number, why: string, 점수들: object }}
|
|
161
|
+
* @returns {{ mode: string|null, score: number, why: string, 겹침: boolean, 점수들: object }}
|
|
118
162
|
* mode 가 null 이면 '모르겠다' 는 뜻이다 — 종합 모드에 그대로 둔다.
|
|
163
|
+
* 겹침이 true 면 계획을 내고 승인을 받아 실행까지 이어야 한다는 뜻이다.
|
|
119
164
|
*/
|
|
120
165
|
export function route(text) {
|
|
121
166
|
const s = String(text ?? '');
|
|
167
|
+
|
|
168
|
+
// 겹친 요청은 점수표보다 먼저 본다. 점수표에 맡기면 '만들어' 가 이겨서
|
|
169
|
+
// 계획을 건너뛴다 — 그게 바로 이 길을 만든 이유다.
|
|
170
|
+
const 겹 = 겹친요청(s);
|
|
171
|
+
if (겹.겹침) return { mode: 'plan', score: 0, why: 겹.why, 겹침: true, 점수들: {} };
|
|
172
|
+
|
|
122
173
|
const 점수들 = {};
|
|
123
174
|
const 근거 = {};
|
|
124
175
|
|
|
@@ -138,19 +189,20 @@ export function route(text) {
|
|
|
138
189
|
const 순위 = Object.entries(점수들).sort((a, b) => b[1] - a[1]);
|
|
139
190
|
const [으뜸, 점수] = 순위[0] ?? [null, 0];
|
|
140
191
|
if (!으뜸 || 점수 < (문턱[으뜸] ?? 4)) {
|
|
141
|
-
return { mode: null, score: 점수, why: '무슨 일인지 뚜렷하지 않음', 점수들 };
|
|
192
|
+
return { mode: null, score: 점수, why: '무슨 일인지 뚜렷하지 않음', 겹침: false, 점수들 };
|
|
142
193
|
}
|
|
143
194
|
|
|
144
195
|
// 1·2등이 붙으면 고르지 않는다. 반반인 것을 억지로 정하면 절반은 틀린다.
|
|
145
196
|
const 둘째 = 순위[1]?.[1] ?? 0;
|
|
146
197
|
if (점수 - 둘째 < 2 && 둘째 >= (문턱[순위[1][0]] ?? 4)) {
|
|
147
|
-
return { mode: null, score: 점수, why: `${으뜸} 과 ${순위[1][0]} 가 비슷함`, 점수들 };
|
|
198
|
+
return { mode: null, score: 점수, why: `${으뜸} 과 ${순위[1][0]} 가 비슷함`, 겹침: false, 점수들 };
|
|
148
199
|
}
|
|
149
200
|
|
|
150
201
|
return {
|
|
151
202
|
mode: 으뜸,
|
|
152
203
|
score: 점수,
|
|
153
204
|
why: 근거[으뜸].slice(0, 2).map((x) => `'${x}'`).join(', '),
|
|
205
|
+
겹침: false,
|
|
154
206
|
점수들,
|
|
155
207
|
};
|
|
156
208
|
}
|