leerness 1.9.171 β†’ 1.9.173

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,122 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.173 β€” 2026-05-21
4
+
5
+ **🌐 LSP μ–΄λŒ‘ν„° λ‹€κ΅­μ–΄ ν™•μž₯ β€” JavaScript + Python + Go + Rust + Java (5개 μ–Έμ–΄ regex fallback).**
6
+
7
+ 자율 λͺ¨λ“œ 103 λΌμš΄λ“œ. 1.9.167 codeIntel (JS/TS only) β†’ 5개 μ–Έμ–΄ ν™•μž₯.
8
+
9
+ ### `_LSP_LANG_PATTERNS` β€” 5개 μ–Έμ–΄ νŒ¨ν„΄
10
+ | μ–Έμ–΄ | μΆ”μΆœ κ°€λŠ₯ |
11
+ |---|---|
12
+ | **javascript** (.ts/.tsx/.js/.jsx/.mjs/.cjs) | function, class, interface, type, enum, arrow function |
13
+ | **python** (.py/.pyw/.pyi) | def, async def, class |
14
+ | **go** (.go) | func (receiver 포함), type struct/interface, type alias |
15
+ | **rust** (.rs) | fn (pub/async), struct, enum, trait, impl, type |
16
+ | **java** (.java/.kt/.scala) | class (public/private/abstract), interface, enum, method |
17
+
18
+ ### `_detectLspLang(file)` β€” ν™•μž₯자 μžλ™ λΌμš°νŒ…
19
+ 파일 ν™•μž₯자 기반 μ–Έμ–΄ μžλ™ 감지. 미지원 ν™•μž₯μžλŠ” javascript κΈ°λ³Έ (1.9.167 ν˜Έν™˜).
20
+
21
+ ### μ‚¬μš© μ˜ˆμ‹œ
22
+ ```bash
23
+ $ leerness lsp symbols src/api.py
24
+ # leerness lsp symbols (1.9.173 λ‹€κ΅­μ–΄)
25
+ file: src/api.py Β· lang: python
26
+ mode: regex-fallback (python) Β· 12 symbols Β· 4ms
27
+ 1:function parse_request
28
+ 8:function fetch_data
29
+ 15:class Handler
30
+ 16:function __init__
31
+ ...
32
+
33
+ $ leerness lsp symbols src/main.rs
34
+ # leerness lsp symbols (1.9.173 λ‹€κ΅­μ–΄)
35
+ file: src/main.rs Β· lang: rust
36
+ mode: regex-fallback (rust) Β· 9 symbols Β· 5ms
37
+ 1:function hello
38
+ 5:struct User
39
+ 9:impl User
40
+ 15:trait Greeter
41
+ ...
42
+ ```
43
+
44
+ ### ν‚€μ›Œλ“œ false-positive 제거
45
+ Java method μ •κ·œμ‹μ΄ `if(`, `for(`, `while(`, `switch(`, `catch(`, `return(`, `throw(`, `new(` λ“± ν‚€μ›Œλ“œμ— λ§€μΉ˜λ˜λŠ” 경우 ν•„ν„°.
46
+
47
+ ### references λ‹€κ΅­μ–΄ 파일 μŠ€μΊ”
48
+ `leerness lsp references <name>` κ°€ `.py/.go/.rs/.java/.kt/.scala` νŒŒμΌλ„ μŠ€μΊ” (κΈ°μ‘΄ `.ts/.js/.md` 에 μΆ”κ°€).
49
+
50
+ ### μ‹€μΈ‘ (regex fallback)
51
+ - Python (5 symbols): 472ms
52
+ - Go (4 symbols): 566ms
53
+ - Rust (6 symbols): 531ms
54
+ - Java (4 symbols): 1229ms
55
+
56
+ ### Verified
57
+ - e2e 217/217 baseline μœ μ§€
58
+ - stress-v118: **15/15** (νŒ¨ν„΄ μ •μ˜ 4 + Python 1 + Go 1 + Rust 1 + Java 1 + JS ν˜Έν™˜ 1 + references 1 + λˆ„μ  νšŒκ·€ 5)
59
+ - VERSION = 1.9.173 / autonomous-rounds = 103 / main μžλ™ push 34 λΌμš΄λ“œ 연속
60
+
61
+ ---
62
+
63
+ ## 1.9.172 β€” 2026-05-21
64
+
65
+ **🎨 슀트리밍 UX κ°•ν™” β€” spinner + Claude tool_use κ°€μ‹œν™” + diff νŒ¨ν„΄ μžλ™ 색깔 (μ‚¬μš©μž λͺ…μ‹œ κ°•ν™”).**
66
+
67
+ 자율 λͺ¨λ“œ 102 λΌμš΄λ“œ. 1.9.170 μ‚¬μš©μž λͺ…μ‹œ ("터미널 ν™”λ©΄ 계속 κ°±μ‹ , 좔둠쀑/diff/μƒκ°ν•˜λŠ” κ³Όμ • μ‹€μ‹œκ°„ ν‘œμ‹œ") 의 μ§„μ§œ μ˜λ„ 보강.
68
+
69
+ ### 1. 좔둠쀑 Spinner β€” Visual Feedback
70
+ - `_cliChatStream` μ•ˆμ— `setInterval(120ms)` spinner β€” stdout idle 800ms+ μ‹œ κΉœλΉ‘μž„
71
+ - `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` 10 frames + κ²½κ³Ό μ‹œκ°„ ν‘œμ‹œ ("β ™ 좔둠쀑... (3s)")
72
+ - `\r\x1b[K` ANSI escape β€” ν˜„μž¬ 라인 클리어 + spinner κ°±μ‹ 
73
+ - `stopSpinner()` β€” stdout/stderr data 도착 μ‹œ μ¦‰μ‹œ spinner 라인 클리어
74
+ - μ˜΅μ…˜: `opts.noSpinner: true` λ˜λŠ” λΉ„-TTY ν™˜κ²½μ—μ„  μžλ™ λΉ„ν™œμ„±
75
+
76
+ ### 2. Claude tool_use κ°€μ‹œν™”
77
+ - Claude `--output-format=stream-json` 의 `content_block_start` μ΄λ²€νŠΈμ—μ„œ `type='tool_use'` 감지
78
+ - ν‘œμ‹œ: `πŸ”§ Tool: Read 호좜 쀑...` (cyan 색)
79
+ - AIκ°€ 파일 읽기/μ“°κΈ°/Bash 호좜 μ‹œ μ‚¬μš©μžκ°€ μ¦‰μ‹œ 인지
80
+
81
+ ### 3. diff νŒ¨ν„΄ μžλ™ 색깔 (writeColored)
82
+ ```diff
83
+ diff --git a/file.js b/file.js # yellow (헀더)
84
+ --- a/file.js # yellow
85
+ +++ b/file.js # yellow
86
+ @@ -10,3 +10,3 @@ # cyan (hunk)
87
+ - old line # red
88
+ + new line # green
89
+ context # κΈ°λ³Έ 색
90
+ ```
91
+ - 라인 λ‹¨μœ„ 처리 (`lineBuf` 버퍼링)
92
+ - μ •κ·œμ‹ λ§€μΉ­: `/^\+(?!\+\+)/` β†’ green, `/^-(?!--)/` β†’ red, `/^@@.*@@/` β†’ cyan
93
+ - `flushLineBuf()` β€” μ’…λ£Œ μ‹œ μž”μ—¬ 라인 좜λ ₯
94
+
95
+ ### 톡합 흐름
96
+ ```
97
+ user> 이 ν•¨μˆ˜ λ¦¬νŒ©ν† λ§ + diff λ³΄μ—¬μ€˜
98
+ β†’ claude CLI stream 호좜 쀑... (Ctrl+C 둜 쀑단)
99
+ ── claude stream ──
100
+ β Ή 좔둠쀑... (2s) # 응닡 μ‹œμž‘ μ „ spinner
101
+ [claude-opus-4-7]
102
+ πŸ”§ Tool: Read 호좜 쀑... # tool_use 감지
103
+
104
+ 뢄석 κ²°κ³Ό:
105
+
106
+ diff --git a/utils.js b/utils.js # yellow
107
+ @@ -5,3 +5,3 @@ # cyan
108
+ - function bad() { return null; } # red
109
+ + function good() { return {}; } # green
110
+ ── /stream (4521ms) ──
111
+ ```
112
+
113
+ ### Verified
114
+ - e2e 217/217 baseline μœ μ§€
115
+ - stress-v117: **20/20** (spinner 4 + tool_use 2 + diff 4 + ANSI helpers 2 + 톡합 2 + λˆ„μ  νšŒκ·€ 6)
116
+ - VERSION = 1.9.172 / autonomous-rounds = 102 / main μžλ™ push 33 λΌμš΄λ“œ 연속
117
+
118
+ ---
119
+
3
120
  ## 1.9.171 β€” 2026-05-21
