leerness 1.9.204 β†’ 1.9.206

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,83 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.206 β€” 2026-05-22
4
+
5
+ **🌐 i18n MVP + UI/UX 헬퍼 (μ‚¬μš©μž λͺ…μ‹œ 2μ’…).**
6
+
7
+ ### μ‚¬μš©μž λͺ…μ‹œ
8
+ > 1. *"μ„€μΉ˜ κ°€μ΄λ“œμ—μ„œ μ–Έμ–΄ 선택에 따라 μ„€μΉ˜ κ°€μ΄λ“œ 및 REPL agent λͺ¨λ“œ λ“± μ„€μ •λœ μ–Έμ–΄λ‘œ ν‘œμ‹œλ˜μ–΄μ•Ό"*
9
+ > 2. *"μ„€μΉ˜ κ°€μ΄λ“œ 및 REPL agent λͺ¨λ“œ UI UXλ₯Ό κ°œμ„ ν•΄μ€˜, λͺ¨μ…˜μ΄λ‚˜, ν…μŠ€νŠΈ 좜λ ₯방식 λ“±λ“±"*
10
+
11
+ ### 1. i18n μ‹œμŠ€ν…œ MVP (10 핡심 keys)
12
+ - `STRINGS` table (ko/en) β€” install.lang.* / install.agents.* / repl.welcome.* / common.*
13
+ - `_currentLang(root)` β€” `LEERNESS_LANG` env β†’ `.harness/LANGUAGE` β†’ 'ko' fallback
14
+ - `_t(key, lang)` β€” i18n lookup helper
15
+
16
+ ### 2. μ˜μ–΄ 선택 μ‹œ 후속 prompt μžλ™ μ˜μ–΄ ν‘œμ‹œ
17
+ - μ–Έμ–΄ 선택 μžμ²΄λŠ” ν•œκ΅­μ–΄ (μ‚¬μš©μžκ°€ μ–Έμ–΄λ₯Ό λͺ¨λ₯΄λ―€λ‘œ)
18
+ - 'en' 선택 μ‹œ `process.env.LEERNESS_LANG = 'en'` μ¦‰μ‹œ μ„€μ • β†’ agents prompt λ“± μžλ™ μ˜μ–΄ λ³€ν™˜
19
+
20
+ ### 3. UI/UX 헬퍼 (`_typewrite`, `_ui`)
21
+ - `_typewrite(text, delayMs)` β€” opt-in via `LEERNESS_TYPEWRITER=1` (TTY 만)
22
+ - `_ui.bold/dim/cyan/green/yellow/hr` β€” 색상 + ꡬ뢄선 ν‘œμ€€ν™”
23
+ - λΉ„-TTY ν™˜κ²½ μžλ™ fallback (plain text)
24
+
25
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.200~205) β€” λͺ¨λ‘ μœ μ§€
26
+
27
+ ### 5. stress-v151 β€” 16/16 PASS
28
+ - i18n (3) + UI helpers (1) + install λ‹€κ΅­μ–΄ 적용 (3) + μ„±λŠ₯ (2) + λˆ„μ  (7)
29
+ - μ„±λŠ₯: --version cold start avg **1049 ms** Β· MCP 54 도ꡬ **768 ms**
30
+
31
+ ### 6. MVP λ…ΈνŠΈ (ν™•μž₯ κ°€λŠ₯ 섀계)
32
+ - ν˜„μž¬: 10 핡심 keys (μ„€μΉ˜ κ°€μ΄λ“œ + REPL welcome + common)
33
+ - μΆ”ν›„: REPL agent λͺ¨λ“œ 전체 메뉴 / handoff / resume λ“± ν™•μž₯ κ°€λŠ₯ (점진적)
34
+
35
+ ### 7. μžλ™ release
36
+ - main μžλ™ push **68 λΌμš΄λ“œ 연속** (1.9.140~206)
37
+ - npm publish μžλ™ (1.9.178~)
38
+
39
+ ---
40
+
41
+ ## 1.9.205 β€” 2026-05-22
42
+
43
+ **⚑ ScheduleWakeup μ‚¬μš©μž μš”μ²­ μš°μ„  κ°±μ‹  (μ‚¬μš©μž λͺ…μ‹œ) β€” wakeup miss μ‹€μΈ‘ 후속.**
44
+
45
+ ### μ‚¬μš©μž λͺ…μ‹œ + 보고
46
+ > *"μžλ™λͺ¨λ“œλ‘œ μ§„ν–‰ν• λ•Œ, μ˜ˆμ •λœ μ•ŒλžŒμ΄μ „μ— μ‚¬μš©μž μš”μ²­μ΄ λ“€μ–΄μ˜€λ©΄ λ°±κ·ΈλΌμš΄λ“œμ˜ μ•ŒλžŒμ„ μ’…λ£Œν›„ λ‹€μ‹œ κ°±μ‹ ν•˜λŠ” 방식은 μ–΄λ–€μ§€"*
47
+ > *"ScheduleWakeup 10:51 KST κ°€ 지났어"* β€” μ‚¬μš©μžκ°€ λͺ…μ‹œ λ³΄κ³ ν•œ wakeup miss 사둀
48
+
49
+ ### 1. `.harness/active-wakeups.json` 등둝 좔적
50
+ - release sync-main 마무리 μ‹œ λ‹€μŒ wakeup μžλ™ 등둝
51
+ - ꡬ쑰: `{ expectedFireAt, intervalMin, source, status: pending/fired/missed/superseded, registeredAt }`
52
+ - 졜근 20개 μœ μ§€
53
+ - ν•¨μˆ˜: `_loadActiveWakeups` / `_writeActiveWakeups` / `_recordWakeup` / `_activeWakeupsPath`
54
+
55
+ ### 2. handoff μ§„μž… μ‹œ wakeup μƒνƒœ 뢄석 (`_analyzeWakeupStatus`)
56
+ | kind | 쑰건 | μ•Œλ¦Ό |
57
+ |---|---|---|
58
+ | `early` | delta < -5min (미래) | `## ⚑ μ‚¬μš©μž μ‘°κΈ° μ§„μž… 감지` |
59
+ | `on-time` | -5 ~ +5min | `βœ“ ScheduleWakeup μ •μ‹œ μ§„μž…` |
60
+ | `late` | +5 ~ +30min | (쑰용히) |
61
+ | `missed` | > +30min | `## ⏰ ScheduleWakeup miss ν™•μ •` + status μžλ™ κ°±μ‹  |
62
+
63
+ ### 3. μ‘°κΈ° μ§„μž… μ‹œ μžλ™ κ°±μ‹  κ°€μ΄λ“œ
64
+ μ‚¬μš©μžκ°€ wakeup μ˜ˆμ • μ‹œκ° 이전 μ§„μž… β†’ "λΌμš΄λ“œ 마무리 μ‹œ μƒˆ wakeup 등둝 (이전 μžλ™ superseded)" μ•ˆλ‚΄
65
+
66
+ ### 4. miss μžλ™ status κ°±μ‹ 
67
+ fire μ‹œκ°„ 지남 + 30λΆ„ 이상 β†’ status `pending` β†’ `missed` μžλ™ μ—…λ°μ΄νŠΈ β†’ 톡계 λˆ„μ 
68
+
69
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.199~204) β€” λͺ¨λ‘ μœ μ§€
70
+
71
+ ### 6. stress-v150 β€” 16/16 PASS
72
+ - 등둝 좔적 (3) + μƒνƒœ 뢄석 (2) + μž„μ‹œ μ›Œν¬μŠ€νŽ˜μ΄μŠ€ 톡합 (2) + μ„±λŠ₯ (2) + λˆ„μ  (7)
73
+ - μ„±λŠ₯: --version cold start avg **464 ms** Β· MCP 54 도ꡬ **460 ms**
74
+
75
+ ### 7. μžλ™ release
76
+ - main μžλ™ push **67 λΌμš΄λ“œ 연속** (1.9.140~205)
77
+ - npm publish μžλ™ (1.9.178~)
78
+
79
+ ---
80
+
3
81
  ## 1.9.204 β€” 2026-05-22
