leerness 1.9.233 β†’ 1.9.235

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,70 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.235 β€” 2026-05-23
4
+
5
+ **πŸ—‘ leerness release cleanup CLI β€” local release/* branches 정리 + --keep BUG fix.**
6
+
7
+ ### 1. `leerness release cleanup` μƒˆ λͺ…λ Ή
8
+ - local `release/*` branches 쀑 main 에 merge된 것 μžλ™ 정리
9
+ - κΈ°λ³Έ dry-run (λͺ©λ‘λ§Œ), `--apply` λͺ…μ‹œ μ‹œ μ‹€ μ‚­μ œ
10
+ - `--keep N` (default 5) β€” 졜근 N개 μœ μ§€
11
+ - μ•ˆμ „ κ°€λ“œ: merged 만 μ‚­μ œ, unmerged 보호, ν˜„μž¬ branch 보호
12
+ - `--json` μ˜΅μ…˜
13
+
14
+ ### 2. μ‹€ μΈ‘μ • β€” leerness 자체 λˆ„μ  191개
15
+ - merged: 191개 / unmerged: 0개
16
+ - keep 5 β†’ μ‚­μ œ 후보 186개 (1.9.6~229)
17
+ - μœ μ§€: 1.9.234, 1.9.233, 1.9.232, 1.9.231, 1.9.230 (top 5 μ΅œμ‹ )
18
+ - handoff abnormal-shutdown `release-branch-pending` μ‹ ν˜Έ κ°€μ€‘μΉ˜ ν•΄μ†Œ κ°€λŠ₯
19
+
20
+ ### 3. πŸ› `--keep` ν”Œλž˜κ·Έ nonFlagArgs 등둝 (BUG fix)
21
+ - κΈ°μ‘΄: `--keep 10`의 `10`이 non-flag arg 둜 잘λͺ» λΆ„λ₯˜ β†’ root 인자 됨
22
+ - 1.9.235 fix: `withValue.Set` 에 `--keep` μΆ”κ°€ β†’ 정상 인식
23
+
24
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.207~234) β€” λͺ¨λ‘ μœ μ§€
25
+
26
+ ### 5. stress-v180 β€” 16/16 PASS
27
+ - 1.9.235 (7): VERSION + dry-run + --keep + μ •λ ¬ + μ•ˆμ „ κ°€λ“œ + git fallback + human 좜λ ₯
28
+ - μ„±λŠ₯ (2): cold_start avg 353ms / release cleanup 701ms
29
+ - λˆ„μ  νšŒκ·€ (7): 1.9.207~234
30
+
31
+ ### 6. μžλ™ release (97 λΌμš΄λ“œ main-push streak Β· 58 λΌμš΄λ“œ npm publish streak)
32
+
33
+ πŸ—‘ **운영 λˆ„μ  정리 μ‹œμŠ€ν…œ** β€” 191 branches λˆ„μ  β†’ 5개 μœ μ§€ κ°€λŠ₯ (handoff μ‹ ν˜Έ ν•΄μ†Œ)
34
+
35
+ ---
36
+
37
+ ## 1.9.234 β€” 2026-05-23
38
+
39
+ **πŸ“œ handoff/session close/health --json recentChanges 톡합 (8 ν•„λ“œ μ™„μ„±) β€” μ™ΈλΆ€ AI 졜근 5 λΌμš΄λ“œ μ¦‰μ‹œ 인지.**
40
+
41
+ ### 1. handoff/session close/health --json 8번째 톡합 ν•„λ“œ `recentChanges`
42
+ - git tag 기반 졜근 5 λΌμš΄λ“œμ˜ 핡심 λ³€κ²½ μžλ™ 회수
43
+ - ν˜•μ‹: `[{ version, date, subject }]` (κ°€μž₯ μ΅œκ·ΌλΆ€ν„°)
44
+ - AI κ°€ "졜근 무엇이 λ°”λ€Œμ—ˆλ‚˜?"λ₯Ό 단일 호좜둜 인지 β†’ μ»¨ν…μŠ€νŠΈ μ ˆμ•½
45
+ - 1.9.218 (4 ν•„λ“œ) β†’ 1.9.223 (5) β†’ 1.9.227 (6) β†’ 1.9.230 (7) β†’ 1.9.234 (**8 ν•„λ“œ**)
46
+
47
+ ### 2. 3 λͺ…λ Ή 일관성 맀트릭슀 β€” 3 Γ— 8 = 24 톡합 포인트
48
+ - handoff/session close/health 동일 ν˜•μ‹ + 동일 κ²°κ³Ό
49
+ - μ™ΈλΆ€ μžλ™ν™”/CI μ–΄λŠ λͺ…λ ΉμœΌλ‘œ μ‘°νšŒν•΄λ„ 동일 응닡
50
+
51
+ ### 3. AI 인지 λΉ„μš© 절감
52
+ - κΈ°μ‘΄: AI κ°€ CHANGELOG.md 읽기 β†’ 토큰 λ‹€λŸ‰ μ†ŒλΉ„
53
+ - 1.9.234: handoff --json ν•œ λ²ˆμ— 졜근 5 λΌμš΄λ“œ μžλ™ 회수 β†’ ~500 토큰 μ ˆμ•½
54
+
55
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.207~233) β€” λͺ¨λ‘ μœ μ§€
56
+
57
+ ### 5. stress-v179 β€” 17/17 PASS
58
+ - 1.9.234 (7): VERSION + handoff/session close/health JSON + 3 λͺ…λ Ή 일관성 + 8 ν•„λ“œ 검증 + ν˜•νƒœ 검증
59
+ - μ„±λŠ₯ (2): cold_start avg 376ms / handoff --json (8 ν•„λ“œ) 1109ms
60
+ - λˆ„μ  νšŒκ·€ (8): 1.9.207~233
61
+
62
+ ### 6. μžλ™ release (96 λΌμš΄λ“œ main-push streak Β· 57 λΌμš΄λ“œ npm publish streak)
63
+
64
+ πŸ“œ **JSON 톡합 맀트릭슀 8 ν•„λ“œ μ™„μ„±** β€” μ™ΈλΆ€ AI κ°€ leerness μƒνƒœ + 졜근 λ³€κ²½ λͺ¨λ‘ ν•œ λ²ˆμ— 회수
65
+
66
+ ---
67
+
3
68
  ## 1.9.233 β€” 2026-05-23
4
69
 
5
70
  **πŸ“š leerness commands μƒˆ λͺ…λ Ή (9 μΉ΄ν…Œκ³ λ¦¬ 51 CLI) + MCP 65번째 도ꡬ.**
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.233-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v178-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-65-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-51-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-189-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-95_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-100%2F100_(5μΆ•)-brightgreen)]() [![commands](https://img.shields.io/badge/πŸ“š_commands-9_μΉ΄ν…Œκ³ λ¦¬_51_λͺ…λ Ή-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-413ms-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.235-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v180-16%2F16-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-65-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-52-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-191-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-97_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-100%2F100_(5μΆ•)-brightgreen)]() [![cleanup](https://img.shields.io/badge/πŸ—‘_release-cleanup_CLI-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-353ms-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.233 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.235 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸ“š leerness commands Β· 9 μΉ΄ν…Œκ³ λ¦¬ Β· 51 CLI Β· MCP 65 β•‘
17
+ β•‘ πŸ—‘ release cleanup Β· 191 branches λˆ„μ  정리 + BUG fix β•‘
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.233';
10
+ const VERSION = '1.9.235';
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 처리).
@@ -180,7 +180,7 @@ function arg(name, def = null) { const i = process.argv.indexOf(name); return i
180
180
  function has(name) { return process.argv.includes(name); }
181
181
  function nonFlagArgs() {
182
182
  const out = [];
183
- const withValue = new Set(['--language','--skills','--path','--status','--progress','--goal','--reason','--next','--target','--token-env','--package','--out','--from','--repo','--id','--note','--evidence','--query','--limit','--action','--agent','--tool','--doc','--command','--capability','--before','--after','--display','--threshold','--trigger','--check','--set','--min-score','--include','--days','--gh-pages-src','--roadmap','--since','--agents','--model','--timeout','--retry-on-fail','--label','--score','--tokens','--alternatives','--impact','--tag','--surface','--depends-on','--affects','--co-changes-with','--files','--branch','--remote','--task-add','--next-action','--role','--provider','--env-var','--deploy','--token-lifetime-hours','--port','--secret']);
183
+ const withValue = new Set(['--language','--skills','--path','--status','--progress','--goal','--reason','--next','--target','--token-env','--package','--out','--from','--repo','--id','--note','--evidence','--query','--limit','--action','--agent','--tool','--doc','--command','--capability','--before','--after','--display','--threshold','--trigger','--check','--set','--min-score','--include','--days','--gh-pages-src','--roadmap','--since','--agents','--model','--timeout','--retry-on-fail','--label','--score','--tokens','--alternatives','--impact','--tag','--surface','--depends-on','--affects','--co-changes-with','--files','--branch','--remote','--task-add','--next-action','--role','--provider','--env-var','--deploy','--token-lifetime-hours','--port','--secret','--keep']);
184
184
  const a = process.argv.slice(2);
185
185
  for (let i = 0; i < a.length; i++) {
186
186
  const x = a[i];
@@ -2196,6 +2196,36 @@ function milestonesCmd(root) {
2196
2196
  }
2197
2197
  }
2198
2198
 
2199
+ // 1.9.234: recentChanges β€” 졜근 N λΌμš΄λ“œμ˜ 핡심 λ³€κ²½ μš”μ•½ (git tag commit subject)
2200
+ // 응닡: [{ version, date, subject }] (κ°€μž₯ μ΅œκ·ΌλΆ€ν„°)
2201
+ function _computeRecentChanges(root, limit = 5) {
2202
+ const items = [];
2203
+ try {
2204
+ const r = cp.spawnSync('git', ['log', '--tags', '--simplify-by-decoration', '--pretty=format:%H|%D|%ad|%s', '--date=short', '-n', String(limit * 3)], {
2205
+ cwd: root, encoding: 'utf8', timeout: 5000
2206
+ });
2207
+ if (r.status !== 0 || !r.stdout) return items;
2208
+ const lines = r.stdout.split('\n').filter(l => l.trim());
2209
+ for (const line of lines) {
2210
+ const idx1 = line.indexOf('|');
2211
+ if (idx1 < 0) continue;
2212
+ const idx2 = line.indexOf('|', idx1 + 1);
2213
+ const idx3 = line.indexOf('|', idx2 + 1);
2214
+ if (idx2 < 0 || idx3 < 0) continue;
2215
+ const refs = line.slice(idx1 + 1, idx2);
2216
+ const date = line.slice(idx2 + 1, idx3);
2217
+ const subject = line.slice(idx3 + 1);
2218
+ const m = refs.match(/tag: v(\d+\.\d+\.\d+)/);
2219
+ if (!m) continue;
2220
+ const version = m[1];
2221
+ const cleanSubject = subject.replace(/^\d+\.\d+\.\d+\s*[β€”-]\s*/, '').trim();
2222
+ items.push({ version, date, subject: cleanSubject.slice(0, 100) });
2223
+ if (items.length >= limit) break;
2224
+ }
2225
+ } catch {}
2226
+ return items;
2227
+ }
2228
+
2199
2229
  // 1.9.231: leerness pulse β€” ν•œ 쀄 μ’…ν•© μš”μ•½ (10 핡심 μ§€ν‘œ)
