leerness 1.9.202 → 1.9.204

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,114 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.204 — 2026-05-22
4
+
5
+ **⏰ timezone 보강 + 🔄 auto-loop 활성 라벨 (사용자 명시 2종).**
6
+
7
+ ### 사용자 명시
8
+ > 1. *"시간관련 기능 등은 해당 국가의 시간대를 고려해서 작업이 될 수 있는지"*
9
+ > 2. *"자동으로 깨어나서 작업하는 모드도 활성룰에 표시되어야 할 것 같은데, 활성 룰 에 표시되는 기능 점검"*
10
+
11
+ ### 1. timezone 시스템 (Intl.DateTimeFormat 기반, 의존성 0)
12
+ - `_getLocalTz()` — `process.env.LEERNESS_TZ` → 시스템 timezone → `Asia/Seoul` fallback
13
+ - `_formatLocal(iso, opts)` — ISO UTC → 사용자 local time (예: `2026-05-22 10:13 KST`)
14
+ - 단축 라벨: KST / JST / UTC / 자동 추출
15
+ - **저장은 UTC ISO 유지** (이식성/일관성), **display 만 local time 변환**
16
+
17
+ ### 2. resume CLI local time 표시
18
+ ```
19
+ $ leerness resume
20
+ 📅 plan 저장: 2026-05-22 09:48 KST (29분 전)
21
+ ⏰ 예상 fire: 2026-05-22 10:13 KST (정시)
22
+ ```
23
+ 이전: `2026-05-22T00:48:23.298Z` (한국 사용자 +9 머릿속 변환 필요)
24
+ 이후: `2026-05-22 09:48 KST` (즉시 인지 가능)
25
+
26
+ ### 3. 자동 모드 활성 라벨 (헤드라인 12번째)
27
+ ```
28
+ 📊 헤드라인 (1.9.81/93/113/152/162/192/197/204): ... · 🔄 auto-loop 25min · ...
29
+ ```
30
+ - `_getAutoLoopRule(root)` — `R-XXXX [every-round]` 활성 룰 자동 감지
31
+ - 룰 텍스트에서 `25분` 또는 `1500초` 패턴 추출 → 분 단위 표시
32
+
33
+ ### 4. 누적 회귀 (1.9.198~203) — 모두 유지
34
+
35
+ ### 5. stress-v149 — 16/16 PASS
36
+ - timezone (3) + resume display (1) + headline label (2) + 실 검증 (1) + 성능 (2) + 누적 (7)
37
+ - 성능: --version cold start avg **471 ms** · MCP 54 도구 **438 ms**
38
+
39
+ ### 6. 자동 release 흐름
40
+ - main 자동 push **66 라운드 연속** (1.9.140~204)
41
+ - npm publish 자동 (1.9.178~)
42
+
43
+ ---
44
+
45
+ ## 1.9.203 — 2026-05-22
46
+
47
+ **📋 자동 라운드 plan 자동 정리 + `leerness resume` 신규 CLI (사용자 명시).**
48
+
49
+ ### 사용자 명시
50
+ > *"자동모드는 백그라운드에 다음 작업이 시작가능한 예상 시간을 설정해서 백그라운드에서 알람 트리거 같은걸 구현하고 일어났을때 해야하는 일을 정리해서 또 진행하고 반복으로 하는건 어떨까"*
51
+
52
+ ### 1. `.harness/auto-resume-plan.json` 자동 저장
53
+ - 라운드 마무리 (release sync-main 시 npm publish 완료 후) **자동 저장**
54
+ - plan 구조:
55
+ - `savedAt`, `nextRoundVersion`, `expectedFireAt`, `intervalMin`
56
+ - `focus` (자유 텍스트)
57
+ - `contextSnapshot` (currentVersion, activeTaskId, activeTaskRequest, memorySurface, r0001Rule)
58
+ - `nextActions` (1.9.201 next-action-queue snapshot)
59
+ - 함수: `_loadAutoResumePlan` / `_writeAutoResumePlan` / `_buildAutoResumePlan` / `_autoResumePlanPath`
60
+
61
+ ### 2. `leerness resume` 신규 CLI
62
+ 일어났을 때 즉시 무엇부터 할지 한눈에:
63
+ ```
64
+ $ leerness resume
65
+ # 🔄 leerness resume (1.9.203 자동 라운드 plan 적용)
66
+ 📅 plan 저장: 2026-05-22T00:10:10.797Z (15분 전)
67
+ ⏰ 예상 fire: 2026-05-22T00:35:10.797Z (정시)
68
+ 🎯 focus: 다음 라운드: handoff → next-action take → 사용자 명시 또는 5축 매트릭스 보강
69
+
70
+ ## 다음 라운드: next after 1.9.203
71
+ 현재 버전: 1.9.203
72
+ 활성 task: T-9999 — ...
73
+ memory: T2/D15/R1/L3
74
+ 룰: 25분 간격 (사용자 명시, R-0001)
75
+
76
+ ## 사전 정리된 next-actions (3건)
77
+ 🛡 lessons.md "X" 관련 ...
78
+ `leerness lessons --auto --path .`
79
+ ...
80
+ → 즉시 task 추가: leerness next-action take
81
+
82
+ ✓ resume 준비 완료 — 권장: leerness handoff . 또는 leerness next-action take
83
+ ```
84
+ - `--json` 옵션 (자동화/MCP)
85
+
86
+ ### 3. handoff 자동 plan 알림
87
+ handoff 진입 시 plan 존재 시 자동 노출:
88
+ ```
89
+ ## 📋 auto-resume-plan 로드 (1.9.203) — 15분 전 저장 (정시)
90
+ 🎯 focus: 다음 라운드: ...
91
+ 📦 다음 버전: next after 1.9.203
92
+ 📥 사전 정리된 actions: 3건 → leerness next-action take
93
+ → 상세: leerness resume
94
+ ```
95
+ 끄기: `LEERNESS_NO_RESUME_PLAN=1`
96
+
97
+ ### 4. 자율 모드 cycle 완성
98
+ - 라운드 마무리 → plan 자동 저장 → ScheduleWakeup 25분 → wakeup → handoff plan 자동 로드 → resume / next-action take → 즉시 진입
99
+
100
+ ### 5. 누적 회귀 (1.9.197~202) — 모두 유지
101
+
102
+ ### 6. stress-v148 — 17/17 PASS
103
+ - auto-resume-plan 5 + resume CLI 3 + handoff 통합 + 성능 2 + 누적 회귀 7
104
+ - 성능: --version cold start avg **464 ms** · MCP 54 도구 **449 ms**
105
+
106
+ ### 7. 자동 release 흐름
107
+ - main 자동 push **65 라운드 연속** (1.9.140~203)
108
+ - npm publish 자동 (1.9.178~)
109
+
110
+ ---
111
+
3
112
  ## 1.9.202 — 2026-05-22
