leerness 1.9.209 โ†’ 1.9.211

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,103 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.211 โ€” 2026-05-22
4
+
5
+ **๐Ÿ“‚ `.harness` โ†’ `.leerness` opt-in ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ + AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ (์‚ฌ์šฉ์ž ๋ช…์‹œ).**
6
+
7
+ ### ์‚ฌ์šฉ์ž ๋ช…์‹œ
8
+ > *"leerness์„ค์น˜์‹œ ์ƒ์„ฑ๋˜๋Š” .harness๋ฅผ .leerness์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์„๊นŒ / ๊ธฐ์กด ๋ฒ„์ „์—์„œ๋„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์‹œ AI๊ฐ€ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ธฐ๋„ํ•˜๋ฉด ์–ด๋–จ๊นŒ"*
9
+
10
+ ### 1. workspace-dir ํ—ฌํผ (default-safe opt-in)
11
+ - `_workspaceDirName(root)` โ€” env `LEERNESS_WORKSPACE_DIR` > `.leerness/MIGRATED_FROM_HARNESS` ๋งˆ์ปค > default `.harness`
12
+ - `_workspaceDirAbs(root)` โ€” ์ ˆ๋Œ€ ๊ฒฝ๋กœ
13
+ - ๊ธฐ๋ณธ ๋™์ž‘: **`.harness` ์œ ์ง€** (226+ path reference ์•ˆ์ •) โ€” breaking change 0
14
+
15
+ ### 2. ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ์Šคํ…œ
16
+ - `_migrateWorkspaceDir(root, opts)` โ€” `.harness` ์ „์ฒด ์žฌ๊ท€ copy โ†’ `.leerness`
17
+ ยท `--dry-run` ์ง€์›
18
+ ยท `--force` (๊ธฐ์กด dst ๋ฎ์–ด์“ฐ๊ธฐ)
19
+ ยท skip / errors / copiedFiles ๋ถ„๋ฅ˜ ๋ณด๊ณ 
20
+ - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฐ๊ณผ๋ฌผ:
21
+ ยท `.leerness/MIGRATED_FROM_HARNESS` ๋งˆ์ปค
22
+ ยท `.leerness/WHERE_TO_FIND.md` AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ (์ž๋™ ์ƒ์„ฑ)
23
+ ยท `.harness/MIGRATED_TO_LEERNESS.md` redirect ์•ˆ๋‚ด (backward compat ์œ ์ง€)
24
+
25
+ ### 3. AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ (`_buildWorkspaceReferenceGuide`)
26
+ - ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ์‹œ๊ฐํ™” (ํ•ต์‹ฌ ํŒŒ์ผ 25๊ฐœ)
27
+ - "์ž์ฃผ ๋ฌป๋Š” ์œ„์น˜" ๋งคํ•‘ ํ‘œ (ํ˜„์žฌ task / ์˜๊ตฌ ๋ฃฐ / pre-wake / ๋ฏธ๋‹ต ์š”์ฒญ / ๋‹ค์Œ ๋ผ์šด๋“œ plan / API ์ œ์•ฝ / wakeup ๊ถŒ์žฅ)
28
+ - ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์•ˆ๋‚ด (3๊ฐ€์ง€ ์ƒํƒœ ๋ถ„๊ธฐ)
29
+ - 1.9.207~210 ์‹ ๊ทœ ๋ฉ”ํƒ€ํŒŒ์ผ ์œ„์น˜ ๋ช…์‹œ
30
+
31
+ ### 4. `leerness workspace-dir <get|guide>` CLI
32
+ - `get` โ€” ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ + .harness/.leerness ์กด์žฌ ์—ฌ๋ถ€ + ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒํƒœ
33
+ - `guide` โ€” AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ ์ถœ๋ ฅ
34
+
35
+ ### 5. `leerness migrate-workspace-dir` CLI
36
+ - `--dry-run` / `--force` ์ง€์›
37
+ - copied / skipped / errors ๋ถ„๋ฅ˜
38
+ - ์ž๋™ ๋งˆ์ปค + ๊ฐ€์ด๋“œ + redirect ์ƒ์„ฑ
39
+
40
+ ### 6. ๋ˆ„์  ํšŒ๊ท€ (1.9.200~210) โ€” ๋ชจ๋‘ ์œ ์ง€
41
+
42
+ ### 7. stress-v156 โ€” 19/19 PASS
43
+ - 1.9.211 (10) + ์„ฑ๋Šฅ (2) + ๋ˆ„์  ํšŒ๊ท€ (7)
44
+ - ๊ฒฉ๋ฆฌ tmp dir ์ „์ฒด ์‚ฌ์ดํด (default โ†’ dry-run โ†’ migrate โ†’ auto-detect โ†’ env override)
45
+ - AI ๊ฐ€์ด๋“œ ํ‚ค ์„น์…˜ ๊ฒ€์ฆ
46
+ - ์„ฑ๋Šฅ: --version cold start avg 452ms ยท MCP 54 ๋„๊ตฌ 445ms
47
+
48
+ ### 8. ์ž๋™ release (73 ๋ผ์šด๋“œ main-push streak ยท 34 ๋ผ์šด๋“œ npm publish streak)
49
+
50
+ ### 9. ํ›„์† ๋ผ์šด๋“œ ๋กœ๋“œ๋งต
51
+ - ์ด๋ฒˆ ๋ผ์šด๋“œ๋Š” **opt-in ํ—ฌํผ + CLI**๋งŒ ์ถ”๊ฐ€ (breaking change 0)
52
+ - ํ›„์†์—์„œ 226+ path reference ๋ฅผ `_workspaceDirAbs(root)` ๋กœ ์ ์ง„ ์ „ํ™˜
53
+
54
+ ---
55
+
56
+ ## 1.9.210 โ€” 2026-05-22
57
+
58
+ **โšก adaptive wakeup interval โ€” ์‚ฌ์šฉ์ž ํ™œ๋™ ๊ธฐ๋ฐ˜ ์ž๋™ ์กฐ์ ˆ (์‚ฌ์šฉ์ž ๋ช…์‹œ).**
59
+
60
+ ### ์‚ฌ์šฉ์ž ๋ช…์‹œ
61
+ > *"wakeup 30๋ถ„๋“ฑ ๊ธด ์‹œ๊ฐ„์ด ํ•„์š”์—†๋‹ค๋ฉด ๊ฐ„๊ฒฉ์„ ํƒ€์ดํŠธํ•˜๊ฒŒ ์ž๋™์œผ๋กœ ์กฐ์ ˆํ•˜๋„๋ก"*
62
+
63
+ ### 1. `.harness/wakeup-history.json` โ€” fire ์ด๋ ฅ ๋ˆ„์ 
64
+ - `_wakeupHistoryPath / _loadWakeupHistory / _writeWakeupHistory` โ€” ํŒŒ์ผ I/O
65
+ - `_recordWakeupFire(root, kind)` โ€” kind: 'auto' / 'user-trigger' / 'wakeup-miss'
66
+ - ์ตœ๊ทผ 50๊ฐœ rotate
67
+
68
+ ### 2. `_computeAdaptiveInterval(root)` โ€” ํ™œ๋™๋Ÿ‰ ๊ธฐ๋ฐ˜ ๊ถŒ์žฅ interval
69
+ - ๋ฒ”์œ„: **600s (10min) ~ 2700s (45min)**, default 1500s
70
+ - ๋ถ„์„ ์ฐจ์›:
71
+ - ์ตœ๊ทผ 2์‹œ๊ฐ„ user-trigger ๋นˆ๋„ (3+ โ†’ 15min, 0 โ†’ 35min)
72
+ - ์ตœ๊ทผ 5๊ฑด user-trigger ํ‰๊ท  gap (avg < 20min โ†’ ๋‹จ์ถ•)
73
+ - pre-wake critical ์‹ ํ˜ธ (1.9.209 ํ†ตํ•ฉ) โ†’ 20min ๋‹จ์ถ•
74
+ - opt-out:
75
+ - env `LEERNESS_FIXED_INTERVAL=1500`
76
+ - `leerness wakeup-interval set 900` (override)
77
+
78
+ ### 3. `leerness wakeup-interval <get|set|auto|history|record>` CLI
79
+ - `get` โ€” ๊ถŒ์žฅ interval + stats (--json)
80
+ - `set <secs>` โ€” override ์„ค์ •
81
+ - `auto` โ€” override ํ•ด์ œ + ์ž๋™ ๊ณ„์‚ฐ ๋ณต๊ท€
82
+ - `history` โ€” ์ตœ๊ทผ fire ์ด๋ ฅ (--json)
83
+ - `record <kind>` โ€” fire ๊ธฐ๋ก (ํ…Œ์ŠคํŠธ/์™ธ๋ถ€ ํ†ตํ•ฉ์šฉ)
84
+
85
+ ### 4. ์ž๋™ ๋‹จ์ถ• ์‹œ๋‚˜๋ฆฌ์˜ค ๊ฒ€์ฆ
86
+ - ์‹ ๊ทœ ์›Œํฌ์ŠคํŽ˜์ด์Šค idle โ†’ **35min**
87
+ - user-trigger 3ํšŒ/2h โ†’ **10min** (1500 โ†’ 600)
88
+ - pre-wake critical + ํ™œ๋™ ๋ณดํ†ต โ†’ **20min**
89
+
90
+ ### 5. ๋ˆ„์  ํšŒ๊ท€ (1.9.200~209) โ€” ๋ชจ๋‘ ์œ ์ง€
91
+
92
+ ### 6. stress-v155 โ€” 18/18 PASS
93
+ - 1.9.210 (9) + ์„ฑ๋Šฅ (2) + ๋ˆ„์  ํšŒ๊ท€ (7)
94
+ - ๊ฒฉ๋ฆฌ tmp dir ๋ผ์ดํ”„์‚ฌ์ดํด + override ํ† ๊ธ€ + rotate to 50
95
+ - ์„ฑ๋Šฅ: --version cold start avg 440ms ยท MCP 54 ๋„๊ตฌ 421ms
96
+
97
+ ### 7. ์ž๋™ release (72 ๋ผ์šด๋“œ main-push streak ยท 33 ๋ผ์šด๋“œ npm publish streak)
98
+
99
+ ---
100
+
3
101
  ## 1.9.209 โ€” 2026-05-22
