leerness 1.9.204 → 1.9.205

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,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.205 — 2026-05-22
4
+
5
+ **⚡ ScheduleWakeup 사용자 요청 우선 갱신 (사용자 명시) — wakeup miss 실측 후속.**
6
+
7
+ ### 사용자 명시 + 보고
8
+ > *"자동모드로 진행할때, 예정된 알람이전에 사용자 요청이 들어오면 백그라운드의 알람을 종료후 다시 갱신하는 방식은 어떤지"*
9
+ > *"ScheduleWakeup 10:51 KST 가 지났어"* — 사용자가 명시 보고한 wakeup miss 사례
10
+
11
+ ### 1. `.harness/active-wakeups.json` 등록 추적
12
+ - release sync-main 마무리 시 다음 wakeup 자동 등록
13
+ - 구조: `{ expectedFireAt, intervalMin, source, status: pending/fired/missed/superseded, registeredAt }`
14
+ - 최근 20개 유지
15
+ - 함수: `_loadActiveWakeups` / `_writeActiveWakeups` / `_recordWakeup` / `_activeWakeupsPath`
16
+
17
+ ### 2. handoff 진입 시 wakeup 상태 분석 (`_analyzeWakeupStatus`)
18
+ | kind | 조건 | 알림 |
19
+ |---|---|---|
20
+ | `early` | delta < -5min (미래) | `## ⚡ 사용자 조기 진입 감지` |
21
+ | `on-time` | -5 ~ +5min | `✓ ScheduleWakeup 정시 진입` |
22
+ | `late` | +5 ~ +30min | (조용히) |
23
+ | `missed` | > +30min | `## ⏰ ScheduleWakeup miss 확정` + status 자동 갱신 |
24
+
25
+ ### 3. 조기 진입 시 자동 갱신 가이드
26
+ 사용자가 wakeup 예정 시각 이전 진입 → "라운드 마무리 시 새 wakeup 등록 (이전 자동 superseded)" 안내
27
+
28
+ ### 4. miss 자동 status 갱신
29
+ fire 시간 지남 + 30분 이상 → status `pending` → `missed` 자동 업데이트 → 통계 누적
30
+
31
+ ### 5. 누적 회귀 (1.9.199~204) — 모두 유지
32
+
33
+ ### 6. stress-v150 — 16/16 PASS
34
+ - 등록 추적 (3) + 상태 분석 (2) + 임시 워크스페이스 통합 (2) + 성능 (2) + 누적 (7)
35
+ - 성능: --version cold start avg **464 ms** · MCP 54 도구 **460 ms**
36
+
37
+ ### 7. 자동 release
38
+ - main 자동 push **67 라운드 연속** (1.9.140~205)
39
+ - npm publish 자동 (1.9.178~)
40
+
41
+ ---
42
+
3
43
  ## 1.9.204 — 2026-05-22
4
44
 