4
82
 
5
83
  **⏰ timezone 보강 + πŸ”„ auto-loop ν™œμ„± 라벨 (μ‚¬μš©μž λͺ…μ‹œ 2μ’…).**
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.204-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v149-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-134-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-66_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![tz-aware](https://img.shields.io/badge/timezone-KST_local_time-success)]() [![perf](https://img.shields.io/badge/cold_start-471ms-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.206-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v151-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-136-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-68_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![i18n](https://img.shields.io/badge/i18n-ko%2Fen_MVP-success)]() [![perf](https://img.shields.io/badge/cold_start-1049ms-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.204 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.206 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ ⏰ timezone 보강 (KST) Β· πŸ”„ auto-loop 25min ν—€λ“œλΌμΈ 라벨 β•‘
17
+ β•‘ 🌐 i18n ko/en MVP Β· _typewrite/_ui UI/UX 헬퍼 (μ‚¬μš©μž λͺ…μ‹œ) β•‘
18
18
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
19
19
  ```
20
20
 
@@ -530,7 +530,7 @@ MIT β€” Β© leerness contributors
530
530
  <!-- leerness:project-readme:start -->
531
531
  ## Leerness Project Harness
532
532
 
533
- 이 ν”„λ‘œμ νŠΈλŠ” Leerness v1.9.144 ν•˜λ„€μŠ€λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. AI μ—μ΄μ „νŠΈλŠ” μž‘μ—… μ „ `leerness handoff`둜 μ»¨ν…μŠ€νŠΈλ₯Ό μ μž¬ν•˜κ³ , μž‘μ—… ν›„ `leerness check`/`leerness audit`/`leerness session close`λ₯Ό μˆ˜ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.
533
+ 이 ν”„λ‘œμ νŠΈλŠ” Leerness v1.9.206 ν•˜λ„€μŠ€λ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. AI μ—μ΄μ „νŠΈλŠ” μž‘μ—… μ „ `leerness handoff`둜 μ»¨ν…μŠ€νŠΈλ₯Ό μ μž¬ν•˜κ³ , μž‘μ—… ν›„ `leerness check`/`leerness audit`/`leerness session close`λ₯Ό μˆ˜ν–‰ν•΄μ•Ό ν•©λ‹ˆλ‹€.
534
534
 
535
535
  ### Core Commands
536
536
 
@@ -572,7 +572,7 @@ leerness memory restore decision <date|title>
572
572
 
573
573
  ### MCP server (μ™ΈλΆ€ AI 톡합)
574
574
 
575
- Leerness v1.9.144λŠ” stdio JSON-RPC MCP serverλ₯Ό λ‚΄μž₯ν•©λ‹ˆλ‹€ β€” Claude Code Β· Cursor Β· Codex CLI λ“± μ™ΈλΆ€ AI에 **42개 도ꡬ**λ₯Ό λ…ΈμΆœ:
575
+ Leerness v1.9.206λŠ” stdio JSON-RPC MCP serverλ₯Ό λ‚΄μž₯ν•©λ‹ˆλ‹€ β€” Claude Code Β· Cursor Β· Codex CLI λ“± μ™ΈλΆ€ AI에 **42개 도ꡬ**λ₯Ό λ…ΈμΆœ:
576
576
 
577
577
  ```jsonc
578
578
  // μΉ΄ν…Œκ³ λ¦¬λ³„
@@ -593,7 +593,7 @@ Leerness v1.9.144λŠ” stdio JSON-RPC MCP serverλ₯Ό λ‚΄μž₯ν•©λ‹ˆλ‹€ β€” Claude Cod
593
593
  `<<autonomous-loop-dynamic>>` μ‹ ν˜Έλ§Œ 보내면 AIκ°€:
594
594
  1) λ‹€μŒ λΌμš΄λ“œ 후보 μ„ μ • β†’ 2) μ½”λ“œ λ³€κ²½ β†’ 3) stress-v* μ‹ κ·œ μž‘μ„± + λˆ„μ  νšŒκ·€ β†’ 4) e2e 219/219 β†’ 5) npm pack + git tag + GitHub release β†’ 6) main μžλ™ push (1.9.140+) β†’ 7) session close β†’ 8) λ‹€μŒ λΌμš΄λ“œ μ˜ˆμ•½.
