leerness 1.9.185 โ†’ 1.9.187

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,225 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.187 โ€” 2026-05-21
4
+
5
+ **๐Ÿ”“ ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ •์„ .env โ†’ .harness/leerness-config.json ์œผ๋กœ ๋ถ„๋ฆฌ (AI ๊ฐ€์‹œ์„ฑ).**
6
+
7
+ ์ž์œจ ๋ชจ๋“œ 117 ๋ผ์šด๋“œ. ์‚ฌ์šฉ์ž ๋ช…์‹œ: *".env์— ์ž…๋ ฅ๋˜์–ด์„œ AI ์—์ด์ „ํŠธ๊ฐ€ ์ฐธ์กฐํ•˜๊ฑฐ๋‚˜ ์ฝ์„ ์ˆ˜ ์—†๋Š” ์œ„์น˜ํ•˜๋ฉด ์ž…๋ ฅ๋˜๋Š” ์œ„์น˜๋ฅผ ๋ณ€๊ฒฝ"*.
8
+
9
+ ### ๋ฐฐ๊ฒฝ & ๋ถ„๋ฆฌ ์ •์ฑ…
10
+
11
+ | ์œ„์น˜ | ๋ณด์•ˆ ์ •์ฑ… | ์šฉ๋„ |
12
+ |---|---|---|
13
+ | `.env` (.gitignore) | ์‹œํฌ๋ฆฟ only | NPM_TOKEN, GITHUB_TOKEN, *_API_KEY |
14
+ | `.harness/leerness-config.json` (git checked-in) | ๋น„์‹œํฌ๋ฆฟ only | ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ, ๊ณต๊ฐœ URL, ๋ชจ๋ธ ์ด๋ฆ„ |
15
+
16
+ **ํ•ต์‹ฌ**: `.env`๋Š” .gitignore + ์‹œํฌ๋ฆฟ์šฉ โ†’ ๋‹ค๋ฅธ AI ์—์ด์ „ํŠธ(Claude Code, Cursor, Copilot ๋“ฑ)๊ฐ€ ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ฝ์„ ๋•Œ ์ž๋™ ๋…ธ์ถœ X. ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ •์„ AI ๊ฐ€์‹œ ์œ„์น˜๋กœ ์˜ฎ๊ฒจ **AI ์—์ด์ „ํŠธ ์ธ์ง€๋„ โ†‘**.
17
+
18
+ ### ๋ถ„๋ฆฌ ๋Œ€์ƒ (11๊ฐœ ๋น„์‹œํฌ๋ฆฟ ํ‚ค)
19
+ ```js
20
+ const _LEERNESS_NONSECRET_KEYS = new Set([
21
+ 'LEERNESS_OLLAMA_BASE_URL', // localhost URL โ€” ๋น„๋ฐ€ X
22
+ 'LEERNESS_OLLAMA_MODEL', // ๋ชจ๋ธ ์ด๋ฆ„ โ€” ๋น„๋ฐ€ X
23
+ 'LEERNESS_ENABLE_CLAUDE', // ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ
24
+ 'LEERNESS_ENABLE_CODEX',
25
+ 'LEERNESS_ENABLE_GEMINI',
26
+ 'LEERNESS_ENABLE_COPILOT',
27
+ 'LEERNESS_ENABLE_OLLAMA',
28
+ 'LEERNESS_SKILL_DISCOVER_URL', // ๊ณต๊ฐœ URL
29
+ 'LEERNESS_SKILL_AUTO_DISCOVER',
30
+ 'LEERNESS_SKILL_AUTO_INSTALL',
31
+ 'LEERNESS_SKILL_AUTO_PRESETS'
32
+ ]);
33
+ ```
34
+
35
+ ### .harness/leerness-config.json ์˜ˆ์‹œ
36
+ ```json
37
+ {
38
+ "_comment": "leerness ๋น„์‹œํฌ๋ฆฟ ์„ค์ •. AI ์—์ด์ „ํŠธ๊ฐ€ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ์œ„์น˜ (git checked-in). ์‹œํฌ๋ฆฟ(TOKEN/SECRET/PASSWORD)์€ .env ์‚ฌ์šฉ.",
39
+ "_docs": "https://github.com/gugu9999gu/leerness#config",
40
+ "_version": "1.9.187",
41
+ "LEERNESS_OLLAMA_BASE_URL": "http://localhost:11434",
42
+ "LEERNESS_OLLAMA_MODEL": "llama3",
43
+ "LEERNESS_ENABLE_CLAUDE": "1",
44
+ "LEERNESS_ENABLE_CODEX": "1",
45
+ "LEERNESS_ENABLE_GEMINI": "0",
46
+ "LEERNESS_ENABLE_COPILOT": "0",
47
+ "LEERNESS_ENABLE_OLLAMA": "0",
48
+ "LEERNESS_SKILL_DISCOVER_URL": "",
49
+ "LEERNESS_SKILL_AUTO_DISCOVER": "0",
50
+ "LEERNESS_SKILL_AUTO_INSTALL": "0",
51
+ "LEERNESS_SKILL_AUTO_PRESETS": "vercel,anthropic"
52
+ }
53
+ ```
54
+
55
+ ### .env (simplified)
56
+ ```
57
+ # Leerness โ€” SECRET ํ™˜๊ฒฝ๋ณ€์ˆ˜๋งŒ (TOKEN/SECRET/PASSWORD). ๋น„์‹œํฌ๋ฆฟ ์„ค์ •์€ .harness/leerness-config.json ์‚ฌ์šฉ.
58
+ # .env ๋Š” .gitignore โ€” AI ์—์ด์ „ํŠธ(Claude Code, Cursor ๋“ฑ)์— ๋…ธ์ถœ๋˜์ง€ ์•Š์Œ. ์‹œํฌ๋ฆฟ ์•ˆ์ „ ๋ณด๊ด€.
59
+ # ๋น„์‹œํฌ๋ฆฟ (ํ™œ์„ฑํ™” ํ”Œ๋ž˜๊ทธ/๋ชจ๋ธ ์ด๋ฆ„/๊ณต๊ฐœ URL) ์€ .harness/leerness-config.json ์ฐธ์กฐ โ†’ AI ๊ฐ€์‹œ์„ฑ โ†‘.
60
+
61
+ # === ์‹œํฌ๋ฆฟ (TOKEN/KEY) ===
62
+ LEERNESS_NPM_TOKEN=
63
+ LEERNESS_GITHUB_TOKEN=
64
+ ```
65
+
66
+ ### ๋ณด์•ˆ ๊ฐ€๋“œ (์ด์ค‘ ์•ˆ์ „๋ง)
67
+ - **load ๊ฐ€๋“œ**: `_loadLeernessConfig` ์—์„œ `_isSecretKey(k)` ๋งค์น˜ ์‹œ inject ์ฐจ๋‹จ
68
+ - **write ๊ฐ€๋“œ**: `_writeLeernessConfig` ์—์„œ ์‹œํฌ๋ฆฟ ํŒจํ„ด ์ž๋™ ์ œ๊ฑฐ (์ž˜๋ชป ๋“ค์–ด๊ฐ„ ํ‚ค sanitize)
69
+ - **์ •๊ทœ์‹**: `/TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE/i`
70
+
71
+ ### ์ž๋™ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (1.9.186 ์ด์ „ .env)
72
+ `_migrateNonsecretFromEnv(root)` โ€” ๊ธฐ์กด .env ์— ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ๊ฐ€ ์žˆ์œผ๋ฉด:
73
+ 1. config ํŒŒ์ผ๋กœ ์ž๋™ ์ด๋™
74
+ 2. .env ์—์„œ ์ œ๊ฑฐ (์ง์ „ # ์ฃผ์„๋„ ํ•จ๊ป˜)
75
+ 3. ์‹œํฌ๋ฆฟ (TOKEN/KEY/PASSWORD) ์€ ์ ˆ๋Œ€ ๊ฑด๋“œ๋ฆฌ์ง€ ์•Š์Œ (๋ณด์•ˆ first)
76
+
77
+ ### ์šฐ์„ ์ˆœ์œ„ (ํ˜ธํ™˜์„ฑ)
78
+ ```
79
+ 1. process.env (์ด๋ฏธ ์„ค์ •๋œ ํ™˜๊ฒฝ๋ณ€์ˆ˜)
80
+ 2. .env ํŒŒ์ผ
81
+ 3. .harness/leerness-config.json
82
+ 4. ์ฝ”๋“œ defaults
83
+ ```
84
+
85
+ ๊ธฐ์กด `process.env.LEERNESS_*` ์ง์ ‘ ์ ‘๊ทผ ์ฝ”๋“œ๋Š” ๋ณ€๊ฒฝ X. leerness ์‹œ์ž‘ ์‹œ `_loadEnvFile()` ๊ฐ€ `_loadLeernessConfig()` ๋„ ์ž๋™ ํ˜ธ์ถœ โ†’ config ๊ฐ’์„ process.env ๋กœ inject.
86
+
87
+ ### Live ๊ฒ€์ฆ (์‚ฌ์šฉ์ž ์›Œํฌ์ŠคํŽ˜์ด์Šค ์‹œ๋ฎฌ๋ ˆ์ด์…˜)
88
+ ```bash
89
+ # Before (1.9.186 .env)
90
+ LEERNESS_NPM_TOKEN=test-token-12345
91
+ LEERNESS_OLLAMA_BASE_URL=http://localhost:11434
92
+ LEERNESS_OLLAMA_MODEL=llama3
93
+ LEERNESS_ENABLE_CLAUDE=1
94
+ ...
95
+
96
+ # After leerness init (1.9.187)
97
+ .env:
98
+ LEERNESS_NPM_TOKEN=test-token-12345 โ† ์‹œํฌ๋ฆฟ ๋ณด์กด
99
+ LEERNESS_GITHUB_TOKEN=
100
+
101
+ .harness/leerness-config.json:
102
+ LEERNESS_OLLAMA_BASE_URL: "http://localhost:11434"
103
+ LEERNESS_OLLAMA_MODEL: "llama3"
104
+ LEERNESS_ENABLE_CLAUDE: "1"
105
+ ...
106
+ ```
107
+
108
+ ### Verified
109
+ - stress-v132: **19/19 PASS** (์‚ฌ์šฉ์ž ๋ช…์‹œ 7 + live 3 + ๋ˆ„์  9)
110
+ - e2e 217/217 baseline ์œ ์ง€
111
+ - live ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒ€์ฆ: ์‹œํฌ๋ฆฟ ์†์‹ค 0๊ฑด, ๋น„์‹œํฌ๋ฆฟ 100% ์ด๋™
112
+ - VERSION = 1.9.187 ยท autonomous-rounds = 117 ยท main ์ž๋™ push 48 ๋ผ์šด๋“œ ์—ฐ์†
113
+
114
+ ---
115
+
116
+ ## 1.9.186 โ€” 2026-05-21
117
+
118
+ **๐Ÿ› REPL claude stream 0์ž ์‘๋‹ต BUG fix + ๊ตฌ์กฐ ์ตœ์ ํ™” ์ฒดํฌ (์‚ฌ์šฉ์ž ๋ช…์‹œ ํ•ต์‹ฌ ๋ฒ„๊ทธ).**
119
+
120
+ ์ž์œจ ๋ชจ๋“œ 116 ๋ผ์šด๋“œ. ์‚ฌ์šฉ์ž ๋ช…์‹œ ํ•ต์‹ฌ ๋ฒ„๊ทธ ๋ณด๊ณ :
121
+ ```
122
+ agent[claude/actor/โ–ถ]> ํŒŒ์ด์ฌ ํ”„๋กœ๊ทธ๋žจ ํ•˜๋‚˜ ์ œ์ž‘ํ•ด์ค˜
123
+ โ”€โ”€ /stream (27596ms) โ”€โ”€
124
+ [assistant: claude/default, role=actor, 27743ms ยท 0์ž]
125
+ ```
126
+ *"REPL agent ๋ชจ๋“œ์—์„œ ์ž‘์—… ์š”์ฒญ ์‹œ AI๊ฐ€ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜์ง€ ๋ชปํ•˜๊ณ  ์žˆ๋‹ค"*
127
+
128
+ ### ๊ทผ๋ณธ ์›์ธ ๋ฐœ๊ฒฌ (์ฝ”๋“œ audit)
129
+
130
+ | ํ•จ์ˆ˜ | spawn ๋ฐฉ์‹ | shell | ์ž‘๋™ ์—ฌ๋ถ€ |
131
+ |---|---|---|---|
132
+ | `_cliChat` (line 10920) | `runCommandSafe` โ†’ spawnSync `shell: true` | true | โœ“ ์ž‘๋™ (`agents multi --execute` ๊ฒ€์ฆ) |
133
+ | `_cliChatStream` (line 11018) | `cp.spawn(cmd, args, { shell: false })` | **false** | โœ— **0์ž ์‘๋‹ต** |
134
+
135
+ **์›์ธ 1**: Windows ์—์„œ `cp.spawn('claude', ..., { shell: false })` ๊ฐ€ `claude.cmd` ๋ฅผ ์ฐพ์ง€ ๋ชปํ•ด stdout ๋น„์–ด์žˆ๊ณ  27์ดˆ timeout ํ›„ ์ข…๋ฃŒ (exit code 0).
136
+
137
+ **์›์ธ 2**: claude CLI `--output-format=stream-json --verbose` ๊ฐ€ ์ผ๋ถ€ ๋ฒ„์ „์—์„œ ๋นˆ stdout ๋ฐ˜ํ™˜ โ†’ out ๋ณ€์ˆ˜ 0์ž ๋ˆ„์ .
138
+
139
+ ### Fix #1 โ€” shell: true (Windows .cmd ํ˜ธํ™˜)
140
+ ```diff
141
+ - child = cp.spawn(cmd, args, {
142
+ - cwd: process.cwd(),
143
+ - env: _scrubEnv({}),
144
+ - shell: false,
145
+ - stdio: ['ignore', 'pipe', 'pipe']
146
+ - });
147
+ + // 1.9.186 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): Windows .cmd ํ˜ธํ™˜์„ ์œ„ํ•ด shell: true.
148
+ + // _cliChat (์ž‘๋™ํ•˜๋Š” ํ•จ์ˆ˜) ์ด runCommandSafe โ†’ spawnSync shell: true ํŒจํ„ด์ด๋ผ์„œ ์ž‘๋™.
149
+ + // _cliChatStream ๋„ ๋™์ผํ•˜๊ฒŒ shell: true ์‚ฌ์šฉ (DEP0190 ์€ 1.9.184/185 fix ๋กœ ์–ต์ œ๋จ).
150
+ + child = cp.spawn(cmd, args, {
151
+ + cwd: process.cwd(),
152
+ + env: _scrubEnv({}),
153
+ + shell: true,
154
+ + stdio: ['ignore', 'pipe', 'pipe']
155
+ + });
156
+ ```
157
+
158
+ ### Fix #2 โ€” claude default = plain `--print` (stream-json opt-in)
159
+ ```diff
160
+ - if (provider === 'claude') { cmd = 'claude'; args = ['--print', '--output-format=stream-json', '--verbose', promptText]; }
161
+ + // 1.9.186 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): claude --output-format=stream-json ๊ฐ€ ์ผ๋ถ€ ๋ฒ„์ „์—์„œ ๋นˆ ์‘๋‹ต.
162
+ + // default ๋ฅผ plain --print ๋กœ ๋ณ€๊ฒฝ โ†’ _cliChat ๊ณผ ๋™์ผํ•œ ์ธ์ž, ์ž‘๋™ ๊ฒ€์ฆ๋œ ํŒจํ„ด.
163
+ + // stream ํ˜•์‹ ์‚ฌ์šฉ opt-in: LEERNESS_REPL_STREAM_FORMAT=json
164
+ + const useStreamJson = process.env.LEERNESS_REPL_STREAM_FORMAT === 'json';
165
+ + if (provider === 'claude') {
166
+ + cmd = 'claude';
167
+ + args = useStreamJson
168
+ + ? ['--print', '--output-format=stream-json', '--verbose', promptText]
169
+ + : ['--print', promptText];
170
+ + }
171
+ ```
172
+
173
+ handleClaudeStream ์กฐ๊ฑด๋ถ€ ํ˜ธ์ถœ (useStreamJson ์ผ ๋•Œ๋งŒ):
174
+ ```diff
175
+ - if (provider === 'claude') {
176
+ + if (provider === 'claude' && useStreamJson) {
177
+ handleClaudeStream(chunk);
178
+ } else {
179
+ stopSpinner();
180
+ ```
181
+
182
+ ### ๊ตฌ์กฐ ์ตœ์ ํ™” ์ฒดํฌ (์‚ฌ์šฉ์ž ๋ช…์‹œ ๋ณด๊ฐ•)
183
+
184
+ **๋ณด๊ณ ์„œ**: `_reports/structure-optimization-1.9.186.md`
185
+
186
+ **๋ฉ”ํŠธ๋ฆญ**:
187
+ - bin/harness.js: **~12000 lines** (1 monolithic)
188
+ - ์˜์กด์„ฑ **0** (Node built-in)
189
+ - npm tarball ~358 KB (gzipped)
190
+ - MCP 54 ยท CLI 100+ ยท ํ•จ์ˆ˜ 250+
191
+
192
+ **์ตœ์ ํ™” ๊ฐ•์ ** (์œ ์ง€):
193
+ - ์˜์กด์„ฑ 0 ์ •์ฑ… (cross-platform + supply chain risk ์ตœ์†Œ)
194
+ - ๋ฉ”๋ชจ๋ฆฌ ์บ์‹ฑ (usage-stats / listAllSkills / lessons keyword index)
195
+ - Provider/MCP/Bridge ๋ช…ํ™•ํ•œ abstraction
196
+ - runCommandSafe sandbox (cwd jail + env scrub)
197
+ - ๋งค ๋ผ์šด๋“œ stress + e2e + main ์ž๋™ push
198
+
199
+ **์ตœ์ ํ™” ์•ฝ์ ** (1.9.187+ ํ›„๋ณด):
200
+ - โš  Monolithic 12000 lines โ€” ํ˜„ ์ƒํƒœ ์œ ์ง€ (npm pack ๋‹จ์ˆœ์„ฑ ์šฐ์„ )
201
+ - โš  `_cliChat` โ†” `_cliChatStream` ์ฝ”๋“œ ์ค‘๋ณต โ€” 1.9.186 fix๊ฐ€ ์ด ๊ฐญ์—์„œ ๋ฐœ์ƒ. 1.9.187 ํ›„๋ณด: `_buildCliArgs` ๊ณตํ†ต ํ—ฌํผ
202
+ - โš  skill catalog ์บ์‹œ stale ๊ฐ€๋Šฅ โ€” mtime ๊ธฐ๋ฐ˜ invalidation ํ›„๋ณด
203
+ - โš  MCP tools/list ๋งค๋ฒˆ ์žฌ์ƒ์„ฑ โ€” ๋ฉ”๋ชจ๋ฆฌ ์บ์‹œ ํ›„๋ณด
204
+ - โš  280 task ๋ˆ„์  โ€” archive ์ž๋™ํ™” (1.9.190+)
205
+
206
+ ### 1.9.186 fix ์ •ํ•ฉ์„ฑ (๋ณด๊ณ ์„œ ยง4)
207
+ | ์˜ํ–ฅ | ํ‰๊ฐ€ |
208
+ |---|---|
209
+ | ๋ณด์•ˆ (shell escape) | promptText ๋Š” ์‚ฌ์šฉ์ž ์ง์ ‘ ์ž…๋ ฅ, REPL ์•ˆ์—์„œ๋งŒ ์‚ฌ์šฉ โ†’ ์ถ”๊ฐ€ ์œ„ํ—˜ ์—†์Œ |
210
+ | ์„ฑ๋Šฅ | shell:true ๊ฐ€ ~10ms ๋А๋ฆผ (๋ฌด์‹œ ๊ฐ€๋Šฅ) |
211
+ | ๋‹ค๋ฅธ provider (codex/gemini/copilot) | shell:true ๋™์ผ ์ ์šฉ โ€” ๋” ์•ˆ์ •์  (.cmd ํ˜ธํ™˜) |
212
+ | ์‹ค์‹œ๊ฐ„ ์ŠคํŠธ๋ฆฌ๋ฐ (1.9.170 ์‚ฌ์šฉ์ž ๋ช…์‹œ) | LEERNESS_REPL_STREAM_FORMAT=json ์œผ๋กœ opt-in ๊ฐ€๋Šฅ โ€” ๊ธฐ๋Šฅ ์œ ์ง€ |
213
+ | ๊ธฐ์กด ์ž‘๋™ | `_cliChat` ๋ณ€๊ฒฝ X โ€” `agents multi --execute` ํšŒ๊ท€ ์—†์Œ |
214
+
215
+ ### Verified
216
+ - stress-v131: **16/16 PASS** (์‚ฌ์šฉ์ž ๋ช…์‹œ 4 + live 3 + ๋ˆ„์  9)
217
+ - e2e 217/217 baseline ์œ ์ง€
218
+ - live ๊ฒ€์ฆ: `agents multi --execute --only claude` ์‹ค ํ˜ธ์ถœ ์‘๋‹ต "2" (4.5์ดˆ)
219
+ - VERSION = 1.9.186 ยท autonomous-rounds = 116 ยท main ์ž๋™ push 47 ๋ผ์šด๋“œ ์—ฐ์†
220
+
221
+ ---
222
+
3
223
  ## 1.9.185 โ€” 2026-05-21
4
224
 
5
225
  **๐Ÿ”ง DEP0190 ์ž์‹ process ์ „ํŒŒ fix + REPL stream ์นœ์ ˆ ์ง„๋‹จ + Hermes UX ๋ถ„์„ ๋ณด๊ณ  (์‚ฌ์šฉ์ž ๋ช…์‹œ).**
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.185-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v130-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-115-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-46_rounds-success)]() [![dep-child](https://img.shields.io/badge/DEP0190-๋ถ€๋ชจ%2B์ž์‹_์ „ํŒŒ-success)]() [![repl-diag](https://img.shields.io/badge/REPL_์‹คํŒจ-์นœ์ ˆ_์ง„๋‹จ_์•ˆ๋‚ด-success)]() [![hermes-ux](https://img.shields.io/badge/Hermes_UX-๋ถ„์„_์™„๋ฃŒ-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)]()
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.187-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v132-19%2F19-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-117-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-48_rounds-success)]() [![config-split](https://img.shields.io/badge/.env_๋ถ„๋ฆฌ-๋น„์‹œํฌ๋ฆฟ_AI๊ฐ€์‹œ์„ฑ-success)]() [![secret-guard](https://img.shields.io/badge/์‹œํฌ๋ฆฟ_๊ฐ€๋“œ-load%2Bwrite_์ด์ค‘๋ง-success)]() [![install](https://img.shields.io/badge/npm_i_leerness-์ฆ‰์‹œ_์‚ฌ์šฉ_๊ฐ€๋Šฅ-success)]() [![repl-fix](https://img.shields.io/badge/REPL_stream-shell%3Atrue_fix-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.185 AI Agent Reliability Harness + Sandbox โ•‘
15
+ โ•‘ v1.9.187 AI Agent Reliability Harness + Sandbox โ•‘
16
16
  โ•‘ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ•‘
17
- โ•‘ ๐Ÿ”ง DEP0190 ์ž์‹ ์ „ํŒŒยทREPL ์นœ์ ˆ ์ง„๋‹จยทHermes UX ๋ถ„์„ โ•‘
17
+ โ•‘ ๐Ÿ”“ .env ๋ถ„๋ฆฌ (์‹œํฌ๋ฆฟ) ยท leerness-config.json (AI ๊ฐ€์‹œ) โ•‘
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.185';
10
+ const VERSION = '1.9.187';
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) => {
@@ -10953,7 +11085,16 @@ async function _cliChatStream(root, provider, promptText, opts) {
10953
11085
  return { ok: false, error: `${provider} ๋น„ํ™œ์„ฑ (${status.status}) โ€” .env ์—์„œ ${agent.envFlag}=1 ํ•„์š”`, provider };
10954
11086
  }
10955
11087
  let cmd, args;
10956
- if (provider === 'claude') { cmd = 'claude'; args = ['--print', '--output-format=stream-json', '--verbose', promptText]; }
11088
+ // 1.9.186 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): claude --output-format=stream-json ๊ฐ€ ์ผ๋ถ€ ๋ฒ„์ „์—์„œ ๋นˆ ์‘๋‹ต.
11089
+ // default ๋ฅผ plain --print ๋กœ ๋ณ€๊ฒฝ โ†’ _cliChat ๊ณผ ๋™์ผํ•œ ์ธ์ž, ์ž‘๋™ ๊ฒ€์ฆ๋œ ํŒจํ„ด.
11090
+ // stream ํ˜•์‹ ์‚ฌ์šฉ opt-in: LEERNESS_REPL_STREAM_FORMAT=json (์‹ค์‹œ๊ฐ„ thinking/tool_use ๋ณด๊ณ  ์‹ถ์„ ๋•Œ).
11091
+ const useStreamJson = process.env.LEERNESS_REPL_STREAM_FORMAT === 'json';
11092
+ if (provider === 'claude') {
11093
+ cmd = 'claude';
11094
+ args = useStreamJson
11095
+ ? ['--print', '--output-format=stream-json', '--verbose', promptText]
11096
+ : ['--print', promptText]; // plain text ์‘๋‹ต (์ž‘๋™ ๊ฒ€์ฆ๋จ)
11097
+ }
10957
11098
  else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', promptText]; }
10958
11099
  else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', promptText]; }
10959
11100
  else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', promptText]; }
@@ -11015,10 +11156,15 @@ async function _cliChatStream(root, provider, promptText, opts) {
11015
11156
  process.stdout.write(dim(`\n โ”€โ”€ ${provider} stream โ”€โ”€\n`));
11016
11157
  let child;
11017
11158
  try {
11159
+ // 1.9.186 (์‚ฌ์šฉ์ž ๋ช…์‹œ fix): Windows .cmd ํ˜ธํ™˜์„ ์œ„ํ•ด shell: true.
11160
+ // 1.9.185 ๊นŒ์ง€ shell: false โ†’ Windows ์—์„œ claude.cmd ๋ชป ์ฐพ์•„ ๋นˆ ์‘๋‹ต (์‚ฌ์šฉ์ž ๋ณด๊ณ : 27์ดˆ ํ›„ 0์ž).
11161
+ // _cliChat (์ž‘๋™ํ•˜๋Š” ํ•จ์ˆ˜) ์ด runCommandSafe โ†’ spawnSync shell: true ํŒจํ„ด์ด๋ผ์„œ ์ž‘๋™.
11162
+ // _cliChatStream ๋„ ๋™์ผํ•˜๊ฒŒ shell: true ์‚ฌ์šฉ (DEP0190 ์€ 1.9.184/185 fix ๋กœ ์–ต์ œ๋จ).
11163
+ // security: shell escape ์šฐ๋ ค๋Š” promptText ๊ฐ€ ์‚ฌ์šฉ์ž ์ง์ ‘ ์ž…๋ ฅ โ€” sandboxed REPL ์•ˆ์—์„œ๋งŒ ์‚ฌ์šฉ.
11018
11164
  child = cp.spawn(cmd, args, {
11019
11165
  cwd: process.cwd(),
11020
11166
  env: _scrubEnv({}),
11021
- shell: false,
11167
+ shell: true,
11022
11168
  stdio: ['ignore', 'pipe', 'pipe']
11023
11169
  });
11024
11170
  } catch (e) {
@@ -11070,7 +11216,8 @@ async function _cliChatStream(root, provider, promptText, opts) {
11070
11216
 
11071
11217
  child.stdout.on('data', chunk => {
11072
11218
  lastActivity = Date.now();
11073
- if (provider === 'claude') {
11219
+ // 1.9.186: claude default ๋Š” plain --print โ†’ JSON ๋ผ์ธ ํŒŒ์‹ฑ X. useStreamJson ์‹œ์—๋งŒ stream-json ์ฒ˜๋ฆฌ.
11220
+ if (provider === 'claude' && useStreamJson) {
11074
11221
  handleClaudeStream(chunk);
11075
11222
  } else {
11076
11223
  stopSpinner();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.185",
3
+ "version": "1.9.187",
4
4
  "description": "Leerness: ๋น„ํŒŒ๊ดด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ž๋™ ๋ฒ„์ „ ๊ฐ์ง€ยท์—…๋ฐ์ดํŠธ, ๊ณ„ํš/์ง„ํ–‰/ํ•ธ๋“œ์˜คํ”„ ์ž๋™ํ™”, ๊ฒŒ์œผ๋ฆ„ยท์‹œํฌ๋ฆฟยท์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ€๋“œ, Claude Code ์Šฌ๋ž˜์‹œ ํ†ตํ•ฉ์„ ๊ฐ–์ถ˜ ํ•œ๊ตญ์–ด ์šฐ์„  AI ๊ฐœ๋ฐœ ํ•˜๋„ค์Šค.",
5
5
  "keywords": [
6
6
  "leerness",