leerness 1.9.219 โ†’ 1.9.221

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,91 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.221 โ€” 2026-05-22
4
+
5
+ **๐ŸŽ‰ MCP 60 ๋„๊ตฌ ๋งˆ์ผ์Šคํ†ค + abnormalShutdown JSON ํ†ตํ•ฉ (handoff/session close).**
6
+
7
+ ### 1. MCP 60๋ฒˆ์งธ ๋„๊ตฌ โ€” `leerness_session_resume`
8
+ - 1.9.220 ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ ์‹œ์Šคํ…œ์„ ์™ธ๋ถ€ AI์— ๋…ธ์ถœ
9
+ - ์ธ์ž: `{ path? }`
10
+ - ์‘๋‹ต: `{ abnormalShutdown, severity, signals[], resumeGuide[] }`
11
+ - 5์‹ ํ˜ธ ๋ถ„์„ (last-handoff/wakeup-missed/in-progress-stale/auto-resume-plan/release-branch)
12
+
13
+ ### 2. handoff --json `abnormalShutdown` ํ•„๋“œ ์ถ”๊ฐ€
14
+ - `{ detected, severity, signalCount, signals[], resumeGuide[] }`
15
+ - ์™ธ๋ถ€ ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ / CI ๊ฐ€ ๋น„์ •์ƒ ์ข…๋ฃŒ ์ž๋™ ๊ฐ์ง€
16
+ - 1.9.218 ํ†ตํ•ฉ ํŒจํ„ด ํ™•์žฅ (handoff JSON 4์ข… ํ•„๋“œ ์™„์„ฑ: userRequests/preWake/idempotency/abnormalShutdown)
17
+
18
+ ### 3. session close --json ๋™์ผ ํ†ตํ•ฉ
19
+ - ๋งˆ๊ฐ ์‹œ ๋‹ค์Œ ๋ผ์šด๋“œ ์ง„์ž… ์‹œ ์žฌ๊ฐœ ๊ฐ€์ด๋“œ ์ž๋™ ํšŒ์ˆ˜
20
+ - CI ๋งˆ๊ฐ ํ›„ ๋น„์ •์ƒ ์ข…๋ฃŒ detect ๊ฐ€๋Šฅ
21
+
22
+ ### 4. ๋ˆ„์  ํšŒ๊ท€ (1.9.207~220) โ€” ๋ชจ๋‘ ์œ ์ง€
23
+
24
+ ### 5. stress-v166 โ€” 16/16 PASS
25
+ - 1.9.221 (4): VERSION + MCP 60 + session_resume ๋“ฑ๋ก + ์‹ค ํ˜ธ์ถœ (high severity ์‘๋‹ต)
26
+ - abnormalShutdown JSON ํ†ตํ•ฉ (3): handoff/session close + wakeup miss ์‹œ๋‚˜๋ฆฌ์˜ค
27
+ - ์„ฑ๋Šฅ (2): cold_start 374ms / MCP 60 ๋„๊ตฌ 385ms
28
+ - ๋ˆ„์  ํšŒ๊ท€ (7): 1.9.207~220
29
+
30
+ ### 6. ์ž๋™ release (83 ๋ผ์šด๋“œ main-push streak ยท 44 ๋ผ์šด๋“œ npm publish streak)
31
+
32
+ ๐ŸŽ‰ **MCP 60 ๋„๊ตฌ ๋„๋‹ฌ** (53 โ†’ 60, +7: 1.9.168 +3 / 1.9.216 +5 / 1.9.221 +1)
33
+ ๐ŸŽ‰ **handoff/session close JSON 4 ํ†ตํ•ฉ ํ•„๋“œ ์™„์„ฑ** (userRequests/preWake/idempotency/abnormalShutdown)
34
+
35
+ ---
36
+
37
+ ## 1.9.220 โ€” 2026-05-22
38
+
39
+ **๐Ÿ”Œ ๋น„์ •์ƒ ์ข…๋ฃŒ ํ›„ ์ž์œจ ์žฌ๊ฐœ ์‹œ์Šคํ…œ (์‚ฌ์šฉ์ž ๋ช…์‹œ).**
40
+
41
+ ### ์‚ฌ์šฉ์ž ๋ช…์‹œ
42
+ > *"์ž์œจ๋ชจ๋“œ์ผ๋•Œ ์ ˆ์ „๋ชจ๋“œ์˜€๊ฑฐ๋‚˜, ์‹œ์Šคํ…œ์ข…๋ฃŒ๋‚˜ AI์—์ด์ „ํŠธ ์„ธ์…˜์ข…๋ฃŒ๋“ฑ์œผ๋กœ ์ž์œจ๊ฐœ๋ฐœ๋ชจ๋“œ๊ฐ€ ๋น„์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ๋‹ค๊ฐ€ ๋‹ค์‹œ ์‹คํ–‰ํ•˜์—ฌ ์„ธ์…˜์„ ์ด์–ด๊ฐˆ๋•Œ, ๊ฐ์ง€ํ•˜๊ณ  ์ž‘์—…์ด ์ค‘๋‹จ๋œ์ง€์ ์ด๋‚˜ ๋ˆ„๋ฝ๋œ์ž‘์—…์„ ๊ฐ์ง€ํ•˜๊ณ  ๊ฐœ๋ฐœ์„์ด์–ด๊ฐ€๊ฑฐ๋‚˜ ์ž์œจ๋ชจ๋“œ๋ฅผ ๋‹ค์‹œ ์ง„ํ–‰"*
43
+
44
+ ### 1. `_detectAbnormalShutdown(root)` โ€” 5์‹ ํ˜ธ ์ข…ํ•ฉ ๊ฐ์ง€
45
+ | # | ์‹ ํ˜ธ | ํŠธ๋ฆฌ๊ฑฐ | severity |
46
+ |---|---|---|---|
47
+ | 1 | `last-handoff-stale` | gap > 60min (์ •์ƒ 25min ๋Œ€๋น„) | gap > 240min: high / else medium |
48
+ | 2 | `wakeup-missed` | active-wakeups pending โ†’ 30min+ ์ง€๋‚จ | gap > 120min: high |
49
+ | 3 | `in-progress-stale` | in-progress task + progress-tracker 24h+ stale | high |
50
+ | 4 | `auto-resume-plan-unused` | auto-resume-plan 60min+ ๋ฏธ์‚ฌ์šฉ | ageMin > 240: medium |
51
+ | 5 | `release-branch-pending` | 2+ release/* branch (sync-main ๋ˆ„๋ฝ) | low |
52
+
53
+ ### 2. `leerness session-resume` CLI
54
+ - ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + 5์‹ ํ˜ธ ๋ถ„์„
55
+ - ์ž๋™ ์žฌ๊ฐœ ๊ฐ€์ด๋“œ ์ถœ๋ ฅ (7๋‹จ๊ณ„)
56
+ - `--json` ์˜ต์…˜ (์™ธ๋ถ€ ์ž๋™ํ™”)
57
+
58
+ ### 3. handoff ํ—ค๋“œ๋ผ์ธ 16๋ฒˆ์งธ ์š”์†Œ
59
+ - ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ ์‹œ: `๐Ÿ”Œ ๋น„์ •์ƒ์ข…๋ฃŒ <severity> (N์‹ ํ˜ธ)`
60
+ - ํ—ค๋“œ๋ผ์ธ ๋ผ๋ฒจ: `1.9.81/93/113/152/162/192/197/204/207/209/215/220`
61
+
62
+ ### 4. ์žฌ๊ฐœ ๊ฐ€์ด๋“œ 7๋‹จ๊ณ„ (์ž๋™ ์ƒ์„ฑ)
63
+ ์‹ ํ˜ธ๋ณ„ ๋งž์ถค ๊ฐ€์ด๋“œ:
64
+ 1. `leerness handoff .` โ€” ํ˜„์žฌ ์ƒํƒœ ์ข…ํ•ฉ
65
+ 2. `leerness resume` โ€” auto-resume-plan ์ ์šฉ (auto-resume-plan-unused ์‹œ)
66
+ 3. `leerness task list --status in-progress` โ€” stale task ๊ฒ€ํ†  (in-progress-stale ์‹œ)
67
+ 4. ScheduleWakeup ์žฌ๋“ฑ๋ก (wakeup-missed ์‹œ)
68
+ 5. `git branch -a` โ†’ `leerness release sync-main` (release-branch-pending ์‹œ)
69
+ 6. `leerness pre-wake-audit` โ€” ๋ˆ„๋ฝ ์ž‘์—… / ์ถฉ๋Œ ์ ๊ฒ€
70
+ 7. `leerness requests audit` โ€” ๋ฏธ๋‹ต ์‚ฌ์šฉ์ž ์š”์ฒญ ํ™•์ธ
71
+
72
+ ### 5. ์‹ค ๊ฒ€์ฆ ์‹œ๋‚˜๋ฆฌ์˜ค
73
+ - **์ •์ƒ ์›Œํฌ์ŠคํŽ˜์ด์Šค** โ†’ `abnormalShutdown: false / severity: none`
74
+ - **wakeup 2024๋ถ„ ์ง€๋‚จ** (์‹œ๋ฎฌ๋ ˆ์ด์…˜) โ†’ `severity: high / wakeup-missed 1์‹ ํ˜ธ + ๊ฐ€์ด๋“œ 4๋‹จ๊ณ„ ์ž๋™ ์ƒ์„ฑ`
75
+
76
+ ### 6. ๋ˆ„์  ํšŒ๊ท€ (1.9.207~219) โ€” ๋ชจ๋‘ ์œ ์ง€
77
+
78
+ ### 7. stress-v165 โ€” 18/18 PASS
79
+ - 1.9.220 (7): VERSION + 5์‹ ํ˜ธ ๋“ฑ๋ก + ์ •์ƒ/๋น„์ •์ƒ ์‹œ๋‚˜๋ฆฌ์˜ค + handoff ํ†ตํ•ฉ + CLI ๋ผ์šฐํŒ…
80
+ - ์„ฑ๋Šฅ (2): cold_start 461ms / MCP 59 ๋„๊ตฌ 424ms
81
+ - ๋ˆ„์  ํšŒ๊ท€ (9): 1.9.207~219
82
+
83
+ ### 8. ์ž๋™ release (82 ๋ผ์šด๋“œ main-push streak ยท 43 ๋ผ์šด๋“œ npm publish streak)
84
+
85
+ ๐ŸŽ‰ **์ž์œจ ๋ชจ๋“œ ๋ณต์›๋ ฅ ๊ฐ•ํ™”** โ€” ์ ˆ์ „/์ข…๋ฃŒ/์„ธ์…˜์ข…๋ฃŒ ๋ฌด๊ด€, ์žฌ์‹œ์ž‘ ์‹œ ์ž๋™ ์ง„๋‹จ + ์žฌ๊ฐœ ๊ฐ€๋Šฅ
86
+
87
+ ---
88
+
3
89
  ## 1.9.219 โ€” 2026-05-22
4
90
 
5
91
  **๐ŸŽ‰ 80 ๋ผ์šด๋“œ ์ž์œจ ๋ชจ๋“œ ๋งˆ์ผ์Šคํ†ค + session-workflow.md ํ†ตํ•ฉ ๊ฐฑ์‹ .**
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.219-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v164-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-59-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-149-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-81_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/๋ชฉ์ _๋งคํŠธ๋ฆญ์Šค-100%2F100_(5์ถ•)-brightgreen)]() [![milestone](https://img.shields.io/badge/๐ŸŽ‰_milestone-80_๋ผ์šด๋“œ-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-618ms-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.221-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v166-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-60_๐ŸŽ‰-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-151-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-83_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/๋ชฉ์ _๋งคํŠธ๋ฆญ์Šค-100%2F100_(5์ถ•)-brightgreen)]() [![mcp-60](https://img.shields.io/badge/๐ŸŽ‰_MCP-60_๋„๊ตฌ_๋งˆ์ผ์Šคํ†ค-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-374ms-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.219 AI Agent Reliability Harness + Sandbox โ•‘
15
+ โ•‘ v1.9.221 AI Agent Reliability Harness + Sandbox โ•‘
16
16
  โ•‘ verify ยท remember ยท orchestrate ยท audit ยท sandbox ยท drift โ•‘
17
- โ•‘ ๐ŸŽ‰ 80 ๋ผ์šด๋“œ ์ž์œจ ๋งˆ์ผ์Šคํ†ค ยท 5์ถ• 100/100 ยท MCP 59 ยท ๋ฐฑ๋กœ๊ทธ 0 โ•‘
17
+ โ•‘ ๐ŸŽ‰ MCP 60 ๋„๊ตฌ ๋งˆ์ผ์Šคํ†ค ยท handoff JSON 4 ํ†ตํ•ฉ ํ•„๋“œ ์™„์„ฑ โ•‘
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.219';
10
+ const VERSION = '1.9.221';
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,140 @@ function _saveAndAppendPreWakeReport(root, audit) {
2251
2251
  return audit;
2252
2252
  }
2253
2253
 
2254
+ // 1.9.220: ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + ์ž์œจ ์žฌ๊ฐœ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
2255
+ // "์ ˆ์ „๋ชจ๋“œ/์‹œ์Šคํ…œ์ข…๋ฃŒ/AI์—์ด์ „ํŠธ ์„ธ์…˜์ข…๋ฃŒ๋กœ ๋น„์ •์ƒ ์ข…๋ฃŒ๋œ ํ›„ ์žฌ์‹คํ–‰ ์‹œ ์ค‘๋‹จ์ง€์ /๋ˆ„๋ฝ์ž‘์—… ๊ฐ์ง€"
2256
+ // 5์‹ ํ˜ธ ์ข…ํ•ฉ ๋ถ„์„:
2257
+ // 1) last-handoff gap > 60min (1.9.199, 25min interval ๊ธฐ๋Œ€ ๋Œ€๋น„ 2.4๋ฐฐ+)
2258
+ // 2) wakeup miss (active-wakeups pending โ†’ 30min+ ์ง€๋‚จ, 1.9.205)
2259
+ // 3) in-progress task 24h+ ๊ฐฑ์‹  ์—†์Œ (stale, progress-tracker mtime)
2260
+ // 4) auto-resume-plan ์กด์žฌ + ageMin > 60min (1.9.203, ์‚ฌ์šฉ ์•ˆ ๋จ)
2261
+ // 5) release/X.X.X branch ์žˆ์Œ + main ๋ฏธ๋จธ์ง€ (release sync-main ๋ˆ„๋ฝ)
2262
+ // ๊ฒฐ๊ณผ: { abnormalShutdown: true/false, signals: [...], severity, resumeGuide }
2263
+ function _detectAbnormalShutdown(root) {
2264
+ const result = {
2265
+ detectedAt: new Date().toISOString(),
2266
+ signals: [],
2267
+ severity: 'none',
2268
+ abnormalShutdown: false,
2269
+ resumeGuide: []
2270
+ };
2271
+ const now = Date.now();
2272
+
2273
+ // 1) last-handoff gap > 60min (์ •์ƒ 25min interval ๋Œ€๋น„)
2274
+ try {
2275
+ const lhFp = path.join(root, '.harness', 'last-handoff.json');
2276
+ if (exists(lhFp)) {
2277
+ const j = JSON.parse(read(lhFp));
2278
+ const last = j.lastAt || (j.history && j.history.length > 0 ? j.history[j.history.length - 1] : null);
2279
+ if (last) {
2280
+ const lastMs = new Date(last).getTime();
2281
+ const gapMin = Math.floor((now - lastMs) / 60000);
2282
+ if (gapMin > 60) {
2283
+ result.signals.push({
2284
+ kind: 'last-handoff-stale',
2285
+ gapMin,
2286
+ detail: `last handoff ${gapMin}๋ถ„ ์ „ (์ •์ƒ 25min ๊ฐ„๊ฒฉ ๋Œ€๋น„ ${Math.round(gapMin/25)}x ์ดˆ๊ณผ) โ€” ์ ˆ์ „/์ข…๋ฃŒ ์˜์‹ฌ`,
2287
+ severity: gapMin > 240 ? 'high' : 'medium'
2288
+ });
2289
+ }
2290
+ }
2291
+ }
2292
+ } catch {}
2293
+
2294
+ // 2) wakeup miss (active-wakeups pending โ†’ 30min+ ์ง€๋‚จ)
2295
+ try {
2296
+ const wkStat = _analyzeWakeupStatus(root);
2297
+ if (wkStat.hasWakeup && wkStat.deltaMin > 30 && wkStat.kind === 'missed') {
2298
+ result.signals.push({
2299
+ kind: 'wakeup-missed',
2300
+ gapMin: wkStat.deltaMin,
2301
+ detail: `wakeup ${wkStat.deltaMin}๋ถ„ ์ง€๋‚จ (status=pending) โ€” ์ž๋™ ์žฌ๊ฐœ ์‹คํŒจ`,
2302
+ severity: wkStat.deltaMin > 120 ? 'high' : 'medium'
2303
+ });
2304
+ }
2305
+ } catch {}
2306
+
2307
+ // 3) in-progress task 24h+ stale (progress-tracker mtime + ํ™œ์„ฑ task ์นด์šดํŠธ)
2308
+ try {
2309
+ const ptPath = path.join(root, '.harness', 'progress-tracker.md');
2310
+ if (exists(ptPath)) {
2311
+ const stat = fs.statSync(ptPath);
2312
+ const ageMin = Math.floor((now - stat.mtimeMs) / 60000);
2313
+ const content = read(ptPath);
2314
+ const inProgress = (content.match(/^\|\s*T-\d{4,}\s*\|\s*in-progress\s*\|/gm) || []).length;
2315
+ if (inProgress > 0 && ageMin > 24 * 60) {
2316
+ result.signals.push({
2317
+ kind: 'in-progress-stale',
2318
+ inProgressCount: inProgress,
2319
+ ageMin,
2320
+ detail: `${inProgress}๊ฑด in-progress task, progress-tracker ${Math.floor(ageMin/60)}h ๊ฐฑ์‹  ์—†์Œ`,
2321
+ severity: 'high'
2322
+ });
2323
+ }
2324
+ }
2325
+ } catch {}
2326
+
2327
+ // 4) auto-resume-plan ์กด์žฌ + ageMin > 60min (์‚ฌ์šฉ ์•ˆ ๋จ)
2328
+ try {
2329
+ const plan = _loadAutoResumePlan(root);
2330
+ if (plan && plan.ageMin > 60) {
2331
+ result.signals.push({
2332
+ kind: 'auto-resume-plan-unused',
2333
+ ageMin: plan.ageMin,
2334
+ nextRound: plan.nextRoundVersion,
2335
+ detail: `auto-resume-plan ${plan.ageMin}๋ถ„ ์ „ ์ž‘์„ฑ (${plan.nextRoundVersion}) โ€” ์‚ฌ์šฉ ์•ˆ ๋จ`,
2336
+ severity: plan.ageMin > 240 ? 'medium' : 'low'
2337
+ });
2338
+ }
2339
+ } catch {}
2340
+
2341
+ // 5) release/X.X.X branch ์žˆ์Œ + main ๋ฏธ๋จธ์ง€ (git ์ƒํƒœ)
2342
+ try {
2343
+ const r = cp.spawnSync('git', ['branch', '--list', 'release/*'], { cwd: root, encoding: 'utf8', timeout: 3000 });
2344
+ if (r.status === 0) {
2345
+ const branches = (r.stdout || '').split('\n').map(s => s.trim().replace(/^\*?\s*/, '')).filter(b => b.startsWith('release/'));
2346
+ if (branches.length >= 2) { // 2๊ฐœ ์ด์ƒ์ด๋ฉด ๋ˆ„๋ฝ ๊ฐ€๋Šฅ์„ฑ
2347
+ result.signals.push({
2348
+ kind: 'release-branch-pending',
2349
+ count: branches.length,
2350
+ recent: branches.slice(-3),
2351
+ detail: `${branches.length}๊ฑด release/* branch (sync-main ๋ˆ„๋ฝ ๊ฐ€๋Šฅ)`,
2352
+ severity: 'low'
2353
+ });
2354
+ }
2355
+ }
2356
+ } catch {}
2357
+
2358
+ // ์ข…ํ•ฉ ํŒ๋‹จ
2359
+ const high = result.signals.filter(s => s.severity === 'high').length;
2360
+ const medium = result.signals.filter(s => s.severity === 'medium').length;
2361
+ if (high > 0) { result.severity = 'high'; result.abnormalShutdown = true; }
2362
+ else if (medium > 0) { result.severity = 'medium'; result.abnormalShutdown = true; }
2363
+ else if (result.signals.length > 0) result.severity = 'low';
2364
+
2365
+ // ์žฌ๊ฐœ ๊ฐ€์ด๋“œ
2366
+ if (result.abnormalShutdown) {
2367
+ result.resumeGuide.push('1. leerness handoff . โ€” ํ˜„์žฌ ์ƒํƒœ ์ข…ํ•ฉ ํ™•์ธ');
2368
+ if (result.signals.some(s => s.kind === 'auto-resume-plan-unused')) {
2369
+ result.resumeGuide.push('2. leerness resume โ€” ์ €์žฅ๋œ auto-resume-plan ์ ์šฉ (1.9.203)');
2370
+ }
2371
+ if (result.signals.some(s => s.kind === 'in-progress-stale')) {
2372
+ result.resumeGuide.push('3. leerness task list --status in-progress โ€” stale task ๊ฒ€ํ†  (drop or ์žฌ๊ฐœ)');
2373
+ }
2374
+ if (result.signals.some(s => s.kind === 'wakeup-missed')) {
2375
+ result.resumeGuide.push('4. ScheduleWakeup ์žฌ๋“ฑ๋ก (์‚ฌ์šฉ์ž ํŠธ๋ฆฌ๊ฑฐ ๋˜๋Š” R-0001 ๋ฃฐ)');
2376
+ }
2377
+ if (result.signals.some(s => s.kind === 'release-branch-pending')) {
2378
+ result.resumeGuide.push('5. git branch -a๋กœ release/* ํ™•์ธ + leerness release sync-main ์žฌ์‹คํ–‰');
2379
+ }
2380
+ result.resumeGuide.push('6. leerness pre-wake-audit โ€” ๋ˆ„๋ฝ ์ž‘์—… / ์ถฉ๋Œ ์ข…ํ•ฉ ์ ๊ฒ€');
2381
+ result.resumeGuide.push('7. leerness requests audit โ€” ๋ฏธ๋‹ต ์‚ฌ์šฉ์ž ์š”์ฒญ ํ™•์ธ');
2382
+ } else {
2383
+ result.resumeGuide.push('์ •์ƒ ์šด์˜ ์ค‘ โ€” ๋น„์ •์ƒ ์ข…๋ฃŒ ์‹ ํ˜ธ ์—†์Œ');
2384
+ }
2385
+ return result;
2386
+ }
2387
+
2254
2388
  // 1.9.210: adaptive wakeup interval (์‚ฌ์šฉ์ž ๋ช…์‹œ)