4
102
 
5
103
  **๐Ÿ” pre-wake sub-agent audit + ๊นจ์–ด๋‚จ ์งํ›„ ์ž๋™ ๋…ธ์ถœ (์‚ฌ์šฉ์ž ๋ช…์‹œ).**
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.209-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v154-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-139-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-71_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/๋ชฉ์ _๋งคํŠธ๋ฆญ์Šค-98%2F100_(5์ถ•)-brightgreen)]() [![pre-wake](https://img.shields.io/badge/pre--wake-sub--agent_audit-success)]() [![perf](https://img.shields.io/badge/cold_start-517ms-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.211-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v156-19%2F19-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-141-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-73_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/๋ชฉ์ _๋งคํŠธ๋ฆญ์Šค-98%2F100_(5์ถ•)-brightgreen)]() [![workspace](https://img.shields.io/badge/.harnessโ†’.leerness-opt--in_migration-success)]() [![perf](https://img.shields.io/badge/cold_start-452ms-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.209 AI Agent Reliability Harness + Sandbox โ•‘
15
+ โ•‘ v1.9.211 AI Agent Reliability Harness + Sandbox โ•‘
16
16
  โ•‘ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ•‘
17
- โ•‘ ๐Ÿ” pre-wake sub-agent audit (sleep ์ „ ์ ๊ฒ€) (์‚ฌ์šฉ์ž ๋ช…์‹œ) โ•‘
17
+ โ•‘ ๐Ÿ“‚ .harness โ†’ .leerness opt-in ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์‚ฌ์šฉ์ž ๋ช…์‹œ) โ•‘
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.209';
10
+ const VERSION = '1.9.211';
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 ์ฒ˜๋ฆฌ).
@@ -2251,6 +2251,281 @@ function _saveAndAppendPreWakeReport(root, audit) {
2251
2251
  return audit;
2252
2252
  }
2253
2253
 