4
113
 
5
114
  **🌐 C축 (공식 표준 스킬 자동 활용) 9.0/10 → 9.5/10 보강 — matched skill 설치 상태 표시 + leerness skill install-top.**
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.202-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v147-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-132-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-64_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/목적_매트릭스-98%2F100_(5축)-brightgreen)]() [![skill-install-top](https://img.shields.io/badge/skill-install--top_즉시-success)]() [![perf](https://img.shields.io/badge/cold_start-457ms-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.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)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.202 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.204 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🌐 skill install-top + 설치 상태 · 매트릭스 98/100 · A·D·E 10/10
17
+ timezone 보강 (KST) · 🔄 auto-loop 25min 헤드라인 라벨
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.202';
10
+ const VERSION = '1.9.204';
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 처리).
@@ -130,6 +130,52 @@ function append(p, s) { mkdirp(path.dirname(p)); fs.appendFileSync(p, s, 'utf8')
130
130
  function rel(root, p) { return path.relative(root, p).replace(/\\/g, '/') || '.'; }
131
131
  function today() { return new Date().toISOString().slice(0, 10); }
132
132
  function now() { return new Date().toISOString(); }
133
+ // 1.9.204: timezone 보강 (사용자 명시)
134
+ // ISO UTC timestamp 저장은 유지 (이식성/일관성) → display 시 사용자 local time 변환
135
+ // 환경변수: LEERNESS_TZ (default = 시스템 timezone, fallback: 'Asia/Seoul')
136
+ function _getLocalTz() {
137
+ if (process.env.LEERNESS_TZ) return process.env.LEERNESS_TZ;
138
+ try {
139
+ const sys = Intl.DateTimeFormat().resolvedOptions().timeZone;
140
+ if (sys && sys !== 'UTC') return sys;
141
+ } catch {}
142
+ return 'Asia/Seoul';
143
+ }
144
+ // ISO timestamp → local time 표시 (예: "2026-05-22 10:13 KST")
145
+ function _formatLocal(iso, opts) {
146
+ if (!iso) return '?';
147
+ opts = opts || {};
148
+ const tz = opts.tz || _getLocalTz();
149
+ try {
150
+ const d = typeof iso === 'string' ? new Date(iso) : iso;
151
+ if (isNaN(d.getTime())) return String(iso);
152
+ const fmt = new Intl.DateTimeFormat('en-CA', {
153
+ timeZone: tz,
154
+ year: 'numeric', month: '2-digit', day: '2-digit',
155
+ hour: '2-digit', minute: '2-digit',
156
+ hour12: false
157
+ });
158
+ const parts = fmt.formatToParts(d);
159
+ const get = (t) => (parts.find(p => p.type === t) || {}).value || '';
160
+ const date = `${get('year')}-${get('month')}-${get('day')}`;
161
+ const time = `${get('hour')}:${get('minute')}`;
162
+ // tz 약어 (KST/JST/UTC 등) — 간단 추출
163
+ const tzShort = tz === 'Asia/Seoul' ? 'KST' : tz === 'Asia/Tokyo' ? 'JST' : tz === 'UTC' ? 'UTC' : tz.split('/').pop().slice(0, 3);
164
+ return opts.dateOnly ? date : `${date} ${time} ${tzShort}`;
165
+ } catch { return String(iso); }
166
+ }
167
+ // 자동 모드 활성 여부 (R-XXXX every-round 룰 존재 시 true)
168
+ function _isAutoLoopActive(root) {
169
+ try {
170
+ const rules = readRules(root);
171
+ return rules.some(r => r.status === 'active' && /every-round|every-session/i.test(r.trigger || ''));
172
+ } catch { return false; }
173
+ }
174
+ function _getAutoLoopRule(root) {
175
+ try {
176
+ return readRules(root).find(r => r.status === 'active' && /every-round/i.test(r.trigger || '')) || null;
177
+ } catch { return null; }
178
+ }
133
179
  function arg(name, def = null) { const i = process.argv.indexOf(name); return i >= 0 ? (process.argv[i + 1] || true) : def; }
134
180
  function has(name) { return process.argv.includes(name); }
135
181
  function nonFlagArgs() {
@@ -1733,6 +1779,115 @@ async function skillAutoCacheCmd(root, sub) {
1733
1779
  if (c.expired) log(` → 갱신: leerness skill auto-cache refresh`);
1734
1780
  }
1735
1781
 
1782
+ // 1.9.203: 자동 라운드 plan 정리 — 사용자 명시
1783
+ // "백그라운드에 다음 작업이 시작가능한 예상 시간 + 일어났을때 해야하는 일을 정리"
1784
+ // 라운드 마무리 시 .harness/auto-resume-plan.json 자동 저장 → 다음 wakeup 시 즉시 실행 가능
1785
+ // plan 구조: { savedAt, nextRoundVersion, expectedFireAt, focus, contextSnapshot, nextActions }
1786
+ function _autoResumePlanPath(root) { return path.join(root, '.harness', 'auto-resume-plan.json'); }
1787
+ function _loadAutoResumePlan(root) {
1788
+ try {
1789
+ const fp = _autoResumePlanPath(root);
1790
+ if (!exists(fp)) return null;
1791
+ const j = JSON.parse(read(fp));
1792
+ if (!j.savedAt) return null;
1793
+ const ageMs = Date.now() - new Date(j.savedAt).getTime();
1794
+ const expiredMs = j.expectedFireAt ? Date.now() - new Date(j.expectedFireAt).getTime() : 0;
1795
+ return { ...j, ageMs, ageMin: Math.floor(ageMs / 60000), elapsedFromExpected: Math.floor(expiredMs / 60000) };
1796
+ } catch { return null; }
1797
+ }
1798
+ function _writeAutoResumePlan(root, plan) {
1799
+ try {
1800
+ mkdirp(path.join(root, '.harness'));
1801
+ const payload = { savedAt: new Date().toISOString(), ...plan };
1802
+ writeUtf8(_autoResumePlanPath(root), JSON.stringify(payload, null, 2));
1803
+ return true;
1804
+ } catch { return false; }
1805
+ }
1806
+ // 현재 워크스페이스 상태에서 다음 라운드 plan 자동 생성 (현재 task / matrix / handoff gap / next-action queue 결합)
1807
+ function _buildAutoResumePlan(root, opts) {
1808
+ opts = opts || {};
1809
+ const intervalMin = opts.intervalMin || 25; // R-0001 영구 룰
1810
+ const now = Date.now();
1811
+ const expectedFireAt = new Date(now + intervalMin * 60 * 1000).toISOString();
1812
+ // 현재 in-progress task
1813
+ const rows = readProgressRows(root);
1814
+ const active = rows.find(r => r.status === 'in-progress' || r.status === '[진행]');
1815
+ // 현재 VERSION
1816
+ const currentVersion = VERSION;
1817
+ // next-action queue snapshot
1818
+ const queueState = _loadNextActionQueue(root);
1819
+ const queueTop = queueState.queue.length > 0 ? queueState.queue[queueState.queue.length - 1] : null;
1820
+ // memory surface counts
1821
+ const memorySurface = {
1822
+ tasksInProgress: rows.filter(r => r.status === 'in-progress').length,
1823
+ decisions: exists(decisionsPath(root)) ? (read(decisionsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0,
1824
+ rulesActive: readRules(root).filter(r => r.status === 'active').length,
1825
+ lessons: exists(lessonsPath(root)) ? (read(lessonsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0
1826
+ };
1827
+ return {
1828
+ nextRoundVersion: opts.nextRoundVersion || `next after ${currentVersion}`,
1829
+ expectedFireAt,
1830
+ intervalMin,
1831
+ focus: opts.focus || (active ? `${active.id} — ${(active.request || '').slice(0, 80)}` : '사용자 명시 대기 또는 5축 매트릭스 보강'),
1832
+ contextSnapshot: {
1833
+ currentVersion,
1834
+ activeTaskId: active ? active.id : null,
1835
+ activeTaskRequest: active ? (active.request || '').slice(0, 200) : null,
1836
+ memorySurface,
1837
+ r0001Rule: '25분 간격 (사용자 명시, R-0001)'
1838
+ },
1839
+ nextActions: (queueState.queue || []).slice(-3).map(a => ({ icon: a.icon, title: a.title, command: a.command || null })),
1840
+ note: opts.note || '다음 wakeup 시 leerness resume 또는 leerness handoff 실행 → plan 자동 적용'
1841
+ };
1842
+ }
1843
+
1844
+ // 1.9.203: leerness resume 신규 CLI — auto-resume-plan 읽고 즉시 진행할 일 안내
1845
+ function resumeCmd(root) {
1846
+ root = absRoot(root);
1847
+ const plan = _loadAutoResumePlan(root);
1848
+ if (!plan) {
1849
+ log(`# leerness resume (1.9.203)`);
1850
+ log(` (auto-resume-plan 없음 — 첫 라운드)`);
1851
+ log(` → leerness handoff . 로 시작`);
1852
+ return;
1853
+ }
1854
+ if (has('--json')) { log(JSON.stringify(plan, null, 2)); return; }
1855
+ const isTty = process.stdout && process.stdout.isTTY;
1856
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
1857
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
1858
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
1859
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
1860
+ log(cyan(`# 🔄 leerness resume (1.9.203 자동 라운드 plan 적용)`));
1861
+ log('');
1862
+ log(` 📅 plan 저장: ${_formatLocal(plan.savedAt)} (${plan.ageMin}분 전)`);
1863
+ log(` ⏰ 예상 fire: ${_formatLocal(plan.expectedFireAt)}` + (plan.elapsedFromExpected > 0 ? yel(` (${plan.elapsedFromExpected}분 지연)`) : ' (정시)'));
1864
+ log(` 🎯 focus: ${plan.focus}`);
1865
+ log('');
1866
+ log(grn(`## 다음 라운드: ${plan.nextRoundVersion}`));
1867
+ if (plan.contextSnapshot) {
1868
+ const c = plan.contextSnapshot;
1869
+ log(` 현재 버전: ${c.currentVersion}`);
1870
+ if (c.activeTaskId) log(` 활성 task: ${c.activeTaskId} — ${(c.activeTaskRequest || '').slice(0, 70)}`);
1871
+ const m = c.memorySurface || {};
1872
+ log(` memory: T${m.tasksInProgress || 0}/D${m.decisions || 0}/R${m.rulesActive || 0}/L${m.lessons || 0}`);
1873
+ log(` 룰: ${c.r0001Rule || '-'}`);
1874
+ }
1875
+ if (plan.nextActions && plan.nextActions.length > 0) {
1876
+ log('');
1877
+ log(grn(`## 사전 정리된 next-actions (${plan.nextActions.length}건)`));
1878
+ for (const a of plan.nextActions) {
1879
+ log(` ${a.icon || '•'} ${a.title}`);
1880
+ if (a.command) log(dim(` \`${a.command}\``));
1881
+ }
1882
+ log('');
1883
+ log(dim(` → 즉시 task 추가: leerness next-action take`));
1884
+ }
1885
+ log('');
1886
+ log(dim(plan.note || ''));
1887
+ log('');
1888
+ log(grn(`✓ resume 준비 완료 — 권장: leerness handoff . 또는 leerness next-action take`));
1889
+ }
1890
+
1736
1891
  // 1.9.199: wakeup miss 자동 회복 강화 — last-handoff timestamp 기록 + 25min 기대 interval 정밀 측정
1737
1892
  // 사용자 명시 (1.9.198 후속): "22:01에 라운드에 자동진입되지않았어"
1738
1893
  // 1.9.196 task-log mtime 기반 detector 는 task-log 가 자주 안 업데이트되면 false positive.
@@ -3535,6 +3690,9 @@ function handoff(root) {
3535
3690
  let _priorHandoffGap = { hasLast: false };
3536
3691
  try { _priorHandoffGap = _getLastHandoffGap(root); } catch {}
3537
3692
  try { _recordLastHandoff(root); } catch {}
3693
+ // 1.9.203: auto-resume-plan 자동 로드 (사용자 명시 — 자동 모드 알람 트리거)
3694
+ let _autoResumePlan = null;
3695
+ try { _autoResumePlan = _loadAutoResumePlan(root); } catch {}
3538
3696
  // 1.9.96: --json 옵션 (구조화 출력, MCP 통합 / 외부 AI 친화)
3539
3697
  if (has('--json')) {
3540
3698
  const result = {
@@ -3738,6 +3896,23 @@ function handoff(root) {
3738
3896
  parts.push(`🌐 official ${m.total}/${m.cacheTotal} (${ageStr}${status})`);
3739
3897
  }
3740
3898
  } catch {}
3899
+ // 12) 1.9.204: 자동 모드 활성 표시 (사용자 명시) — R-0001 every-round 룰 활성 시 헤드라인 노출
3900
+ try {
3901
+ const autoRule = _getAutoLoopRule(root);
3902
+ if (autoRule) {
3903
+ // 룰 텍스트에서 "25분" 우선, 없으면 "1500초" 패턴 추출 (분 우선순위)
3904
+ const txt = autoRule.rule || '';
3905
+ const minMatch = txt.match(/(\d+)\s*분/);
3906
+ const secMatch = txt.match(/(\d+)\s*초/);
3907
+ let label = '🔄 auto-loop';
3908
+ if (minMatch) {
3909
+ label = `🔄 auto-loop ${minMatch[1]}min`;
3910
+ } else if (secMatch) {
3911
+ label = `🔄 auto-loop ${Math.round(Number(secMatch[1]) / 60)}min`;
3912
+ }
3913
+ parts.push(label);
3914
+ }
3915
+ } catch {}
3741
3916
  // 11) 1.9.197: A축 (범용 AI 하네스) 9.5→10 보강 — provider probe 60분 캐시 자동 노출
3742
3917
  // 사용자 명시: "범용 AI 하네스 ... 최고의 도구" — 매 handoff 시 11 backend ready 상태 즉시 노출
3743
3918
  try {
@@ -3750,7 +3925,7 @@ function handoff(root) {
3750
3925
  if (parts.length) {
3751
3926
  const isTty = process.stdout && process.stdout.isTTY;
3752
3927
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3753
- log(cy(`📊 헤드라인 (1.9.81/93/113/152/162/192/197): ${parts.join(' · ')}`));
3928
+ log(cy(`📊 헤드라인 (1.9.81/93/113/152/162/192/197/204): ${parts.join(' · ')}`));
3754
3929
  }
3755
3930
  } catch {}
3756
3931
  }
@@ -4049,6 +4224,26 @@ function handoff(root) {
4049
4224
  }
4050
4225
  } catch {}
4051
4226
  }
4227
+ // 1.9.203: auto-resume-plan 알림 — 라운드 마무리 시 저장된 plan 자동 노출
4228
+ // 사용자 명시: "백그라운드에 다음 작업이 시작가능한 예상 시간 + 일어났을때 해야하는 일을 정리"
4229
+ if (_autoResumePlan && !has('--quiet') && process.env.LEERNESS_NO_RESUME_PLAN !== '1') {
4230
+ try {
4231
+ const isTty = process.stdout && process.stdout.isTTY;
4232
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
4233
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
4234
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
4235
+ const elapsed = _autoResumePlan.elapsedFromExpected || 0;
4236
+ const status = elapsed > 5 ? yel(`(${elapsed}분 지연)`) : (elapsed > -5 ? '(정시)' : dim(`(${Math.abs(elapsed)}분 일찍)`));
4237
+ log(cy(`## 📋 auto-resume-plan 로드 (1.9.203) — ${_autoResumePlan.ageMin}분 전 저장 ${status}`));
4238
+ log(dim(` 🎯 focus: ${_autoResumePlan.focus || '(none)'}`));
4239
+ log(dim(` 📦 다음 버전: ${_autoResumePlan.nextRoundVersion || '(none)'}`));
4240
+ if (_autoResumePlan.nextActions && _autoResumePlan.nextActions.length > 0) {
4241
+ log(dim(` 📥 사전 정리된 actions: ${_autoResumePlan.nextActions.length}건 → leerness next-action take`));
4242
+ }
4243
+ log(dim(` → 상세: leerness resume`));
4244
+ log('');
4245
+ } catch {}
4246
+ }
4052
4247
  // 1.9.196/199: ScheduleWakeup miss detector — 자율 모드 사용자 명시 "못일어나는 경우 종종 있음"
4053
4248
  // 1.9.196: task-log mtime 기반 (60min+) — false positive 가능
4054
4249
  // 1.9.199: last-handoff.json 정밀 측정 — R-0001 룰 (25min) 대비 gap 측정
@@ -9130,6 +9325,16 @@ function _publishToNpm(root, opts = {}) {
9130
9325
  if (pubR.status === 0) {
9131
9326
  ok(`npm publish 완료: ${pkgName}@${pkgVersion}`);
9132
9327
  try { _recordRun(root, { kind: 'npm_publish', package: pkgName, version: pkgVersion, dryRun: !!opts.dryRun, ok: true }); } catch {}
9328
+ // 1.9.203: 라운드 마무리 시 다음 라운드 plan 자동 저장
9329
+ try {
9330
+ const plan = _buildAutoResumePlan(root, {
9331
+ nextRoundVersion: `next after ${pkgVersion}`,
9332
+ focus: '다음 라운드: handoff → next-action take → 사용자 명시 또는 5축 매트릭스 보강',
9333
+ intervalMin: 25,
9334
+ note: `다음 wakeup 시: 1) leerness resume . 으로 plan 확인 2) leerness handoff . 실행 3) leerness next-action take 로 즉시 task 진입`
9335
+ });
9336
+ _writeAutoResumePlan(root, plan);
9337
+ } catch {}
9133
9338
  } else {
9134
9339
  const errOut = (pubR.stderr || pubR.stdout || '').slice(-400);
9135
9340
  if (/EPUBLISHCONFLICT|already exists|cannot publish over/i.test(errOut)) {
@@ -14965,6 +15170,8 @@ async function main() {
14965
15170
  if (cmd === 'roadmap') return roadmapCmd(args[1] || process.cwd());
14966
15171
  // 1.9.201: next-action queue CLI (E축 9.5→10)
14967
15172
  if (cmd === 'next-action') return nextActionCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15173
+ // 1.9.203: leerness resume — auto-resume-plan 읽고 다음 라운드 즉시 안내 (사용자 명시)
15174
+ if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
14968
15175
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
14969
15176
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
14970
15177
  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.202",
3
+ "version": "1.9.204",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",