2255
2389
  // "wakeup 30๋ถ„๋“ฑ ๊ธด ์‹œ๊ฐ„์ด ํ•„์š”์—†๋‹ค๋ฉด ๊ฐ„๊ฒฉ์„ ํƒ€์ดํŠธํ•˜๊ฒŒ ์ž๋™์œผ๋กœ ์กฐ์ ˆํ•˜๋„๋ก"
2256
2390
  // ์‚ฌ์šฉ์ž ํ™œ๋™๋Ÿ‰ ๊ธฐ๋ฐ˜ ๊ถŒ์žฅ interval ๊ณ„์‚ฐ โ†’ ScheduleWakeup ์‹œ ์ž๋™ ์ ์šฉ (R-0001 ๋ฃฐ ์™ธ ๋™์  ๋ณด์ •)
@@ -2920,6 +3054,47 @@ function requestsCmd(root, sub, ...rest) {
2920
3054
  process.exit(1);
2921
3055
  }
2922
3056
 
3057
+ // 1.9.220: leerness session-resume โ€” ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + ์ž์œจ ์žฌ๊ฐœ ๊ฐ€์ด๋“œ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
3058
+ function sessionResumeCmd(root) {
3059
+ root = absRoot(root);
3060
+ const isTty = process.stdout && process.stdout.isTTY;
3061
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
3062
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
3063
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
3064
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
3065
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
3066
+
3067
+ const result = _detectAbnormalShutdown(root);
3068
+ if (has('--json')) { log(JSON.stringify(result, null, 2)); return; }
3069
+ log(cyan(`# leerness session-resume (1.9.220) โ€” ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + ์ž์œจ ์žฌ๊ฐœ`));
3070
+ log(` detected at: ${result.detectedAt}`);
3071
+ log('');
3072
+ if (!result.abnormalShutdown) {
3073
+ log(grn(` โœ“ ๋น„์ •์ƒ ์ข…๋ฃŒ ์‹ ํ˜ธ ์—†์Œ โ€” ์ •์ƒ ์šด์˜ ์ค‘`));
3074
+ if (result.signals.length > 0) {
3075
+ log(dim(` (low-severity ์‹ ํ˜ธ ${result.signals.length}๊ฑด โ€” ๋ฌด์‹œ ๊ฐ€๋Šฅ)`));
3076
+ }
3077
+ log('');
3078
+ log(dim(` โ†’ ํ‰์†Œ๋Œ€๋กœ leerness handoff . ๋กœ ์‹œ์ž‘`));
3079
+ return;
3080
+ }
3081
+ const sevIcon = result.severity === 'high' ? '๐Ÿšจ' : 'โš ';
3082
+ const sevColor = result.severity === 'high' ? red : yel;
3083
+ log(sevColor(` ${sevIcon} ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ โ€” severity: ${result.severity} (signals: ${result.signals.length})`));
3084
+ log('');
3085
+ log(`## ๊ฐ์ง€ ์‹ ํ˜ธ`);
3086
+ for (const s of result.signals) {
3087
+ const icon = s.severity === 'high' ? '๐Ÿšจ' : (s.severity === 'medium' ? 'โš ' : 'โ„น');
3088
+ const c = s.severity === 'high' ? red : (s.severity === 'medium' ? yel : dim);
3089
+ log(c(` ${icon} [${s.kind}] ${s.detail}`));
3090
+ }
3091
+ log('');
3092
+ log(`## ์žฌ๊ฐœ ๊ฐ€์ด๋“œ`);
3093
+ for (const g of result.resumeGuide) {
3094
+ log(` ${g}`);
3095
+ }
3096
+ }
3097
+
2923
3098
  // 1.9.208: leerness constraints <list|check|add> โ€” ํ”Œ๋žซํผ/API ์ œ์•ฝ ์‚ฌ์ „ ์ฒดํฌ CLI (์‚ฌ์šฉ์ž ๋ช…์‹œ)
