leerness 1.9.205 β†’ 1.9.207

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,88 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.207 β€” 2026-05-22
4
+
5
+ **πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 MVP (μ‚¬μš©μž λͺ…μ‹œ).**
6
+
7
+ ### μ‚¬μš©μž λͺ…μ‹œ
8
+ > *"leernessκ°€ 적용된 ν”„λ‘œμ νŠΈμ—μ„œ, μ΅œκ·Όμ— λͺ…λ Ήλ°›μ•„ μ§„ν–‰ν•œ 것쀑에 λˆ„λ½λœκ²Œ μ—†λŠ”μ§€ ν™•μΈν•˜λŠ” μ ˆμ°¨λ„ μžˆλŠ”μ§€ ν™•μΈν•΄μ€˜ / λ‹€μŒ λΌμš΄λ“œμ‹œ μ°Έμ‘°"*
9
+
10
+ ### 1. `.harness/user-requests.json` β€” μ‚¬μš©μž μš”μ²­ λˆ„μ  기둝
11
+ - `_userRequestsPath/_loadUserRequests/_writeUserRequests` 파일 I/O 헬퍼
12
+ - `_recordUserRequest(root, text, opts)` β€” μžλ™ ID λΆ€μ—¬(UR-XXXX) + 쀑볡 λ°©μ§€(동일 ν…μŠ€νŠΈ + open μƒνƒœ)
13
+ - `_updateUserRequest(root, id, patch)` β€” status μ „ν™˜ (open β†’ in-progress β†’ completed/dropped)
14
+ - 졜근 200개 μœ μ§€ (rotate)
15
+
16
+ ### 2. `_auditUserRequests(root)` β€” λˆ„λ½ 후보 탐지
17
+ - open/in-progress μš”μ²­ ν…μŠ€νŠΈμ˜ 첫 12 단어 μΆ”μΆœ β†’ task-log/plan/decisions 와 비ꡐ
18
+ - hits β‰₯ min(3, wordsΓ—0.5) β†’ **tracked** (μ–΄λ”˜κ°€ μž‘μ—…μ΄ μ‹œμž‘λ¨)
19
+ - κ·Έ μ™Έ β†’ **missing** (λˆ„λ½ 후보, AI/μ‚¬μš©μžκ°€ μžŠμ€ κ°€λŠ₯μ„±)
20
+ - 7일+ open β†’ **stale** (μž¬κ²€ν†  ν•„μš”)
21
+
22
+ ### 3. `leerness requests <sub>` CLI (μ‚¬μš©μž λͺ…μ‹œ β€” "확인 절차")
23
+ - `audit` β€” missing/tracked/stale 보고 (--json κ°€λŠ₯)
24
+ - `add "<text>"` β€” μ‚¬μš©μž μš”μ²­ μˆ˜λ™ 기둝
25
+ - `list [--status open|completed|dropped]` β€” 전체 좜λ ₯
26
+ - `complete <UR-id>` / `drop <UR-id>` β€” μƒνƒœ μ „ν™˜
27
+
28
+ ### 4. handoff ν—€λ“œλΌμΈ 톡합 (13번째 μš”μ†Œ)
29
+ - missing β‰₯ 1 μ‹œ: `πŸ“₯ λ―Έλ‹΅ μš”μ²­ N건` (red)
30
+ - open > 0 (λͺ¨λ‘ tracked) μ‹œ: `πŸ“₯ μš”μ²­ N (tracked)` (green)
31
+ - ν—€λ“œλΌμΈ 라벨: `1.9.81/93/113/152/162/192/197/204/207`
32
+
33
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.200~206) β€” λͺ¨λ‘ μœ μ§€
34
+
35
+ ### 6. stress-v152 β€” 17/17 PASS
36
+ - 1.9.207 (8) + μ„±λŠ₯ (2) + λˆ„μ  νšŒκ·€ (7)
37
+ - μ‚¬μš©μž μš”μ²­ 라이프사이클 격리 tmpdir ν…ŒμŠ€νŠΈ (add β†’ list β†’ audit β†’ complete β†’ drop)
38
+ - 쀑볡 λ°©μ§€ 확인
39
+ - μ„±λŠ₯: --version cold start avg 2206ms Β· MCP 54 도ꡬ 1597ms
40
+
41
+ ### 7. μžλ™ release (69 λΌμš΄λ“œ main-push streak Β· 30 λΌμš΄λ“œ npm publish streak)
42
+ - `release sync-main` μžλ™ β†’ main merge + npm publish leerness@1.9.207
43
+ - 1.9.140λΆ€ν„° main μžλ™ push (69 λΌμš΄λ“œ 무쀑단)
44
+ - 1.9.178λΆ€ν„° npm publish (30 λΌμš΄λ“œ 무쀑단)
45
+
46
+ ---
47
+
48
+ ## 1.9.206 β€” 2026-05-22
49
+
50
+ **🌐 i18n MVP + UI/UX 헬퍼 (μ‚¬μš©μž λͺ…μ‹œ 2μ’…).**
51
+
52
+ ### μ‚¬μš©μž λͺ…μ‹œ
53
+ > 1. *"μ„€μΉ˜ κ°€μ΄λ“œμ—μ„œ μ–Έμ–΄ 선택에 따라 μ„€μΉ˜ κ°€μ΄λ“œ 및 REPL agent λͺ¨λ“œ λ“± μ„€μ •λœ μ–Έμ–΄λ‘œ ν‘œμ‹œλ˜μ–΄μ•Ό"*
54
+ > 2. *"μ„€μΉ˜ κ°€μ΄λ“œ 및 REPL agent λͺ¨λ“œ UI UXλ₯Ό κ°œμ„ ν•΄μ€˜, λͺ¨μ…˜μ΄λ‚˜, ν…μŠ€νŠΈ 좜λ ₯방식 λ“±λ“±"*
55
+
56
+ ### 1. i18n μ‹œμŠ€ν…œ MVP (10 핡심 keys)
57
+ - `STRINGS` table (ko/en) β€” install.lang.* / install.agents.* / repl.welcome.* / common.*
58
+ - `_currentLang(root)` β€” `LEERNESS_LANG` env β†’ `.harness/LANGUAGE` β†’ 'ko' fallback
59
+ - `_t(key, lang)` β€” i18n lookup helper
60
+
61
+ ### 2. μ˜μ–΄ 선택 μ‹œ 후속 prompt μžλ™ μ˜μ–΄ ν‘œμ‹œ
62
+ - μ–Έμ–΄ 선택 μžμ²΄λŠ” ν•œκ΅­μ–΄ (μ‚¬μš©μžκ°€ μ–Έμ–΄λ₯Ό λͺ¨λ₯΄λ―€λ‘œ)
63
+ - 'en' 선택 μ‹œ `process.env.LEERNESS_LANG = 'en'` μ¦‰μ‹œ μ„€μ • β†’ agents prompt λ“± μžλ™ μ˜μ–΄ λ³€ν™˜
64
+
65
+ ### 3. UI/UX 헬퍼 (`_typewrite`, `_ui`)
66
+ - `_typewrite(text, delayMs)` β€” opt-in via `LEERNESS_TYPEWRITER=1` (TTY 만)
67
+ - `_ui.bold/dim/cyan/green/yellow/hr` β€” 색상 + ꡬ뢄선 ν‘œμ€€ν™”
68
+ - λΉ„-TTY ν™˜κ²½ μžλ™ fallback (plain text)
69
+
70
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.200~205) β€” λͺ¨λ‘ μœ μ§€
71
+
72
+ ### 5. stress-v151 β€” 16/16 PASS
73
+ - i18n (3) + UI helpers (1) + install λ‹€κ΅­μ–΄ 적용 (3) + μ„±λŠ₯ (2) + λˆ„μ  (7)
74
+ - μ„±λŠ₯: --version cold start avg **1049 ms** Β· MCP 54 도ꡬ **768 ms**
75
+
76
+ ### 6. MVP λ…ΈνŠΈ (ν™•μž₯ κ°€λŠ₯ 섀계)
77
+ - ν˜„μž¬: 10 핡심 keys (μ„€μΉ˜ κ°€μ΄λ“œ + REPL welcome + common)
78
+ - μΆ”ν›„: REPL agent λͺ¨λ“œ 전체 메뉴 / handoff / resume λ“± ν™•μž₯ κ°€λŠ₯ (점진적)
79
+
80
+ ### 7. μžλ™ release
81
+ - main μžλ™ push **68 λΌμš΄λ“œ 연속** (1.9.140~206)
82
+ - npm publish μžλ™ (1.9.178~)
83
+
84
+ ---
85
+
3
86
  ## 1.9.205 β€” 2026-05-22