5
45
  **⏰ timezone 보강 + 🔄 auto-loop 활성 라벨 (사용자 명시 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.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)]()
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.205-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v150-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-135-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-67_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/목적_매트릭스-98%2F100_(5축)-brightgreen)]() [![wakeup-track](https://img.shields.io/badge/wakeup-early%2Fmissed_감지-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.204 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.205 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- timezone 보강 (KST) · 🔄 auto-loop 25min 헤드라인 라벨
17
+ wakeup early/missed 감지 + 자동 갱신 가이드 (사용자 명시)
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.204';
10
+ const VERSION = '1.9.205';
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 처리).
@@ -1779,6 +1779,60 @@ async function skillAutoCacheCmd(root, sub) {
1779
1779
  if (c.expired) log(` → 갱신: leerness skill auto-cache refresh`);
1780
1780
  }
1781
1781
 
1782
+ // 1.9.205: ScheduleWakeup 등록 추적 (사용자 명시)
1783
+ // "예정된 알람이전에 사용자 요청이 들어오면 백그라운드의 알람을 종료후 다시 갱신"
1784
+ // 라운드 마무리 시 등록된 wakeup id/expectedFireAt 을 .harness/active-wakeups.json 에 기록 →
1785
+ // handoff/resume 진입 시 expected fire 가 과거이면 "wakeup miss 또는 사용자 조기 진입" 감지 →
1786
+ // 메인 에이전트가 새 wakeup 호출하라는 명확한 안내
1787
+ function _activeWakeupsPath(root) { return path.join(root, '.harness', 'active-wakeups.json'); }
1788
+ function _loadActiveWakeups(root) {
1789
+ try {
1790
+ const fp = _activeWakeupsPath(root);
1791
+ if (!exists(fp)) return { wakeups: [] };
1792
+ const j = JSON.parse(read(fp));
1793
+ return { wakeups: Array.isArray(j.wakeups) ? j.wakeups : [] };
1794
+ } catch { return { wakeups: [] }; }
1795
+ }
1796
+ function _writeActiveWakeups(root, wakeups) {
1797
+ try {
1798
+ mkdirp(path.join(root, '.harness'));
1799
+ writeUtf8(_activeWakeupsPath(root), JSON.stringify({ wakeups, updatedAt: new Date().toISOString() }, null, 2));
1800
+ return true;
1801
+ } catch { return false; }
1802
+ }
1803
+ function _recordWakeup(root, entry) {
1804
+ const state = _loadActiveWakeups(root);
1805
+ // entry: { expectedFireAt, intervalMin, source, status: 'pending'|'fired'|'missed'|'superseded' }
1806
+ const e = { ...entry, registeredAt: new Date().toISOString() };
1807
+ // 같은 expectedFireAt 중복 시 replace
1808
+ state.wakeups = state.wakeups.filter(w => w.expectedFireAt !== e.expectedFireAt);
1809
+ state.wakeups.push(e);
1810
+ // 최근 20개만 유지
1811
+ if (state.wakeups.length > 20) state.wakeups = state.wakeups.slice(-20);
1812
+ _writeActiveWakeups(root, state.wakeups);
1813
+ return e;
1814
+ }
1815
+ // wakeup miss / 사용자 조기 진입 감지 — 활성 wakeup 중 가장 가까운 미래/과거 fire 분석
1816
+ function _analyzeWakeupStatus(root) {
1817
+ const state = _loadActiveWakeups(root);
1818
+ const pending = state.wakeups.filter(w => w.status === 'pending');
1819
+ if (pending.length === 0) return { hasWakeup: false };
1820
+ // 가장 최근 등록된 pending wakeup
1821
+ const latest = pending.sort((a, b) => new Date(b.registeredAt).getTime() - new Date(a.registeredAt).getTime())[0];
1822
+ const now = Date.now();
1823
+ const expectedMs = new Date(latest.expectedFireAt).getTime();
1824
+ const deltaMin = Math.floor((now - expectedMs) / 60000);
1825
+ // deltaMin < 0 → 아직 fire 시간 전 (사용자 조기 진입)
1826
+ // deltaMin >= 0 → fire 시간 지남 (정시 또는 miss)
1827
+ // |deltaMin| < 5 → 정시 부근
1828
+ let kind;
1829
+ if (deltaMin < -5) kind = 'early'; // 사용자 조기 진입
1830
+ else if (deltaMin > 30) kind = 'missed'; // wakeup miss
1831
+ else if (deltaMin > 5) kind = 'late'; // 약간 지연
1832
+ else kind = 'on-time';
1833
+ return { hasWakeup: true, latest, deltaMin, kind, pending: pending.length };
1834
+ }
1835
+
1782
1836
  // 1.9.203: 자동 라운드 plan 정리 — 사용자 명시
1783
1837
  // "백그라운드에 다음 작업이 시작가능한 예상 시간 + 일어났을때 해야하는 일을 정리"
1784
1838
  // 라운드 마무리 시 .harness/auto-resume-plan.json 자동 저장 → 다음 wakeup 시 즉시 실행 가능
@@ -4224,6 +4278,40 @@ function handoff(root) {
4224
4278
  }
4225
4279
  } catch {}
