leerness 1.9.207 β†’ 1.9.209

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,94 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.209 β€” 2026-05-22
4
+
5
+ **πŸ” pre-wake sub-agent audit + 깨어남 직후 μžλ™ λ…ΈμΆœ (μ‚¬μš©μž λͺ…μ‹œ).**
6
+
7
+ ### μ‚¬μš©μž λͺ…μ‹œ
8
+ > *"메인 μ—μ΄μ „νŠΈκ°€ μŠ¬λ¦½μ „μ— μ„œλΈŒμ—μ΄μ „νŠΈλ₯Ό ν˜ΈμΆœν•΄μ„œ λ―ΈλΉ„λœ 뢀뢄이 μžˆλŠ”μ§€, μΆ©λŒλ‚˜λŠ” 뢀뢄이 μžˆλŠ”μ§€, 이전에 λˆ„λ½λœ μž‘μ—…μ΄ μžˆλŠ”μ§€ λ“±λ“± μ—¬λŸ¬λΆ€λΆ„μ„ νƒμƒ‰ν•˜λ©΄μ„œ ν•„μš”ν•œ λ‚΄μš©μ„ μ •λ¦¬ν•˜κ³  메인 μ—μ΄μ „νŠΈκ°€ κΉ¨μ–΄λ‚¬μ„λ•Œ, μ„œλΈŒμ—μ΄μ „νŠΈκ°€ μ •λ¦¬ν•œ λ‚΄μš©μ„ ν™•μΈν•΄λ³΄κ±°λ‚˜ ν•˜λŠ” κΈ°λŠ₯"*
9
+
10
+ ### 1. `.harness/pre-wake-report.json` β€” sleep μ „ audit λˆ„μ 
11
+ - `_preWakeReportPath/_loadPreWakeReport/_writePreWakeReport` 파일 I/O 헬퍼
12
+ - 졜근 10개만 μœ μ§€ (rotate)
13
+ - ꡬ쑰: `{ auditedAt, auditVersion, findings: {critical/warning/info}, summary }`
14
+
15
+ ### 2. `_runPreWakeAudit(root)` β€” sub-agent audit 6μ˜μ—­
16
+ - **missing-user-requests** (1.9.207 톡합) β€” task-log/plan/decisions λ§€μΉ­ μ•ˆ 된 μš”μ²­
17
+ - **stale-user-requests** β€” 7일+ open μš”μ²­
18
+ - **stale-in-progress** β€” 24h+ μ§„μ²™ μ—†λŠ” task
19
+ - **drift-handoff-stale** β€” session-handoff.md 5일+ stale
20
+ - **wakeup-missed** β€” wakeup miss 감지 (1.9.205)
21
+ - **next-action-pending** β€” next-action queue λŒ€κΈ°
22
+ - **auto-resume-plan-ready/stale** β€” 1.9.203 plan μƒνƒœ
23
+
24
+ ### 3. `leerness pre-wake-audit` CLI
25
+ - κΈ°λ³Έ: μƒˆ audit μ‹€ν–‰ + μ €μž₯
26
+ - `--last` / `show` / `review` β€” κ°€μž₯ 졜근 μ €μž₯된 audit ν‘œμ‹œ
27
+ - `--json` β€” JSON 좜λ ₯
28
+
29
+ ### 4. handoff μžλ™ λ…ΈμΆœ (μ‚¬μš©μž λͺ…μ‹œ "κΉ¨μ–΄λ‚¬μ„λ•Œ 확인")
30
+ - ν—€λ“œλΌμΈ 14번째 μš”μ†Œ: `πŸ” pre-wake NC/MW (ageMin)` β€” critical N건 + warning M건
31
+ - λ³Έλ¬Έ μžλ™ μ„Ήμ…˜: `## πŸ” 직전 sleep pre-wake-audit (1.9.209, NλΆ„ μ „)`
32
+ Β· critical/warning μ΅œλŒ€ 3κ°œμ”© λ…ΈμΆœ
33
+ Β· 4μ‹œκ°„ 이내 λ³΄κ³ μ„œλ§Œ (stale μžλ™ hide)
34
+ - ν—€λ“œλΌμΈ 라벨: `1.9.81/93/113/152/162/192/197/204/207/209`
35
+
36
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.200~208) β€” λͺ¨λ‘ μœ μ§€
37
+
38
+ ### 6. stress-v154 β€” 18/18 PASS
39
+ - 1.9.209 (9) + μ„±λŠ₯ (2) + λˆ„μ  νšŒκ·€ (7)
40
+ - 격리 tmp dir 라이프사이클 검증 (μ‹€ν–‰ β†’ μ €μž₯ β†’ --last β†’ rotate to 10)
41
+ - 6 findings kind ν‚€μ›Œλ“œ 검증
42
+ - μ„±λŠ₯: --version cold start avg 517ms Β· MCP 54 도ꡬ 574ms
43
+
44
+ ### 7. μžλ™ release (71 λΌμš΄λ“œ main-push streak Β· 32 λΌμš΄λ“œ npm publish streak)
45
+
46
+ ---
47
+
48
+ ## 1.9.208 β€” 2026-05-22
49
+
50
+ **🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ).**
51
+
52
+ ### μ‚¬μš©μž λͺ…μ‹œ
53
+ > *"μ‚¬μš©μžμ˜ λͺ…령을 λ°›μœΌλ©΄, κ³ λ €ν•΄μ•Όν•˜λŠ” 뢀뢄을 λ¨Όμ € ν™•μΈν•˜κ³  μ§„ν–‰ν•˜κ²Œ ν•˜λ©΄ μ–΄λ–¨μ§€ / νŠΉμ • ν”Œλž«νΌμ˜ API ... ν˜ΈμΆœμ†λ„κ°€ μ΄ˆλ‹Ή 5회 μ΄ν•˜λ‘œλ§Œ ν•΄μ•Όν•œλ‹€λŠ” κ·œμ •μ„ λ¨Όμ € 확인"*
54
+
55
+ ### 1. `.harness/platform-constraints.json` + κΈ°λ³Έ catalog 6μ’…
56
+ - **stripe** (rate: 100 req/s, idempotency key, webhook signing)
57
+ - **openai** (tier-based RPM, TPM, cost per 1M tokens)
58
+ - **anthropic** (claude tier, context-window, cost)
59
+ - **github** (5K req/hr auth, search 30 req/min, secondary)
60
+ - **discord** (50 req/s global, invalid ban)
61
+ - **twitter** (tier-based, OAuth 2.0 PKCE)
62
+ - 각 alias array λ§€μΉ­ + docs URL 동봉
63
+
64
+ ### 2. 헬퍼 ν•¨μˆ˜
65
+ - `_loadPlatformConstraints(root)` β€” default merge user override
66
+ - `_writePlatformConstraints(root, catalog)` β€” μ‚¬μš©μž catalog μ €μž₯
67
+ - `_checkRequestConstraints(root, text)` β€” ν…μŠ€νŠΈ β†’ λ§€μΉ­ ν”Œλž«νΌ + μ œμ•½ + suggestions
68
+
69
+ ### 3. `leerness constraints <list|check|add>` CLI
70
+ - `list` β€” 등둝 catalog 좜λ ₯ (--json)
71
+ - `check "<req>"` β€” μ‚¬μš©μž μš”μ²­ λ§€μΉ­ β†’ μ œμ•½ 보고 (--json)
72
+ - `add <id> --alias name --constraint "kind:detail"` β€” μ‚¬μš©μž μ •μ˜
73
+
74
+ ### 4. review-request μžλ™ 톡합
75
+ - `_checkRequestConstraints()` review-request λ‚΄λΆ€μ—μ„œ μžλ™ 호좜
76
+ - JSON 좜λ ₯에 `platformConstraints` + `constraintSuggestions` ν•„λ“œ μΆ”κ°€
77
+ - human 좜λ ₯에 `## 🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크` μ„Ήμ…˜
78
+ - efficiencyHints 에 λ§€μΉ­ N건 μ•Œλ¦Ό
79
+
80
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.200~207) β€” λͺ¨λ‘ μœ μ§€
81
+
82
+ ### 6. stress-v153 β€” 18/18 PASS
83
+ - 1.9.208 (9) + μ„±λŠ₯ (2) + λˆ„μ  νšŒκ·€ (7)
84
+ - κΈ°λ³Έ 6 catalog + μ‚¬μš©μž μ •μ˜ add + review-request 톡합 검증
85
+ - μ„±λŠ₯: --version cold start avg 483ms Β· MCP 54 도ꡬ 431ms
86
+
87
+ ### 7. μžλ™ release (70 λΌμš΄λ“œ main-push streak Β· 31 λΌμš΄λ“œ npm publish streak)
88
+ - `release sync-main` μžλ™ β†’ main merge + npm publish leerness@1.9.208
89
+
90
+ ---
91
+
3
92
  ## 1.9.207 β€” 2026-05-22