4
87
 
5
88
  **⚑ ScheduleWakeup μ‚¬μš©μž μš”μ²­ μš°μ„  κ°±μ‹  (μ‚¬μš©μž λͺ…μ‹œ) β€” wakeup miss μ‹€μΈ‘ 후속.**
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.205-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v150-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-135-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-67_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![wakeup-track](https://img.shields.io/badge/wakeup-early%2Fmissed_감지-success)]() [![perf](https://img.shields.io/badge/cold_start-464ms-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.207-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v152-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-137-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-69_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![requests](https://img.shields.io/badge/requests-audit_λˆ„λ½ν™•μΈ-success)]() [![perf](https://img.shields.io/badge/cold_start-2206ms-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.205 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.207 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ ⚑ wakeup early/missed 감지 + μžλ™ κ°±μ‹  κ°€μ΄λ“œ (μ‚¬μš©μž λͺ…μ‹œ) β•‘
17
+ β•‘ πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 (requests audit) (μ‚¬μš©μž λͺ…μ‹œ) β•‘
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.205';
10
+ const VERSION = '1.9.207';
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,84 @@ 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
+
1782
1867
  // 1.9.205: ScheduleWakeup 등둝 좔적 (μ‚¬μš©μž λͺ…μ‹œ)
1783
1868
  // "μ˜ˆμ •λœ μ•ŒλžŒμ΄μ „μ— μ‚¬μš©μž μš”μ²­μ΄ λ“€μ–΄μ˜€λ©΄ λ°±κ·ΈλΌμš΄λ“œμ˜ μ•ŒλžŒμ„ μ’…λ£Œν›„ λ‹€μ‹œ κ°±μ‹ "
1784
1869
  // λΌμš΄λ“œ 마무리 μ‹œ λ“±λ‘λœ wakeup id/expectedFireAt 을 .harness/active-wakeups.json 에 기둝 β†’
@@ -1833,6 +1918,94 @@ function _analyzeWakeupStatus(root) {
1833
1918
  return { hasWakeup: true, latest, deltaMin, kind, pending: pending.length };
1834
1919
  }
1835
1920
 
1921
+ // 1.9.207: μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 β€” μ‚¬μš©μž λͺ…μ‹œ
1922
+ // "μ΅œκ·Όμ— λͺ…λ Ήλ°›μ•„ μ§„ν–‰ν•œ 것쀑에 λˆ„λ½λœκ²Œ μ—†λŠ”μ§€ ν™•μΈν•˜λŠ” μ ˆμ°¨λ„ μžˆλŠ”μ§€"
1923
+ // .harness/user-requests.json 에 μ‚¬μš©μž λͺ…μ‹œ μš”μ²­μ„ λˆ„μ  기둝 β†’ audit μ‹œ task-log/plan/decisions 와 λ§€μΉ­ β†’ λˆ„λ½ 보고
1924
+ // λ ˆμ½”λ“œ ꡬ쑰: { id, text, recordedAt, status: 'open'|'in-progress'|'completed'|'dropped',
1925
+ // linkedTaskIds?, linkedPlanIds?, linkedDecisionIds?, notes?, completedAt?, droppedAt? }
1926
+ function _userRequestsPath(root) { return path.join(root, '.harness', 'user-requests.json'); }
1927
+ function _loadUserRequests(root) {
1928
+ try {
1929
+ const fp = _userRequestsPath(root);
1930
+ if (!exists(fp)) return { requests: [] };
1931
+ const j = JSON.parse(read(fp));
1932
+ return { requests: Array.isArray(j.requests) ? j.requests : [] };
1933
+ } catch { return { requests: [] }; }
1934
+ }
1935
+ function _writeUserRequests(root, requests) {
1936
+ try {
1937
+ mkdirp(path.join(root, '.harness'));
1938
+ writeUtf8(_userRequestsPath(root), JSON.stringify({ requests, updatedAt: new Date().toISOString() }, null, 2));
1939
+ return true;
1940
+ } catch { return false; }
1941
+ }
1942
+ function _recordUserRequest(root, text, opts = {}) {
1943
+ if (!text || typeof text !== 'string') return null;
1944
+ const state = _loadUserRequests(root);
1945
+ // 쀑볡 λ°©μ§€: 동일 ν…μŠ€νŠΈ + open μƒνƒœ 이미 쑴재 μ‹œ skip
1946
+ const norm = text.trim().slice(0, 500);
1947
+ const dup = state.requests.find(r => r.text === norm && (r.status === 'open' || r.status === 'in-progress'));
1948
+ if (dup) return dup;
1949
+ const id = 'UR-' + String(state.requests.length + 1).padStart(4, '0');
1950
+ const entry = {
1951
+ id, text: norm, recordedAt: new Date().toISOString(),
1952
+ status: opts.status || 'open',
1953
+ linkedTaskIds: opts.linkedTaskIds || [],
1954
+ linkedPlanIds: opts.linkedPlanIds || [],
1955
+ linkedDecisionIds: opts.linkedDecisionIds || [],
1956
+ notes: opts.notes || ''
1957
+ };
1958
+ state.requests.push(entry);
1959
+ // 졜근 200개만 μœ μ§€
1960
+ if (state.requests.length > 200) state.requests = state.requests.slice(-200);
1961
+ _writeUserRequests(root, state.requests);
1962
+ return entry;
1963
+ }
1964
+ function _updateUserRequest(root, id, patch) {
1965
+ const state = _loadUserRequests(root);
1966
+ const idx = state.requests.findIndex(r => r.id === id);
1967
+ if (idx < 0) return null;
1968
+ state.requests[idx] = { ...state.requests[idx], ...patch, updatedAt: new Date().toISOString() };
1969
+ if (patch.status === 'completed' && !state.requests[idx].completedAt) state.requests[idx].completedAt = new Date().toISOString();
1970
+ if (patch.status === 'dropped' && !state.requests[idx].droppedAt) state.requests[idx].droppedAt = new Date().toISOString();
1971
+ _writeUserRequests(root, state.requests);
1972
+ return state.requests[idx];
1973
+ }
1974
+ // audit: open/in-progress μš”μ²­ 쀑 task-log 에 λ§€μΉ­λ˜λŠ” μž‘μ—…μ΄ μ—†λŠ” ν•­λͺ©μ„ "λˆ„λ½ 후보"둜 보고
1975
+ function _auditUserRequests(root) {
1976
+ const state = _loadUserRequests(root);
1977
+ const open = state.requests.filter(r => r.status === 'open' || r.status === 'in-progress');
1978
+ const taskLog = (() => { try { return read(path.join(root, '.harness', 'task-log.md')); } catch { return ''; } })();
1979
+ const plan = (() => { try { return read(path.join(root, '.harness', 'plan.md')); } catch { return ''; } })();
1980
+ const decisions = (() => { try { return read(path.join(root, '.harness', 'decisions.md')); } catch { return ''; } })();
1981
+ const haystack = (taskLog + '\n' + plan + '\n' + decisions).toLowerCase();
1982
+ const missing = [];
1983
+ const tracked = [];
1984
+ open.forEach(req => {
1985
+ const linked = (req.linkedTaskIds && req.linkedTaskIds.length > 0)
1986
+ || (req.linkedPlanIds && req.linkedPlanIds.length > 0)
1987
+ || (req.linkedDecisionIds && req.linkedDecisionIds.length > 0);
1988
+ // ν‚€μ›Œλ“œ λ§€μΉ­ β€” μš”μ²­ ν…μŠ€νŠΈμ˜ 처음 8단어 쀑 3단어 이상 task-log/plan/decisions 에 쑴재 β†’ tracked
1989
+ const words = req.text.toLowerCase().split(/[\s,.()/\\-]+/).filter(w => w.length >= 2).slice(0, 12);
1990
+ const hits = words.filter(w => haystack.includes(w)).length;
1991
+ const matched = hits >= Math.min(3, Math.ceil(words.length * 0.5));
1992
+ if (linked || matched) tracked.push({ ...req, hits, words: words.length });
1993
+ else missing.push({ ...req, hits, words: words.length });
1994
+ });
1995
+ // 7일 이상 묡은 open μš”μ²­ (stale)
1996
+ const now = Date.now();
1997
+ const stale = open.filter(r => (now - new Date(r.recordedAt).getTime()) > 7 * 24 * 60 * 60 * 1000);
1998
+ return {
1999
+ total: state.requests.length,
2000
+ open: open.length,
2001
+ missing,
2002
+ tracked,
2003
+ stale,
2004
+ completed: state.requests.filter(r => r.status === 'completed').length,
2005
+ dropped: state.requests.filter(r => r.status === 'dropped').length
2006
+ };
2007
+ }
2008
+
1836
2009
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
1837
2010
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
1838
2011
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -1942,6 +2115,122 @@ function resumeCmd(root) {
1942
2115
  log(grn(`βœ“ resume μ€€λΉ„ μ™„λ£Œ β€” ꢌμž₯: leerness handoff . λ˜λŠ” leerness next-action take`));
1943
2116
  }
1944
2117
 
2118
+ // 1.9.207: leerness requests <sub> β€” μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 CLI (μ‚¬μš©μž λͺ…μ‹œ)
2119
+ // audit β†’ open μš”μ²­ 쀑 task-log/plan/decisions 에 λ§€μΉ­ μ•ˆ λ˜λŠ” ν•­λͺ© 보고 (λˆ„λ½ 후보)
2120
+ // add β†’ μ‚¬μš©μž μš”μ²­ μˆ˜λ™ 기둝 (예: leerness requests add "API rate limit 5/s 적용")
2121
+ // list β†’ λͺ¨λ“  μš”μ²­ 좜λ ₯ (--json μ˜΅μ…˜)
2122
+ // complete <id> β†’ μš”μ²­ μ™„λ£Œ ν‘œμ‹œ
2123
+ // drop <id> β†’ μš”μ²­ λ“œλ‘­ ν‘œμ‹œ (μ‚¬μš©μž λͺ…μ‹œ μ·¨μ†Œ)
2124
+ function requestsCmd(root, sub, ...rest) {
2125
+ root = absRoot(root);
2126
+ const isTty = process.stdout && process.stdout.isTTY;
2127
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2128
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2129
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2130
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
2131
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2132
+
2133
+ if (!sub || sub === 'help' || sub === '--help') {
2134
+ log(`# leerness requests (1.9.207) β€” μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차`);
2135
+ log('');
2136
+ log(` audit β†’ λˆ„λ½ 후보 + tracked + stale 보고 (--json κ°€λŠ₯)`);
2137
+ log(` add "<text>" β†’ μ‚¬μš©μž μš”μ²­ μˆ˜λ™ 기둝`);
2138
+ log(` list β†’ 전체 μš”μ²­ 좜λ ₯ (--json κ°€λŠ₯, --status open|completed|dropped)`);
2139
+ log(` complete <id> β†’ μš”μ²­ μ™„λ£Œ ν‘œμ‹œ (예: UR-0003)`);
2140
+ log(` drop <id> β†’ μš”μ²­ λ“œλ‘­ ν‘œμ‹œ`);
2141
+ return;
2142
+ }
2143
+
2144
+ if (sub === 'add') {
2145
+ const text = rest.filter(x => !x.startsWith('-')).join(' ');
2146
+ if (!text) { console.error('Usage: leerness requests add "<request text>"'); process.exit(1); }
2147
+ const entry = _recordUserRequest(root, text);
2148
+ if (!entry) { console.error('failed to record'); process.exit(1); }
2149
+ if (has('--json')) { log(JSON.stringify(entry, null, 2)); return; }
2150
+ log(grn(`βœ“ recorded ${entry.id}: ${entry.text}`));
2151
+ return;
2152
+ }
2153
+
2154
+ if (sub === 'list') {
2155
+ const state = _loadUserRequests(root);
2156
+ const filterStatus = arg('--status', null);
2157
+ let list = state.requests;
2158
+ if (filterStatus) list = list.filter(r => r.status === filterStatus);
2159
+ if (has('--json')) { log(JSON.stringify({ total: state.requests.length, filtered: list.length, requests: list }, null, 2)); return; }
2160
+ log(cyan(`# leerness requests list (1.9.207)`));
2161
+ log(` total ${state.requests.length}${filterStatus ? ` Β· filter status=${filterStatus} β†’ ${list.length}` : ''}`);
2162
+ log('');
2163
+ if (list.length === 0) { log(dim(' (μ—†μŒ)')); return; }
2164
+ for (const r of list) {
2165
+ const statusIcon = r.status === 'completed' ? 'βœ“' : (r.status === 'dropped' ? 'βœ—' : (r.status === 'in-progress' ? 'β–Ά' : 'β—―'));
2166
+ const recordedDay = (r.recordedAt || '').slice(0, 10);
2167
+ log(` ${statusIcon} [${r.id}] ${dim(recordedDay)} ${r.text.slice(0, 100)}${r.text.length > 100 ? '…' : ''}`);
2168
+ if (r.linkedTaskIds && r.linkedTaskIds.length > 0) log(dim(` linked tasks: ${r.linkedTaskIds.join(', ')}`));
2169
+ }
2170
+ return;
2171
+ }
2172
+
2173
+ if (sub === 'complete') {
2174
+ const id = rest[0];
2175
+ if (!id) { console.error('Usage: leerness requests complete <id>'); process.exit(1); }
2176
+ const updated = _updateUserRequest(root, id, { status: 'completed' });
2177
+ if (!updated) { console.error(`request not found: ${id}`); process.exit(1); }
2178
+ if (has('--json')) { log(JSON.stringify(updated, null, 2)); return; }
2179
+ log(grn(`βœ“ completed ${updated.id}`));
2180
+ return;
2181
+ }
2182
+
2183
+ if (sub === 'drop') {
2184
+ const id = rest[0];
2185
+ if (!id) { console.error('Usage: leerness requests drop <id>'); process.exit(1); }
2186
+ const updated = _updateUserRequest(root, id, { status: 'dropped' });
2187
+ if (!updated) { console.error(`request not found: ${id}`); process.exit(1); }
2188
+ if (has('--json')) { log(JSON.stringify(updated, null, 2)); return; }
2189
+ log(yel(`βœ— dropped ${updated.id}`));
2190
+ return;
2191
+ }
2192
+
2193
+ if (sub === 'audit') {
2194
+ const audit = _auditUserRequests(root);
2195
+ if (has('--json')) { log(JSON.stringify(audit, null, 2)); return; }
2196
+ log(cyan(`# leerness requests audit (1.9.207) β€” μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차`));
2197
+ log('');
2198
+ log(` πŸ“Š 톡계: total ${audit.total} Β· open ${audit.open} Β· completed ${audit.completed} Β· dropped ${audit.dropped}`);
2199
+ log('');
2200
+ if (audit.missing.length === 0) {
2201
+ log(grn(` βœ“ λˆ„λ½ μ—†μŒ β€” open μš”μ²­ λͺ¨λ‘ task-log/plan/decisions 에 흔적 있음`));
2202
+ } else {
2203
+ log(red(` ⚠ λˆ„λ½ 후보 ${audit.missing.length}건 (open μƒνƒœμ΄λ‚˜ task/plan/decisions λ§€μΉ­ μ—†μŒ):`));
2204
+ for (const m of audit.missing) {
2205
+ log(` β€’ [${m.id}] ${m.text.slice(0, 90)}${m.text.length > 90 ? '…' : ''}`);
2206
+ log(dim(` ${m.recordedAt.slice(0, 10)} Β· hits=${m.hits}/${m.words}`));
2207
+ }
2208
+ log('');
2209
+ log(dim(` β†’ ꢌμž₯: λˆ„λ½ ν•­λͺ©λ³„ leerness task add "<μš”μ²­>" λ˜λŠ” leerness requests complete <id>`));
2210
+ }
2211
+ if (audit.tracked.length > 0) {
2212
+ log('');
2213
+ log(grn(` βœ“ tracked ${audit.tracked.length}건 (open + task/plan/decisions 맀칭됨):`));
2214
+ for (const t of audit.tracked.slice(0, 5)) {
2215
+ log(` β€’ [${t.id}] ${t.text.slice(0, 80)}${t.text.length > 80 ? '…' : ''} ${dim(`(hits=${t.hits})`)}`);
2216
+ }
2217
+ }
2218
+ if (audit.stale.length > 0) {
2219
+ log('');
2220
+ log(yel(` ⏳ stale ${audit.stale.length}건 (7일+ open):`));
2221
+ for (const s of audit.stale.slice(0, 5)) {
2222
+ const days = Math.floor((Date.now() - new Date(s.recordedAt).getTime()) / 86400000);
2223
+ log(` β€’ [${s.id}] ${days}일 ${s.text.slice(0, 70)}…`);
2224
+ }
2225
+ }
2226
+ return;
2227
+ }
2228
+
2229
+ console.error(`Unknown subcommand: ${sub}`);
2230
+ console.error(`Run: leerness requests help`);
2231
+ process.exit(1);
2232
+ }
2233
+
1945
2234
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
1946
2235
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
1947
2236
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -3976,10 +4265,19 @@ function handoff(root) {
3976
4265
  parts.push(`πŸ”Œ backend ${pb.ready}/${pb.total} (${ageLabel}${pb.expired ? '⚠' : 'βœ“'})`);
3977
4266
  }
3978
4267
  } catch {}
