leerness 1.9.186 โ†’ 1.9.188

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/CHANGELOG.md CHANGED
@@ -1,5 +1,215 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.188 โ€” 2026-05-21
4
+
5
+ **๐Ÿ› REPL ํ•œ๊ธ€ prompt ์ „๋‹ฌ BUG fix (stdin) + ์„ธ๋ถ€ ๋ชจ๋ธ ํ‘œ์‹œ + ์ž…๋ ฅ ๊ตฌ๋ถ„์„  (์‚ฌ์šฉ์ž ๋ช…์‹œ 3์ข…).**
6
+
7
+ ์ž์œจ ๋ชจ๋“œ 118 ๋ผ์šด๋“œ. ์‚ฌ์šฉ์ž ๋ช…์‹œ ํ•ต์‹ฌ ๋ฒ„๊ทธ:
8
+ ```
9
+ agent[claude/actor/โ–ถ]> ์ด ํด๋”์— ํŒŒ์ด์ฌ ํ”„๋กœ๊ทธ๋žจ์„ ํ•˜๋‚˜ ์ œ์ž‘ํ•ด์ค˜
10
+ โ”€โ”€ claude stream โ”€โ”€
11
+ ์ €๋Š” ์ด ํ”„๋กœ์ ํŠธ์˜ **์ˆ˜์„ ๊ฐœ๋ฐœ์ž์ด์ž ํ”„๋กœ์ ํŠธ ๋งค๋‹ˆ์ €**์ž…๋‹ˆ๋‹ค.
12
+ ...
13
+ ๋ฉ”์‹œ์ง€๊ฐ€ "์—ญํ• :"๋กœ๋งŒ ๋๋‚˜์„œ ์˜๋„๋ฅผ ํ™•์‹ ํ•˜๊ธฐ ์–ด๋ ต์Šต๋‹ˆ๋‹ค.
14
+ ```
15
+ ์‚ฌ์šฉ์ž ์˜๋„์™€ ๋ฌด๊ด€ํ•œ ์‘๋‹ต โ€” **claude๊ฐ€ ํ•œ๊ธ€ prompt ๋๋ถ€๋ถ„("์—ญํ• :") fragment๋งŒ ๋ฐ›์Œ**.
16
+
17
+ ### ๊ทผ๋ณธ ์›์ธ (์ฝ”๋“œ audit)
18
+
19
+ `cp.spawn(cmd, args, { shell: true })` + Windows cmd.exe + ํ•œ๊ธ€/ํŠน์ˆ˜๋ฌธ์ž promptText:
20
+ - shell:true ์ผ ๋•Œ args ๊ฐ€ join๋˜์–ด shell command line ํ˜•์„ฑ
21
+ - Windows cmd.exe๊ฐ€ ํ•œ๊ธ€/๊ณต๋ฐฑ escape ์‹คํŒจ โ†’ claude์— ์ผ๋ถ€ fragment๋งŒ ์ „๋‹ฌ
22
+
23
+ ### Fix #1 โ€” prompt๋ฅผ stdin์œผ๋กœ ์ „๋‹ฌ (shell escape ์šฐํšŒ)
24
+
25
+ ```diff
26
+ - if (provider === 'claude') { cmd = 'claude'; args = ['--print', promptText]; }
27
+ + // 1.9.188: promptText ๋Š” stdin ์œผ๋กœ โ†’ args ์—์„œ ์ œ๊ฑฐ (ํ•œ๊ธ€/ํŠน์ˆ˜๋ฌธ์ž ์•ˆ์ „)
28
+ + let useStdinForPrompt = false;
29
+ + if (provider === 'claude') {
30
+ + cmd = 'claude';
31
+ + args = ['--print']; // promptText ์ œ๊ฑฐ
32
+ + useStdinForPrompt = true;
33
+ + }
34
+ ```
35
+
36
+ spawn ์‹œ:
37
+ ```js
38
+ child = cp.spawn(cmd, args, {
39
+ stdio: [useStdinForPrompt ? 'pipe' : 'ignore', 'pipe', 'pipe'],
40
+ shell: true
41
+ });
42
+ if (useStdinForPrompt && child.stdin) {
43
+ child.stdin.write(promptText);
44
+ child.stdin.end();
45
+ }
46
+ ```
47
+
48
+ `_cliChat` ๋„ ๋™์ผํ•˜๊ฒŒ `runCommandSafe(..., { input: stdinInput })` ์œผ๋กœ ๋ณ€๊ฒฝ.
49
+
50
+ ### Live ๊ฒ€์ฆ (ํ•œ๊ธ€ prompt ์ •ํ™• ์ „๋‹ฌ)
51
+ ```bash
52
+ $ leerness agents multi "์ด ํด๋”์— ํŒŒ์ด์ฌ hello world ํ”„๋กœ๊ทธ๋žจ ์ œ์ž‘ (์‘๋‹ต๋งŒ ์ฝ”๋“œ)" --only claude --execute
53
+ โœ“ claude ยท 8279ms ยท 9 ํ† ํฐ
54
+ --- ์ฒ˜์Œ 600์ž ---
55
+ `hello.py` ์ƒ์„ฑ ๊ถŒํ•œ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์Šน์ธํ•ด ์ฃผ์„ธ์š”.
56
+ ```
57
+ **1.9.187๊นŒ์ง€**: "์ˆ˜์„ ๊ฐœ๋ฐœ์ž/ํ”„๋กœ์ ํŠธ ๋งค๋‹ˆ์ €..." ๊ฐ™์€ ๋ฌด๊ด€ํ•œ ์‘๋‹ต (์—ญํ•  fragment๋งŒ ๋ฐ›์Œ)
58
+ **1.9.188**: `hello.py ์ƒ์„ฑ ๊ถŒํ•œ ํ•„์š”` โ€” ์ •ํ™•ํ•œ ์š”์ฒญ ์ดํ•ด โœ“
59
+
60
+ ### Fix #2 โ€” ์„ธ๋ถ€ ๋ชจ๋ธ ํ‘œ์‹œ
61
+ **Before**: `โšก provider=claude ยท model=(๊ธฐ๋ณธ)`
62
+ **After**: `โšก provider=claude ยท model=claude-opus-4-7`
63
+
64
+ ```js
65
+ // 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ): state.model ์ž๋™ default (catalog ์ฒซ ๋ชจ๋ธ)
66
+ if (!state.model) {
67
+ const cat = _PROVIDER_MODEL_CATALOG[state.provider];
68
+ if (cat && cat.length) state.model = cat[0].id;
69
+ }
70
+ ```
71
+
72
+ prompt ๋„ ์„ธ๋ถ€ ๋ชจ๋ธ ๋ช…์‹œ:
73
+ ```
74
+ Before: agent[claude/actor/โ–ถ]>
75
+ After: agent[claude ยท opus-4-7/actor/โ–ถ]>
76
+ ```
77
+
78
+ ### Fix #3 โ€” ์ž…๋ ฅ ๊ตฌ๋ถ„์„  (Hermes UX ์˜๊ฐ)
79
+ ๊ฐ ์‘๋‹ต ๋์— ๊ฐ€๋กœ ๋””๋ฐ”์ด๋” ์ž๋™ ์ถœ๋ ฅ โ†’ ์ž…๋ ฅ ์˜์—ญ ์‹œ๊ฐ ๋ช…ํ™•:
80
+
81
+ ```
82
+ [assistant: claude/claude-opus-4-7, role=actor, 4598ms ยท 425์ž]
83
+
84
+ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€...
85
+ agent[claude ยท opus-4-7/actor/โ–ถ]> _
86
+ ```
87
+
88
+ `_printInputDivider()` ํ•จ์ˆ˜ ๋„์ž… โ€” ํ„ฐ๋ฏธ๋„ width ๊ธฐ์ค€ ์ž๋™ ๊ธธ์ด.
89
+
90
+ ### Verified
91
+ - stress-v133: **15/15 PASS** (์‚ฌ์šฉ์ž ๋ช…์‹œ 6 + live 2 + ๋ˆ„์  7)
92
+ - e2e 217/217 baseline ์œ ์ง€
93
+ - live ๊ฒ€์ฆ:
94
+ - ํ•œ๊ธ€ prompt "์ด ํด๋”์— ํŒŒ์ด์ฌ hello world ์ œ์ž‘" โ†’ claude ์ •ํ™• ์‘๋‹ต (12.5์ดˆ)
95
+ - ์˜๋ฌธ prompt "1+1=?" โ†’ claude ์‘๋‹ต (9.3์ดˆ, ํšŒ๊ท€ ์—†์Œ)
96
+ - VERSION = 1.9.188 ยท autonomous-rounds = 118 ยท main ์ž๋™ push 49 ๋ผ์šด๋“œ ์—ฐ์†
97
+
98
+ ---
99
+
100
+ ## 1.9.187 โ€” 2026-05-21
101
+
102
+ **๐Ÿ”“ ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ •์„ .env โ†’ .harness/leerness-config.json ์œผ๋กœ ๋ถ„๋ฆฌ (AI ๊ฐ€์‹œ์„ฑ).**
103
+
104
+ ์ž์œจ ๋ชจ๋“œ 117 ๋ผ์šด๋“œ. ์‚ฌ์šฉ์ž ๋ช…์‹œ: *".env์— ์ž…๋ ฅ๋˜์–ด์„œ AI ์—์ด์ „ํŠธ๊ฐ€ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ ์ฝ์„ ์ˆ˜ ์—†๋Š” ์œ„์น˜ํ•˜๋ฉด ์ž…๋ ฅ๋˜๋Š” ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝ"*.
105
+
106
+ ### ๋ฐฐ๊ฒฝ & ๋ถ„๋ฆฌ ์ •์ฑ…
107
+
108
+ | ์œ„์น˜ | ๋ณด์•ˆ ์ •์ฑ… | ์šฉ๋„ |
109
+ |---|---|---|
110
+ | `.env` (.gitignore) | ์‹œํฌ๋ฆฟ only | NPM_TOKEN, GITHUB_TOKEN, *_API_KEY |
111
+ | `.harness/leerness-config.json` (git checked-in) | ๋น„์‹œํฌ๋ฆฟ only | ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ, ๊ณต๊ฐœ URL, ๋ชจ๋ธ ์ด๋ฆ„ |
112
+
113
+ **ํ•ต์‹ฌ**: `.env`๋Š” .gitignore + ์‹œํฌ๋ฆฟ์šฉ โ†’ ๋‹ค๋ฅธ AI ์—์ด์ „ํŠธ(Claude Code, Cursor, Copilot ๋“ฑ)๊ฐ€ ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ฝ์„ ๋•Œ ์ž๋™ ๋…ธ์ถœ X. ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ •์„ AI ๊ฐ€์‹œ ์œ„์น˜๋กœ ์˜ฎ๊ฒจ **AI ์—์ด์ „ํŠธ ์ธ์ง€๋„ โ†‘**.
114
+
115
+ ### ๋ถ„๋ฆฌ ๋Œ€์ƒ (11๊ฐœ ๋น„์‹œํฌ๋ฆฟ ํ‚ค)
116
+ ```js
117
+ const _LEERNESS_NONSECRET_KEYS = new Set([
118
+ 'LEERNESS_OLLAMA_BASE_URL', // localhost URL โ€” ๋น„๋ฐ€ X
119
+ 'LEERNESS_OLLAMA_MODEL', // ๋ชจ๋ธ ์ด๋ฆ„ โ€” ๋น„๋ฐ€ X
120
+ 'LEERNESS_ENABLE_CLAUDE', // ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ
121
+ 'LEERNESS_ENABLE_CODEX',
122
+ 'LEERNESS_ENABLE_GEMINI',
123
+ 'LEERNESS_ENABLE_COPILOT',
124
+ 'LEERNESS_ENABLE_OLLAMA',
125
+ 'LEERNESS_SKILL_DISCOVER_URL', // ๊ณต๊ฐœ URL
126
+ 'LEERNESS_SKILL_AUTO_DISCOVER',
127
+ 'LEERNESS_SKILL_AUTO_INSTALL',
128
+ 'LEERNESS_SKILL_AUTO_PRESETS'
129
+ ]);
130
+ ```
131
+
132
+ ### .harness/leerness-config.json ์˜ˆ์‹œ
133
+ ```json
134
+ {
135
+ "_comment": "leerness ๋น„์‹œํฌ๋ฆฟ ์„ค์ •. AI ์—์ด์ „ํŠธ๊ฐ€ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์œ„์น˜ (git checked-in). ์‹œํฌ๋ฆฟ(TOKEN/SECRET/PASSWORD)์€ .env ์‚ฌ์šฉ.",
136
+ "_docs": "https://github.com/gugu9999gu/leerness#config",
137
+ "_version": "1.9.187",
138
+ "LEERNESS_OLLAMA_BASE_URL": "http://localhost:11434",
139
+ "LEERNESS_OLLAMA_MODEL": "llama3",
140
+ "LEERNESS_ENABLE_CLAUDE": "1",
141
+ "LEERNESS_ENABLE_CODEX": "1",
142
+ "LEERNESS_ENABLE_GEMINI": "0",
143
+ "LEERNESS_ENABLE_COPILOT": "0",
144
+ "LEERNESS_ENABLE_OLLAMA": "0",
145
+ "LEERNESS_SKILL_DISCOVER_URL": "",
146
+ "LEERNESS_SKILL_AUTO_DISCOVER": "0",
147
+ "LEERNESS_SKILL_AUTO_INSTALL": "0",
148
+ "LEERNESS_SKILL_AUTO_PRESETS": "vercel,anthropic"
149
+ }
150
+ ```
151
+
152
+ ### .env (simplified)
153
+ ```
154
+ # Leerness โ€” SECRET ํ™˜๊ฒฝ๋ณ€์ˆ˜๋งŒ (TOKEN/SECRET/PASSWORD). ๋น„์‹œํฌ๋ฆฟ ์„ค์ •์€ .harness/leerness-config.json ์‚ฌ์šฉ.
155
+ # .env ๋Š” .gitignore โ€” AI ์—์ด์ „ํŠธ(Claude Code, Cursor ๋“ฑ)์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ. ์‹œํฌ๋ฆฟ ์•ˆ์ „ ๋ณด๊ด€.
156
+ # ๋น„์‹œํฌ๋ฆฟ (ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ/๋ชจ๋ธ ์ด๋ฆ„/๊ณต๊ฐœ URL) ์€ .harness/leerness-config.json ์ฐธ์กฐ โ†’ AI ๊ฐ€์‹œ์„ฑ โ†‘.
157
+
158
+ # === ์‹œํฌ๋ฆฟ (TOKEN/KEY) ===
159
+ LEERNESS_NPM_TOKEN=
160
+ LEERNESS_GITHUB_TOKEN=
161
+ ```
162
+
163
+ ### ๋ณด์•ˆ ๊ฐ€๋“œ (์ด์ค‘ ์•ˆ์ „๋ง)
164
+ - **load ๊ฐ€๋“œ**: `_loadLeernessConfig` ์—์„œ `_isSecretKey(k)` ๋งค์น˜ ์‹œ inject ์ฐจ๋‹จ
165
+ - **write ๊ฐ€๋“œ**: `_writeLeernessConfig` ์—์„œ ์‹œํฌ๋ฆฟ ํŒจํ„ด ์ž๋™ ์ œ๊ฑฐ (์ž˜๋ชป ๋“ค์–ด๊ฐ„ ํ‚ค sanitize)
166
+ - **์ •๊ทœ์‹**: `/TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE/i`
167
+
168
+ ### ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (1.9.186 ์ด์ „ .env)
169
+ `_migrateNonsecretFromEnv(root)` โ€” ๊ธฐ์กด .env ์— ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ๊ฐ€ ์žˆ์œผ๋ฉด:
170
+ 1. config ํŒŒ์ผ๋กœ ์ž๋™ ์ด๋™
171
+ 2. .env ์—์„œ ์ œ๊ฑฐ (์ง์ „ # ์ฃผ์„๋„ ํ•จ๊ป˜)
172
+ 3. ์‹œํฌ๋ฆฟ (TOKEN/KEY/PASSWORD) ์€ ์ ˆ๋Œ€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ (๋ณด์•ˆ first)
173
+
174
+ ### ์šฐ์„ ์ˆœ์œ„ (ํ˜ธํ™˜์„ฑ)
175
+ ```
176
+ 1. process.env (์ด๋ฏธ ์„ค์ •๋œ ํ™˜๊ฒฝ๋ณ€์ˆ˜)
177
+ 2. .env ํŒŒ์ผ
178
+ 3. .harness/leerness-config.json
179
+ 4. ์ฝ”๋“œ defaults
180
+ ```
181
+
182
+ ๊ธฐ์กด `process.env.LEERNESS_*` ์ง์ ‘ ์ ‘๊ทผ ์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝ X. leerness ์‹œ์ž‘ ์‹œ `_loadEnvFile()` ๊ฐ€ `_loadLeernessConfig()` ๋„ ์ž๋™ ํ˜ธ์ถœ โ†’ config ๊ฐ’์„ process.env ๋กœ inject.
183
+
184
+ ### Live ๊ฒ€์ฆ (์‚ฌ์šฉ์ž ์›Œํฌ์ŠคํŽ˜์ด์Šค ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
185
+ ```bash
186
+ # Before (1.9.186 .env)
187
+ LEERNESS_NPM_TOKEN=test-token-12345
188
+ LEERNESS_OLLAMA_BASE_URL=http://localhost:11434
189
+ LEERNESS_OLLAMA_MODEL=llama3
190
+ LEERNESS_ENABLE_CLAUDE=1
191
+ ...
192
+
193
+ # After leerness init (1.9.187)
194
+ .env:
195
+ LEERNESS_NPM_TOKEN=test-token-12345 โ† ์‹œํฌ๋ฆฟ ๋ณด์กด
196
+ LEERNESS_GITHUB_TOKEN=
197
+
198
+ .harness/leerness-config.json:
199
+ LEERNESS_OLLAMA_BASE_URL: "http://localhost:11434"
200
+ LEERNESS_OLLAMA_MODEL: "llama3"
201
+ LEERNESS_ENABLE_CLAUDE: "1"
202
+ ...
203
+ ```
204
+
205
+ ### Verified
206
+ - stress-v132: **19/19 PASS** (์‚ฌ์šฉ์ž ๋ช…์‹œ 7 + live 3 + ๋ˆ„์  9)
207
+ - e2e 217/217 baseline ์œ ์ง€
208
+ - live ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒ€์ฆ: ์‹œํฌ๋ฆฟ ์†์‹ค 0๊ฑด, ๋น„์‹œํฌ๋ฆฟ 100% ์ด๋™
209
+ - VERSION = 1.9.187 ยท autonomous-rounds = 117 ยท main ์ž๋™ push 48 ๋ผ์šด๋“œ ์—ฐ์†
210
+
211
+ ---
212
+
3
213
  ## 1.9.186 โ€” 2026-05-21
4
214
 
5
215
  **๐Ÿ› REPL claude stream 0์ž ์‘๋‹ต BUG fix + ๊ตฌ์กฐ ์ตœ์ ํ™” ์ฒดํฌ (์‚ฌ์šฉ์ž ๋ช…์‹œ ํ•ต์‹ฌ ๋ฒ„๊ทธ).**
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **AI ์ฝ”๋”ฉ ์—์ด์ „ํŠธ์˜ ๊ฑฐ์ง“ ์™„๋ฃŒยท์ค‘๋ณตยท๋ง๊ฐยท์ถฉ๋Œ์„ ๋ง‰์•„์ฃผ๋Š” ๊ฒ€์ˆ˜ยท๊ธฐ์–ตยทํ˜‘์—… CLI ํ•˜๋„ค์Šค.**
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.186-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v131-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-116-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-47_rounds-success)]() [![repl-fix](https://img.shields.io/badge/REPL_stream-0์ž_์‘๋‹ต_BUG_fix-success)]() [![structure](https://img.shields.io/badge/๊ตฌ์กฐ_์ตœ์ ํ™”-์ฒดํฌ_์™„๋ฃŒ-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-์ฆ‰์‹œ_์‚ฌ์šฉ_๊ฐ€๋Šฅ-success)]() [![dep-child](https://img.shields.io/badge/DEP0190-๋ถ€๋ชจ%2B์ž์‹_์ „ํŒŒ-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.188-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v133-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-118-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-49_rounds-success)]() [![hangul-fix](https://img.shields.io/badge/ํ•œ๊ธ€_prompt-stdin_์ •ํ™•์ „๋‹ฌ-success)]() [![model-show](https://img.shields.io/badge/์„ธ๋ถ€๋ชจ๋ธ-prompt%2B์ƒํƒœ๋ฐ”_ํ‘œ์‹œ-success)]() [![divider](https://img.shields.io/badge/์ž…๋ ฅ_๊ตฌ๋ถ„์„ -์ž๋™์ถœ๋ ฅ-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-์ฆ‰์‹œ_์‚ฌ์šฉ_๊ฐ€๋Šฅ-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  โ•”โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•—
@@ -12,9 +12,9 @@
12
12
  โ•‘ โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ• โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•”โ•โ•โ• โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘ โ•‘
13
13
  โ•‘ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘ โ•‘
14
14
  โ•‘ โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ• โ•šโ•โ•โ•šโ•โ• โ•šโ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•‘
15
- โ•‘ v1.9.186 AI Agent Reliability Harness + Sandbox โ•‘
15
+ โ•‘ v1.9.188 AI Agent Reliability Harness + Sandbox โ•‘
16
16
  โ•‘ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ•‘
17
- โ•‘ ๐Ÿ› REPL stream 0์ž ์‘๋‹ต BUG fix ยท ๊ตฌ์กฐ ์ตœ์ ํ™” ์ฒดํฌ โ•‘
17
+ โ•‘ ๐Ÿ› ํ•œ๊ธ€ prompt stdin ์ „๋‹ฌ ยท ์„ธ๋ถ€๋ชจ๋ธ ํ‘œ์‹œ ยท ์ž…๋ ฅ ๊ตฌ๋ถ„์„  โ•‘
18
18
  โ•šโ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•โ•
19
19
  ```
20
20
 
package/bin/harness.js CHANGED
@@ -7,7 +7,7 @@ const cp = require('child_process');
7
7
  const os = require('os'); // 1.9.178: _publishToNpm ์—์„œ os.tmpdir() ์‚ฌ์šฉ (์ „์—ญ import)
8
8
  const readline = require('readline');
9
9
 
10
- const VERSION = '1.9.186';
10
+ const VERSION = '1.9.188';
11
11
 
12
12
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning ์–ต์ œ (์‚ฌ์šฉ์ž ๋ช…์‹œ).
13
13
  // leerness ๋Š” cross-platform PATH resolution ์„ ์œ„ํ•ด shell: true ๋ฅผ ์˜๋„์ ์œผ๋กœ ์‚ฌ์šฉ (claude.cmd / ollama.cmd ๋“ฑ Windows .cmd ์ฒ˜๋ฆฌ).
@@ -904,44 +904,50 @@ async function install(root, opts = {}) {
904
904
  return new Set([a]); // back-compat: ๋‹จ์ผ ๋ฌธ์ž์—ด
905
905
  })();
906
906
  const enable = (cli) => enabledSet.has(cli);
907
- // 1.9.153: .env.example ์€ ํ…œํ”Œ๋ฆฟ (๋ฐฐํฌ ๊ฐ€๋Šฅ, ์‹ค์ œ ์‹œํฌ๋ฆฟ ๊ฐ’ ์—†์Œ)
908
- // .env ๋Š” ์‹ค ์‚ฌ์šฉ ํŒŒ์ผ โ€” ์‚ฌ์šฉ์ž๊ฐ€ ํ† ํฐ ์ฑ„์›Œ ๋„ฃ์Œ. ๋ณด์•ˆ ์ •์ฑ…: ํ† ํฐ ๊ฐ’์€ ์ ˆ๋Œ€ ์ž๋™ ์ฑ„์šฐ์ง€ ์•Š์Œ (ํ‚ค๋งŒ).
909
- // .gitignore ์— .env ๊ฐ€ ๋“ค์–ด๊ฐ€ ์žˆ์–ด์•ผ ํ•จ (audit ๊ฐ€ ์ž๋™ ๊ฒ€์ฆ). mergeLinesFile ์€ ๊ธฐ์กด ํ‚ค ์œ ์ง€ + ์‹ ๊ทœ ์ถ”๊ฐ€.
907
+ // 1.9.187 (์‚ฌ์šฉ์ž ๋ช…์‹œ): .env ์—๋Š” ์‹œํฌ๋ฆฟ๋งŒ, ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ๋Š” .harness/leerness-config.json ์œผ๋กœ ๋ถ„๋ฆฌ.
908
+ // ๋ฐฐ๊ฒฝ: .env ๋Š” .gitignore + ์‹œํฌ๋ฆฟ์šฉ โ†’ AI ์—์ด์ „ํŠธ ๊ฐ€์‹œ์„ฑ ์—†์Œ.
909
+ // ๋น„์‹œํฌ๋ฆฟ (ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ, ๋ชจ๋ธ, ๊ณต๊ฐœ URL) ์€ git checked-in ์œ„์น˜ (.harness/) ์— ๋‘๋ฉด AI ๊ฐ€ ์ž๋™ ์ฝ์Œ.
910
+ // ํ˜ธํ™˜์„ฑ: leerness ์‹œ์ž‘ ์‹œ _loadLeernessConfig() ๊ฐ€ config โ†’ process.env inject. ๊ธฐ์กด ์ฝ”๋“œ ๊ทธ๋Œ€๋กœ ์ž‘๋™.
910
911
  const envLines = [
911
- '# Leerness โ€” environment variable names only. Do not commit real secrets (this file is in .gitignore).',
912
+ '# Leerness โ€” SECRET ํ™˜๊ฒฝ๋ณ€์ˆ˜๋งŒ (TOKEN/SECRET/PASSWORD). ๋น„์‹œํฌ๋ฆฟ ์„ค์ •์€ .harness/leerness-config.json ์‚ฌ์šฉ.',
912
913
  `# Generated/migrated by leerness v${VERSION} at ${new Date().toISOString().slice(0, 10)}.`,
913
- 'LEERNESS_NPM_TOKEN=','LEERNESS_GITHUB_TOKEN=',
914
- '# 1.9.22 โ€” orchestrate opt-in. URL์ด ์„ค์ •๋˜๋ฉด leerness๊ฐ€ Ollama๋ฅผ ์‚ฌ์šฉ ๊ฐ€๋Šฅ. ๋ฏธ์„ค์ • ์‹œ LLM ํ˜ธ์ถœ ์ž๋™ ์‹œ์ž‘ ๊ธˆ์ง€.',
915
- `LEERNESS_OLLAMA_BASE_URL=${enable('ollama') ? 'http://localhost:11434' : ''}`,
916
- '# ์„ ํƒ. ๊ธฐ๋ณธ ๋ชจ๋ธ (orchestrate --model ๋กœ override ๊ฐ€๋Šฅ). ์˜ˆ: llama3 / qwen2.5-coder / gpt-oss',
917
- 'LEERNESS_OLLAMA_MODEL=',
918
- '# 1.9.30+1.9.146+1.9.151 โ€” ์™ธ๋ถ€ AI CLI ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ (๋ณต์ˆ˜ ์„ ํƒ). 1=ํ™œ์„ฑ, 0/๋ฏธ์„ค์ •=๋น„ํ™œ์„ฑ. ๋ฉ”์ธ ์—์ด์ „ํŠธ๊ฐ€ sub-agent ๋ถ„๋ฐฐ ์‹œ ํ™œ์„ฑ CLI๋“ค์— ์ž‘์—… ์œ„์ž„ ๊ฐ€๋Šฅ.',
919
- `LEERNESS_ENABLE_CLAUDE=${enable('claude') ? 1 : 0}`,
920
- `LEERNESS_ENABLE_CODEX=${enable('codex') ? 1 : 0}`,
921
- `LEERNESS_ENABLE_GEMINI=${enable('gemini') ? 1 : 0}`,
922
- `LEERNESS_ENABLE_COPILOT=${enable('copilot') ? 1 : 0}`,
923
- `LEERNESS_ENABLE_OLLAMA=${enable('ollama') ? 1 : 0}`,
924
- '# 1.9.42 โ€” agentskills.io ๊ณต๊ฐœ ํ‘œ์ค€ ์Šคํ‚ฌ ์ž๋™ ํƒ์ƒ‰ (opt-in). URL ์„ค์ • ์‹œ `leerness skill discover` ์‚ฌ์šฉ ๊ฐ€๋Šฅ.',
925
- '# ์˜ˆ์‹œ URL: https://agentskills.io/llms.txt',
926
- 'LEERNESS_SKILL_DISCOVER_URL=',
927
- '# (์„ ํƒ) ์‚ฌ์šฉ์ž ์š”์ฒญ ๋ถ„์„ ์‹œ ์ž๋™ ๋งค์นญ ์Šคํ‚ฌ ์ถ”์ฒœ. 1=ํ™œ์„ฑ, 0/๋ฏธ์„ค์ •=๋น„ํ™œ์„ฑ.',
928
- 'LEERNESS_SKILL_AUTO_DISCOVER=0',
929
- '# 1.9.182 โ€” handoff ์‹œ ๊ณต์‹ catalog (vercel-labs, anthropics) ์ž๋™ ํƒ์ƒ‰ + ๋งค์นญ ์‹œ ์ž๋™ install. 1=ํ™œ์„ฑ (opt-in ๋ณด์•ˆ).',
930
- 'LEERNESS_SKILL_AUTO_INSTALL=0',
931
- '# 1.9.182 โ€” handoff ์ž๋™ ํƒ์ƒ‰ ๋Œ€์ƒ preset (์ฝค๋งˆ ๊ตฌ๋ถ„). ์˜ˆ: vercel,anthropic',
932
- 'LEERNESS_SKILL_AUTO_PRESETS=vercel,anthropic'
914
+ `# .env ๋Š” .gitignore โ€” AI ์—์ด์ „ํŠธ(Claude Code, Cursor ๋“ฑ)์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ. ์‹œํฌ๋ฆฟ ์•ˆ์ „ ๋ณด๊ด€.`,
915
+ `# ๋น„์‹œํฌ๋ฆฟ (ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ/๋ชจ๋ธ ์ด๋ฆ„/๊ณต๊ฐœ URL) ์€ .harness/leerness-config.json ์ฐธ์กฐ โ†’ AI ๊ฐ€์‹œ์„ฑ โ†‘.`,
916
+ '',
917
+ '# === ์‹œํฌ๋ฆฟ (TOKEN/KEY) ===',
918
+ 'LEERNESS_NPM_TOKEN=',
919
+ 'LEERNESS_GITHUB_TOKEN=',
933
920
  ];
934
921
  mergeLinesFile(path.join(root, '.env.example'), envLines);
935
- // 1.9.153: .env ์ง์ ‘ ์ƒ์„ฑ/๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์‚ฌ์šฉ์ž ๋ช…์‹œ ์š”์ฒญ). ๋ณด์•ˆ = ๋นˆ ๊ฐ’๋งŒ โ€” ์‚ฌ์šฉ์ž๊ฐ€ ์ง์ ‘ ํ† ํฐ ์ฑ„์›€.
936
- // ๊ธฐ์กด .env ๊ฐ€ ์žˆ์œผ๋ฉด mergeEnvFile ์ด KEY ๊ธฐ์ค€ ์ฒ˜๋ฆฌ:
937
- // - ๊ธฐ์กด ํ‚ค (์‚ฌ์šฉ์ž๊ฐ€ ์ฑ„์šด ๊ฐ’ ํฌํ•จ) ๋Š” ์ ˆ๋Œ€ ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ
938
- // - ๋ˆ„๋ฝ๋œ ํ‚ค๋งŒ ๋นˆ ๊ฐ’์œผ๋กœ ์ถ”๊ฐ€
939
- // .env ๊ฐ€ .gitignore ์— ๋“ฑ๋ก๋˜์–ด ์žˆ๋Š”์ง€ audit ๊ฐ€ ๊ฒ€์ฆ (1.9.75+).
940
922
  try {
941
923
  mergeEnvFile(path.join(root, '.env'), envLines);
942
924
  } catch (e) {
943
925
  warn(`.env ์ƒ์„ฑ/๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํŒจ (๊ณ„์† ์ง„ํ–‰): ${e.message}`);
944
926
  }
927
+ // 1.9.187: ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ • โ†’ .harness/leerness-config.json (AI ๊ฐ€์‹œ์„ฑ)
928
+ try {
929
+ _writeLeernessConfig(root, {
930
+ LEERNESS_OLLAMA_BASE_URL: enable('ollama') ? 'http://localhost:11434' : '',
931
+ LEERNESS_OLLAMA_MODEL: '',
932
+ LEERNESS_ENABLE_CLAUDE: enable('claude') ? '1' : '0',
933
+ LEERNESS_ENABLE_CODEX: enable('codex') ? '1' : '0',
934
+ LEERNESS_ENABLE_GEMINI: enable('gemini') ? '1' : '0',
935
+ LEERNESS_ENABLE_COPILOT: enable('copilot') ? '1' : '0',
936
+ LEERNESS_ENABLE_OLLAMA: enable('ollama') ? '1' : '0',
937
+ LEERNESS_SKILL_DISCOVER_URL: '',
938
+ LEERNESS_SKILL_AUTO_DISCOVER: '0',
939
+ LEERNESS_SKILL_AUTO_INSTALL: '0',
940
+ LEERNESS_SKILL_AUTO_PRESETS: 'vercel,anthropic'
941
+ });
942
+ } catch (e) {
943
+ warn(`.harness/leerness-config.json ์ƒ์„ฑ ์‹คํŒจ (๊ณ„์† ์ง„ํ–‰): ${e.message}`);
944
+ }
945
+ // 1.9.187: ๊ธฐ์กด .env์— ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ๊ฐ€ ์žˆ์œผ๋ฉด .harness/leerness-config.json ์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ›„ .env์—์„œ ์ œ๊ฑฐ.
946
+ try {
947
+ _migrateNonsecretFromEnv(root);
948
+ } catch (e) {
949
+ warn(`.env ๋น„์‹œํฌ๋ฆฟ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํŒจ (๊ณ„์† ์ง„ํ–‰): ${e.message}`);
950
+ }
945
951
  // 1.9.146: agent ๊ถŒํ•œ ํŒŒ์ผ ์ž๋™ ์ƒ์„ฑ (์‚ฌ์šฉ์ž ๋ช…์‹œ ์š”์ฒญ #5)
946
952
  if (resolved.permissionMode) {
947
953
  try { _writePermissionsPreset(root, resolved.permissionMode); } catch (e) { warn('permissions ์ƒ์„ฑ ์‹คํŒจ: ' + e.message); }
@@ -4310,23 +4316,149 @@ function verifyClaimCmd(root, taskId) {
4310
4316
  function _loadEnvFile(root) {
4311
4317
  // root ๊ฒฝ๋กœ(๋˜๋Š” cwd)์˜ .env ํŒŒ์ผ์„ ๊ฐ„๋‹จ ํŒŒ์‹ฑํ•ด process.env์— ๋จธ์ง€ (์ด๋ฏธ ์žˆ๋Š” ํ‚ค๋Š” ๋ฎ์–ด์“ฐ์ง€ ์•Š์Œ)
4312
4318
  const envFile = path.join(root || process.cwd(), '.env');
4313
- if (!exists(envFile)) return false;
4319
+ let loadedEnv = false;
4320
+ if (exists(envFile)) {
4321
+ try {
4322
+ const txt = read(envFile);
4323
+ for (const line of txt.split(/\r?\n/)) {
4324
+ const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*?)\s*$/);
4325
+ if (!m) continue;
4326
+ const key = m[1];
4327
+ let val = m[2];
4328
+ // ์ฃผ์„ ์ œ๊ฑฐ
4329
+ if (val.startsWith('#')) continue;
4330
+ // ๋”ฐ์˜ดํ‘œ ์ œ๊ฑฐ
4331
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) val = val.slice(1, -1);
4332
+ if (!process.env[key]) process.env[key] = val;
4333
+ }
4334
+ loadedEnv = true;
4335
+ } catch {}
4336
+ }
4337
+ // 1.9.187: .env ๋กœ๋“œ ํ›„ ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ๋„ .harness/leerness-config.json ์—์„œ ์ž๋™ inject.
4338
+ // ์šฐ์„ ์ˆœ์œ„: process.env (์ด๋ฏธ ์žˆ์Œ) > .env > .harness/leerness-config.json > defaults.
4339
+ try { _loadLeernessConfig(root); } catch {}
4340
+ return loadedEnv;
4341
+ }
4342
+
4343
+ // 1.9.187 (์‚ฌ์šฉ์ž ๋ช…์‹œ): ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ •์„ .env์—์„œ .harness/leerness-config.json ์œผ๋กœ ๋ถ„๋ฆฌ.
4344
+ // ๋ฐฐ๊ฒฝ: .env ๋Š” .gitignore + ์‹œํฌ๋ฆฟ์šฉ โ†’ ๋‹ค๋ฅธ AI ์—์ด์ „ํŠธ (Claude Code, Cursor ๋“ฑ) ๊ฐ€ ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ฝ์„ ๋•Œ ๋ณด์•ˆ ์ •์ฑ…์ƒ ์ž๋™ ๋…ธ์ถœ X.
4345
+ // ๋น„์‹œํฌ๋ฆฟ (ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ, ๋ชจ๋ธ ์ด๋ฆ„, ๊ณต๊ฐœ URL) ์€ .harness/leerness-config.json (git checked-in) ์œผ๋กœ ์˜ฎ๊ธฐ๋ฉด AI ๊ฐ€์‹œ์„ฑ โ†‘.
4346
+ // ํ˜ธํ™˜์„ฑ: ์‹œ์ž‘ ์‹œ์ ์— config ํŒŒ์ผ ์ฝ์–ด์„œ process.env์— inject. ๊ธฐ์กด process.env.LEERNESS_* ์ฝ”๋“œ ๊ทธ๋Œ€๋กœ ์ž‘๋™.
4347
+ // ์šฐ์„ ์ˆœ์œ„: 1) process.env (์ด๋ฏธ ์„ค์ •๋œ ๊ฐ’) > 2) .env ํŒŒ์ผ > 3) .harness/leerness-config.json > 4) defaults.
4348
+ const _LEERNESS_NONSECRET_KEYS = new Set([
4349
+ 'LEERNESS_OLLAMA_BASE_URL', // ์ผ๋ฐ˜ localhost URL โ€” ๋น„๋ฐ€ X
4350
+ 'LEERNESS_OLLAMA_MODEL', // ๋ชจ๋ธ ์ด๋ฆ„ โ€” ๋น„๋ฐ€ X
4351
+ 'LEERNESS_ENABLE_CLAUDE', // ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ
4352
+ 'LEERNESS_ENABLE_CODEX',
4353
+ 'LEERNESS_ENABLE_GEMINI',
4354
+ 'LEERNESS_ENABLE_COPILOT',
4355
+ 'LEERNESS_ENABLE_OLLAMA',
4356
+ 'LEERNESS_SKILL_DISCOVER_URL', // ๊ณต๊ฐœ URL
4357
+ 'LEERNESS_SKILL_AUTO_DISCOVER',
4358
+ 'LEERNESS_SKILL_AUTO_INSTALL',
4359
+ 'LEERNESS_SKILL_AUTO_PRESETS'
4360
+ ]);
4361
+ // ์‹œํฌ๋ฆฟ์€ ์ ˆ๋Œ€ .harness/leerness-config.json ์œผ๋กœ ์˜ฎ๊ธฐ์ง€ ์•Š์Œ (TOKEN ํŒจํ„ด ์ž๋™ ์ฐจ๋‹จ)
4362
+ function _isSecretKey(k) {
4363
+ return /TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE/i.test(k);
4364
+ }
4365
+ function _leernessConfigPath(root) { return path.join(absRoot(root || process.cwd()), '.harness', 'leerness-config.json'); }
4366
+ function _loadLeernessConfig(root) {
4367
+ const f = _leernessConfigPath(root);
4368
+ if (!exists(f)) return false;
4314
4369
  try {
4315
- const txt = read(envFile);
4316
- for (const line of txt.split(/\r?\n/)) {
4317
- const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*?)\s*$/);
4318
- if (!m) continue;
4319
- const key = m[1];
4320
- let val = m[2];
4321
- // ์ฃผ์„ ์ œ๊ฑฐ
4322
- if (val.startsWith('#')) continue;
4323
- // ๋”ฐ์˜ดํ‘œ ์ œ๊ฑฐ
4324
- if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) val = val.slice(1, -1);
4325
- if (!process.env[key]) process.env[key] = val;
4370
+ const j = JSON.parse(read(f));
4371
+ // _comment ๊ฐ™์€ ๋ฉ”ํƒ€ ํ‚ค๋Š” ๋ฌด์‹œ. ์‹ค์ œ LEERNESS_* ํ‚ค๋งŒ inject.
4372
+ for (const k of Object.keys(j || {})) {
4373
+ if (k.startsWith('_')) continue;
4374
+ if (_isSecretKey(k)) continue; // ๋ณด์•ˆ ๊ฐ€๋“œ โ€” ์ž˜๋ชป ๋“ค์–ด๊ฐ„ ์‹œํฌ๋ฆฟ ์ฐจ๋‹จ
4375
+ if (typeof j[k] !== 'string' && typeof j[k] !== 'number' && typeof j[k] !== 'boolean') continue;
4376
+ if (!process.env[k]) process.env[k] = String(j[k]);
4326
4377
  }