4226
4280
  }
4281
+ // 1.9.205: ScheduleWakeup 상태 분석 + 사용자 조기 진입 / miss 감지 (사용자 명시)
4282
+ if (process.env.LEERNESS_NO_WAKEUP_STATUS !== '1' && !has('--quiet')) {
4283
+ try {
4284
+ const wakeup = _analyzeWakeupStatus(root);
4285
+ if (wakeup.hasWakeup) {
4286
+ const isTty = process.stdout && process.stdout.isTTY;
4287
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
4288
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
4289
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
4290
+ const expectedLocal = _formatLocal(wakeup.latest.expectedFireAt);
4291
+ if (wakeup.kind === 'early') {
4292
+ // 사용자 조기 진입 — 기존 wakeup 갱신 권장
4293
+ log(yel(`## ⚡ 사용자 조기 진입 감지 (1.9.205) — 등록된 wakeup ${Math.abs(wakeup.deltaMin)}분 후 fire 예정`));
4294
+ log(dim(` 예정 fire: ${expectedLocal} (${wakeup.latest.source})`));
4295
+ log(dim(` → 권장: 라운드 마무리 시 새 wakeup 등록 (이전 자동 superseded)`));
4296
+ log('');
4297
+ } else if (wakeup.kind === 'missed') {
4298
+ // wakeup miss — fire 시간 지남
4299
+ log(yel(`## ⏰ ScheduleWakeup miss 확정 (1.9.205) — 예정 fire ${wakeup.deltaMin}분 전`));
4300
+ log(dim(` 예정 fire: ${expectedLocal} (${wakeup.latest.source})`));
4301
+ log(dim(` → 회복: 라운드 마무리 시 새 wakeup 자동 등록`));
4302
+ // 자동으로 status 를 'missed' 로 업데이트
4303
+ try {
4304
+ const s = _loadActiveWakeups(root);
4305
+ const idx = s.wakeups.findIndex(w => w.expectedFireAt === wakeup.latest.expectedFireAt);
4306
+ if (idx >= 0) { s.wakeups[idx].status = 'missed'; _writeActiveWakeups(root, s.wakeups); }
4307
+ } catch {}
4308
+ log('');
4309
+ } else if (wakeup.kind === 'on-time') {
4310
+ log(grn(` ✓ ScheduleWakeup 정시 진입 (1.9.205, delta ${wakeup.deltaMin}분)`));
4311
+ }
4312
+ }
4313
+ } catch {}
4314
+ }
4227
4315
  // 1.9.203: auto-resume-plan 알림 — 라운드 마무리 시 저장된 plan 자동 노출
4228
4316
  // 사용자 명시: "백그라운드에 다음 작업이 시작가능한 예상 시간 + 일어났을때 해야하는 일을 정리"
4229
4317
  if (_autoResumePlan && !has('--quiet') && process.env.LEERNESS_NO_RESUME_PLAN !== '1') {
@@ -9334,6 +9422,14 @@ function _publishToNpm(root, opts = {}) {
9334
9422
  note: `다음 wakeup 시: 1) leerness resume . 으로 plan 확인 2) leerness handoff . 실행 3) leerness next-action take 로 즉시 task 진입`
9335
9423
  });
9336
9424
  _writeAutoResumePlan(root, plan);
9425
+ // 1.9.205: 다음 wakeup 등록 추적 (사용자 명시 갱신용)
9426
+ _recordWakeup(root, {
9427
+ expectedFireAt: plan.expectedFireAt,
9428
+ intervalMin: plan.intervalMin || 25,
9429
+ source: 'release-sync-main',
9430
+ status: 'pending',
9431
+ fromVersion: pkgVersion
9432
+ });
9337
9433
  } catch {}
9338
9434
  } else {
9339
9435
  const errOut = (pubR.stderr || pubR.stdout || '').slice(-400);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.204",
3
+ "version": "1.9.205",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",