leerness 1.9.206 β†’ 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,50 @@
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
+
3
48
  ## 1.9.206 β€” 2026-05-22
4
49
 
5
50
  **🌐 i18n MVP + UI/UX 헬퍼 (μ‚¬μš©μž λͺ…μ‹œ 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.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)]()
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.206 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.207 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ 🌐 i18n ko/en MVP Β· _typewrite/_ui UI/UX 헬퍼 (μ‚¬μš©μž λͺ…μ‹œ) β•‘
17
+ β•‘ πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 (requests audit) (μ‚¬μš©μž λͺ…μ‹œ) β•‘
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.206';
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 처리).
@@ -1918,6 +1918,94 @@ function _analyzeWakeupStatus(root) {
1918
1918
  return { hasWakeup: true, latest, deltaMin, kind, pending: pending.length };
1919
1919
  }
1920
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
+
1921
2009
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
1922
2010
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
1923
2011
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -2027,6 +2115,122 @@ function resumeCmd(root) {
2027
2115
  log(grn(`βœ“ resume μ€€λΉ„ μ™„λ£Œ β€” ꢌμž₯: leerness handoff . λ˜λŠ” leerness next-action take`));
2028
2116
  }
2029
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
+
2030
2234
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
2031
2235
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
2032
2236
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -4061,10 +4265,19 @@ function handoff(root) {
4061
4265
  parts.push(`πŸ”Œ backend ${pb.ready}/${pb.total} (${ageLabel}${pb.expired ? '⚠' : 'βœ“'})`);
4062
4266
  }
4063
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 {}
4064
4277
  if (parts.length) {
4065
4278
  const isTty = process.stdout && process.stdout.isTTY;
4066
4279
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
4067
- 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(' Β· ')}`));
4068
4281
  }
4069
4282
  } catch {}
4070
4283
  }
@@ -15353,6 +15566,8 @@ async function main() {
15353
15566
  if (cmd === 'next-action') return nextActionCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15354
15567
  // 1.9.203: leerness resume β€” auto-resume-plan 읽고 λ‹€μŒ λΌμš΄λ“œ μ¦‰μ‹œ μ•ˆλ‚΄ (μ‚¬μš©μž λͺ…μ‹œ)
15355
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));
15356
15571
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
15357
15572
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
15358
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.206",
3
+ "version": "1.9.207",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",