leerness 1.9.237 β†’ 1.9.239

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,84 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.239 β€” 2026-05-24
4
+
5
+ **🐍 leerness py-check + πŸ€– agent-mode + MCP 67/68 (μ‚¬μš©μž λͺ…μ‹œ UR-0013).**
6
+
7
+ ### μ‚¬μš©μž λͺ…μ‹œ (UR-0013)
8
+ > *"leernessκ°€ md뿐만 μ•„λ‹ˆλΌ pyλ₯Ό ν¬ν•¨ν•œ 파이썬 슀크립트 등이 νš¨μœ¨μ μ΄λΌκ±°λ‚˜, μ—μ΄μ „νŠΈ λͺ¨λ“œλ₯Ό μœ„ν•œ μž₯치 λ“± 더 효율적으둜 μž‘λ™ν•  수 μžˆλ„λ‘ κ΅¬ν˜„"*
9
+
10
+ ### 1. `leerness py-check` μƒˆ λͺ…λ Ή β€” Python 파일 뢄석
11
+ - μ˜μ‘΄μ„± 0, regex fallback (Node λ‚΄μž₯만)
12
+ - 뢄석: 파일 수 / LOC / imports / functions / classes / TODO/FIXME
13
+ - Top 5 파일 (LOC κΈ°μ€€)
14
+ - `--json` μ˜΅μ…˜ (μ™ΈλΆ€ μžλ™ν™”)
15
+ - `.harness` / `node_modules` / `venv` / `__pycache__` λ“± μžλ™ skip
16
+ - leerness κ°€ .md 외에도 **.py 인지** β€” 닀쀑 μ–Έμ–΄ μ›Œν¬μŠ€νŽ˜μ΄μŠ€ 지원
17
+
18
+ ### 2. `leerness agent-mode <start|tick|stop>` β€” 자율 λͺ¨λ“œ μ „μš© 톡합
19
+ - **start**: handoff + drift --auto-fix + session-resume --auto-fix (μ§„μž… μžλ™ν™”)
20
+ - **tick**: pulse ν•œ 쀄 (λ§€ λΌμš΄λ“œ 가벼움)
21
+ - **stop**: session close --auto-apply-delivered --auto-cleanup-branches (마감 톡합)
22
+ - AI 자율 λΌμš΄λ“œ μ§„μž…/λ§€ λΌμš΄λ“œ/λ§ˆκ°μ„ **1 λͺ…λ ΉμœΌλ‘œ μ••μΆ•** β†’ 호좜 λΉ„μš© 절감
23
+
24
+ ### 3. MCP 67/68번째 도ꡬ
25
+ - `leerness_py_check` β€” μ™ΈλΆ€ AI κ°€ Python ν‘œλ©΄ 뢄석
26
+ - `leerness_agent_mode` β€” μ™ΈλΆ€ AI κ°€ 자율 λͺ¨λ“œ 단일 호좜
27
+ - MCP 66 β†’ 68 (+2)
28
+
29
+ ### 4. 검증 β€” μ‹€ Python 파일 (격리)
30
+ - 2 파일 β†’ 22 LOC / 4 imports / 4 funcs / 2 classes / 2 TODOs (μ •ν™• 회수)
31
+ - 200 file limit μ•ˆμ „ κ°€λ“œ + heavy dir skip
32
+
33
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.207~238) β€” λͺ¨λ‘ μœ μ§€
34
+
35
+ ### 6. stress-v184 β€” 18/18 PASS
36
+ - 1.9.239 (8): VERSION + py-check (격리/human/fallback) + agent-mode (help/tick) + MCP 68 + μ‹€ 호좜
37
+ - μ„±λŠ₯ (2): cold_start avg 372ms / py-check 387ms
38
+ - λˆ„μ  νšŒκ·€ (8): 1.9.207~238
39
+
40
+ ### 7. μžλ™ release (101 λΌμš΄λ“œ main-push streak Β· 62 λΌμš΄λ“œ npm publish streak)
41
+
42
+ 🐍 **leerness 닀쀑 μ–Έμ–΄ 지원 μ‹ μ„€** β€” .md β†’ .md + .py (ν™•μž₯ κ°€λŠ₯ 베이슀)
43
+ πŸ€– **agent-mode 톡합** β€” 자율 λͺ¨λ“œ μ§„μž…/λ§€λΌμš΄λ“œ/λ§ˆκ°μ„ 3 λͺ…λ ΉμœΌλ‘œ 단좕
44
+
45
+ ---
46
+
47
+ ## 1.9.238 β€” 2026-05-24
48
+
49
+ **πŸŽ‰ R100 main-push streak 달성 + CLAUDE/AGENTS/session-workflow drift 차단 (1.9.228~237 9 λΌμš΄λ“œ λˆ„μ ).**
50
+
51
+ ### 1. CLAUDE.md drift 차단 κ°±μ‹  (1.9.228~237 β€” 10 버전 톡합)
52
+ - μ‹ κ·œ μ„Ήμ…˜: "κ°€μ‹œν™” + μžλ™ν™” 9 λΌμš΄λ“œ (1.9.228~237)"
53
+ - 9 λΌμš΄λ“œ κ°œλ³„ ν•­λͺ© + 자율 λͺ¨λ“œ λ§ˆμΌμŠ€ν†€ μš”μ•½
54
+
55
+ ### 2. AGENTS.md drift 차단 κ°±μ‹ 
56
+ - "λΌμš΄λ“œ 진행도 κ°€μ‹œν™” (1.9.226~234)" μ„Ήμ…˜ ν™•μž₯ (8 ν•„λ“œ / MCP 66 / 51 CLI)
57
+ - "release cleanup μƒνƒœκ³„ (1.9.235~237 β€” 3 λΌμš΄λ“œ μ™„μ„±)" μ‹ κ·œ μ„Ήμ…˜
58
+
59
+ ### 3. session-workflow.md drift 차단 κ°±μ‹ 
60
+ - "λΌμš΄λ“œ 진행도 κ°€μ‹œν™” + μƒνƒœ λͺ…λ Ή (1.9.226~234)" β€” 4 μ‹ κ·œ λͺ…λ Ή
61
+ - "JSON 톡합 맀트릭슀 8 ν•„λ“œ (1.9.218~234)" β€” μ§„ν™” history
62
+ - "release cleanup μƒνƒœκ³„ (1.9.235~237)" β€” 3 λΌμš΄λ“œ μ™„μ„±
63
+
64
+ ### 4. πŸŽ‰ R100 main-push streak 달성 (1.9.140 β†’ 1.9.238)
65
+ - 1.9.140 (main μžλ™ push) μ‚¬μš©μž λͺ…μ‹œ λΆ€ν„° μ‹œμž‘
66
+ - 100 λΌμš΄λ“œ 연속 무손싀 μžλ™ commit + tag + push + main sync
67
+ - 1.9.178 λΆ€ν„° npm publish μžλ™ 톡합 (61 λΌμš΄λ“œ 연속)
68
+
69
+ ### 5. λˆ„μ  νšŒκ·€ (1.9.207~237) β€” λͺ¨λ‘ μœ μ§€
70
+
71
+ ### 6. stress-v183 β€” 13/13 PASS
72
+ - 1.9.238 (4): VERSION + CLAUDE 10 버전 + AGENTS κ°±μ‹  + session-workflow κ°±μ‹ 
73
+ - μ„±λŠ₯ (1): cold_start avg 312ms
74
+ - μ’…ν•© λˆ„μ  νšŒκ·€ (8): 1.9.207~237 λͺ¨λ“  μ‹œμŠ€ν…œ
75
+
76
+ ### 7. μžλ™ release (100 λΌμš΄λ“œ main-push streak πŸŽ‰ Β· 61 λΌμš΄λ“œ npm publish streak)
77
+
78
+ πŸŽ‰ **R100 main-push streak 달성** β€” leerness μžμ²΄κ°€ μ•ˆμ •μ„± 검증 μΈμŠ€ν„΄μŠ€
79
+
80
+ ---
81
+
3
82
  ## 1.9.237 β€” 2026-05-24