2200
2230
  // handoff ν—€λ“œλΌμΈμ˜ μΆ•μ•½ 버전 β€” μ‚¬μš©μžκ°€ λΉ λ₯΄κ²Œ μƒνƒœ νŒŒμ•…
2201
2231
  // 응닡 (--json): { version, roundCount, mcpTools, memorySurface, security, health, driftScore, nextMilestone, etaDays, abnormal }
@@ -6206,6 +6236,10 @@ function handoff(root) {
6206
6236
  avgRoundsPerDay: ms.avgRoundsPerDay
6207
6237
  };
6208
6238
  } catch {}
6239
+ // 1.9.234: recentChanges 톡합 (handoff JSON 8번째 톡합 ν•„λ“œ) β€” 졜근 5 λΌμš΄λ“œ λ³€κ²½ μš”μ•½
6240
+ try {
6241
+ result.recentChanges = _computeRecentChanges(root, 5);
6242
+ } catch {}
6209
6243
  } catch {}
6210
6244
  try {
6211
6245
  const pwState = _loadPreWakeReport(root);
@@ -10365,6 +10399,10 @@ function sessionClose(root, opts = {}) {
10365
10399
  avgRoundsPerDay: ms.avgRoundsPerDay
10366
10400
  };
10367
10401
  } catch {}