2924
3099
  // "ํŠน์ • ํ”Œ๋žซํผ์˜ API ... ํ˜ธ์ถœ์†๋„๊ฐ€ ์ดˆ๋‹น 5ํšŒ ์ดํ•˜๋กœ๋งŒ ํ•ด์•ผํ•œ๋‹ค๋Š” ๊ทœ์ •์„ ๋จผ์ € ํ™•์ธ"
2925
3100
  // list โ†’ ๋“ฑ๋ก๋œ ๋ชจ๋“  ํ”Œ๋žซํผ catalog ์ถœ๋ ฅ
@@ -5522,6 +5697,17 @@ function handoff(root) {
5522
5697
  overall: idemp.summary.overall
5523
5698
  };
5524
5699
  } catch {}
5700
+ // 1.9.221: abnormalShutdown ์ž๋™ ๊ฐ์ง€ (1.9.220 ํ†ตํ•ฉ)
5701
+ try {
5702
+ const ad = _detectAbnormalShutdown(root);
5703
+ result.abnormalShutdown = {
5704
+ detected: ad.abnormalShutdown,
5705
+ severity: ad.severity,
5706
+ signalCount: ad.signals.length,
5707
+ signals: ad.signals.map(s => ({ kind: s.kind, severity: s.severity, detail: s.detail })),
5708
+ resumeGuide: ad.resumeGuide
5709
+ };
5710
+ } catch {}
5525
5711
  log(JSON.stringify(result, null, 2));
