leerness 1.9.29 → 1.9.31

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,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.31 — 2026-05-15
4
+
5
+ **`leerness agents quota` — 외부 AI CLI 사용량/한도 추정 + provider 대시보드 안내**.
6
+
7
+ ### Added
8
+
9
+ - **`leerness agents quota`** (1.9.31): 활성 CLI별 quota/rate-limit 정보 표시.
10
+ - **claude**: 비대화형 quota API 없음 → `/status` 슬래시 또는 https://console.anthropic.com/settings/usage 안내.
11
+ - **codex**: `codex --help`에서 `usage`/`quota` 키워드 감지 시 시도 가능 표시, 미감지 시 https://platform.openai.com/account/usage 안내.
12
+ - **gemini**: 무료 티어 `60 req/min, 1000 req/day` 명시.
13
+ - **copilot (gh)**: `gh auth status`로 인증 확인 → 구독자 무제한 또는 `gh auth login` 필요 안내.
14
+ - `--json` 출력 지원 (`{ quota: [{id, bin, status, quota, hint, raw}, ...] }`).
15
+ - **`agents` 사용법 메시지에 `quota` 추가**: `list|check|quota|dispatch`.
16
+ - **`agents dispatch` 안내문에 quota 명령 cross-link** (1.9.31+).
17
+
18
+ ### Policy
19
+ - ❌ leerness는 사용량을 직접 추적하지 않음 (provider 대시보드 참조)
20
+ - ✅ sub-agent 분배 시 quota 여유 큰 CLI를 메인 에이전트가 우선 선택하도록 신호 제공
21
+ - ✅ rate-limit/plan 차이는 provider별 다름 — leerness는 hint만 제공
22
+
23
+ ### 실측 (이번 라운드 사용 사례)
24
+ - agents quota 신규 명령 검증 후 sub-agent ×3 동시 분배
25
+ - e2e: 146/146 통과 (1.9.30 144 + quota 2)
26
+
27
+ ## 1.9.30 — 2026-05-15
28
+
29
+ **외부 AI CLI 오케스트레이션 — 환경변수 활성화 정책 + `leerness agents list/check/dispatch`**.
30
+
31
+ claude/codex/gemini/copilot CLI들을 sub-agent로 명시적 활용 가능. 사용자 동의(환경변수) + PATH 존재 둘 다 충족 시에만 ready.
32
+
33
+ ### Added
34
+
35
+ - **`.env.example`에 4개 활성화 플래그 추가**:
36
+ - `LEERNESS_ENABLE_CLAUDE=1` (Anthropic Claude Code, 기본 활성)
37
+ - `LEERNESS_ENABLE_CODEX=0` (OpenAI Codex CLI, 격리 sandbox)
38
+ - `LEERNESS_ENABLE_GEMINI=0` (Google Gemini CLI, `--yolo` 모드는 워크스페이스 직접 수정 가능)
39
+ - `LEERNESS_ENABLE_COPILOT=0` (GitHub Copilot CLI = `gh copilot`)
40
+ - **`leerness agents list`**: 4 CLI별 (env=1 여부) + (PATH 존재 여부) + 버전 + 상태 (ready/disabled/not-installed) 표 출력. `--json` 지원.
41
+ - **`leerness agents check`**: alias of list (재확인 강조).
42
+ - **`leerness agents dispatch "<task>" --to <id>`**: 활성 ready CLI에 대상 명령 자동 생성 (`claude "..."`, `codex exec "..."`, `gemini -p "..." --yolo`, `gh copilot suggest "..."`).
43
+ - **leerness는 자동 호출 안 함** — 사용자/메인 에이전트가 명시적 실행.
44
+ - 비활성/미설치 시 안내 후 `exit 1`.
45
+
46
+ ### Policy
47
+ - ❌ 환경변수 미설정 또는 PATH 없으면 dispatch 거부
48
+ - ✅ 환경변수 + PATH 둘 다 충족 시에만 ready
49
+ - ✅ leerness는 외부 CLI 자동 호출 금지 (1.9.22 Ollama opt-in과 동일 원칙)
50
+
51
+ ### 실측 (이번 라운드 사용 사례)
52
+ - Claude sub-agent ×2 (PvP 매치메이킹 + 길드 시스템) → 각각 26/26, 23/23 통과
53
+ - Gemini CLI 외부 호출 (yolo 모드) → rpg-stats 통계 대시보드 자동 생성 (13/13, HTML 5.5KB)
54
+ - → 3 도메인 동시 진행, 메인 에이전트가 외부 CLI를 sub-agent처럼 활용
55
+
3
56
  ## 1.9.29 — 2026-05-15
