leerness 1.9.197 → 1.9.199

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,99 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.199 — 2026-05-21
4
+
5
+ **⏰ wakeup miss 자동 회복 강화 — last-handoff timestamp 정밀 측정 + R-0001 룰 (25분) 대비 강한 알림.**
6
+
7
+ 자율 모드 129 라운드. 사용자 보고 "22:01에 라운드에 자동진입되지않았어" 대응.
8
+
9
+ ### 1. last-handoff timestamp 기록 (`.harness/last-handoff.json`)
10
+ - handoff 진입 시 자동 기록 — `last` + `history` (최근 10개)
11
+ - 함수: `_recordLastHandoff(root)` / `_getLastHandoffGap(root)` / `_lastHandoffPath(root)`
12
+ - 기록 순서: 1) 이전 timestamp 보존 → 2) 현재 timestamp 기록 → 3) 보존된 값으로 gap 측정
13
+
14
+ ### 2. 강한 의심 알림 (1.9.196 task-log 기반 → 1.9.199 정밀)
15
+ **R-0001 영구 룰 (25분) 대비:**
16
+ - gap > 60분 → 🔴 강한 의심 ("시스템 sleep / wakeup 누락 확실")
17
+ - gap 35~60분 → ⚠ 의심 ("buffer 초과, 한 cycle 누락 가능성")
18
+ - gap ≤ 30분 + history ≥ 2 → ✓ 정상
19
+
20
+ ```
21
+ ## ⏰ ScheduleWakeup miss 강한 의심 (1.9.199) — 이전 handoff 90분 전
22
+ R-0001 영구 룰 (25분) 대비 3× 초과 — 시스템 sleep / wakeup 누락 확실
23
+ → 회복: 사용자가 "다음 라운드" 입력 또는 leerness rule list 로 룰 확인
24
+ → handoff 이력: 09:30:21 → 10:55:14 → 12:25:33
25
+ ```
26
+
27
+ ### 3. 누적 회귀 (1.9.193~198) — 모두 유지
28
+
29
+ ### 4. stress-v144 — 16/16 PASS
30
+ - last-handoff 측정 (5) + 임시 워크스페이스 통합 (2) + 성능 (2) + 누적 회귀 (7)
31
+ - 성능: --version cold start avg **1065 ms** · MCP 54 도구 **717 ms**
32
+
33
+ ### 5. 다층 redundancy (사용자 보고 대응)
34
+ 세션 내 활성 cron jobs:
35
+ - `3f617fbc` — Every 25 minutes (R-0001 sync)
36
+ - `33a4ed9f` — 3,28,53 * * * * (offset backup)
37
+ - ScheduleWakeup 1500s primary
38
+
39
+ ### 6. 자동 release 흐름
40
+ - main 자동 push **61 라운드 연속** (1.9.140~199)
41
+ - npm publish 자동 (1.9.178~)
42
+
43
+ ---
44
+
45
+ ## 1.9.198 — 2026-05-21
46
+
47
+ **🤖 B축 9/10 → 9.5/10 — handoff multi-agent consensus 이력 기반 best agent 자동 추천.**
48
+
49
+ 자율 모드 128 라운드. 1.9.193 consensus 자동 lessons.md 저장 (B축) 후속 — 저장한 데이터를 **handoff 에서 즉시 재활용**.
50
+
51
+ ### 사용자 의도 정렬 (1.9.191 verbatim)
52
+ > *"멀티 서브 에이전트 오케스트라 ... 최고의 도구"*
53
+
54
+ ### 1. handoff 신규 섹션 (B축 핵심)
55
+ ```
56
+ ## 🤖 task 매칭 best agent (1.9.198 B축) — 키워드 "authentication"
57
+ 과거 multi-agent consensus 2건 — 추천 agent: claude (1회 best)
58
+ 최근 hit: 2026-05-20 agent=claude score=0.873
59
+ → 재실행: leerness agents multi "<task>" --execute
60
+ ```
61
+ - lessons.md 의 1.9.193 `multi-agent consensus — best=X (1.9.193)` 패턴 자동 추출
62
+ - keyword fuzzy 매칭으로 관련 hits 선별
63
+ - agent 별 빈도 카운트 → 최다 best agent 자동 노출
64
+ - 끄기: `--no-multiagent-hint` 또는 `LEERNESS_NO_MULTIAGENT_HINT=1`
65
+
66
+ ### 2. 신규 함수
67
+ - `_loadMultiAgentConsensusHistory(root, keyword)` — lessons.md 1.9.193 패턴 추출 + fuzzy 매칭
68
+
69
+ ### 3. 누적 회귀 (1.9.192~197) — 모두 유지
70
+ - [1.9.197] _readyBackendCountFromCache (60분 캐시 + 헤드라인)
71
+ - [1.9.196] D축 7일+/30일+ recall + ScheduleWakeup miss detector
72
+ - [1.9.195] _probeProviderEndpoints (provider universal probe)
73
+ - [1.9.194] _suggestNextActions (handoff next-actions)
74
+ - [1.9.193] agents multi consensus → lessons.md
75
+ - [1.9.192] _loadOfficialSkillCache + 24h 캐시
76
+
77
+ ### 4. stress-v143 — 14/14 PASS
78
+ - B축 보강 (5: 함수 + 섹션 + 임시 워크스페이스 통합 검증 + 빈도 카운트)
79
+ - 성능: --version cold start avg **500 ms** · MCP 54 도구 **494 ms**
80
+
81
+ ### 5. 5축 매트릭스 변동
82
+ | 축 | 1.9.197 | 1.9.198 | Δ |
83
+ |---|---|---|---|
84
+ | B. 멀티 Sub-Agent 오케스트라 | 9/10 | **9.5/10** | +0.5 (consensus 이력 자동 회수) |
85
+ | 종합 | 47.5/50 (95%) | **48/50 (96%)** | +1% |
86
+
87
+ ### 6. 사용자 명시 조치
88
+ - Cron `10dcaa07` (hourly :17 backup) 사용자 요청으로 삭제
89
+ - 자동 루프는 **ScheduleWakeup 1500s (25분, R-0001 영구 룰)** 단독 운영
90
+
91
+ ### 7. 자동 release 흐름
92
+ - main 자동 push **60 라운드 연속** (1.9.140~198)
93
+ - npm publish 자동 (1.9.178~)
94
+
95
+ ---
96
+
3
97
  ## 1.9.197 — 2026-05-21
