leerness 1.9.234 β†’ 1.9.236

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,75 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.236 β€” 2026-05-24
4
+
5
+ **πŸ”Œ MCP 66번째 도ꡬ (release_cleanup) + drift check --auto-fix 톡합 (50+ branches μžλ™ 정리).**
6
+
7
+ ### 1. MCP 66번째 도ꡬ β€” `leerness_release_cleanup`
8
+ - 1.9.235 release cleanup CLI λ₯Ό μ™ΈλΆ€ AI 에 λ…ΈμΆœ
9
+ - 인자: `{ path?, apply? (default false), keep? (default 5) }`
10
+ - 응닡: `{ apply, keep, total, merged, unmerged, deleteCount, toDelete[], recent[], unmergedSample[] }`
11
+ - MCP 65 β†’ **66** (+1)
12
+
13
+ ### 2. `drift check --auto-fix` release cleanup 톡합 (1.9.82/225 νŒ¨ν„΄ ν™•μž₯)
14
+ - merged `release/*` branches 50개 초과 μ‹œ μžλ™ 정리
15
+ - μ•ˆμ „: keep 10 (졜근 10개 μœ μ§€), merged 만 μ‚­μ œ (unmerged 보호)
16
+ - 1.9.82 audit --fix β†’ 1.9.225 delivered β†’ 1.9.236 release cleanup (3 λΌμš΄λ“œ λˆ„μ )
17
+
18
+ ### 3. drift 회볡 μ‹œμŠ€ν…œ ν™•μž₯ 맀트릭슀 (4 μžλ™ 회볡 톡합)
19
+ | λΌμš΄λ“œ | μ‹ ν˜Έ | μžλ™ 회볡 |
20
+ |---|---|---|
21
+ | 1.9.39 | critical | session close μžλ™ |
22
+ | 1.9.82 | λ³΄μ•ˆ | audit --fix |
23
+ | 1.9.225 | delivered | requests μžλ™ μ™„λ£Œ |
24
+ | **1.9.236** | **release-branch** | **release cleanup --keep 10** ⭐ |
25
+
26
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.207~235) β€” λͺ¨λ‘ μœ μ§€
27
+
28
+ ### 5. stress-v181 β€” 15/15 PASS
29
+ - 1.9.236 (5): VERSION + MCP 66 등둝 + μ‹€ 호좜 + keep νŒŒλΌλ―Έν„° + drift 톡합 μ½”λ“œ
30
+ - μ„±λŠ₯ (2): cold_start avg 389ms / MCP 66 tools/list 363ms
31
+ - λˆ„μ  νšŒκ·€ (8): 1.9.207~235
32
+
33
+ ### 6. μžλ™ release (98 λΌμš΄λ“œ main-push streak Β· 59 λΌμš΄λ“œ npm publish streak)
34
+
35
+ πŸ”Œ **drift μžλ™ 회볡 4 톡합** β€” leerness κ°€ 슀슀둜 λˆ„μ  폐기물 정리
36
+
37
+ ---
38
+
39
+ ## 1.9.235 β€” 2026-05-23
40
+
41
+ **πŸ—‘ leerness release cleanup CLI β€” local release/* branches 정리 + --keep BUG fix.**
42
+
43
+ ### 1. `leerness release cleanup` μƒˆ λͺ…λ Ή
44
+ - local `release/*` branches 쀑 main 에 merge된 것 μžλ™ 정리
45
+ - κΈ°λ³Έ dry-run (λͺ©λ‘λ§Œ), `--apply` λͺ…μ‹œ μ‹œ μ‹€ μ‚­μ œ
46
+ - `--keep N` (default 5) β€” 졜근 N개 μœ μ§€
47
+ - μ•ˆμ „ κ°€λ“œ: merged 만 μ‚­μ œ, unmerged 보호, ν˜„μž¬ branch 보호
48
+ - `--json` μ˜΅μ…˜
49
+
50
+ ### 2. μ‹€ μΈ‘μ • β€” leerness 자체 λˆ„μ  191개
51
+ - merged: 191개 / unmerged: 0개
52
+ - keep 5 β†’ μ‚­μ œ 후보 186개 (1.9.6~229)
53
+ - μœ μ§€: 1.9.234, 1.9.233, 1.9.232, 1.9.231, 1.9.230 (top 5 μ΅œμ‹ )
54
+ - handoff abnormal-shutdown `release-branch-pending` μ‹ ν˜Έ κ°€μ€‘μΉ˜ ν•΄μ†Œ κ°€λŠ₯
55
+
56
+ ### 3. πŸ› `--keep` ν”Œλž˜κ·Έ nonFlagArgs 등둝 (BUG fix)
57
+ - κΈ°μ‘΄: `--keep 10`의 `10`이 non-flag arg 둜 잘λͺ» λΆ„λ₯˜ β†’ root 인자 됨
58
+ - 1.9.235 fix: `withValue.Set` 에 `--keep` μΆ”κ°€ β†’ 정상 인식
59
+
60
+ ### 4. λˆ„μ  νšŒκ·€ (1.9.207~234) β€” λͺ¨λ‘ μœ μ§€
61
+
62
+ ### 5. stress-v180 β€” 16/16 PASS
63
+ - 1.9.235 (7): VERSION + dry-run + --keep + μ •λ ¬ + μ•ˆμ „ κ°€λ“œ + git fallback + human 좜λ ₯
64
+ - μ„±λŠ₯ (2): cold_start avg 353ms / release cleanup 701ms
65
+ - λˆ„μ  νšŒκ·€ (7): 1.9.207~234
66
+
67
+ ### 6. μžλ™ release (97 λΌμš΄λ“œ main-push streak Β· 58 λΌμš΄λ“œ npm publish streak)
68
+
69
+ πŸ—‘ **운영 λˆ„μ  정리 μ‹œμŠ€ν…œ** β€” 191 branches λˆ„μ  β†’ 5개 μœ μ§€ κ°€λŠ₯ (handoff μ‹ ν˜Έ ν•΄μ†Œ)
70
+
71
+ ---
72
+
3
73
  ## 1.9.234 β€” 2026-05-23
4
74
 
5
75
  **πŸ“œ handoff/session close/health --json recentChanges 톡합 (8 ν•„λ“œ μ™„μ„±) β€” μ™ΈλΆ€ AI 졜근 5 λΌμš΄λ“œ μ¦‰μ‹œ 인지.**
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.234-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v179-17%2F17-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-190-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-96_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-100%2F100_(5μΆ•)-brightgreen)]() [![json-8fields](https://img.shields.io/badge/πŸ“œ_JSON_톡합-8%2F8_ν•„λ“œ-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-376ms-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.236-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v181-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-66-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-52-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-192-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-98_rounds-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-100%2F100_(5μΆ•)-brightgreen)]() [![drift-recovery](https://img.shields.io/badge/πŸ”Œ_drift--auto--fix-4_톡합-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-389ms-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.234 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.236 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸ“œ JSON 8 ν•„λ“œ μ™„μ„± (3 λͺ…λ Ή Γ— 8 = 24 톡합 포인트) β•‘
17
+ β•‘ πŸ”Œ MCP 66 Β· drift --auto-fix 4 톡합 (release cleanup) β•‘
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.234';
10
+ const VERSION = '1.9.236';
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];
@@ -12156,6 +12156,95 @@ function releaseSyncMainCmd(root) {
12156
12156
  }
12157
12157
  }
12158
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
+
12159
12248
  // 1.9.178: NPM μžλ™ 배포 (μ‚¬μš©μž λͺ…μ‹œ β€” release sync-main ν›„ μžλ™ trigger).
12160
12249
  // λ³΄μ•ˆ:
12161
12250
  // - NPM_TOKEN λ˜λŠ” LEERNESS_NPM_TOKEN ν™˜κ²½λ³€μˆ˜μ—μ„œλ§Œ 읽음 (κ°’ μ ˆλŒ€ 둜그 X)
@@ -13391,6 +13480,42 @@ function driftCheckCmd(root, opts = {}) {
13391
13480
  log(`⚠ delivered auto-apply 였λ₯˜ (1.9.225): ${e.message}`);
13392
13481
  }
13393
13482
  }
13483
+ // 1.9.236: drift check --auto-fix 에 release cleanup 톡합 (1.9.235 회수)
13484
+ // λˆ„μ λœ 50개+ release/* branches β†’ abnormal-shutdown release-branch-pending μ‹ ν˜Έ 가쀑
13485
+ // μ•ˆμ „: keep 10 (졜근 10개 μœ μ§€), merged 만 μ‚­μ œ (1.9.235 μ•ˆμ „ κ°€λ“œ)
13486
+ // μž„κ³„: 50개 초과 μ‹œλ§Œ μžλ™ 정리 (μ†ŒλŸ‰ λˆ„μ μ€ 정상 운영)
13487
+ if (autoFix) {
13488
+ try {
13489
+ const branchR = cp.spawnSync('git', ['branch', '--merged', 'main', '--list', 'release/*'], { cwd: root, encoding: 'utf8' });
13490
+ if (branchR.status === 0) {
13491
+ const merged = (branchR.stdout || '').split('\n')
13492
+ .map(l => l.replace(/^\*?\s+/, '').trim())
13493
+ .filter(l => l && /^release\/\d+\.\d+\.\d+$/.test(l));
13494
+ if (merged.length > 50) {
13495
+ log('');
13496
+ log(`πŸ—‘ --auto-fix ν™œμ„± (1.9.236) β€” release/* merged ${merged.length}개 (50+) μžλ™ 정리 (keep 10)...`);
13497
+ // μ •λ ¬ (semver desc)
13498
+ merged.sort((a, b) => {
13499
+ const va = a.replace('release/', '').split('.').map(n => parseInt(n, 10) || 0);
13500
+ const vb = b.replace('release/', '').split('.').map(n => parseInt(n, 10) || 0);
13501
+ for (let i = 0; i < 3; i++) if (va[i] !== vb[i]) return vb[i] - va[i];
13502
+ return 0;
13503
+ });
13504
+ const currentBranchR = cp.spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], { cwd: root, encoding: 'utf8' });
13505
+ const currentBranch = (currentBranchR.stdout || '').trim();
13506
+ const toDelete = merged.slice(10).filter(b => b !== currentBranch);
13507
+ let ok = 0;
13508
+ for (const b of toDelete) {
13509
+ const r = cp.spawnSync('git', ['branch', '-d', b], { cwd: root, encoding: 'utf8' });
13510
+ if (r.status === 0) ok++;
13511
+ }
13512
+ log(`βœ“ release cleanup μžλ™ μ™„λ£Œ ${ok}/${toDelete.length}건 (keep 10)`);
13513
+ }
13514
+ }
13515
+ } catch (e) {
13516
+ log(`⚠ release cleanup auto-fix 였λ₯˜ (1.9.236): ${e.message}`);
13517
+ }
13518
+ }
13394
13519
  if (autoFix && level === 'πŸ”΄ critical' && !hasSecurityFired) {
13395
13520
  log('');
13396
13521
  log(`πŸ”§ --auto-fix ν™œμ„± β€” session close μžλ™ μ‹€ν–‰ 쀑...`);
@@ -14379,7 +14504,8 @@ function mcpServeCmd(root) {
14379
14504
  { name: 'leerness_round_history', description: '1.9.226 β€” 자율 λΌμš΄λ“œ 톡계. git tag v1.9.X 기반 λˆ„μ  λΌμš΄λ“œ 카운트 + λ‹€μŒ λ§ˆμΌμŠ€ν†€ (50/75/100/125/150/175/200/250/300/400/500) + 평균 rounds/day. 응닡: { currentVersion, roundCount, baselineVersion, latestTags[], nextMilestone, roundsToNextMilestone, firstTagAt, latestTagAt, daysActive, avgRoundsPerDay }. μ™ΈλΆ€ AIκ°€ "이 ν”„λ‘œμ νŠΈλŠ” μ–Όλ§ˆλ‚˜ 진행됐고 λ‹€μŒ λ§ˆμΌμŠ€ν†€κΉŒμ§€ λͺ‡ λΌμš΄λ“œ λ‚¨μ•˜λ‚˜?"λ₯Ό 회수. 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
14380
14505
  { name: 'leerness_milestones', description: '1.9.229 (1.9.226 ν™•μž₯) β€” 도달 λ§ˆμΌμŠ€ν†€ + λ‹€μŒ ETA. git tag 순차 λΆ„μ„μœΌλ‘œ 25/50/75/100/125/150/175/200/250/300/400/500 λ§ˆμΌμŠ€ν†€ 도달 일자 + λ‹€μŒ λ§ˆμΌμŠ€ν†€ ETA (ν˜„μž¬ 속도 κΈ°μ€€) 계산. 응닡: { totalRounds, reached: [{milestone, version, reachedAt, daysFromBaseline}], next: {milestone, roundsRemaining, etaDays, etaDate}, baselineAt, avgRoundsPerDay }. μ™ΈλΆ€ AIκ°€ "μ§€κΈˆκΉŒμ§€ μ–΄λ–€ λ§ˆμΌμŠ€ν†€μ„ μ–Έμ œ λ‹¬μ„±ν–ˆκ³  λ‹€μŒ λ§ˆμΌμŠ€ν†€ μ˜ˆμƒ 도달일은?"을 회수. 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
14381
14506
  { name: 'leerness_pulse', description: '1.9.231 β€” ν•œ 쀄 μ’…ν•© μš”μ•½ (10 핡심 μ§€ν‘œ). 응닡: { version, roundCount, mcpTools, memorySurface, security, health, driftScore, nextMilestone, etaDays, abnormalShutdown }. μ™ΈλΆ€ AIκ°€ "leerness μƒνƒœ ν•œ λˆˆμ— 보기"λ₯Ό κ°€λ²Όμš΄ 단일 호좜둜 회수. handoff 보닀 5λ°° 빠름 (drift/health 계산 skip). 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
14382
- { name: 'leerness_commands', description: '1.9.233 β€” μΉ΄ν…Œκ³ λ¦¬ν™”λœ 전체 CLI λͺ…λ Ή λͺ©λ‘ (9 μΉ΄ν…Œκ³ λ¦¬). 응닡: { version, totalCommands, categories: { status, task, memory, audit, workflow, release, skill, bridge, config } }. μ™ΈλΆ€ AIκ°€ "leerness κ°€ μ œκ³΅ν•˜λŠ” λͺ…령이 뭐가 μžˆλ‚˜"λ₯Ό 직접 회수. 맀뉴얼/도움말 동적 생성. 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } }
14507
+ { name: 'leerness_commands', description: '1.9.233 β€” μΉ΄ν…Œκ³ λ¦¬ν™”λœ 전체 CLI λͺ…λ Ή λͺ©λ‘ (9 μΉ΄ν…Œκ³ λ¦¬). 응닡: { version, totalCommands, categories: { status, task, memory, audit, workflow, release, skill, bridge, config } }. μ™ΈλΆ€ AIκ°€ "leerness κ°€ μ œκ³΅ν•˜λŠ” λͺ…령이 뭐가 μžˆλ‚˜"λ₯Ό 직접 회수. 맀뉴얼/도움말 동적 생성. 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
14508
+ { name: 'leerness_release_cleanup', description: '1.9.236 (1.9.235 μžλ™ 회수) β€” local release/* branches 정리. main 에 merge된 κ²ƒλ§Œ 후보 (unmerged 보호, ν˜„μž¬ branch 보호). 응닡: { apply, keep, total, merged, unmerged, deleteCount, toDelete[], recent[], unmergedSample[] }. μ™ΈλΆ€ AIκ°€ "운영 λˆ„μ  release branches 정리 κ°€λŠ₯?"을 회수. κΈ°λ³Έ dry-run, apply: true μ‹œμ—λ§Œ μ‹€ μ‚­μ œ. 인자: { path?, apply? (default false), keep? (default 5) }', inputSchema: { type: 'object', properties: { path: { type: 'string' }, apply: { type: 'boolean' }, keep: { type: 'number' } } } }
14383
14509
  ];
14384
14510
 
14385
14511
  function send(obj) {
@@ -14556,6 +14682,12 @@ function mcpServeCmd(root) {
14556
14682
  // 1.9.233: μΉ΄ν…Œκ³ λ¦¬ν™”λœ 전체 CLI λͺ…λ Ή λͺ©λ‘
14557
14683
  cliArgs = ['commands', '--json'];
14558
14684
  break;
14685
+ case 'leerness_release_cleanup':
14686
+ // 1.9.236 (1.9.235): local release/* branches 정리
14687
+ cliArgs = ['release', 'cleanup', '--json'];
14688
+ if (args.apply === true) cliArgs.push('--apply');
14689
+ if (typeof args.keep === 'number') cliArgs.push('--keep', String(args.keep));
14690
+ break;
14559
14691
  default:
14560
14692
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
14561
14693
  }
@@ -18248,6 +18380,8 @@ async function main() {
18248
18380
  if (cmd === 'release' && args[1] === 'publish') return releasePublish(args[2] || arg('--path', process.cwd()));
18249
18381
  if (cmd === 'release' && args[1] === 'pack') return await releasePackCmd(args[2] || arg('--path', process.cwd()));
18250
18382
  if (cmd === 'release' && args[1] === 'sync-main') return releaseSyncMainCmd(args[2] || arg('--path', process.cwd()));
18383
+ // 1.9.235: leerness release cleanup β€” local release/* branches 정리 (merged 만, --apply μ‹œ μ‹€ μ‚­μ œ)
18384
+ if (cmd === 'release' && args[1] === 'cleanup') return releaseCleanupCmd((args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd()));
18251
18385
  // 1.9.141: feature causality graph
18252
18386
  if (cmd === 'feature' && args[1] === 'add') return featureAddCmd(arg('--path', process.cwd()), args.slice(2).filter(x => !x.startsWith('--')).join(' '));
18253
18387
  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.234",
3
+ "version": "1.9.236",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",