4
93
 
5
94
  **πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 MVP (μ‚¬μš©μž λͺ…μ‹œ).**
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.207-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v152-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-137-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-69_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![requests](https://img.shields.io/badge/requests-audit_λˆ„λ½ν™•μΈ-success)]() [![perf](https://img.shields.io/badge/cold_start-2206ms-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.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)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,9 +12,9 @@
12
12
  β•‘ β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β• β–ˆβ–ˆβ•”β•β•β–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β•šβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•”β•β•β• β•šβ•β•β•β•β–ˆβ–ˆβ•‘ β•‘
13
13
  β•‘ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ•‘ β–ˆβ–ˆβ•‘β–ˆβ–ˆβ•‘ β•šβ–ˆβ–ˆβ–ˆβ–ˆβ•‘β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•—β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ•‘ β•‘
14
14
  β•‘ β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β• β•šβ•β•β•šβ•β• β•šβ•β•β•β•β•šβ•β•β•β•β•β•β•β•šβ•β•β•β•β•β•β• β•‘
15
- β•‘ v1.9.207 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.209 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸ“₯ μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 (requests audit) (μ‚¬μš©μž λͺ…μ‹œ) β•‘
17
+ β•‘ πŸ” pre-wake sub-agent audit (sleep μ „ 점검) (μ‚¬μš©μž λͺ…μ‹œ) β•‘
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.207';
10
+ const VERSION = '1.9.209';
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 처리).
@@ -2006,6 +2006,251 @@ function _auditUserRequests(root) {
2006
2006
  };
2007
2007
  }
2008
2008
 