10402
+ // 1.9.234: recentChanges 톡합 (session close JSON 8번째 톡합 ν•„λ“œ) β€” 졜근 5 λΌμš΄λ“œ λ³€κ²½
10403
+ try {
10404
+ jsonResult.recentChanges = _computeRecentChanges(root, 5);
10405
+ } catch {}
10368
10406
  } catch {}
10369
10407
  try {
10370
10408
  // 1.9.209: pre-wake-audit μžλ™ μ‹€ν–‰ + μ €μž₯ (sleep μ „ μžλ™ 점검)
@@ -12118,6 +12156,95 @@ function releaseSyncMainCmd(root) {
12118
12156
  }
12119
12157
  }
12120
12158
 
12159
+ // 1.9.235: leerness release cleanup β€” local release/* branches 정리 (main 에 merge된 것)
12160
+ // 문제: λ§€ λΌμš΄λ“œ release/X.Y.Z κ°€ λˆ„μ  β†’ handoff abnormal-shutdown release-branch-pending μ‹ ν˜Έ 가쀑
12161
+ // κΈ°λ³Έ: dry-run (μ‚­μ œ 후보 ν‘œμ‹œ) Β· --apply λͺ…μ‹œ μ‹œ μ‹€ μ‚­μ œ Β· --keep N (졜근 N개 μœ μ§€, default 5)
12162
+ // μ•ˆμ „: main 에 mergeλ˜μ§€ μ•Šμ€ branch λŠ” μ ˆλŒ€ μ‚­μ œ X Β· ν˜„μž¬ branch 도 보호
12163
+ function releaseCleanupCmd(root) {
12164
+ root = absRoot(root || process.cwd());
12165
+ const isTty = process.stdout && process.stdout.isTTY;
12166
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
12167
+ const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
12168
+ const yl = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
12169
+ const rd = s => isTty ? `\x1b[31m${s}\x1b[0m` : s;
12170
+ const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
12171
+ const apply = has('--apply');
12172
+ const keep = parseInt(arg('--keep', '5'), 10) || 5;
12173
+ // 1) git μ €μž₯μ†Œ 확인
12174
+ const headR = cp.spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: root, encoding: 'utf8' });
12175
+ if (headR.status !== 0) {
12176
+ log(rd('# leerness release cleanup (1.9.235) β€” git μ €μž₯μ†Œ μ•„λ‹˜'));
12177
+ return;
12178
+ }
12179
+ const currentBranch = (headR.stdout || '').trim();
12180
+ // 2) local release/* branches + merged μƒνƒœ 회수
12181
+ const branchR = cp.spawnSync('git', ['branch', '--merged', 'main', '--list', 'release/*'], { cwd: root, encoding: 'utf8' });
12182
+ const mergedBranches = (branchR.stdout || '').split('\n')
12183
+ .map(l => l.replace(/^\*?\s+/, '').trim())
12184
+ .filter(l => l && /^release\/\d+\.\d+\.\d+$/.test(l));
12185
+ // 3) λͺ¨λ“  release/* 도 회수 (unmerged λΆ„μ„μš©)
12186
+ const allR = cp.spawnSync('git', ['branch', '--list', 'release/*'], { cwd: root, encoding: 'utf8' });
12187
+ const allBranches = (allR.stdout || '').split('\n')
12188
+ .map(l => l.replace(/^\*?\s+/, '').trim())
12189
+ .filter(l => l && /^release\/\d+\.\d+\.\d+$/.test(l));
12190
+ const unmergedBranches = allBranches.filter(b => !mergedBranches.includes(b));
12191
+ // 4) μ •λ ¬ (semver desc) + keep N + ν˜„μž¬ branch μ œμ™Έ
12192
+ mergedBranches.sort((a, b) => {
12193
+ const va = a.replace('release/', '').split('.').map(n => parseInt(n, 10) || 0);
12194
+ const vb = b.replace('release/', '').split('.').map(n => parseInt(n, 10) || 0);
12195
+ for (let i = 0; i < 3; i++) if (va[i] !== vb[i]) return vb[i] - va[i];
12196
+ return 0;
12197
+ });
12198
+ const recent = mergedBranches.slice(0, keep);
12199
+ const toDelete = mergedBranches.slice(keep).filter(b => b !== currentBranch);
12200
+ // 5) JSON
12201
+ if (has('--json')) {
12202
+ log(JSON.stringify({
12203
+ apply, keep, total: allBranches.length, merged: mergedBranches.length,
12204
+ unmerged: unmergedBranches.length, deleteCount: toDelete.length,
12205
+ toDelete, recent: recent.slice(0, keep), unmergedSample: unmergedBranches.slice(0, 5)
12206
+ }, null, 2));
12207
+ return;
12208
+ }
12209
+ // 6) Human 좜λ ₯
12210
+ log(cy(`# leerness release cleanup (1.9.235) β€” local release/* branches 정리`));
12211
+ log('');
12212
+ log(` πŸ“Š 총 ${allBranches.length}개 Β· merged ${mergedBranches.length}개 Β· unmerged ${unmergedBranches.length}개`);
12213
+ log(` πŸ“ ν˜„μž¬ branch: ${currentBranch}${currentBranch.startsWith('release/') ? yl(' (보호)') : ''}`);
12214
+ log(` πŸ›‘ keep 졜근 ${keep}개 μœ μ§€`);
12215
+ log('');
12216
+ if (toDelete.length === 0) {
12217
+ log(gr(` βœ“ 정리 λŒ€μƒ μ—†μŒ (merged ${mergedBranches.length} ≀ keep ${keep})`));
12218
+ if (unmergedBranches.length > 0) {
12219
+ log('');
12220
+ log(yl(` ⚠ unmerged ${unmergedBranches.length}개 β€” sync-main λ―Έμ§„ν–‰. 정리 λŒ€μƒ μ•„λ‹˜:`));
12221
+ unmergedBranches.slice(0, 5).forEach(b => log(` β€’ ${b}`));
12222
+ if (unmergedBranches.length > 5) log(dm(` ... +${unmergedBranches.length - 5}개`));
12223
+ }
12224
+ return;
12225
+ }
12226
+ log(yl(` πŸ—‘ μ‚­μ œ 후보 ${toDelete.length}개 (merged + keep λ²”μœ„ μ™Έ):`));
12227
+ toDelete.slice(0, 10).forEach(b => log(` β€’ ${b}`));
12228
+ if (toDelete.length > 10) log(dm(` ... +${toDelete.length - 10}개`));
12229
+ log('');
12230
+ log(` πŸ“ μœ μ§€ ${recent.length}개:`);
12231
+ recent.slice(0, 5).forEach(b => log(` βœ“ ${b}`));
12232
+ if (recent.length > 5) log(dm(` ... +${recent.length - 5}개`));
12233
+ log('');
12234
+ if (apply) {
12235
+ let ok = 0, fail = 0;
12236
+ for (const b of toDelete) {
12237
+ const r = cp.spawnSync('git', ['branch', '-d', b], { cwd: root, encoding: 'utf8' });
12238
+ if (r.status === 0) ok++;
12239
+ else fail++;
12240
+ }
12241
+ log(gr(` βœ“ μ‚­μ œ μ™„λ£Œ: ${ok}/${toDelete.length}건`) + (fail > 0 ? rd(` Β· μ‹€νŒ¨ ${fail}`) : ''));
12242
+ } else {
12243
+ log(dm(` β†’ 적용: leerness release cleanup --apply (μ•ˆμ „: merged만 μ‚­μ œ, ν˜„μž¬ branch 보호)`));
12244
+ log(dm(` β†’ --keep N: 졜근 N개 μœ μ§€ (default 5)`));
12245
+ }
12246
+ }
12247
+
12121
12248
  // 1.9.178: NPM μžλ™ 배포 (μ‚¬μš©μž λͺ…μ‹œ β€” release sync-main ν›„ μžλ™ trigger).