4
57
 
5
58
  **페르소나 시스템 — 5종 내장 + `leerness review --persona` (도메인 깊이 3-4배)**.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > 한국어 우선 AI 개발 하네스. 멀티 에이전트 오케스트레이션 · 자동 검수 · 워크스페이스 가시성 · Ollama opt-in 통합.
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.29-green)]() [![tests](https://img.shields.io/badge/e2e-139%2F139-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.31-green)]() [![tests](https://img.shields.io/badge/e2e-146%2F146-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ## ⚙️ 설치 (Install)
8
8
 
@@ -31,6 +31,8 @@ npm i --save-dev leerness && npx leerness handoff .
31
31
  - LLM 컨텍스트 비용 → `--compact` 모드로 4KB → 500자
32
32
  - AI가 "API 호출 완료"라 보고했지만 코드에 호출 흔적이 없는 낙관적 표시 → `optimism-check`로 자동 감지 (1.9.26/27)
33
33
  - 코드 리뷰가 표면적이라 도메인 깊이 부족 → `leerness review <file> --persona security,performance,ux`로 도메인 페르소나 자동 부여 (1.9.29)
34
+ - 외부 AI CLI(claude/codex/gemini/copilot)를 sub-agent로 활용하고 싶지만 자동 호출은 위험 → 환경변수 활성화 + `leerness agents list/dispatch`로 명시적 분배 (1.9.30)
35
+ - 어떤 CLI에 quota 여유가 남았는지 한눈에 보고 싶을 때 → `leerness agents quota`로 provider별 사용량/한도 추정 (1.9.31)
34
36
 
35
37
  ---
36
38
 
@@ -97,6 +99,9 @@ leerness review <file> --persona security,performance,ux # 1.9.29 도메인
97
99
  leerness persona list # 5종 내장 + 사용자 정의
98
100
  leerness persona show security # 페르소나 본문
99
101
  leerness persona add my-domain # 사용자 정의 페르소나
102
+ leerness agents list # 1.9.30 외부 AI CLI 상태표
103
+ leerness agents quota # 1.9.31 CLI별 사용량/한도 추정
104
+ leerness agents dispatch "<task>" --to gemini # 1.9.30 sub-agent 명령 생성
100
105
  ```
101
106
 
102
107
  ### 워크스페이스 (멀티 프로젝트)
@@ -213,6 +218,9 @@ leerness orchestrate "복잡한 기능" --agents 20
213
218
  | `handoff --compact` | LLM 시스템 프롬프트용 압축 출력 | 1.9.22 |
214
219
  | `review --persona X` | 도메인별 sub-agent 자동 프롬프트 (security/performance/ux/testing/docs) | 1.9.29 |
215
220
  | `persona list/show/add` | 페르소나 카탈로그 관리 (.harness/personas/) | 1.9.29 |
221
+ | `agents list/check` | 4 CLI(claude/codex/gemini/copilot) 활성/설치 상태표 (env + PATH 검증) | 1.9.30 |
222
+ | `agents dispatch --to X` | ready CLI에 대상 명령 자동 생성 (자동 호출 금지) | 1.9.30 |
223
+ | `agents quota` | provider별 사용량/한도 추정 + 대시보드 안내 | 1.9.31 |
216
224
 
217
225
  ---
218
226
 
@@ -293,6 +301,47 @@ leerness persona add my-domain # .harness/personas/my-domain.md 템플릿 생
293
301
 
294
302
  ---
295
303
 
304
+ ## 🤖 외부 AI CLI 오케스트레이션 (1.9.30 / 1.9.31)
305
+
306
+ claude/codex/gemini/copilot CLI들을 sub-agent로 명시적 활용. **자동 호출 절대 금지** — 환경변수 활성화 + PATH 존재 둘 다 충족 시에만 ready.
307
+
308
+ ### 활성화 (`.env`)
309
+ ```bash
310
+ LEERNESS_ENABLE_CLAUDE=1 # Anthropic Claude Code CLI
311
+ LEERNESS_ENABLE_CODEX=1 # OpenAI Codex CLI (격리 sandbox)
312
+ LEERNESS_ENABLE_GEMINI=1 # Google Gemini CLI (--yolo 모드는 워크스페이스 직접 수정 가능)
313
+ LEERNESS_ENABLE_COPILOT=1 # GitHub Copilot CLI (gh copilot)
314
+ ```
315
+
316
+ ### 사용
317
+ ```bash
318
+ leerness agents list # 4 CLI 상태표 (env + PATH + 버전)
319
+ leerness agents quota # provider별 사용량/한도 추정 (1.9.31)
320
+ leerness agents dispatch "<task>" --to gemini # ready CLI에 명령 자동 생성
321
+ ```
322
+
323
+ ### quota 안내 (1.9.31)
324
+ | CLI | 추정 | 안내 |
325
+ |---|---|---|
326
+ | claude | `unknown` | 대화 내 `/status` 슬래시 또는 https://console.anthropic.com/settings/usage |
327
+ | codex | `cli-supported` 또는 `unknown` | `codex usage`/`codex quota` 시도 또는 https://platform.openai.com/account/usage |
328
+ | gemini | `rate-limited` | 무료 60 req/min, 1000 req/day · https://ai.google.dev/gemini-api/docs/rate-limits |
329
+ | copilot | `subscription` 또는 `not-authed` | 월 구독자 무제한 · `gh auth login` 필요 시 안내 |
330
+
331
+ ### 실측 (이번 라운드)
332
+ - Sub B (rpg-craft, Claude 페르소나) → 20/20 pass, 1,567 라인
333
+ - Sub C (rpg-achievements, Claude 페르소나) → 22/22 pass, 1,375 라인
334
+ - Sub D (rpg-instance, Claude 페르소나, cross-project require 시연) → 20/20 pass, 1,016 라인
335
+ - → **3 도메인 동시 진행**, 메인 에이전트가 sub-agent들에 페르소나 + 영역 분배 + 충돌 방지 컨벤션 명시.
336
+
337
+ ### 정책
338
+ - ❌ leerness는 외부 CLI를 자동 호출하지 않음 (사용자/메인 에이전트가 명시적 실행)
339
+ - ✅ `agents dispatch`는 명령 텍스트만 출력 — 복사해서 실행
340
+ - ✅ quota 여유 큰 CLI를 메인 에이전트가 우선 선택하도록 신호 제공
341
+ - ⚠ `gemini --yolo`는 워크스페이스 파일 직접 수정 가능 — 격리 sandbox 아님 (codex와 차이)
342
+
343
+ ---
344
+
296
345
  ## 🤝 Claude Code 통합
297
346
 
298
347
  설치 시 자동 등록: `.claude/commands/{handoff, session-close, audit, lazy-detect, update}.md` · `.claude/skills/leerness.md` (스킬 정의) · `.claude/settings.local.json` (SessionStart hook `update --check`) · `.cursor/rules/leerness.mdc` (Cursor) · `.github/copilot-instructions.md` (Copilot)
@@ -323,6 +372,10 @@ leerness skill consolidate
323
372
  | `LEERNESS_GITHUB_TOKEN` | gh release용 (있을 때) |
324
373
  | **`LEERNESS_OLLAMA_BASE_URL`** | **1.9.22 — orchestrate opt-in 활성화** |
325
374
  | `LEERNESS_OLLAMA_MODEL` | 기본 모델 (orchestrate `--model`로 override) |
375
+ | **`LEERNESS_ENABLE_CLAUDE`** | **1.9.30 — `agents list/dispatch` Claude Code CLI 활성** (=1) |
376
+ | **`LEERNESS_ENABLE_CODEX`** | **1.9.30 — Codex CLI 활성** (=1) |
377
+ | **`LEERNESS_ENABLE_GEMINI`** | **1.9.30 — Gemini CLI 활성** (=1) |
378
+ | **`LEERNESS_ENABLE_COPILOT`** | **1.9.30 — gh copilot 활성** (=1) |
326
379
 
327
380
  ---
328
381
 
@@ -362,12 +415,14 @@ A. `--all-apps`는 현재 디렉토리 + `_apps/*` (또는 부모의 `_apps/*`)
362
415
  npm test # = node ./scripts/e2e.js
363
416
  ```
364
417
 
365
- **139/139 시나리오** 통과 (1.9.7~1.9.29 회귀 + 신규 검증).
418
+ **146/146 시나리오** 통과 (1.9.7~1.9.31 회귀 + 신규 검증).
366
419
 
367
420
  ---
368
421
 
369
422
  ## 📜 변경 이력 (최근)
370
423
 
424
+ - **1.9.31** — `leerness agents quota` (provider별 사용량/한도 추정 + 대시보드 안내). 멀티 에이전트 분배 신호.
425
+ - **1.9.30** — 외부 AI CLI 오케스트레이션 (claude/codex/gemini/copilot) + 환경변수 활성화 정책 + `leerness agents list/check/dispatch`
371
426
  - **1.9.29** — 페르소나 시스템 (5종 내장) + `leerness review --persona` (도메인 깊이 3-4배)
372
427
  - **1.9.28** — 카카오페이/네이버페이 패턴 + confidence floor 0.15
373
428
  - **1.9.27** — `optimism-check` 강화: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수
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.29';
9
+ const VERSION = '1.9.31';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -439,7 +439,12 @@ async function install(root, opts = {}) {
439
439
  '# 1.9.22 — orchestrate opt-in. URL이 설정되면 leerness가 Ollama를 사용 가능. 미설정 시 LLM 호출 자동 시작 금지.',
440
440
  'LEERNESS_OLLAMA_BASE_URL=',
441
441
  '# 선택. 기본 모델 (orchestrate --model 로 override 가능).',
442
- 'LEERNESS_OLLAMA_MODEL='
442
+ 'LEERNESS_OLLAMA_MODEL=',
443
+ '# 1.9.30 — 외부 AI CLI 활성화 플래그. 1=활성, 0/미설정=비활성. 메인 에이전트가 sub-agent 분배 시 활성 CLI들에 작업 위임 가능.',
444
+ 'LEERNESS_ENABLE_CLAUDE=1',
445
+ 'LEERNESS_ENABLE_CODEX=0',
446
+ 'LEERNESS_ENABLE_GEMINI=0',
447
+ 'LEERNESS_ENABLE_COPILOT=0'
443
448
  ]);
444
449
  mergeLinesFile(path.join(root, '.gitattributes'), [
445
450
  '* text=auto eol=lf','*.bat text eol=crlf','*.ps1 text eol=crlf'
@@ -2262,6 +2267,182 @@ function _resolvePersona(root, id) {
2262
2267
  return null;
2263
2268
  }
2264
2269
 
2270
+ // 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/gemini/copilot 가용성 + 활성화 체크
2271
+ // 사용자 정책: 환경변수로 활성화 명시 + 실제 PATH 존재 확인 + 메인이 sub-agent 분배 시 참조
2272
+ const EXTERNAL_AGENTS = [
2273
+ { id: 'claude', bin: 'claude', envFlag: 'LEERNESS_ENABLE_CLAUDE', versionArgs: ['--version'], desc: 'Anthropic Claude Code CLI' },
2274
+ { id: 'codex', bin: 'codex', envFlag: 'LEERNESS_ENABLE_CODEX', versionArgs: ['--version'], desc: 'OpenAI Codex CLI (격리 sandbox)' },
2275
+ { id: 'gemini', bin: 'gemini', envFlag: 'LEERNESS_ENABLE_GEMINI', versionArgs: ['--version'], desc: 'Google Gemini CLI (--yolo 모드 워크스페이스 직접 수정 가능)' },
2276
+ { id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)' }
2277
+ ];
2278
+
2279
+ function _checkAgent(agent, opts = {}) {
2280
+ const enabled = process.env[agent.envFlag] === '1';
2281
+ // PATH 존재 확인 (which / where)
2282
+ let installed = false, version = null, error = null;
2283
+ try {
2284
+ const r = cp.spawnSync(agent.bin, agent.versionArgs, { encoding: 'utf8', timeout: 5000, shell: true });
2285
+ if (r.status === 0 || (r.stdout && r.stdout.trim())) {
2286
+ installed = true;
2287
+ version = (r.stdout || r.stderr || '').trim().split('\n')[0].slice(0, 80);
2288
+ } else if (r.error) {
2289
+ error = r.error.code || r.error.message;
2290
+ } else {
2291
+ error = `exit ${r.status}`;
2292
+ }
2293
+ } catch (e) { error = e.message; }
2294
+ return {
2295
+ id: agent.id, bin: agent.bin, desc: agent.desc, envFlag: agent.envFlag,
2296
+ enabled, installed, version, error,
2297
+ status: enabled && installed ? 'ready' : !installed ? 'not-installed' : !enabled ? 'disabled' : 'unknown'
2298
+ };
2299
+ }
2300
+
2301
+ function agentsCmd(root, sub, ...args) {
2302
+ root = absRoot(root || process.cwd());
2303
+ // .env 자동 로드 (1.9.22)
2304
+ _loadEnvFile(root);
2305
+ _loadEnvFile(path.join(root, '..'));
2306
+
2307
+ if (!sub || sub === 'list') {
2308
+ const checks = EXTERNAL_AGENTS.map(a => _checkAgent(a));
2309
+ if (has('--json')) { log(JSON.stringify({ agents: checks }, null, 2)); return; }
2310
+ log(`# 외부 AI CLI 오케스트레이션 (1.9.30)`);
2311
+ log('');
2312
+ log(`| Agent | env (${'env=1 활성'}) | 설치 | 버전 | 상태 |`);
2313
+ log(`|---|---|---|---|---|`);
2314
+ for (const c of checks) {
2315
+ const envMark = c.enabled ? '✓' : '✗';
2316
+ const instMark = c.installed ? '✓' : '✗';
2317
+ const statusEmoji = c.status === 'ready' ? '🟢 ready' : c.status === 'not-installed' ? '⚪ 미설치' : c.status === 'disabled' ? '🟡 비활성' : '❓';
2318
+ log(`| ${c.id} | ${envMark} ${c.envFlag} | ${instMark} | ${c.version || '-'} | ${statusEmoji} |`);
2319
+ }
2320
+ const ready = checks.filter(c => c.status === 'ready');
2321
+ log('');
2322
+ log(`## 활성 (${ready.length}/${checks.length}): ${ready.map(c => c.id).join(', ') || '(없음)'}`);
2323
+ if (!ready.length) {
2324
+ log('');
2325
+ log(`💡 활성화 방법:`);
2326
+ log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/gemini-cli\`)`);
2327
+ log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1, LEERNESS_ENABLE_GEMINI=1`);
2328
+ log(` 3) \`leerness agents check\`로 재확인`);
2329
+ } else {
2330
+ log('');
2331
+ log(`💡 메인 에이전트가 sub-agent 분배 시 위 ${ready.length}개 CLI 활용 가능:`);
2332
+ log(` \`leerness agents dispatch "<task>" --to <id>\` 로 프롬프트 전달`);
2333
+ }
2334
+ return;
2335
+ }
2336
+
2337
+ if (sub === 'check') {
2338
+ // list의 alias, 단 명시적 재확인 (JSON 출력 기본)
2339
+ const checks = EXTERNAL_AGENTS.map(a => _checkAgent(a));
2340
+ if (has('--json')) { log(JSON.stringify({ agents: checks, ready: checks.filter(c => c.status === 'ready').map(c => c.id) }, null, 2)); return; }
2341
+ return agentsCmd(root, 'list'); // 비-JSON은 list와 동일
2342
+ }
2343
+
2344
+ if (sub === 'dispatch') {
2345
+ const task = args.filter(x => !x.startsWith('-')).join(' ').trim() || arg('--task', null);
2346
+ const target = arg('--to', null);
2347
+ if (!task) { fail('dispatch "<task>" 또는 --task 필요'); return process.exit(1); }
2348
+ if (!target) { fail('--to <agent_id> 필요 (claude/codex/gemini/copilot)'); return process.exit(1); }
2349
+ const agentDef = EXTERNAL_AGENTS.find(a => a.id === target);
2350
+ if (!agentDef) { fail(`알 수 없는 agent: ${target}`); return process.exit(1); }
2351
+ const status = _checkAgent(agentDef);
2352
+ if (status.status !== 'ready') {
2353
+ fail(`${target} 비활성 (${status.status}). 환경변수 ${agentDef.envFlag}=1 + CLI 설치 필요.`);
2354
+ return process.exit(1);
2355
+ }
2356
+ // 실제 호출은 안 함 — 프롬프트만 생성 (사용자가 명시적으로 실행)
2357
+ log(`# leerness agents dispatch (1.9.30)`);
2358
+ log(`대상: ${target} (${agentDef.bin})`);
2359
+ log(`상태: 🟢 ready, 버전 ${status.version || '?'}`);
2360
+ log('');
2361
+ log(`## 실행 명령 (사용자가 복사해서 실행)`);
2362
+ log('');
2363
+ if (target === 'claude') {
2364
+ log(`claude "${task.replace(/"/g, '\\"')}"`);
2365
+ } else if (target === 'codex') {
2366
+ log(`codex exec "${task.replace(/"/g, '\\"')}"`);
2367
+ } else if (target === 'gemini') {
2368
+ log(`gemini -p "${task.replace(/"/g, '\\"')}" --yolo # ⚠ yolo는 워크스페이스 직접 수정 가능`);
2369
+ } else if (target === 'copilot') {
2370
+ log(`gh copilot suggest "${task.replace(/"/g, '\\"')}"`);
2371
+ }
2372
+ log('');
2373
+ log(`## 정책 (1.9.30)`);
2374
+ log(` - leerness는 외부 CLI를 자동 호출하지 않음 (사용자 명시적 실행)`);
2375
+ log(` - 메인 에이전트(Claude)가 위 명령을 보고 sub-agent로 spawn 가능`);
2376
+ log(` - quota 체크: \`leerness agents quota\` (1.9.31+)`);
2377
+ return;
2378
+ }
2379
+
2380
+ if (sub === 'quota') {
2381
+ // 1.9.31: 각 CLI 사용량/쿼터 추정 + provider 대시보드 링크
2382
+ const results = [];
2383
+ for (const agent of EXTERNAL_AGENTS) {
2384
+ const base = _checkAgent(agent);
2385
+ const out = { id: agent.id, bin: agent.bin, status: base.status, quota: null, hint: null, raw: null };
2386
+ if (base.status !== 'ready') {
2387
+ out.hint = base.status === 'not-installed' ? `${agent.bin} CLI 미설치` : base.status === 'disabled' ? `${agent.envFlag}=1 필요` : '알 수 없음';
2388
+ results.push(out); continue;
2389
+ }
2390
+ // CLI별 quota 탐지 시도
2391
+ try {
2392
+ if (agent.id === 'claude') {
2393
+ // claude는 /status 슬래시 (대화형)만 지원. 비대화형 추정 불가.
2394
+ out.quota = 'unknown';
2395
+ out.hint = '대화 내 `/status` 슬래시 또는 https://console.anthropic.com/settings/usage 확인';
2396
+ } else if (agent.id === 'codex') {
2397
+ // codex CLI: codex --help에 usage 명령 있는지 확인
2398
+ const r = cp.spawnSync(agent.bin, ['--help'], { encoding: 'utf8', timeout: 4000, shell: true });
2399
+ const help = (r.stdout || r.stderr || '').toLowerCase();
2400
+ if (help.includes('usage') || help.includes('quota')) {
2401
+ out.quota = 'cli-supported';
2402
+ out.hint = '`codex usage` 또는 `codex quota` 시도 가능';
2403
+ } else {
2404
+ out.quota = 'unknown';
2405
+ out.hint = 'https://platform.openai.com/account/usage 확인';
2406
+ }
2407
+ out.raw = help.slice(0, 200);
2408
+ } else if (agent.id === 'gemini') {
2409
+ // gemini CLI: 무료 티어는 분당 60req 제한, CLI 자체에선 노출 안 됨
2410
+ out.quota = 'rate-limited';
2411
+ out.hint = '무료 티어: 60 req/min, 1000 req/day · 유료는 https://ai.google.dev/gemini-api/docs/rate-limits';
2412
+ } else if (agent.id === 'copilot') {
2413
+ // gh copilot은 GitHub Copilot 구독 (월 단위 quota 없음, individual/business 플랜)
2414
+ const r = cp.spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 4000, shell: true });
2415
+ const authed = r.status === 0;
2416
+ out.quota = authed ? 'subscription' : 'not-authed';
2417
+ out.hint = authed ? 'Copilot 구독자 무제한 (월 플랜) · https://github.com/settings/copilot' : '`gh auth login` 필요';
2418
+ }
2419
+ } catch (e) {
2420
+ out.quota = 'error';
2421
+ out.hint = e.message;
2422
+ }
2423
+ results.push(out);
2424
+ }
2425
+ if (has('--json')) { log(JSON.stringify({ quota: results }, null, 2)); return; }
2426
+ log(`# 외부 AI CLI quota 추정 (1.9.31)`);
2427
+ log('');
2428
+ log(`| Agent | 상태 | quota | 안내 |`);
2429
+ log(`|---|---|---|---|`);
2430
+ for (const q of results) {
2431
+ const statusEmoji = q.status === 'ready' ? '🟢' : q.status === 'not-installed' ? '⚪' : q.status === 'disabled' ? '🟡' : '❓';
2432
+ log(`| ${q.id} | ${statusEmoji} ${q.status} | ${q.quota || '-'} | ${q.hint || '-'} |`);
2433
+ }
2434
+ log('');
2435
+ log(`## 주의`);
2436
+ log(` - leerness는 CLI 사용량을 직접 추적하지 않음 (provider 대시보드 참조)`);
2437
+ log(` - rate-limit/quota는 plan/티어에 따라 달라짐`);
2438
+ log(` - sub-agent 분배 시 quota 여유 큰 CLI 우선 활용 권장`);
2439
+ return;
2440
+ }
2441
+
2442
+ fail('사용법: leerness agents list|check|quota|dispatch "<task>" --to <id>');
2443
+ return process.exit(1);
2444
+ }
2445
+
2265
2446
  function personaCmd(root, sub, idOrName, ...rest) {
2266
2447
  root = absRoot(root || process.cwd());
2267
2448
  if (!sub || sub === 'list') {
@@ -4452,7 +4633,7 @@ function viewworkInstall(root) {
4452
4633
  }
4453
4634
 
4454
4635
  function help() {
4455
- 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 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 viewwork install [path]\n leerness viewwork emit [path] [--action a] [--note n] [--agent x] [--tool t]\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
4636
+ 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 # 1.9.30 외부 AI CLI 가용성 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\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 viewwork install [path]\n leerness viewwork emit [path] [--action a] [--note n] [--agent x] [--tool t]\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
4456
4637
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
4457
4638
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
4458
4639
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -4495,6 +4676,7 @@ async function main() {
4495
4676
  if (cmd === 'optimism-check') return optimismCheckCmd(arg('--path', process.cwd()), args[1]);
4496
4677
  if (cmd === 'persona') return personaCmd(arg('--path', process.cwd()), args[1], args[2]);
4497
4678
  if (cmd === 'review') return reviewCmd(arg('--path', process.cwd()), args[1]);
4679
+ if (cmd === 'agents') return agentsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
4498
4680
  if (cmd === 'session' && args[1] === 'close') { const r = sessionClose(args[2] || process.cwd()); viewworkEmit(args[2] || process.cwd(), { action: 'task', tool: 'session-close', note: 'session close' }); return r; }
4499
4681
  if (cmd === 'viewwork' && args[1] === 'install') return viewworkInstall(args[2] || process.cwd());
4500
4682
  if (cmd === 'viewwork' && args[1] === 'emit') return viewworkEmit(args[2] || process.cwd(), { action: arg('--action','task'), note: arg('--note',''), agent: arg('--agent','leerness'), tool: arg('--tool','leerness-cli') });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.29",
3
+ "version": "1.9.31",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -794,6 +794,79 @@ total++;
794
794
  if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
795
795
  }
796
796
 
797
+ // 1.9.30 회귀: 외부 CLI 오케스트레이션 (agents list/check/dispatch)
798
+ total++;
799
+ {
800
+ // agents list — claude가 환경변수 + PATH 둘 다 충족 시 ready
801
+ const env1 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '1', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_COPILOT: '0' };
802
+ const r1 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list'], { encoding: 'utf8', timeout: 15000, env: env1 });
803
+ const okList = r1.status === 0
804
+ && /외부 AI CLI 오케스트레이션 \(1\.9\.30\)/.test(r1.stdout)
805
+ && /\| claude \|/.test(r1.stdout)
806
+ && /\| codex \|/.test(r1.stdout)
807
+ && /\| gemini \|/.test(r1.stdout)
808
+ && /\| copilot \|/.test(r1.stdout);
809
+ // env 모두 0 → 비활성
810
+ const env2 = { ...process.env, LEERNESS_ENABLE_CLAUDE: '0', LEERNESS_ENABLE_CODEX: '0', LEERNESS_ENABLE_GEMINI: '0', LEERNESS_ENABLE_COPILOT: '0' };
811
+ const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'list', '--json'], { encoding: 'utf8', timeout: 15000, env: env2 });
812
+ let parsed = null;
813
+ try { parsed = JSON.parse(r2.stdout); } catch {}
814
+ const okJson = parsed && Array.isArray(parsed.agents) && parsed.agents.length === 4 && parsed.agents.every(a => a.status !== 'ready');
815
+ const ok = okList && okJson;
816
+ console.log(ok ? '✓ B(1.9.30) agents list: 4 CLI 정의 + env 0 시 모두 비활성' : `✗ agents list 실패 (list=${okList} json=${okJson})`);
817
+ if (!ok) { failed++; console.log(r1.stdout.slice(0, 500)); }
818
+ }
819
+
820
+ total++;
821
+ {
822
+ // agents dispatch — 활성 미충족 시 거부
823
+ const env = { ...process.env, LEERNESS_ENABLE_CODEX: '0' };
824
+ const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', 'test task', '--to', 'codex'], { encoding: 'utf8', timeout: 10000, env });
825
+ const okBlocked = r.status !== 0 && /비활성|disabled|not-installed/i.test(r.stdout);
826
+ // --to 누락 거부
827
+ const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', 'test'], { encoding: 'utf8', timeout: 10000 });
828
+ const okNoTarget = r2.status !== 0 && /--to.*필요/.test(r2.stdout + r2.stderr);
829
+ // 알 수 없는 agent 거부
830
+ const r3 = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', 'test', '--to', 'jedi'], { encoding: 'utf8', timeout: 10000 });
831
+ const okBadAgent = r3.status !== 0 && /알 수 없는 agent/.test(r3.stdout + r3.stderr);
832
+ const ok = okBlocked && okNoTarget && okBadAgent;
833
+ console.log(ok ? '✓ B(1.9.30) agents dispatch: env=0/--to 누락/잘못된 agent 모두 거부' : `✗ dispatch 실패 (block=${okBlocked} noT=${okNoTarget} bad=${okBadAgent})`);
834
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
835
+ }
836
+
837
+ // 1.9.31 회귀: agents quota (각 CLI 사용량/quota 조회)
838
+ total++;
839
+ {
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' };
842
+ const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota'], { encoding: 'utf8', timeout: 15000, env });
843
+ const okText = r.status === 0
844
+ && /외부 AI CLI quota 추정 \(1\.9\.31\)/.test(r.stdout)
845
+ && /\| claude \|/.test(r.stdout)
846
+ && /\| codex \|/.test(r.stdout)
847
+ && /\| gemini \|/.test(r.stdout)
848
+ && /\| copilot \|/.test(r.stdout)
849
+ && /provider 대시보드 참조/.test(r.stdout);
850
+ // JSON 출력
851
+ const r2 = cp.spawnSync(process.execPath, [CLI, 'agents', 'quota', '--json'], { encoding: 'utf8', timeout: 15000, env });
852
+ let parsed = null;
853
+ try { parsed = JSON.parse(r2.stdout); } catch {}
854
+ const okJson = parsed && Array.isArray(parsed.quota) && parsed.quota.length === 4
855
+ && parsed.quota.every(q => typeof q.id === 'string' && typeof q.status === 'string' && (q.hint === null || typeof q.hint === 'string'));
856
+ const ok = okText && okJson;
857
+ console.log(ok ? '✓ B(1.9.31) agents quota: 4 CLI 사용량/안내 + JSON 출력' : `✗ quota 실패 (text=${okText} json=${okJson})`);
858
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
859
+ }
860
+
861
+ total++;
862
+ {
863
+ // 사용법 메시지에 quota 포함
864
+ const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'foo'], { encoding: 'utf8', timeout: 10000 });
865
+ const ok = r.status !== 0 && /list\|check\|quota\|dispatch/.test(r.stdout + r.stderr);
866
+ console.log(ok ? '✓ B(1.9.31) agents 사용법에 quota 명시' : `✗ usage 메시지 실패`);
867
+ if (!ok) { failed++; console.log((r.stdout + r.stderr).slice(0, 300)); }
868
+ }
869
+
797
870
  // 1.9.22 회귀: handoff --compact + orchestrate opt-in 정책 + llm-bench record
798
871
  total++;
799
872
  {