4
98
 
5
99
  **🔌 A축 9.5/10 → 10/10 — provider probe handoff 자동 통합 + 60분 캐시. 5축 매트릭스 47.5/50 (95%).**
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.197-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v142-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-127-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-59_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/목적_매트릭스-95%2F100_(5축)-brightgreen)]() [![backend-probe](https://img.shields.io/badge/handoff-13_backend_자동-success)]() [![perf](https://img.shields.io/badge/cold_start-506ms-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.199-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v144-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-129-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-61_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/목적_매트릭스-96%2F100_(5축)-brightgreen)]() [![wakeup-miss](https://img.shields.io/badge/wakeup_miss-정밀_측정_(R--0001)-success)]() [![perf](https://img.shields.io/badge/cold_start-1065ms-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.197 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.199 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🔌 5축 매트릭스 95/100 · backend probe handoff 자동 · 127 라운드
17
+ wakeup miss 정밀 측정 (R-0001) · 96/100 · 129 라운드
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.197';
10
+ const VERSION = '1.9.199';
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 처리).
@@ -1662,6 +1662,77 @@ async function skillAutoCacheCmd(root, sub) {
1662
1662
  if (c.expired) log(` → 갱신: leerness skill auto-cache refresh`);
1663
1663
  }
1664
1664
 
1665
+ // 1.9.199: wakeup miss 자동 회복 강화 — last-handoff timestamp 기록 + 25min 기대 interval 정밀 측정
1666
+ // 사용자 명시 (1.9.198 후속): "22:01에 라운드에 자동진입되지않았어"
1667
+ // 1.9.196 task-log mtime 기반 detector 는 task-log 가 자주 안 업데이트되면 false positive.
1668
+ // last-handoff.json 추가로 정확도 ↑ + R-0001 룰 (25min) 대비 actual gap 측정.
1669
+ function _lastHandoffPath(root) { return path.join(root, '.harness', 'last-handoff.json'); }
1670
+ function _recordLastHandoff(root) {
1671
+ try {
1672
+ mkdirp(path.join(root, '.harness'));
1673
+ const fp = _lastHandoffPath(root);
1674
+ let history = [];
1675
+ if (exists(fp)) {
1676
+ try { const j = JSON.parse(read(fp)); if (Array.isArray(j.history)) history = j.history; } catch {}
1677
+ }
1678
+ const now = new Date().toISOString();
1679
+ history.push(now);
1680
+ // 최근 10개만 유지
1681
+ if (history.length > 10) history = history.slice(-10);
1682
+ writeUtf8(fp, JSON.stringify({ last: now, history }, null, 2));
1683
+ return true;
1684
+ } catch { return false; }
1685
+ }
1686
+ function _getLastHandoffGap(root) {
1687
+ try {
1688
+ const fp = _lastHandoffPath(root);
1689
+ if (!exists(fp)) return { hasLast: false };
1690
+ const j = JSON.parse(read(fp));
1691
+ if (!j.last) return { hasLast: false };
1692
+ const lastMs = new Date(j.last).getTime();
1693
+ const gapMs = Date.now() - lastMs;
1694
+ const gapMin = Math.floor(gapMs / 60000);
1695
+ // R-0001 영구 룰: 25분 (1500s). gap > 35분 (140% buffer) → wakeup miss 의심
1696
+ const expected = 25;
1697
+ const isMiss = gapMin > 35;
1698
+ const isLong = gapMin > 60;
1699
+ return { hasLast: true, lastAt: j.last, gapMin, expected, isMiss, isLong, history: j.history || [] };
1700
+ } catch { return { hasLast: false }; }
1701
+ }
1702
+
1703
+ // 1.9.198: B축 (멀티 Sub-Agent 오케스트라) 보강 — handoff 에서 task keyword 매칭 best agent 추천
1704
+ // 1.9.193 에서 agents multi --execute 결과를 lessons.md 에 기록 → 이를 keyword 매칭하여 추출
1705
+ // 사용자/AI 가 같은 task 재시도 시 "과거 best agent 우선 시도" 즉시 인지
1706
+ function _loadMultiAgentConsensusHistory(root, keyword) {
1707
+ try {
1708
+ const lp = lessonsPath(root);
1709
+ if (!exists(lp)) return [];
1710
+ const lt = read(lp);
1711
+ // 1.9.193 패턴: ### YYYY-MM-DD multi-agent consensus — best=<agent> (1.9.193)
1712
+ // blocks 분리 후 keyword 매칭 + best agent 추출
1713
+ const blocks = lt.split(/\n### /).slice(1).map(b => '### ' + b);
1714
+ const re193 = /multi-agent consensus — best=(\S+) \(1\.9\.193\)/;
1715
+ const fuzzyRe = keyword ? new RegExp(escapeRegex(keyword.slice(0, Math.max(4, Math.floor(keyword.length * 0.7)))), 'i') : null;
1716
+ const hits = [];
1717
+ for (const b of blocks) {
1718
+ const m = re193.exec(b);
1719
+ if (!m) continue;
1720
+ if (fuzzyRe && !fuzzyRe.test(b)) continue;
1721
+ // task / score 추출
1722
+ const taskM = b.match(/- task: ([^\n]+)/);
1723
+ const scoreM = b.match(/- best agent: \S+, score=([\d.]+)/);
1724
+ const dateM = b.match(/^### (\d{4}-\d{2}-\d{2})/);
1725
+ hits.push({
1726
+ date: dateM ? dateM[1] : '?',
1727
+ agent: m[1],
1728
+ task: taskM ? taskM[1].slice(0, 80) : '',
1729
+ score: scoreM ? Number(scoreM[1]) : null
1730
+ });
1731
+ }
1732
+ return hits;
1733
+ } catch { return []; }
1734
+ }
1735
+
1665
1736
  // 1.9.194: E축 (게으름 방지) — handoff 에서 다음 단계 1-3개 자동 제안
1666
1737
  // 현재 in-progress task description + Memory Surface (decisions/lessons/plan) 조합 →
1667
1738
  // "다음 단계로 무엇을 할지" 추론 (사용자/AI 직접 입력 없이 즉시 노출)
@@ -3284,6 +3355,11 @@ function memorySearch(root, query) {
3284
3355
 
3285
3356
  function handoff(root) {
3286
3357
  root = absRoot(root);
3358
+ // 1.9.199: handoff 진입 시 1) 이전 timestamp 보존 → 2) 현재 timestamp 기록
3359
+ // detector 가 prior gap 을 정확히 측정 (overwrite 전 값 필요). 함수 최상단에 선언.
3360
+ let _priorHandoffGap = { hasLast: false };
3361
+ try { _priorHandoffGap = _getLastHandoffGap(root); } catch {}
3362
+ try { _recordLastHandoff(root); } catch {}
3287
3363
  // 1.9.96: --json 옵션 (구조화 출력, MCP 통합 / 외부 AI 친화)
3288
3364
  if (has('--json')) {
3289
3365
  const result = {
@@ -3725,6 +3801,29 @@ function handoff(root) {
3725
3801
  }
3726
3802
  } catch {}
3727
3803
  }
3804
+ // 1.9.198: B축 (멀티 Sub-Agent 오케스트라) 보강 — handoff 에서 keyword 매칭 best agent 자동 추천
3805
+ // 1.9.193 lessons.md 기록을 회수 → "이 task 키워드는 이전에 agent X 가 best 였다" 표시
3806
+ // 끄기: --no-multiagent-hint 또는 LEERNESS_NO_MULTIAGENT_HINT=1
3807
+ if (!has('--no-multiagent-hint') && !has('--quiet') && process.env.LEERNESS_NO_MULTIAGENT_HINT !== '1') {
3808
+ try {
3809
+ const maHits = _loadMultiAgentConsensusHistory(root, keyword);
3810
+ if (maHits.length > 0) {
3811
+ // 가장 최근 hit 1 + 동일 agent 빈도 카운트
3812
+ const recent = maHits[maHits.length - 1];
3813
+ const agentCounts = {};
3814
+ for (const h of maHits) { agentCounts[h.agent] = (agentCounts[h.agent] || 0) + 1; }
3815
+ const topAgent = Object.entries(agentCounts).sort((a, b) => b[1] - a[1])[0];
3816
+ const isTty = process.stdout && process.stdout.isTTY;
3817
+ const blu = s => isTty ? `\x1b[34m${s}\x1b[0m` : s;
3818
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3819
+ log(blu(`## 🤖 task 매칭 best agent (1.9.198 B축) — 키워드 "${keyword}"`));
3820
+ log(dim(` 과거 multi-agent consensus ${maHits.length}건 — 추천 agent: ${topAgent[0]} (${topAgent[1]}회 best)`));
3821
+ log(dim(` 최근 hit: ${recent.date} agent=${recent.agent}${recent.score != null ? ` score=${recent.score.toFixed(3)}` : ''}`));
3822
+ log(dim(` → 재실행: leerness agents multi "${(latestRow.request || '').slice(0, 60)}" --execute`));
3823
+ log('');
3824
+ }
3825
+ } catch {}
3826
+ }
3728
3827
  // 1.9.196: D축 (장기 맥락 유지) 보강 — 30일+ 전 lessons 자동 회고 (long-term memory recall)
3729
3828
  // keyword 와 매칭되는 30일+ 오래된 lessons 1건 노출 → 잊혀진 과거 교훈 재상기
3730
3829
  // 끄기: --no-longterm-recall 또는 LEERNESS_NO_LONGTERM_RECALL=1
@@ -3766,25 +3865,51 @@ function handoff(root) {
3766
3865
  }
3767
3866
  } catch {}
3768
3867
  }
3769
- // 1.9.196: ScheduleWakeup miss detector — 자율 모드 사용자 명시 "못일어나는 경우 종종 있음" (1.9.191) 후속
3770
- // 마지막 session 시간 (handoff mtime 또는 task-log 마지막 entry) > 2h ago 시 알림
3868
+ // 1.9.196/199: ScheduleWakeup miss detector — 자율 모드 사용자 명시 "못일어나는 경우 종종 있음"
3869
+ // 1.9.196: task-log mtime 기반 (60min+) false positive 가능
3870
+ // 1.9.199: last-handoff.json 정밀 측정 — R-0001 룰 (25min) 대비 gap 측정
3771
3871
  // 끄기: LEERNESS_NO_WAKEUP_MISS=1
3772
3872
  if (process.env.LEERNESS_NO_WAKEUP_MISS !== '1' && !has('--quiet') && !has('--no-wakeup-miss')) {
3773
3873
  try {
3774
- const tlp = taskLogPath(root);
3775
- if (exists(tlp)) {
3776
- const ageMs = Date.now() - fs.statSync(tlp).mtimeMs;
3777
- const ageMin = Math.floor(ageMs / 60000);
3778
- // 자율 모드는 ~15분 cycle. 60분 이상 활동이면 wakeup miss 의심 (CronCreate 13min 백업과 함께)
3779
- if (ageMin >= 60) {
3780
- const isTty = process.stdout && process.stdout.isTTY;
3781
- const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3782
- const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3783
- const label = ageMin < 120 ? `${ageMin}분` : (ageMin < 1440 ? `${Math.floor(ageMin/60)}시간` : `${Math.floor(ageMin/1440)}일`);
3784
- log(yel(`## ScheduleWakeup miss 의심 (1.9.196) — 마지막 활동 ${label} 전`));
3785
- log(dim(` 자율 모드 정상 cycle: ~15분. 60분 이상 활동 시스템 sleep 또는 wakeup 누락 가능성`));
3786
- log(dim(` 재개: 사용자가 "다음 라운드" 또는 "/loop" 입력`));
3874
+ // 1.9.199 신규: 이전 handoff 시점 (overwrite 전 보존된 값) 사용
3875
+ const gapInfo = _priorHandoffGap;
3876
+ const isTty = process.stdout && process.stdout.isTTY;
3877
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3878
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
3879
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3880
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3881
+ if (gapInfo.hasLast) {
3882
+ // 마지막 handoff 시점 알고 있음 정확한 측정 (1.9.199)
3883
+ if (gapInfo.isLong) {
3884
+ // 60분+ 강한 알림
3885
+ log(red(`## ScheduleWakeup miss 강한 의심 (1.9.199) 이전 handoff ${gapInfo.gapMin}분 전`));
3886
+ log(dim(` R-0001 영구 (25분) 대비 ${Math.floor(gapInfo.gapMin/25)}× 초과 — 시스템 sleep / wakeup 누락 확실`));
3887
+ log(dim(` → 회복: 사용자가 "다음 라운드" 입력 또는 leerness rule list 로 룰 확인`));
3888
+ log(dim(` → handoff 이력: ${(gapInfo.history || []).slice(-3).map(t => t.slice(11, 19)).join(' → ')}`));
3787
3889
  log('');
3890
+ } else if (gapInfo.isMiss) {
3891
+ // 35~60분 → 의심
3892
+ log(yel(`## ⏰ ScheduleWakeup 지연 (1.9.199) — 이전 handoff ${gapInfo.gapMin}분 전 (룰: 25분)`));
3893
+ log(dim(` ±10분 buffer 초과 — wakeup 한 cycle 누락 가능성`));
3894
+ log('');
3895
+ } else if (gapInfo.gapMin >= 0 && gapInfo.gapMin <= 30) {
3896
+ // 정상 범위 (handoff_history.length >= 2 일 때만 의미 있음 — 첫 진입 제외)
3897
+ if ((gapInfo.history || []).length >= 2) {
3898
+ log(dim(` ✓ ScheduleWakeup cycle 정상 (gap ${gapInfo.gapMin}분, 룰 25분 — 1.9.199)`));
3899
+ }
3900
+ }
3901
+ } else {
3902
+ // last-handoff.json 부재 — 1.9.196 fallback (task-log mtime)
3903
+ const tlp = taskLogPath(root);
3904
+ if (exists(tlp)) {
3905
+ const ageMs = Date.now() - fs.statSync(tlp).mtimeMs;
3906
+ const ageMin = Math.floor(ageMs / 60000);
3907
+ if (ageMin >= 60) {
3908
+ const label = ageMin < 120 ? `${ageMin}분` : (ageMin < 1440 ? `${Math.floor(ageMin/60)}시간` : `${Math.floor(ageMin/1440)}일`);
3909
+ log(yel(`## ⏰ ScheduleWakeup miss 의심 (1.9.196 fallback) — task-log 마지막 ${label} 전`));
3910
+ log(dim(` 1.9.199 last-handoff.json 첫 기록 — 다음 handoff 부터 정확 측정`));
3911
+ log('');
3912
+ }
3788
3913
  }
3789
3914
  }
3790
3915
  } catch {}
@@ -4197,6 +4322,7 @@ function handoffCmd(root) {
4197
4322
  if (has('--all-apps') || arg('--include', null)) {
4198
4323
  return _handoffWorkspace(absRoot(root));
4199
4324
  }
4325
+ // 1.9.199: timestamp 기록/측정은 handoff() 내부에서 수행 (중복 방지)
4200
4326
  // 1.9.37: drift 자동 경고 (메인 에이전트가 leerness를 점점 안 쓰는 현상 감지)
4201
4327
  // 1.9.38 (A): drift 임계 시 .harness/agent-reminders.md 자동 생성 — 메인 에이전트 프롬프트에 표시되도록.
4202
4328
  // 1.9.38 (D): skip 횟수 학습 — --no-drift-check 빈도 ≥5 시 임계 완화 (1d → 2d).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.197",
3
+ "version": "1.9.199",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",