12122
12249
  // λ³΄μ•ˆ:
12123
12250
  // - NPM_TOKEN λ˜λŠ” LEERNESS_NPM_TOKEN ν™˜κ²½λ³€μˆ˜μ—μ„œλ§Œ 읽음 (κ°’ μ ˆλŒ€ 둜그 X)
@@ -16738,6 +16865,10 @@ function healthCmd(root) {
16738
16865
  avgRoundsPerDay: ms.avgRoundsPerDay
16739
16866
  };
16740
16867
  } catch { out.milestones = { error: 'milestones 점검 μ‹€νŒ¨' }; }
16868
+ // 1.9.234: health --json recentChanges 톡합 (3 λͺ…λ Ή 8 ν•„λ“œ 일관성)
16869
+ try {
16870
+ out.recentChanges = _computeRecentChanges(root, 5);
16871
+ } catch { out.recentChanges = { error: 'recentChanges 점검 μ‹€νŒ¨' }; }
16741
16872
  // 1.9.163: 5λŠ₯λ ₯ 맀트릭슀 μžλ™ 평가 (1.9.155 sub-agent 점검 β†’ μ½”λ“œ 기반 μžλ™ν™”)
16742
16873
  // 각 λŠ₯λ ₯을 μ½”λ“œ grep 으둜 κ²€μΆœ β†’ 0~100 점수. μ‚¬μš©μžκ°€ λ§€ health 호좜 μ‹œ leerness 자기 평가 확인.
16743
16874
  try {
@@ -18206,6 +18337,8 @@ async function main() {
18206
18337
  if (cmd === 'release' && args[1] === 'publish') return releasePublish(args[2] || arg('--path', process.cwd()));
18207
18338
  if (cmd === 'release' && args[1] === 'pack') return await releasePackCmd(args[2] || arg('--path', process.cwd()));
18208
18339
  if (cmd === 'release' && args[1] === 'sync-main') return releaseSyncMainCmd(args[2] || arg('--path', process.cwd()));
18340
+ // 1.9.235: leerness release cleanup β€” local release/* branches 정리 (merged 만, --apply μ‹œ μ‹€ μ‚­μ œ)
18341
+ if (cmd === 'release' && args[1] === 'cleanup') return releaseCleanupCmd((args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd()));
18209
18342
  // 1.9.141: feature causality graph
18210
18343
  if (cmd === 'feature' && args[1] === 'add') return featureAddCmd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('--')).join(' '));
18211
18344
  if (cmd === 'feature' && args[1] === 'link') return featureLinkCmd(arg('--path', process.cwd()), args[2]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.233",
3
+ "version": "1.9.235",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",