5526
5712
  return;
5527
5713
  }
@@ -5694,6 +5880,13 @@ function handoff(root) {
5694
5880
  }
5695
5881
  }
5696
5882
  } catch {}
5883
+ // 16) 1.9.220: ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ (์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” ๊นจ์–ด๋‚จ ์งํ›„ ์ž๋™ ๋…ธ์ถœ
5884
+ try {
5885
+ const ad = _detectAbnormalShutdown(root);
5886
+ if (ad.abnormalShutdown) {
5887
+ parts.push(`๐Ÿ”Œ ๋น„์ •์ƒ์ข…๋ฃŒ ${ad.severity} (${ad.signals.length}์‹ ํ˜ธ)`);
5888
+ }
5889
+ } catch {}
5697
5890
  // 15) 1.9.215: ํ˜„์žฌ ํ™œ์„ฑ task์—์„œ constraints/intent ์ž๋™ ๋ถ„์„ (1.9.208/213 ํ†ตํ•ฉ)
5698
5891
  try {
5699
5892
  // progress-tracker.md ์˜ ์ฒซ active task request ์ถ”์ถœ
@@ -5732,7 +5925,7 @@ function handoff(root) {
5732
5925
  if (parts.length) {
5733
5926
  const isTty = process.stdout && process.stdout.isTTY;
5734
5927
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
5735
- log(cy(`๐Ÿ“Š ํ—ค๋“œ๋ผ์ธ (1.9.81/93/113/152/162/192/197/204/207/209/215): ${parts.join(' ยท ')}`));
5928
+ log(cy(`๐Ÿ“Š ํ—ค๋“œ๋ผ์ธ (1.9.81/93/113/152/162/192/197/204/207/209/215/220): ${parts.join(' ยท ')}`));
5736
5929
  }
5737
5930
  } catch {}
5738
5931
  }