2009
+ // 1.9.208: ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ)
2010
+ // "μ‚¬μš©μžμ˜ λͺ…령을 λ°›μœΌλ©΄, κ³ λ €ν•΄μ•Όν•˜λŠ” 뢀뢄을 λ¨Όμ € ν™•μΈν•˜κ³  μ§„ν–‰ν•˜κ²Œ ν•˜λ©΄ μ–΄λ–¨μ§€"
2011
+ // "νŠΉμ • ν”Œλž«νΌμ˜ API ... ν˜ΈμΆœμ†λ„κ°€ μ΄ˆλ‹Ή 5회 μ΄ν•˜λ‘œλ§Œ ν•΄μ•Όν•œλ‹€λŠ” κ·œμ •μ„ λ¨Όμ € 확인"
2012
+ // .harness/platform-constraints.json 에 ν”Œλž«νΌλ³„ μ œμ•½ catalog μ €μž₯ β†’ μš”μ²­ ν…μŠ€νŠΈ ν‚€μ›Œλ“œ λ§€μΉ­ β†’ 적용 κ°€λŠ₯ν•œ μ œμ•½ 보고
2013
+ // κΈ°λ³Έ catalog 6μ’…: Stripe / OpenAI / Anthropic / GitHub / Discord / Twitter
2014
+ function _platformConstraintsPath(root) { return path.join(root, '.harness', 'platform-constraints.json'); }
2015
+ const _DEFAULT_PLATFORM_CONSTRAINTS = {
2016
+ version: '1.9.208',
2017
+ platforms: {
2018
+ stripe: {
2019
+ aliases: ['stripe', 'stripe api', 'payment', '결제'],
2020
+ docs: 'https://stripe.com/docs/rate-limits',
2021
+ constraints: [
2022
+ { kind: 'rate-limit', detail: 'read: 100 req/s, write: 100 req/s (live mode), test mode: 25 req/s' },
2023
+ { kind: 'idempotency', detail: 'Idempotency-Key 헀더 24h μœ μ§€ β€” 쀑볡 결제 λ°©μ§€ ν•„μˆ˜' },
2024
+ { kind: 'webhook', detail: 'webhook μ„œλͺ… 검증 ν•„μˆ˜ (Stripe-Signature header + endpoint secret)' }
2025
+ ]
2026
+ },
2027
+ openai: {
2028
+ aliases: ['openai', 'gpt', 'chatgpt', 'gpt-4', 'gpt-3'],
2029
+ docs: 'https://platform.openai.com/docs/guides/rate-limits',
2030
+ constraints: [
2031
+ { kind: 'rate-limit', detail: 'tier-based: Free 3 RPM / Tier 1 500 RPM / Tier 5 10,000 RPM' },
2032
+ { kind: 'token-limit', detail: 'TPM (tokens/min) 별도 β€” 큰 μž…λ ₯ μ‹œ RPM 도달 μ „ 차단 κ°€λŠ₯' },
2033
+ { kind: 'cost', detail: 'gpt-4: $30/$60 per 1M input/output tokens β€” λŒ€λŸ‰ 호좜 μ „ λΉ„μš© μΆ”μ • ν•„μˆ˜' }
2034
+ ]
2035
+ },
2036
+ anthropic: {
2037
+ aliases: ['anthropic', 'claude', 'claude api', 'sonnet', 'opus', 'haiku'],
2038
+ docs: 'https://docs.anthropic.com/claude/reference/rate-limits',
2039
+ constraints: [
2040
+ { kind: 'rate-limit', detail: 'tier-based: Free 5 RPM / Tier 1 50 RPM / Tier 4 4,000 RPM' },
2041
+ { kind: 'context-window', detail: 'claude-sonnet 200K context, claude-opus 200K, 1M tier 별도' },
2042
+ { kind: 'cost', detail: 'sonnet: $3/$15 per 1M tokens (1M context tier 2x)' }
2043
+ ]
2044
+ },
2045
+ github: {
2046
+ aliases: ['github', 'github api', 'gh api', 'octokit'],
2047
+ docs: 'https://docs.github.com/en/rest/rate-limit',
2048
+ constraints: [
2049
+ { kind: 'rate-limit', detail: 'authenticated: 5,000 req/hr, unauthenticated: 60 req/hr' },
2050
+ { kind: 'rate-limit', detail: 'search API: 30 req/min (authenticated)' },
2051
+ { kind: 'secondary', detail: 'secondary rate limit β€” concurrent + content creation 별도 κ°€λ“œ' }
2052
+ ]
2053
+ },
2054
+ discord: {
2055
+ aliases: ['discord', 'discord api', 'discord bot'],
2056
+ docs: 'https://discord.com/developers/docs/topics/rate-limits',
2057
+ constraints: [
2058
+ { kind: 'rate-limit', detail: 'global: 50 req/s, per-route 별도' },
2059
+ { kind: 'invalid', detail: '10,000 invalid req/10min β†’ 1h ban μœ„ν—˜' }
2060
+ ]
2061
+ },
2062
+ twitter: {
2063
+ aliases: ['twitter', 'twitter api', 'x api', 'x.com api'],
2064
+ docs: 'https://developer.twitter.com/en/docs/twitter-api/rate-limits',
2065
+ constraints: [
2066
+ { kind: 'rate-limit', detail: 'tier-based: Free 1,500 posts/month, Basic 50,000 posts/month' },
2067
+ { kind: 'auth', detail: 'OAuth 2.0 PKCE ν•„μˆ˜ (user context), App-onlyλŠ” 별도 endpoint' }
2068
+ ]
2069
+ }
2070
+ }
2071
+ };
2072
+ function _loadPlatformConstraints(root) {
2073
+ try {
2074
+ const fp = _platformConstraintsPath(root);
2075
+ if (!exists(fp)) return _DEFAULT_PLATFORM_CONSTRAINTS;
2076
+ const j = JSON.parse(read(fp));
2077
+ // user catalog 와 default merge β€” user override μš°μ„ 
2078
+ const merged = { ...JSON.parse(JSON.stringify(_DEFAULT_PLATFORM_CONSTRAINTS)) };
2079
+ if (j.platforms) {
2080
+ for (const k of Object.keys(j.platforms)) {
2081
+ merged.platforms[k] = j.platforms[k];
2082
+ }
2083
+ }
2084
+ return merged;
2085
+ } catch { return _DEFAULT_PLATFORM_CONSTRAINTS; }
2086
+ }
2087
+ function _writePlatformConstraints(root, catalog) {
2088
+ try {
2089
+ mkdirp(path.join(root, '.harness'));
2090
+ writeUtf8(_platformConstraintsPath(root), JSON.stringify({ ...catalog, updatedAt: new Date().toISOString() }, null, 2));
2091
+ return true;
2092
+ } catch { return false; }
2093
+ }
2094
+ // μ‚¬μš©μž μš”μ²­ ν…μŠ€νŠΈμ—μ„œ ν”Œλž«νΌ alias λ§€μΉ­ β†’ 적용 μ œμ•½ λͺ©λ‘ λ°˜ν™˜
2095
+ function _checkRequestConstraints(root, text) {
2096
+ if (!text || typeof text !== 'string') return { matched: [], suggestions: [] };
2097
+ const catalog = _loadPlatformConstraints(root);
2098
+ const lower = text.toLowerCase();
2099
+ const matched = [];
2100
+ for (const [pid, plat] of Object.entries(catalog.platforms)) {
2101
+ const aliases = plat.aliases || [];
2102
+ const hit = aliases.find(a => lower.includes(a.toLowerCase()));
2103
+ if (hit) matched.push({ platform: pid, matchedAlias: hit, docs: plat.docs, constraints: plat.constraints });
2104
+ }
2105
+ // suggestions β€” 보편 ν‚€μ›Œλ“œ (api/rate/limit/integration) μ‹œ catalog κ²€ν†  ꢌμž₯
2106
+ const suggestions = [];
2107
+ const generic = /\bapi\b|연동|integration|호좜|rate|limit|quota|webhook/i.test(text);
2108
+ if (generic && matched.length === 0) {
2109
+ suggestions.push('일반적 API 연동 ν‚€μ›Œλ“œ 감지 β€” leerness constraints list 둜 사전 λ“±λ‘λœ ν”Œλž«νΌ catalog 확인 ꢌμž₯');
2110
+ }
2111
+ return { matched, suggestions, totalPlatforms: Object.keys(catalog.platforms).length };
2112
+ }
2113
+
2114
+ // 1.9.209: pre-wake sub-agent audit (μ‚¬μš©μž λͺ…μ‹œ)
2115
+ // "메인 μ—μ΄μ „νŠΈκ°€ μŠ¬λ¦½μ „μ— μ„œλΈŒμ—μ΄μ „νŠΈλ₯Ό ν˜ΈμΆœν•΄μ„œ λ―ΈλΉ„λœ 뢀뢄이 μžˆλŠ”μ§€, μΆ©λŒλ‚˜λŠ” 뢀뢄이 μžˆλŠ”μ§€,
2116
+ // 이전에 λˆ„λ½λœ μž‘μ—…μ΄ μžˆλŠ”μ§€ λ“±λ“± μ—¬λŸ¬λΆ€λΆ„μ„ νƒμƒ‰ν•˜λ©΄μ„œ ν•„μš”ν•œ λ‚΄μš©μ„ μ •λ¦¬ν•˜κ³ 
2117
+ // 메인 μ—μ΄μ „νŠΈκ°€ κΉ¨μ–΄λ‚¬μ„λ•Œ, μ„œλΈŒμ—μ΄μ „νŠΈκ°€ μ •λ¦¬ν•œ λ‚΄μš©μ„ ν™•μΈν•΄λ³΄κ±°λ‚˜ ν•˜λŠ” κΈ°λŠ₯"
2118
+ // .harness/pre-wake-report.json 에 sleep μ „ audit κ²°κ³Ό λˆ„μ  (졜근 10개 μœ μ§€) β†’
2119
+ // λ‹€μŒ handoff μ—μ„œ μžλ™ λ…ΈμΆœ β†’ 깨어남 직후 메인 μ—μ΄μ „νŠΈκ°€ κ²€ν†  κ°€λŠ₯
2120
+ // audit μ˜μ—­: λ―Έλ‹΅ μš”μ²­ / 24h+ 무진척 task / drift μ‹ ν˜Έ / feature_graph 좩돌 / ν”Œλž«νΌ μ œμ•½ λˆ„λ½
2121
+ function _preWakeReportPath(root) { return path.join(root, '.harness', 'pre-wake-report.json'); }
2122
+ function _loadPreWakeReport(root) {
2123
+ try {
2124
+ const fp = _preWakeReportPath(root);
2125
+ if (!exists(fp)) return { reports: [] };
2126
+ const j = JSON.parse(read(fp));
2127
+ return { reports: Array.isArray(j.reports) ? j.reports : [] };
2128
+ } catch { return { reports: [] }; }
2129
+ }
2130
+ function _writePreWakeReport(root, reports) {
2131
+ try {
2132
+ mkdirp(path.join(root, '.harness'));
2133
+ writeUtf8(_preWakeReportPath(root), JSON.stringify({ reports, updatedAt: new Date().toISOString() }, null, 2));
2134
+ return true;
2135
+ } catch { return false; }
2136
+ }
2137
+ // μ‹€μ œ audit μ‹€ν–‰ β€” sub-agent μ—­ν•  (단일 ν”„λ‘œμ„ΈμŠ€μ—μ„œ sync둜 λ‹€μ–‘ν•œ μ‹ ν˜Έ μˆ˜μ§‘)
2138
+ function _runPreWakeAudit(root) {
2139
+ const audit = {
2140
+ auditedAt: new Date().toISOString(),
2141
+ auditVersion: VERSION,
2142
+ findings: { critical: [], warning: [], info: [] }
2143
+ };
2144
+ // 1) λ―Έλ‹΅ μ‚¬μš©μž μš”μ²­ (1.9.207)
2145
+ try {
2146
+ const reqAudit = _auditUserRequests(root);
2147
+ if (reqAudit.missing && reqAudit.missing.length > 0) {
2148
+ audit.findings.critical.push({
2149
+ kind: 'missing-user-requests',
2150
+ count: reqAudit.missing.length,
2151
+ detail: `${reqAudit.missing.length}건 μ‚¬μš©μž μš”μ²­μ΄ task-log/plan/decisions 에 λ§€μΉ­ μ•ˆ 됨`,
2152
+ items: reqAudit.missing.slice(0, 5).map(r => ({ id: r.id, text: r.text.slice(0, 80) }))
2153
+ });
2154
+ }
2155
+ if (reqAudit.stale && reqAudit.stale.length > 0) {
2156
+ audit.findings.warning.push({
2157
+ kind: 'stale-user-requests',
2158
+ count: reqAudit.stale.length,
2159
+ detail: `${reqAudit.stale.length}건 μ‚¬μš©μž μš”μ²­μ΄ 7일+ open μƒνƒœ`
2160
+ });
2161
+ }
2162
+ } catch (e) { audit.findings.info.push({ kind: 'audit-error', area: 'user-requests', error: String(e.message || e) }); }
2163
+
2164
+ // 2) 24h+ 무진척 task β€” progress-tracker 검사
2165
+ try {
2166
+ const ptPath = path.join(root, '.harness', 'progress-tracker.md');
2167
+ if (exists(ptPath)) {
2168
+ const content = read(ptPath);
2169
+ const stat = fs.statSync(ptPath);
2170
+ const ageMin = Math.floor((Date.now() - stat.mtimeMs) / 60000);
2171
+ // in-progress νƒœκ·Έκ°€ μžˆλŠ”λ° 24h+ κ°±μ‹  μ—†μŒ
2172
+ const inProgressCount = (content.match(/status:\s*in-progress/gi) || []).length;
2173
+ if (inProgressCount > 0 && ageMin > 24 * 60) {
2174
+ audit.findings.warning.push({
2175
+ kind: 'stale-in-progress',
2176
+ count: inProgressCount,
2177
+ detail: `${inProgressCount}건 in-progress task, ${Math.floor(ageMin/60)}h κ°±μ‹  μ—†μŒ`,
2178
+ ageMin
2179
+ });
2180
+ }
2181
+ }
2182
+ } catch {}
2183
+
2184
+ // 3) drift μ‹ ν˜Έ β€” session-handoff/progress-tracker stale 검사
2185
+ try {
2186
+ const handoffPath = path.join(root, '.harness', 'session-handoff.md');
2187
+ if (exists(handoffPath)) {
2188
+ const stat = fs.statSync(handoffPath);
2189
+ const ageDay = (Date.now() - stat.mtimeMs) / 86400000;
2190
+ if (ageDay > 5) {
2191
+ audit.findings.warning.push({
2192
+ kind: 'drift-handoff-stale',
2193
+ ageDay: Math.round(ageDay * 10) / 10,
2194
+ detail: `session-handoff.md ${ageDay.toFixed(1)}일 stale β€” drift μœ„ν—˜`
2195
+ });
2196
+ }
2197
+ }
2198
+ } catch {}
2199
+
2200
+ // 4) λ‹€μŒ λΌμš΄λ“œ plan 적용 κ°€λŠ₯ μ—¬λΆ€ (1.9.203)
2201
+ try {
2202
+ const plan = _loadAutoResumePlan(root);
2203
+ if (!plan) {
2204
+ audit.findings.info.push({ kind: 'no-auto-resume-plan', detail: 'auto-resume-plan.json μ—†μŒ β€” λ‹€μŒ λΌμš΄λ“œ μ§„μž… μ‹œ manual context' });
2205
+ } else if (plan.ageMin > 12 * 60) {
2206
+ audit.findings.warning.push({ kind: 'auto-resume-plan-stale', detail: `plan ${plan.ageMin}λΆ„ μ „ μž‘μ„± β€” outdated κ°€λŠ₯` });
2207
+ } else {
2208
+ audit.findings.info.push({ kind: 'auto-resume-plan-ready', detail: `nextRoundVersion=${plan.nextRoundVersion}, focus=${(plan.focus||'').slice(0,50)}` });
2209
+ }
2210
+ } catch {}
2211
+
2212
+ // 5) Active wakeup μƒνƒœ (1.9.205)
2213
+ try {
2214
+ const wkStat = _analyzeWakeupStatus(root);
2215
+ if (wkStat.hasWakeup && wkStat.kind === 'missed') {
2216
+ audit.findings.critical.push({
2217
+ kind: 'wakeup-missed',
2218
+ deltaMin: wkStat.deltaMin,
2219
+ detail: `wakeup ${wkStat.deltaMin}λΆ„ 지남 β€” λ‹€μŒ λΌμš΄λ“œ μ§„μž… 늦음`
2220
+ });
2221
+ }
2222
+ } catch {}
2223
+
2224
+ // 6) next-action queue μž”μ—¬ (1.9.201)
2225
+ try {
2226
+ const q = _loadNextActionQueue(root);
2227
+ if (q && q.pending && q.pending.length > 0) {
2228
+ audit.findings.info.push({
2229
+ kind: 'next-action-pending',
2230
+ count: q.pending.length,
2231
+ detail: `${q.pending.length}건 next-action λŒ€κΈ° β€” leerness next-action take 둜 μ¦‰μ‹œ 처리`
2232
+ });
2233
+ }
2234
+ } catch {}
2235
+
2236
+ // 7) 톡합 μš”μ•½
2237
+ audit.summary = {
2238
+ criticalCount: audit.findings.critical.length,
2239
+ warningCount: audit.findings.warning.length,
2240
+ infoCount: audit.findings.info.length,
2241
+ needsAttention: audit.findings.critical.length > 0 || audit.findings.warning.length >= 2
2242
+ };
2243
+ return audit;
2244
+ }
2245
+ function _saveAndAppendPreWakeReport(root, audit) {
2246
+ const state = _loadPreWakeReport(root);
2247
+ state.reports.push(audit);
2248
+ // 졜근 10개만 μœ μ§€
2249
+ if (state.reports.length > 10) state.reports = state.reports.slice(-10);
2250
+ _writePreWakeReport(root, state.reports);
2251
+ return audit;
2252
+ }
2253
+
2009
2254
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
2010
2255
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
2011
2256
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -2231,6 +2476,191 @@ function requestsCmd(root, sub, ...rest) {
2231
2476
  process.exit(1);
2232
2477
  }