595
595
 
596
- ν˜„μž¬ λˆ„μ : **70 λΌμš΄λ“œ (1.9.40 β†’ 1.9.144)** Β· λ§€ λΌμš΄λ“œ GitHub release/νƒœκ·Έ 생성 Β· _reports/λŠ” λΉ„κ³΅κ°œ 보쑴.
596
+ ν˜„μž¬ λˆ„μ : **70 λΌμš΄λ“œ (1.9.40 β†’ 1.9.206)** Β· λ§€ λΌμš΄λ“œ GitHub release/νƒœκ·Έ 생성 Β· _reports/λŠ” λΉ„κ³΅κ°œ 보쑴.
597
597
 
598
598
  ### μ„±λŠ₯ κ°€μ΄λ“œ (1.9.140 μΈ‘μ •)
599
599
 
@@ -631,6 +631,6 @@ leerness release pack --close --auto-main-push
631
631
  - `.harness/session-handoff.md`: λ‹€μŒ μ„Έμ…˜ μΈμˆ˜μΈκ³„ (μžλ™ μž‘μ„±)
632
632
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영ꡬ λ©”λͺ¨λ¦¬ (5 surface)
633
633
 
634
- Last synced by Leerness v1.9.144: 2026-05-20
634
+ Last synced by Leerness v1.9.206: 2026-05-22
635
635
  <!-- leerness:project-readme:end -->