4
83
 
5
84
  **πŸ—‘ session close --auto-cleanup-branches + handoff body 50+ branches κ²½κ³ .**
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.237-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v182-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-193-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-99_rounds-success)]() [![npm-streak](https://img.shields.io/badge/πŸŽ‰_npm--publish-60_streak-success)]() [![goal-matrix](https://img.shields.io/badge/λͺ©μ _맀트릭슀-100%2F100_(5μΆ•)-brightgreen)]() [![cleanup](https://img.shields.io/badge/πŸ—‘_release--cleanup-3_λΌμš΄λ“œ_μ™„μ„±-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-387ms-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.239-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v184-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-68-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-54-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-195-blueviolet)]() [![main-push](https://img.shields.io/badge/πŸŽ‰_main--push_streak-101_rounds-success)]() [![npm-streak](https://img.shields.io/badge/πŸŽ‰_npm--publish-62_streak-success)]() [![langs](https://img.shields.io/badge/🐍_supported-md_%2B_py-blueviolet)]() [![agent-mode](https://img.shields.io/badge/πŸ€–_agent--mode-start%2Ftick%2Fstop-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-372ms-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.237 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.239 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ πŸŽ‰ 60 npm publish streak Β· release cleanup 3 λΌμš΄λ“œ μ™„μ„± β•‘
17
+ β•‘ 🐍 py-check Β· πŸ€– agent-mode Β· MCP 68 (μ‚¬μš©μž λͺ…μ‹œ UR-0013) β•‘
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.237';
10
+ const VERSION = '1.9.239';
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 처리).
@@ -2196,6 +2196,156 @@ function milestonesCmd(root) {
2196
2196
  }
2197
2197
  }
