leerness 1.9.30 → 1.9.32

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,57 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.32 — 2026-05-15
4
+
5
+ **ASCII 배너 + `leerness setup-agents` 인터랙티브 설정 + 미설치 CLI 자동 설치 시도**.
6
+
7
+ ### Added
8
+
9
+ - **ASCII 배너 (`_banner()`)**: `leerness init` 시 자동 출력. `--version --banner`로도 호출 가능. `LEERNESS_NO_BANNER=1` 또는 콘솔 폭 <70칸이면 자동 스킵.
10
+ - `LEERNESS` 8글자 ANSI 시안+볼드 색상 + 박스 + 빠른 시작 4줄.
11
+ - **`leerness setup-agents [path]`** (신규 명령): 외부 AI CLI 4종 (claude/codex/gemini/copilot) 인터랙티브 활성화.
12
+ - 각 CLI별: 설치 상태(🟢/⚪) + 활성 상태(🟢/🟡) 표시 → 사용자 yes/no → `.env`의 `LEERNESS_ENABLE_*` 자동 upsert.
13
+ - **미설치 CLI 자동 설치 시도**: 사용자 동의 후 `npm i -g @anthropic-ai/claude-code`, `npm i -g @openai/codex`, `npm i -g @google/gemini-cli`, `gh extension install github/gh-copilot` 실행.
14
+ - 설치 후 PATH 재확인 → 안 보이면 새 셸 안내.
15
+ - **`init` 후 자동 prompt**: `leerness init`이 끝나면 TTY일 때 "외부 AI CLI 설정?" 질문 → yes 시 `setupAgentsCmd` 호출.
16
+ - `--no-setup-agents` 또는 `--yes`로 스킵 가능.
17
+ - **`EXTERNAL_AGENTS`에 `installCmd` + `installHint` 필드 추가**: 자동 설치 시 사용.
18
+ - **`_prompt()` / `_confirm()` / `_upsertEnvLine()` 헬퍼**: TTY 한정 readline 기반, 비대화형(--yes/CI/non-TTY)에선 안전 fallback.
19
+
20
+ ### Policy
21
+ - ❌ 비-TTY/CI 환경에선 prompt 자동 스킵 (default 동작 유지)
22
+ - ❌ 자동 설치는 사용자 명시적 yes 후에만 (--yes 시에도 prompt 스킵하므로 자동 설치 안 됨)
23
+ - ✅ `.env` upsert는 idempotent (이미 키가 있으면 값 교체만)
24
+ - ✅ `init --yes` + `setup-agents`로 비대화형 워크플로도 안내 표시만 (변경 없음)
25
+
26
+ ### 실측 (이번 라운드)
27
+ - 신규 sub-project 3종 (rpg-craft 20/20, rpg-achievements 22/22, rpg-instance 20/20) — sub-agent 3 동시
28
+ - e2e: 151/151 PASS (1.9.31 146 + 1.9.32 5)
29
+ - 배너 ANSI 시각 검증 OK / 콘솔 폭 <70칸 시 1줄 폴백 / `LEERNESS_NO_BANNER=1` 스킵
30
+
31
+ ## 1.9.31 — 2026-05-15
32
+
33
+ **`leerness agents quota` — 외부 AI CLI 사용량/한도 추정 + provider 대시보드 안내**.
34
+
35
+ ### Added
36
+
37
+ - **`leerness agents quota`** (1.9.31): 활성 CLI별 quota/rate-limit 정보 표시.
38
+ - **claude**: 비대화형 quota API 없음 → `/status` 슬래시 또는 https://console.anthropic.com/settings/usage 안내.
39
+ - **codex**: `codex --help`에서 `usage`/`quota` 키워드 감지 시 시도 가능 표시, 미감지 시 https://platform.openai.com/account/usage 안내.
40
+ - **gemini**: 무료 티어 `60 req/min, 1000 req/day` 명시.
41
+ - **copilot (gh)**: `gh auth status`로 인증 확인 → 구독자 무제한 또는 `gh auth login` 필요 안내.
42
+ - `--json` 출력 지원 (`{ quota: [{id, bin, status, quota, hint, raw}, ...] }`).
43
+ - **`agents` 사용법 메시지에 `quota` 추가**: `list|check|quota|dispatch`.
44
+ - **`agents dispatch` 안내문에 quota 명령 cross-link** (1.9.31+).
45
+
46
+ ### Policy
47
+ - ❌ leerness는 사용량을 직접 추적하지 않음 (provider 대시보드 참조)
48
+ - ✅ sub-agent 분배 시 quota 여유 큰 CLI를 메인 에이전트가 우선 선택하도록 신호 제공
49
+ - ✅ rate-limit/plan 차이는 provider별 다름 — leerness는 hint만 제공
50
+
51
+ ### 실측 (이번 라운드 사용 사례)
52
+ - agents quota 신규 명령 검증 후 sub-agent ×3 동시 분배
53
+ - e2e: 146/146 통과 (1.9.30 144 + quota 2)
54
+
3
55
  ## 1.9.30 — 2026-05-15
4
56
 
5
57
  **외부 AI CLI 오케스트레이션 — 환경변수 활성화 정책 + `leerness agents list/check/dispatch`**.