4
121
 
5
122
  **AGENTS.md / CLAUDE.md / session-workflow.md 1.9.88~170 λˆ„μ  κ°±μ‹  (drift 차단).**
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.171-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v116-20%2F20-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-53-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-101-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-32_rounds-success)]() [![metadata](https://img.shields.io/badge/AGENTS%2FCLAUDE%2Fworkflow-1.9.88~170_λˆ„μ -success)]() [![repl-tab](https://img.shields.io/badge/REPL-Tab_cycle%2Bμ‹€μ‹œκ°„_슀트리밍-success)]() [![mcp-bridge](https://img.shields.io/badge/MCP_bridge-web%2Fpc%2Flsp_λ…ΈμΆœ-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![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.173-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v118-15%2F15-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-53-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-103-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-34_rounds-success)]() [![lsp-multi](https://img.shields.io/badge/LSP_λ‹€κ΅­μ–΄-JS%2FPython%2FGo%2FRust%2FJava-success)]() [![stream-ux](https://img.shields.io/badge/슀트리밍-spinner%2Btool__use%2Bdiff_색깔-success)]() [![mcp-bridge](https://img.shields.io/badge/MCP_bridge-web%2Fpc%2Flsp_λ…ΈμΆœ-success)]() [![capability](https://img.shields.io/badge/6_capability-72%25_production--ready-brightgreen)]() [![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.171 AI Agent Reliability Harness + Sandbox β•‘
15
+ β•‘ v1.9.173 AI Agent Reliability Harness + Sandbox β•‘
16
16
  β•‘ verify Β· remember Β· orchestrate Β· audit Β· sandbox Β· drift β•‘
17
- β•‘ metadata 1.9.88~170 λˆ„μ  κ°±μ‹  Β· λ‹€μŒ μ„Έμ…˜ drift 차단 β•‘
17
+ β•‘ 🌐 LSP λ‹€κ΅­μ–΄ 5μ’… (JS/Python/Go/Rust/Java regex fallback) β•‘
18
18
  β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
19
19
  ```
20
20
 
package/bin/harness.js CHANGED
@@ -6,7 +6,7 @@ const path = require('path');
6
6
  const cp = require('child_process');
7
7
  const readline = require('readline');
8
8
 
9
- const VERSION = '1.9.171';
9
+ const VERSION = '1.9.173';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -10552,6 +10552,7 @@ async function _cliChat(root, provider, prompt, opts) {
10552
10552
  }
10553
10553
 
10554
10554
  // 1.9.170: REPL μ‹€μ‹œκ°„ 슀트리밍 β€” CLI stdout을 μ¦‰μ‹œ 터미널에 ν‘œμ‹œ (μ‚¬μš©μž λͺ…μ‹œ μš”μ²­)
10555
+ // 1.9.172: 좔둠쀑 spinner + Claude tool_use κ°€μ‹œν™” + diff νŒ¨ν„΄ μžλ™ 색깔 (μ‚¬μš©μž λͺ…μ‹œ κ°•ν™”)
10555
10556
  // 좔둠쀑/diff/thinking 과정이 stdout 으둜 흐λ₯΄λŠ” λ™μ•ˆ μ‹€μ‹œκ°„ λ Œλ”λ§.
10556
10557
  // runCommandSafe 의 배치 결과와 달리 cp.spawn 의 child.stdout 을 직접 pipe.
10557
10558
  // env scrub + cwd jail 은 λ™μΌν•˜κ²Œ 적용.
@@ -10574,9 +10575,55 @@ async function _cliChatStream(root, provider, promptText, opts) {
10574
10575
  let out = '', err = '';
10575
10576
  let buf = ''; // claude stream-json 라인 버퍼
10576
10577
  let firstChunk = true;
10578
+ let lineBuf = ''; // 1.9.172: diff νŒ¨ν„΄ 색깔 μœ„ν•œ 라인 버퍼
10577
10579
  const isTty = process.stdout.isTTY;
10578
10580
  const dim = isTty ? (s) => `\x1b[2m${s}\x1b[0m` : s => s;
10579
10581
  const cy = isTty ? (s) => `\x1b[36m${s}\x1b[0m` : s => s;
10582
+ const green = isTty ? (s) => `\x1b[32m${s}\x1b[0m` : s => s;
10583
+ const red = isTty ? (s) => `\x1b[31m${s}\x1b[0m` : s => s;
10584
+ const yel = isTty ? (s) => `\x1b[33m${s}\x1b[0m` : s => s;
10585
+
10586
+ // 1.9.172: 좔둠쀑 spinner β€” stdout이 일정 μ‹œκ°„ idle ν•˜λ©΄ κΉœλΉ‘μ΄λŠ” visual feedback
10587
+ // AIκ°€ 응닡 μ‹œμž‘ν•˜κΈ° μ „ (preprocessing/thinking) λ™μ•ˆ μ‚¬μš©μžκ°€ "λ™μž‘ 쀑" 인지 확인.
10588
+ const spinnerFrames = ['β ‹', 'β ™', 'β Ή', 'β Έ', 'β Ό', 'β ΄', 'β ¦', 'β §', 'β ‡', '⠏'];
10589
+ let spinnerIdx = 0;
10590
+ let spinnerActive = false;
10591
+ let lastActivity = Date.now();
10592
+ const spinnerInterval = isTty && !opts.noSpinner ? setInterval(() => {
10593
+ const idle = Date.now() - lastActivity;
10594
+ if (idle > 800) {
10595
+ if (!spinnerActive) { spinnerActive = true; }
10596
+ // ν˜„μž¬ 라인을 ν΄λ¦¬μ–΄ν•˜κ³  spinner ν‘œμ‹œ
10597
+ process.stdout.write(`\r\x1b[K${dim(spinnerFrames[spinnerIdx % spinnerFrames.length] + ' 좔둠쀑... (' + Math.floor(idle / 1000) + 's)')}`);
10598
+ spinnerIdx++;
10599
+ }
10600
+ }, 120) : null;
10601
+ const stopSpinner = () => {
10602
+ if (spinnerActive) {
10603
+ process.stdout.write('\r\x1b[K'); // spinner 라인 클리어
10604
+ spinnerActive = false;
10605
+ }
10606
+ lastActivity = Date.now();
10607
+ };
10608
+
10609
+ // 1.9.172: diff νŒ¨ν„΄ μžλ™ 색깔 β€” `+ ` / `- ` / `@@` 라인을 green/red/cyan
10610
+ function writeColored(s) {
10611
+ // 라인 λ‹¨μœ„λ‘œ 처리 (라인 λκΉŒμ§€ λͺ¨μ€ ν›„ 색깔 μž…νžˆκΈ°)
10612
+ lineBuf += s;
10613
+ const lines = lineBuf.split('\n');
10614
+ lineBuf = lines.pop() || '';
10615
+ for (const ln of lines) {
10616
+ if (/^\+(?!\+\+)/.test(ln)) process.stdout.write(green(ln) + '\n');
10617
+ else if (/^-(?!--)/.test(ln)) process.stdout.write(red(ln) + '\n');
10618
+ else if (/^@@.*@@/.test(ln)) process.stdout.write(cy(ln) + '\n');
10619
+ else if (/^(diff --git|---|\+\+\+|index )/.test(ln)) process.stdout.write(yel(ln) + '\n');
10620
+ else process.stdout.write(ln + '\n');
10621
+ }
10622
+ }
10623
+ function flushLineBuf() {
10624
+ if (lineBuf) { process.stdout.write(lineBuf); lineBuf = ''; }
10625
+ }
10626
+
10580
10627
  process.stdout.write(dim(`\n ── ${provider} stream ──\n`));
10581
10628
  let child;
10582
10629
  try {
@@ -10587,6 +10634,7 @@ async function _cliChatStream(root, provider, promptText, opts) {
10587
10634
  stdio: ['ignore', 'pipe', 'pipe']
10588
10635
  });
10589
10636
  } catch (e) {
10637
+ if (spinnerInterval) clearInterval(spinnerInterval);
10590
10638
  return resolve({ ok: false, error: 'spawn μ‹€νŒ¨: ' + e.message, provider });
10591
10639
  }
10592
10640
  const timer = setTimeout(() => {
@@ -10594,6 +10642,7 @@ async function _cliChatStream(root, provider, promptText, opts) {
10594
10642
  }, opts.timeout || 120000);
10595
10643
 
10596
10644
  // Claude stream-json λͺ¨λ“œ: 각 라인이 JSON event. type=assistant_message_delta.text 만 좜λ ₯
10645
+ // 1.9.172: tool_use κ°€μ‹œν™” μΆ”κ°€ (Read/Write/Bash 호좜 μ‹œ "πŸ”§ Tool: <name>" ν‘œμ‹œ)
10597
10646
  function handleClaudeStream(chunk) {
10598
10647
  buf += chunk.toString();
10599
10648
  const lines = buf.split('\n');
@@ -10602,44 +10651,63 @@ async function _cliChatStream(root, provider, promptText, opts) {
10602
10651
  if (!ln.trim()) continue;
10603
10652
  try {
10604
10653
  const ev = JSON.parse(ln);
10605
- // assistant λ©”μ‹œμ§€ chunk만 ν‘œμ‹œ (thinking/system 은 dim)
10654
+ // assistant λ©”μ‹œμ§€ chunk
10606
10655
  if (ev.type === 'content_block_delta' && ev.delta?.text) {
10607
- process.stdout.write(ev.delta.text);
10656
+ stopSpinner();
10657
+ writeColored(ev.delta.text);
10608
10658
  out += ev.delta.text;
10609
10659
  } else if (ev.type === 'message_start' && firstChunk) {
10610
10660
  firstChunk = false;
10661
+ stopSpinner();
10611
10662
  process.stdout.write(dim(` [${ev.message?.model || provider}] `));
10612
10663
  } else if (ev.type === 'thinking_delta' && ev.delta?.thinking) {
10664
+ stopSpinner();
10613
10665
  process.stdout.write(dim(ev.delta.thinking));
10666
+ } else if (ev.type === 'content_block_start' && ev.content_block?.type === 'tool_use') {
10667
+ // 1.9.172: tool_use μ‹œμž‘ μ‹œ "πŸ”§ Tool: Read" 같은 μ•ˆλ‚΄
10668
+ stopSpinner();
10669
+ const toolName = ev.content_block.name || 'tool';
10670
+ process.stdout.write(cy(`\n πŸ”§ Tool: ${toolName} 호좜 쀑...\n`));
10671
+ } else if (ev.type === 'tool_use' || ev.type === 'content_block_stop') {
10672
+ // 도ꡬ μ™„λ£Œ μ‹œκ·Έλ„ (ν•„μš”μ‹œ ν™œλ™ ν‘œμ‹œ)
10614
10673
  }
10615
10674
  } catch {
10616
10675
  // JSON μ•„λ‹ˆλ©΄ κ·ΈλŒ€λ‘œ 좜λ ₯ (fallback)
10617
- process.stdout.write(ln + '\n');
10676
+ stopSpinner();
10677
+ writeColored(ln + '\n');
10618
10678
  out += ln + '\n';
10619
10679
  }
10620
10680
  }
10621
10681
  }
10622
10682
 
10623
10683
  child.stdout.on('data', chunk => {
10684
+ lastActivity = Date.now();
10624
10685
  if (provider === 'claude') {
10625
10686
  handleClaudeStream(chunk);
10626
10687
  } else {
10688
+ stopSpinner();
10627
10689
  const s = chunk.toString();
10628
10690
  out += s;
10629
- process.stdout.write(s);
10691
+ writeColored(s);
10630
10692
  }
10631
10693
  });
10632
10694
  child.stderr.on('data', chunk => {
10695
+ lastActivity = Date.now();
10696
+ stopSpinner();
10633
10697
  const s = chunk.toString();
10634
10698
  err += s;
10635
10699
  // 좔둠쀑/μ§„ν–‰ λ©”μ‹œμ§€λŠ” dim 으둜 (μ‹€μ œ μ—λŸ¬λŠ” 좜λ ₯ 후에 μ’…ν•©)
10636
10700
  process.stdout.write(dim(s));
10637
10701
  });
10638
10702
  child.on('error', e => {
10703
+ if (spinnerInterval) clearInterval(spinnerInterval);
10639
10704
  clearTimeout(timer);
10640
10705
  resolve({ ok: false, error: 'child error: ' + e.message, provider });
10641
10706
  });
10642
10707
  child.on('close', code => {
10708
+ if (spinnerInterval) clearInterval(spinnerInterval);
10709
+ stopSpinner();
10710
+ flushLineBuf(); // 1.9.172: lineBuf μž”μ—¬ flush
10643
10711
  clearTimeout(timer);
10644
10712
  const dt = Date.now() - t0;
10645
10713
  process.stdout.write(dim(`\n ── /stream (${dt}ms) ──\n`));
@@ -12492,22 +12560,69 @@ function _tryLoadLSP() {
12492
12560
  return { ok: false, error: 'typescript λ―Έμ„€μΉ˜ β€” `npm i -g typescript` ν›„ λ‹€μ‹œ μ‹œλ„ (λ˜λŠ” μ •κ·œμ‹ fallback μ‚¬μš©)' };
12493
12561
  }
12494
12562
 
12495
- // μ •κ·œμ‹ fallback β€” TypeScript/JavaScript symbol μΆ”μΆœ (LSP 없이도 λ™μž‘)
12496
- function _lspRegexSymbols(content) {
12497
- const symbols = [];
12498
- const lines = content.split(/\r?\n/);
12499
- const patterns = [
12563
+ // 1.9.173: λ‹€κ΅­μ–΄ ν™•μž₯ β€” Python/Go/Rust/Java νŒ¨ν„΄ μΆ”κ°€ (regex fallback)
12564
+ // 파일 ν™•μž₯자 기반 μžλ™ λΌμš°νŒ…. TypeScript Compiler API λ―Έμ„€μΉ˜ μ‹œμ—λ„ 5개 μ–Έμ–΄ 지원.
12565
+ const _LSP_LANG_PATTERNS = {
12566
+ javascript: [
12500
12567
  { re: /^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/, kind: 'function' },
12501
12568
  { re: /^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/, kind: 'class' },
12502
12569
  { re: /^\s*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: 'interface' },
12503
12570
  { re: /^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:function|\()/, kind: 'function' },
12504
12571
  { re: /^\s*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: 'type' },
12505
- { re: /^\s*(?:export\s+)?enum\s+([A-Za-z_$][\w$]*)/, kind: 'enum' },
12506
- ];
12572
+ { re: /^\s*(?:export\s+)?enum\s+([A-Za-z_$][\w$]*)/, kind: 'enum' }
12573
+ ],
12574
+ python: [
12575
+ { re: /^\s*async\s+def\s+([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
12576
+ { re: /^\s*def\s+([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
12577
+ { re: /^\s*class\s+([A-Za-z_][\w]*)\s*[(:]/, kind: 'class' }
12578
+ ],
12579
+ go: [
12580
+ { re: /^\s*func\s+(?:\([^)]*\)\s+)?([A-Za-z_][\w]*)\s*\(/, kind: 'function' },
12581
+ { re: /^\s*type\s+([A-Za-z_][\w]*)\s+struct\b/, kind: 'struct' },
12582
+ { re: /^\s*type\s+([A-Za-z_][\w]*)\s+interface\b/, kind: 'interface' },
12583
+ { re: /^\s*type\s+([A-Za-z_][\w]*)\s+[A-Za-z]/, kind: 'type' }
12584
+ ],
12585
+ rust: [
12586
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?(?:async\s+)?fn\s+([A-Za-z_][\w]*)/, kind: 'function' },
12587
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?struct\s+([A-Za-z_][\w]*)/, kind: 'struct' },
12588
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?enum\s+([A-Za-z_][\w]*)/, kind: 'enum' },
12589
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?trait\s+([A-Za-z_][\w]*)/, kind: 'trait' },
12590
+ { re: /^\s*impl\s+(?:[^{]+\s+for\s+)?([A-Za-z_][\w]*)/, kind: 'impl' },
12591
+ { re: /^\s*(?:pub(?:\([^)]+\))?\s+)?type\s+([A-Za-z_][\w]*)\s*=/, kind: 'type' }
12592
+ ],
12593
+ java: [
12594
+ { re: /^\s*(?:public|private|protected)?\s*(?:final\s+)?(?:abstract\s+)?class\s+([A-Za-z_][\w]*)/, kind: 'class' },
12595
+ { re: /^\s*(?:public|private|protected)?\s*(?:abstract\s+)?interface\s+([A-Za-z_][\w]*)/, kind: 'interface' },
12596
+ { re: /^\s*(?:public|private|protected)?\s*(?:static\s+)?(?:final\s+)?enum\s+([A-Za-z_][\w]*)/, kind: 'enum' },
12597
+ // method: visibility + return type + name( (heuristic β€” 첫 번째 ( λ§€μΉ­, ν‚€μ›Œλ“œ ν•„ν„°)
12598
+ { re: /^\s*(?:public|private|protected)\s+(?:static\s+)?(?:final\s+)?(?:[A-Za-z_<>,\s\[\]]+\s+)?([A-Za-z_][\w]*)\s*\(/, kind: 'method' }
12599
+ ]
12600
+ };
12601
+
12602
+ function _detectLspLang(file) {
12603
+ const ext = (file.match(/\.[a-zA-Z0-9]+$/) || [''])[0].toLowerCase();
12604
+ if (/^\.(py|pyw|pyi)$/.test(ext)) return 'python';
12605
+ if (ext === '.go') return 'go';
12606
+ if (ext === '.rs') return 'rust';
12607
+ if (/^\.(java|kt|scala)$/.test(ext)) return 'java';
12608
+ if (/^\.(ts|tsx|js|jsx|mjs|cjs)$/.test(ext)) return 'javascript';
12609
+ return 'javascript'; // default β€” κΈ°λ³Έ JS νŒ¨ν„΄ (.txt/.md λ“± 미지원 ν™•μž₯자)
12610
+ }
12611
+
12612
+ // μ •κ·œμ‹ fallback β€” 5개 μ–Έμ–΄ (JS/TS/Python/Go/Rust/Java) symbol μΆ”μΆœ (LSP 없이도 λ™μž‘)
12613
+ // 1.9.173: lang 인자 μΆ”κ°€ β€” λ―Έμ§€μ • μ‹œ javascript νŒ¨ν„΄ (1.9.167 ν˜Έν™˜).
12614
+ function _lspRegexSymbols(content, lang) {
12615
+ const symbols = [];
12616
+ const lines = content.split(/\r?\n/);
12617
+ const patterns = _LSP_LANG_PATTERNS[lang || 'javascript'] || _LSP_LANG_PATTERNS.javascript;
12507
12618
  lines.forEach((line, idx) => {
12508
12619
  for (const p of patterns) {
12509
12620
  const m = line.match(p.re);
12510
- if (m) { symbols.push({ name: m[1], kind: p.kind, line: idx + 1 }); break; }
12621
+ // ν‚€μ›Œλ“œ false-positive 제거 (예: java method μ •κ·œμ‹μ΄ `if(`, `for(` 등에 λ§€μΉ˜λ˜λŠ” 경우)
12622
+ if (m && !/^(if|for|while|switch|catch|return|throw|new)$/.test(m[1])) {
12623
+ symbols.push({ name: m[1], kind: p.kind, line: idx + 1 });
12624
+ break;
12625
+ }
12511
12626
  }
12512
12627
  });
12513
12628
  return symbols;
@@ -12573,31 +12688,32 @@ function lspCmd(root, sub, ...args) {
12573
12688
  if (!fs.existsSync(file)) return fail(`파일 μ—†μŒ: ${file}`);
12574
12689
  const content = fs.readFileSync(file, 'utf8');
12575
12690
  const t0 = Date.now();
12691
+ const lang = _detectLspLang(file); // 1.9.173: μ–Έμ–΄ μžλ™ 감지
12576
12692
  const r = _tryLoadLSP();
12577
12693
  let symbols, mode;
12578
12694
  try {
12579
- if (r.ok && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file)) {
12695
+ if (r.ok && lang === 'javascript' && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file)) {
12580
12696
  symbols = _lspTsSymbols(r.lib, content, file);
12581
12697
  mode = 'typescript-compiler';
12582
12698
  } else {
12583
- symbols = _lspRegexSymbols(content);
12584
- mode = 'regex-fallback';
12699
+ symbols = _lspRegexSymbols(content, lang); // 1.9.173: lang 전달
12700
+ mode = `regex-fallback (${lang})`;
12585
12701
  }
12586
12702
  } catch (e) {
12587
- symbols = _lspRegexSymbols(content);
12588
- mode = 'regex-fallback (after error: ' + e.message + ')';
12703
+ symbols = _lspRegexSymbols(content, lang);
12704
+ mode = `regex-fallback (${lang}, after error: ${e.message})`;
12589
12705
  }
12590
12706
  const dt = Date.now() - t0;
12591
12707
  if (has('--json')) {
12592
- log(JSON.stringify({ file, symbols, count: symbols.length, mode, durationMs: dt }, null, 2));
12708
+ log(JSON.stringify({ file, lang, symbols, count: symbols.length, mode, durationMs: dt }, null, 2));
12593
12709
  } else {
12594
- log(`# leerness lsp symbols (1.9.167)`);
12595
- log(`file: ${file}`);
12710
+ log(`# leerness lsp symbols (1.9.173 λ‹€κ΅­μ–΄)`);
12711
+ log(`file: ${file} Β· lang: ${lang}`);
12596
12712
  log(`mode: ${mode} Β· ${symbols.length} symbols Β· ${dt}ms`);
12597
12713
  symbols.slice(0, 50).forEach(s => log(` ${String(s.line).padStart(5)}:${s.kind.padEnd(10)} ${s.name}`));
12598
12714
  if (symbols.length > 50) log(` ... ${symbols.length - 50} more`);
12599
12715
  }
12600
- try { _recordRun(root, { kind: 'lsp_symbols', file, count: symbols.length, mode, durationMs: dt, ok: true }); } catch {}
12716
+ try { _recordRun(root, { kind: 'lsp_symbols', file, lang, count: symbols.length, mode, durationMs: dt, ok: true }); } catch {}
12601
12717
  return;
12602
12718
  }
12603
12719
  if (sub === 'references') {
@@ -12615,7 +12731,7 @@ function lspCmd(root, sub, ...args) {
12615
12731
  if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist' || e.name === 'build') continue;
12616
12732
  const p = path.join(d, e.name);
12617
12733
  if (e.isDirectory()) walk(p);
12618
- else if (/\.(ts|tsx|js|jsx|mjs|cjs|md)$/.test(e.name)) {
12734
+ else if (/\.(ts|tsx|js|jsx|mjs|cjs|md|py|pyw|pyi|go|rs|java|kt|scala)$/.test(e.name)) {
12619
12735
  try {
12620
12736
  const lines = fs.readFileSync(p, 'utf8').split(/\r?\n/);
12621
12737
  lines.forEach((ln, idx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.171",
3
+ "version": "1.9.173",
4
4
  "description": "Leerness: λΉ„νŒŒκ΄΄ λ§ˆμ΄κ·Έλ ˆμ΄μ…˜, μžλ™ 버전 κ°μ§€Β·μ—…λ°μ΄νŠΈ, κ³„νš/μ§„ν–‰/ν•Έλ“œμ˜€ν”„ μžλ™ν™”, κ²ŒμœΌλ¦„Β·μ‹œν¬λ¦ΏΒ·μΈμ½”λ”© μžλ™ κ°€λ“œ, Claude Code μŠ¬λž˜μ‹œ 톡합을 κ°–μΆ˜ ν•œκ΅­μ–΄ μš°μ„  AI 개발 ν•˜λ„€μŠ€.",
5
5
  "keywords": [
6
6
  "leerness",