2233
2478
 
2479
+ // 1.9.208: leerness constraints <list|check|add> β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크 CLI (μ‚¬μš©μž λͺ…μ‹œ)
2480
+ // "νŠΉμ • ν”Œλž«νΌμ˜ API ... ν˜ΈμΆœμ†λ„κ°€ μ΄ˆλ‹Ή 5회 μ΄ν•˜λ‘œλ§Œ ν•΄μ•Όν•œλ‹€λŠ” κ·œμ •μ„ λ¨Όμ € 확인"
2481
+ // list β†’ λ“±λ‘λœ λͺ¨λ“  ν”Œλž«νΌ catalog 좜λ ₯
2482
+ // check "<req>" β†’ μ‚¬μš©μž μš”μ²­ ν…μŠ€νŠΈμ—μ„œ ν”Œλž«νΌ λ§€μΉ­ β†’ μ œμ•½ 보고 (--json κ°€λŠ₯)
2483
+ // add <id> --aliases ... --constraint "kind:detail" β†’ μ‚¬μš©μž μ •μ˜ ν”Œλž«νΌ μΆ”κ°€
2484
+ function constraintsCmd(root, sub, ...rest) {
2485
+ root = absRoot(root);
2486
+ const isTty = process.stdout && process.stdout.isTTY;
2487
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2488
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2489
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2490
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
2491
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2492
+
2493
+ if (!sub || sub === 'help' || sub === '--help') {
2494
+ log(`# leerness constraints (1.9.208) β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크`);
2495
+ log('');
2496
+ log(` list β†’ λ“±λ‘λœ λͺ¨λ“  ν”Œλž«νΌ catalog 좜λ ₯ (--json κ°€λŠ₯)`);
2497
+ log(` check "<request>" β†’ μ‚¬μš©μž μš”μ²­μ—μ„œ ν”Œλž«νΌ λ§€μΉ­ β†’ μ œμ•½ 보고 (--json κ°€λŠ₯)`);
2498
+ log(` add <id> --constraint "kind:detail" --alias name`);
2499
+ log('');
2500
+ log(dim(` 예: leerness constraints check "Stripe API 연동 결제 λͺ¨λ“ˆ"`));
2501
+ return;
2502
+ }
2503
+
2504
+ if (sub === 'list') {
2505
+ const catalog = _loadPlatformConstraints(root);
2506
+ if (has('--json')) { log(JSON.stringify(catalog, null, 2)); return; }
2507
+ log(cyan(`# leerness constraints list (1.9.208)`));
2508
+ log(` total platforms: ${Object.keys(catalog.platforms).length}`);
2509
+ log('');
2510
+ for (const [pid, plat] of Object.entries(catalog.platforms)) {
2511
+ log(grn(` πŸ“¦ ${pid}`) + dim(` aliases: ${(plat.aliases || []).join(', ')}`));
2512
+ log(dim(` docs: ${plat.docs || '-'}`));
2513
+ for (const c of plat.constraints || []) {
2514
+ const icon = c.kind === 'rate-limit' ? '🚦' : (c.kind === 'cost' ? 'πŸ’°' : (c.kind === 'auth' ? 'πŸ”' : 'πŸ“‹'));
2515
+ log(` ${icon} [${c.kind}] ${c.detail}`);
2516
+ }
2517
+ log('');
2518
+ }
2519
+ return;
2520
+ }
2521
+
2522
+ if (sub === 'check') {
2523
+ const text = rest.filter(x => !x.startsWith('-')).join(' ');
2524
+ if (!text) { console.error('Usage: leerness constraints check "<request text>"'); process.exit(1); }
2525
+ const result = _checkRequestConstraints(root, text);
2526
+ if (has('--json')) { log(JSON.stringify({ query: text, ...result }, null, 2)); return; }
2527
+ log(cyan(`# leerness constraints check (1.9.208)`));
2528
+ log(` query: ${text.slice(0, 80)}${text.length > 80 ? '…' : ''}`);
2529
+ log('');
2530
+ if (result.matched.length === 0) {
2531
+ log(grn(` βœ“ 맀칭된 ν”Œλž«νΌ μ—†μŒ (catalog ${result.totalPlatforms}μ’… κ²€ν†  μ™„λ£Œ)`));
2532
+ if (result.suggestions.length) {
2533
+ log('');
2534
+ log(yel(` πŸ’‘ μ œμ•ˆ:`));
2535
+ result.suggestions.forEach(s => log(` β€’ ${s}`));
2536
+ }
2537
+ return;
2538
+ }
2539
+ log(red(` ⚠ ${result.matched.length}개 ν”Œλž«νΌ λ§€μΉ­ β€” μ œμ•½ 사전 확인 ν•„μš”:`));
2540
+ log('');
2541
+ for (const m of result.matched) {
2542
+ log(grn(` πŸ“¦ ${m.platform}`) + dim(` (matched: "${m.matchedAlias}")`));
2543
+ log(dim(` docs: ${m.docs || '-'}`));
2544
+ for (const c of m.constraints || []) {
2545
+ const icon = c.kind === 'rate-limit' ? '🚦' : (c.kind === 'cost' ? 'πŸ’°' : (c.kind === 'auth' ? 'πŸ”' : 'πŸ“‹'));
2546
+ log(` ${icon} [${c.kind}] ${c.detail}`);
2547
+ }
2548
+ log('');
2549
+ }
2550
+ log(dim(` β†’ κ΅¬ν˜„ μ „ μœ„ μ œμ•½μ„ λ°˜μ˜ν•œ 섀계 ꢌμž₯ (rate limiter / idempotency key / λΉ„μš© μΆ”μ •)`));
2551
+ return;
2552
+ }
2553
+
2554
+ if (sub === 'add') {
2555
+ const id = rest[0];
2556
+ if (!id) { console.error('Usage: leerness constraints add <platform-id> --constraint "kind:detail"'); process.exit(1); }
2557
+ const catalog = _loadPlatformConstraints(root);
2558
+ const aliases = (arg('--alias', '') || '').split(',').map(s => s.trim()).filter(Boolean);
2559
+ const detailRaw = arg('--constraint', '');
2560
+ if (!detailRaw) { console.error('--constraint "kind:detail" required'); process.exit(1); }
2561
+ const [kind, ...rest2] = detailRaw.split(':');
2562
+ const detail = rest2.join(':').trim();
2563
+ if (!catalog.platforms[id]) catalog.platforms[id] = { aliases: [], constraints: [] };
2564
+ if (aliases.length) catalog.platforms[id].aliases = [...new Set([...(catalog.platforms[id].aliases || []), ...aliases])];
2565
+ if (kind && detail) catalog.platforms[id].constraints.push({ kind: kind.trim(), detail });
2566
+ _writePlatformConstraints(root, catalog);
2567
+ if (has('--json')) { log(JSON.stringify(catalog.platforms[id], null, 2)); return; }
2568
+ log(grn(`βœ“ platform "${id}" κ°±μ‹  β€” constraints: ${catalog.platforms[id].constraints.length}`));
2569
+ return;
2570
+ }
2571
+
2572
+ console.error(`Unknown subcommand: ${sub}`);
2573
+ console.error(`Run: leerness constraints help`);
2574
+ process.exit(1);
2575
+ }
2576
+
2577
+ // 1.9.209: leerness pre-wake-audit β€” sleep μ „ sub-agent audit CLI (μ‚¬μš©μž λͺ…μ‹œ)
2578
+ // "메인 μ—μ΄μ „νŠΈκ°€ μŠ¬λ¦½μ „μ— μ„œλΈŒμ—μ΄μ „νŠΈλ₯Ό ν˜ΈμΆœν•΄μ„œ λ―ΈλΉ„λœ 뢀뢄이 μžˆλŠ”μ§€, μΆ©λŒλ‚˜λŠ” 뢀뢄이 μžˆλŠ”μ§€,
2579
+ // 이전에 λˆ„λ½λœ μž‘μ—…μ΄ μžˆλŠ”μ§€ λ“±λ“± μ—¬λŸ¬λΆ€λΆ„μ„ νƒμƒ‰ν•˜λ©΄μ„œ ν•„μš”ν•œ λ‚΄μš©μ„ 정리"
2580
+ // κΈ°λ³Έ: μƒˆ audit μ‹€ν–‰ + μ €μž₯ + 좜λ ₯
2581
+ // --last: 졜근 μ €μž₯된 audit ν‘œμ‹œ (μ‚¬μš©μž 깨어났을 λ•Œ κ²€ν† μš©)
2582
+ // --json: JSON 좜λ ₯
2583
+ function preWakeAuditCmd(root, sub) {
2584
+ root = absRoot(root);
2585
+ const isTty = process.stdout && process.stdout.isTTY;
2586
+ const cyan = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2587
+ const grn = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2588
+ const yel = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2589
+ const red = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
2590
+ const dim = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2591
+
2592
+ // --last λ˜λŠ” review/show β€” κ°€μž₯ 졜근 audit ν‘œμ‹œ (μ‹€ν–‰ μ•ˆ 함)
2593
+ if (has('--last') || sub === 'show' || sub === 'review') {
2594
+ const state = _loadPreWakeReport(root);
2595
+ if (state.reports.length === 0) {
2596
+ log(cyan(`# leerness pre-wake-audit (1.9.209) β€” review`));
2597
+ log(dim(` (μ €μž₯된 audit μ—†μŒ β€” leerness pre-wake-audit 으둜 μƒˆ μ‹€ν–‰)`));
2598
+ return;
2599
+ }
2600
+ const latest = state.reports[state.reports.length - 1];
2601
+ if (has('--json')) { log(JSON.stringify(latest, null, 2)); return; }
2602
+ log(cyan(`# leerness pre-wake-audit (1.9.209) β€” review (latest)`));
2603
+ log(dim(` audited at: ${latest.auditedAt} (version ${latest.auditVersion})`));
2604
+ log('');
2605
+ const sum = latest.summary || {};
2606
+ log(` πŸ“Š ${red(`critical ${sum.criticalCount || 0}`)} Β· ${yel(`warning ${sum.warningCount || 0}`)} Β· info ${sum.infoCount || 0}`);
2607
+ if (sum.needsAttention) log(yel(` ⚠ needsAttention=true β€” 메인 μ—μ΄μ „νŠΈ κ²€ν†  ν•„μš”`));
2608
+ log('');
2609
+ if (latest.findings.critical && latest.findings.critical.length) {
2610
+ log(red(`## 🚨 critical (${latest.findings.critical.length})`));
2611
+ latest.findings.critical.forEach(f => log(` - [${f.kind}] ${f.detail}`));
2612
+ log('');
2613
+ }
2614
+ if (latest.findings.warning && latest.findings.warning.length) {
2615
+ log(yel(`## ⚠ warning (${latest.findings.warning.length})`));
2616
+ latest.findings.warning.forEach(f => log(` - [${f.kind}] ${f.detail}`));
2617
+ log('');
2618
+ }
2619
+ if (latest.findings.info && latest.findings.info.length) {
2620
+ log(dim(`## β„Ή info (${latest.findings.info.length})`));
2621
+ latest.findings.info.forEach(f => log(dim(` - [${f.kind}] ${f.detail}`)));
2622
+ }
2623
+ return;
2624
+ }
2625
+
2626
+ // κΈ°λ³Έ: μƒˆ audit μ‹€ν–‰ + μ €μž₯
2627
+ const audit = _runPreWakeAudit(root);
2628
+ _saveAndAppendPreWakeReport(root, audit);
2629
+
2630
+ if (has('--json')) { log(JSON.stringify(audit, null, 2)); return; }
2631
+ log(cyan(`# leerness pre-wake-audit (1.9.209) β€” sleep μ „ sub-agent audit`));
2632
+ log(dim(` audited at: ${audit.auditedAt}`));
2633
+ log('');
2634
+ const sum = audit.summary;
2635
+ log(` πŸ“Š ${red(`critical ${sum.criticalCount}`)} Β· ${yel(`warning ${sum.warningCount}`)} Β· info ${sum.infoCount}`);
2636
+ if (sum.needsAttention) {
2637
+ log(yel(` ⚠ needsAttention=true β€” κΉ¨μ–΄λ‚œ ν›„ 메인 μ—μ΄μ „νŠΈ κ²€ν†  ν•„μˆ˜`));
2638
+ } else {
2639
+ log(grn(` βœ“ 잠재 이슈 μ—†μŒ β€” sleep μ•ˆμ „`));
2640
+ }
2641
+ log('');
2642
+ if (audit.findings.critical.length) {
2643
+ log(red(`## 🚨 critical (${audit.findings.critical.length})`));
2644
+ audit.findings.critical.forEach(f => {
2645
+ log(` - [${f.kind}] ${f.detail}`);
2646
+ if (f.items) f.items.forEach(i => log(dim(` β€’ ${i.id}: ${i.text}`)));
2647
+ });
2648
+ log('');
2649
+ }
2650
+ if (audit.findings.warning.length) {
2651
+ log(yel(`## ⚠ warning (${audit.findings.warning.length})`));
2652
+ audit.findings.warning.forEach(f => log(` - [${f.kind}] ${f.detail}`));
2653
+ log('');
2654
+ }
2655
+ if (audit.findings.info.length) {
2656
+ log(dim(`## β„Ή info (${audit.findings.info.length})`));
2657
+ audit.findings.info.forEach(f => log(dim(` - [${f.kind}] ${f.detail}`)));
2658
+ log('');
2659
+ }
2660
+ log(dim(` β†’ μ €μž₯: .harness/pre-wake-report.json (졜근 10개 μœ μ§€)`));
2661
+ log(dim(` β†’ κΉ¨μ–΄λ‚œ ν›„: leerness pre-wake-audit --last`));
2662
+ }
2663
+
2234
2664
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
2235
2665
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
2236
2666
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -4274,13 +4704,52 @@ function handoff(root) {
4274
4704
  parts.push(`πŸ“₯ μš”μ²­ ${audit.open} (tracked)`);
4275
4705
  }