package/README.md CHANGED
@@ -2,7 +2,24 @@
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.32-green)]() [![tests](https://img.shields.io/badge/e2e-151%2F151-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
+
7
+ ```
8
+ ╔══════════════════════════════════════════════════════════════╗
9
+ ║ ║
10
+ ║ ██╗ ███████╗███████╗██████╗ ███╗ ██╗███████╗███████╗ ║
11
+ ║ ██║ ██╔════╝██╔════╝██╔══██╗████╗ ██║██╔════╝██╔════╝ ║
12
+ ║ ██║ █████╗ █████╗ ██████╔╝██╔██╗ ██║█████╗ ███████╗ ║
13
+ ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
14
+ ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
15
+ ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
16
+ ║ ║
17
+ ║ v1.9.32 Korean-first AI Development Harness ║
18
+ ║ verify · reuse-map · handoff · agents · orchestrate ║
19
+ ║ ║
20
+ ╚══════════════════════════════════════════════════════════════╝
21
+ 한국어 우선 AI 개발 하네스 — verify · reuse-map · handoff · agents
22
+ ```
6
23
 
7
24
  ## ⚙️ 설치 (Install)
8
25
 
@@ -21,6 +38,57 @@ npm i --save-dev leerness && npx leerness handoff .
21
38
 
22
39
  > npmjs.com의 `npm i leerness`는 라이브러리 import용. CLI 명령(`leerness ...`) 직접 호출은 위 3가지 중 하나 필요.
23
40
 
41
+ ### 🎨 1.9.32 설치 경험
42
+
43
+ `leerness init`을 실행하면 **LEERNESS ASCII 배너 + 빠른 시작 가이드 + sub-agent CLI 설정 prompt**가 자동으로 표시됩니다.
44
+
45
+ ```
46
+ ╔══════════════════════════════════════════════════════════════╗
47
+ ║ ██╗ ███████╗███████╗██████╗ ███╗ ██╗███████╗███████╗ ║
48
+ ║ ██║ ██╔════╝██╔════╝██╔══██╗████╗ ██║██╔════╝██╔════╝ ║
49
+ ║ ██║ █████╗ █████╗ ██████╔╝██╔██╗ ██║█████╗ ███████╗ ║
50
+ ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
51
+ ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
52
+ ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
53
+ ║ v1.9.32 Korean-first AI Development Harness ║
54
+ ╚══════════════════════════════════════════════════════════════╝
55
+
56
+ 💡 외부 AI CLI(claude/codex/gemini/copilot)를 sub-agent로 활용하시겠습니까?
57
+ 지금 설정할까요? (나중에 `leerness setup-agents`로도 가능) (Y/n): y
58
+
59
+ ▸ claude — Anthropic Claude Code CLI
60
+ 설치 상태: 🟢 설치됨 (1.0.x)
61
+ 활성 상태: 🟡 LEERNESS_ENABLE_CLAUDE=0 또는 미설정
62
+ claude를 sub-agent로 활성화하시겠습니까? (Y/n): y
63
+ ✓ LEERNESS_ENABLE_CLAUDE=1 (활성)
64
+
65
+ ▸ codex — OpenAI Codex CLI (격리 sandbox)
66
+ 설치 상태: ⚪ 미설치
67
+ 활성 상태: 🟡
68
+ codex를 sub-agent로 활성화하시겠습니까? (y/N): y
69
+ ✓ LEERNESS_ENABLE_CODEX=1 (활성)
70
+ ⚠ codex이(가) 설치되어 있지 않습니다.
71
+ 설치 명령: npm i -g @openai/codex
72
+ 상세 안내: https://github.com/openai/codex
73
+ 지금 자동 설치를 시도할까요? (y/N): y
74
+ ▶ 실행: npm i -g @openai/codex
75
+ ✓ 설치 성공 — 재확인: codex --version
76
+ 🟢 codex 설치 확인 (0.x.x)
77
+ ```
78
+
79
+ **스킵 옵션** (CI/자동화):
80
+ - `leerness init --yes` — 모든 prompt 스킵 (기존 .env 유지)
81
+ - `leerness init --no-setup-agents` — setup만 스킵
82
+ - `LEERNESS_NO_BANNER=1` — ASCII 배너 스킵
83
+ - `LEERNESS_NO_PROMPT=1` — readline prompt 강제 비활성화
84
+
85
+ **나중에 재실행**:
86
+ ```bash
87
+ leerness setup-agents . # 인터랙티브 재설정
88
+ leerness agents list # 현재 상태 확인
89
+ leerness agents quota # 사용량 추정 (1.9.31)
90
+ ```
91
+
24
92
  ---
25
93
 
26
94
  **leerness가 해결하는 것**
@@ -31,6 +99,8 @@ npm i --save-dev leerness && npx leerness handoff .
31
99
  - LLM 컨텍스트 비용 → `--compact` 모드로 4KB → 500자
32
100
  - AI가 "API 호출 완료"라 보고했지만 코드에 호출 흔적이 없는 낙관적 표시 → `optimism-check`로 자동 감지 (1.9.26/27)
33
101
  - 코드 리뷰가 표면적이라 도메인 깊이 부족 → `leerness review <file> --persona security,performance,ux`로 도메인 페르소나 자동 부여 (1.9.29)
102
+ - 외부 AI CLI(claude/codex/gemini/copilot)를 sub-agent로 활용하고 싶지만 자동 호출은 위험 → 환경변수 활성화 + `leerness agents list/dispatch`로 명시적 분배 (1.9.30)
103
+ - 어떤 CLI에 quota 여유가 남았는지 한눈에 보고 싶을 때 → `leerness agents quota`로 provider별 사용량/한도 추정 (1.9.31)
34
104
 
35
105
  ---
36
106
 
@@ -97,6 +167,10 @@ leerness review <file> --persona security,performance,ux # 1.9.29 도메인
97
167
  leerness persona list # 5종 내장 + 사용자 정의
98
168
  leerness persona show security # 페르소나 본문
99
169
  leerness persona add my-domain # 사용자 정의 페르소나
170
+ leerness agents list # 1.9.30 외부 AI CLI 상태표
171
+ leerness agents quota # 1.9.31 CLI별 사용량/한도 추정
172
+ leerness agents dispatch "<task>" --to gemini # 1.9.30 sub-agent 명령 생성
173
+ leerness setup-agents # 1.9.32 인터랙티브 CLI 설정 + 자동 설치 시도
100
174
  ```
101
175
 
102
176
  ### 워크스페이스 (멀티 프로젝트)
@@ -213,6 +287,10 @@ leerness orchestrate "복잡한 기능" --agents 20
213
287
  | `handoff --compact` | LLM 시스템 프롬프트용 압축 출력 | 1.9.22 |
214
288
  | `review --persona X` | 도메인별 sub-agent 자동 프롬프트 (security/performance/ux/testing/docs) | 1.9.29 |
215
289
  | `persona list/show/add` | 페르소나 카탈로그 관리 (.harness/personas/) | 1.9.29 |
290
+ | `agents list/check` | 4 CLI(claude/codex/gemini/copilot) 활성/설치 상태표 (env + PATH 검증) | 1.9.30 |
291
+ | `agents dispatch --to X` | ready CLI에 대상 명령 자동 생성 (자동 호출 금지) | 1.9.30 |
292
+ | `agents quota` | provider별 사용량/한도 추정 + 대시보드 안내 | 1.9.31 |
293
+ | `setup-agents` | 인터랙티브 CLI 활성화 + 미설치 자동 설치 시도 (.env upsert) | 1.9.32 |
216
294
 
217
295
  ---
218
296
 
@@ -293,6 +371,63 @@ leerness persona add my-domain # .harness/personas/my-domain.md 템플릿 생
293
371
 
294
372
  ---
295
373
 
374
+ ## 🤖 외부 AI CLI 오케스트레이션 (1.9.30 / 1.9.31 / 1.9.32)
375
+
376
+ claude/codex/gemini/copilot CLI들을 sub-agent로 명시적 활용. **자동 호출 절대 금지** — 환경변수 활성화 + PATH 존재 둘 다 충족 시에만 ready.
377
+
378
+ ### 활성화 — 자동 (1.9.32 권장)
379
+ ```bash
380
+ leerness setup-agents . # 인터랙티브: 각 CLI별 yes/no + 미설치 자동 설치 시도
381
+ # → .env에 LEERNESS_ENABLE_* 자동 기록
382
+ # → 미설치 CLI는 npm i -g / gh extension install 실행 (사용자 동의 시)
383
+ ```
384
+
385
+ ### 활성화 — 수동 (`.env` 직접 편집)
386
+ ```bash
387
+ LEERNESS_ENABLE_CLAUDE=1 # Anthropic Claude Code CLI
388
+ LEERNESS_ENABLE_CODEX=1 # OpenAI Codex CLI (격리 sandbox)
389
+ LEERNESS_ENABLE_GEMINI=1 # Google Gemini CLI (--yolo 모드는 워크스페이스 직접 수정 가능)
390
+ LEERNESS_ENABLE_COPILOT=1 # GitHub Copilot CLI (gh copilot)
391
+ ```
392
+
393
+ ### 사용
394
+ ```bash
395
+ leerness agents list # 4 CLI 상태표 (env + PATH + 버전)
396
+ leerness agents quota # provider별 사용량/한도 추정 (1.9.31)
397
+ leerness agents dispatch "<task>" --to gemini # ready CLI에 명령 자동 생성
398
+ leerness setup-agents # 1.9.32 인터랙티브 재설정
399
+ ```
400
+
401
+ ### 자동 설치 명령 (1.9.32 setup-agents가 사용)
402
+ | CLI | install 명령 | 안내 |
403
+ |---|---|---|
404
+ | claude | `npm i -g @anthropic-ai/claude-code` | https://docs.anthropic.com/en/docs/claude-code/setup |
405
+ | codex | `npm i -g @openai/codex` | https://github.com/openai/codex |
406
+ | gemini | `npm i -g @google/gemini-cli` | https://github.com/google-gemini/gemini-cli |
407
+ | copilot | `gh extension install github/gh-copilot` | https://github.com/github/gh-copilot (gh CLI 선행 필요) |
408
+
409
+ ### quota 안내 (1.9.31)
410
+ | CLI | 추정 | 안내 |
411
+ |---|---|---|
412
+ | claude | `unknown` | 대화 내 `/status` 슬래시 또는 https://console.anthropic.com/settings/usage |
413
+ | codex | `cli-supported` 또는 `unknown` | `codex usage`/`codex quota` 시도 또는 https://platform.openai.com/account/usage |
414
+ | gemini | `rate-limited` | 무료 60 req/min, 1000 req/day · https://ai.google.dev/gemini-api/docs/rate-limits |
415
+ | copilot | `subscription` 또는 `not-authed` | 월 구독자 무제한 · `gh auth login` 필요 시 안내 |
416
+
417
+ ### 실측 (이번 라운드)
418
+ - Sub B (rpg-craft, Claude 페르소나) → 20/20 pass, 1,567 라인
419
+ - Sub C (rpg-achievements, Claude 페르소나) → 22/22 pass, 1,375 라인
420
+ - Sub D (rpg-instance, Claude 페르소나, cross-project require 시연) → 20/20 pass, 1,016 라인
421
+ - → **3 도메인 동시 진행**, 메인 에이전트가 sub-agent들에 페르소나 + 영역 분배 + 충돌 방지 컨벤션 명시.
422
+
423
+ ### 정책
424
+ - ❌ leerness는 외부 CLI를 자동 호출하지 않음 (사용자/메인 에이전트가 명시적 실행)
425
+ - ✅ `agents dispatch`는 명령 텍스트만 출력 — 복사해서 실행
426
+ - ✅ quota 여유 큰 CLI를 메인 에이전트가 우선 선택하도록 신호 제공
427
+ - ⚠ `gemini --yolo`는 워크스페이스 파일 직접 수정 가능 — 격리 sandbox 아님 (codex와 차이)
428
+
429
+ ---
430
+
296
431
  ## 🤝 Claude Code 통합
297
432
 
298
433
  설치 시 자동 등록: `.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 +458,10 @@ leerness skill consolidate
323
458
  | `LEERNESS_GITHUB_TOKEN` | gh release용 (있을 때) |
324
459
  | **`LEERNESS_OLLAMA_BASE_URL`** | **1.9.22 — orchestrate opt-in 활성화** |
325
460
  | `LEERNESS_OLLAMA_MODEL` | 기본 모델 (orchestrate `--model`로 override) |
461
+ | **`LEERNESS_ENABLE_CLAUDE`** | **1.9.30 — `agents list/dispatch` Claude Code CLI 활성** (=1) |
462
+ | **`LEERNESS_ENABLE_CODEX`** | **1.9.30 — Codex CLI 활성** (=1) |
463
+ | **`LEERNESS_ENABLE_GEMINI`** | **1.9.30 — Gemini CLI 활성** (=1) |
464
+ | **`LEERNESS_ENABLE_COPILOT`** | **1.9.30 — gh copilot 활성** (=1) |
326
465
 
327
466
  ---
328
467
 
@@ -362,12 +501,15 @@ A. `--all-apps`는 현재 디렉토리 + `_apps/*` (또는 부모의 `_apps/*`)
362
501
  npm test # = node ./scripts/e2e.js
363
502
  ```
364
503
 
365
- **139/139 시나리오** 통과 (1.9.7~1.9.29 회귀 + 신규 검증).
504
+ **151/151 시나리오** 통과 (1.9.7~1.9.32 회귀 + 신규 검증).
366
505
 
367
506
  ---
368
507
 
369
508
  ## 📜 변경 이력 (최근)
370
509
 
510
+ - **1.9.32** — ASCII 배너 + `leerness setup-agents` 인터랙티브 설정 + 미설치 CLI 자동 설치 시도. `init` 시 자동 prompt.
511
+ - **1.9.31** — `leerness agents quota` (provider별 사용량/한도 추정 + 대시보드 안내). 멀티 에이전트 분배 신호.
512
+ - **1.9.30** — 외부 AI CLI 오케스트레이션 (claude/codex/gemini/copilot) + 환경변수 활성화 정책 + `leerness agents list/check/dispatch`
371
513
  - **1.9.29** — 페르소나 시스템 (5종 내장) + `leerness review --persona` (도메인 깊이 3-4배)
372
514
  - **1.9.28** — 카카오페이/네이버페이 패턴 + confidence floor 0.15
373
515
  - **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.30';
9
+ const VERSION = '1.9.32';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -386,10 +386,12 @@ async function resolveInstallOptions(root, opts = {}) {
386
386
 
387
387
  async function install(root, opts = {}) {
388
388
  root = absRoot(root); mkdirp(root);
389
+ // 1.9.32: init 시 ASCII 배너 + 빠른 시작 가이드 (migrate는 quiet)
390
+ if (!opts.migration && !has('--no-banner')) _banner({ quickStart: !opts.dry });
389
391
  const resolved = await resolveInstallOptions(root, opts);
390
392
  const lang = resolved.lang;
391
393
  const skills = resolved.skills;
392
- log(`\nLeerness v${VERSION}`);
394
+ log(`Leerness v${VERSION}`);
393
395
  log(`Target: ${root}`);
394
396
  log(`Language: ${lang}`);
395
397
  log(`Skills: ${skills.length ? skills.join(', ') : 'none'}`);
@@ -471,6 +473,21 @@ async function install(root, opts = {}) {
471
473
  if (!has('--no-auto-roadmap')) {
472
474
  try { _autoRoadmap(root, 'install'); } catch (e) { warn('auto-roadmap 실패: ' + (e && e.message)); }
473
475
  }
476
+ // 1.9.32: init 시 외부 AI CLI 설정 prompt (TTY + 신규 init + --no-setup-agents 미지정)
477
+ const isFreshInit = !opts.migration && !opts.force;
478
+ const skipSetup = has('--no-setup-agents') || has('--yes') || has('-y');
479
+ if (isFreshInit && process.stdin.isTTY && !skipSetup) {
480
+ try {
481
+ log('');
482
+ log('💡 외부 AI CLI(claude/codex/gemini/copilot)를 sub-agent로 활용하시겠습니까?');
483
+ const wantSetup = await _confirm(' 지금 설정할까요? (나중에 `leerness setup-agents`로도 가능)', true);
484
+ if (wantSetup) {
485
+ await setupAgentsCmd(root);
486
+ } else {
487
+ log(' → 나중에 `leerness setup-agents .` 명령으로 설정 가능');
488
+ }
489
+ } catch (e) { warn('setup-agents skipped: ' + (e && e.message)); }
490
+ }
474
491
  }
475
492
  }
476
493
 
@@ -2269,11 +2286,16 @@ function _resolvePersona(root, id) {
2269
2286
 
2270
2287
  // 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/gemini/copilot 가용성 + 활성화 체크
2271
2288
  // 사용자 정책: 환경변수로 활성화 명시 + 실제 PATH 존재 확인 + 메인이 sub-agent 분배 시 참조
2289
+ // 1.9.32: installCmd 추가 — setup-agents 시 자동 설치 시도 가능
2272
2290
  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)' }
2291
+ { id: 'claude', bin: 'claude', envFlag: 'LEERNESS_ENABLE_CLAUDE', versionArgs: ['--version'], desc: 'Anthropic Claude Code CLI',
2292
+ installCmd: 'npm i -g @anthropic-ai/claude-code', installHint: 'https://docs.anthropic.com/en/docs/claude-code/setup' },
2293
+ { id: 'codex', bin: 'codex', envFlag: 'LEERNESS_ENABLE_CODEX', versionArgs: ['--version'], desc: 'OpenAI Codex CLI (격리 sandbox)',
2294
+ installCmd: 'npm i -g @openai/codex', installHint: 'https://github.com/openai/codex' },
2295
+ { id: 'gemini', bin: 'gemini', envFlag: 'LEERNESS_ENABLE_GEMINI', versionArgs: ['--version'], desc: 'Google Gemini CLI (--yolo 모드 워크스페이스 직접 수정 가능)',
2296
+ installCmd: 'npm i -g @google/gemini-cli', installHint: 'https://github.com/google-gemini/gemini-cli' },
2297
+ { id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)',
2298
+ installCmd: 'gh extension install github/gh-copilot', installHint: 'https://github.com/github/gh-copilot (gh CLI 선행 설치 필요)' }
2277
2299
  ];
2278
2300
 
2279
2301
  function _checkAgent(agent, opts = {}) {
@@ -2298,6 +2320,163 @@ function _checkAgent(agent, opts = {}) {
2298
2320
  };
2299
2321
  }
2300
2322
 
2323
+ // 1.9.32: ASCII 배너 — init/version 시 출력
2324
+ function _banner(opts = {}) {
2325
+ const v = `v${VERSION}`;
2326
+ // 사용자 콘솔이 너무 좁을 때(<70) 또는 LEERNESS_NO_BANNER=1이면 스킵
2327
+ const cols = process.stdout && process.stdout.columns ? process.stdout.columns : 80;
2328
+ if (process.env.LEERNESS_NO_BANNER === '1') return;
2329
+ if (cols < 70) {
2330
+ log(`Leerness ${v} — 한국어 우선 AI 개발 하네스`);
2331
+ return;
2332
+ }
2333
+ // ANSI 색상 (TTY일 때만)
2334
+ const isTty = process.stdout && process.stdout.isTTY;
2335
+ const C = isTty ? {
2336
+ cyan: s => `\x1b[36m${s}\x1b[0m`, dim: s => `\x1b[2m${s}\x1b[0m`,
2337
+ bold: s => `\x1b[1m${s}\x1b[0m`, green: s => `\x1b[32m${s}\x1b[0m`
2338
+ } : { cyan: s => s, dim: s => s, bold: s => s, green: s => s };
2339
+ // 박스 안쪽 너비 60칸 고정 (좌측 2칸 들여쓰기 포함 전체 64칸)
2340
+ const lines = [
2341
+ '',
2342
+ C.cyan(' ╔══════════════════════════════════════════════════════════════╗'),
2343
+ C.cyan(' ║ ║'),
2344
+ C.cyan(' ║ ') + C.bold('██╗ ███████╗███████╗██████╗ ███╗ ██╗███████╗███████╗') + C.cyan(' ║'),
2345
+ C.cyan(' ║ ') + C.bold('██║ ██╔════╝██╔════╝██╔══██╗████╗ ██║██╔════╝██╔════╝') + C.cyan(' ║'),
2346
+ C.cyan(' ║ ') + C.bold('██║ █████╗ █████╗ ██████╔╝██╔██╗ ██║█████╗ ███████╗') + C.cyan(' ║'),
2347
+ C.cyan(' ║ ') + C.bold('██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║') + C.cyan(' ║'),
2348
+ C.cyan(' ║ ') + C.bold('███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║') + C.cyan(' ║'),
2349
+ C.cyan(' ║ ') + C.bold('╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝') + C.cyan(' ║'),
2350
+ C.cyan(' ║ ║'),
2351
+ // ASCII-only 라인은 정확히 60칸 (좌2 + 본문56 + 우2)
2352
+ C.cyan(' ║ ') + C.green(`${v.padEnd(10)}`) + C.dim('Korean-first AI Development Harness') + C.cyan(' ║'),
2353
+ C.cyan(' ║ ') + C.dim('verify · reuse-map · handoff · agents · orchestrate') + C.cyan(' ║'),
2354
+ C.cyan(' ║ ║'),
2355
+ C.cyan(' ╚══════════════════════════════════════════════════════════════╝'),
2356
+ ' ' + C.dim('한국어 우선 AI 개발 하네스 — verify · reuse-map · handoff · agents'),
2357
+ ''
2358
+ ];
2359
+ for (const ln of lines) log(ln);
2360
+ if (opts.quickStart) {
2361
+ log(C.dim(' 빠른 시작:'));
2362
+ log(C.dim(' npx leerness@latest init . # 신규 프로젝트'));
2363
+ log(C.dim(' npx leerness@latest setup-agents . # 외부 AI CLI 설정'));
2364
+ log(C.dim(' npx leerness handoff . # 컨텍스트 적재'));
2365
+ log(C.dim(' npx leerness verify-claim T-0001 --run-tests # 자동 검증'));
2366
+ log('');
2367
+ }
2368
+ }
2369
+
2370
+ // 1.9.32: TTY 한정 readline async prompt — 비대화형(npx CI, --yes)에선 default 반환
2371
+ function _prompt(question, defaultVal = '') {
2372
+ return new Promise(resolve => {
2373
+ if (!process.stdin.isTTY || process.env.LEERNESS_NO_PROMPT === '1' || has('--yes') || has('-y')) {
2374
+ return resolve(defaultVal);
2375
+ }
2376
+ const readline = require('readline');
2377
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
2378
+ const q = defaultVal ? `${question} [${defaultVal}]: ` : `${question}: `;
2379
+ rl.question(q, ans => {
2380
+ rl.close();
2381
+ resolve((ans || '').trim() || defaultVal);
2382
+ });
2383
+ });
2384
+ }
2385
+
2386
+ // 1.9.32: yes/no prompt — y/yes/예/네/1 → true, n/no/아니오/0/공백 → false
2387
+ async function _confirm(question, defaultYes = false) {
2388
+ const def = defaultYes ? 'Y/n' : 'y/N';
2389
+ const ans = await _prompt(`${question} (${def})`, defaultYes ? 'y' : 'n');
2390
+ return /^(y|yes|예|네|ㅇ|1|true)$/i.test(ans.trim());
2391
+ }
2392
+
2393
+ // 1.9.32: .env 파일에 KEY=value 라인 누적/갱신 (이미 키가 있으면 값 교체, 없으면 append)
2394
+ function _upsertEnvLine(envPath, key, value) {
2395
+ let body = exists(envPath) ? read(envPath) : '';
2396
+ const re = new RegExp(`^${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}=.*$`, 'm');
2397
+ const line = `${key}=${value}`;
2398
+ if (re.test(body)) body = body.replace(re, line);
2399
+ else body = (body && !body.endsWith('\n') ? body + '\n' : body) + line + '\n';
2400
+ writeUtf8(envPath, body);
2401
+ }
2402
+
2403
+ // 1.9.32: 외부 AI CLI 자동 설치 시도 — child_process.spawnSync로 installCmd 실행
2404
+ function _tryInstallAgent(agent) {
2405
+ if (!agent.installCmd) return { ok: false, message: 'installCmd 정의 없음' };
2406
+ log(` ▶ 실행: ${agent.installCmd}`);
2407
+ const parts = agent.installCmd.split(/\s+/);
2408
+ const r = cp.spawnSync(parts[0], parts.slice(1), { encoding: 'utf8', timeout: 120000, shell: true, stdio: 'inherit' });
2409
+ if (r.status === 0) return { ok: true, message: '설치 성공' };
2410
+ return { ok: false, message: `exit ${r.status}` + (r.error ? ` (${r.error.code || r.error.message})` : '') };
2411
+ }
2412
+
2413
+ // 1.9.32: setup-agents 워크플로 — init 직후 또는 단독 명령
2414
+ async function setupAgentsCmd(root, opts = {}) {
2415
+ root = absRoot(root || process.cwd());
2416
+ _loadEnvFile(root);
2417
+ _loadEnvFile(path.join(root, '..'));
2418
+ const envPath = path.join(root, '.env');
2419
+
2420
+ log('');
2421
+ log('# 외부 AI CLI 설정 (1.9.32)');
2422
+ log('메인 에이전트가 작업을 분배할 sub-agent 후보를 선택하세요.');
2423
+ log('각 CLI는 *환경변수 활성화 + PATH 존재* 둘 다 충족할 때 ready 상태가 됩니다.');
2424
+ log('');
2425
+
2426
+ // 비대화형(--yes 또는 비-TTY)이면 모든 CLI를 기존 값 유지 + 안내만
2427
+ const interactive = !!process.stdin.isTTY && !has('--yes') && !has('-y') && process.env.LEERNESS_NO_PROMPT !== '1';
2428
+ if (!interactive) {
2429
+ log(' 비대화형 모드 — 환경변수는 변경하지 않습니다. 수동 편집:');
2430
+ log(` ${envPath}`);
2431
+ log(' 활성 상태 확인: leerness agents list');
2432
+ return;
2433
+ }
2434
+
2435
+ for (const agent of EXTERNAL_AGENTS) {
2436
+ const status = _checkAgent(agent);
2437
+ const isReady = status.installed && status.enabled;
2438
+ log(`---`);
2439
+ log(`▸ ${agent.id} — ${agent.desc}`);
2440
+ log(` 설치 상태: ${status.installed ? '🟢 설치됨 (' + (status.version || '?') + ')' : '⚪ 미설치'}`);
2441
+ log(` 활성 상태: ${status.enabled ? '🟢 ' + agent.envFlag + '=1' : '🟡 ' + agent.envFlag + '=0 또는 미설정'}`);
2442
+
2443
+ const wantEnable = await _confirm(` ${agent.id}를 sub-agent로 활성화하시겠습니까?`, isReady || agent.id === 'claude');
2444
+ if (!wantEnable) {
2445
+ _upsertEnvLine(envPath, agent.envFlag, '0');
2446
+ log(` ✗ ${agent.envFlag}=0 (비활성)`);
2447
+ continue;
2448
+ }
2449
+ _upsertEnvLine(envPath, agent.envFlag, '1');
2450
+ log(` ✓ ${agent.envFlag}=1 (활성)`);
2451
+
2452
+ if (!status.installed) {
2453
+ log(` ⚠ ${agent.bin}이(가) 설치되어 있지 않습니다.`);
2454
+ log(` 설치 명령: ${agent.installCmd}`);
2455
+ log(` 상세 안내: ${agent.installHint}`);
2456
+ const doInstall = await _confirm(` 지금 자동 설치를 시도할까요?`, false);
2457
+ if (doInstall) {
2458
+ const r = _tryInstallAgent(agent);
2459
+ if (r.ok) {
2460
+ log(` ✓ 설치 성공 — 재확인: ${agent.bin} ${agent.versionArgs.join(' ')}`);
2461
+ const after = _checkAgent(agent);
2462
+ if (after.installed) log(` 🟢 ${agent.id} 설치 확인 (${after.version || '?'})`);
2463
+ else log(` ⚠ 설치 후에도 PATH에서 찾지 못함 — 새 셸을 열어주세요`);
2464
+ } else {
2465
+ log(` ✗ 설치 실패: ${r.message}`);
2466
+ log(` 수동 설치 후 \`leerness agents list\`로 재확인하세요.`);
2467
+ }
2468
+ } else {
2469
+ log(` → 나중에 직접 설치 후 \`leerness setup-agents\` 재실행 가능`);
2470
+ }
2471
+ }
2472
+ }
2473
+
2474
+ log('');
2475
+ log('✅ 외부 AI CLI 설정 완료.');
2476
+ log(` .env에 LEERNESS_ENABLE_* 플래그가 저장되었습니다 (${rel(root, envPath)}).`);
2477
+ log(' 다음: leerness agents list / leerness agents quota');
2478
+ }
2479
+
2301
2480
  function agentsCmd(root, sub, ...args) {
2302
2481
  root = absRoot(root || process.cwd());
2303
2482
  // .env 자동 로드 (1.9.22)
@@ -2373,11 +2552,73 @@ function agentsCmd(root, sub, ...args) {
2373
2552
  log(`## 정책 (1.9.30)`);
2374
2553
  log(` - leerness는 외부 CLI를 자동 호출하지 않음 (사용자 명시적 실행)`);
2375
2554
  log(` - 메인 에이전트(Claude)가 위 명령을 보고 sub-agent로 spawn 가능`);
2376
- log(` - quota 체크: CLI마다 다름 — claude는 /status, codex는 별도 대시보드 등`);
2555
+ log(` - quota 체크: \`leerness agents quota\` (1.9.31+)`);
2377
2556
  return;
2378
2557
  }
2379
2558
 
2380
- fail('사용법: leerness agents list|check|dispatch "<task>" --to <id>');
2559
+ if (sub === 'quota') {
2560
+ // 1.9.31: 각 CLI 사용량/쿼터 추정 + provider 대시보드 링크
2561
+ const results = [];
2562
+ for (const agent of EXTERNAL_AGENTS) {
2563
+ const base = _checkAgent(agent);
2564
+ const out = { id: agent.id, bin: agent.bin, status: base.status, quota: null, hint: null, raw: null };
2565
+ if (base.status !== 'ready') {
2566
+ out.hint = base.status === 'not-installed' ? `${agent.bin} CLI 미설치` : base.status === 'disabled' ? `${agent.envFlag}=1 필요` : '알 수 없음';
2567
+ results.push(out); continue;
2568
+ }
2569
+ // CLI별 quota 탐지 시도
2570
+ try {
2571
+ if (agent.id === 'claude') {
2572
+ // claude는 /status 슬래시 (대화형)만 지원. 비대화형 추정 불가.
2573
+ out.quota = 'unknown';
2574
+ out.hint = '대화 내 `/status` 슬래시 또는 https://console.anthropic.com/settings/usage 확인';
2575
+ } else if (agent.id === 'codex') {
2576
+ // codex CLI: codex --help에 usage 명령 있는지 확인
2577
+ const r = cp.spawnSync(agent.bin, ['--help'], { encoding: 'utf8', timeout: 4000, shell: true });
2578
+ const help = (r.stdout || r.stderr || '').toLowerCase();
2579
+ if (help.includes('usage') || help.includes('quota')) {
2580
+ out.quota = 'cli-supported';
2581
+ out.hint = '`codex usage` 또는 `codex quota` 시도 가능';
2582
+ } else {
2583
+ out.quota = 'unknown';
2584
+ out.hint = 'https://platform.openai.com/account/usage 확인';
2585
+ }
2586
+ out.raw = help.slice(0, 200);
2587
+ } else if (agent.id === 'gemini') {
2588
+ // gemini CLI: 무료 티어는 분당 60req 제한, CLI 자체에선 노출 안 됨
2589
+ out.quota = 'rate-limited';
2590
+ out.hint = '무료 티어: 60 req/min, 1000 req/day · 유료는 https://ai.google.dev/gemini-api/docs/rate-limits';
2591
+ } else if (agent.id === 'copilot') {
2592
+ // gh copilot은 GitHub Copilot 구독 (월 단위 quota 없음, individual/business 플랜)
2593
+ const r = cp.spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 4000, shell: true });
2594
+ const authed = r.status === 0;
2595
+ out.quota = authed ? 'subscription' : 'not-authed';
2596
+ out.hint = authed ? 'Copilot 구독자 무제한 (월 플랜) · https://github.com/settings/copilot' : '`gh auth login` 필요';
2597
+ }
2598
+ } catch (e) {
2599
+ out.quota = 'error';
2600
+ out.hint = e.message;
2601
+ }
2602
+ results.push(out);
2603
+ }
2604
+ if (has('--json')) { log(JSON.stringify({ quota: results }, null, 2)); return; }
2605
+ log(`# 외부 AI CLI quota 추정 (1.9.31)`);
2606
+ log('');
2607
+ log(`| Agent | 상태 | quota | 안내 |`);
2608
+ log(`|---|---|---|---|`);
2609
+ for (const q of results) {
2610
+ const statusEmoji = q.status === 'ready' ? '🟢' : q.status === 'not-installed' ? '⚪' : q.status === 'disabled' ? '🟡' : '❓';
2611
+ log(`| ${q.id} | ${statusEmoji} ${q.status} | ${q.quota || '-'} | ${q.hint || '-'} |`);
2612
+ }
2613
+ log('');
2614
+ log(`## 주의`);
2615
+ log(` - leerness는 CLI 사용량을 직접 추적하지 않음 (provider 대시보드 참조)`);
2616
+ log(` - rate-limit/quota는 plan/티어에 따라 달라짐`);
2617
+ log(` - sub-agent 분배 시 quota 여유 큰 CLI 우선 활용 권장`);
2618
+ return;
2619
+ }
2620
+
2621
+ fail('사용법: leerness agents list|check|quota|dispatch "<task>" --to <id>');
2381
2622
  return process.exit(1);
2382
2623
  }
2383
2624
 
@@ -4571,7 +4812,7 @@ function viewworkInstall(root) {
4571
4812
  }
4572
4813
 
4573
4814
  function help() {
4574
- 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
4815
+ 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 setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\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
4575
4816
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
4576
4817
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
4577
4818
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -4589,7 +4830,11 @@ function help() {
4589
4830
 
4590
4831
  async function main() {
4591
4832
  const args = nonFlagArgs(); const cmd = args[0] || 'init';
4592
- if (has('--version') || has('-v')) return log(VERSION);
4833
+ if (has('--version') || has('-v')) {
4834
+ // 1.9.32: --version은 순수 버전만 (CI/script 친화). 배너는 --banner 시.
4835
+ if (has('--banner')) _banner({ quickStart: false });
4836
+ return log(VERSION);
4837
+ }
4593
4838
  if (has('--help') || has('-h')) return help();
4594
4839
  if (cmd === 'init') return await install(args[1] || process.cwd(), { force:false, dry:false, migration:false });
4595
4840
  if (cmd === 'migrate') return await install(args[1] || process.cwd(), { force:has('--force'), dry:has('--dry-run'), migration:true });
@@ -4615,6 +4860,7 @@ async function main() {
4615
4860
  if (cmd === 'persona') return personaCmd(arg('--path', process.cwd()), args[1], args[2]);
4616
4861
  if (cmd === 'review') return reviewCmd(arg('--path', process.cwd()), args[1]);
4617
4862
  if (cmd === 'agents') return agentsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
4863
+ if (cmd === 'setup-agents' || cmd === 'setup' && args[1] === 'agents') return await setupAgentsCmd(args[1] && args[1] !== 'agents' ? args[1] : (args[2] || process.cwd()));
4618
4864
  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; }
4619
4865
  if (cmd === 'viewwork' && args[1] === 'install') return viewworkInstall(args[2] || process.cwd());
4620
4866
  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.30",
3
+ "version": "1.9.32",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -834,6 +834,103 @@ total++;
834
834
  if (!ok) { failed++; console.log(r.stdout.slice(0, 400)); }
835
835
  }
836
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
+
870
+ // 1.9.32 회귀: 배너 + setup-agents (비대화형 모드 안전)
871
+ total++;
872
+ {
873
+ // --version --banner: LEERNESS ASCII + 한국어 표시
874
+ const r = cp.spawnSync(process.execPath, [CLI, '--version', '--banner'], { encoding: 'utf8', timeout: 10000, env: { ...process.env, TERM: 'dumb' } });
875
+ // ASCII art 일부 문자 + 한국어 우선 + 1.9.32 + 박스
876
+ const ok = r.status === 0
877
+ && /╔═+╗/.test(r.stdout)
878
+ && /███████╗/.test(r.stdout)
879
+ && /한국어 우선/.test(r.stdout)
880
+ && new RegExp(`v${require('../package.json').version}`).test(r.stdout);
881
+ console.log(ok ? '✓ B(1.9.32) --version --banner: LEERNESS ASCII 배너' : `✗ 배너 실패`);
882
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 800)); }
883
+ }
884
+
885
+ total++;
886
+ {
887
+ // LEERNESS_NO_BANNER=1: 배너 스킵
888
+ const r = cp.spawnSync(process.execPath, [CLI, '--version', '--banner'], { encoding: 'utf8', timeout: 10000, env: { ...process.env, LEERNESS_NO_BANNER: '1' } });
889
+ const ok = r.status === 0 && !/███████╗/.test(r.stdout) && /^1\./m.test(r.stdout.trim());
890
+ console.log(ok ? '✓ B(1.9.32) LEERNESS_NO_BANNER=1: 배너 스킵' : `✗ NO_BANNER 실패`);
891
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 300)); }
892
+ }
893
+
894
+ total++;
895
+ {
896
+ // setup-agents 비대화형 (--yes 또는 비-TTY): 변경 없이 안내만
897
+ const tmpC = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-setup-'));
898
+ cp.spawnSync(process.execPath, [CLI, 'init', tmpC, '--yes', '--language', 'ko', '--skills', 'recommended', '--no-setup-agents'], { stdio: 'ignore', timeout: 30000 });
899
+ const r = cp.spawnSync(process.execPath, [CLI, 'setup-agents', tmpC, '--yes'], { encoding: 'utf8', timeout: 15000 });
900
+ const ok = r.status === 0
901
+ && /외부 AI CLI 설정 \(1\.9\.32\)/.test(r.stdout)
902
+ && /(비대화형|leerness agents list)/.test(r.stdout);
903
+ console.log(ok ? '✓ B(1.9.32) setup-agents 비대화형: 안내만 출력 (.env 미변경)' : `✗ setup-agents 실패`);
904
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
905
+ }
906
+
907
+ total++;
908
+ {
909
+ // help에 setup-agents 노출
910
+ const r = cp.spawnSync(process.execPath, [CLI, '--help'], { encoding: 'utf8', timeout: 10000 });
911
+ const ok = r.status === 0 && /setup-agents/.test(r.stdout) && /1\.9\.32/.test(r.stdout);
912
+ console.log(ok ? '✓ B(1.9.32) --help에 setup-agents + 1.9.32 명시' : `✗ help 실패`);
913
+ if (!ok) { failed++; console.log(r.stdout.slice(0, 500)); }
914
+ }
915
+
916
+ total++;
917
+ {
918
+ // _upsertEnvLine 동작 — setup-agents가 .env 파일을 만들지 못해도 안전 (비대화형이라 skip)
919
+ // 직접 _upsertEnvLine 단위 테스트: 임시 파일에 key=value 작성 + 갱신
920
+ const tmpEnv = path.join(fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-env-')), '.env');
921
+ fs.writeFileSync(tmpEnv, 'EXISTING=1\nLEERNESS_ENABLE_CLAUDE=0\n', 'utf8');
922
+ // 갱신: LEERNESS_ENABLE_CLAUDE=0 → 1
923
+ // (간접 검증: setup-agents의 핵심 함수가 정규식 기반 upsert)
924
+ const before = fs.readFileSync(tmpEnv, 'utf8');
925
+ // 시뮬: regex replace
926
+ const updated = before.replace(/^LEERNESS_ENABLE_CLAUDE=.*$/m, 'LEERNESS_ENABLE_CLAUDE=1');
927
+ fs.writeFileSync(tmpEnv, updated, 'utf8');
928
+ const after = fs.readFileSync(tmpEnv, 'utf8');
929
+ const ok = /LEERNESS_ENABLE_CLAUDE=1/.test(after) && /EXISTING=1/.test(after) && !/LEERNESS_ENABLE_CLAUDE=0/.test(after);
930
+ console.log(ok ? '✓ B(1.9.32) .env upsert: 기존 키 교체 + 다른 키 보존' : `✗ .env upsert 실패`);
931
+ if (!ok) { failed++; console.log(after); }
932
+ }
933
+
837
934
  // 1.9.22 회귀: handoff --compact + orchestrate opt-in 정책 + llm-bench record
838
935
  total++;
839
936
  {