4268
+ // 13) 1.9.207: μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 (μ‚¬μš©μž λͺ…μ‹œ) β€” λˆ„λ½ 후보 β‰₯ 1 μ‹œ ν—€λ“œλΌμΈ λ…ΈμΆœ
4269
+ try {
4270
+ const audit = _auditUserRequests(root);
4271
+ if (audit.missing && audit.missing.length > 0) {
4272
+ parts.push(`πŸ“₯ λ―Έλ‹΅ μš”μ²­ ${audit.missing.length}건`);
4273
+ } else if (audit.open > 0) {
4274
+ parts.push(`πŸ“₯ μš”μ²­ ${audit.open} (tracked)`);
4275
+ }
4276
+ } catch {}
3979
4277
  if (parts.length) {
3980
4278
  const isTty = process.stdout && process.stdout.isTTY;
3981
4279
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3982
- log(cy(`πŸ“Š ν—€λ“œλΌμΈ (1.9.81/93/113/152/162/192/197/204): ${parts.join(' Β· ')}`));
4280
+ log(cy(`πŸ“Š ν—€λ“œλΌμΈ (1.9.81/93/113/152/162/192/197/204/207): ${parts.join(' Β· ')}`));
3983
4281
  }
3984
4282
  } catch {}
3985
4283
  }
@@ -15268,6 +15566,8 @@ async function main() {
15268
15566
  if (cmd === 'next-action') return nextActionCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15269
15567
  // 1.9.203: leerness resume β€” auto-resume-plan 읽고 λ‹€μŒ λΌμš΄λ“œ μ¦‰μ‹œ μ•ˆλ‚΄ (μ‚¬μš©μž λͺ…μ‹œ)
15270
15568
  if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
15569
+ // 1.9.207: leerness requests <audit|add|list|complete|drop> β€” μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 (μ‚¬μš©μž λͺ…μ‹œ)
15570
+ if (cmd === 'requests') return requestsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15271
15571
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
15272
15572
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
15273
15573
  if (cmd === 'rule' && args[1] === 'remove') return ruleRemove(arg('--path', process.cwd()), args[2]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.205",
3
+ "version": "1.9.207",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",