4276
4706
  } catch {}
4707
+ // 14) 1.9.209: pre-wake-audit 졜근 λ³΄κ³ μ„œ (μ‚¬μš©μž λͺ…μ‹œ) β€” 깨어남 직후 μžλ™ λ…ΈμΆœ
4708
+ try {
4709
+ const pwState = _loadPreWakeReport(root);
4710
+ if (pwState.reports.length > 0) {
4711
+ const latest = pwState.reports[pwState.reports.length - 1];
4712
+ const sum = latest.summary || {};
4713
+ if (sum.criticalCount > 0 || sum.warningCount > 0) {
4714
+ const ageMin = Math.floor((Date.now() - new Date(latest.auditedAt).getTime()) / 60000);
4715
+ parts.push(`πŸ” pre-wake ${sum.criticalCount}C/${sum.warningCount}W (${ageMin}m)`);
4716
+ }
4717
+ }
4718
+ } catch {}
4277
4719
  if (parts.length) {
4278
4720
  const isTty = process.stdout && process.stdout.isTTY;
4279
4721
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
4280
- log(cy(`πŸ“Š ν—€λ“œλΌμΈ (1.9.81/93/113/152/162/192/197/204/207): ${parts.join(' Β· ')}`));
4722
+ log(cy(`πŸ“Š ν—€λ“œλΌμΈ (1.9.81/93/113/152/162/192/197/204/207/209): ${parts.join(' Β· ')}`));
4281
4723
  }
