leerness 1.9.247 → 1.9.249

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,77 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.249 — 2026-05-28 — UR-0018 터미널 인코딩 자동 회복 (한국어 Windows)
4
+
5
+ **🌐 사용자 명시 (UR-0018): "leerness가 적용된 프로젝트에서 터미널 출력이 깨지지 않게, 하드웨어의 언어 등을 사전에 참고하여 진행".**
6
+
7
+ ### 배경
8
+ 사용자 보고: `.harness/protected-files.md` 출력에서 "?뚯씪 ??젣/?뺣━" 패턴 (UTF-8 → CP949 오해석) 발생. 한국어 Windows 기본 코드페이지(CP949, 949)에서 leerness가 UTF-8로 출력한 한글이 깨져 보이는 문제.
9
+
10
+ ### 구현 (bootstrap 자동 회복 + 가시화)
11
+ 1. **`_ensureStdoutEncoding()` IIFE** (harness.js bootstrap, DEP0190 다음):
12
+ - `process.stdout/stderr.setEncoding('utf8')` 즉시 강제
13
+ - Windows + 비-65001 코드페이지 감지 시 → `chcp.com 65001` 자동 호출 (best-effort)
14
+ - 결과를 `process.env._LEERNESS_AUTOCHCP_APPLIED` 에 기록
15
+ - 무한 재호출 방지: `_LEERNESS_CHCP_DONE='1'` 자식 process 가드
16
+ - opt-out: `LEERNESS_NO_AUTOCHCP=1`
17
+ 2. **env summary** 강화:
18
+ - "터미널 인코딩 UTF-8 (65001) — 안전" / "터미널 코드페이지 CP949 — 한국어 출력 깨짐 위험" 명시
19
+ 3. **handoff body** 한국어 Windows + 비-65001 시 경고 섹션 추가
20
+ 4. **JSON envInfo** 신규 2필드:
21
+ - `terminalEncodingOk: codepage === 65001`
22
+ - `autoChcpApplied: process.env._LEERNESS_AUTOCHCP_APPLIED` (적용된 이전 코드페이지)
23
+ 5. **BUG fix** (`_collectRuntimeEnv`): chcp 출력 파싱 regex `\d{3,4}` → `\d{3,5}` (65001 5자리 캡처)
24
+
25
+ ### 영향 받지 않은 영역
26
+ - 1.9.248 agy / 1.9.247 fallback / 1.9.246 status bar / 1.9.245 api-skill / 1.9.244 HOTFIX 모두 유지
27
+ - handoff JSON 11 필드 매트릭스 유지 (envInfo 확장)
28
+
29
+ ### stress-v194 — **23/23 PASS · 100%**
30
+ - 1.9.249 (10): VERSION + IIFE bootstrap + chcp 자동/opt-out/재호출 가드 + env summary + handoff body + JSON envInfo 2필드 + 실제 응답 (terminalEncodingOk=true) + env summary 실제 출력
31
+ - 성능 (2): cold start avg 385ms (autochcp 포함) · 357ms (opt-out)
32
+ - 누적 회귀 (11): 1.9.207~248 + 보안
33
+
34
+ ### 자동 release (111 main-push streak · 72 npm publish streak · R205)
35
+
36
+ 🌐 **사용자 환경 자동 회복** — Windows 한국어 사용자의 한글 깨짐을 코드페이지 차원에서 사전 차단.
37
+
38
+ ---
39
+
40
+ ## 1.9.248 — 2026-05-26 — UR-0017 Gemini CLI 제거 + Antigravity CLI (agy) 도입
41
+
42
+ **🔄 사용자 명시 (UR-0017): Gemini CLI 제거 + Antigravity (agy) CLI 전체 교체.**
43
+
44
+ ### 변경 범위 (65 occurrences case-insensitive)
45
+ - **EXTERNAL_AGENTS**: `gemini` → `agy` (bin/envFlag/install 안내 모두 교체)
46
+ - **_PROVIDER_CYCLE_ORDER**: `['ollama', 'claude', 'codex', 'agy', 'copilot']` (Tab cycle)
47
+ - **_PROVIDER_MODEL_CATALOG.agy**: `antigravity-pro/flash/experimental` 모델 (1M+ context)
48
+ - **환경변수**: `LEERNESS_ENABLE_GEMINI` → `LEERNESS_ENABLE_AGY`
49
+ - **CLI bin**: `gemini -p` → `agy -p` (1-shot + REPL stream + dispatch)
50
+ - **agents dispatch --write**: `gemini --yolo` → `agy --yolo`
51
+ - **setup-agents UI**: "Gemini (gemini CLI)" → "Antigravity (agy CLI)"
52
+ - **handoff body sub-agent display** (1.9.246 status bar): allAgents = ['claude', 'codex', 'agy', 'copilot']
53
+ - **MCP description**: agents_list / provider_list 등 5종 → agy 명시
54
+ - **REPL :provider validation**: validProviders = ['ollama', 'claude', 'codex', 'agy', 'copilot']
55
+ - **.harness/leerness-config.json**: LEERNESS_ENABLE_AGY 기본값 "0"
56
+ - **scripts/e2e.js**: 회귀 테스트 — agents quota/dispatch/list/register-pending 모두 agy
57
+ - **install hint**: `npm i -g @google/antigravity-cli` (https://antigravity.google.com)
58
+
59
+ ### 영향 받지 않은 영역
60
+ - 1.9.247 multi-provider fallback (agy 도 자동 fallback 대상)
61
+ - 1.9.246 status bar (agy 5종 한 줄 표시)
62
+ - handoff JSON 11 필드 (apiSkills/envInfo 등) 유지
63
+
64
+ ### stress-v193 — **23/23 PASS · 100%**
65
+ - 1.9.248 (12): VERSION + EXTERNAL_AGENTS 교체 + 모델 catalog + Tab cycle + setup-agents UI + handoff status bar + dispatch + quota + list + config.json + validation + nonsecret keys
66
+ - 성능 (1): cold start avg 419ms
67
+ - 누적 회귀 (10): 1.9.207~247
68
+
69
+ ### 자동 release (110 main-push streak · 71 npm publish streak · R204)
70
+
71
+ 🔄 **Gemini CLI 단계적 제거 → Antigravity (agy) 전환** — 사용자 환경 변화 즉시 반영
72
+
73
+ ---
74
+
3
75
  ## 1.9.247 — 2026-05-26 — UR-0016 2단계 + UR-0015 2단계
4
76
 
5
77
  **🔁 REPL multi-provider auto-fallback + 📚 api-skill audit 통합.**
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.247-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v192-20%2F20-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-70-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-57-brightgreen)]() [![json-fields](https://img.shields.io/badge/JSON_통합-11_필드-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous_R-203-blueviolet)]() [![main-push](https://img.shields.io/badge/🎉_main--push_streak-109_rounds-success)]() [![npm-streak](https://img.shields.io/badge/🎉_npm--publish-70_streak-success)]() [![fallback](https://img.shields.io/badge/🔁_REPL_fallback-multi--provider-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-588ms-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.249-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v194-23%2F23-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-70-brightgreen)]() [![cli](https://img.shields.io/badge/CLI_commands-57-brightgreen)]() [![json-fields](https://img.shields.io/badge/JSON_통합-11_필드-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous_R-205-blueviolet)]() [![main-push](https://img.shields.io/badge/🎉_main--push_streak-111_rounds-success)]() [![npm-streak](https://img.shields.io/badge/🎉_npm--publish-72_streak-success)]() [![encoding](https://img.shields.io/badge/🌐_터미널인코딩-자동회복-blueviolet)]() [![perf](https://img.shields.io/badge/cold_start-385ms-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.247 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.249 AI Agent Reliability Harness + Sandbox ║
16
16
  ║ verify · remember · orchestrate · audit · sandbox · drift ║
17
- 🔁 REPL multi-provider fallback + 📚 api-skill audit
17
+ 🌐 터미널 인코딩 자동 회복 (한국어 Windows · UR-0018)
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.247';
10
+ const VERSION = '1.9.249';
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 처리).
@@ -24,6 +24,36 @@ if (!/--no-deprecation/.test(process.env.NODE_OPTIONS || '')) {
24
24
  process.env.NODE_OPTIONS = ((process.env.NODE_OPTIONS || '') + ' --no-deprecation').trim();
25
25
  }
26
26
 
27
+ // 1.9.249 (사용자 명시 UR-0018): 터미널 출력 한국어 깨짐 사전 방지
28
+ // 사용자 보고: protected-files.md 같은 한국어 텍스트가 ? / 한자 조각으로 깨짐 (UTF-8 → CP949 오인식)
29
+ // 조치: (1) stdout/stderr UTF-8 명시 (2) Windows + 한국어 OS + 비-65001 코드페이지 시 chcp 65001 자동 시도
30
+ // 안전: 1회만 시도 (LEERNESS_NO_AUTOCHCP=1 로 opt-out), 실패 시 무시
31
+ (function _ensureStdoutEncoding() {
32
+ try {
33
+ if (process.stdout && process.stdout.setEncoding) process.stdout.setEncoding('utf8');
34
+ if (process.stderr && process.stderr.setEncoding) process.stderr.setEncoding('utf8');
35
+ } catch {}
36
+ // Windows + 한국어 사용자 환경 (chcp 949 = CP949) → 한국어 콘솔 자동 65001 (UTF-8)
37
+ if (process.platform !== 'win32') return;
38
+ if (process.env.LEERNESS_NO_AUTOCHCP === '1') return;
39
+ if (process.env._LEERNESS_CHCP_DONE === '1') return; // 자식 spawn 무한 재호출 방지
40
+ try {
41
+ const r = cp.spawnSync('chcp.com', [], { encoding: 'utf8', timeout: 2000, shell: true });
42
+ const out = (r.stdout || '') + (r.stderr || '');
43
+ const m = out.match(/(\d+)\s*$/m);
44
+ const code = m ? parseInt(m[1], 10) : null;
45
+ // 65001 = UTF-8, 949 = CP949 (한국), 932 = CP932 (일본), 936 = CP936 (중국)
46
+ if (code && code !== 65001) {
47
+ // 자동 chcp 65001 시도 (실패해도 무시 — 출력 방향 X 인 비-TTY 등)
48
+ try {
49
+ cp.spawnSync('chcp.com', ['65001'], { encoding: 'utf8', timeout: 2000, shell: true, stdio: 'ignore' });
50
+ process.env._LEERNESS_AUTOCHCP_APPLIED = String(code); // 진단용 — handoff body 에서 노출
51
+ } catch {}
52
+ }
53
+ process.env._LEERNESS_CHCP_DONE = '1';
54
+ } catch {}
55
+ })();
56
+
27
57
  const MARK = '<!-- leerness:managed -->';
28
58
  const README_START = '<!-- leerness:project-readme:start -->';
29
59
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -410,7 +440,7 @@ leerness agents dispatch "<task>" --to <id> # 작업 유형 추천 자동
410
440
  - 작업 유형별 최적 sub-agent:
411
441
  - 텍스트/번역/분석 → claude (1.7× 빠름)
412
442
  - 깊은 코드 추론 → codex (가장 상세)
413
- - 파일 직접 수정 → gemini --yolo (정확)
443
+ - 파일 직접 수정 → agy --yolo (정확, Antigravity CLI 1.9.248)
414
444
  - 보안 리뷰 → \`leerness review --persona security\`
415
445
  - **충돌 방지 규칙 (필수)**:
416
446
  - 각 sub-agent에 *자신만 수정할 파일 경로* 명시
@@ -798,20 +828,20 @@ async function resolveInstallOptions(root, opts = {}) {
798
828
  const picked = await _selectMany(_t('install.agents.title', _agLang), [
799
829
  { label: 'Claude (ANTHROPIC_API_KEY 또는 claude CLI)', description: '추론력 최고 — 코드 작성/리뷰 기본', id: 'claude' },
800
830
  { label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
801
- { label: 'Gemini (gemini CLI)', description: 'Google 멀티모달 모델', id: 'gemini' },
831
+ { label: 'Antigravity (agy CLI)', description: 'Google Antigravity 멀티모달 에이전트 (1.9.248~)', id: 'agy' },
802
832
  { label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
803
833
  { label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 — 무료/오프라인', id: 'ollama' }
804
834
  ], { defaults: [] });
805
835
  agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
806
836
  } else {
807
837
  log('\nCLI 에이전트 활성화 (복수 선택 — 콤마로 구분, opt-in):');
808
- log(' 1) claude 2) codex 3) gemini 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
838
+ log(' 1) claude 2) codex 3) agy (Antigravity) 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
809
839
  const a = (await ask('선택 [none]: ')).trim().toLowerCase();
810
- if (a === 'all') agentsOptIn = ['claude', 'codex', 'gemini', 'copilot', 'ollama'];
840
+ if (a === 'all') agentsOptIn = ['claude', 'codex', 'agy', 'copilot', 'ollama'];
811
841
  else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
812
842
  else {
813
- const map = { '1': 'claude', '2': 'codex', '3': 'gemini', '4': 'copilot', '5': 'ollama' };
814
- const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','gemini','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
843
+ const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'copilot', '5': 'ollama' };
844
+ const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','agy','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
815
845
  agentsOptIn = picks.length ? picks : 'none';
816
846
  }
817
847
  }
@@ -951,7 +981,7 @@ async function install(root, opts = {}) {
951
981
  const a = resolved.agentsOptIn || 'none';
952
982
  const enabledSet = (() => {
953
983
  if (Array.isArray(a)) return new Set(a);
954
- if (a === 'all') return new Set(['claude', 'codex', 'gemini', 'copilot', 'ollama']);
984
+ if (a === 'all') return new Set(['claude', 'codex', 'agy', 'copilot', 'ollama']);
955
985
  if (a === 'none' || !a) return new Set();
956
986
  return new Set([a]); // back-compat: 단일 문자열
957
987
  })();
@@ -983,7 +1013,7 @@ async function install(root, opts = {}) {
983
1013
  LEERNESS_OLLAMA_MODEL: '',
984
1014
  LEERNESS_ENABLE_CLAUDE: enable('claude') ? '1' : '0',
985
1015
  LEERNESS_ENABLE_CODEX: enable('codex') ? '1' : '0',
986
- LEERNESS_ENABLE_GEMINI: enable('gemini') ? '1' : '0',
1016
+ LEERNESS_ENABLE_AGY: enable('agy') ? '1' : '0',
987
1017
  LEERNESS_ENABLE_COPILOT: enable('copilot') ? '1' : '0',
988
1018
  LEERNESS_ENABLE_OLLAMA: enable('ollama') ? '1' : '0',
989
1019
  LEERNESS_SKILL_DISCOVER_URL: '',
@@ -2408,9 +2438,9 @@ function _collectRuntimeEnv() {
2408
2438
  // 한국어 Windows 감지
2409
2439
  try {
2410
2440
  if (env.os.platform === 'win32') {
2411
- // chcp 출력으로 코드페이지 확인
2441
+ // chcp 출력으로 코드페이지 확인 (1.9.249 fix: 65001 (5자리) 까지 매칭 — 이전 \d{3,4} 는 "6500" 만 캡처 BUG)
2412
2442
  const r = cp.spawnSync('chcp', [], { encoding: 'utf8', shell: true, timeout: 3000 });
2413
- const m = (r.stdout || '').match(/(\d{3,4})/);
2443
+ const m = (r.stdout || '').match(/(\d{3,5})/);
2414
2444
  if (m) {
2415
2445
  const cp_num = parseInt(m[1], 10);
2416
2446
  env.locale.codepage = cp_num;
@@ -2577,6 +2607,17 @@ function envCmd(root, sub) {
2577
2607
  log(` 💻 OS: ${gr(env.os.platform)} ${env.os.release} (${env.os.arch})`);
2578
2608
  log(` 📦 Node: ${gr(env.node.version)}`);
2579
2609
  log(` 🌐 Locale: LANG=${env.locale.LANG || 'unset'} · CP=${env.locale.codepage || 'unknown'}${env.locale.isKoreanWindows ? yl(' (한국어 Windows)') : ''}`);
2610
+ // 1.9.249 (UR-0018): 터미널 출력 인코딩 자동 점검 — 한국어 Windows + 비-65001 → 경고 + 자동 회복 결과
2611
+ if (env.locale.isKoreanWindows && env.locale.codepage !== 65001) {
2612
+ log(yl(` ⚠ 터미널 코드페이지 ${env.locale.codepage} (CP949) — 한국어 출력 깨짐 위험`));
2613
+ if (process.env._LEERNESS_AUTOCHCP_APPLIED) {
2614
+ log(gr(` ✓ chcp 65001 자동 적용됨 (이전: ${process.env._LEERNESS_AUTOCHCP_APPLIED}, 1.9.249)`));
2615
+ } else {
2616
+ log(dm(` → 수동: chcp 65001 (또는 LEERNESS_NO_AUTOCHCP=0)`));
2617
+ }
2618
+ } else if (env.locale.codepage === 65001) {
2619
+ log(gr(` ✓ 터미널 인코딩 UTF-8 (65001) — 한국어 출력 안전`));
2620
+ }
2580
2621
  log(` 🖥 Hardware: ${env.hardware.cpus} CPUs · ${env.hardware.memGB} GB RAM`);
2581
2622
  log(` 🖱 Terminal: TTY=${env.terminal.isTTY}${env.terminal.powershell ? ` · PowerShell v${env.terminal.powershell}` : ''}`);
2582
2623
  log(` 🔧 Tools:`);
@@ -6977,6 +7018,7 @@ function handoff(root) {
6977
7018
  };
6978
7019
  } catch {}
6979
7020
  // 1.9.242: envInfo 통합 (handoff JSON 10번째 통합 필드) — 환경 + 인코딩 위험 (UR-0014 2단계)
7021
+ // 1.9.249: terminalEncodingOk + autoChcpApplied 필드 추가 (UR-0018)
6980
7022
  try {
6981
7023
  const runtimeEnv = _collectRuntimeEnv();
6982
7024
  const encScan = _scanShellScriptsEncoding(root);
@@ -6987,7 +7029,10 @@ function handoff(root) {
6987
7029
  nodeVersion: runtimeEnv.node.version,
6988
7030
  shellScriptsScanned: encScan.scanned,
6989
7031
  encodingRiskCount: encScan.atRisk.length,
6990
- encodingRiskFiles: encScan.atRisk.slice(0, 5).map(r => r.file)
7032
+ encodingRiskFiles: encScan.atRisk.slice(0, 5).map(r => r.file),
7033
+ // 1.9.249 (UR-0018): 터미널 출력 인코딩 안전 여부 + 자동 회복 결과
7034
+ terminalEncodingOk: runtimeEnv.locale.codepage === 65001 || !runtimeEnv.locale.isKoreanWindows,
7035
+ autoChcpApplied: process.env._LEERNESS_AUTOCHCP_APPLIED || null
6991
7036
  };
6992
7037
  } catch {}
6993
7038
  // 1.9.245: apiSkills 통합 (handoff JSON 11번째 통합 필드) — UR-0015 API 문서 캐시
@@ -7375,6 +7420,26 @@ function handoff(root) {
7375
7420
  }
7376
7421
  } catch {}
7377
7422
 
7423
+ // 1.9.249 (UR-0018): 터미널 코드페이지 자동 점검 + 자동 회복 노출
7424
+ // 한국어 Windows + 비-65001 → leerness 출력 깨짐 위험 → 자동 chcp 65001 (bootstrap), 결과 본문 노출
7425
+ try {
7426
+ const runtimeEnv = _collectRuntimeEnv();
7427
+ if (runtimeEnv.locale.isKoreanWindows && runtimeEnv.locale.codepage !== 65001) {
7428
+ const isTty5 = process.stdout && process.stdout.isTTY;
7429
+ const yl6 = s => isTty5 ? `\x1b[33m${s}\x1b[0m` : s;
7430
+ const gr6 = s => isTty5 ? `\x1b[32m${s}\x1b[0m` : s;
7431
+ const dm6 = s => isTty5 ? `\x1b[2m${s}\x1b[0m` : s;
7432
+ log('');
7433
+ log(yl6(`## ⚠ 터미널 인코딩 — 한국어 Windows + CP${runtimeEnv.locale.codepage} (1.9.249, UR-0018)`));
7434
+ log(dm6(` leerness 출력의 한국어가 ?/한자 조각으로 깨질 수 있음 (UTF-8 → CP949 오인식)`));
7435
+ if (process.env._LEERNESS_AUTOCHCP_APPLIED) {
7436
+ log(gr6(` ✓ 자동 회복: chcp 65001 적용됨 (이전: ${process.env._LEERNESS_AUTOCHCP_APPLIED})`));
7437
+ } else {
7438
+ log(dm6(` → 수동: chcp 65001 + 새 터미널 또는 LEERNESS_NO_AUTOCHCP 미설정 시 다음 호출 자동 회복`));
7439
+ }
7440
+ }
7441
+ } catch {}
7442
+
7378
7443
  // 1.9.245: API skill cache 자동 참조 (사용자 명시 UR-0015)
7379
7444
  // 현재 task 키워드 기반으로 .harness/api-skills/ 매칭 → 사용자가 정리해둔 API 문서 자동 노출
7380
7445
  try {
@@ -8808,7 +8873,7 @@ const _LEERNESS_NONSECRET_KEYS = new Set([
8808
8873
  'LEERNESS_OLLAMA_MODEL', // 모델 이름 — 비밀 X
8809
8874
  'LEERNESS_ENABLE_CLAUDE', // 활성화 플래그
8810
8875
  'LEERNESS_ENABLE_CODEX',
8811
- 'LEERNESS_ENABLE_GEMINI',
8876
+ 'LEERNESS_ENABLE_AGY',
8812
8877
  'LEERNESS_ENABLE_COPILOT',
8813
8878
  'LEERNESS_ENABLE_OLLAMA',
8814
8879
  'LEERNESS_SKILL_DISCOVER_URL', // 공개 URL
@@ -9452,7 +9517,7 @@ function _resolvePersona(root, id) {
9452
9517
  return null;
9453
9518
  }
9454
9519
 
9455
- // 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/gemini/copilot 가용성 + 활성화 체크
9520
+ // 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/agy/copilot 가용성 + 활성화 체크
9456
9521
  // 사용자 정책: 환경변수로 활성화 명시 + 실제 PATH 존재 확인 + 메인이 sub-agent 분배 시 참조
9457
9522
  // 1.9.32: installCmd 추가 — setup-agents 시 자동 설치 시도 가능
9458
9523
  const EXTERNAL_AGENTS = [
@@ -9460,8 +9525,11 @@ const EXTERNAL_AGENTS = [
9460
9525
  installCmd: 'npm i -g @anthropic-ai/claude-code', installHint: 'https://docs.anthropic.com/en/docs/claude-code/setup' },
9461
9526
  { id: 'codex', bin: 'codex', envFlag: 'LEERNESS_ENABLE_CODEX', versionArgs: ['--version'], desc: 'OpenAI Codex CLI (격리 sandbox)',
9462
9527
  installCmd: 'npm i -g @openai/codex', installHint: 'https://github.com/openai/codex' },
9463
- { id: 'gemini', bin: 'gemini', envFlag: 'LEERNESS_ENABLE_GEMINI', versionArgs: ['--version'], desc: 'Google Gemini CLI (--yolo 모드 워크스페이스 직접 수정 가능)',
9464
- installCmd: 'npm i -g @google/gemini-cli', installHint: 'https://github.com/google-gemini/gemini-cli' },
9528
+ // 1.9.248 (사용자 명시 UR-0017): Gemini CLI 제거 + Antigravity CLI (agy 명령어) 교체
9529
+ // Google Antigravity IDE 기반 멀티 모달 에이전트 도구. agy CLI 는 워크스페이스 직접 수정 가능 모드 지원.
9530
+ // 기존 LEERNESS_ENABLE_AGY 환경변수는 LEERNESS_ENABLE_AGY 로 교체. provider id 도 'agy' 로 통일.
9531
+ { id: 'agy', bin: 'agy', envFlag: 'LEERNESS_ENABLE_AGY', versionArgs: ['--version'], desc: 'Google Antigravity CLI (멀티모달 에이전트, --yolo 워크스페이스 수정 가능)',
9532
+ installCmd: 'npm i -g @google/antigravity-cli', installHint: 'https://antigravity.google.com (Antigravity IDE/CLI)' },
9465
9533
  { id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)',
9466
9534
  installCmd: 'gh extension install github/gh-copilot', installHint: 'https://github.com/github/gh-copilot (gh CLI 선행 설치 필요)' },
9467
9535
  // 1.9.146: Ollama 추가 (사용자 명시 요청 #3) — 로컬 LLM, HTTP API 11434
@@ -9668,7 +9736,7 @@ function providerCmd(root, sub, ...args) {
9668
9736
  // 1.9.195: provider probe — A축 (범용 AI 하네스) 보강. 사용자 의도: "범용 AI 하네스 ... 최고의 도구"
9669
9737
  // 시스템에 사용 가능한 provider 자동 감지 (PATH 명령 + 로컬 endpoint + API 키 환경변수)
9670
9738
  // 감지 대상:
9671
- // - CLI: claude / codex / gemini / copilot / ollama (PATH check)
9739
+ // - CLI: claude / codex / agy / copilot / ollama (PATH check)
9672
9740
  // - Local endpoint: Ollama (11434) / LM Studio (1234) / llama.cpp server (8080)
9673
9741
  // - Cloud API key: OPENROUTER_API_KEY / GROQ_API_KEY / TOGETHER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY
9674
9742
  // 결과 → table + LEERNESS_ENABLE_* 설정 권장
@@ -9721,7 +9789,7 @@ async function _probeProviderEndpoints(root, timeoutMs) {
9721
9789
  const cliTargets = [
9722
9790
  { id: 'claude', bin: 'claude' },
9723
9791
  { id: 'codex', bin: 'codex' },
9724
- { id: 'gemini', bin: 'gemini' },
9792
+ { id: 'agy', bin: 'agy' },
9725
9793
  { id: 'copilot', bin: 'copilot' },
9726
9794
  { id: 'ollama', bin: 'ollama' }
9727
9795
  ];
@@ -9830,7 +9898,7 @@ function _recommendAgent(task) {
9830
9898
  // 파일 작성·수정·생성
9831
9899
  if (hasAny(['create', 'write', 'generate', 'patch', 'fix', 'implement', 'edit',
9832
9900
  '구현', '생성', '작성', '수정', '추가'])) {
9833
- return { target: 'gemini', reason: '워크스페이스 직접 수정은 gemini --yolo가 정확' };
9901
+ return { target: 'agy', reason: '워크스페이스 직접 수정은 agy --yolo (Antigravity) 가 정확' };
9834
9902
  }
9835
9903
  return { target: null, reason: '' };
9836
9904
  }
@@ -10346,7 +10414,7 @@ function _dispatchCommand(agentId, task, writeMode) {
10346
10414
  const q = String(task || '').replace(/"/g, '\\"');
10347
10415
  if (agentId === 'claude') return `claude ${writeMode ? '--print --dangerously-skip-permissions' : '--print'} "${q}"`;
10348
10416
  if (agentId === 'codex') return `codex ${writeMode ? 'exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox' : 'exec --skip-git-repo-check'} "${q}"`;
10349
- if (agentId === 'gemini') return `gemini ${writeMode ? '-p --yolo' : '-p'} "${q}"`;
10417
+ if (agentId === 'agy') return `agy ${writeMode ? '-p --yolo' : '-p'} "${q}"`;
10350
10418
  if (agentId === 'copilot') return `gh copilot suggest "${q}"`;
10351
10419
  if (agentId === 'ollama') return `# ollama — leerness agent "${q}" --provider ollama 로 직접 호출 (REPL: leerness agent)`;
10352
10420
  return `# ${agentId}: 명령 빌더 미정의`;
@@ -10380,8 +10448,8 @@ function agentsCmd(root, sub, ...args) {
10380
10448
  if (!ready.length) {
10381
10449
  log('');
10382
10450
  log(`💡 활성화 방법:`);
10383
- log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/gemini-cli\`)`);
10384
- log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1, LEERNESS_ENABLE_GEMINI=1`);
10451
+ log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/antigravity-cli\`)`);
10452
+ log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1, LEERNESS_ENABLE_AGY=1`);
10385
10453
  log(` 3) \`leerness agents check\`로 재확인`);
10386
10454
  log(` 💡 1.9.157: 빌트인 외 CLI 추가: \`leerness provider add <id> --bin <cmd>\``);
10387
10455
  } else {
@@ -10564,7 +10632,7 @@ function agentsCmd(root, sub, ...args) {
10564
10632
  if (has('--multi') || target === 'all' || target === '*') {
10565
10633
  return agentsCmd(root, 'multi', ...args);
10566
10634
  }
10567
- if (!target) { fail('--to <agent_id> 필요 (claude/codex/gemini/copilot) — 활성 전체에 일괄 분배는 `leerness agents multi`'); return process.exit(1); }
10635
+ if (!target) { fail('--to <agent_id> 필요 (claude/codex/agy/copilot) — 활성 전체에 일괄 분배는 `leerness agents multi`'); return process.exit(1); }
10568
10636
  const agentDef = EXTERNAL_AGENTS.find(a => a.id === target);
10569
10637
  if (!agentDef) { fail(`알 수 없는 agent: ${target}`); return process.exit(1); }
10570
10638
  // 1.9.36: 작업 유형 키워드 분석 → 최적 CLI 추천 (ready 체크 전에 출력 — 비활성이어도 추천)
@@ -10599,9 +10667,9 @@ function agentsCmd(root, sub, ...args) {
10599
10667
  log(`codex ${flags} "${q}"`);
10600
10668
  log(`# ℹ codex는 PowerShell 경유 — POSIX /tmp 경로는 C:\\tmp\\로 해석됨`);
10601
10669
  if (writeMode) log(`# ⚠ --dangerously-bypass-approvals-and-sandbox: sandbox 우회`);
10602
- } else if (target === 'gemini') {
10670
+ } else if (target === 'agy') {
10603
10671
  const flags = writeMode ? '-p --yolo' : '-p';
10604
- log(`gemini ${flags} "${q}"`);
10672
+ log(`agy ${flags} "${q}"`);
10605
10673
  if (writeMode) log(`# ⚠ --yolo: 워크스페이스 파일 직접 수정 가능`);
10606
10674
  } else if (target === 'copilot') {
10607
10675
  log(`gh copilot suggest "${q}"`);
@@ -10652,9 +10720,9 @@ function agentsCmd(root, sub, ...args) {
10652
10720
  ? ['exec', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', task]
10653
10721
  : ['exec', '--skip-git-repo-check', task];
10654
10722
  cmd = 'codex';
10655
- } else if (agent.id === 'gemini') {
10723
+ } else if (agent.id === 'agy') {
10656
10724
  cmdArgs = writeMode ? ['-p', task, '--yolo'] : ['-p', task];
10657
- cmd = 'gemini';
10725
+ cmd = 'agy';
10658
10726
  } else if (agent.id === 'copilot') {
10659
10727
  cmdArgs = ['copilot', 'suggest', task];
10660
10728
  cmd = 'gh';
@@ -10728,10 +10796,10 @@ function agentsCmd(root, sub, ...args) {
10728
10796
  out.hint = 'https://platform.openai.com/account/usage 확인';
10729
10797
  }
10730
10798
  out.raw = help.slice(0, 200);
10731
- } else if (agent.id === 'gemini') {
10732
- // gemini CLI: 무료 티어는 분당 60req 제한, CLI 자체에선 노출 안 됨
10799
+ } else if (agent.id === 'agy') {
10800
+ // agy CLI (Antigravity): 무료 티어는 분당 60req 제한, CLI 자체에선 노출 안 됨
10733
10801
  out.quota = 'rate-limited';
10734
- out.hint = '무료 티어: 60 req/min, 1000 req/day · 유료는 https://ai.google.dev/gemini-api/docs/rate-limits';
10802
+ out.hint = '무료 티어: 60 req/min, 1000 req/day · Antigravity 유료 플랜은 https://antigravity.google.com';
10735
10803
  } else if (agent.id === 'copilot') {
10736
10804
  // gh copilot은 GitHub Copilot 구독 (월 단위 quota 없음, individual/business 플랜)
10737
10805
  const r = cp.spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 4000, shell: true });
@@ -10873,13 +10941,13 @@ function reviewCmd(root, target) {
10873
10941
  }
10874
10942
 
10875
10943
  // 1.9.25: register-pending — sub-agent/외부 모델이 작업 시작 즉시 progress-tracker에 in-progress 등록
10876
- // 사용 예: leerness register-pending "<요청 내용>" --agent gemini
10944
+ // 사용 예: leerness register-pending "<요청 내용>" --agent agy
10877
10945
  // → 다음 T-ID 자동 할당, status=in-progress, evidence="(pending) by <agent>"
10878
10946
  // → 다른 세션이 즉시 발견 가능 (모순 감지)
10879
10947
  function registerPendingCmd(root, requestParts) {
10880
10948
  root = absRoot(root || process.cwd());
10881
10949
  const request = (requestParts || []).join(' ').trim();
10882
- if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent gemini'); return process.exit(1); }
10950
+ if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent agy'); return process.exit(1); }
10883
10951
  const agent = arg('--agent', 'unknown');
10884
10952
  const note = arg('--note', '');
10885
10953
 
@@ -14952,7 +15020,7 @@ async function _benchmarkMeasure(root, task) {
14952
15020
  let cmd, cliArgs;
14953
15021
  if (agent.id === 'claude') { cmd = 'claude'; cliArgs = ['--print', task]; }
14954
15022
  else if (agent.id === 'codex') { cmd = 'codex'; cliArgs = ['exec', '--skip-git-repo-check', task]; }
14955
- else if (agent.id === 'gemini') { cmd = 'gemini'; cliArgs = ['-p', task]; }
15023
+ else if (agent.id === 'agy') { cmd = 'agy'; cliArgs = ['-p', task]; }
14956
15024
  else continue;
14957
15025
  const t0 = Date.now();
14958
15026
  // 1.9.150: runCommandSafe — agent CLI bench sandbox (env scrub + observability)
@@ -15428,7 +15496,7 @@ function mcpServeCmd(root) {
15428
15496
  { name: 'leerness_audit', description: '1.9.102 — 워크스페이스 일관성 감사 JSON (warnings/failures/fixed/healthy + findings[]. kind 11종: design_dup/design_system_default/reuse_map_empty/milestone_unlinked/handoff_not_generated/current_state_stale/readme_version_mismatch/npm_cve/gitignore_missing_secrets/env_keys_missing/strict_promoted)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, fix: { type: 'boolean' }, strict: { type: 'boolean' } } } },
15429
15497
  { name: 'leerness_verify_claim', description: 'AI 거짓 완료 자동 검증 (evidence 파일 + 실 테스트 실행)', inputSchema: { type: 'object', properties: { taskId: { type: 'string' }, path: { type: 'string' }, runTests: { type: 'boolean' }, strictClaims: { type: 'boolean' } }, required: ['taskId'] } },
15430
15498
  { name: 'leerness_contract_verify', description: '명세 ↔ 구현 함수/필드 일치 자동 검사', inputSchema: { type: 'object', properties: { spec: { type: 'string' }, impl: { type: 'string' } }, required: ['spec', 'impl'] } },
15431
- { name: 'leerness_agents_list', description: '외부 AI CLI 가용성 표 (claude/codex/gemini/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
15499
+ { name: 'leerness_agents_list', description: '외부 AI CLI 가용성 표 (claude/codex/agy/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
15432
15500
  { name: 'leerness_reuse_map', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
15433
15501
  { name: 'leerness_whats_new', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
15434
15502
  { name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
@@ -15470,7 +15538,7 @@ function mcpServeCmd(root) {
15470
15538
  { name: 'leerness_feature_add', description: '1.9.142 — Feature Graph 에 새 노드 추가 (외부 AI가 코드 작성 중 직접 feature 등록). 인자: { title (required), dependsOn?, affects?, coChangesWith?, files?, path? }. 자동 F-XXXX ID 부여. CRUD 완성에 기여', inputSchema: { type: 'object', properties: { title: { type: 'string' }, dependsOn: { type: 'string' }, affects: { type: 'string' }, coChangesWith: { type: 'string' }, files: { type: 'string' }, path: { type: 'string' } }, required: ['title'] } },
15471
15539
  { name: 'leerness_feature_link', description: '1.9.142 — 기존 feature 노드에 의존/영향/공변경 엣지 추가. 인자: { id (required, F-XXXX), dependsOn?, affects?, coChangesWith?, path? }. 외부 AI가 코드 변경 도중 발견한 인과관계를 즉시 그래프에 반영', inputSchema: { type: 'object', properties: { id: { type: 'string' }, dependsOn: { type: 'string' }, affects: { type: 'string' }, coChangesWith: { type: 'string' }, path: { type: 'string' } }, required: ['id'] } },
15472
15540
  { 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' } } } },
15473
- { 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' } } } },
15541
+ { name: 'leerness_provider_list', description: '1.9.157/158 — Provider Registry 조회 JSON ({ total, builtin, user, providers: [{ id, bin, envFlag, source, desc }] }). 빌트인 5종 (claude/codex/agy/copilot/ollama) + .harness/providers.json 사용자 정의 통합. 외부 AI가 sub-agent 분배 가능한 provider 전체 회수 (OpenRouter/Bedrock 등 등록되어 있으면 같이 노출). 🎉 MCP 48 도구 마일스톤', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
15474
15542
  { 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'] } },
15475
15543
  { 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'] } },
15476
15544
  { 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'] } },
@@ -16163,13 +16231,13 @@ function permissionCheck(root, action, target) {
16163
16231
  }
16164
16232
 
16165
16233
  // ===== 1.9.146: leerness agent — OpenClaw/Hermes 스타일 오픈소스 CLI 에이전트 모드 (사용자 명시 요청 #4) =====
16166
- // MVP: handoff 컨텍스트 자동 로드 → 활성 CLI (claude/codex/gemini/ollama) 1개에 작업 위임.
16234
+ // MVP: handoff 컨텍스트 자동 로드 → 활성 CLI (claude/codex/agy/ollama) 1개에 작업 위임.
16167
16235
  // 권한은 .harness/agent-permissions.json 기준. 실제 LLM 호출은 외부 CLI 또는 Ollama HTTP API.
16168
16236
  function _activeCliAgents() {
16169
16237
  const out = [];
16170
16238
  if (process.env.LEERNESS_ENABLE_CLAUDE === '1') out.push('claude');
16171
16239
  if (process.env.LEERNESS_ENABLE_CODEX === '1') out.push('codex');
16172
- if (process.env.LEERNESS_ENABLE_GEMINI === '1') out.push('gemini');
16240
+ if (process.env.LEERNESS_ENABLE_AGY === '1') out.push('agy');
16173
16241
  if (process.env.LEERNESS_ENABLE_COPILOT === '1') out.push('copilot');
16174
16242
  if (process.env.LEERNESS_ENABLE_OLLAMA === '1') out.push('ollama');
16175
16243
  return out;
@@ -16225,7 +16293,7 @@ async function _ollamaListModels() {
16225
16293
  }
16226
16294
 
16227
16295
  // 1.9.153: 외부 CLI 채팅 호출 (multi-provider REPL — 사용자 명시 요청)
16228
- // claude/codex/gemini/copilot 를 child_process 로 호출 후 stdout 캡처.
16296
+ // claude/codex/agy/copilot 를 child_process 로 호출 후 stdout 캡처.
16229
16297
  // runCommandSafe 경유 — env scrub + permissions + observability 자동 적용.
16230
16298
  async function _cliChat(root, provider, prompt, opts) {
16231
16299
  opts = opts || {};
@@ -16240,7 +16308,7 @@ async function _cliChat(root, provider, prompt, opts) {
16240
16308
  let cmd, args, stdinInput = null;
16241
16309
  if (provider === 'claude') { cmd = 'claude'; args = ['--print']; stdinInput = prompt; }
16242
16310
  else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', '-']; stdinInput = prompt; }
16243
- else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', prompt]; } // gemini 는 인자 only
16311
+ else if (provider === 'agy') { cmd = 'agy'; args = ['-p', prompt]; } // agy (Antigravity) 는 인자 only
16244
16312
  else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', prompt]; }
16245
16313
  else return { ok: false, error: `provider ${provider} 미지원`, provider };
16246
16314
  // runCommandSafe — env scrub + observability 자동
@@ -16296,7 +16364,7 @@ async function _cliChatStream(root, provider, promptText, opts) {
16296
16364
  args = ['exec', '--skip-git-repo-check', '-']; // - = stdin from claude/codex convention
16297
16365
  useStdinForPrompt = true;
16298
16366
  }
16299
- else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', promptText]; } // gemini -p 는 인자 모드만 지원
16367
+ else if (provider === 'agy') { cmd = 'agy'; args = ['-p', promptText]; } // agy -p 는 인자 모드만 지원
16300
16368
  else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', promptText]; }
16301
16369
  else return { ok: false, error: `provider ${provider} 미지원`, provider };
16302
16370
  const t0 = Date.now();
@@ -16632,10 +16700,10 @@ const _PROVIDER_MODEL_CATALOG = {
16632
16700
  { id: 'gpt-5-codex', note: '코드 특화 (Codex)' },
16633
16701
  { id: 'o4-mini', note: '빠른 reasoning' }
16634
16702
  ],
16635
- gemini: [
16636
- { id: 'gemini-2.5-pro', note: 'Google 최고급 (1M+ context)' },
16637
- { id: 'gemini-2.5-flash', note: '빠른 응답' },
16638
- { id: 'gemini-3.0-pro', note: '실험적 (사용 가능 시)' }
16703
+ agy: [
16704
+ { id: 'antigravity-pro', note: 'Antigravity 최고급 (1M+ context)' },
16705
+ { id: 'antigravity-flash', note: '빠른 응답' },
16706
+ { id: 'antigravity-experimental', note: '실험적 (사용 가능 시)' }
16639
16707
  ],
16640
16708
  copilot: [
16641
16709
  { id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
@@ -16649,7 +16717,7 @@ const _PROVIDER_MODEL_CATALOG = {
16649
16717
  };
16650
16718
 
16651
16719
  // 1.9.170: provider cycle 순서 (Tab) — 빌트인 5종. user provider는 동적으로 뒤에 추가.
16652
- const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'gemini', 'copilot'];
16720
+ const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'copilot'];
16653
16721
 
16654
16722
  // 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
16655
16723
  const _AGENT_ROLE_PROMPTS = {
@@ -16829,7 +16897,7 @@ async function _agentRepl(root, opts) {
16829
16897
  // prompt 에 세부 모델 표시 (사용자 명시): agent[claude · claude-opus-4-7 / actor / ▶]>
16830
16898
  const prompt = () => {
16831
16899
  if (!isTty) return 'agent> ';
16832
- const modelShort = (state.model || '').replace(/^(claude|gpt|gemini)-/, '').slice(0, 18);
16900
+ const modelShort = (state.model || '').replace(/^(claude|gpt|gemini|antigravity)-/, '').slice(0, 18);
16833
16901
  const modelTag = modelShort ? C.dim(' · ') + C.mag(modelShort) : '';
16834
16902
  return C.cy(`agent[${state.provider}${modelTag}${C.cy('/' + state.role)}${state.streamMode ? C.cy('/▶') : ''}${C.cy(']>')} `);
16835
16903
  };
@@ -16849,7 +16917,7 @@ async function _agentRepl(root, opts) {
16849
16917
  if (m.includes('gpt-4')) return 128000;
16850
16918
  return 128000;
16851
16919
  }
16852
- if (provider === 'gemini') return 1000000; // 2M까지 가능하지만 보수적으로 1M
16920
+ if (provider === 'agy') return 1000000; // 2M까지 가능하지만 보수적으로 1M
16853
16921
  if (provider === 'copilot') return 64000;
16854
16922
  if (provider === 'ollama') return 8000; // 모델별 다르나 default 8k
16855
16923
  return 100000;
@@ -16882,7 +16950,7 @@ async function _agentRepl(root, opts) {
16882
16950
  .map(a => ({ id: a.id, st: _checkAgent(a).status }))
16883
16951
  .filter(x => x.st === 'ready');
16884
16952
  } catch {}
16885
- const allAgents = ['claude', 'codex', 'gemini', 'copilot'];
16953
+ const allAgents = ['claude', 'codex', 'agy', 'copilot'];
16886
16954
  const subBar = allAgents.map(id => {
16887
16955
  if (id === state.provider) return C.bold(C.green(`*${id}`));
16888
16956
  const isReady = ready.find(r => r.id === id);
@@ -17025,7 +17093,7 @@ async function _agentRepl(root, opts) {
17025
17093
  ['/quit', 'REPL 종료 (세션 자동 저장)'],
17026
17094
  ['/clear', '대화 히스토리 초기화'],
17027
17095
  ['/status', '현재 provider / model / role / perms 상태'],
17028
- ['/provider <id>', 'provider 전환 (ollama, claude, codex, gemini, copilot)'],
17096
+ ['/provider <id>', 'provider 전환 (ollama, claude, codex, agy, copilot)'],
17029
17097
  ['/model <id>', '현재 provider의 모델 전환'],
17030
17098
  ['/role <id>', '역할 전환 (actor / planner / reviewer)'],
17031
17099
  ['/stream on|off', '실시간 스트리밍 토글 (default ON)']
@@ -17077,7 +17145,7 @@ async function _agentRepl(root, opts) {
17077
17145
  log(' :model <name> — 모델 변경 (1.9.155 모든 provider 지원, 예: :model claude-opus-4-7)');
17078
17146
  log(' :models — provider 별 모델 목록 (ollama 실시간 / 그 외 추천 카탈로그)');
17079
17147
  log(' :role <r> — 역할 변경 (planner / reviewer / actor)');
17080
- log(' :provider <p> — provider 변경 (ollama / claude / codex / gemini / copilot — ready 검증)');
17148
+ log(' :provider <p> — provider 변경 (ollama / claude / codex / agy / copilot — ready 검증)');
17081
17149
  log(' :stream on|off — 🆕 1.9.170 실시간 스트리밍 토글 (추론중/diff/thinking 실시간 표시)');
17082
17150
  log(' :status — 현재 세션 상태 자세히 (1.9.155)');
17083
17151
  log(' :clear — 화면 클리어 + history 유지');
@@ -17088,7 +17156,7 @@ async function _agentRepl(root, opts) {
17088
17156
  log(' :perm [mode] — :permissions 단축 alias');
17089
17157
  log(' :quit / :exit / :q — 종료 (자동 저장)');
17090
17158
  log(C.bold('\n 🆕 1.9.170 키보드 단축키:'));
17091
- log(' Tab — 다음 provider 로 cycle (ollama → claude → codex → gemini → copilot)');
17159
+ log(' Tab — 다음 provider 로 cycle (ollama → claude → codex → agy → copilot)');
17092
17160
  log(' Shift+Tab — 현재 provider 의 다음 model 로 cycle (catalog 기준)');
17093
17161
  log(C.bold('\n Slash 명령 (1.9.150) — leerness 내부 명령 직접 호출:'));
17094
17162
  log(' :verify — leerness verify-code (테스트/타입/린트 자동 검수)');
@@ -17155,7 +17223,7 @@ async function _agentRepl(root, opts) {
17155
17223
  }
17156
17224
  if (op === 'provider') {
17157
17225
  const newProv = rest[0] || state.provider;
17158
- const validProviders = ['ollama', 'claude', 'codex', 'gemini', 'copilot'];
17226
+ const validProviders = ['ollama', 'claude', 'codex', 'agy', 'copilot'];
17159
17227
  if (!validProviders.includes(newProv)) {
17160
17228
  log(C.yel(` ⚠ provider 는 ${validProviders.join(' / ')} (받음: ${newProv})`));
17161
17229
  return false;
@@ -17392,12 +17460,12 @@ async function _agentRepl(root, opts) {
17392
17460
  const finalPrompt = `${rolePrompt}\n\nConversation so far:\n${state.history.slice(-6).map(m => `[${m.role}] ${m.content}`).join('\n')}\n\nRespond as ${state.role}:`;
17393
17461
  const t0 = Date.now();
17394
17462
  let result;
17395
- // 1.9.153: multi-provider REPL — ollama 외 claude/codex/gemini/copilot 도 세션 관리 (사용자 명시)
17463
+ // 1.9.153: multi-provider REPL — ollama 외 claude/codex/agy/copilot 도 세션 관리 (사용자 명시)
17396
17464
  // 1.9.170: streamMode === true 이면 _cliChatStream 사용 (사용자 명시 — 추론중/diff 실시간 표시)
17397
17465
  if (state.provider === 'ollama') {
17398
17466
  log(C.dim(` → ollama${state.model ? ' (' + state.model + ')' : ''} 호출 중...`));
17399
17467
  result = await _ollamaChat(finalPrompt, state.model);
17400
- } else if (['claude', 'codex', 'gemini', 'copilot'].includes(state.provider)) {
17468
+ } else if (['claude', 'codex', 'agy', 'copilot'].includes(state.provider)) {
17401
17469
  if (state.streamMode) {
17402
17470
  log(C.dim(` → ${state.provider} CLI stream 호출 중... (Ctrl+C 로 중단)`));
17403
17471
  result = await _cliChatStream(root, state.provider, finalPrompt, { timeout: 120000 });
@@ -17406,7 +17474,7 @@ async function _agentRepl(root, opts) {
17406
17474
  result = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
17407
17475
  }
17408
17476
  } else {
17409
- log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|gemini|copilot`));
17477
+ log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|agy|copilot`));
17410
17478
  promptWithStatus(); return;
17411
17479
  }
17412
17480
  const dt = Date.now() - t0;
@@ -17415,7 +17483,7 @@ async function _agentRepl(root, opts) {
17415
17483
  state.history.push({ role: 'assistant', content: result.response });
17416
17484
  // 1.9.170: stream 모드에서는 이미 실시간으로 출력됐으므로 헤더만 표시 (응답 중복 방지)
17417
17485
  // 1.9.246: 정상 완료 작업은 초록색 ✓ 강조 (사용자 명시 UR-0016)
17418
- if (state.streamMode && ['claude', 'codex', 'gemini', 'copilot'].includes(state.provider)) {
17486
+ if (state.streamMode && ['claude', 'codex', 'agy', 'copilot'].includes(state.provider)) {
17419
17487
  log(C.green(` ✓ [assistant: ${state.provider}/${state.model || 'default'}, role=${state.role}, ${dt}ms · ${result.response.length}자]`));
17420
17488
  } else {
17421
17489
  log('');
@@ -17432,8 +17500,8 @@ async function _agentRepl(root, opts) {
17432
17500
  // 실패 원인 분류
17433
17501
  if (/exit=null/.test(errMsg) || /timeout/i.test(errMsg)) {
17434
17502
  log(C.dim(` ↳ 가능 원인: (1) ${state.provider} CLI 응답 시간 초과 (모델 로딩/큰 응답 대기 중)`));
17435
- log(C.dim(` (2) network/auth 문제 (특히 codex/gemini 는 인터넷 + 토큰 필요)`));
17436
- log(C.dim(` ↳ 직접 검증: ${state.provider} --print "ping" ${state.provider === 'gemini' ? '--yolo' : ''}`));
17503
+ log(C.dim(` (2) network/auth 문제 (특히 codex/agy 는 인터넷 + 토큰 필요)`));
17504
+ log(C.dim(` ↳ 직접 검증: ${state.provider} --print "ping" ${state.provider === 'agy' ? '--yolo' : ''}`));
17437
17505
  } else if (/exit=1/.test(errMsg) || /unauth/i.test(errMsg) || /login/i.test(errMsg)) {
17438
17506
  log(C.dim(` ↳ 가능 원인: 인증 누락 (login 필요)`));
17439
17507
  log(C.dim(` ↳ 해결: ${state.provider} login ${state.provider === 'copilot' ? '(gh auth login)' : ''}`));
@@ -17488,7 +17556,7 @@ async function _agentRepl(root, opts) {
17488
17556
  log(C.dim(` 💡 즉시 전환: ${others.map(x => `:provider ${x.def.id}`).join(' · ')} 또는 Tab 키`));
17489
17557
  if (!state.autoFallback) log(C.dim(` 💡 다음 실패 시 자동 전환: \`:fallback on\` (1.9.247 UR-0016 2단계)`));
17490
17558
  } else {
17491
- log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|GEMINI|COPILOT>=1`));
17559
+ log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|AGY|COPILOT>=1`));
17492
17560
  }
17493
17561
  } catch {}
17494
17562
  }
@@ -17521,12 +17589,12 @@ async function agentCmd(root, taskArg) {
17521
17589
  log(' leerness agent "<task>" # 1회 위임 (actor 역할 기본)');
17522
17590
  log(' leerness agent "<task>" --role planner # 계획만 (1.9.148)');
17523
17591
  log(' leerness agent "<task>" --role reviewer # 비판적 검토 (1.9.148)');
17524
- log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/gemini/copilot)');
17592
+ log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/agy/copilot)');
17525
17593
  log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + Ollama 모델 선택');
17526
17594
  log('');
17527
17595
  log('REPL 메타 명령: :help / :model / :role / :provider / :history / :save / :quit');
17528
17596
  log('REPL Slash 명령 (1.9.150): :verify / :audit / :handoff / :health (sandboxed runCommandSafe)');
17529
- log('REPL Multi-provider (1.9.153): ollama / claude / codex / gemini / copilot — 활성 CLI 자동 감지');
17597
+ log('REPL Multi-provider (1.9.153): ollama / claude / codex / agy / copilot — 활성 CLI 자동 감지');
17530
17598
  log('');
17531
17599
  log('현재 활성 provider: ' + (_activeCliAgents().join(', ') || '(없음) — .env에서 LEERNESS_ENABLE_* 활성화'));
17532
17600
  log('권한 모드: ' + (_readPermissions(root).mode || 'basic'));
@@ -17557,17 +17625,17 @@ async function agentCmd(root, taskArg) {
17557
17625
  // 1.9.148: role prompt 자동 prepend (모든 provider 공통)
17558
17626
  const finalPrompt = `${rolePrompt}\n\nTask: ${task}`;
17559
17627
  const t0 = Date.now();
17560
- // 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/gemini/copilot 직접 호출 (1.9.153 _cliChat 재사용)
17628
+ // 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/agy/copilot 직접 호출 (1.9.153 _cliChat 재사용)
17561
17629
  let r;
17562
17630
  if (provider === 'ollama') {
17563
17631
  log('\n[ollama 호출 중...]');
17564
17632
  r = await _ollamaChat(finalPrompt);
17565
- } else if (['claude', 'codex', 'gemini', 'copilot'].includes(provider)) {
17633
+ } else if (['claude', 'codex', 'agy', 'copilot'].includes(provider)) {
17566
17634
  log(`\n[${provider} CLI 호출 중...]`);
17567
17635
  r = await _cliChat(root, provider, finalPrompt, { timeout: 90000 });
17568
17636
  if (r.ok && !r.model) r.model = provider; // _cliChat 결과 보강
17569
17637
  } else {
17570
- fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/gemini/copilot)`);
17638
+ fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/agy/copilot)`);
17571
17639
  process.exitCode = 1;
17572
17640
  return;
17573
17641
  }
@@ -18136,7 +18204,7 @@ function healthCmd(root) {
18136
18204
  const hasRepl = /async function _agentRepl/.test(harnessSrc);
18137
18205
  const hasCliChat = /async function _cliChat/.test(harnessSrc);
18138
18206
  cap.replMultiProvider = (hasRepl && hasCliChat)
18139
- ? { score: 90, status: '✓', evidence: 'ollama/claude/codex/gemini/copilot 5종 (1.9.149+1.9.153)' }
18207
+ ? { score: 90, status: '✓', evidence: 'ollama/claude/codex/agy/copilot 5종 (1.9.149+1.9.153)' }
18140
18208
  : { score: 30, status: '⚠', evidence: 'REPL 미완성' };
18141
18209
  // (5) MCP 도구 — tools array 카운트
18142
18210
  const toolsMatch = harnessSrc.match(/{ name: 'leerness_/g);
@@ -19345,7 +19413,7 @@ function whichCmd() {
19345
19413
  }
19346
19414
 
19347
19415
  function help() {
19348
- 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 review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\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
19416
+ 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/agy/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 review-request "<request>" [--json] # 1.9.176 사용자 요청 사전 검토 (충돌/재사용/효율/권장 단계 — 사용자 명시)\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
19349
19417
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
19350
19418
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
19351
19419
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.247",
3
+ "version": "1.9.249",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -605,11 +605,11 @@ total++;
605
605
  // register-pending
606
606
  const tmpR = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-regp-'));
607
607
  cp.spawnSync(process.execPath, [CLI, 'init', tmpR, '--yes', '--language', 'ko', '--skills', 'recommended'], { stdio: 'ignore', timeout: 30000 });
608
- const r = cp.spawnSync(process.execPath, [CLI, 'register-pending', 'JSON export 기능', '--agent', 'gemini', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
609
- const okReg = r.status === 0 && /등록됨 \(in-progress\) by gemini/.test(r.stdout);
608
+ const r = cp.spawnSync(process.execPath, [CLI, 'register-pending', 'JSON export 기능', '--agent', 'agy', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
609
+ const okReg = r.status === 0 && /등록됨 \(in-progress\) by agy/.test(r.stdout);
610
610
  // 즉시 progress-tracker에서 검색 가능
611
611
  const r2 = cp.spawnSync(process.execPath, [CLI, 'memory', 'search', 'JSON export', '--path', tmpR], { encoding: 'utf8', timeout: 10000 });
612
- const okSearch = /in-progress/.test(r2.stdout) && /pending.*gemini/.test(r2.stdout);
612
+ const okSearch = /in-progress/.test(r2.stdout) && /pending.*agy/.test(r2.stdout);
613
613
  const ok = okReg && okSearch;
614
614
  console.log(ok ? '✓ B(1.9.25) register-pending: 즉시 등록 + 검색 가능' : `✗ register-pending 실패 (reg=${okReg} search=${okSearch})`);
615
615
  if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
@@ -797,23 +797,23 @@ total++;
797
797
  total++;
798
798
  {
799
799
  // agents list — claude가 환경변수 + PATH 둘 다 충족 시 ready
800
- const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_COPILOT: '0' };
800
+ const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
801
801
  const r1 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list'], { encoding: 'utf8', timeout: 15000, env: env1 });
802
802
  const okList = r1.status === 0
803
803
  && /외부 AI CLI 오케스트레이션 \(1\.9\.30\)/.test(r1.stdout)
804
804
  && /\| claude \|/.test(r1.stdout)
805
805
  && /\| codex \|/.test(r1.stdout)
806
- && /\| gemini \|/.test(r1.stdout)
806
+ && /\| agy \|/.test(r1.stdout)
807
807
  && /\| copilot \|/.test(r1.stdout);
808
808
  // env 모두 0 → 비활성
809
809
  // 1.9.146: Ollama 추가 → 5 CLI
810
- const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_COPILOT: '0', LEERNESS_ENABLE_OLLAMA: '0' };
810
+ const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0', LEERNESS_ENABLE_OLLAMA: '0' };
811
811
  const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list', '--json'], { encoding: 'utf8', timeout: 15000, env: env2 });
812
812
  let parsed = null;
813
813
  try { parsed = JSON.parse(r2.stdout); } catch {}
814
814
  const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length === 5 && parsed.agents.every(a => a.status !== 'ready');
815
815
  const ok = okList && okJson;
816
- console.log(ok ? '✓ B(1.9.30+1.9.146) agents list: 5 CLI 정의 (claude/codex/gemini/copilot/ollama)' : `✗ agents list 실패 (list=${okList} json=${okJson})`);
816
+ console.log(ok ? '✓ B(1.9.30+1.9.146) agents list: 5 CLI 정의 (claude/codex/agy/copilot/ollama)' : `✗ agents list 실패 (list=${okList} json=${okJson})`);
817
817
  if (!ok) { failed++; console.log(r1.stdout.slice(0, 500)); }
818
818
  }
819
819
 
@@ -838,13 +838,13 @@ total++;
838
838
  total++;
839
839
  {
840
840
  // agents quota — env=0 시 모두 disabled/not-installed, 안내 메시지 포함
841
- const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_COPILOT: '0' };
841
+ const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
842
842
  const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota'], { encoding: 'utf8', timeout: 15000, env });
843
843
  const okText = r.status === 0
844
844
  && /외부 AI CLI quota 추정 \(1\.9\.31\)/.test(r.stdout)
845
845
  && /\| claude \|/.test(r.stdout)
846
846
  && /\| codex \|/.test(r.stdout)
847
- && /\| gemini \|/.test(r.stdout)
847
+ && /\| agy \|/.test(r.stdout)
848
848
  && /\| copilot \|/.test(r.stdout)
849
849
  && /provider 대시보드 참조/.test(r.stdout);
850
850
  // JSON 출력
@@ -1752,13 +1752,13 @@ total++;
1752
1752
  // 1.9.36 회귀: dispatch 권장 플래그 + bench + 작업 유형 추천
1753
1753
  total++;
1754
1754
  {
1755
- // dispatch --write 시 gemini --yolo 자동 추가
1756
- const env = { ...process.env, LEERNESS_ENABLE_GEMINI: '1' };
1757
- const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '코드 분석해서 요약', '--to', 'gemini', '--write'], { encoding: 'utf8', timeout: 15000, env });
1758
- // gemini가 ready면 명령 출력에 --yolo 포함, 비-ready면 거부 — 둘 다 OK
1755
+ // dispatch --write 시 agy --yolo 자동 추가
1756
+ const env = { ...process.env, LEERNESS_ENABLE_AGY: '1' };
1757
+ const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '코드 분석해서 요약', '--to', 'agy', '--write'], { encoding: 'utf8', timeout: 15000, env });
1758
+ // agy가 ready면 명령 출력에 --yolo 포함, 비-ready면 거부 — 둘 다 OK
1759
1759
  const ok = (r.status === 0 && /--yolo/.test(r.stdout) && /write \(파일 수정 가능\)/.test(r.stdout))
1760
1760
  || (r.status !== 0 && /비활성|disabled|not-installed/.test(r.stdout));
1761
- console.log(ok ? '✓ B(1.9.36) dispatch --write: gemini --yolo 자동 첨부 또는 비활성 거부' : `✗ dispatch --write 실패`);
1761
+ console.log(ok ? '✓ B(1.9.36) dispatch --write: agy --yolo 자동 첨부 또는 비활성 거부' : `✗ dispatch --write 실패`);
1762
1762
  if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
1763
1763
  }
1764
1764
 
@@ -1777,8 +1777,8 @@ total++;
1777
1777
  total++;
1778
1778
  {
1779
1779
  // 작업 유형 추천 — 비활성 CLI에도 추천 메시지 우선 출력
1780
- const env = { ...process.env, LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_CLAUDE: '0' };
1781
- const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘 한국어를 영어로', '--to', 'gemini'], { encoding: 'utf8', timeout: 15000, env });
1780
+ const env = { ...process.env, LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_CLAUDE: '0' };
1781
+ const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘 한국어를 영어로', '--to', 'agy'], { encoding: 'utf8', timeout: 15000, env });
1782
1782
  // 번역 → claude 추천. ready 체크 전에 추천 출력 → stdout에 "추천...claude" 포함
1783
1783
  const ok = /추천.*claude/.test(r.stdout);
1784
1784
  console.log(ok ? '✓ B(1.9.36) 작업 유형 추천: 번역→claude 추천 (비활성이어도 출력)' : `✗ 추천 실패`);
@@ -1788,7 +1788,7 @@ total++;
1788
1788
  total++;
1789
1789
  {
1790
1790
  // bench 명령: ready CLI 없을 때 거부
1791
- const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_COPILOT: '0' };
1791
+ const env = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_AGY: '0', LEERNESS_ENABLE_COPILOT: '0' };
1792
1792
  const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'bench', 'test'], { encoding: 'utf8', timeout: 15000, env });
1793
1793
  const ok = r.status !== 0 && /ready CLI 없음/.test(r.stdout);
1794
1794
  console.log(ok ? '✓ B(1.9.36) agents bench: ready 없을 때 거부' : `✗ bench 거부 실패`);