leerness 1.9.208 β†’ 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,50 @@
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
+
3
48
  ## 1.9.208 β€” 2026-05-22
4
49
 
5
50
  **🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ).**
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.208-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v153-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-138-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-70_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-98%2F100_(5μΆ•)-brightgreen)]() [![constraints](https://img.shields.io/badge/constraints-API_사전체크_6plat-success)]() [![perf](https://img.shields.io/badge/cold_start-483ms-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.208 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.209 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ 🚦 ν”Œλž«νΌ/API μ œμ•½ 사전 체크 6μ’… (μ‚¬μš©μž λͺ…μ‹œ) β•‘
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.208';
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 처리).
@@ -2111,6 +2111,146 @@ function _checkRequestConstraints(root, text) {
2111
2111
  return { matched, suggestions, totalPlatforms: Object.keys(catalog.platforms).length };
2112
2112
  }
2113
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
+
2114
2254
  // 1.9.203: μžλ™ λΌμš΄λ“œ plan 정리 β€” μ‚¬μš©μž λͺ…μ‹œ
2115
2255
  // "λ°±κ·ΈλΌμš΄λ“œμ— λ‹€μŒ μž‘μ—…μ΄ μ‹œμž‘κ°€λŠ₯ν•œ μ˜ˆμƒ μ‹œκ°„ + μΌμ–΄λ‚¬μ„λ•Œ ν•΄μ•Όν•˜λŠ” 일을 정리"
2116
2256
  // λΌμš΄λ“œ 마무리 μ‹œ .harness/auto-resume-plan.json μžλ™ μ €μž₯ β†’ λ‹€μŒ wakeup μ‹œ μ¦‰μ‹œ μ‹€ν–‰ κ°€λŠ₯
@@ -2434,6 +2574,93 @@ function constraintsCmd(root, sub, ...rest) {
2434
2574
  process.exit(1);
2435
2575
  }
2436
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
+
2437
2664
  // 1.9.199: wakeup miss μžλ™ 회볡 κ°•ν™” β€” last-handoff timestamp 기둝 + 25min κΈ°λŒ€ interval μ •λ°€ μΈ‘μ •
2438
2665
  // μ‚¬μš©μž λͺ…μ‹œ (1.9.198 후속): "22:01에 λΌμš΄λ“œμ— μžλ™μ§„μž…λ˜μ§€μ•Šμ•˜μ–΄"
2439
2666
  // 1.9.196 task-log mtime 기반 detector λŠ” task-log κ°€ 자주 μ•ˆ μ—…λ°μ΄νŠΈλ˜λ©΄ false positive.
@@ -4477,13 +4704,52 @@ function handoff(root) {
4477
4704
  parts.push(`πŸ“₯ μš”μ²­ ${audit.open} (tracked)`);
4478
4705
  }
4479
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 {}
4480
4719
  if (parts.length) {
4481
4720
  const isTty = process.stdout && process.stdout.isTTY;
4482
4721
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
4483
- 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(' Β· ')}`));
4484
4723
  }
4485
4724
  } catch {}
4486
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 {}
4487
4753
  // 1.9.8: active rules μžλ™ λ…ΈμΆœ (λ§€ μ„Έμ…˜ μ‹œμž‘ μ‹œ AIμ—κ²Œ λ³΄μž„)
4488
4754
  const activeRules = readRules(root).filter(r => r.status === 'active');
4489
4755
  if (activeRules.length) {
@@ -15796,6 +16062,8 @@ async function main() {
15796
16062
  if (cmd === 'requests') return requestsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
15797
16063
  // 1.9.208: leerness constraints <list|check|add> β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ)
15798
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]);
15799
16067
  if (cmd === 'rule' && args[1] === 'add') return ruleAdd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('-')).join(' '));
15800
16068
  if (cmd === 'rule' && args[1] === 'list') return ruleList(arg('--path', process.cwd()));
15801
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.208",
3
+ "version": "1.9.209",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",