leerness 1.9.201 β†’ 1.9.203

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,116 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.203 β€” 2026-05-22
4
+
5
+ **πŸ“‹ μžλ™ λΌμš΄λ“œ plan μžλ™ 정리 + `leerness resume` μ‹ κ·œ CLI (μ‚¬μš©μž λͺ…μ‹œ).**
6
+
7
+ ### μ‚¬μš©μž λͺ…μ‹œ
8
+ > *"μžλ™λͺ¨λ“œλŠ” λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„μ„ μ„€μ •ν•΄μ„œ λ°±κ·ΈλΌμš΄λ“œμ—μ„œ μ•ŒλžŒ 트리거 같은걸 κ΅¬ν˜„ν•˜κ³  μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 μ •λ¦¬ν•΄μ„œ 또 μ§„ν–‰ν•˜κ³  반볡으둜 ν•˜λŠ”κ±΄ μ–΄λ–¨κΉŒ"*
9
+
10
+ ### 1. `.harness/auto-resume-plan.json` μžλ™ μ €μž₯
11
+ - λΌμš΄λ“œ 마무리 (release sync-main μ‹œ npm publish μ™„λ£Œ ν›„) **μžλ™ μ €μž₯**
12
+ - plan ꡬ쑰:
13
+ - `savedAt`, `nextRoundVersion`, `expectedFireAt`, `intervalMin`
14
+ - `focus` (자유 ν…μŠ€νŠΈ)
15
+ - `contextSnapshot` (currentVersion, activeTaskId, activeTaskRequest, memorySurface, r0001Rule)
16
+ - `nextActions` (1.9.201 next-action-queue snapshot)
17
+ - ν•¨μˆ˜: `_loadAutoResumePlan` / `_writeAutoResumePlan` / `_buildAutoResumePlan` / `_autoResumePlanPath`
18
+
19
+ ### 2. `leerness resume` μ‹ κ·œ CLI
20
+ 일어났을 λ•Œ μ¦‰μ‹œ 무엇뢀터 ν• μ§€ ν•œλˆˆμ—:
21
+ ```
22
+ $ leerness resume
23
+ # πŸ”„ leerness resume (1.9.203 μžλ™ λΌμš΄λ“œ plan 적용)
24
+ πŸ“… plan μ €μž₯: 2026-05-22T00:10:10.797Z (15λΆ„ μ „)
25
+ ⏰ μ˜ˆμƒ fire: 2026-05-22T00:35:10.797Z (μ •μ‹œ)
26
+ 🎯 focus: λ‹€μŒ λΌμš΄λ“œ: handoff β†’ next-action take β†’ μ‚¬μš©μž λͺ…μ‹œ λ˜λŠ” 5μΆ• 맀트릭슀 보강
27
+
28
+ ## λ‹€μŒ λΌμš΄λ“œ: next after 1.9.203
29
+ ν˜„μž¬ 버전: 1.9.203
30
+ ν™œμ„± task: T-9999 β€” ...
31
+ memory: T2/D15/R1/L3
32
+ λ£°: 25λΆ„ 간격 (μ‚¬μš©μž λͺ…μ‹œ, R-0001)
33
+
34
+ ## 사전 μ •λ¦¬λœ next-actions (3건)
35
+ πŸ›‘ lessons.md "X" κ΄€λ ¨ ...
36
+ `leerness lessons --auto --path .`
37
+ ...
38
+ β†’ μ¦‰μ‹œ task μΆ”κ°€: leerness next-action take
39
+
40
+ βœ“ resume μ€€λΉ„ μ™„λ£Œ β€” ꢌμž₯: leerness handoff . λ˜λŠ” leerness next-action take
41
+ ```
42
+ - `--json` μ˜΅μ…˜ (μžλ™ν™”/MCP)
43
+
44
+ ### 3. handoff μžλ™ plan μ•Œλ¦Ό
45
+ handoff μ§„μž… μ‹œ plan 쑴재 μ‹œ μžλ™ λ…ΈμΆœ:
46
+ ```
47
+ ## πŸ“‹ auto-resume-plan λ‘œλ“œ (1.9.203) β€” 15λΆ„ μ „ μ €μž₯ (μ •μ‹œ)
48
+ 🎯 focus: λ‹€μŒ λΌμš΄λ“œ: ...
49
+ πŸ“¦ λ‹€μŒ 버전: next after 1.9.203
50
+ πŸ“₯ 사전 μ •λ¦¬λœ actions: 3건 β†’ leerness next-action take
51
+ β†’ 상세: leerness resume
52
+ ```
53
+ 끄기: `LEERNESS_NO_RESUME_PLAN=1`
54
+
55
+ ### 4. 자율 λͺ¨λ“œ cycle μ™„μ„±
56
+ - λΌμš΄λ“œ 마무리 β†’ plan μžλ™ μ €μž₯ β†’ ScheduleWakeup 25λΆ„ β†’ wakeup β†’ handoff plan μžλ™ λ‘œλ“œ β†’ resume / next-action take β†’ μ¦‰μ‹œ μ§„μž…
57
+
58
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.197~202) β€” λͺ¨λ‘ μœ μ§€
59
+
60
+ ### 6. stress-v148 β€” 17/17 PASS
61
+ - auto-resume-plan 5 + resume CLI 3 + handoff 톡합 + μ„±λŠ₯ 2 + λˆ„μ  νšŒκ·€ 7
62
+ - μ„±λŠ₯: --version cold start avg **464 ms** Β· MCP 54 도ꡬ **449 ms**
63
+
64
+ ### 7. μžλ™ release 흐름
65
+ - main μžλ™ push **65 λΌμš΄λ“œ 연속** (1.9.140~203)
66
+ - npm publish μžλ™ (1.9.178~)
67
+
68
+ ---
69
+
70
+ ## 1.9.202 β€” 2026-05-22
71
+
72
+ **🌐 CμΆ• (곡식 ν‘œμ€€ μŠ€ν‚¬ μžλ™ ν™œμš©) 9.0/10 β†’ 9.5/10 보강 β€” matched skill μ„€μΉ˜ μƒνƒœ ν‘œμ‹œ + leerness skill install-top.**
73
+
74
+ 자율 λͺ¨λ“œ 132 λΌμš΄λ“œ. 100% 맀트릭슀 도전 β€” CμΆ• λ§ˆμ§€λ§‰ 0.5 보강 (97 β†’ 97.5).
75
+
76
+ ### 1. install 이λ ₯ 기둝 + μ„€μΉ˜ μƒνƒœ ν‘œμ‹œ
77
+ - `.harness/skill-installed-log.json` (λΉ„μ‹œν¬λ¦Ώ, 체크인 κ°€λŠ₯)
78
+ - ν•¨μˆ˜: `_loadSkillInstalledLog` / `_appendSkillInstalledLog` / `_isSkillInstalled`
79
+ - handoff matched entry 에 `βœ“ μ„€μΉ˜λ¨` λ˜λŠ” `πŸ†•` νƒœκ·Έ μžλ™ ν‘œμ‹œ
80
+
81
+ ### 2. `leerness skill install-top` μ‹ κ·œ CLI
82
+ - handoff μ»¨ν…μŠ€νŠΈ (in-progress task) 의 keyword 둜 official skill λ§€μΉ­
83
+ - top 1 (이미 μ„€μΉ˜λœ 것 μžλ™ skip) μ¦‰μ‹œ download + install
84
+ - μ„€μΉ˜ ν›„ μžλ™ 이λ ₯ 기둝 β†’ 쀑볡 install λ°©μ§€
85
+ - `--force`: 이미 μ„€μΉ˜λœ 것 κ°•μ œ μž¬μ„€μΉ˜
86
+
87
+ ### 3. handoff body 톡합
88
+ ```
89
+ ## 🌐 곡식 organization μŠ€ν‚¬ μžλ™ λ§€μΉ­ (1.9.192/193) β€” ν‚€μ›Œλ“œ "X"
90
+ βœ“ μ„€μΉ˜λ¨ [vercel] skill-A β€” ...
91
+ πŸ†• [anthropic] skill-B β€” ...
92
+ β†’ 1개 μ¦‰μ‹œ: leerness skill install-top (1.9.202)
93
+ β†’ 전체 μ„€μΉ˜: leerness skill auto-install --yes
94
+ ```
95
+
96
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.196~201) β€” λͺ¨λ‘ μœ μ§€
97
+
98
+ ### 5. stress-v147 β€” 15/15 PASS
99
+ - μ„€μΉ˜ 이λ ₯ (6) + μ„±λŠ₯ (2) + λˆ„μ  νšŒκ·€ (7)
100
+ - μ„±λŠ₯: --version cold start avg **457 ms** Β· MCP 54 도ꡬ **468 ms**
101
+
102
+ ### 6. 5μΆ• 맀트릭슀
103
+ | μΆ• | 1.9.201 | 1.9.202 | μƒνƒœ |
104
+ |---|---|---|---|
105
+ | C. 곡식 ν‘œμ€€ μŠ€ν‚¬ μžλ™ ν™œμš© | 9.0 | **9.5** | +0.5 (μ„€μΉ˜ μƒνƒœ + install-top) |
106
+ | **μ’…ν•©** | 48.5/50 (97%) | **49/50 (98%)** | **+1%** |
107
+
108
+ ### 7. μžλ™ release
109
+ - main μžλ™ push **64 λΌμš΄λ“œ 연속** (1.9.140~202)
110
+ - npm publish μžλ™ (1.9.178~)
111
+
112
+ ---
113
+
3
114
  ## 1.9.201 β€” 2026-05-22
