leerness 1.9.166 → 1.9.168

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,112 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.168 — 2026-05-20
4
+
5
+ **MCP bridge 3종 노출 (web/pc/lsp) — 50 → 53 도구 + 외부 AI 자동화 능력 직결.**
6
+
7
+ 자율 모드 98 라운드. 1.9.165~167 에서 leerness CLI 에 추가한 web/pc/lsp bridge 3종을 MCP 도구로 노출 → 외부 AI (Claude, Codex, Gemini, Copilot)가 leerness 의 웹/PC/LSP 자동화 능력을 **직접 호출** 가능.
8
+
9
+ ### Added — MCP 도구 3종
10
+ | 도구 | 라우팅 | 설명 |
11
+ |---|---|---|
12
+ | `leerness_web` | `web check\|screenshot\|extract` | 1.9.165 playwright bridge MCP 노출 |
13
+ | `leerness_pc` | `pc check\|click\|type\|screenshot` | 1.9.166 robotjs/nut-tree bridge MCP 노출 |
14
+ | `leerness_lsp` | `lsp check\|symbols\|references` | 1.9.167 LSP 어댑터 MCP 노출 (typescript opt-in + regex fallback) |
15
+
16
+ ### 멀티 에이전트 오케스트레이션 강화
17
+ 1.9.156 `agents multi --execute` (실제 spawn + multi-signal consensus) 와 결합하면:
18
+ - 외부 AI 1 (Claude) → `leerness_web screenshot` (검수 자료 캡처)
19
+ - 외부 AI 2 (Codex) → `leerness_lsp symbols` (코드 인텔리전스)
20
+ - 외부 AI 3 (Gemini) → `leerness_pc screenshot` (UI 테스트 자동화)
21
+
22
+ → **leerness 가 모든 외부 AI의 도구 공급망 역할** (= 진정한 범용 AI 하네스).
23
+
24
+ ### MCP 53 도구 마일스톤
25
+ | 라운드 | 도구 수 | 마일스톤 |
26
+ |---|---|---|
27
+ | 1.9.110 | 30 | Memory CRUD 5종 완성 |
28
+ | 1.9.159 | 50 | Provider Registry CRUD 완성 |
29
+ | **1.9.168** | **53** | **Bridge 3종 외부 노출 (web/pc/lsp)** |
30
+
31
+ ### 사용 예시 (MCP tools/call)
32
+ ```json
33
+ { "name": "leerness_lsp", "arguments": { "sub": "symbols", "file": "src/api.ts" } }
34
+ { "name": "leerness_web", "arguments": { "sub": "screenshot", "url": "https://example.com", "out": "shot.png" } }
35
+ { "name": "leerness_pc", "arguments": { "sub": "click", "x": 800, "y": 400 } }
36
+ ```
37
+
38
+ ### Verified
39
+ - e2e 217/217 baseline (1.9.167 유지)
40
+ - stress-v113: 17/17 (MCP 등록 4 + tools/call 실 동작 4 + 6능력 매트릭스 2 + 누적 회귀 7)
41
+ - 실측: `leerness_lsp symbols harness.js` (MCP) → 472 symbols
42
+ - VERSION = 1.9.168 / autonomous-rounds = 98 / main 자동 push 29 라운드 연속
43
+
44
+ ### 6능력 매트릭스 (영향 없음, 100 라운드 마일스톤 임박)
45
+ | 영역 | 1.9.167 | **1.9.168** |
46
+ |---|---|---|
47
+ | (5) MCP 도구 | 100% (50+ 50 도구) | **100% (53 도구)** |
48
+ | 종합 | 72% | **72%** (production-ready 유지) |
49
+
50
+ 다음 라운드 (1.9.169~170): 100 라운드 마일스톤 임박 (2 라운드 남음).
51
+
52
+ ---
53
+
54
+ ## 1.9.167 — 2026-05-20
55
+
56
+ **LSP 어댑터 MVP — codeIntel 6번째 영역 신설 (typescript opt-in + regex fallback).**
57
+
58
+ 자율 모드 97 라운드. 1.9.165 (web) + 1.9.166 (pc) 흐름에 이어 코드 인텔리전스 신규 영역 추가.
59
+ 5능력 매트릭스 → **6능력 매트릭스**로 확장 (제 6번 codeIntel 영역 신설).
60
+
61
+ ### Added — `leerness lsp check|symbols|references`
62
+ **의존성 0 원칙 유지** — `typescript` 미설치 시 정규식 fallback 으로도 동작 (항상 사용 가능).
63
+
64
+ ```bash
65
+ # 1) typescript 설치 (정확 모드 = Compiler API)
66
+ npm i -g typescript
67
+ leerness lsp check # → ✓ typescript 발견 (Compiler API)
68
+
69
+ # 미설치 시 정규식 fallback 자동 사용 (TS/JS 한정)
70
+ leerness lsp check # → ⚠ typescript 미설치 → regex fallback
71
+
72
+ # 2) 심볼 추출
73
+ leerness lsp symbols src/api.ts # → function/class/interface/type/enum
74
+ leerness lsp symbols src/api.ts --json # 구조화 출력 (line, kind, name)
75
+
76
+ # 3) 참조 검색
77
+ leerness lsp references myFunction --in src # → 모든 호출 위치 (file:line)
78
+ ```
79
+
80
+ ### Bridge 패턴 — opt-in 의존성 + 정규식 fallback (이중 안전망)
81
+ - `_tryLoadLSP()` — `typescript` (Compiler API) try + npm 글로벌 root 폴백
82
+ - 미설치 시 → `_lspRegexSymbols()` 정규식 fallback (function/class/interface/type/enum/arrow function)
83
+ - 설치 시 → `_lspTsSymbols()` TypeScript Compiler API 정확 모드 (AST 기반)
84
+ - `_recordRun(kind: 'lsp_symbols' | 'lsp_references')` observability
85
+
86
+ ### 6능력 매트릭스 (신규 영역 신설 + production-ready 유지)
87
+ | 영역 | 1.9.166 | **1.9.167** |
88
+ |---|---|---|
89
+ | (1) 웹 자동화 | 50% ⚠ | 50% ⚠ |
90
+ | (2) PC 조작 | 50% ⚠ | 50% ⚠ |
91
+ | (3) 멀티 오케스트레이션 | 90% ✓ | 90% ✓ |
92
+ | (4) REPL/자율성 | 90% ✓ | 90% ✓ |
93
+ | (5) MCP 도구 | 100% ✓ | 100% ✓ |
94
+ | (6) **코드 인텔리전스** | **— (없음)** | **50% ⚠** (bridge, typescript 미설치) → **90% ✓** (사용자 설치 시) |
95
+ | **종합** | 76% (5 영역 평균) | **72%** (6 영역 평균, production-ready 유지) |
96
+
97
+ **평가**: 종합 점수는 영역 추가로 일시적으로 76→72% 로 떨어졌으나 production-ready (≥70%) 유지. 새 영역 codeIntel 이 90% 도달 시 종합 75%.
98
+
99
+ ### Verified
100
+ - e2e baseline (1.9.166: 217/217) 유지 회귀 없음
101
+ - stress-v112: 23/23 (LSP 함수 6 + CLI 실 동작 7 + 6능력 매트릭스 3 + 누적 회귀 7)
102
+ - VERSION = 1.9.167 / autonomous-rounds = 97 / main 자동 push 28 라운드 연속
103
+
104
+ ### 실 측정 (regex fallback 모드)
105
+ - `lsp symbols harness.js` (12,000+ lines) → 472 symbols / 392ms
106
+ - `lsp references lspCmd --in leerness-pkg` → 3 refs / 9ms
107
+
108
+ ---
109
+
3
110
  ## 1.9.166 — 2026-05-20