@@ -9541,6 +9734,17 @@ function sessionClose(root, opts = {}) {
9541
9734
  overall: idemp.summary.overall
9542
9735
  };
9543
9736
  } catch {}
9737
+ try {
9738
+ // 1.9.221: abnormalShutdown ์ž๋™ ๊ฐ์ง€ (1.9.220 ํ†ตํ•ฉ) โ€” session close ์‹œ ๋‹ค์Œ ์žฌ๊ฐœ ๊ฐ€์ด๋“œ ํšŒ์ˆ˜
9739
+ const ad = _detectAbnormalShutdown(root);
9740
+ jsonResult.abnormalShutdown = {
9741
+ detected: ad.abnormalShutdown,
9742
+ severity: ad.severity,
9743
+ signalCount: ad.signals.length,
9744
+ signals: ad.signals.map(s => ({ kind: s.kind, severity: s.severity, detail: s.detail })),
9745
+ resumeGuide: ad.resumeGuide
9746
+ };
9747
+ } catch {}
9544
9748
 
9545
9749
  process.stdout.write(JSON.stringify(jsonResult, null, 2) + '\n');
9546
9750
  } else {
@@ -13416,7 +13620,8 @@ function mcpServeCmd(root) {
13416
13620
  { name: 'leerness_constraints_check', description: '1.9.216 (1.9.208 ์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” ํ”Œ๋žซํผ/API ์ œ์•ฝ ์‚ฌ์ „ ์ฒดํฌ. ์‚ฌ์šฉ์ž ์š”์ฒญ ํ…์ŠคํŠธ์—์„œ ํ”Œ๋žซํผ alias ๋งค์นญ (stripe/openai/anthropic/github/discord/twitter 6์ข… + ์‚ฌ์šฉ์ž ์ •์˜) โ†’ ๊ฐ ํ”Œ๋žซํผ์˜ rate-limit / idempotency / auth / cost ์ œ์•ฝ ๋…ธ์ถœ. ์™ธ๋ถ€ AI๊ฐ€ "์ด ๊ธฐ๋Šฅ ๊ตฌํ˜„ ์ „ ์–ด๋–ค ๊ทœ์ •์„ ๋ด์•ผ ํ•˜๋‚˜?"๋ฅผ ์ง์ ‘ ํšŒ์ˆ˜. ์ธ์ž: { request (required), path? }', inputSchema: { type: 'object', properties: { request: { type: 'string' }, path: { type: 'string' } }, required: ['request'] } },
13417
13621
  { name: 'leerness_pre_wake_audit', description: '1.9.216 (1.9.209 ์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” sleep ์ „ sub-agent audit. 6 ์˜์—ญ ์ ๊ฒ€: missing-user-requests / stale-in-progress / drift-handoff-stale / wakeup-missed / next-action-pending / auto-resume-plan. ์™ธ๋ถ€ AI๊ฐ€ "๊นจ์–ด๋‚˜๊ธฐ ์ „ ์ ๊ฒ€ํ•  ๋ถ€๋ถ„"์„ ํšŒ์ˆ˜. ์‘๋‹ต: { auditedAt, findings: {critical, warning, info}, summary }. ์ธ์ž: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
13418
13622
  { name: 'leerness_intent_classify', description: '1.9.216 (1.9.213 ์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” ์‚ฌ์šฉ์ž ์˜๋„ ํŒŒ์•… + scope expansion ๊ฒŒ์ดํŠธ. ์‘๋‹ต: { intent: precise|broad|default, signals, domain, explicitMentions, expansionCandidates, mode: dry-run }. 3์›์น™ ์•ˆ์ „: (1) Always-Off Opt-In, (2) Dry-run ๊ธฐ๋ณธ (์‹คํ–‰ X), (3) ๋ช…์‹œ vs ์ถ”๋ก  ๋ถ„๋ฆฌ ๋ผ๋ฒจ๋ง. 5 ๋„๋ฉ”์ธ (game/web/api/cli/data). ์™ธ๋ถ€ AI๊ฐ€ "์ด ์š”์ฒญ์€ ์ •ํ™•ํžˆ ๊ทธ๊ฒƒ๋งŒ / ํฌ๊ด„์  / ๊ธฐ๋ณธ์ธ๊ฐ€?"๋ฅผ ํšŒ์ˆ˜. ์ธ์ž: { request (required), path? }', inputSchema: { type: 'object', properties: { request: { type: 'string' }, path: { type: 'string' } }, required: ['request'] } },
13419
- { name: 'leerness_idempotency_audit', description: '1.9.216 (1.9.212 ์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” ๋ฉฑ๋“ฑ์„ฑ ์œ„๋ฐ˜ ํƒ์ง€. 4์˜์—ญ ์ ๊ฒ€: rule-duplicate (medium) / task-duplicate-request (medium) / user-request-duplicate (low) / wakeup-duplicate (high). ์‘๋‹ต: { violations[], verified[], summary: {totalViolations, high/medium/low, overall} }. ์™ธ๋ถ€ AI๊ฐ€ "์›Œํฌ์ŠคํŽ˜์ด์Šค์— ์ค‘๋ณต/์ถฉ๋Œ์ด ์žˆ๋‚˜?"๋ฅผ ํšŒ์ˆ˜. ๐ŸŽ‰ MCP 58 ๋„๊ตฌ ๋งˆ์ผ์Šคํ†ค (50โ†’58). ์ธ์ž: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
13623
+ { name: 'leerness_idempotency_audit', description: '1.9.216 (1.9.212 ์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” ๋ฉฑ๋“ฑ์„ฑ ์œ„๋ฐ˜ ํƒ์ง€. 4์˜์—ญ ์ ๊ฒ€: rule-duplicate (medium) / task-duplicate-request (medium) / user-request-duplicate (low) / wakeup-duplicate (high). ์‘๋‹ต: { violations[], verified[], summary: {totalViolations, high/medium/low, overall} }. ์™ธ๋ถ€ AI๊ฐ€ "์›Œํฌ์ŠคํŽ˜์ด์Šค์— ์ค‘๋ณต/์ถฉ๋Œ์ด ์žˆ๋‚˜?"๋ฅผ ํšŒ์ˆ˜. ๐ŸŽ‰ MCP 58 ๋„๊ตฌ ๋งˆ์ผ์Šคํ†ค (50โ†’58). ์ธ์ž: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
13624
+ { name: 'leerness_session_resume', description: '1.9.221 (1.9.220 ์‚ฌ์šฉ์ž ๋ช…์‹œ) โ€” ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + ์ž์œจ ์žฌ๊ฐœ. 5์‹ ํ˜ธ ๋ถ„์„: last-handoff-stale / wakeup-missed / in-progress-stale / auto-resume-plan-unused / release-branch-pending. ์‘๋‹ต: { abnormalShutdown, severity (none/low/medium/high), signals[], resumeGuide[] }. ์™ธ๋ถ€ AI๊ฐ€ "์ ˆ์ „/์‹œ์Šคํ…œ์ข…๋ฃŒ/์„ธ์…˜์ข…๋ฃŒ ํ›„ leerness ์ƒํƒœ๊ฐ€ ์ •์ƒ์ธ๊ฐ€? ์–ด๋–ป๊ฒŒ ์žฌ๊ฐœ?"๋ฅผ ํšŒ์ˆ˜. ๐ŸŽ‰ MCP 60 ๋„๊ตฌ ๋งˆ์ผ์Šคํ†ค (53โ†’60, +7 in 1.9.168/216/221). ์ธ์ž: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
13420
13625
  ];
13421
13626
 
13422
13627
  function send(obj) {
@@ -13568,6 +13773,10 @@ function mcpServeCmd(root) {
13568
13773
  // 1.9.216 (1.9.212): ๋ฉฑ๋“ฑ์„ฑ ๊ฐ์‚ฌ (rule/task/user-requests/wakeups 4์˜์—ญ)
13569
13774
  cliArgs = ['idempotency', 'audit', '--path', targetPath, '--json'];
13570
13775
  break;
13776
+ case 'leerness_session_resume':
13777
+ // 1.9.221 (1.9.220): ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + ์ž์œจ ์žฌ๊ฐœ
13778
+ cliArgs = ['session-resume', '--path', targetPath, '--json'];
13779
+ break;
13571
13780
  default:
13572
13781
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
13573
13782
  }
@@ -17197,6 +17406,8 @@ async function main() {
17197
17406
  if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
17198
17407
  // 1.9.207: leerness requests <audit|add|list|complete|drop> โ€” ์‚ฌ์šฉ์ž ์š”์ฒญ ๋ˆ„๋ฝ ํ™•์ธ ์ ˆ์ฐจ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
17199
17408
  if (cmd === 'requests') return requestsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
17409
+ // 1.9.220: leerness session-resume โ€” ๋น„์ •์ƒ ์ข…๋ฃŒ ๊ฐ์ง€ + ์ž์œจ ์žฌ๊ฐœ ๊ฐ€์ด๋“œ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
17410
+ if (cmd === 'session-resume') return sessionResumeCmd(arg('--path', process.cwd()));
17200
17411
  // 1.9.208: leerness constraints <list|check|add> โ€” ํ”Œ๋žซํผ/API ์ œ์•ฝ ์‚ฌ์ „ ์ฒดํฌ (์‚ฌ์šฉ์ž ๋ช…์‹œ)
17201
17412
  if (cmd === 'constraints') return constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
17202
17413
  // 1.9.209: leerness pre-wake-audit โ€” sleep ์ „ sub-agent audit (์‚ฌ์šฉ์ž ๋ช…์‹œ)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.219",
3
+ "version": "1.9.221",
4
4
  "description": "Leerness: ๋น„ํŒŒ๊ดด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜, ์ž๋™ ๋ฒ„์ „ ๊ฐ์ง€ยท์—…๋ฐ์ดํŠธ, ๊ณ„ํš/์ง„ํ–‰/ํ•ธ๋“œ์˜คํ”„ ์ž๋™ํ™”, ๊ฒŒ์œผ๋ฆ„ยท์‹œํฌ๋ฆฟยท์ธ์ฝ”๋”ฉ ์ž๋™ ๊ฐ€๋“œ, Claude Code ์Šฌ๋ž˜์‹œ ํ†ตํ•ฉ์„ ๊ฐ–์ถ˜ ํ•œ๊ตญ์–ด ์šฐ์„  AI ๊ฐœ๋ฐœ ํ•˜๋„ค์Šค.",
5
5
  "keywords": [
6
6
  "leerness",