4282
4724
  } catch {}
4283
4725
  }
4726
+
4727
+ // 1.9.209: pre-wake-audit λ³Έλ¬Έ μžλ™ λ…ΈμΆœ (handoff λ§ˆμ§€λ§‰ λΆ€λΆ„, μ‚¬μš©μž λͺ…μ‹œ)
4728
+ // "메인 μ—μ΄μ „νŠΈκ°€ κΉ¨μ–΄λ‚¬μ„λ•Œ, μ„œλΈŒμ—μ΄μ „νŠΈκ°€ μ •λ¦¬ν•œ λ‚΄μš©μ„ 확인"
4729
+ try {
4730
+ const pwState = _loadPreWakeReport(root);
4731
+ if (pwState.reports.length > 0) {
4732
+ const latest = pwState.reports[pwState.reports.length - 1];
4733
+ const ageMin = Math.floor((Date.now() - new Date(latest.auditedAt).getTime()) / 60000);
4734
+ // 4μ‹œκ°„ 이내 λ³΄κ³ μ„œλ§Œ μžλ™ λ…ΈμΆœ (였래된 건 stale)
4735
+ if (ageMin < 240 && (latest.summary.criticalCount > 0 || latest.summary.warningCount > 0)) {
4736
+ const isTty = process.stdout && process.stdout.isTTY;
4737
+ const cy2 = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
4738
+ const rd2 = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
4739
+ const yl2 = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
4740
+ const dm2 = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
4741
+ log('');
4742
+ log(cy2(`## πŸ” 직전 sleep pre-wake-audit (1.9.209, ${ageMin}λΆ„ μ „)`));
4743
+ if (latest.findings.critical && latest.findings.critical.length) {
4744
+ latest.findings.critical.slice(0, 3).forEach(f => log(rd2(` 🚨 [${f.kind}] ${f.detail}`)));
4745
+ }
4746
+ if (latest.findings.warning && latest.findings.warning.length) {
4747
+ latest.findings.warning.slice(0, 3).forEach(f => log(yl2(` ⚠ [${f.kind}] ${f.detail}`)));
4748
+ }
4749
+ log(dm2(` β†’ 상세: leerness pre-wake-audit --last`));
4750
+ }
4751
+ }
4752
+ } catch {}
4284
4753
  // 1.9.8: active rules μžλ™ λ…ΈμΆœ (λ§€ μ„Έμ…˜ μ‹œμž‘ μ‹œ AIμ—κ²Œ λ³΄μž„)