4327
4378
  return true;
4328
4379
  } catch { return false; }
4329
4380
  }
4381
+ // 1.9.187: ๊ธฐ์กด .env ์— ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ๊ฐ€ ์žˆ์œผ๋ฉด โ†’ .harness/leerness-config.json ์œผ๋กœ ์˜ฎ๊ธฐ๊ณ  .env ์—์„œ ์ œ๊ฑฐ.
4382
+ // ์‚ฌ์šฉ์ž๊ฐ€ 1.9.186 ์ด์ „ ๋ฒ„์ „์œผ๋กœ ๋งŒ๋“  .env ๋ฅผ 1.9.187 ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ดํŠธํ•  ๋•Œ ์ž๋™ ์ •๋ฆฌ.
4383
+ // .env ์˜ ์‹œํฌ๋ฆฟ (TOKEN/KEY/PASSWORD) ์€ ์ ˆ๋Œ€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ. ๋ณด์•ˆ first.
4384
+ function _migrateNonsecretFromEnv(root) {
4385
+ const envFile = path.join(absRoot(root || process.cwd()), '.env');
4386
+ if (!exists(envFile)) return { migrated: 0, kept: 0 };
4387
+ const txt = read(envFile);
4388
+ const lines = txt.split(/\r?\n/);
4389
+ const migrated = {};
4390
+ const remaining = [];
4391
+ let movedCount = 0;
4392
+ let pendingComment = null; // ์ง์ „ # ์ฃผ์„ ๋ผ์ธ ์ถ”์  (๋น„์‹œํฌ๋ฆฟ ํ‚ค ์ด๋™ ์‹œ ํ•จ๊ป˜ ์ œ๊ฑฐ)
4393
+ for (let i = 0; i < lines.length; i++) {
4394
+ const line = lines[i];
4395
+ if (/^\s*#/.test(line) || /^\s*$/.test(line)) {
4396
+ // ์ฃผ์„/๋นˆ์ค„ โ€” ์ผ๋‹จ ๋ณด๊ด€, ๋น„์‹œํฌ๋ฆฟ ํ‚ค ๋ฐœ๊ฒฌ ์‹œ ์ง์ „ ์ฃผ์„๋„ ์ œ๊ฑฐ ๋Œ€์ƒ
4397
+ if (/^\s*#/.test(line)) pendingComment = remaining.length;
4398
+ remaining.push(line);
4399
+ continue;
4400
+ }
4401
+ const m = line.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*?)\s*$/);
4402
+ if (!m) { remaining.push(line); pendingComment = null; continue; }
4403
+ const key = m[1];
4404
+ const val = m[2].replace(/^["']|["']$/g, '');
4405
+ if (_LEERNESS_NONSECRET_KEYS.has(key) && !_isSecretKey(key)) {
4406
+ // ๋น„์‹œํฌ๋ฆฟ โ†’ config ๋กœ ์ด๋™, .env ์—์„œ ์ œ๊ฑฐ (์ง์ „ # ์ฃผ์„๋„ ์ œ๊ฑฐ)
4407
+ migrated[key] = val;
4408
+ movedCount++;
4409
+ if (pendingComment !== null) {
4410
+ // pendingComment ๋ผ์ธ๋ถ€ํ„ฐ ์ง์ „๊นŒ์ง€ ์ œ๊ฑฐ (๋‹จ, ์‹œ์ž‘ ๋ผ์ธ ๋ณดํ˜ธ)
4411
+ // ๋‹จ์ˆœํ™”: ๊ทธ๋ƒฅ ์ง์ „ 1์ค„๋งŒ ์ œ๊ฑฐ (๋Œ€๋ถ€๋ถ„ ์ฃผ์„ 1์ค„ + ํ‚ค 1์ค„ ํŒจํ„ด)
4412
+ if (remaining.length > 0 && /^\s*#/.test(remaining[remaining.length - 1])) {
4413
+ remaining.pop();
4414
+ }
4415
+ }
4416
+ pendingComment = null;
4417
+ continue;
4418
+ }
4419
+ remaining.push(line);
4420
+ pendingComment = null;
4421
+ }
4422
+ if (movedCount === 0) return { migrated: 0, kept: lines.length };
4423
+ // .env ๋‹ค์‹œ ์“ฐ๊ธฐ (์‹œํฌ๋ฆฟ + ์ฃผ์„๋งŒ ์œ ์ง€)
4424
+ // ์—ฐ์†๋œ ๋นˆ ์ค„ ์ •๋ฆฌ
4425
+ const cleaned = [];
4426
+ let prevEmpty = false;
4427
+ for (const ln of remaining) {
4428
+ const isEmpty = /^\s*$/.test(ln);
4429
+ if (isEmpty && prevEmpty) continue;
4430
+ cleaned.push(ln);
4431
+ prevEmpty = isEmpty;
4432
+ }
4433
+ writeUtf8(envFile, cleaned.join('\n').replace(/\n+$/, '\n'));
4434
+ // config ์— ๋จธ์ง€
4435
+ _writeLeernessConfig(root, migrated);
4436
+ return { migrated: movedCount, kept: cleaned.length };
4437
+ }
4438
+
4439
+ function _writeLeernessConfig(root, kv) {
4440
+ const f = _leernessConfigPath(root);
4441
+ mkdirp(path.dirname(f));
4442
+ // ๊ธฐ์กด ํŒŒ์ผ ๋จธ์ง€ (์‚ฌ์šฉ์ž๊ฐ€ ์ˆ˜๋™ ํŽธ์ง‘ํ•œ ๊ฐ’ ๋ณด์กด)
4443
+ let existing = {};
4444
+ if (exists(f)) {
4445
+ try { existing = JSON.parse(read(f)); } catch {}
4446
+ }
4447
+ const merged = {
4448
+ _comment: 'leerness ๋น„์‹œํฌ๋ฆฟ ์„ค์ •. AI ์—์ด์ „ํŠธ๊ฐ€ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์œ„์น˜ (git checked-in). ์‹œํฌ๋ฆฟ(TOKEN/SECRET/PASSWORD)์€ .env ์‚ฌ์šฉ.',
4449
+ _docs: 'https://github.com/gugu9999gu/leerness#config',
4450
+ _version: VERSION,
4451
+ ...existing,
4452
+ ...kv
4453
+ };
4454
+ // ๋ณด์•ˆ ๊ฐ€๋“œ โ€” ์‹œํฌ๋ฆฟ ํŒจํ„ด ์ž๋™ ์ œ๊ฑฐ
4455
+ for (const k of Object.keys(merged)) {
4456
+ if (k.startsWith('_')) continue;
4457
+ if (_isSecretKey(k)) { delete merged[k]; }
4458
+ }
4459
+ writeUtf8(f, JSON.stringify(merged, null, 2) + '\n');
4460
+ return f;
4461
+ }
4330
4462
 
4331
4463
  function _httpPostJson(urlStr, body, timeoutMs = 300000) {
4332
4464
  return new Promise((resolve, reject) => {
@@ -10916,10 +11048,11 @@ async function _cliChat(root, provider, prompt, opts) {
10916
11048
  return { ok: false, error: `${provider} ๋น„ํ™œ์„ฑ (${status.status}) โ€” .env ์—์„œ ${agent.envFlag}=1 + CLI ์„ค์น˜ ํ•„์š”`, provider };
10917
11049
  }
10918
11050
  // CLI ๋ณ„ ๋น„-์ธํ„ฐ๋ž™ํ‹ฐ๋ธŒ ํ˜ธ์ถœ ์ธ์ž ๋งคํ•‘ (read-only ๋ชจ๋“œ โ€” REPL ์•ˆ์—์„œ ํŒŒ์ผ ์ˆ˜์ • X)
10919
- let cmd, args;
10920
- if (provider === 'claude') { cmd = 'claude'; args = ['--print', prompt]; }
10921
- else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', prompt]; }
10922
- else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', prompt]; }
11051
+ // 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): ํ•œ๊ธ€/ํŠน์ˆ˜๋ฌธ์ž prompt ์‹œ shell escape ์‹คํŒจ ํšŒํ”ผ โ†’ stdin ์œผ๋กœ ์ „๋‹ฌ.
11052
+ let cmd, args, stdinInput = null;
11053
+ if (provider === 'claude') { cmd = 'claude'; args = ['--print']; stdinInput = prompt; }
11054
+ else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', '-']; stdinInput = prompt; }
11055
+ else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', prompt]; } // gemini ๋Š” ์ธ์ž only
10923
11056
  else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', prompt]; }
10924
11057
  else return { ok: false, error: `provider ${provider} ๋ฏธ์ง€์›`, provider };
10925
11058
  // runCommandSafe โ€” env scrub + observability ์ž๋™
@@ -10927,7 +11060,8 @@ async function _cliChat(root, provider, prompt, opts) {
10927
11060
  cwd: process.cwd(), root,
10928
11061
  timeout: opts.timeout || 60000,
10929
11062
  allowOutsideCwd: true, // CLI ๊ฐ€ cwd ๋ฐ–์—์„œ ์‹คํ–‰๋  ์ˆ˜ ์žˆ์Œ
10930
- kind: 'agent_repl_cli', label: `repl-${provider}`
11063
+ kind: 'agent_repl_cli', label: `repl-${provider}`,
11064
+ input: stdinInput // 1.9.188: claude/codex ๋Š” stdin ์ „๋‹ฌ
10931
11065
  });
10932
11066
  if (r.status === 0) {
10933
11067
  return { ok: true, response: (r.stdout || '').trim(), provider, model: provider };
@@ -10956,15 +11090,25 @@ async function _cliChatStream(root, provider, promptText, opts) {
10956
11090
  // 1.9.186 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): claude --output-format=stream-json ๊ฐ€ ์ผ๋ถ€ ๋ฒ„์ „์—์„œ ๋นˆ ์‘๋‹ต.
10957
11091
  // default ๋ฅผ plain --print ๋กœ ๋ณ€๊ฒฝ โ†’ _cliChat ๊ณผ ๋™์ผํ•œ ์ธ์ž, ์ž‘๋™ ๊ฒ€์ฆ๋œ ํŒจํ„ด.
10958
11092
  // stream ํ˜•์‹ ์‚ฌ์šฉ opt-in: LEERNESS_REPL_STREAM_FORMAT=json (์‹ค์‹œ๊ฐ„ thinking/tool_use ๋ณด๊ณ  ์‹ถ์„ ๋•Œ).
11093
+ // 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): shell:true + Windows cmd.exe + ํ•œ๊ธ€/ํŠน์ˆ˜๋ฌธ์ž promptText โ†’ args escape ์‹คํŒจ.
11094
+ // ์‚ฌ์šฉ์ž ๋ณด๊ณ : "์ด ํด๋”์— ํŒŒ์ด์ฌ ํ”„๋กœ๊ทธ๋žจ ์ œ์ž‘" ๋ณด๋ƒˆ์œผ๋‚˜ claude๋Š” "์—ญํ• :" fragment๋งŒ ๋ฐ›์Œ.
11095
+ // ํ•ด๊ฒฐ: promptText ๋ฅผ args ์—์„œ ์ œ๊ฑฐํ•˜๊ณ  stdin ์œผ๋กœ ์ „๋‹ฌ (shell escape ์šฐํšŒ).
10959
11096
  const useStreamJson = process.env.LEERNESS_REPL_STREAM_FORMAT === 'json';
11097
+ let useStdinForPrompt = false; // 1.9.188: claude/codex ๋Š” stdin ์œผ๋กœ prompt ์ „๋‹ฌ (shell escape ์šฐํšŒ)
10960
11098
  if (provider === 'claude') {
10961
11099
  cmd = 'claude';
11100
+ // 1.9.188: promptText ๋Š” stdin ์œผ๋กœ โ†’ args ์—์„œ ์ œ๊ฑฐ (ํ•œ๊ธ€/ํŠน์ˆ˜๋ฌธ์ž ์•ˆ์ „)
10962
11101
  args = useStreamJson
10963
- ? ['--print', '--output-format=stream-json', '--verbose', promptText]
10964
- : ['--print', promptText]; // plain text ์‘๋‹ต (์ž‘๋™ ๊ฒ€์ฆ๋จ)
11102
+ ? ['--print', '--output-format=stream-json', '--verbose']
11103
+ : ['--print'];
11104
+ useStdinForPrompt = true;
11105
+ }
11106
+ else if (provider === 'codex') {
11107
+ cmd = 'codex';
11108
+ args = ['exec', '--skip-git-repo-check', '-']; // - = stdin from claude/codex convention
11109
+ useStdinForPrompt = true;
10965
11110
  }
10966
- else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', promptText]; }
10967
- else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', promptText]; }
11111
+ else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', promptText]; } // gemini -p ๋Š” ์ธ์ž ๋ชจ๋“œ๋งŒ ์ง€์›
10968
11112
  else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', promptText]; }
10969
11113
  else return { ok: false, error: `provider ${provider} ๋ฏธ์ง€์›`, provider };
10970
11114
  const t0 = Date.now();
@@ -11024,17 +11168,26 @@ async function _cliChatStream(root, provider, promptText, opts) {
11024
11168
  process.stdout.write(dim(`\n โ”€โ”€ ${provider} stream โ”€โ”€\n`));
11025
11169
  let child;
11026
11170
  try {
11027
- // 1.9.186 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): Windows .cmd ํ˜ธํ™˜์„ ์œ„ํ•ด shell: true.
11028
- // 1.9.185 ๊นŒ์ง€ shell: false โ†’ Windows ์—์„œ claude.cmd ๋ชป ์ฐพ์•„ ๋นˆ ์‘๋‹ต (์‚ฌ์šฉ์ž ๋ณด๊ณ : 27์ดˆ ํ›„ 0์ž).
11029
- // _cliChat (์ž‘๋™ํ•˜๋Š” ํ•จ์ˆ˜) ์ด runCommandSafe โ†’ spawnSync shell: true ํŒจํ„ด์ด๋ผ์„œ ์ž‘๋™.
11030
- // _cliChatStream ๋„ ๋™์ผํ•˜๊ฒŒ shell: true ์‚ฌ์šฉ (DEP0190 ์€ 1.9.184/185 fix ๋กœ ์–ต์ œ๋จ).
11031
- // security: shell escape ์šฐ๋ ค๋Š” promptText ๊ฐ€ ์‚ฌ์šฉ์ž ์ง์ ‘ ์ž…๋ ฅ โ€” sandboxed REPL ์•ˆ์—์„œ๋งŒ ์‚ฌ์šฉ.
11171
+ // 1.9.186 + 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix):
11172
+ // Windows .cmd ํ˜ธํ™˜์„ ์œ„ํ•ด shell: true ์œ ์ง€.
11173
+ // 1.9.188: promptText ๊ฐ€ ํ•œ๊ธ€/ํŠน์ˆ˜๋ฌธ์ž์ผ ๋•Œ shell escape ์‹คํŒจ โ†’ stdin ์œผ๋กœ ์ „๋‹ฌ.
11174
+ // useStdinForPrompt=true ์‹œ stdio[0]='pipe' + child.stdin.write(promptText) + end().
11175
+ // shell ์ธ์ž์—๋Š” escape-safe flags ๋งŒ ๋‚จ๊น€ ('--print' ๋“ฑ).
11032
11176
  child = cp.spawn(cmd, args, {
11033
11177
  cwd: process.cwd(),
11034
11178
  env: _scrubEnv({}),
11035
11179
  shell: true,
11036
- stdio: ['ignore', 'pipe', 'pipe']
11180
+ stdio: [useStdinForPrompt ? 'pipe' : 'ignore', 'pipe', 'pipe']
11037
11181
  });
11182
+ // 1.9.188: promptText ๋ฅผ stdin ์œผ๋กœ ์•ˆ์ „ ์ „๋‹ฌ
11183
+ if (useStdinForPrompt && child.stdin) {
11184
+ try {
11185
+ child.stdin.write(promptText);
11186
+ child.stdin.end();
11187
+ } catch (e) {
11188
+ // stdin ์“ฐ๊ธฐ ์‹คํŒจ ์‹œ child ๊ฐ€ stdin ์•ˆ ๋ฐ›์„ ์ˆ˜๋„ โ€” ๋ฌด์‹œํ•˜๊ณ  ์ง„ํ–‰
11189
+ }
11190
+ }
11038
11191
  } catch (e) {
11039
11192
  if (spinnerInterval) clearInterval(spinnerInterval);
11040
11193
  return resolve({ ok: false, error: 'spawn ์‹คํŒจ: ' + e.message, provider });
@@ -11439,10 +11592,16 @@ async function _agentRepl(root, opts) {
11439
11592
  log('');
11440
11593
  log(C.dim(' โŒจ Tab=provider cycle ยท Shift+Tab=model ยท Ctrl+C=quit'));
11441
11594
  log('');
11442
- // 1.9.179: ์ƒํƒœ๋ฐ” ํ•œ ์ค„ โ€” ์ƒ‰๊น”/๊ตฌ๋ถ„์ž ๊ฐ•ํ™”
11595
+ // 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ): state.model ์ด ๋น„์–ด์žˆ์œผ๋ฉด catalog ์ฒซ ๋ชจ๋ธ ์ž๋™ ์„ค์ • โ†’ ์„ธ๋ถ€ ๋ชจ๋ธ ํ‘œ์‹œ.
11596
+ // "model=(๊ธฐ๋ณธ)" ๊ฐ™์€ ๋ชจํ˜ธํ•œ ํ‘œ์‹œ ๋Œ€์‹  ์‹ค์ œ ์‚ฌ์šฉ๋  ๋ชจ๋ธ ์ด๋ฆ„ (์˜ˆ: claude-opus-4-7) ๋ช…์‹œ.
11597
+ if (!state.model) {
11598
+ const cat = _PROVIDER_MODEL_CATALOG[state.provider];
11599
+ if (cat && cat.length) state.model = cat[0].id;
11600
+ }
11601
+ // 1.9.179+1.9.188: ์ƒํƒœ๋ฐ” ํ•œ ์ค„ โ€” ์„ธ๋ถ€ ๋ชจ๋ธ + ์ƒ‰๊น”/๊ตฌ๋ถ„์ž ๊ฐ•ํ™”
11443
11602
  const permMode = _readPermissions(root).mode || 'basic';
11444
11603
  log(' ' + C.bold('โšก ') + C.cy(`provider=${state.provider}`) + ' ยท '
11445
- + C.mag(`model=${state.model || '(๊ธฐ๋ณธ)'}`) + ' ยท '
11604
+ + C.mag(`model=${state.model || '(unknown)'}`) + ' ยท '
11446
11605
  + C.green(`role=${state.role}`) + ' ยท '
11447
11606
  + C.yel(`perms=${permMode}`) + ' ยท '
11448
11607
  + (state.streamMode ? C.green('โ–ถ stream=on') : C.dim('โ–ก stream=off')));
@@ -11466,7 +11625,24 @@ async function _agentRepl(root, opts) {
11466
11625
  }
11467
11626
  } catch {}
11468
11627
  log('');
11469
- const prompt = () => isTty ? C.cy(`agent[${state.provider}/${state.role}${state.streamMode ? '/โ–ถ' : ''}]> `) : 'agent> ';
11628
+ // 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ): prompt ์œ„/์•„๋ž˜ ๊ตฌ๋ถ„์„  + ์„ธ๋ถ€ ๋ชจ๋ธ ๋ช…์‹œ (์‚ฌ์šฉ์ž ์ž…๋ ฅ์นธ ์‹œ๊ฐ ๊ฐ•์กฐ).
11629
+ // ๋งค ์‘๋‹ต ์งํ›„ readline.question ํ˜ธ์ถœ ์ „์— _printInputDivider() ํ˜ธ์ถœํ•˜์—ฌ ์ž…๋ ฅ ์˜์—ญ ๋ช…ํ™•ํ™”.
11630
+ const _termCols = () => {
11631
+ try { return Math.max(40, (process.stdout.columns || 80)); } catch { return 80; }
11632
+ };
11633
+ const _printInputDivider = () => {
11634
+ if (!isTty) return;
11635
+ const cols = _termCols();
11636
+ const line = 'โ”€'.repeat(Math.min(cols - 4, 120));
11637
+ process.stdout.write('\n' + C.dim(' ' + line) + '\n');
11638
+ };
11639
+ // prompt ์— ์„ธ๋ถ€ ๋ชจ๋ธ ํ‘œ์‹œ (์‚ฌ์šฉ์ž ๋ช…์‹œ): agent[claude ยท claude-opus-4-7 / actor / โ–ถ]>
11640
+ const prompt = () => {
11641
+ if (!isTty) return 'agent> ';
11642
+ const modelShort = (state.model || '').replace(/^(claude|gpt|gemini)-/, '').slice(0, 18);
11643
+ const modelTag = modelShort ? C.dim(' ยท ') + C.mag(modelShort) : '';
11644
+ return C.cy(`agent[${state.provider}${modelTag}${C.cy('/' + state.role)}${state.streamMode ? C.cy('/โ–ถ') : ''}${C.cy(']>')} `);
11645
+ };
11470
11646
  rl.setPrompt(prompt());
11471
11647
 
11472
11648
  // 1.9.170: Tab cycle โ€” provider (Tab) / model within provider (Shift+Tab)
@@ -11880,13 +12056,13 @@ async function _agentRepl(root, opts) {
11880
12056
  // 1.9.170: stream ๋ชจ๋“œ์—์„œ๋Š” ์ด๋ฏธ ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ถœ๋ ฅ๋์œผ๋ฏ€๋กœ ํ—ค๋”๋งŒ ํ‘œ์‹œ (์‘๋‹ต ์ค‘๋ณต ๋ฐฉ์ง€)
11881
12057
  if (state.streamMode && ['claude', 'codex', 'gemini', 'copilot'].includes(state.provider)) {
11882
12058
  log(C.dim(` [assistant: ${state.provider}/${state.model || 'default'}, role=${state.role}, ${dt}ms ยท ${result.response.length}์ž]`));
11883
- log('');
11884
12059
  } else {
11885
12060
  log('');
11886
12061
  log(C.bold(`assistant (${state.model || state.provider}, role=${state.role}, ${dt}ms)`));
11887
12062
  log(result.response);
11888
- log('');
11889
12063
  }
12064
+ // 1.9.188 (์‚ฌ์šฉ์ž ๋ช…์‹œ): ์‘๋‹ต ๋ + ์ž…๋ ฅ ๊ตฌ๋ถ„์„  (์ž…๋ ฅ์นธ ์‹œ๊ฐ ๋ช…ํ™•)
12065
+ _printInputDivider();
11890
12066
  if (state.history.length % 6 === 0) saveSession(); // 6ํ„ด๋งˆ๋‹ค ์ž๋™ ์ €์žฅ
11891
12067
  } else {
11892
12068
  // 1.9.185 (์‚ฌ์šฉ์ž ๋ช…์‹œ): REPL ํ˜ธ์ถœ ์‹คํŒจ ์นœ์ ˆํ•œ ์ง„๋‹จ โ€” ์–ด๋А ๋‹จ๊ณ„์—์„œ ์‹คํŒจํ–ˆ๋Š”์ง€ + ์ฆ‰์‹œ ๊ฒ€์ฆ ๋ช…๋ น ์•ˆ๋‚ด
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.186",
3
+ "version": "1.9.188",
4
4
  "description": "Leerness: ๋น„ํŒŒ๊ดด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ž๋™ ๋ฒ„์ „ ๊ฐ์ง€ยท์—…๋ฐ์ดํŠธ, ๊ณ„ํš/์ง„ํ–‰/ํ•ธ๋“œ์˜คํ”„ ์ž๋™ํ™”, ๊ฒŒ์œผ๋ฆ„ยท์‹œํฌ๋ฆฟยท์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ€๋“œ, Claude Code ์Šฌ๋ž˜์‹œ ํ†ตํ•ฉ์„ ๊ฐ–์ถ˜ ํ•œ๊ตญ์–ด ์šฐ์„  AI ๊ฐœ๋ฐœ ํ•˜๋„ค์Šค.",
5
5
  "keywords": [
6
6
  "leerness",