2254
+ // 1.9.210: adaptive wakeup interval (์‚ฌ์šฉ์ž ๋ช…์‹œ)
2255
+ // "wakeup 30๋ถ„๋“ฑ ๊ธด ์‹œ๊ฐ„์ด ํ•„์š”์—†๋‹ค๋ฉด ๊ฐ„๊ฒฉ์„ ํƒ€์ดํŠธํ•˜๊ฒŒ ์ž๋™์œผ๋กœ ์กฐ์ ˆํ•˜๋„๋ก"
2256
+ // ์‚ฌ์šฉ์ž ํ™œ๋™๋Ÿ‰ ๊ธฐ๋ฐ˜ ๊ถŒ์žฅ interval ๊ณ„์‚ฐ โ†’ ScheduleWakeup ์‹œ ์ž๋™ ์ ์šฉ (R-0001 ๋ฃฐ ์™ธ ๋™์  ๋ณด์ •)
2257
+ // .harness/wakeup-history.json ์— fire ์ด๋ ฅ ๋ˆ„์  โ†’ ์ตœ๊ทผ N๊ฐœ ์‚ฌ์ด ํ‰๊ท  user-trigger gap ๋ถ„์„
2258
+ // opt-out: LEERNESS_FIXED_INTERVAL=1 (env) ๋˜๋Š” leerness wakeup-interval set <secs>
2259
+ function _wakeupHistoryPath(root) { return path.join(root, '.harness', 'wakeup-history.json'); }
2260
+ function _loadWakeupHistory(root) {
2261
+ try {
2262
+ const fp = _wakeupHistoryPath(root);
2263
+ if (!exists(fp)) return { fires: [], override: null };
2264
+ const j = JSON.parse(read(fp));
2265
+ return { fires: Array.isArray(j.fires) ? j.fires : [], override: j.override || null };
2266
+ } catch { return { fires: [], override: null }; }
2267
+ }
2268
+ function _writeWakeupHistory(root, state) {
2269
+ try {
2270
+ mkdirp(path.join(root, '.harness'));
2271
+ writeUtf8(_wakeupHistoryPath(root), JSON.stringify({ ...state, updatedAt: new Date().toISOString() }, null, 2));
2272
+ return true;
2273
+ } catch { return false; }
2274
+ }
2275
+ function _recordWakeupFire(root, kind) {
2276
+ // kind: 'auto' (scheduled), 'user-trigger' (์‚ฌ์šฉ์ž ๋ช…์‹œ ๋‹ค์Œ๋ผ์šด๋“œ), 'wakeup-miss'
2277
+ const state = _loadWakeupHistory(root);
2278
+ state.fires.push({ at: new Date().toISOString(), kind: kind || 'auto' });
2279
+ // ์ตœ๊ทผ 50๊ฐœ๋งŒ ์œ ์ง€
2280
+ if (state.fires.length > 50) state.fires = state.fires.slice(-50);
2281
+ _writeWakeupHistory(root, state);
2282
+ return state;
2283
+ }
2284
+ // ๊ถŒ์žฅ interval ๊ณ„์‚ฐ (sec)
2285
+ // user-trigger ๋นˆ๋„ + pre-wake critical + ์ตœ๊ทผ ํ‰๊ท  gap ๊ธฐ๋ฐ˜
2286
+ // ๋ฒ”์œ„: 600 (10min) ~ 2700 (45min)
2287
+ function _computeAdaptiveInterval(root) {
2288
+ const DEFAULT = 1500; // 25min
2289
+ const MIN = 600; // 10min
2290
+ const MAX = 2700; // 45min
2291
+
2292
+ // 1) opt-out โ€” env or override
2293
+ if (process.env.LEERNESS_FIXED_INTERVAL && process.env.LEERNESS_FIXED_INTERVAL !== '0') {
2294
+ const fixed = Number(process.env.LEERNESS_FIXED_INTERVAL);
2295
+ if (!isNaN(fixed) && fixed >= 60) return { interval: fixed, reason: 'env LEERNESS_FIXED_INTERVAL', adaptive: false };
2296
+ return { interval: DEFAULT, reason: 'env override (default)', adaptive: false };
2297
+ }
2298
+ const state = _loadWakeupHistory(root);
2299
+ if (state.override && Number(state.override) >= 60) {
2300
+ return { interval: Number(state.override), reason: 'wakeup-history.json override', adaptive: false };
2301
+ }
2302
+
2303
+ // 2) ํ™œ๋™๋Ÿ‰ ๋ถ„์„ โ€” ์ตœ๊ทผ 2์‹œ๊ฐ„ user-trigger ์ˆ˜
2304
+ const now = Date.now();
2305
+ const recent = state.fires.filter(f => (now - new Date(f.at).getTime()) < 2 * 60 * 60 * 1000);
2306
+ const userTriggers = recent.filter(f => f.kind === 'user-trigger').length;
2307
+ const totalRecent = recent.length;
2308
+
2309
+ // 3) ํ‰๊ท  user-trigger gap (๋ถ„)
2310
+ const userFires = state.fires.filter(f => f.kind === 'user-trigger').slice(-5);
2311
+ let avgGapMin = null;
2312
+ if (userFires.length >= 2) {
2313
+ let totalGap = 0;
2314
+ for (let i = 1; i < userFires.length; i++) {
2315
+ totalGap += new Date(userFires[i].at).getTime() - new Date(userFires[i - 1].at).getTime();
2316
+ }
2317
+ avgGapMin = Math.floor((totalGap / (userFires.length - 1)) / 60000);
2318
+ }
2319
+
2320
+ // 4) pre-wake critical signal (1.9.209)
2321
+ let criticalSignal = false;
2322
+ try {
2323
+ const pw = _loadPreWakeReport(root);
2324
+ if (pw.reports.length > 0) {
2325
+ const latest = pw.reports[pw.reports.length - 1];
2326
+ if (latest.summary && latest.summary.criticalCount > 0) criticalSignal = true;
2327
+ }
2328
+ } catch {}
2329
+
2330
+ // 5) ๊ณ„์‚ฐ
2331
+ let interval = DEFAULT;
2332
+ const reasons = [];
2333
+ if (userTriggers >= 3) {
2334
+ interval = Math.max(MIN, 900); // 15min โ€” ํ™œ๋ฐœํ•œ ์‚ฌ์šฉ์ž ํ™œ๋™
2335
+ reasons.push(`user-trigger ${userTriggers}ํšŒ (2h)`);
2336
+ } else if (userTriggers === 0 && totalRecent <= 1) {
2337
+ interval = Math.min(MAX, 2100); // 35min โ€” ํ•œ์‚ฐ
2338
+ reasons.push('user-trigger ์—†์Œ (2h)');
2339
+ } else {
2340
+ interval = DEFAULT;
2341
+ reasons.push('default 25min');
2342
+ }
2343
+ // avgGap ๊ธฐ๋ฐ˜ ๋ฏธ์„ธ ์กฐ์ •
2344
+ if (avgGapMin !== null && avgGapMin < 20) {
2345
+ interval = Math.max(MIN, Math.min(interval, avgGapMin * 60));
2346
+ reasons.push(`avg user gap ${avgGapMin}min`);
2347
+ }
2348
+ // critical ์‹ ํ˜ธ ์‹œ ์งง๊ฒŒ
2349
+ if (criticalSignal && interval > 1200) {
2350
+ interval = 1200; // 20min
2351
+ reasons.push('pre-wake critical detected');
2352
+ }
2353
+
2354
+ return {
2355
+ interval,
2356
+ reason: reasons.join(' + '),
2357
+ adaptive: true,
2358
+ stats: {
2359
+ recentTotal: totalRecent,
2360
+ userTriggers,
2361
+ avgUserGapMin: avgGapMin,
2362
+ criticalSignal
2363
+ }
2364
+ };
2365
+ }
2366
+
2367
+ // 1.9.211: .harness โ†’ .leerness rename + migration + AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
2368
+ // "leerness์„ค์น˜์‹œ ์ƒ์„ฑ๋˜๋Š” .harness๋ฅผ .leerness์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์„๊นŒ / ๊ธฐ์กด ๋ฒ„์ „์—์„œ๋„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์‹œ AI๊ฐ€ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ธฐ๋„ํ•˜๋ฉด ์–ด๋–จ๊นŒ"
2369
+ // ์ ‘๊ทผ ๋ฐฉ์‹: opt-in ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (breaking change ์ตœ์†Œํ™”)
2370
+ // - default: .harness ์œ ์ง€ (226+ path reference ์•ˆ์ •)
2371
+ // - LEERNESS_WORKSPACE_DIR env ๋˜๋Š” .leerness/MARKER ์กด์žฌ ์‹œ .leerness ์‚ฌ์šฉ
2372
+ // - leerness migrate-workspace-dir CLI: .harness โ†’ .leerness copy + AI reference guide ์ƒ์„ฑ
2373
+ // - AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ: WHERE_TO_FIND.md (๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ + ํŒŒ์ผ๋ณ„ ์—ญํ•  + ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ history)
2374
+ function _workspaceDirName(root) {
2375
+ // 1) env override
2376
+ if (process.env.LEERNESS_WORKSPACE_DIR) {
2377
+ const v = process.env.LEERNESS_WORKSPACE_DIR.trim();
2378
+ if (v) return v;
2379
+ }
2380
+ // 2) .leerness ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋งˆ์ปค ์กด์žฌ ์‹œ .leerness ์šฐ์„ 
2381
+ try {
2382
+ if (root && exists(path.join(root, '.leerness', 'MIGRATED_FROM_HARNESS'))) {
2383
+ return '.leerness';
2384
+ }
2385
+ } catch {}
2386
+ // 3) default
2387
+ return '.harness';
2388
+ }
2389
+ function _workspaceDirAbs(root) {
2390
+ return path.join(root, _workspaceDirName(root));
2391
+ }
2392
+ // .harness โ†’ .leerness ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (copy + reference guide ์ƒ์„ฑ)
2393
+ function _migrateWorkspaceDir(root, opts = {}) {
2394
+ const dryRun = opts.dryRun === true;
2395
+ const srcDir = path.join(root, '.harness');
2396
+ const dstDir = path.join(root, '.leerness');
2397
+ const report = {
2398
+ at: new Date().toISOString(),
2399
+ version: VERSION,
2400
+ src: srcDir,
2401
+ dst: dstDir,
2402
+ srcExists: exists(srcDir),
2403
+ dstExists: exists(dstDir),
2404
+ copiedFiles: [],
2405
+ skippedFiles: [],
2406
+ errors: [],
2407
+ dryRun
2408
+ };
2409
+ if (!report.srcExists) {
2410
+ report.errors.push('source .harness not found');
2411
+ return report;
2412
+ }
2413
+ if (!dryRun) {
2414
+ mkdirp(dstDir);
2415
+ }
2416
+ // ์žฌ๊ท€ copy ํ—ฌํผ
2417
+ function copyRec(rel) {
2418
+ const srcAbs = path.join(srcDir, rel);
2419
+ const dstAbs = path.join(dstDir, rel);
2420
+ let stat;
2421
+ try { stat = fs.statSync(srcAbs); } catch (e) { report.errors.push(`stat ${rel}: ${e.message}`); return; }
2422
+ if (stat.isDirectory()) {
2423
+ if (!dryRun) mkdirp(dstAbs);
2424
+ let entries;
2425
+ try { entries = fs.readdirSync(srcAbs); } catch (e) { report.errors.push(`readdir ${rel}: ${e.message}`); return; }
2426
+ for (const e of entries) copyRec(path.join(rel, e));
2427
+ } else if (stat.isFile()) {
2428
+ // ๊ธฐ์กด dst ํŒŒ์ผ์ด ์žˆ๊ณ  ์šฐ๋ฆฌ๊ฐ€ force๊ฐ€ ์•„๋‹ˆ๋ฉด skip
2429
+ if (exists(dstAbs) && !opts.force) {
2430
+ report.skippedFiles.push(rel);
2431
+ return;
2432
+ }
2433
+ if (!dryRun) {
2434
+ try {
2435
+ fs.copyFileSync(srcAbs, dstAbs);
2436
+ report.copiedFiles.push(rel);
2437
+ } catch (e) {
2438
+ report.errors.push(`copy ${rel}: ${e.message}`);
2439
+ }
2440
+ } else {
2441
+ report.copiedFiles.push(rel);
2442
+ }
2443
+ }
2444
+ }
2445
+ copyRec('');
2446
+ // ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋งˆ์ปค + AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ ์ž‘์„ฑ
2447
+ if (!dryRun) {
2448
+ const marker = `Migrated from .harness at ${report.at} by leerness ${VERSION}\n`;
2449
+ try { writeUtf8(path.join(dstDir, 'MIGRATED_FROM_HARNESS'), marker); } catch (e) { report.errors.push(`marker: ${e.message}`); }
2450
+ const guide = _buildWorkspaceReferenceGuide(root, '.leerness');
2451
+ try { writeUtf8(path.join(dstDir, 'WHERE_TO_FIND.md'), guide); } catch (e) { report.errors.push(`guide: ${e.message}`); }
2452
+ // .harness ์—๋„ redirect ์•ˆ๋‚ด ์ถ”๊ฐ€ (AI๊ฐ€ .harness ๋จผ์ € ๋ด๋„ .leerness ๋กœ ๊ฐ€๊ฒŒ)
2453
+ try {
2454
+ const redirect = `# .harness โ†’ .leerness migration (${report.at})\n\n` +
2455
+ `์ด ์›Œํฌ์ŠคํŽ˜์ด์Šค๋Š” \`.harness\` โ†’ \`.leerness\` ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋˜์—ˆ์Šต๋‹ˆ๋‹ค (leerness ${VERSION}).\n\n` +
2456
+ `**AI ์—์ด์ „ํŠธ๋Š” ${'.leerness/'} ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์šฐ์„  ์ฐธ์กฐํ•˜์‹ญ์‹œ์˜ค.**\n\n` +
2457
+ `- ์‹ ๊ทœ ๋ฉ”ํƒ€ํŒŒ์ผ ์œ„์น˜: \`.leerness/\`\n` +
2458
+ `- ์ฐธ์กฐ ๊ฐ€์ด๋“œ: \`.leerness/WHERE_TO_FIND.md\`\n` +
2459
+ `- ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋งˆ์ปค: \`.leerness/MIGRATED_FROM_HARNESS\`\n\n` +
2460
+ `\`.harness/\` ๋Š” backward compatibility ๋ฅผ ์œ„ํ•ด ๋ณด์กด๋ฉ๋‹ˆ๋‹ค (read-only ๊ถŒ์žฅ).\n`;
2461
+ writeUtf8(path.join(srcDir, 'MIGRATED_TO_LEERNESS.md'), redirect);
2462
+ } catch (e) { report.errors.push(`redirect: ${e.message}`); }
2463
+ }
2464
+ return report;
2465
+ }
2466
+ // AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ ๋นŒ๋” โ€” ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ + ํŒŒ์ผ๋ณ„ ์—ญํ•  ๋งคํ•‘
2467
+ function _buildWorkspaceReferenceGuide(root, dirName) {
2468
+ const lines = [];
2469
+ lines.push(`# Leerness Workspace Reference Guide`);
2470
+ lines.push('');
2471
+ lines.push(`> AI ์—์ด์ „ํŠธ๊ฐ€ leerness ์›Œํฌ์ŠคํŽ˜์ด์Šค์—์„œ ์–ด๋–ค ํŒŒ์ผ์„ ์–ด๋””์„œ ์ฐพ๋Š”์ง€ ์•ˆ๋‚ดํ•ฉ๋‹ˆ๋‹ค (1.9.211).`);
2472
+ lines.push('');
2473
+ lines.push(`Generated: ${new Date().toISOString()} by leerness ${VERSION}`);
2474
+ lines.push(`Workspace dir: \`${dirName}/\``);
2475
+ lines.push('');
2476
+ lines.push(`## ๐Ÿ“ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ (ํ•ต์‹ฌ)`);
2477
+ lines.push('');
2478
+ lines.push('```');
2479
+ lines.push(`${dirName}/`);
2480
+ lines.push(`โ”œโ”€โ”€ plan.md โ† ๋ฌด์—‡์„ ํ•  ๊ฒƒ์ธ๊ฐ€ (์‚ฌ์šฉ์ž ๋ฉ”๋ชจ๋ฆฌ)`);
2481
+ lines.push(`โ”œโ”€โ”€ progress-tracker.md โ† ๋ฌด์—‡์„ ํ–ˆ๋Š”๊ฐ€ (์ฆ๊ฑฐ ํฌํ•จ, ์‚ฌ์šฉ์ž ๋ฉ”๋ชจ๋ฆฌ)`);
2482
+ lines.push(`โ”œโ”€โ”€ decisions.md โ† ์™œ ๊ทธ๋ ‡๊ฒŒ ํ–ˆ๋Š”๊ฐ€ (์‚ฌ์šฉ์ž ๋ฉ”๋ชจ๋ฆฌ)`);
2483
+ lines.push(`โ”œโ”€โ”€ session-handoff.md โ† ๋‹ค์Œ ์„ธ์…˜ ์ธ๊ณ„ (์‚ฌ์šฉ์ž ๋ฉ”๋ชจ๋ฆฌ)`);
2484
+ lines.push(`โ”œโ”€โ”€ lessons.md โ† ๊ณผ๊ฑฐ ๊ตํ›ˆ (์ž๋™ fuzzy ํšŒ์ˆ˜)`);
2485
+ lines.push(`โ”œโ”€โ”€ rules.md โ† ์ž์—ฐ์–ด ๋ฃฐ (๋งค ์„ธ์…˜ ์ž๋™ ๋…ธ์ถœ, R-XXXX)`);
2486
+ lines.push(`โ”œโ”€โ”€ task-log.md โ† in-progress / dropped task ์ด๋ ฅ`);
2487
+ lines.push(`โ”œโ”€โ”€ reuse-map.md โ† ์›Œํฌ์ŠคํŽ˜์ด์Šค capability ๋งคํ•‘`);
2488
+ lines.push(`โ”œโ”€โ”€ skill-suggestions.md โ† skill rolling history`);
2489
+ lines.push(`โ”œโ”€โ”€ feature-graph.md โ† ๊ธฐ๋Šฅ ์˜์กด ๊ทธ๋ž˜ํ”„ (F-XXXX)`);
2490
+ lines.push(`โ”œโ”€โ”€ manifest.json โ† ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋ฉ”ํƒ€`);
2491
+ lines.push(`โ”œโ”€โ”€ leerness-config.json โ† ๋น„์‹œํฌ๋ฆฟ LEERNESS_* ์„ค์ • (1.9.187, AI ๊ฐ€์‹œ)`);
2492
+ lines.push(`โ”œโ”€โ”€ user-requests.json โ† ์‚ฌ์šฉ์ž ๋ช…์‹œ ์š”์ฒญ ๋ˆ„์  (1.9.207)`);
2493
+ lines.push(`โ”œโ”€โ”€ active-wakeups.json โ† ScheduleWakeup ์ƒํƒœ (1.9.205)`);
2494
+ lines.push(`โ”œโ”€โ”€ pre-wake-report.json โ† sleep ์ „ sub-agent audit (1.9.209)`);
2495
+ lines.push(`โ”œโ”€โ”€ wakeup-history.json โ† adaptive wakeup ์ด๋ ฅ (1.9.210)`);
2496
+ lines.push(`โ”œโ”€โ”€ platform-constraints.json โ† API ์ œ์•ฝ catalog (1.9.208)`);
2497
+ lines.push(`โ”œโ”€โ”€ auto-resume-plan.json โ† ๋‹ค์Œ ๋ผ์šด๋“œ plan (1.9.203)`);
2498
+ lines.push(`โ”œโ”€โ”€ next-action-queue.json โ† ๋‹ค์Œ next-action ํ (1.9.201)`);
2499
+ lines.push(`โ”œโ”€โ”€ last-handoff.json โ† ๋งˆ์ง€๋ง‰ handoff timestamp`);
2500
+ lines.push(`โ”œโ”€โ”€ environment.json โ† ํ™˜๊ฒฝ ๋ณ€๋™ ์ถ”์  (1.9.145)`);
2501
+ lines.push(`โ”œโ”€โ”€ skills/ โ† ์„ค์น˜๋œ skill ๋””๋ ‰ํ† ๋ฆฌ`);
2502
+ lines.push(`โ””โ”€โ”€ templates/ โ† ์›Œํฌ์ŠคํŽ˜์ด์Šค ํ…œํ”Œ๋ฆฟ`);
2503
+ lines.push('```');
2504
+ lines.push('');
2505
+ lines.push(`## ๐Ÿงญ ์ž์ฃผ ๋ฌป๋Š” ์œ„์น˜`);
2506
+ lines.push('');
2507
+ lines.push(`| ์ฐพ๋Š” ๊ฒƒ | ์œ„์น˜ |`);
2508
+ lines.push(`|---|---|`);
2509
+ lines.push(`| ํ˜„์žฌ ์ง„ํ–‰ ์ค‘์ธ task | \`${dirName}/progress-tracker.md\` (status: in-progress) |`);
2510
+ lines.push(`| ์‚ฌ์šฉ์ž๊ฐ€ ๋ช…์‹œํ•œ ์˜๊ตฌ ๋ฃฐ | \`${dirName}/rules.md\` (active R-XXXX) |`);
2511
+ lines.push(`| ์ง์ „ sleep ์ „ audit ๊ฒฐ๊ณผ | \`${dirName}/pre-wake-report.json\` (1.9.209) |`);
2512
+ lines.push(`| ๋ฏธ๋‹ต ์‚ฌ์šฉ์ž ์š”์ฒญ | \`${dirName}/user-requests.json\` (status: open) |`);
2513
+ lines.push(`| ๋‹ค์Œ ๋ผ์šด๋“œ ๊ถŒ์žฅ ๋‹จ๊ณ„ | \`${dirName}/auto-resume-plan.json\` (1.9.203) |`);
2514
+ lines.push(`| API ์ œ์•ฝ catalog | \`${dirName}/platform-constraints.json\` (1.9.208) |`);
2515
+ lines.push(`| ์ž๋™ wakeup ๊ถŒ์žฅ ๊ฐ„๊ฒฉ | \`${dirName}/wakeup-history.json\` (1.9.210) |`);
2516
+ lines.push('');
2517
+ lines.push(`## ๐Ÿ”„ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์•ˆ๋‚ด`);
2518
+ lines.push('');
2519
+ lines.push(`์ด ์›Œํฌ์ŠคํŽ˜์ด์Šค๋Š” \`.harness\` โ†’ \`.leerness\` ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜๋˜์—ˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.`);
2520
+ lines.push(`- \`.leerness/MIGRATED_FROM_HARNESS\` ์กด์žฌ โ†’ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒ, \`.leerness\` ์šฐ์„  ์‚ฌ์šฉ`);
2521
+ lines.push(`- \`.harness/MIGRATED_TO_LEERNESS.md\` ์กด์žฌ โ†’ \`.leerness/\` ๋กœ ๊ฐ€์•ผ ํ•จ`);
2522
+ lines.push(`- ์–‘์ชฝ ๋ชจ๋‘ ์—†์Œ โ†’ ๊ธฐ๋ณธ \`.harness\` ์‚ฌ์šฉ ์ค‘`);
2523
+ lines.push('');
2524
+ lines.push(`AI ์—์ด์ „ํŠธ๋Š” \`leerness handoff .\` ๊ฒฐ๊ณผ๋ฅผ ์‹ ๋ขฐํ•˜์‹ญ์‹œ์˜ค โ€” ์ž๋™์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.`);
2525
+ lines.push('');
2526
+ return lines.join('\n');
2527
+ }
2528
+
2254
2529
  // 1.9.203: ์ž๋™ ๋ผ์šด๋“œ plan ์ •๋ฆฌ โ€” ์‚ฌ์šฉ์ž ๋ช…์‹œ