4285
4754
  const activeRules = readRules(root).filter(r => r.status === 'active');
4286
4755
  if (activeRules.length) {
@@ -15238,6 +15707,15 @@ function reviewRequestCmd(root, request) {
15238
15707
  efficiencyHints.push('✨ 좩돌 μ‹ ν˜Έ μ—†μŒ β€” μ¦‰μ‹œ μ§„ν–‰ μ•ˆμ „');
15239
15708
  }
15240
15709
 
15710
+ // 6.5) 1.9.208: ν”Œλž«νΌ/API μ œμ•½ 사전 체크 β€” μ‚¬μš©μž λͺ…μ‹œ ("ν˜ΈμΆœμ†λ„ μ΄ˆλ‹Ή 5회" 같은 κ·œμ • 사전 확인)
15711
+ let constraintsCheck = { matched: [], suggestions: [] };
15712
+ try {
15713
+ constraintsCheck = _checkRequestConstraints(root, text);
15714
+ if (constraintsCheck.matched.length > 0) {
15715
+ efficiencyHints.push(`⚠ ν”Œλž«νΌ μ œμ•½ ${constraintsCheck.matched.length}건 β€” leerness constraints check 둜 상세 확인`);
15716
+ }
15717
+ } catch {}
15718
+
15241
15719
  // 7) proceed ꢌμž₯ (좩돌 critical μ‹œ false)
15242
15720
  const proceed = conflictHints.length < 3 && planConflicts.length === 0;
15243
15721
 
@@ -15252,6 +15730,8 @@ function reviewRequestCmd(root, request) {
15252
15730
  featureConflicts,
15253
15731
  recommendedSteps,
15254
15732
  efficiencyHints,
15733
+ platformConstraints: constraintsCheck.matched,
15734
+ constraintSuggestions: constraintsCheck.suggestions,
15255
15735
  proceed,
15256
15736
  proceedReason: proceed ? 'μ•ˆμ „ β€” 좩돌 μ‹ ν˜Έ < 3 + plan 좩돌 0' : '⚠ 좩돌 critical β€” μ‚¬μš©μž 확인 ν›„ μ§„ν–‰',
15257
15737
  durationMs: dt
@@ -15295,6 +15775,18 @@ function reviewRequestCmd(root, request) {
15295
15775
  log(`## πŸ’‘ 효율 μ œμ•ˆ`);
15296
15776
  efficiencyHints.forEach(h => log(` ${h}`));
15297
15777
  log('');
15778
+ // 1.9.208: ν”Œλž«νΌ/API μ œμ•½ 사전 λ…ΈμΆœ (μ‚¬μš©μž λͺ…μ‹œ)
15779
+ if (constraintsCheck.matched.length > 0) {
15780
+ log(`## 🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (${constraintsCheck.matched.length})`);
15781
+ for (const m of constraintsCheck.matched) {
15782
+ log(` - πŸ“¦ ${m.platform} (docs: ${m.docs || '-'})`);
15783
+ for (const c of (m.constraints || []).slice(0, 3)) {
15784
+ log(` β€’ [${c.kind}] ${c.detail}`);
15785
+ }
15786
+ }
15787
+ log(` β†’ leerness constraints check "${text.slice(0, 40)}…" 둜 상세 확인`);
15788
+ log('');
15789
+ }
15298
15790
  if (recommendedSteps.length) {
15299
15791
  log(`## πŸ“ ꢌμž₯ 단계 (${estimatedType})`);
15300
15792
  recommendedSteps.forEach(s => log(` ${s}`));
@@ -15568,6 +16060,10 @@ async function main() {
15568
16060
  if (cmd === 'resume') return resumeCmd(arg('--path', process.cwd()));
15569
16061
  // 1.9.207: leerness requests <audit|add|list|complete|drop> β€” μ‚¬μš©μž μš”μ²­ λˆ„λ½ 확인 절차 (μ‚¬μš©μž λͺ…μ‹œ)
15570
16062
  if (cmd === 'requests') return requestsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
16063
+ // 1.9.208: leerness constraints <list|check|add> β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ)
16064
+ if (cmd === 'constraints') return constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
16065
+ // 1.9.209: leerness pre-wake-audit β€” sleep μ „ sub-agent audit (μ‚¬μš©μž λͺ…μ‹œ)
16066
+ if (cmd === 'pre-wake-audit') return preWakeAuditCmd(arg('--path', process.cwd()), args[1]);
15571
16067
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
15572
16068
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
15573
16069
  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.207",
3
+ "version": "1.9.209",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",