4
111
 
5
112
  **🎉 production-ready 76% 마일스톤 — pc 조작 bridge MVP (robotjs/nut-tree opt-in).**
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.166-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v111-20%2F20-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-50-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-96-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-27_rounds-success)]() [![pc-bridge](https://img.shields.io/badge/pc_bridge-robotjs%2Fnut--tree_opt--in-success)]() [![web-bridge](https://img.shields.io/badge/playwright_bridge-opt--in_MVP-success)]() [![capability](https://img.shields.io/badge/5_capability-76%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.168-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v113-17%2F17-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-53-brightgreen)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-98-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-29_rounds-success)]() [![mcp-bridge](https://img.shields.io/badge/MCP_bridge-web%2Fpc%2Flsp_노출-success)]() [![lsp-bridge](https://img.shields.io/badge/lsp_bridge-typescript_opt--in%2Bregex_fallback-success)]() [![pc-bridge](https://img.shields.io/badge/pc_bridge-robotjs%2Fnut--tree_opt--in-success)]() [![web-bridge](https://img.shields.io/badge/playwright_bridge-opt--in_MVP-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.166 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.168 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🎉 76% production-ready (web + pc bridge opt-in MVP)
17
+ 53 MCP tools · web/pc/lsp bridge 외부 AI 직접 호출 가능
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.166';
9
+ const VERSION = '1.9.168';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -9850,7 +9850,10 @@ function mcpServeCmd(root) {
9850
9850
  { name: 'leerness_env_detect', description: '1.9.145 — 실행 환경 자동 감지 + 변동 추적 JSON ({ snapshot: { os, hardware, locale, shell, node, tools, scriptDependencies }, diff: { firstCapture, changes, missing }, persisted }). "X은(는) 내부 또는 외부 명령... 아닙니다" 사전 방지: package.json scripts 의존 도구가 PATH에 있는지 검증 + 머신/Node/도구 변경 감지. 절대경로 마스킹 (보안). 인자: { path? }', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
9851
9851
  { name: 'leerness_provider_list', description: '1.9.157/158 — Provider Registry 조회 JSON ({ total, builtin, user, providers: [{ id, bin, envFlag, source, desc }] }). 빌트인 5종 (claude/codex/gemini/copilot/ollama) + .harness/providers.json 사용자 정의 통합. 외부 AI가 sub-agent 분배 가능한 provider 전체 회수 (OpenRouter/Bedrock 등 등록되어 있으면 같이 노출). 🎉 MCP 48 도구 마일스톤', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
9852
9852
  { name: 'leerness_provider_add', description: '1.9.159 — Provider Registry 에 새 provider 동적 추가. 인자: { id (required), bin?, envFlag?, versionArgs?, desc?, path? }. 외부 AI가 새 CLI 발견 시 자가 확장 (OpenRouter / Bedrock / Groq / Hugging Face 등 등록). 같은 id 두 번 호출 → 갱신. 빌트인 id 호출 → user override. id 는 영문자/숫자/_- 만 허용.', inputSchema: { type: 'object', properties: { id: { type: 'string' }, bin: { type: 'string' }, envFlag: { type: 'string' }, versionArgs: { type: 'string' }, desc: { type: 'string' }, installHint: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
9853
- { name: 'leerness_provider_remove', description: '1.9.159 — Provider Registry 에서 사용자 정의 provider 제거. 인자: { id (required), path? }. 빌트인 5종 id 는 제거 불가 (override 만 제거 가능). 🎉 MCP 50 도구 마일스톤 — Provider Registry CRUD MCP 완성 (list/add/remove)', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } }
9853
+ { name: 'leerness_provider_remove', description: '1.9.159 — Provider Registry 에서 사용자 정의 provider 제거. 인자: { id (required), path? }. 빌트인 5종 id 는 제거 불가 (override 만 제거 가능). 🎉 MCP 50 도구 마일스톤 — Provider Registry CRUD MCP 완성 (list/add/remove)', inputSchema: { type: 'object', properties: { id: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
9854
+ { name: 'leerness_web', description: '1.9.168 — Web Bridge (1.9.165 playwright opt-in). sub: check (설치 + permissions.browser 확인) | screenshot (URL → PNG) | extract (URL + CSS selector → DOM 텍스트). 외부 AI가 leerness 의 웹 자동화 능력을 직접 호출. playwright 미설치 시 친절 안내 (graceful). 인자: { sub (required), url?, out?, selector?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'screenshot', 'extract'] }, url: { type: 'string' }, out: { type: 'string' }, selector: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } },
9855
+ { name: 'leerness_pc', description: '1.9.168 — PC Bridge (1.9.166 robotjs/nut-tree opt-in). sub: check (설치 + permissions.mouse/keyboard) | click (x,y) | type (text) | screenshot (out). ⚠ full permissions 권장 (mouse/keyboard 접근). 외부 AI가 데스크탑 자동화 능력을 직접 호출. 인자: { sub (required), x?, y?, text?, out?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'click', 'type', 'screenshot'] }, x: { type: 'number' }, y: { type: 'number' }, text: { type: 'string' }, out: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } },
9856
+ { name: 'leerness_lsp', description: '1.9.168 — LSP Bridge (1.9.167 typescript opt-in + regex fallback). sub: check (설치 여부) | symbols (file → function/class/interface/type/enum 목록) | references (name + in 디렉토리 → 호출 위치). 외부 AI가 코드 인텔리전스를 직접 호출 (의존성 0 fallback 동작). 🎉 MCP 53 도구 마일스톤. 인자: { sub (required), file?, name?, in?, path? }', inputSchema: { type: 'object', properties: { sub: { type: 'string', enum: ['check', 'symbols', 'references'] }, file: { type: 'string' }, name: { type: 'string' }, in: { type: 'string' }, path: { type: 'string' } }, required: ['sub'] } }
9854
9857
  ];
9855
9858
 
9856
9859
  function send(obj) {
@@ -9957,6 +9960,27 @@ function mcpServeCmd(root) {
9957
9960
  case 'leerness_provider_remove':
9958
9961
  cliArgs = ['provider', 'remove', String(args.id || ''), '--path', targetPath];
9959
9962
  break;
9963
+ // 1.9.168: Bridge 3종 MCP 노출 (web/pc/lsp) — 외부 AI 가 직접 호출
9964
+ case 'leerness_web':
9965
+ cliArgs = ['web', String(args.sub || 'check'), '--path', targetPath, '--json'];
9966
+ if (args.url) cliArgs.splice(2, 0, String(args.url));
9967
+ if (args.out) cliArgs.push('--out', String(args.out));
9968
+ if (args.selector) cliArgs.push('--selector', String(args.selector));
9969
+ break;
9970
+ case 'leerness_pc':
9971
+ cliArgs = ['pc', String(args.sub || 'check'), '--path', targetPath, '--json'];
9972
+ if (typeof args.x === 'number' && typeof args.y === 'number') {
9973
+ cliArgs.splice(2, 0, String(args.x), String(args.y));
9974
+ }
9975
+ if (args.text) cliArgs.splice(2, 0, String(args.text));
9976
+ if (args.out) cliArgs.push('--out', String(args.out));
9977
+ break;
9978
+ case 'leerness_lsp':
9979
+ cliArgs = ['lsp', String(args.sub || 'check'), '--path', targetPath, '--json'];
9980
+ if (args.file) cliArgs.splice(2, 0, String(args.file));
9981
+ if (args.name) cliArgs.splice(2, 0, String(args.name));
9982
+ if (args.in) cliArgs.push('--in', String(args.in));
9983
+ break;
9960
9984
  default:
9961
9985
  return send({ jsonrpc: '2.0', id, error: { code: -32601, message: `Unknown tool: ${name}` } });
9962
9986
  }
@@ -11617,11 +11641,22 @@ function healthCmd(root) {
11617
11641
  cap.mcpTools = toolCount >= 50
11618
11642
  ? { score: 100, status: '✓', evidence: `${toolCount}/50+ 도구 (1.9.159 CRUD 완성)` }
11619
11643
  : { score: Math.round((toolCount / 50) * 100), status: toolCount > 30 ? '✓' : '⚠', evidence: `${toolCount} 도구` };
11620
- const avgScore = Math.round((cap.webAutomation.score + cap.pcAutomation.score + cap.multiAgentOrchestration.score + cap.replMultiProvider.score + cap.mcpTools.score) / 5);
11644
+ // (6) 코드 인텔리전스 1.9.167 LSP 어댑터 + typescript 설치 detect
11645
+ const hasLspBridge = /function lspCmd\(root, sub/.test(harnessSrc);
11646
+ let tsInstalled = false;
11647
+ try { require('typescript'); tsInstalled = true; } catch {}
11648
+ if (hasLspBridge && tsInstalled) {
11649
+ cap.codeIntel = { score: 90, status: '✓', evidence: 'typescript 설치 + leerness lsp bridge (1.9.167, Compiler API)' };
11650
+ } else if (hasLspBridge) {
11651
+ cap.codeIntel = { score: 50, status: '⚠', evidence: 'leerness lsp bridge 있음, typescript 미설치 (regex fallback 동작, npm i -g typescript)' };
11652
+ } else {
11653
+ cap.codeIntel = { score: 5, status: '❌', evidence: 'LSP 어댑터 미구현 (코드 인텔리전스 없음)' };
11654
+ }
11655
+ const avgScore = Math.round((cap.webAutomation.score + cap.pcAutomation.score + cap.multiAgentOrchestration.score + cap.replMultiProvider.score + cap.mcpTools.score + cap.codeIntel.score) / 6);
11621
11656
  out.capabilityMatrix = {
11622
11657
  capabilities: cap,
11623
11658
  overallScore: avgScore,
11624
- summary: `웹${cap.webAutomation.score}/PC${cap.pcAutomation.score}/멀티${cap.multiAgentOrchestration.score}/REPL${cap.replMultiProvider.score}/MCP${cap.mcpTools.score} · 종합 ${avgScore}%`,
11659
+ summary: `웹${cap.webAutomation.score}/PC${cap.pcAutomation.score}/멀티${cap.multiAgentOrchestration.score}/REPL${cap.replMultiProvider.score}/MCP${cap.mcpTools.score}/LSP${cap.codeIntel.score} · 종합 ${avgScore}%`,
11625
11660
  assessment: avgScore >= 70 ? 'production-ready' : avgScore >= 50 ? 'beta-ready' : 'mvp'
11626
11661
  };
11627
11662
  } catch { out.capabilityMatrix = { error: '5능력 매트릭스 평가 실패' }; }
@@ -11668,7 +11703,7 @@ function healthCmd(root) {
11668
11703
  // 1.9.163: 5능력 매트릭스 — 1.9.155 sub-agent 점검의 코드 기반 자동 평가
11669
11704
  if (out.capabilityMatrix && !out.capabilityMatrix.error) {
11670
11705
  log('');
11671
- log(`## 🧪 5능력 매트릭스 (1.9.163 자동 평가)`);
11706
+ log(`## 🧪 6능력 매트릭스 (1.9.167 자동 평가)`);
11672
11707
  const cm = out.capabilityMatrix;
11673
11708
  log(` 종합: ${cm.overallScore}% (${cm.assessment})`);
11674
11709
  log(` (1) 웹 자동화 ${cm.capabilities.webAutomation.status} ${cm.capabilities.webAutomation.score}% · ${cm.capabilities.webAutomation.evidence}`);
@@ -11676,6 +11711,7 @@ function healthCmd(root) {
11676
11711
  log(` (3) 멀티 오케스트레이션 ${cm.capabilities.multiAgentOrchestration.status} ${cm.capabilities.multiAgentOrchestration.score}% · ${cm.capabilities.multiAgentOrchestration.evidence}`);
11677
11712
  log(` (4) REPL multi-provider ${cm.capabilities.replMultiProvider.status} ${cm.capabilities.replMultiProvider.score}% · ${cm.capabilities.replMultiProvider.evidence}`);
11678
11713
  log(` (5) MCP 도구 ${cm.capabilities.mcpTools.status} ${cm.capabilities.mcpTools.score}% · ${cm.capabilities.mcpTools.evidence}`);
11714
+ log(` (6) 코드 인텔리전스 ${cm.capabilities.codeIntel.status} ${cm.capabilities.codeIntel.score}% · ${cm.capabilities.codeIntel.evidence}`);
11679
11715
  }
11680
11716
  if (issues.length) {
11681
11717
  log('');
@@ -12215,6 +12251,177 @@ function pcCmd(root, sub, ...args) {
12215
12251
  fail(`알 수 없는 sub: ${sub} (check / click / type / screenshot)`);
12216
12252
  }
12217
12253
 
12254
+ // 1.9.167: LSP 어댑터 MVP — 코드 인텔리전스 bridge (opt-in 의존성)
12255
+ // typescript 모듈 detect → 실제 TypeScript Compiler API 사용
12256
+ // 미설치 시 정규식 fallback (그래도 동작) → score 5/50/90 차등
12257
+ function _tryLoadLSP() {
12258
+ // typescript 우선 (Compiler API), 추후 pyright/vscode-languageserver 후보
12259
+ const candidates = ['typescript'];
12260
+ for (const id of candidates) {
12261
+ try { return { ok: true, lib: require(id), name: id }; } catch {}
12262
+ }
12263
+ // 글로벌 npm root 시도
12264
+ try {
12265
+ const r = cp.spawnSync('npm', ['root', '-g'], { encoding: 'utf8', timeout: 5000, shell: true });
12266
+ if (r.status === 0) {
12267
+ const globalRoot = (r.stdout || '').trim();
12268
+ for (const id of candidates) {
12269
+ try { return { ok: true, lib: require(path.join(globalRoot, id)), name: id, source: 'global' }; } catch {}
12270
+ }
12271
+ }
12272
+ } catch {}
12273
+ return { ok: false, error: 'typescript 미설치 — `npm i -g typescript` 후 다시 시도 (또는 정규식 fallback 사용)' };
12274
+ }
12275
+
12276
+ // 정규식 fallback — TypeScript/JavaScript symbol 추출 (LSP 없이도 동작)
12277
+ function _lspRegexSymbols(content) {
12278
+ const symbols = [];
12279
+ const lines = content.split(/\r?\n/);
12280
+ const patterns = [
12281
+ { re: /^\s*(?:export\s+)?(?:async\s+)?function\s+([A-Za-z_$][\w$]*)\s*\(/, kind: 'function' },
12282
+ { re: /^\s*(?:export\s+)?class\s+([A-Za-z_$][\w$]*)/, kind: 'class' },
12283
+ { re: /^\s*(?:export\s+)?interface\s+([A-Za-z_$][\w$]*)/, kind: 'interface' },
12284
+ { re: /^\s*(?:export\s+)?(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?(?:function|\()/, kind: 'function' },
12285
+ { re: /^\s*(?:export\s+)?type\s+([A-Za-z_$][\w$]*)\s*=/, kind: 'type' },
12286
+ { re: /^\s*(?:export\s+)?enum\s+([A-Za-z_$][\w$]*)/, kind: 'enum' },
12287
+ ];
12288
+ lines.forEach((line, idx) => {
12289
+ for (const p of patterns) {
12290
+ const m = line.match(p.re);
12291
+ if (m) { symbols.push({ name: m[1], kind: p.kind, line: idx + 1 }); break; }
12292
+ }
12293
+ });
12294
+ return symbols;
12295
+ }
12296
+
12297
+ // TypeScript Compiler API 기반 symbol 추출 (정확)
12298
+ function _lspTsSymbols(ts, content, fileName) {
12299
+ const symbols = [];
12300
+ const sf = ts.createSourceFile(fileName, content, ts.ScriptTarget.Latest, true);
12301
+ function visit(node) {
12302
+ let name = null, kind = null;
12303
+ if (ts.isFunctionDeclaration(node) && node.name) { name = node.name.text; kind = 'function'; }
12304
+ else if (ts.isClassDeclaration(node) && node.name) { name = node.name.text; kind = 'class'; }
12305
+ else if (ts.isInterfaceDeclaration(node)) { name = node.name.text; kind = 'interface'; }
12306
+ else if (ts.isTypeAliasDeclaration(node)) { name = node.name.text; kind = 'type'; }
12307
+ else if (ts.isEnumDeclaration(node)) { name = node.name.text; kind = 'enum'; }
12308
+ else if (ts.isVariableStatement(node)) {
12309
+ node.declarationList.declarations.forEach(d => {
12310
+ if (d.name && d.name.text && d.initializer
12311
+ && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))) {
12312
+ const { line } = sf.getLineAndCharacterOfPosition(d.getStart());
12313
+ symbols.push({ name: d.name.text, kind: 'function', line: line + 1 });
12314
+ }
12315
+ });
12316
+ }
12317
+ if (name) {
12318
+ const { line } = sf.getLineAndCharacterOfPosition(node.getStart());
12319
+ symbols.push({ name, kind, line: line + 1 });
12320
+ }
12321
+ ts.forEachChild(node, visit);
12322
+ }
12323
+ visit(sf);
12324
+ return symbols;
12325
+ }
12326
+
12327
+ function lspCmd(root, sub, ...args) {
12328
+ root = absRoot(root || process.cwd());
12329
+ if (!sub || sub === 'check') {
12330
+ const r = _tryLoadLSP();
12331
+ if (has('--json')) {
12332
+ log(JSON.stringify({
12333
+ installed: r.ok,
12334
+ name: r.name || null,
12335
+ source: r.source || 'local',
12336
+ error: r.error || null,
12337
+ fallback: 'regex (always available)'
12338
+ }, null, 2));
12339
+ return;
12340
+ }
12341
+ log(`# leerness lsp check (1.9.167)`);
12342
+ if (r.ok) {
12343
+ log(`✓ ${r.name} 발견${r.source ? ` (${r.source})` : ''}`);
12344
+ log(` → leerness lsp symbols / references 정확 모드 (Compiler API) 사용`);
12345
+ } else {
12346
+ log(`⚠ ${r.error}`);
12347
+ log(` → 정규식 fallback 으로 동작 (TS/JS 한정, 정확도 약간 낮음)`);
12348
+ }
12349
+ return;
12350
+ }
12351
+ if (sub === 'symbols') {
12352
+ const file = args[0] || arg('--file', '');
12353
+ if (!file) return fail('leerness lsp symbols <file> 필요');
12354
+ if (!fs.existsSync(file)) return fail(`파일 없음: ${file}`);
12355
+ const content = fs.readFileSync(file, 'utf8');
12356
+ const t0 = Date.now();
12357
+ const r = _tryLoadLSP();
12358
+ let symbols, mode;
12359
+ try {
12360
+ if (r.ok && /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(file)) {
12361
+ symbols = _lspTsSymbols(r.lib, content, file);
12362
+ mode = 'typescript-compiler';
12363
+ } else {
12364
+ symbols = _lspRegexSymbols(content);
12365
+ mode = 'regex-fallback';
12366
+ }
12367
+ } catch (e) {
12368
+ symbols = _lspRegexSymbols(content);
12369
+ mode = 'regex-fallback (after error: ' + e.message + ')';
12370
+ }
12371
+ const dt = Date.now() - t0;
12372
+ if (has('--json')) {
12373
+ log(JSON.stringify({ file, symbols, count: symbols.length, mode, durationMs: dt }, null, 2));
12374
+ } else {
12375
+ log(`# leerness lsp symbols (1.9.167)`);
12376
+ log(`file: ${file}`);
12377
+ log(`mode: ${mode} · ${symbols.length} symbols · ${dt}ms`);
12378
+ symbols.slice(0, 50).forEach(s => log(` ${String(s.line).padStart(5)}:${s.kind.padEnd(10)} ${s.name}`));
12379
+ if (symbols.length > 50) log(` ... ${symbols.length - 50} more`);
12380
+ }
12381
+ try { _recordRun(root, { kind: 'lsp_symbols', file, count: symbols.length, mode, durationMs: dt, ok: true }); } catch {}
12382
+ return;
12383
+ }
12384
+ if (sub === 'references') {
12385
+ const name = args[0] || arg('--name', '');
12386
+ if (!name) return fail('leerness lsp references <symbol-name> 필요');
12387
+ const inDir = arg('--in', root);
12388
+ const t0 = Date.now();
12389
+ // grep 기반 fallback (실 LSP textDocument/references 대신)
12390
+ const refs = [];
12391
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
12392
+ const reWord = new RegExp(`\\b${escapedName}\\b`);
12393
+ function walk(d) {
12394
+ let entries; try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
12395
+ for (const e of entries) {
12396
+ if (e.name.startsWith('.') || e.name === 'node_modules' || e.name === 'dist' || e.name === 'build') continue;
12397
+ const p = path.join(d, e.name);
12398
+ if (e.isDirectory()) walk(p);
12399
+ else if (/\.(ts|tsx|js|jsx|mjs|cjs|md)$/.test(e.name)) {
12400
+ try {
12401
+ const lines = fs.readFileSync(p, 'utf8').split(/\r?\n/);
12402
+ lines.forEach((ln, idx) => {
12403
+ if (reWord.test(ln)) refs.push({ file: path.relative(root, p), line: idx + 1, text: ln.trim().slice(0, 120) });
12404
+ });
12405
+ } catch {}
12406
+ }
12407
+ }
12408
+ }
12409
+ walk(inDir);
12410
+ const dt = Date.now() - t0;
12411
+ if (has('--json')) {
12412
+ log(JSON.stringify({ name, count: refs.length, references: refs.slice(0, 100), durationMs: dt }, null, 2));
12413
+ } else {
12414
+ log(`# leerness lsp references (1.9.167)`);
12415
+ log(`symbol: "${name}" · ${refs.length} references · ${dt}ms`);
12416
+ refs.slice(0, 30).forEach(r => log(` ${r.file}:${r.line} ${r.text}`));
12417
+ if (refs.length > 30) log(` ... ${refs.length - 30} more`);
12418
+ }
12419
+ try { _recordRun(root, { kind: 'lsp_references', name, count: refs.length, durationMs: dt, ok: true }); } catch {}
12420
+ return;
12421
+ }
12422
+ fail(`알 수 없는 sub: ${sub} (check / symbols / references)`);
12423
+ }
12424
+
12218
12425
  // 1.9.164: leerness which — 진단 도구 (구버전 충돌 / npx 캐시 / PATH 충돌 해결)
12219
12426
  // 사용자가 "최신 버전 작동 안 함" 의심 시: 실제 실행 중인 leerness 의 경로 / 버전 / npm 캐시 / PATH 후보 표시.
12220
12427
  function whichCmd() {
@@ -12296,7 +12503,7 @@ function whichCmd() {
12296
12503
  }
12297
12504
 
12298
12505
  function help() {
12299
- log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
12506
+ log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: 실 spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness which [--json] # 1.9.164 진단: 현재 실행 경로/버전 + npm 캐시 + PATH 후보 (구버전 충돌 해결)\n leerness web check|screenshot|extract <url> [--out file.png] [--selector "css"] # 1.9.165 playwright bridge (opt-in: npm i -g playwright + permissions.browser)\n leerness pc check|click|type|screenshot [--x N --y N] [--text "s"] [--out f.png] # 1.9.166 robotjs/nut-tree bridge (opt-in: npm i -g robotjs + permissions.mouse/keyboard, ⚠ full 모드 권장)\n leerness lsp check|symbols|references <file/name> [--in dir] [--json] # 1.9.167 LSP 어댑터 MVP (typescript opt-in + regex fallback, 코드 인텔리전스)\n leerness contract verify <spec.md> <impl.js> [--json] # 1.9.35 명세 ↔ 구현 일치 검사 (함수/필드)\n leerness reuse autodetect [path] [--apply] [--json] # 1.9.35 src/*.js의 module.exports → reuse-map 후보 등록\n leerness audit [path] [--fix] # 1.9.35 --fix: session-handoff/current-state 자동 갱신\n leerness verify-claim <T-ID> ... [--strict-claims] # 1.9.26 verify-claim에 낙관적 표시 자동 검사 통합\n leerness reuse-map [path] [--all-apps] [--include p1,p2] [--strict-elements] [--json] # 1.9.18 중복/잠재중복/depends-on\n leerness verify-claim <T-ID> [--path .] [--run-tests] [--json] # 1.9.18-20 evidence 자동 검증 (1.9.20: scenes/scripts 등 도메인 폴더 + jest/mocha 파싱)\n leerness verify-code [path] [--build] [--bench] # 1.9.20 --bench: scripts.bench 추가 실행 + evidence 누적\n leerness session close [path]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
12300
12507
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
12301
12508
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
12302
12509
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -12376,6 +12583,8 @@ async function main() {
12376
12583
  if (cmd === 'web') return webCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
12377
12584
  // 1.9.166: leerness pc — robotjs/nut-tree bridge (opt-in 의존성)
12378
12585
  if (cmd === 'pc') return pcCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
12586
+
12587
+ if (cmd === 'lsp') return lspCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
12379
12588
  if (cmd === 'contract' && args[1] === 'verify') return contractVerifyCmd(args[2], args[3]);
12380
12589
  if (cmd === 'drift' && (args[1] === 'check' || !args[1])) return driftCheckCmd(args[2] || arg('--path', process.cwd()));
12381
12590
  if (cmd === 'usage' && (args[1] === 'stats' || !args[1])) return usageStatsCmd(args[2] || arg('--path', process.cwd()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.166",
3
+ "version": "1.9.168",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",