2255
2530
  // "๋ฐฑ๊ทธ๋ผ์šด๋“œ์— ๋‹ค์Œ ์ž‘์—…์ด ์‹œ์ž‘๊ฐ€๋Šฅํ•œ ์˜ˆ์ƒ ์‹œ๊ฐ„ + ์ผ์–ด๋‚ฌ์„๋•Œ ํ•ด์•ผํ•˜๋Š” ์ผ์„ ์ •๋ฆฌ"
2256
2531
  // ๋ผ์šด๋“œ ๋งˆ๋ฌด๋ฆฌ ์‹œ .harness/auto-resume-plan.json ์ž๋™ ์ €์žฅ โ†’ ๋‹ค์Œ wakeup ์‹œ ์ฆ‰์‹œ ์‹คํ–‰ ๊ฐ€๋Šฅ
@@ -2661,6 +2936,199 @@ function preWakeAuditCmd(root, sub) {
2661
2936
  log(dim(` โ†’ ๊นจ์–ด๋‚œ ํ›„: leerness pre-wake-audit --last`));
2662
2937
  }
2663
2938
 
2939
+ // 1.9.210: leerness wakeup-interval <get|set|auto|history|record> โ€” adaptive wakeup interval (์‚ฌ์šฉ์ž ๋ช…์‹œ)
2940
+ // "wakeup 30๋ถ„๋“ฑ ๊ธด ์‹œ๊ฐ„์ด ํ•„์š”์—†๋‹ค๋ฉด ๊ฐ„๊ฒฉ์„ ํƒ€์ดํŠธํ•˜๊ฒŒ ์ž๋™์œผ๋กœ ์กฐ์ ˆํ•˜๋„๋ก"
2941
+ // get โ†’ ํ˜„์žฌ ๊ถŒ์žฅ interval + ๋ถ„์„ stats (--json ๊ฐ€๋Šฅ)
2942
+ // set N โ†’ wakeup-history.json override ์„ค์ • (์ดˆ)
2943
+ // auto โ†’ override ํ•ด์ œ (์ž๋™ ๊ณ„์‚ฐ ๋ชจ๋“œ ๋ณต๊ท€)
2944
+ // history โ†’ ์ตœ๊ทผ fire ์ด๋ ฅ ์ถœ๋ ฅ
2945
+ // record <kind> โ†’ fire ๊ธฐ๋ก (user-trigger / auto / wakeup-miss)
2946
+ function wakeupIntervalCmd(root, sub, val) {
2947
+ root = absRoot(root);
2948
+ const isTty = process.stdout && process.stdout.isTTY;
2949
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2950
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2951
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2952
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2953
+
2954
+ if (!sub || sub === 'help' || sub === '--help') {
2955
+ log(`# leerness wakeup-interval (1.9.210) โ€” adaptive wakeup ๊ฐ„๊ฒฉ`);
2956
+ log('');
2957
+ log(` get โ†’ ํ˜„์žฌ ๊ถŒ์žฅ interval + stats (--json ๊ฐ€๋Šฅ)`);
2958
+ log(` set <secs> โ†’ override ์„ค์ • (์˜ˆ: leerness wakeup-interval set 900)`);
2959
+ log(` auto โ†’ override ํ•ด์ œ + ์ž๋™ ๊ณ„์‚ฐ ๋ชจ๋“œ`);
2960
+ log(` history โ†’ ์ตœ๊ทผ fire ์ด๋ ฅ ์ถœ๋ ฅ`);
2961
+ log(` record <kind> โ†’ fire ๊ธฐ๋ก (user-trigger / auto / wakeup-miss)`);
2962
+ log('');
2963
+ log(dim(` opt-out: env LEERNESS_FIXED_INTERVAL=1500 (์ดˆ) ๋˜๋Š” set <secs>`));
2964
+ return;
2965
+ }
2966
+
2967
+ if (sub === 'get') {
2968
+ const result = _computeAdaptiveInterval(root);
2969
+ if (has('--json')) { log(JSON.stringify(result, null, 2)); return; }
2970
+ log(cyan(`# leerness wakeup-interval get (1.9.210)`));
2971
+ log(` ๐Ÿ“Š ๊ถŒ์žฅ interval: ${grn(`${result.interval}s (${Math.round(result.interval/60)}min)`)}`);
2972
+ log(` mode: ${result.adaptive ? grn('adaptive') : yel('fixed')}`);
2973
+ log(` reason: ${result.reason}`);
2974
+ if (result.stats) {
2975
+ log('');
2976
+ log(` ๐Ÿ“ˆ stats (recent 2h):`);
2977
+ log(` โ€ข total fires: ${result.stats.recentTotal}`);
2978
+ log(` โ€ข user triggers: ${result.stats.userTriggers}`);
2979
+ if (result.stats.avgUserGapMin !== null) log(` โ€ข avg user gap: ${result.stats.avgUserGapMin}min`);
2980
+ if (result.stats.criticalSignal) log(yel(` โ€ข pre-wake critical ๊ฐ์ง€`));
2981
+ }
2982
+ log('');
2983
+ log(dim(` โ†’ ScheduleWakeup(delaySeconds: ${result.interval}) ๊ถŒ์žฅ`));
2984
+ return;
2985
+ }
2986
+
2987
+ if (sub === 'set') {
2988
+ const n = Number(val);
2989
+ if (!val || isNaN(n) || n < 60) { console.error('Usage: leerness wakeup-interval set <secs โ‰ฅ 60>'); process.exit(1); }
2990
+ const state = _loadWakeupHistory(root);
2991
+ state.override = n;
2992
+ _writeWakeupHistory(root, state);
2993
+ if (has('--json')) { log(JSON.stringify({ override: n }, null, 2)); return; }
2994
+ log(grn(`โœ“ wakeup-interval override ์„ค์ •: ${n}s (${Math.round(n/60)}min)`));
2995
+ log(dim(` ํ•ด์ œ: leerness wakeup-interval auto`));
2996
+ return;
2997
+ }
2998
+
2999
+ if (sub === 'auto') {
3000
+ const state = _loadWakeupHistory(root);
3001
+ state.override = null;
3002
+ _writeWakeupHistory(root, state);
3003
+ const result = _computeAdaptiveInterval(root);
3004
+ if (has('--json')) { log(JSON.stringify({ override: null, current: result }, null, 2)); return; }
3005
+ log(grn(`โœ“ override ํ•ด์ œ โ€” ์ž๋™ ๊ณ„์‚ฐ ๋ชจ๋“œ`));
3006
+ log(` ํ˜„์žฌ ๊ถŒ์žฅ: ${result.interval}s (${Math.round(result.interval/60)}min) โ€” ${result.reason}`);
3007
+ return;
3008
+ }
3009
+
3010
+ if (sub === 'history') {
3011
+ const state = _loadWakeupHistory(root);
3012
+ if (has('--json')) { log(JSON.stringify({ override: state.override, fires: state.fires.slice(-20) }, null, 2)); return; }
3013
+ log(cyan(`# leerness wakeup-interval history (1.9.210)`));
3014
+ log(` total fires: ${state.fires.length}${state.override ? ` ยท override: ${state.override}s` : ''}`);
3015
+ log('');
3016
+ const recent = state.fires.slice(-15);
3017
+ if (recent.length === 0) { log(dim(' (์—†์Œ)')); return; }
3018
+ for (const f of recent) {
3019
+ const icon = f.kind === 'user-trigger' ? '๐Ÿ‘†' : (f.kind === 'wakeup-miss' ? 'โ—' : 'โฐ');
3020
+ log(` ${icon} ${f.at} ${dim(f.kind)}`);
3021
+ }
3022
+ return;
3023
+ }
3024
+
3025
+ if (sub === 'record') {
3026
+ const kind = val || 'auto';
3027
+ const validKinds = ['auto', 'user-trigger', 'wakeup-miss'];
3028
+ if (!validKinds.includes(kind)) { console.error(`kind must be: ${validKinds.join('/')}`); process.exit(1); }
3029
+ _recordWakeupFire(root, kind);
3030
+ if (has('--json')) { log(JSON.stringify({ recorded: kind, at: new Date().toISOString() }, null, 2)); return; }
3031
+ log(grn(`โœ“ wakeup fire ๊ธฐ๋ก: ${kind}`));
3032
+ return;
3033
+ }
3034
+
3035
+ console.error(`Unknown subcommand: ${sub}`);
3036
+ console.error(`Run: leerness wakeup-interval help`);
3037
+ process.exit(1);
3038
+ }
3039
+
3040
+ // 1.9.211: leerness workspace-dir <get|guide> โ€” ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋””๋ ‰ํ† ๋ฆฌ ์ •๋ณด + AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
3041
+ function workspaceDirCmd(root, sub) {
3042
+ root = absRoot(root);
3043
+ const isTty = process.stdout && process.stdout.isTTY;
3044
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3045
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3046
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3047
+
3048
+ if (!sub || sub === 'get') {
3049
+ const dirName = _workspaceDirName(root);
3050
+ const dirAbs = _workspaceDirAbs(root);
3051
+ const hasHarness = exists(path.join(root, '.harness'));
3052
+ const hasLeerness = exists(path.join(root, '.leerness'));
3053
+ const migrated = exists(path.join(root, '.leerness', 'MIGRATED_FROM_HARNESS'));
3054
+ const result = { current: dirName, abs: dirAbs, hasHarness, hasLeerness, migrated };
3055
+ if (has('--json')) { log(JSON.stringify(result, null, 2)); return; }
3056
+ log(cyan(`# leerness workspace-dir (1.9.211)`));
3057
+ log(` ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ: ${grn(dirName + '/')}`);
3058
+ log(` abs: ${dirAbs}`);
3059
+ log(` .harness ์กด์žฌ: ${hasHarness ? 'โœ“' : 'โœ—'}`);
3060
+ log(` .leerness ์กด์žฌ: ${hasLeerness ? 'โœ“' : 'โœ—'}`);
3061
+ log(` ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒ: ${migrated ? 'โœ“' : 'โœ—'}`);
3062
+ log('');
3063
+ if (hasHarness && !hasLeerness) {
3064
+ log(dim(` โ†’ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜: leerness migrate-workspace-dir`));
3065
+ } else if (migrated) {
3066
+ log(dim(` โ†’ AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ: leerness workspace-dir guide`));
3067
+ }
3068
+ return;
3069
+ }
3070
+
3071
+ if (sub === 'guide') {
3072
+ const dirName = _workspaceDirName(root);
3073
+ const guide = _buildWorkspaceReferenceGuide(root, dirName);
3074
+ if (has('--json')) { log(JSON.stringify({ workspaceDir: dirName, guide }, null, 2)); return; }
3075
+ log(guide);
3076
+ return;
3077
+ }
3078
+
3079
+ console.error(`Usage: leerness workspace-dir [get|guide]`);
3080
+ process.exit(1);
3081
+ }
3082
+
3083
+ // 1.9.211: leerness migrate-workspace-dir โ€” .harness โ†’ .leerness ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
3084
+ function migrateWorkspaceDirCmd(root) {
3085
+ root = absRoot(root);
3086
+ const isTty = process.stdout && process.stdout.isTTY;
3087
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3088
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3089
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3090
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
3091
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3092
+
3093
+ const dryRun = has('--dry-run');
3094
+ const force = has('--force');
3095
+ const report = _migrateWorkspaceDir(root, { dryRun, force });
3096
+
3097
+ if (has('--json')) { log(JSON.stringify(report, null, 2)); return; }
3098
+ log(cyan(`# leerness migrate-workspace-dir (1.9.211)${dryRun ? ' [DRY-RUN]' : ''}`));
3099
+ log(` src: ${report.src} ${report.srcExists ? 'โœ“' : red('โœ— ์—†์Œ')}`);
3100
+ log(` dst: ${report.dst} ${report.dstExists ? yel('โš  ์ด๋ฏธ ์กด์žฌ') : '(์ƒ์„ฑ)'}`);
3101
+ log('');
3102
+ log(` ๐Ÿ“Š copied: ${grn(report.copiedFiles.length)}, skipped: ${report.skippedFiles.length}, errors: ${report.errors.length}`);
3103
+ if (report.copiedFiles.length > 0 && report.copiedFiles.length <= 30) {
3104
+ log('');
3105
+ log(` ๋ณต์‚ฌ๋œ ํŒŒ์ผ:`);
3106
+ report.copiedFiles.slice(0, 20).forEach(f => log(dim(` + ${f}`)));
3107
+ if (report.copiedFiles.length > 20) log(dim(` ... +${report.copiedFiles.length - 20}๊ฑด`));
3108
+ } else if (report.copiedFiles.length > 30) {
3109
+ log(dim(` (${report.copiedFiles.length}๊ฐœ โ€” ์ƒ์„ธ๋Š” --json)`));
3110
+ }
3111
+ if (report.skippedFiles.length > 0) {
3112
+ log('');
3113
+ log(yel(` ์Šคํ‚ต๋œ ํŒŒ์ผ (์ด๋ฏธ ์กด์žฌ, --force ๋กœ ๋ฎ์–ด์“ฐ๊ธฐ ๊ฐ€๋Šฅ):`));
3114
+ report.skippedFiles.slice(0, 5).forEach(f => log(dim(` - ${f}`)));
3115
+ if (report.skippedFiles.length > 5) log(dim(` ... +${report.skippedFiles.length - 5}๊ฑด`));
3116
+ }
3117
+ if (report.errors.length > 0) {
3118
+ log('');
3119
+ log(red(` โš  ์—๋Ÿฌ:`));
3120
+ report.errors.forEach(e => log(red(` - ${e}`)));
3121
+ }
3122
+ log('');
3123
+ if (!dryRun && report.copiedFiles.length > 0) {
3124
+ log(grn(`โœ“ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์™„๋ฃŒ โ€” .leerness/WHERE_TO_FIND.md + MIGRATED_FROM_HARNESS ๋งˆ์ปค ์ƒ์„ฑ`));
3125
+ log(dim(` โ†’ ๋‹ค์Œ handoff๋ถ€ํ„ฐ .leerness ์šฐ์„  ์‚ฌ์šฉ`));
3126
+ log(dim(` โ†’ .harness/MIGRATED_TO_LEERNESS.md ์•ˆ๋‚ด ์ถ”๊ฐ€๋จ (backward compat ๋ณด์กด)`));
3127
+ } else if (dryRun) {
3128
+ log(yel(` (dry-run โ€” ์‹ค์ œ ๋ณ€๊ฒฝ ์—†์Œ. ์‹คํ–‰: leerness migrate-workspace-dir)`));
3129
+ }
3130
+ }
3131
+
2664
3132
  // 1.9.199: wakeup miss ์ž๋™ ํšŒ๋ณต ๊ฐ•ํ™” โ€” last-handoff timestamp ๊ธฐ๋ก + 25min ๊ธฐ๋Œ€ interval ์ •๋ฐ€ ์ธก์ •