2198
2198
 
2199
+ // 1.9.239: Python 파일 뢄석 (μ‚¬μš©μž λͺ…μ‹œ UR-0013) β€” μ˜μ‘΄μ„± 0 regex fallback
2200
+ // leerness κ°€ .md 외에 .py 파이썬 μŠ€ν¬λ¦½νŠΈλ„ 효율적으둜 μΈμ§€ν•˜λ„λ‘.
2201
+ // 뢄석: 파일 수 / import 수 / ν•¨μˆ˜ 수 / class 수 / λ³΅μž‘λ„(approx LOC) / TODO/FIXME
2202
+ // 응닡 (--json): { totalFiles, totalLOC, imports[], funcs[], classes[], complexity, todoCount }
2203
+ function _collectPyFiles(root, limit = 100) {
2204
+ const out = [];
2205
+ function walk(dir) {
2206
+ if (out.length >= limit) return;
2207
+ let entries;
2208
+ try { entries = fs.readdirSync(dir, { withFileTypes: true }); }
2209
+ catch { return; }
2210
+ for (const e of entries) {
2211
+ if (out.length >= limit) return;
2212
+ const fp = path.join(dir, e.name);
2213
+ if (e.isDirectory()) {
2214
+ // skip common heavy dirs
2215
+ if (/^(node_modules|\.git|__pycache__|\.harness|venv|\.venv|dist|build|_apps|_reports)$/.test(e.name)) continue;
2216
+ walk(fp);
2217
+ } else if (e.isFile() && /\.py$/i.test(e.name)) {
2218
+ out.push(fp);
2219
+ }
2220
+ }
2221
+ }
2222
+ walk(root);
2223
+ return out;
2224
+ }
2225
+
2226
+ function _analyzePyFile(fp) {
2227
+ let txt = '';
2228
+ try { txt = fs.readFileSync(fp, 'utf8'); } catch { return null; }
2229
+ const lines = txt.split('\n');
2230
+ const importRe = /^\s*(?:from\s+\S+\s+)?import\s+\S+/;
2231
+ const funcRe = /^\s*(?:async\s+)?def\s+(\w+)\s*\(/;
2232
+ const classRe = /^\s*class\s+(\w+)/;
2233
+ const todoRe = /#\s*(TODO|FIXME|XXX|HACK)\b/;
2234
+ let imports = 0, funcs = [], classes = [], todos = 0;
2235
+ for (const line of lines) {
2236
+ if (importRe.test(line)) imports++;
2237
+ const fm = line.match(funcRe); if (fm) funcs.push(fm[1]);
2238
+ const cm = line.match(classRe); if (cm) classes.push(cm[1]);
2239
+ if (todoRe.test(line)) todos++;
2240
+ }
2241
+ return {
2242
+ file: fp,
2243
+ loc: lines.length,
2244
+ imports,
2245
+ funcs: funcs.length,
2246
+ classes: classes.length,
2247
+ todos
2248
+ };
2249
+ }
2250
+
2251
+ function pyCheckCmd(root) {
2252
+ root = absRoot(root);
2253
+ const isTty = process.stdout && process.stdout.isTTY;
2254
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2255
+ const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2256
+ const yl = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2257
+ const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2258
+ const files = _collectPyFiles(root, 200);
2259
+ const analyses = files.map(f => _analyzePyFile(f)).filter(Boolean);
2260
+ const totalLOC = analyses.reduce((s, a) => s + a.loc, 0);
2261
+ const totalImports = analyses.reduce((s, a) => s + a.imports, 0);
2262
+ const totalFuncs = analyses.reduce((s, a) => s + a.funcs, 0);
2263
+ const totalClasses = analyses.reduce((s, a) => s + a.classes, 0);
2264
+ const totalTodos = analyses.reduce((s, a) => s + a.todos, 0);
2265
+ const result = {
2266
+ totalFiles: analyses.length,
2267
+ totalLOC, totalImports, totalFuncs, totalClasses, totalTodos,
2268
+ biggest: analyses.slice().sort((a, b) => b.loc - a.loc).slice(0, 5).map(a => ({ file: path.relative(root, a.file), loc: a.loc, funcs: a.funcs, classes: a.classes }))
2269
+ };
2270
+ if (has('--json')) { log(JSON.stringify(result, null, 2)); return; }
2271
+ log(cy(`# leerness py-check (1.9.239) β€” Python 파일 뢄석 (μ‚¬μš©μž λͺ…μ‹œ UR-0013)`));
2272
+ log('');
2273
+ if (analyses.length === 0) {
2274
+ log(dm(` Python 파일 μ—†μŒ (.py 검색됨: ${files.length}, 뢄석 κ°€λŠ₯: 0)`));
2275
+ log(dm(` β†’ leerness λŠ” .md/.py λͺ¨λ‘ 뢄석 κ°€λŠ₯ (1.9.239)`));
2276
+ return;
2277
+ }
2278
+ log(` πŸ“Š 톡계:`);
2279
+ log(` 파일 ${gr(String(analyses.length))} Β· LOC ${gr(String(totalLOC))} Β· 평균 ${gr(String(Math.round(totalLOC / analyses.length)))} loc/file`);
2280
+ log(` imports ${totalImports} Β· functions ${totalFuncs} Β· classes ${totalClasses}`);
2281
+ if (totalTodos > 0) log(yl(` ⚠ TODO/FIXME ${totalTodos}건`));
2282
+ log('');
2283
+ if (result.biggest.length > 0) {
2284
+ log(` Top 5 파일 (LOC κΈ°μ€€):`);
2285
+ result.biggest.forEach(f => log(` β€’ ${f.file} β€” ${f.loc} loc / ${f.funcs} func / ${f.classes} class`));
2286
+ }
2287
+ log('');
2288
+ log(dm(` β†’ JSON 좜λ ₯: leerness py-check --json`));
2289
+ }
2290
+
2291
+ // 1.9.239: agent-mode β€” 자율 λͺ¨λ“œ μ „μš© 톡합 λͺ…λ Ή (μ‚¬μš©μž λͺ…μ‹œ UR-0013)
2292
+ // handoff + pulse + drift auto-fix + session-resume λ₯Ό 1 λͺ…λ ΉμœΌλ‘œ
2293
+ // start: μ§„μž… (handoff + drift auto-fix + λΉ„μ •μƒμ’…λ£Œ μžλ™ 회볡)
2294
+ // tick: λ§€ λΌμš΄λ“œ (pulse + 1 line)
2295
+ // stop: 마감 (session close + auto-apply-delivered + auto-cleanup-branches)
2296
+ function agentModeCmd(root, sub) {
2297
+ root = absRoot(root);
2298
+ const isTty = process.stdout && process.stdout.isTTY;
2299
+ const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2300
+ const gr = s => isTty ? `\x1b[32m${s}\x1b[0m` : s;
2301
+ const yl = s => isTty ? `\x1b[33m${s}\x1b[0m` : s;
2302
+ const dm = s => isTty ? `\x1b[2m${s}\x1b[0m` : s;
2303
+ function spawnChild(args, opts) {
2304
+ return cp.spawnSync(process.execPath, [__filename, ...args], Object.assign({ encoding: 'utf8', cwd: root, env: { ...process.env, LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1' } }, opts || {}));
2305
+ }
2306
+ if (!sub || sub === 'help') {
2307
+ log(cy(`# leerness agent-mode (1.9.239) β€” 자율 λͺ¨λ“œ μ „μš© 톡합 λͺ…λ Ή`));
2308
+ log('');
2309
+ log(` start μ§„μž… (handoff + drift --auto-fix + session-resume --auto-fix)`);
2310
+ log(` tick λ§€ λΌμš΄λ“œ (pulse + 핡심 μ‹ ν˜Έ)`);
2311
+ log(` stop 마감 (session close --auto-apply-delivered --auto-cleanup-branches)`);
2312
+ log('');
2313
+ log(dm(` μ‚¬μš©μž λͺ…μ‹œ UR-0013 (Python + agent-mode νš¨μœ¨ν™”)`));
2314
+ return;
2315
+ }
2316
+ if (sub === 'start') {
2317
+ log(cy(`# leerness agent-mode start (1.9.239)`));
2318
+ log(dm(` β†’ 1. drift check --auto-fix (λ³΄μ•ˆ + delivered + release cleanup)`));
2319
+ const r1 = spawnChild(['drift', 'check', root, '--auto-fix']);
2320
+ if (r1.status === 0) log(gr(` βœ“ drift auto-fix μ™„λ£Œ`)); else log(yl(` ⚠ drift auto-fix μ’…λ£Œ ${r1.status}`));
2321
+ log(dm(` β†’ 2. session-resume --auto-fix (λΉ„μ •μƒμ’…λ£Œ μžλ™ 회볡)`));
2322
+ const r2 = spawnChild(['session-resume', '--path', root, '--auto-fix']);
2323
+ if (r2.status === 0) log(gr(` βœ“ session-resume μ™„λ£Œ`)); else log(yl(` ⚠ session-resume μ’…λ£Œ ${r2.status}`));
2324
+ log(dm(` β†’ 3. handoff (μ»¨ν…μŠ€νŠΈ 적재)`));
2325
+ const r3 = spawnChild(['handoff', root]);
2326
+ if (r3.status === 0) log(gr(` βœ“ handoff 좜λ ₯ μ™„λ£Œ (μœ„ μ°Έμ‘°)`)); else log(yl(` ⚠ handoff μ’…λ£Œ ${r3.status}`));
2327
+ log('');
2328
+ log(gr(`βœ“ agent-mode start μ™„λ£Œ β€” 자율 λΌμš΄λ“œ μ§„ν–‰ μ€€λΉ„`));
2329
+ return;
2330
+ }
2331
+ if (sub === 'tick') {
2332
+ // 가벼움 μš°μ„  β€” pulse 만 호좜
2333
+ log(cy(`# leerness agent-mode tick (1.9.239) β€” κ°€λ²Όμš΄ μƒνƒœ 확인`));
2334
+ const r = spawnChild(['pulse', '--path', root]);
2335
+ process.stdout.write(r.stdout || '');
2336
+ return;
2337
+ }
2338
+ if (sub === 'stop') {
2339
+ log(cy(`# leerness agent-mode stop (1.9.239) β€” 톡합 마감`));
2340
+ log(dm(` β†’ session close --auto-apply-delivered --auto-cleanup-branches`));
2341
+ const r = spawnChild(['session', 'close', root, '--auto-apply-delivered', '--auto-cleanup-branches']);
2342
+ process.stdout.write(r.stdout || '');
2343
+ return;
2344
+ }
2345
+ log(yl(`unknown subcommand: ${sub}`));
2346
+ log(dm(` β†’ leerness agent-mode help`));
2347
+ }
2348
+
2199
2349
  // 1.9.234: recentChanges β€” 졜근 N λΌμš΄λ“œμ˜ 핡심 λ³€κ²½ μš”μ•½ (git tag commit subject)
2200
2350
  // 응닡: [{ version, date, subject }] (κ°€μž₯ μ΅œκ·ΌλΆ€ν„°)
2201
2351
  function _computeRecentChanges(root, limit = 5) {
@@ -14558,7 +14708,9 @@ function mcpServeCmd(root) {
14558
14708
  { 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' } } } },
14559
14709
  { 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' } } } },
14560
14710
  { 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' } } } },
14561
- { 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' } } } }
14711
+ { 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' } } } },
14712
+ { name: 'leerness_py_check', description: '1.9.239 (μ‚¬μš©μž λͺ…μ‹œ UR-0013) β€” Python 파일 뢄석. μ˜μ‘΄μ„± 0 regex fallback. .md μ™Έ .py 도 leerness 인지. 응닡: { totalFiles, totalLOC, totalImports, totalFuncs, totalClasses, totalTodos, biggest: [{ file, loc, funcs, classes }] }. μ™ΈλΆ€ AIκ°€ "이 ν”„λ‘œμ νŠΈ Python ν‘œλ©΄μ΄ μ–Όλ§ˆλ‚˜ λ˜λ‚˜"λ₯Ό 회수. 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
14713
+ { name: 'leerness_agent_mode', description: '1.9.239 (μ‚¬μš©μž λͺ…μ‹œ UR-0013) β€” 자율 λͺ¨λ“œ μ „μš© 톡합 λͺ…λ Ή. start: handoff + drift --auto-fix + session-resume --auto-fix (μ§„μž…). tick: pulse ν•œ 쀄 (λ§€ λΌμš΄λ“œ). stop: session close --auto-apply-delivered --auto-cleanup-branches (마감). μ™ΈλΆ€ AI κ°€ 자율 λΌμš΄λ“œ μ§„μž…/λ§€ λΌμš΄λ“œ/λ§ˆκ°μ„ 단일 호좜둜 μˆ˜ν–‰. 인자: { path?, sub (required: "start"|"tick"|"stop"|"help") }', inputSchema: { type: 'object', properties: { path: { type: 'string' }, sub: { type: 'string', enum: ['start', 'tick', 'stop', 'help'] } }, required: ['sub'] } }
14562
14714
  ];
14563
14715
 
14564
14716
  function send(obj) {
@@ -14735,6 +14887,14 @@ function mcpServeCmd(root) {
14735
14887
  // 1.9.233: μΉ΄ν…Œκ³ λ¦¬ν™”λœ 전체 CLI λͺ…λ Ή λͺ©λ‘
14736
14888
  cliArgs = ['commands', '--json'];
14737
14889
  break;
14890
+ case 'leerness_py_check':
14891
+ // 1.9.239 (UR-0013): Python 파일 뢄석
14892
+ cliArgs = ['py-check', '--path', targetPath, '--json'];
14893
+ break;
14894
+ case 'leerness_agent_mode':
14895
+ // 1.9.239 (UR-0013): 자율 λͺ¨λ“œ 톡합 (start/tick/stop)
14896
+ cliArgs = ['agent-mode', String(args.sub || 'help'), '--path', targetPath];
14897
+ break;
14738
14898
  case 'leerness_release_cleanup':
14739
14899
  // 1.9.236 (1.9.235): local release/* branches 정리
14740
14900
  cliArgs = ['release', 'cleanup', '--json'];
@@ -18406,6 +18566,10 @@ async function main() {
18406
18566
  if (cmd === 'pulse') return pulseCmd(arg('--path', process.cwd()));
18407
18567
  // 1.9.233: leerness commands β€” μΉ΄ν…Œκ³ λ¦¬ν™”λœ 전체 CLI λͺ…λ Ή λͺ©λ‘
18408
18568
  if (cmd === 'commands') return commandsCmd(arg('--path', process.cwd()));
18569
+ // 1.9.239: leerness py-check β€” Python 파일 뢄석 (μ‚¬μš©μž λͺ…μ‹œ UR-0013)
18570
+ if (cmd === 'py-check') return pyCheckCmd(arg('--path', process.cwd()));
18571
+ // 1.9.239: leerness agent-mode <start|tick|stop> β€” 자율 λͺ¨λ“œ μ „μš© 톡합 λͺ…λ Ή (μ‚¬μš©μž λͺ…μ‹œ UR-0013)
18572
+ if (cmd === 'agent-mode') return agentModeCmd(arg('--path', process.cwd()), args[1]);
18409
18573
  // 1.9.208: leerness constraints <list|check|add> β€” ν”Œλž«νΌ/API μ œμ•½ 사전 체크 (μ‚¬μš©μž λͺ…μ‹œ)
18410
18574
  if (cmd === 'constraints') return constraintsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
18411
18575
  // 1.9.209: leerness pre-wake-audit β€” sleep μ „ sub-agent audit (μ‚¬μš©μž λͺ…μ‹œ)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.237",
3
+ "version": "1.9.239",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",