leerness 1.9.186 โ†’ 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,118 @@
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
+
3
116
  ## 1.9.186 โ€” 2026-05-21
4
117
 
5
118
  **๐Ÿ› 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.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.186 AI Agent Reliability Harness + Sandbox โ•‘
15
+ โ•‘ v1.9.187 AI Agent Reliability Harness + Sandbox โ•‘
16
16
  โ•‘ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ•‘
17
- โ•‘ ๐Ÿ› REPL stream 0์ž ์‘๋‹ต BUG fix ยท ๊ตฌ์กฐ ์ตœ์ ํ™” ์ฒดํฌ โ•‘
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.186';
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) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.186",
3
+ "version": "1.9.187",
4
4
  "description": "Leerness: ๋น„ํŒŒ๊ดด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ž๋™ ๋ฒ„์ „ ๊ฐ์ง€ยท์—…๋ฐ์ดํŠธ, ๊ณ„ํš/์ง„ํ–‰/ํ•ธ๋“œ์˜คํ”„ ์ž๋™ํ™”, ๊ฒŒ์œผ๋ฆ„ยท์‹œํฌ๋ฆฟยท์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ€๋“œ, Claude Code ์Šฌ๋ž˜์‹œ ํ†ตํ•ฉ์„ ๊ฐ–์ถ˜ ํ•œ๊ตญ์–ด ์šฐ์„  AI ๊ฐœ๋ฐœ ํ•˜๋„ค์Šค.",
5
5
  "keywords": [
6
6
  "leerness",