2665
3133
  // ์‚ฌ์šฉ์ž ๋ช…์‹œ (1.9.198 ํ›„์†): "22:01์— ๋ผ์šด๋“œ์— ์ž๋™์ง„์ž…๋˜์ง€์•Š์•˜์–ด"
2666
3134
  // 1.9.196 task-log mtime ๊ธฐ๋ฐ˜ detector ๋Š” task-log ๊ฐ€ ์ž์ฃผ ์•ˆ ์—…๋ฐ์ดํŠธ๋˜๋ฉด false positive.
@@ -16064,6 +16532,12 @@ async function main() {
16064
16532
  if (cmd === 'constraints') return constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
16065
16533
  // 1.9.209: leerness pre-wake-audit โ€” sleep ์ „ sub-agent audit (์‚ฌ์šฉ์ž ๋ช…์‹œ)
16066
16534
  if (cmd === 'pre-wake-audit') return preWakeAuditCmd(arg('--path', process.cwd()), args[1]);
16535
+ // 1.9.210: leerness wakeup-interval <get|set|auto|history|record> โ€” adaptive interval (์‚ฌ์šฉ์ž ๋ช…์‹œ)
16536
+ if (cmd === 'wakeup-interval') return wakeupIntervalCmd(arg('--path', process.cwd()), args[1], args[2]);
16537
+ // 1.9.211: leerness workspace-dir <get|guide> โ€” ํ˜„์žฌ ์›Œํฌ์ŠคํŽ˜์ด์Šค ๋””๋ ‰ํ† ๋ฆฌ / AI ์ฐธ์กฐ ๊ฐ€์ด๋“œ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
16538
+ if (cmd === 'workspace-dir') return workspaceDirCmd(arg('--path', process.cwd()), args[1]);
16539
+ // 1.9.211: leerness migrate-workspace-dir โ€” .harness โ†’ .leerness ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
16540
+ if (cmd === 'migrate-workspace-dir') return migrateWorkspaceDirCmd(arg('--path', process.cwd()));
16067
16541
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
16068
16542
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
16069
16543
  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.209",
3
+ "version": "1.9.211",
4
4
  "description": "Leerness: ๋น„ํŒŒ๊ดด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ž๋™ ๋ฒ„์ „ ๊ฐ์ง€ยท์—…๋ฐ์ดํŠธ, ๊ณ„ํš/์ง„ํ–‰/ํ•ธ๋“œ์˜คํ”„ ์ž๋™ํ™”, ๊ฒŒ์œผ๋ฆ„ยท์‹œํฌ๋ฆฟยท์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ€๋“œ, Claude Code ์Šฌ๋ž˜์‹œ ํ†ตํ•ฉ์„ ๊ฐ–์ถ˜ ํ•œ๊ตญ์–ด ์šฐ์„  AI ๊ฐœ๋ฐœ ํ•˜๋„ค์Šค.",
5
5
  "keywords": [
6
6
  "leerness",