leerness 1.9.198 → 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,47 @@
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
+
3
45
  ## 1.9.198 — 2026-05-21
4
46
 
5
47
  **🤖 B축 9/10 → 9.5/10 — handoff multi-agent consensus 이력 기반 best agent 자동 추천.**
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.198-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v143-14%2F14-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-128-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-60_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/목적_매트릭스-96%2F100_(5축)-brightgreen)]() [![best-agent](https://img.shields.io/badge/handoff-best_agent_자동_추천-success)]() [![perf](https://img.shields.io/badge/cold_start-500ms-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.198 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.199 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🤖 best agent 자동 추천 · 매트릭스 96/100 · 128 라운드
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.198';
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,44 @@ 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
+
1665
1703
  // 1.9.198: B축 (멀티 Sub-Agent 오케스트라) 보강 — handoff 에서 task keyword 매칭 best agent 추천
1666
1704
  // 1.9.193 에서 agents multi --execute 결과를 lessons.md 에 기록 → 이를 keyword 매칭하여 추출
1667
1705
  // 사용자/AI 가 같은 task 재시도 시 "과거 best agent 우선 시도" 즉시 인지
@@ -3317,6 +3355,11 @@ function memorySearch(root, query) {
3317
3355
 
3318
3356
  function handoff(root) {
3319
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 {}
3320
3363
  // 1.9.96: --json 옵션 (구조화 출력, MCP 통합 / 외부 AI 친화)
3321
3364
  if (has('--json')) {
3322
3365
  const result = {
@@ -3822,25 +3865,51 @@ function handoff(root) {
3822
3865
  }
3823
3866
  } catch {}
3824
3867
  }
3825
- // 1.9.196: ScheduleWakeup miss detector — 자율 모드 사용자 명시 "못일어나는 경우 종종 있음" (1.9.191) 후속
3826
- // 마지막 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 측정
3827
3871
  // 끄기: LEERNESS_NO_WAKEUP_MISS=1
3828
3872
  if (process.env.LEERNESS_NO_WAKEUP_MISS !== '1' && !has('--quiet') && !has('--no-wakeup-miss')) {
3829
3873
  try {
3830
- const tlp = taskLogPath(root);
3831
- if (exists(tlp)) {
3832
- const ageMs = Date.now() - fs.statSync(tlp).mtimeMs;
3833
- const ageMin = Math.floor(ageMs / 60000);
3834
- // 자율 모드는 ~15분 cycle. 60분 이상 활동이면 wakeup miss 의심 (CronCreate 13min 백업과 함께)
3835
- if (ageMin >= 60) {
3836
- const isTty = process.stdout && process.stdout.isTTY;
3837
- const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3838
- const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3839
- const label = ageMin < 120 ? `${ageMin}분` : (ageMin < 1440 ? `${Math.floor(ageMin/60)}시간` : `${Math.floor(ageMin/1440)}일`);
3840
- log(yel(`## ScheduleWakeup miss 의심 (1.9.196) — 마지막 활동 ${label} 전`));
3841
- log(dim(` 자율 모드 정상 cycle: ~15분. 60분 이상 활동 시스템 sleep 또는 wakeup 누락 가능성`));
3842
- 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(' → ')}`));
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 누락 가능성`));
3843
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
+ }
3844
3913
  }
3845
3914
  }
3846
3915
  } catch {}
@@ -4253,6 +4322,7 @@ function handoffCmd(root) {
4253
4322
  if (has('--all-apps') || arg('--include', null)) {
4254
4323
  return _handoffWorkspace(absRoot(root));
4255
4324
  }
4325
+ // 1.9.199: timestamp 기록/측정은 handoff() 내부에서 수행 (중복 방지)
4256
4326
  // 1.9.37: drift 자동 경고 (메인 에이전트가 leerness를 점점 안 쓰는 현상 감지)
4257
4327
  // 1.9.38 (A): drift 임계 시 .harness/agent-reminders.md 자동 생성 — 메인 에이전트 프롬프트에 표시되도록.
4258
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.198",
3
+ "version": "1.9.199",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",