636
636
 
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.204';
10
+ const VERSION = '1.9.206';
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 처리).
@@ -766,19 +766,24 @@ async function resolveInstallOptions(root, opts = {}) {
766
766
  // --no-interactive-select λ˜λŠ” LEERNESS_NO_INTERACTIVE=1 β†’ ꡬ식 숫자 선택
767
767
  const useInteractive = shouldAsk && !has('--no-interactive-select') && process.env.LEERNESS_NO_INTERACTIVE !== '1';
768
768
  if (shouldAsk && !explicitLang) {
769
+ // 1.9.206: μ–Έμ–΄ 선택 μžμ²΄λŠ” 항상 ko (μ‚¬μš©μžκ°€ μ–Έμ–΄λ₯Ό λͺ¨λ₯΄λ―€λ‘œ) β†’ 선택 ν›„ 후속은 μ„ νƒλœ μ–Έμ–΄
769
770
  if (useInteractive) {
770
- const langOpt = await _selectOne('μ„€μΉ˜ μ–Έμ–΄λ₯Ό μ„ νƒν•˜μ„Έμš”', [
771
- { label: 'μžλ™ 감지', description: '디렉토리/파일 뢄석 (ν•œκ΅­μ–΄/μ˜μ–΄ μžλ™ νŒλ³„)', id: 'auto' },
772
- { label: 'ν•œκ΅­μ–΄', description: 'λͺ¨λ“  μΈμŠ€νŠΈλŸ­μ…˜μ„ ν•œκ΅­μ–΄λ‘œ 생성', id: 'ko' },
773
- { label: 'English', description: 'λͺ¨λ“  μΈμŠ€νŠΈλŸ­μ…˜μ„ μ˜μ–΄λ‘œ 생성', id: 'en' }
771
+ const langOpt = await _selectOne(_t('install.lang.title', 'ko'), [
772
+ { label: _t('install.lang.auto', 'ko'), description: _t('install.lang.auto.desc', 'ko'), id: 'auto' },
773
+ { label: _t('install.lang.ko', 'ko'), description: _t('install.lang.ko.desc', 'ko'), id: 'ko' },
774
+ { label: _t('install.lang.en', 'ko'), description: _t('install.lang.en.desc', 'ko'), id: 'en' }
774
775
  ], { defaultIndex: 0 });
775
776
  lang = langOpt && langOpt.id ? detectLanguageValue(root, langOpt.id) : detectLanguageValue(root, 'auto');
776
777
  } else {
777
- log('\nμ„€μΉ˜ μ–Έμ–΄λ₯Ό μ„ νƒν•˜μ„Έμš”.');
778
- log('1) μžλ™ 감지'); log('2) ν•œκ΅­μ–΄'); log('3) English');
779
- const a = await ask('선택 [1]: ');
778
+ log('\n' + _t('install.lang.title', 'ko') + '.');
779
+ log('1) ' + _t('install.lang.auto', 'ko'));
780
+ log('2) ' + _t('install.lang.ko', 'ko'));
781
+ log('3) ' + _t('install.lang.en', 'ko'));
782
+ const a = await ask('Select / 선택 [1]: ');
780
783
  lang = a === '2' ? 'ko' : a === '3' ? 'en' : detectLanguageValue(root, 'auto');
781
784
  }
785
+ // 1.9.206: μ˜μ–΄ 선택 μ‹œ 후속 prompt μ˜μ–΄ ν‘œμ‹œ ν™˜κ²½λ³€μˆ˜ μ¦‰μ‹œ μ„€μ •
786
+ if (lang === 'en') process.env.LEERNESS_LANG = 'en';
782
787
  }
783
788
  // 1.9.148: μŠ€ν‚¬ prompt 제거 (μ‚¬μš©μž λͺ…μ‹œ μš”μ²­) β€” leernessκ°€ μžλ™μœΌλ‘œ 곡식 ν‘œμ€€ μŠ€ν‚¬ 5μ’… μ„€μΉ˜.
784
789
  // ν•„μš”ν•  λ•Œ μ‚¬μš©μžκ°€ leerness skill install <id> 둜 μΆ”κ°€ κ°€λŠ₯.
@@ -787,8 +792,10 @@ async function resolveInstallOptions(root, opts = {}) {
787
792
  // _selectMany 둜 Space ν† κΈ€, a 전체, n ν•΄μ œ, Enter ν™•μ •. μ„ νƒλœ μ—μ΄μ „νŠΈλ“€λ§Œ .env.example에 LEERNESS_ENABLE_* ν™œμ„±ν™”.
788
793
  let agentsOptIn = null; // string[] (닀쀑) λ˜λŠ” 'none' (선택 μ•ˆν•¨)
789
794
  if (shouldAsk && !opts._skipAgentsPrompt) {
795
+ // 1.9.206: μ„ νƒλœ μ–Έμ–΄λ‘œ prompt ν‘œμ‹œ
796
+ const _agLang = lang === 'en' ? 'en' : 'ko';
790
797
  if (useInteractive) {
791
- const picked = await _selectMany('CLI μ—μ΄μ „νŠΈ ν™œμ„±ν™” (볡수 선택, Space ν† κΈ€) β€” sub-agent μœ„μž„μš©', [
798
+ const picked = await _selectMany(_t('install.agents.title', _agLang), [
792
799
  { label: 'Claude (ANTHROPIC_API_KEY λ˜λŠ” claude CLI)', description: 'μΆ”λ‘ λ ₯ 졜고 β€” μ½”λ“œ μž‘μ„±/리뷰 κΈ°λ³Έ', id: 'claude' },
793
800
  { label: 'Codex (OpenAI codex CLI)', description: 'OpenAI μ½”λ“œ λͺ¨λΈ', id: 'codex' },
794
801
  { label: 'Gemini (gemini CLI)', description: 'Google λ©€ν‹°λͺ¨λ‹¬ λͺ¨λΈ', id: 'gemini' },
@@ -1779,6 +1786,138 @@ async function skillAutoCacheCmd(root, sub) {
1779
1786
  if (c.expired) log(` β†’ κ°±μ‹ : leerness skill auto-cache refresh`);
1780
1787
  }
1781
1788
 
1789
+ // 1.9.206: i18n μ‹œμŠ€ν…œ (μ‚¬μš©μž λͺ…μ‹œ) β€” μ„€μΉ˜ κ°€μ΄λ“œ / REPL agent λ‹€κ΅­μ–΄ + UI/UX κ°œμ„ 
1790
+ // μ‚¬μš©μž λͺ…μ‹œ: "μ„€μΉ˜ κ°€μ΄λ“œμ—μ„œ μ–Έμ–΄ 선택에 따라 μ„€μΉ˜ κ°€μ΄λ“œ 및 REPL agent λͺ¨λ“œ λ“± μ„€μ •λœ μ–Έμ–΄λ‘œ ν‘œμ‹œ"
1791
+ // 섀계: 핡심 string table (ko/en) + _t(key, lang) helper. μ„€μΉ˜ μ‹œ 선택 μ–Έμ–΄ β†’ .harness/LANGUAGE μ €μž₯.
1792
+ // lang κ²°μ •: explicit > .harness/LANGUAGE > LEERNESS_LANG env > 'ko' (default)
1793
+ const STRINGS = {
1794
+ // μ„€μΉ˜ κ°€μ΄λ“œ prompt
1795
+ 'install.lang.title': { ko: 'μ„€μΉ˜ μ–Έμ–΄λ₯Ό μ„ νƒν•˜μ„Έμš”', en: 'Select install language' },
1796
+ 'install.lang.auto': { ko: 'μžλ™ 감지', en: 'Auto detect' },
1797
+ 'install.lang.auto.desc': { ko: '디렉토리/파일 뢄석 (ν•œκ΅­μ–΄/μ˜μ–΄ μžλ™ νŒλ³„)', en: 'Analyze dir/files (auto KO/EN)' },
1798
+ 'install.lang.ko': { ko: 'ν•œκ΅­μ–΄', en: 'Korean' },
1799
+ 'install.lang.ko.desc': { ko: 'λͺ¨λ“  μΈμŠ€νŠΈλŸ­μ…˜μ„ ν•œκ΅­μ–΄λ‘œ 생성', en: 'All instructions in Korean' },
1800
+ 'install.lang.en': { ko: 'English', en: 'English' },
1801
+ 'install.lang.en.desc': { ko: 'λͺ¨λ“  μΈμŠ€νŠΈλŸ­μ…˜μ„ μ˜μ–΄λ‘œ 생성', en: 'All instructions in English' },
1802
+ 'install.agents.title': { ko: 'CLI μ—μ΄μ „νŠΈ ν™œμ„±ν™” (볡수 선택, Space ν† κΈ€) β€” sub-agent μœ„μž„μš©',
1803
+ en: 'Enable CLI agents (multi-select, Space toggle) β€” for sub-agent dispatch' },
1804
+ 'install.agents.none': { ko: '선택 μ•ˆν•¨ (λ‚˜μ€‘μ— setup-agents)', en: 'None (later setup-agents)' },
1805
+ 'install.complete': { ko: 'βœ“ μ„€μΉ˜ μ™„λ£Œ', en: 'βœ“ Install complete' },
1806
+ // REPL agent λͺ¨λ“œ
1807
+ 'repl.welcome.title': { ko: 'leerness REPL agent', en: 'leerness REPL agent' },
1808
+ 'repl.welcome.subtitle': { ko: 'Tab provider Β· Shift+Tab model Β· :help Β· /slash', en: 'Tab provider Β· Shift+Tab model Β· :help Β· /slash' },
1809
+ 'repl.welcome.start': { ko: 'μ‹œμž‘ν•˜λ €λ©΄ λ©”μ‹œμ§€λ₯Ό μž…λ ₯ν•˜μ„Έμš”', en: 'Type a message to start' },
1810
+ // 곡톡
1811
+ 'common.cancel': { ko: 'μ·¨μ†Œλ¨', en: 'Cancelled' },
1812
+ 'common.confirm': { ko: '확인', en: 'Confirm' },
1813
+ 'common.ready': { ko: 'μ€€λΉ„ μ™„λ£Œ', en: 'Ready' }
1814
+ };
1815
+ // ν˜„μž¬ μ‚¬μš© μ–Έμ–΄ κ²°μ • (env > config > 'ko')
1816
+ function _currentLang(root) {
1817
+ if (process.env.LEERNESS_LANG) return process.env.LEERNESS_LANG === 'en' ? 'en' : 'ko';
1818
+ try {
1819
+ if (root) {
1820
+ const fp = path.join(root, '.harness', 'LANGUAGE');
1821
+ if (exists(fp)) {
1822
+ const v = read(fp).trim().toLowerCase();
1823
+ if (v === 'en' || v === 'english') return 'en';
1824
+ if (v === 'ko' || v === 'korean' || v === 'kr') return 'ko';
1825
+ }
1826
+ }
1827
+ } catch {}
1828
+ return 'ko'; // default
1829
+ }
1830
+ function _t(key, lang) {
1831
+ const entry = STRINGS[key];
1832
+ if (!entry) return key; // fallback: ν‚€ 자체 λ°˜ν™˜
1833
+ return entry[lang || 'ko'] || entry.ko || key;
1834
+ }
1835
+
1836
+ // 1.9.206: UI/UX κ°œμ„  β€” typewriter / fade-in 효과 (opt-in via LEERNESS_TYPEWRITER=1)
1837
+ function _typewrite(text, delayMs) {
1838
+ delayMs = delayMs || 15;
1839
+ if (process.env.LEERNESS_TYPEWRITER !== '1' || !process.stdout.isTTY) {
1840
+ process.stdout.write(text);
1841
+ return Promise.resolve();
1842
+ }
1843
+ return new Promise((resolve) => {
1844
+ let i = 0;
1845
+ const step = () => {
1846
+ if (i >= text.length) return resolve();
1847
+ process.stdout.write(text[i++]);
1848
+ setTimeout(step, delayMs);
1849
+ };
1850
+ step();
1851
+ });
1852
+ }
1853
+ // 색상 helper (TTY μ‹œ ANSI, λΉ„-TTY μ‹œ plain)
1854
+ const _ui = {
1855
+ bold: s => process.stdout.isTTY ? `\x1b[1m${s}\x1b[0m` : s,
1856
+ dim: s => process.stdout.isTTY ? `\x1b[2m${s}\x1b[0m` : s,
1857
+ cyan: s => process.stdout.isTTY ? `\x1b[36m${s}\x1b[0m` : s,
1858
+ green: s => process.stdout.isTTY ? `\x1b[32m${s}\x1b[0m` : s,
1859
+ yellow: s => process.stdout.isTTY ? `\x1b[33m${s}\x1b[0m` : s,
1860
+ // ꡬ뢄선 μ •λ ¬ (터미널 width κ³ λ €)
1861
+ hr: (char) => {
1862
+ const w = process.stdout.columns || 60;
1863
+ return (char || '─').repeat(Math.min(w - 2, 60));
1864
+ }
1865
+ };
1866
+
1867
+ // 1.9.205: ScheduleWakeup 등둝 좔적 (μ‚¬μš©μž λͺ…μ‹œ)
1868
+ // "μ˜ˆμ •λœ μ•ŒλžŒμ΄μ „μ— μ‚¬μš©μž μš”μ²­μ΄ λ“€μ–΄μ˜€λ©΄ λ°±κ·ΈλΌμš΄λ“œμ˜ μ•ŒλžŒμ„ μ’…λ£Œν›„ λ‹€μ‹œ κ°±μ‹ "
1869
+ // λΌμš΄λ“œ 마무리 μ‹œ λ“±λ‘λœ wakeup id/expectedFireAt 을 .harness/active-wakeups.json 에 기둝 β†’
1870
+ // handoff/resume μ§„μž… μ‹œ expected fire κ°€ 과거이면 "wakeup miss λ˜λŠ” μ‚¬μš©μž μ‘°κΈ° μ§„μž…" 감지 β†’
1871
+ // 메인 μ—μ΄μ „νŠΈκ°€ μƒˆ wakeup ν˜ΈμΆœν•˜λΌλŠ” λͺ…ν™•ν•œ μ•ˆλ‚΄
1872
+ function _activeWakeupsPath(root) { return path.join(root, '.harness', 'active-wakeups.json'); }
1873
+ function _loadActiveWakeups(root) {
1874
+ try {
1875
+ const fp = _activeWakeupsPath(root);
1876
+ if (!exists(fp)) return { wakeups: [] };
1877
+ const j = JSON.parse(read(fp));
1878
+ return { wakeups: Array.isArray(j.wakeups) ? j.wakeups : [] };
1879
+ } catch { return { wakeups: [] }; }
1880
+ }
1881
+ function _writeActiveWakeups(root, wakeups) {
1882
+ try {
1883
+ mkdirp(path.join(root, '.harness'));
1884
+ writeUtf8(_activeWakeupsPath(root), JSON.stringify({ wakeups, updatedAt: new Date().toISOString() }, null, 2));
1885
+ return true;
1886
+ } catch { return false; }
1887
+ }
1888
+ function _recordWakeup(root, entry) {
1889
+ const state = _loadActiveWakeups(root);
1890
+ // entry: { expectedFireAt, intervalMin, source, status: 'pending'|'fired'|'missed'|'superseded' }
1891
+ const e = { ...entry, registeredAt: new Date().toISOString() };
1892
+ // 같은 expectedFireAt 쀑볡 μ‹œ replace
1893
+ state.wakeups = state.wakeups.filter(w => w.expectedFireAt !== e.expectedFireAt);
1894
+ state.wakeups.push(e);
1895
+ // 졜근 20개만 μœ μ§€
1896
+ if (state.wakeups.length > 20) state.wakeups = state.wakeups.slice(-20);
1897
+ _writeActiveWakeups(root, state.wakeups);
1898
+ return e;
1899
+ }
1900
+ // wakeup miss / μ‚¬μš©μž μ‘°κΈ° μ§„μž… 감지 β€” ν™œμ„± wakeup 쀑 κ°€μž₯ κ°€κΉŒμš΄ 미래/κ³Όκ±° fire 뢄석
1901
+ function _analyzeWakeupStatus(root) {
1902
+ const state = _loadActiveWakeups(root);
1903
+ const pending = state.wakeups.filter(w => w.status === 'pending');
1904
+ if (pending.length === 0) return { hasWakeup: false };
1905
+ // κ°€μž₯ 졜근 λ“±λ‘λœ pending wakeup
1906
+ const latest = pending.sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())[0];
1907
+ const now = Date.now();
1908
+ const expectedMs = new Date(latest.expectedFireAt).getTime();
1909
+ const deltaMin = Math.floor((now - expectedMs) / 60000);
1910
+ // deltaMin < 0 β†’ 아직 fire μ‹œκ°„ μ „ (μ‚¬μš©μž μ‘°κΈ° μ§„μž…)
1911
+ // deltaMin >= 0 β†’ fire μ‹œκ°„ 지남 (μ •μ‹œ λ˜λŠ” miss)
1912
+ // |deltaMin| < 5 β†’ μ •μ‹œ λΆ€κ·Ό
1913
+ let kind;
1914
+ if (deltaMin < -5) kind = 'early'; // μ‚¬μš©μž μ‘°κΈ° μ§„μž…
1915
+ else if (deltaMin > 30) kind = 'missed'; // wakeup miss
1916
+ else if (deltaMin > 5) kind = 'late'; // μ•½κ°„ μ§€μ—°
1917
+ else kind = 'on-time';
1918
+ return { hasWakeup: true, latest, deltaMin, kind, pending: pending.length };
1919
+ }
1920
+
1782
1921
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
1783
1922
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
1784
1923
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -4224,6 +4363,40 @@ function handoff(root) {
4224
4363
  }
4225
4364
  } catch {}
4226
4365
  }
4366
+ // 1.9.205: ScheduleWakeup μƒνƒœ 뢄석 + μ‚¬μš©μž μ‘°κΈ° μ§„μž… / miss 감지 (μ‚¬μš©μž λͺ…μ‹œ)
4367
+ if (process.env.LEERNESS_NO_WAKEUP_STATUS !== '1' && !has('--quiet')) {
4368
+ try {
4369
+ const wakeup = _analyzeWakeupStatus(root);
4370
+ if (wakeup.hasWakeup) {
4371
+ const isTty = process.stdout && process.stdout.isTTY;
4372
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
4373
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
4374
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
4375
+ const expectedLocal = _formatLocal(wakeup.latest.expectedFireAt);
4376
+ if (wakeup.kind === 'early') {
4377
+ // μ‚¬μš©μž μ‘°κΈ° μ§„μž… β€” κΈ°μ‘΄ wakeup κ°±μ‹  ꢌμž₯
4378
+ log(yel(`## ⚑ μ‚¬μš©μž μ‘°κΈ° μ§„μž… 감지 (1.9.205) β€” λ“±λ‘λœ wakeup ${Math.abs(wakeup.deltaMin)}λΆ„ ν›„ fire μ˜ˆμ •`));
4379
+ log(dim(` μ˜ˆμ • fire: ${expectedLocal} (${wakeup.latest.source})`));
4380
+ log(dim(` β†’ ꢌμž₯: λΌμš΄λ“œ 마무리 μ‹œ μƒˆ wakeup 등둝 (이전 μžλ™ superseded)`));
4381
+ log('');
4382
+ } else if (wakeup.kind === 'missed') {
4383
+ // wakeup miss β€” fire μ‹œκ°„ 지남
4384
+ log(yel(`## ⏰ ScheduleWakeup miss ν™•μ • (1.9.205) β€” μ˜ˆμ • fire ${wakeup.deltaMin}λΆ„ μ „`));
4385
+ log(dim(` μ˜ˆμ • fire: ${expectedLocal} (${wakeup.latest.source})`));
4386
+ log(dim(` β†’ 회볡: λΌμš΄λ“œ 마무리 μ‹œ μƒˆ wakeup μžλ™ 등둝`));
4387
+ // μžλ™μœΌλ‘œ status λ₯Ό 'missed' 둜 μ—…λ°μ΄νŠΈ
4388
+ try {
4389
+ const s = _loadActiveWakeups(root);
4390
+ const idx = s.wakeups.findIndex(w => w.expectedFireAt === wakeup.latest.expectedFireAt);
4391
+ if (idx >= 0) { s.wakeups[idx].status = 'missed'; _writeActiveWakeups(root, s.wakeups); }
4392
+ } catch {}
4393
+ log('');
4394
+ } else if (wakeup.kind === 'on-time') {
4395
+ log(grn(` βœ“ ScheduleWakeup μ •μ‹œ μ§„μž… (1.9.205, delta ${wakeup.deltaMin}λΆ„)`));
4396
+ }
4397
+ }
4398
+ } catch {}
4399
+ }
4227
4400
  // 1.9.203: auto-resume-plan μ•Œλ¦Ό β€” λΌμš΄λ“œ 마무리 μ‹œ μ €μž₯된 plan μžλ™ λ…ΈμΆœ
4228
4401
  // μ‚¬μš©μž λͺ…μ‹œ: "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
4229
4402
  if (_autoResumePlan && !has('--quiet') && process.env.LEERNESS_NO_RESUME_PLAN !== '1') {
@@ -9334,6 +9507,14 @@ function _publishToNpm(root, opts = {}) {
9334
9507
  note: `λ‹€μŒ wakeup μ‹œ: 1) leerness resume . 으둜 plan 확인 2) leerness handoff . μ‹€ν–‰ 3) leerness next-action take 둜 μ¦‰μ‹œ task μ§„μž…`
9335
9508
  });
9336
9509
  _writeAutoResumePlan(root, plan);
9510
+ // 1.9.205: λ‹€μŒ wakeup 등둝 좔적 (μ‚¬μš©μž λͺ…μ‹œ κ°±μ‹ μš©)
9511
+ _recordWakeup(root, {
9512
+ expectedFireAt: plan.expectedFireAt,
9513
+ intervalMin: plan.intervalMin || 25,
9514
+ source: 'release-sync-main',
9515
+ status: 'pending',
9516
+ fromVersion: pkgVersion
9517
+ });
9337
9518
  } catch {}
9338
9519
  } else {
9339
9520
  const errOut = (pubR.stderr || pubR.stdout || '').slice(-400);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.204",
3
+ "version": "1.9.206",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",