4
115
 
5
116
  **🎯 EμΆ• (κ²ŒμœΌλ¦„ λ°©μ§€) 9.5/10 β†’ 10/10 보강 β€” next-action queue + leerness next-action take λͺ…λ Ή.**
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.201-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v146-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-131-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-63_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-97%2F100_(5μΆ•)-brightgreen)]() [![next-action](https://img.shields.io/badge/next--action-1step_μžλ™_task_add-success)]() [![perf](https://img.shields.io/badge/cold_start-436ms-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.203-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v148-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-133-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-65_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![auto-resume](https://img.shields.io/badge/auto--resume--plan-λΌμš΄λ“œ_마무리_μžλ™-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)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  β•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β•‘
13
13
  β•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•‘
14
14
  β•‘ β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• β•‘
15
- β•‘ v1.9.201 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.203 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ 🎯 next-action 1step Β· 맀트릭슀 97/100 Β· AΒ·DΒ·E 10/10 β•‘
17
+ β•‘ πŸ“‹ auto-resume-plan + leerness resume Β· 자율 cycle μ™„μ„± β•‘
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.201';
10
+ const VERSION = '1.9.203';
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 처리).
@@ -1556,6 +1556,77 @@ async function skillDiscoverCmd(root) {
1556
1556
  log(`πŸ’‘ μ„€μΉ˜: leerness skill install <url>`);
1557
1557
  }
1558
1558
 
1559
+ // 1.9.202: CμΆ• (곡식 ν‘œμ€€ μŠ€ν‚¬ μžλ™ ν™œμš©) 9.0β†’9.5 보강 β€” install 이λ ₯ 기둝 + μ„€μΉ˜ μƒνƒœ ν‘œμ‹œ
1560
+ // 1.9.192 official catalog λ§€μΉ­ μ‹œ "이미 μ„€μΉ˜λ¨" / "μ‹ κ·œ" ꡬ뢄 β†’ 쀑볡 install λ°©μ§€ + μ‚¬μš©μž 인지 도움
1561
+ // 파일: .harness/skill-installed-log.json (λΉ„μ‹œν¬λ¦Ώ)
1562
+ function _skillInstalledLogPath(root) { return path.join(root, '.harness', 'skill-installed-log.json'); }
1563
+ function _loadSkillInstalledLog(root) {
1564
+ try {
1565
+ const fp = _skillInstalledLogPath(root);
1566
+ if (!exists(fp)) return { installed: [] };
1567
+ const j = JSON.parse(read(fp));
1568
+ return { installed: Array.isArray(j.installed) ? j.installed : [] };
1569
+ } catch { return { installed: [] }; }
1570
+ }
1571
+ function _appendSkillInstalledLog(root, entry) {
1572
+ try {
1573
+ const state = _loadSkillInstalledLog(root);
1574
+ const e = { name: entry.name, source: entry.source || entry.preset || '?', url: entry.url || '', at: new Date().toISOString() };
1575
+ // 쀑볡 (같은 name + source) 제거 ν›„ append
1576
+ state.installed = state.installed.filter(x => !(x.name === e.name && x.source === e.source));
1577
+ state.installed.push(e);
1578
+ mkdirp(path.join(root, '.harness'));
1579
+ writeUtf8(_skillInstalledLogPath(root), JSON.stringify({ installed: state.installed }, null, 2));
1580
+ return true;
1581
+ } catch { return false; }
1582
+ }
1583
+ function _isSkillInstalled(root, entry) {
1584
+ try {
1585
+ const state = _loadSkillInstalledLog(root);
1586
+ const src = entry.source || entry.preset || '?';
1587
+ return state.installed.some(x => x.name === entry.name && x.source === src);
1588
+ } catch { return false; }
1589
+ }
1590
+
1591
+ // 1.9.202: leerness skill install-top β€” handoff matched 의 top 1 entry λ₯Ό μ¦‰μ‹œ install
1592
+ async function skillInstallTopCmd(root) {
1593
+ root = absRoot(root);
1594
+ // handoff context 의 keyword μΆ”μΆœ
1595
+ const rows = readProgressRows(root);
1596
+ const active = rows.find(r => r.status === 'in-progress' || r.status === '[μ§„ν–‰]') || rows[rows.length - 1];
1597
+ if (!active || !active.request) { fail('in-progress task μ—†μŒ β€” leerness handoff . λ¨Όμ €'); return process.exit(1); }
1598
+ const m = _matchOfficialSkillsFromCache(root, active.request, 3);
1599
+ if (m.entries.length === 0) {
1600
+ fail(`맀칭된 official skill μ—†μŒ (keyword: "${(active.request || '').slice(0, 40)}", cache total: ${m.cacheTotal || 0})`);
1601
+ return process.exit(1);
1602
+ }
1603
+ // 이미 μ„€μΉ˜λœ 것 skip
1604
+ const candidates = m.entries.filter(e => !_isSkillInstalled(root, e));
1605
+ if (candidates.length === 0) {
1606
+ log(`βœ“ λͺ¨λ“  맀칭된 skill 이미 μ„€μΉ˜λ¨ (1.9.202 log κΈ°μ€€):`);
1607
+ for (const e of m.entries) log(` β€’ ${e.name} (${e.preset || e.source}) β€” 이미 μ„€μΉ˜`);
1608
+ log(` β†’ leerness skill install-top --force 둜 μž¬μ„€μΉ˜`);
1609
+ return;
1610
+ }
1611
+ const top = has('--force') ? m.entries[0] : candidates[0];
1612
+ log(`# leerness skill install-top (1.9.202 CμΆ• 보강)`);
1613
+ log(` task: ${(active.request || '').slice(0, 80)}`);
1614
+ log(` λ§€μΉ­: ${m.total}/${m.cacheTotal}건 Β· top 1 β†’ ${top.name} (${top.preset || top.source})`);
1615
+ log(` URL: ${top.url || '(none)'}`);
1616
+ log('');
1617
+ if (!top.url) { fail(`top entry url λΆ€μž¬ β€” install λΆˆκ°€`); return process.exit(1); }
1618
+ try {
1619
+ log(`πŸ“₯ install μ‹œμž‘...`);
1620
+ await skillInstallCmd(root, top.url);
1621
+ _appendSkillInstalledLog(root, top);
1622
+ log('');
1623
+ log(`βœ“ μ„€μΉ˜ + 이λ ₯ 기둝 μ™„λ£Œ (.harness/skill-installed-log.json)`);
1624
+ } catch (e) {
1625
+ fail(`install μ‹€νŒ¨: ${e.message}`);
1626
+ return process.exit(1);
1627
+ }
1628
+ }
1629
+
1559
1630
  // 1.9.182: skill auto-install β€” 곡식 organization catalog μžλ™ 탐색 β†’ skill match β†’ μžλ™ install.
1560
1631
  // μ‚¬μš©μž λͺ…μ‹œ: "μŠ€ν‚¬μ„ μ‚¬μš©ν•˜κ±°λ‚˜ μ μš©ν•΄λ‘λ©΄ 쒋을 뢀뢄듀을 μ•Œμ•„μ„œ μ›Ήμ—μ„œ 곡식 쑰직의 μŠ€ν‚¬ λͺ¨μŒμ„ νƒμƒ‰ν•΄μ„œ λ‹€μš΄λ‘œλ“œ λ°›μ•„μ„œ μ‚¬μš©"
1561
1632
  // μ •μ±…: LEERNESS_SKILL_AUTO_INSTALL=1 일 λ•Œλ§Œ μ‹€μ œ install (λ³΄μ•ˆ opt-in). λ―Έμ„€μ • μ‹œ dry-run (μΆ”μ²œλ§Œ).
@@ -1662,6 +1733,115 @@ async function skillAutoCacheCmd(root, sub) {
1662
1733
  if (c.expired) log(` β†’ κ°±μ‹ : leerness skill auto-cache refresh`);
1663
1734
  }
1664
1735
 
1736
+ // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
1737
+ // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
1738
+ // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
1739
+ // plan ꡬ쑰: { savedAt, nextRoundVersion, expectedFireAt, focus, contextSnapshot, nextActions }
1740
+ function _autoResumePlanPath(root) { return path.join(root, '.harness', 'auto-resume-plan.json'); }
1741
+ function _loadAutoResumePlan(root) {
1742
+ try {
1743
+ const fp = _autoResumePlanPath(root);
1744
+ if (!exists(fp)) return null;
1745
+ const j = JSON.parse(read(fp));
1746
+ if (!j.savedAt) return null;
1747
+ const ageMs = Date.now() - new Date(j.savedAt).getTime();
1748
+ const expiredMs = j.expectedFireAt ? Date.now() - new Date(j.expectedFireAt).getTime() : 0;
1749
+ return { ...j, ageMs, ageMin: Math.floor(ageMs / 60000), elapsedFromExpected: Math.floor(expiredMs / 60000) };
1750
+ } catch { return null; }
1751
+ }
1752
+ function _writeAutoResumePlan(root, plan) {
1753
+ try {
1754
+ mkdirp(path.join(root, '.harness'));
1755
+ const payload = { savedAt: new Date().toISOString(), ...plan };
1756
+ writeUtf8(_autoResumePlanPath(root), JSON.stringify(payload, null, 2));
1757
+ return true;
1758
+ } catch { return false; }
1759
+ }
1760
+ // ν˜„μž¬ μ›Œν¬μŠ€νŽ˜μ΄μŠ€ μƒνƒœμ—μ„œ λ‹€μŒ λΌμš΄λ“œ plan μžλ™ 생성 (ν˜„μž¬ task / matrix / handoff gap / next-action queue κ²°ν•©)
1761
+ function _buildAutoResumePlan(root, opts) {
1762
+ opts = opts || {};
1763
+ const intervalMin = opts.intervalMin || 25; // R-0001 영ꡬ 룰
1764
+ const now = Date.now();
1765
+ const expectedFireAt = new Date(now + intervalMin * 60 * 1000).toISOString();
1766
+ // ν˜„μž¬ in-progress task
1767
+ const rows = readProgressRows(root);
1768
+ const active = rows.find(r => r.status === 'in-progress' || r.status === '[μ§„ν–‰]');
1769
+ // ν˜„μž¬ VERSION
1770
+ const currentVersion = VERSION;
1771
+ // next-action queue snapshot
1772
+ const queueState = _loadNextActionQueue(root);
1773
+ const queueTop = queueState.queue.length > 0 ? queueState.queue[queueState.queue.length - 1] : null;
1774
+ // memory surface counts
1775
+ const memorySurface = {
1776
+ tasksInProgress: rows.filter(r => r.status === 'in-progress').length,
1777
+ decisions: exists(decisionsPath(root)) ? (read(decisionsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0,
1778
+ rulesActive: readRules(root).filter(r => r.status === 'active').length,
1779
+ lessons: exists(lessonsPath(root)) ? (read(lessonsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0
1780
+ };
1781
+ return {
1782
+ nextRoundVersion: opts.nextRoundVersion || `next after ${currentVersion}`,
1783
+ expectedFireAt,
1784
+ intervalMin,
1785
+ focus: opts.focus || (active ? `${active.id} β€” ${(active.request || '').slice(0, 80)}` : 'μ‚¬μš©μž λͺ…μ‹œ λŒ€κΈ° λ˜λŠ” 5μΆ• 맀트릭슀 보강'),
1786
+ contextSnapshot: {
1787
+ currentVersion,
1788
+ activeTaskId: active ? active.id : null,
1789
+ activeTaskRequest: active ? (active.request || '').slice(0, 200) : null,
1790
+ memorySurface,
1791
+ r0001Rule: '25λΆ„ 간격 (μ‚¬μš©μž λͺ…μ‹œ, R-0001)'
1792
+ },
1793
+ nextActions: (queueState.queue || []).slice(-3).map(a => ({ icon: a.icon, title: a.title, command: a.command || null })),
1794
+ note: opts.note || 'λ‹€μŒ wakeup μ‹œ leerness resume λ˜λŠ” leerness handoff μ‹€ν–‰ β†’ plan μžλ™ 적용'
1795
+ };
1796
+ }
1797
+
1798
+ // 1.9.203: leerness resume μ‹ κ·œ CLI β€” auto-resume-plan 읽고 μ¦‰μ‹œ μ§„ν–‰ν•  일 μ•ˆλ‚΄
1799
+ function resumeCmd(root) {
1800
+ root = absRoot(root);
1801
+ const plan = _loadAutoResumePlan(root);
1802
+ if (!plan) {
1803
+ log(`# leerness resume (1.9.203)`);
1804
+ log(` (auto-resume-plan μ—†μŒ β€” 첫 λΌμš΄λ“œ)`);
1805
+ log(` β†’ leerness handoff . 둜 μ‹œμž‘`);
1806
+ return;
1807
+ }
1808
+ if (has('--json')) { log(JSON.stringify(plan, null, 2)); return; }
1809
+ const isTty = process.stdout && process.stdout.isTTY;
1810
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
1811
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
1812
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
1813
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
1814
+ log(cyan(`# πŸ”„ leerness resume (1.9.203 μžλ™ λΌμš΄λ“œ plan 적용)`));
1815
+ log('');
1816
+ log(` πŸ“… plan μ €μž₯: ${plan.savedAt} (${plan.ageMin}λΆ„ μ „)`);
1817
+ log(` ⏰ μ˜ˆμƒ fire: ${plan.expectedFireAt}` + (plan.elapsedFromExpected > 0 ? yel(` (${plan.elapsedFromExpected}λΆ„ μ§€μ—°)`) : ' (μ •μ‹œ)'));
1818
+ log(` 🎯 focus: ${plan.focus}`);
1819
+ log('');
1820
+ log(grn(`## λ‹€μŒ λΌμš΄λ“œ: ${plan.nextRoundVersion}`));
1821
+ if (plan.contextSnapshot) {
1822
+ const c = plan.contextSnapshot;
1823
+ log(` ν˜„μž¬ 버전: ${c.currentVersion}`);
1824
+ if (c.activeTaskId) log(` ν™œμ„± task: ${c.activeTaskId} β€” ${(c.activeTaskRequest || '').slice(0, 70)}`);
1825
+ const m = c.memorySurface || {};
1826
+ log(` memory: T${m.tasksInProgress || 0}/D${m.decisions || 0}/R${m.rulesActive || 0}/L${m.lessons || 0}`);
1827
+ log(` λ£°: ${c.r0001Rule || '-'}`);
1828
+ }
1829
+ if (plan.nextActions && plan.nextActions.length > 0) {
1830
+ log('');
1831
+ log(grn(`## 사전 μ •λ¦¬λœ next-actions (${plan.nextActions.length}건)`));
1832
+ for (const a of plan.nextActions) {
1833
+ log(` ${a.icon || 'β€’'} ${a.title}`);
1834
+ if (a.command) log(dim(` \`${a.command}\``));
1835
+ }
1836
+ log('');
1837
+ log(dim(` β†’ μ¦‰μ‹œ task μΆ”κ°€: leerness next-action take`));
1838
+ }
1839
+ log('');
1840
+ log(dim(plan.note || ''));
1841
+ log('');
1842
+ log(grn(`βœ“ resume μ€€λΉ„ μ™„λ£Œ β€” ꢌμž₯: leerness handoff . λ˜λŠ” leerness next-action take`));
1843
+ }
1844
+
1665
1845
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
1666
1846
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
1667
1847
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -3464,6 +3644,9 @@ function handoff(root) {
3464
3644
  let _priorHandoffGap = { hasLast: false };
3465
3645
  try { _priorHandoffGap = _getLastHandoffGap(root); } catch {}
3466
3646
  try { _recordLastHandoff(root); } catch {}
3647
+ // 1.9.203: auto-resume-plan μžλ™ λ‘œλ“œ (μ‚¬μš©μž λͺ…μ‹œ β€” μžλ™ λͺ¨λ“œ μ•ŒλžŒ 트리거)
3648
+ let _autoResumePlan = null;
3649
+ try { _autoResumePlan = _loadAutoResumePlan(root); } catch {}
3467
3650
  // 1.9.96: --json μ˜΅μ…˜ (ꡬ쑰화 좜λ ₯, MCP 톡합 / μ™ΈλΆ€ AI μΉœν™”)
3468
3651
  if (has('--json')) {
3469
3652
  const result = {
@@ -3835,10 +4018,14 @@ function handoff(root) {
3835
4018
  log(cyan(`## 🌐 곡식 organization μŠ€ν‚¬ μžλ™ λ§€μΉ­ (1.9.192/193) β€” ν‚€μ›Œλ“œ "${keyword}"`));
3836
4019
  const ageLabel = veryStale ? yel(`${cacheAge} ⚠ 7일+`) : (ofm.expired ? yel(`${cacheAge} ⚠ 만료`) : `${cacheAge} βœ“`);
3837
4020
  log(dim(` vercel-labs/anthropics λ“± catalog μΊμ‹œ ${ageLabel} Β· ${ofm.total}/${ofm.cacheTotal}건 λ§€μΉ­`));
4021
+ // 1.9.202: 각 entry 의 μ„€μΉ˜ μƒνƒœ ν‘œμ‹œ (installed/new)
3838
4022
  for (const e of ofm.entries) {
3839
- log(dim(` β€’ [${e.preset || e.source || '?'}] ${e.name} β€” ${(e.description || '').slice(0, 70)}`));
4023
+ const installed = _isSkillInstalled(root, e);
4024
+ const tag = installed ? 'βœ“ μ„€μΉ˜λ¨' : 'πŸ†•';
4025
+ log(dim(` ${tag} [${e.preset || e.source || '?'}] ${e.name} β€” ${(e.description || '').slice(0, 65)}`));
3840
4026
  }
3841
- log(dim(` β†’ μ„€μΉ˜: leerness skill auto-install --yes`));
4027
+ log(dim(` β†’ 1개 μ¦‰μ‹œ: leerness skill install-top (1.9.202)`));
4028
+ log(dim(` β†’ 전체 μ„€μΉ˜: leerness skill auto-install --yes`));
3842
4029
  if (ofm.expired || veryStale) log(yel(` β†’ κ°±μ‹  ꢌμž₯: leerness skill auto-cache refresh (1.9.193 ${veryStale ? '7일+ stale' : '24h+ expired'})`));
3843
4030
  log('');
3844
4031
  } else if (ofm.cacheTotal === 0) {
@@ -3974,6 +4161,26 @@ function handoff(root) {
3974
4161
  }
3975
4162
  } catch {}
3976
4163
  }
4164
+ // 1.9.203: auto-resume-plan μ•Œλ¦Ό β€” λΌμš΄λ“œ 마무리 μ‹œ μ €μž₯된 plan μžλ™ λ…ΈμΆœ
4165
+ // μ‚¬μš©μž λͺ…μ‹œ: "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
4166
+ if (_autoResumePlan && !has('--quiet') && process.env.LEERNESS_NO_RESUME_PLAN !== '1') {
4167
+ try {
4168
+ const isTty = process.stdout && process.stdout.isTTY;
4169
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
4170
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
4171
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
4172
+ const elapsed = _autoResumePlan.elapsedFromExpected || 0;
4173
+ const status = elapsed > 5 ? yel(`(${elapsed}λΆ„ μ§€μ—°)`) : (elapsed > -5 ? '(μ •μ‹œ)' : dim(`(${Math.abs(elapsed)}λΆ„ 일찍)`));
4174
+ log(cy(`## πŸ“‹ auto-resume-plan λ‘œλ“œ (1.9.203) β€” ${_autoResumePlan.ageMin}λΆ„ μ „ μ €μž₯ ${status}`));
4175
+ log(dim(` 🎯 focus: ${_autoResumePlan.focus || '(none)'}`));
4176
+ log(dim(` πŸ“¦ λ‹€μŒ 버전: ${_autoResumePlan.nextRoundVersion || '(none)'}`));
4177
+ if (_autoResumePlan.nextActions && _autoResumePlan.nextActions.length > 0) {
4178
+ log(dim(` πŸ“₯ 사전 μ •λ¦¬λœ actions: ${_autoResumePlan.nextActions.length}건 β†’ leerness next-action take`));
4179
+ }
4180
+ log(dim(` β†’ 상세: leerness resume`));
4181
+ log('');
4182
+ } catch {}
4183
+ }
3977
4184
  // 1.9.196/199: ScheduleWakeup miss detector β€” 자율 λͺ¨λ“œ μ‚¬μš©μž λͺ…μ‹œ "λͺ»μΌμ–΄λ‚˜λŠ” 경우 μ’…μ’… 있음"
3978
4185
  // 1.9.196: task-log mtime 기반 (60min+) β€” false positive κ°€λŠ₯
3979
4186
  // 1.9.199: last-handoff.json μ •λ°€ μΈ‘μ • β€” R-0001 λ£° (25min) λŒ€λΉ„ gap μΈ‘μ •
@@ -9055,6 +9262,16 @@ function _publishToNpm(root, opts = {}) {
9055
9262
  if (pubR.status === 0) {
9056
9263
  ok(`npm publish μ™„λ£Œ: ${pkgName}@${pkgVersion}`);
9057
9264
  try { _recordRun(root, { kind: 'npm_publish', package: pkgName, version: pkgVersion, dryRun: !!opts.dryRun, ok: true }); } catch {}
9265
+ // 1.9.203: λΌμš΄λ“œ 마무리 μ‹œ λ‹€μŒ λΌμš΄λ“œ plan μžλ™ μ €μž₯
9266
+ try {
9267
+ const plan = _buildAutoResumePlan(root, {
9268
+ nextRoundVersion: `next after ${pkgVersion}`,
9269
+ focus: 'λ‹€μŒ λΌμš΄λ“œ: handoff β†’ next-action take β†’ μ‚¬μš©μž λͺ…μ‹œ λ˜λŠ” 5μΆ• 맀트릭슀 보강',
9270
+ intervalMin: 25,
9271
+ note: `λ‹€μŒ wakeup μ‹œ: 1) leerness resume . 으둜 plan 확인 2) leerness handoff . μ‹€ν–‰ 3) leerness next-action take 둜 μ¦‰μ‹œ task μ§„μž…`
9272
+ });
9273
+ _writeAutoResumePlan(root, plan);
9274
+ } catch {}
9058
9275
  } else {
9059
9276
  const errOut = (pubR.stderr || pubR.stdout || '').slice(-400);
9060
9277
  if (/EPUBLISHCONFLICT|already exists|cannot publish over/i.test(errOut)) {
@@ -14875,6 +15092,8 @@ async function main() {
14875
15092
  if (cmd === 'skill' && args[1] === 'suggest') return skillSuggestCmd(absRoot(arg('--path', process.cwd())));
14876
15093
  // 1.9.182: leerness skill auto-install β€” 곡식 catalog μžλ™ 탐색 + skill match λ§€μΉ­ μ‹œ μžλ™ install (μ‚¬μš©μž λͺ…μ‹œ)
14877
15094
  if (cmd === 'skill' && args[1] === 'auto-install') return await skillAutoInstallCmd(absRoot(arg('--path', process.cwd())));
15095
+ // 1.9.202: leerness skill install-top β€” handoff matched top 1 μ¦‰μ‹œ install (CμΆ• 9β†’9.5 보강)
15096
+ if (cmd === 'skill' && args[1] === 'install-top') return await skillInstallTopCmd(absRoot(arg('--path', process.cwd())));
14878
15097
  // 1.9.192: skill auto-cache β€” 곡식 organization catalog 24h μΊμ‹œ 관리 (CμΆ• 보강)
14879
15098
  if (cmd === 'skill' && args[1] === 'auto-cache') return await skillAutoCacheCmd(absRoot(arg('--path', process.cwd())), args[2] || 'status');
14880
15099
  if (cmd === 'mcp' && args[1] === 'serve') return mcpServeCmd(absRoot(arg('--path', process.cwd())));
@@ -14888,6 +15107,8 @@ async function main() {
14888
15107
  if (cmd === 'roadmap') return roadmapCmd(args[1] || process.cwd());
14889
15108
  // 1.9.201: next-action queue CLI (EμΆ• 9.5β†’10)
14890
15109
  if (cmd === 'next-action') return nextActionCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15110
+ // 1.9.203: leerness resume β€” auto-resume-plan 읽고 λ‹€μŒ λΌμš΄λ“œ μ¦‰μ‹œ μ•ˆλ‚΄ (μ‚¬μš©μž λͺ…μ‹œ)
15111
+ if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
14891
15112
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
14892
15113
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
14893
15114
  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.201",
3
+ "version": "1.9.203",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",