leerness 1.9.246 → 1.9.248
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 +75 -0
- package/README.md +3 -3
- package/bin/harness.js +154 -64
- package/package.json +1 -1
- package/scripts/e2e.js +17 -17
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.248 — 2026-05-26 — UR-0017 Gemini CLI 제거 + Antigravity CLI (agy) 도입
|
|
4
|
+
|
|
5
|
+
**🔄 사용자 명시 (UR-0017): Gemini CLI 제거 + Antigravity (agy) CLI 전체 교체.**
|
|
6
|
+
|
|
7
|
+
### 변경 범위 (65 occurrences case-insensitive)
|
|
8
|
+
- **EXTERNAL_AGENTS**: `gemini` → `agy` (bin/envFlag/install 안내 모두 교체)
|
|
9
|
+
- **_PROVIDER_CYCLE_ORDER**: `['ollama', 'claude', 'codex', 'agy', 'copilot']` (Tab cycle)
|
|
10
|
+
- **_PROVIDER_MODEL_CATALOG.agy**: `antigravity-pro/flash/experimental` 모델 (1M+ context)
|
|
11
|
+
- **환경변수**: `LEERNESS_ENABLE_GEMINI` → `LEERNESS_ENABLE_AGY`
|
|
12
|
+
- **CLI bin**: `gemini -p` → `agy -p` (1-shot + REPL stream + dispatch)
|
|
13
|
+
- **agents dispatch --write**: `gemini --yolo` → `agy --yolo`
|
|
14
|
+
- **setup-agents UI**: "Gemini (gemini CLI)" → "Antigravity (agy CLI)"
|
|
15
|
+
- **handoff body sub-agent display** (1.9.246 status bar): allAgents = ['claude', 'codex', 'agy', 'copilot']
|
|
16
|
+
- **MCP description**: agents_list / provider_list 등 5종 → agy 명시
|
|
17
|
+
- **REPL :provider validation**: validProviders = ['ollama', 'claude', 'codex', 'agy', 'copilot']
|
|
18
|
+
- **.harness/leerness-config.json**: LEERNESS_ENABLE_AGY 기본값 "0"
|
|
19
|
+
- **scripts/e2e.js**: 회귀 테스트 — agents quota/dispatch/list/register-pending 모두 agy
|
|
20
|
+
- **install hint**: `npm i -g @google/antigravity-cli` (https://antigravity.google.com)
|
|
21
|
+
|
|
22
|
+
### 영향 받지 않은 영역
|
|
23
|
+
- 1.9.247 multi-provider fallback (agy 도 자동 fallback 대상)
|
|
24
|
+
- 1.9.246 status bar (agy 5종 한 줄 표시)
|
|
25
|
+
- handoff JSON 11 필드 (apiSkills/envInfo 등) 유지
|
|
26
|
+
|
|
27
|
+
### stress-v193 — **23/23 PASS · 100%**
|
|
28
|
+
- 1.9.248 (12): VERSION + EXTERNAL_AGENTS 교체 + 모델 catalog + Tab cycle + setup-agents UI + handoff status bar + dispatch + quota + list + config.json + validation + nonsecret keys
|
|
29
|
+
- 성능 (1): cold start avg 419ms
|
|
30
|
+
- 누적 회귀 (10): 1.9.207~247
|
|
31
|
+
|
|
32
|
+
### 자동 release (110 main-push streak · 71 npm publish streak · R204)
|
|
33
|
+
|
|
34
|
+
🔄 **Gemini CLI 단계적 제거 → Antigravity (agy) 전환** — 사용자 환경 변화 즉시 반영
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## 1.9.247 — 2026-05-26 — UR-0016 2단계 + UR-0015 2단계
|
|
39
|
+
|
|
40
|
+
**🔁 REPL multi-provider auto-fallback + 📚 api-skill audit 통합.**
|
|
41
|
+
|
|
42
|
+
### 1. UR-0016 2단계 — REPL multi-provider auto-fallback
|
|
43
|
+
사용자 보고 (1.9.246 스크린샷): `codex CLI 응답 실패 (exit=null)` → 멀티 provider auto-fallback 검토.
|
|
44
|
+
|
|
45
|
+
**구현:**
|
|
46
|
+
- `state.autoFallback` 필드 추가 (default OFF — opt-in)
|
|
47
|
+
- 활성화: `:fallback on` (REPL slash) 또는 `LEERNESS_REPL_AUTO_FALLBACK=1` env
|
|
48
|
+
- transient 실패 (`exit=null|timeout`) 감지 시 next ready agent 로 자동 전환 + 재시도 1회
|
|
49
|
+
- 응답 헤더: `↪ auto-fallback (1.9.247): codex 실패 → claude 자동 전환 + 재시도 중...`
|
|
50
|
+
- 성공 시 `✓ fallback 성공 [claude/...]`, 실패 시 친절한 진단 + 수동 :provider 안내
|
|
51
|
+
|
|
52
|
+
### 2. UR-0015 2단계 — api-skill audit 통합
|
|
53
|
+
사용자 명시 (UR-0015): *"AI가 정리해둔 파일이 참조되는지 확인"*
|
|
54
|
+
|
|
55
|
+
**구현:**
|
|
56
|
+
- `audit` 명령에 새 finding 추가: `api_skill_missing`
|
|
57
|
+
- 현재 in-progress task 의 request/nextAction/evidence 에 API 키워드 (`API|endpoint|REST|GraphQL|OAuth|webhook|http[s]?://`) 감지
|
|
58
|
+
- 매칭되는 api-skill 0건 → `warn` + `leerness api-skill add <url> --direction "..."` 안내
|
|
59
|
+
- 매칭 1+건 → `✓ API skill 매칭 OK (현재 task → N건)` 로그
|
|
60
|
+
- non-API task 는 영향 없음
|
|
61
|
+
|
|
62
|
+
### 3. 누적 회귀 (1.9.207~246) — 모두 유지
|
|
63
|
+
- REPL UX status bar (1.9.246) · api-skill cache (1.9.245) · _lastCycleLines HOTFIX (1.9.244)
|
|
64
|
+
- CJK 분류 (1.9.243) · env encoding --apply (1.9.242) · 모두 유지
|
|
65
|
+
- handoff JSON 11 필드 · MCP 70 도구 · agents dispatch 거부 회귀 (1.9.246 flake 방지) 검증
|
|
66
|
+
|
|
67
|
+
### 4. stress-v192 — **20/20 PASS · 100%**
|
|
68
|
+
- 1.9.247 (8): VERSION + autoFallback state + :fallback slash + 재시도 로직 + audit api_skill_missing + 실제 동작 (3 시나리오)
|
|
69
|
+
- 성능 (1): cold start avg 588ms
|
|
70
|
+
- 누적 회귀 (11): 1.9.207~246 + 보안 + e2e flake 방지
|
|
71
|
+
|
|
72
|
+
### 5. 자동 release (109 main-push streak · 70 npm publish streak · R203)
|
|
73
|
+
|
|
74
|
+
🔁 **사용자 실 환경 대응 보강** — 1.9.246 보고된 codex CLI exit=null 케이스 자동 회복 + 후속 API task UR-0015 자동 참조 확인.
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
3
78
|
## 1.9.246 — 2026-05-26 — UR-0016 REPL UX/UI 개선
|
|
4
79
|
|
|
5
80
|
**🎯 사용자 명시 (UR-0016): REPL agent 채팅 입력칸 옆 컨텍스트 게이지 + 서브 에이전트 가시화 + 정상 완료 초록색 강조.**
|
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
|
|
4
4
|
|
|
5
|
-
[](https://www.npmjs.com/package/leerness) [](https://www.npmjs.com/package/leerness) []() []() []() []() []() []() []() []() []() []() []() []()
|
|
6
6
|
|
|
7
7
|
```
|
|
8
8
|
╔══════════════════════════════════════════════════════════════╗
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.248 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
|
-
║
|
|
17
|
+
║ 🔄 Gemini CLI 제거 → Antigravity (agy) CLI 도입 (UR-0017) ║
|
|
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.
|
|
10
|
+
const VERSION = '1.9.248';
|
|
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 처리).
|
|
@@ -410,7 +410,7 @@ leerness agents dispatch "<task>" --to <id> # 작업 유형 추천 자동
|
|
|
410
410
|
- 작업 유형별 최적 sub-agent:
|
|
411
411
|
- 텍스트/번역/분석 → claude (1.7× 빠름)
|
|
412
412
|
- 깊은 코드 추론 → codex (가장 상세)
|
|
413
|
-
- 파일 직접 수정 →
|
|
413
|
+
- 파일 직접 수정 → agy --yolo (정확, Antigravity CLI 1.9.248)
|
|
414
414
|
- 보안 리뷰 → \`leerness review --persona security\`
|
|
415
415
|
- **충돌 방지 규칙 (필수)**:
|
|
416
416
|
- 각 sub-agent에 *자신만 수정할 파일 경로* 명시
|
|
@@ -798,20 +798,20 @@ async function resolveInstallOptions(root, opts = {}) {
|
|
|
798
798
|
const picked = await _selectMany(_t('install.agents.title', _agLang), [
|
|
799
799
|
{ label: 'Claude (ANTHROPIC_API_KEY 또는 claude CLI)', description: '추론력 최고 — 코드 작성/리뷰 기본', id: 'claude' },
|
|
800
800
|
{ label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
|
|
801
|
-
{ label: '
|
|
801
|
+
{ label: 'Antigravity (agy CLI)', description: 'Google Antigravity 멀티모달 에이전트 (1.9.248~)', id: 'agy' },
|
|
802
802
|
{ label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
|
|
803
803
|
{ label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 — 무료/오프라인', id: 'ollama' }
|
|
804
804
|
], { defaults: [] });
|
|
805
805
|
agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
|
|
806
806
|
} else {
|
|
807
807
|
log('\nCLI 에이전트 활성화 (복수 선택 — 콤마로 구분, opt-in):');
|
|
808
|
-
log(' 1) claude 2) codex 3)
|
|
808
|
+
log(' 1) claude 2) codex 3) agy (Antigravity) 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
|
|
809
809
|
const a = (await ask('선택 [none]: ')).trim().toLowerCase();
|
|
810
|
-
if (a === 'all') agentsOptIn = ['claude', 'codex', '
|
|
810
|
+
if (a === 'all') agentsOptIn = ['claude', 'codex', 'agy', 'copilot', 'ollama'];
|
|
811
811
|
else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
|
|
812
812
|
else {
|
|
813
|
-
const map = { '1': 'claude', '2': 'codex', '3': '
|
|
814
|
-
const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','
|
|
813
|
+
const map = { '1': 'claude', '2': 'codex', '3': 'agy', '4': 'copilot', '5': 'ollama' };
|
|
814
|
+
const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','agy','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
|
|
815
815
|
agentsOptIn = picks.length ? picks : 'none';
|
|
816
816
|
}
|
|
817
817
|
}
|
|
@@ -951,7 +951,7 @@ async function install(root, opts = {}) {
|
|
|
951
951
|
const a = resolved.agentsOptIn || 'none';
|
|
952
952
|
const enabledSet = (() => {
|
|
953
953
|
if (Array.isArray(a)) return new Set(a);
|
|
954
|
-
if (a === 'all') return new Set(['claude', 'codex', '
|
|
954
|
+
if (a === 'all') return new Set(['claude', 'codex', 'agy', 'copilot', 'ollama']);
|
|
955
955
|
if (a === 'none' || !a) return new Set();
|
|
956
956
|
return new Set([a]); // back-compat: 단일 문자열
|
|
957
957
|
})();
|
|
@@ -983,7 +983,7 @@ async function install(root, opts = {}) {
|
|
|
983
983
|
LEERNESS_OLLAMA_MODEL: '',
|
|
984
984
|
LEERNESS_ENABLE_CLAUDE: enable('claude') ? '1' : '0',
|
|
985
985
|
LEERNESS_ENABLE_CODEX: enable('codex') ? '1' : '0',
|
|
986
|
-
|
|
986
|
+
LEERNESS_ENABLE_AGY: enable('agy') ? '1' : '0',
|
|
987
987
|
LEERNESS_ENABLE_COPILOT: enable('copilot') ? '1' : '0',
|
|
988
988
|
LEERNESS_ENABLE_OLLAMA: enable('ollama') ? '1' : '0',
|
|
989
989
|
LEERNESS_SKILL_DISCOVER_URL: '',
|
|
@@ -6491,6 +6491,35 @@ function audit(root, opts = {}) {
|
|
|
6491
6491
|
}
|
|
6492
6492
|
} catch {}
|
|
6493
6493
|
}
|
|
6494
|
+
// 1.9.247 (UR-0015 2단계): api-skill 참조 audit — API 관련 task 인데 .harness/api-skills/ 미참조 시 경고
|
|
6495
|
+
// 사용자 명시 (UR-0015): "AI가 정리해둔 파일이 참조되는지 확인"
|
|
6496
|
+
// 현재 in-progress task 의 request/nextAction 에 API 키워드 (URL, "API", "endpoint", "REST", "GraphQL", "OAuth", "webhook") 있는데
|
|
6497
|
+
// _matchAPISkills() 결과가 0 이면 → 경고 + leerness api-skill add <url> 안내
|
|
6498
|
+
try {
|
|
6499
|
+
const rows = readProgressRows(root);
|
|
6500
|
+
const ip = rows.find(r => r.status === 'in-progress');
|
|
6501
|
+
if (ip) {
|
|
6502
|
+
const taskText = (ip.request || '') + ' ' + (ip.nextAction || '') + ' ' + (ip.evidence || '');
|
|
6503
|
+
const apiKeywords = /\bAPI\b|endpoint|REST|GraphQL|OAuth|webhook|https?:\/\/[^\s]+/i;
|
|
6504
|
+
if (apiKeywords.test(taskText)) {
|
|
6505
|
+
const matched = _matchAPISkills(root, taskText);
|
|
6506
|
+
const allSkills = _listAPISkills(root);
|
|
6507
|
+
if (matched.length === 0) {
|
|
6508
|
+
warnings++;
|
|
6509
|
+
warn(`API 관련 task 감지 (현재: "${(ip.request || '').slice(0, 60)}") — .harness/api-skills/ 매칭 0건 (저장 ${allSkills.length})`);
|
|
6510
|
+
warn(` → leerness api-skill add <url> --direction "구현 방향" 으로 정리 권장 (1.9.245 UR-0015 / 1.9.247 audit)`);
|
|
6511
|
+
_finding('api_skill_missing', 'warn', 'API 관련 task 인데 .harness/api-skills/ 매칭 없음', {
|
|
6512
|
+
taskRequest: (ip.request || '').slice(0, 100),
|
|
6513
|
+
apiSkillsTotal: allSkills.length,
|
|
6514
|
+
matched: 0,
|
|
6515
|
+
hint: 'leerness api-skill add <url> --direction "..."'
|
|
6516
|
+
});
|
|
6517
|
+
} else {
|
|
6518
|
+
ok(`API skill 매칭 OK (현재 task → ${matched.length}건 매칭 in .harness/api-skills/, 1.9.247 UR-0015 2단계)`);
|
|
6519
|
+
}
|
|
6520
|
+
}
|
|
6521
|
+
}
|
|
6522
|
+
} catch {}
|
|
6494
6523
|
// 1.9.63: --strict — warnings ≥ threshold 시 failures로 승격 (CI 친화)
|
|
6495
6524
|
if (has('--strict')) {
|
|
6496
6525
|
const threshold = parseInt(arg('--threshold', '1'), 10);
|
|
@@ -8779,7 +8808,7 @@ const _LEERNESS_NONSECRET_KEYS = new Set([
|
|
|
8779
8808
|
'LEERNESS_OLLAMA_MODEL', // 모델 이름 — 비밀 X
|
|
8780
8809
|
'LEERNESS_ENABLE_CLAUDE', // 활성화 플래그
|
|
8781
8810
|
'LEERNESS_ENABLE_CODEX',
|
|
8782
|
-
'
|
|
8811
|
+
'LEERNESS_ENABLE_AGY',
|
|
8783
8812
|
'LEERNESS_ENABLE_COPILOT',
|
|
8784
8813
|
'LEERNESS_ENABLE_OLLAMA',
|
|
8785
8814
|
'LEERNESS_SKILL_DISCOVER_URL', // 공개 URL
|
|
@@ -9423,7 +9452,7 @@ function _resolvePersona(root, id) {
|
|
|
9423
9452
|
return null;
|
|
9424
9453
|
}
|
|
9425
9454
|
|
|
9426
|
-
// 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/
|
|
9455
|
+
// 1.9.30: 외부 AI CLI 오케스트레이션 — claude/codex/agy/copilot 가용성 + 활성화 체크
|
|
9427
9456
|
// 사용자 정책: 환경변수로 활성화 명시 + 실제 PATH 존재 확인 + 메인이 sub-agent 분배 시 참조
|
|
9428
9457
|
// 1.9.32: installCmd 추가 — setup-agents 시 자동 설치 시도 가능
|
|
9429
9458
|
const EXTERNAL_AGENTS = [
|
|
@@ -9431,8 +9460,11 @@ const EXTERNAL_AGENTS = [
|
|
|
9431
9460
|
installCmd: 'npm i -g @anthropic-ai/claude-code', installHint: 'https://docs.anthropic.com/en/docs/claude-code/setup' },
|
|
9432
9461
|
{ id: 'codex', bin: 'codex', envFlag: 'LEERNESS_ENABLE_CODEX', versionArgs: ['--version'], desc: 'OpenAI Codex CLI (격리 sandbox)',
|
|
9433
9462
|
installCmd: 'npm i -g @openai/codex', installHint: 'https://github.com/openai/codex' },
|
|
9434
|
-
|
|
9435
|
-
|
|
9463
|
+
// 1.9.248 (사용자 명시 UR-0017): Gemini CLI 제거 + Antigravity CLI (agy 명령어) 교체
|
|
9464
|
+
// Google Antigravity 는 IDE 기반 멀티 모달 에이전트 도구. agy CLI 는 워크스페이스 직접 수정 가능 모드 지원.
|
|
9465
|
+
// 기존 LEERNESS_ENABLE_AGY 환경변수는 LEERNESS_ENABLE_AGY 로 교체. provider id 도 'agy' 로 통일.
|
|
9466
|
+
{ id: 'agy', bin: 'agy', envFlag: 'LEERNESS_ENABLE_AGY', versionArgs: ['--version'], desc: 'Google Antigravity CLI (멀티모달 에이전트, --yolo 워크스페이스 수정 가능)',
|
|
9467
|
+
installCmd: 'npm i -g @google/antigravity-cli', installHint: 'https://antigravity.google.com (Antigravity IDE/CLI)' },
|
|
9436
9468
|
{ id: 'copilot', bin: 'gh', envFlag: 'LEERNESS_ENABLE_COPILOT', versionArgs: ['copilot', '--version'], desc: 'GitHub Copilot CLI (gh copilot)',
|
|
9437
9469
|
installCmd: 'gh extension install github/gh-copilot', installHint: 'https://github.com/github/gh-copilot (gh CLI 선행 설치 필요)' },
|
|
9438
9470
|
// 1.9.146: Ollama 추가 (사용자 명시 요청 #3) — 로컬 LLM, HTTP API 11434
|
|
@@ -9639,7 +9671,7 @@ function providerCmd(root, sub, ...args) {
|
|
|
9639
9671
|
// 1.9.195: provider probe — A축 (범용 AI 하네스) 보강. 사용자 의도: "범용 AI 하네스 ... 최고의 도구"
|
|
9640
9672
|
// 시스템에 사용 가능한 provider 자동 감지 (PATH 명령 + 로컬 endpoint + API 키 환경변수)
|
|
9641
9673
|
// 감지 대상:
|
|
9642
|
-
// - CLI: claude / codex /
|
|
9674
|
+
// - CLI: claude / codex / agy / copilot / ollama (PATH check)
|
|
9643
9675
|
// - Local endpoint: Ollama (11434) / LM Studio (1234) / llama.cpp server (8080)
|
|
9644
9676
|
// - Cloud API key: OPENROUTER_API_KEY / GROQ_API_KEY / TOGETHER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY
|
|
9645
9677
|
// 결과 → table + LEERNESS_ENABLE_* 설정 권장
|
|
@@ -9692,7 +9724,7 @@ async function _probeProviderEndpoints(root, timeoutMs) {
|
|
|
9692
9724
|
const cliTargets = [
|
|
9693
9725
|
{ id: 'claude', bin: 'claude' },
|
|
9694
9726
|
{ id: 'codex', bin: 'codex' },
|
|
9695
|
-
{ id: '
|
|
9727
|
+
{ id: 'agy', bin: 'agy' },
|
|
9696
9728
|
{ id: 'copilot', bin: 'copilot' },
|
|
9697
9729
|
{ id: 'ollama', bin: 'ollama' }
|
|
9698
9730
|
];
|
|
@@ -9801,7 +9833,7 @@ function _recommendAgent(task) {
|
|
|
9801
9833
|
// 파일 작성·수정·생성
|
|
9802
9834
|
if (hasAny(['create', 'write', 'generate', 'patch', 'fix', 'implement', 'edit',
|
|
9803
9835
|
'구현', '생성', '작성', '수정', '추가'])) {
|
|
9804
|
-
return { target: '
|
|
9836
|
+
return { target: 'agy', reason: '워크스페이스 직접 수정은 agy --yolo (Antigravity) 가 정확' };
|
|
9805
9837
|
}
|
|
9806
9838
|
return { target: null, reason: '' };
|
|
9807
9839
|
}
|
|
@@ -10317,7 +10349,7 @@ function _dispatchCommand(agentId, task, writeMode) {
|
|
|
10317
10349
|
const q = String(task || '').replace(/"/g, '\\"');
|
|
10318
10350
|
if (agentId === 'claude') return `claude ${writeMode ? '--print --dangerously-skip-permissions' : '--print'} "${q}"`;
|
|
10319
10351
|
if (agentId === 'codex') return `codex ${writeMode ? 'exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox' : 'exec --skip-git-repo-check'} "${q}"`;
|
|
10320
|
-
if (agentId === '
|
|
10352
|
+
if (agentId === 'agy') return `agy ${writeMode ? '-p --yolo' : '-p'} "${q}"`;
|
|
10321
10353
|
if (agentId === 'copilot') return `gh copilot suggest "${q}"`;
|
|
10322
10354
|
if (agentId === 'ollama') return `# ollama — leerness agent "${q}" --provider ollama 로 직접 호출 (REPL: leerness agent)`;
|
|
10323
10355
|
return `# ${agentId}: 명령 빌더 미정의`;
|
|
@@ -10351,8 +10383,8 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10351
10383
|
if (!ready.length) {
|
|
10352
10384
|
log('');
|
|
10353
10385
|
log(`💡 활성화 방법:`);
|
|
10354
|
-
log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/
|
|
10355
|
-
log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1,
|
|
10386
|
+
log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/antigravity-cli\`)`);
|
|
10387
|
+
log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1, LEERNESS_ENABLE_AGY=1`);
|
|
10356
10388
|
log(` 3) \`leerness agents check\`로 재확인`);
|
|
10357
10389
|
log(` 💡 1.9.157: 빌트인 외 CLI 추가: \`leerness provider add <id> --bin <cmd>\``);
|
|
10358
10390
|
} else {
|
|
@@ -10535,7 +10567,7 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10535
10567
|
if (has('--multi') || target === 'all' || target === '*') {
|
|
10536
10568
|
return agentsCmd(root, 'multi', ...args);
|
|
10537
10569
|
}
|
|
10538
|
-
if (!target) { fail('--to <agent_id> 필요 (claude/codex/
|
|
10570
|
+
if (!target) { fail('--to <agent_id> 필요 (claude/codex/agy/copilot) — 활성 전체에 일괄 분배는 `leerness agents multi`'); return process.exit(1); }
|
|
10539
10571
|
const agentDef = EXTERNAL_AGENTS.find(a => a.id === target);
|
|
10540
10572
|
if (!agentDef) { fail(`알 수 없는 agent: ${target}`); return process.exit(1); }
|
|
10541
10573
|
// 1.9.36: 작업 유형 키워드 분석 → 최적 CLI 추천 (ready 체크 전에 출력 — 비활성이어도 추천)
|
|
@@ -10570,9 +10602,9 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10570
10602
|
log(`codex ${flags} "${q}"`);
|
|
10571
10603
|
log(`# ℹ codex는 PowerShell 경유 — POSIX /tmp 경로는 C:\\tmp\\로 해석됨`);
|
|
10572
10604
|
if (writeMode) log(`# ⚠ --dangerously-bypass-approvals-and-sandbox: sandbox 우회`);
|
|
10573
|
-
} else if (target === '
|
|
10605
|
+
} else if (target === 'agy') {
|
|
10574
10606
|
const flags = writeMode ? '-p --yolo' : '-p';
|
|
10575
|
-
log(`
|
|
10607
|
+
log(`agy ${flags} "${q}"`);
|
|
10576
10608
|
if (writeMode) log(`# ⚠ --yolo: 워크스페이스 파일 직접 수정 가능`);
|
|
10577
10609
|
} else if (target === 'copilot') {
|
|
10578
10610
|
log(`gh copilot suggest "${q}"`);
|
|
@@ -10623,9 +10655,9 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10623
10655
|
? ['exec', '--skip-git-repo-check', '--dangerously-bypass-approvals-and-sandbox', task]
|
|
10624
10656
|
: ['exec', '--skip-git-repo-check', task];
|
|
10625
10657
|
cmd = 'codex';
|
|
10626
|
-
} else if (agent.id === '
|
|
10658
|
+
} else if (agent.id === 'agy') {
|
|
10627
10659
|
cmdArgs = writeMode ? ['-p', task, '--yolo'] : ['-p', task];
|
|
10628
|
-
cmd = '
|
|
10660
|
+
cmd = 'agy';
|
|
10629
10661
|
} else if (agent.id === 'copilot') {
|
|
10630
10662
|
cmdArgs = ['copilot', 'suggest', task];
|
|
10631
10663
|
cmd = 'gh';
|
|
@@ -10699,10 +10731,10 @@ function agentsCmd(root, sub, ...args) {
|
|
|
10699
10731
|
out.hint = 'https://platform.openai.com/account/usage 확인';
|
|
10700
10732
|
}
|
|
10701
10733
|
out.raw = help.slice(0, 200);
|
|
10702
|
-
} else if (agent.id === '
|
|
10703
|
-
//
|
|
10734
|
+
} else if (agent.id === 'agy') {
|
|
10735
|
+
// agy CLI (Antigravity): 무료 티어는 분당 60req 제한, CLI 자체에선 노출 안 됨
|
|
10704
10736
|
out.quota = 'rate-limited';
|
|
10705
|
-
out.hint = '무료 티어: 60 req/min, 1000 req/day ·
|
|
10737
|
+
out.hint = '무료 티어: 60 req/min, 1000 req/day · Antigravity 유료 플랜은 https://antigravity.google.com';
|
|
10706
10738
|
} else if (agent.id === 'copilot') {
|
|
10707
10739
|
// gh copilot은 GitHub Copilot 구독 (월 단위 quota 없음, individual/business 플랜)
|
|
10708
10740
|
const r = cp.spawnSync('gh', ['auth', 'status'], { encoding: 'utf8', timeout: 4000, shell: true });
|
|
@@ -10844,13 +10876,13 @@ function reviewCmd(root, target) {
|
|
|
10844
10876
|
}
|
|
10845
10877
|
|
|
10846
10878
|
// 1.9.25: register-pending — sub-agent/외부 모델이 작업 시작 즉시 progress-tracker에 in-progress 등록
|
|
10847
|
-
// 사용 예: leerness register-pending "<요청 내용>" --agent
|
|
10879
|
+
// 사용 예: leerness register-pending "<요청 내용>" --agent agy
|
|
10848
10880
|
// → 다음 T-ID 자동 할당, status=in-progress, evidence="(pending) by <agent>"
|
|
10849
10881
|
// → 다른 세션이 즉시 발견 가능 (모순 감지)
|
|
10850
10882
|
function registerPendingCmd(root, requestParts) {
|
|
10851
10883
|
root = absRoot(root || process.cwd());
|
|
10852
10884
|
const request = (requestParts || []).join(' ').trim();
|
|
10853
|
-
if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent
|
|
10885
|
+
if (!request) { fail('register-pending "<요청>" 필요. 예: leerness register-pending "toJson 함수 추가" --agent agy'); return process.exit(1); }
|
|
10854
10886
|
const agent = arg('--agent', 'unknown');
|
|
10855
10887
|
const note = arg('--note', '');
|
|
10856
10888
|
|
|
@@ -14923,7 +14955,7 @@ async function _benchmarkMeasure(root, task) {
|
|
|
14923
14955
|
let cmd, cliArgs;
|
|
14924
14956
|
if (agent.id === 'claude') { cmd = 'claude'; cliArgs = ['--print', task]; }
|
|
14925
14957
|
else if (agent.id === 'codex') { cmd = 'codex'; cliArgs = ['exec', '--skip-git-repo-check', task]; }
|
|
14926
|
-
else if (agent.id === '
|
|
14958
|
+
else if (agent.id === 'agy') { cmd = 'agy'; cliArgs = ['-p', task]; }
|
|
14927
14959
|
else continue;
|
|
14928
14960
|
const t0 = Date.now();
|
|
14929
14961
|
// 1.9.150: runCommandSafe — agent CLI bench sandbox (env scrub + observability)
|
|
@@ -15399,7 +15431,7 @@ function mcpServeCmd(root) {
|
|
|
15399
15431
|
{ 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' } } } },
|
|
15400
15432
|
{ 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'] } },
|
|
15401
15433
|
{ name: 'leerness_contract_verify', description: '명세 ↔ 구현 함수/필드 일치 자동 검사', inputSchema: { type: 'object', properties: { spec: { type: 'string' }, impl: { type: 'string' } }, required: ['spec', 'impl'] } },
|
|
15402
|
-
{ name: 'leerness_agents_list', description: '외부 AI CLI 가용성 표 (claude/codex/
|
|
15434
|
+
{ name: 'leerness_agents_list', description: '외부 AI CLI 가용성 표 (claude/codex/agy/copilot 상태 + 환경변수 활성화 여부)', inputSchema: { type: 'object', properties: {} } },
|
|
15403
15435
|
{ name: 'leerness_reuse_map', description: '워크스페이스 중복 함수/capability 자동 감지 (--all-apps + fuzzy 매칭)', inputSchema: { type: 'object', properties: { path: { type: 'string' }, allApps: { type: 'boolean' }, strictElements: { type: 'boolean' } } } },
|
|
15404
15436
|
{ name: 'leerness_whats_new', description: 'CHANGELOG 차분 자동 추출 (from → to 사이 신규 명령/플래그/파일)', inputSchema: { type: 'object', properties: { from: { type: 'string' }, to: { type: 'string' } } } },
|
|
15405
15437
|
{ name: 'leerness_usage_stats', description: 'leerness 명령별 누적 호출 통계 + drift 통계', inputSchema: { type: 'object', properties: { path: { type: 'string' } } } },
|
|
@@ -15441,7 +15473,7 @@ function mcpServeCmd(root) {
|
|
|
15441
15473
|
{ 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'] } },
|
|
15442
15474
|
{ 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'] } },
|
|
15443
15475
|
{ 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' } } } },
|
|
15444
|
-
{ name: 'leerness_provider_list', description: '1.9.157/158 — Provider Registry 조회 JSON ({ total, builtin, user, providers: [{ id, bin, envFlag, source, desc }] }). 빌트인 5종 (claude/codex/
|
|
15476
|
+
{ 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' } } } },
|
|
15445
15477
|
{ 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'] } },
|
|
15446
15478
|
{ 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'] } },
|
|
15447
15479
|
{ 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'] } },
|
|
@@ -16134,13 +16166,13 @@ function permissionCheck(root, action, target) {
|
|
|
16134
16166
|
}
|
|
16135
16167
|
|
|
16136
16168
|
// ===== 1.9.146: leerness agent — OpenClaw/Hermes 스타일 오픈소스 CLI 에이전트 모드 (사용자 명시 요청 #4) =====
|
|
16137
|
-
// MVP: handoff 컨텍스트 자동 로드 → 활성 CLI (claude/codex/
|
|
16169
|
+
// MVP: handoff 컨텍스트 자동 로드 → 활성 CLI (claude/codex/agy/ollama) 1개에 작업 위임.
|
|
16138
16170
|
// 권한은 .harness/agent-permissions.json 기준. 실제 LLM 호출은 외부 CLI 또는 Ollama HTTP API.
|
|
16139
16171
|
function _activeCliAgents() {
|
|
16140
16172
|
const out = [];
|
|
16141
16173
|
if (process.env.LEERNESS_ENABLE_CLAUDE === '1') out.push('claude');
|
|
16142
16174
|
if (process.env.LEERNESS_ENABLE_CODEX === '1') out.push('codex');
|
|
16143
|
-
if (process.env.
|
|
16175
|
+
if (process.env.LEERNESS_ENABLE_AGY === '1') out.push('agy');
|
|
16144
16176
|
if (process.env.LEERNESS_ENABLE_COPILOT === '1') out.push('copilot');
|
|
16145
16177
|
if (process.env.LEERNESS_ENABLE_OLLAMA === '1') out.push('ollama');
|
|
16146
16178
|
return out;
|
|
@@ -16196,7 +16228,7 @@ async function _ollamaListModels() {
|
|
|
16196
16228
|
}
|
|
16197
16229
|
|
|
16198
16230
|
// 1.9.153: 외부 CLI 채팅 호출 (multi-provider REPL — 사용자 명시 요청)
|
|
16199
|
-
// claude/codex/
|
|
16231
|
+
// claude/codex/agy/copilot 를 child_process 로 호출 후 stdout 캡처.
|
|
16200
16232
|
// runCommandSafe 경유 — env scrub + permissions + observability 자동 적용.
|
|
16201
16233
|
async function _cliChat(root, provider, prompt, opts) {
|
|
16202
16234
|
opts = opts || {};
|
|
@@ -16211,7 +16243,7 @@ async function _cliChat(root, provider, prompt, opts) {
|
|
|
16211
16243
|
let cmd, args, stdinInput = null;
|
|
16212
16244
|
if (provider === 'claude') { cmd = 'claude'; args = ['--print']; stdinInput = prompt; }
|
|
16213
16245
|
else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', '-']; stdinInput = prompt; }
|
|
16214
|
-
else if (provider === '
|
|
16246
|
+
else if (provider === 'agy') { cmd = 'agy'; args = ['-p', prompt]; } // agy (Antigravity) 는 인자 only
|
|
16215
16247
|
else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', prompt]; }
|
|
16216
16248
|
else return { ok: false, error: `provider ${provider} 미지원`, provider };
|
|
16217
16249
|
// runCommandSafe — env scrub + observability 자동
|
|
@@ -16267,7 +16299,7 @@ async function _cliChatStream(root, provider, promptText, opts) {
|
|
|
16267
16299
|
args = ['exec', '--skip-git-repo-check', '-']; // - = stdin from claude/codex convention
|
|
16268
16300
|
useStdinForPrompt = true;
|
|
16269
16301
|
}
|
|
16270
|
-
else if (provider === '
|
|
16302
|
+
else if (provider === 'agy') { cmd = 'agy'; args = ['-p', promptText]; } // agy -p 는 인자 모드만 지원
|
|
16271
16303
|
else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', promptText]; }
|
|
16272
16304
|
else return { ok: false, error: `provider ${provider} 미지원`, provider };
|
|
16273
16305
|
const t0 = Date.now();
|
|
@@ -16603,10 +16635,10 @@ const _PROVIDER_MODEL_CATALOG = {
|
|
|
16603
16635
|
{ id: 'gpt-5-codex', note: '코드 특화 (Codex)' },
|
|
16604
16636
|
{ id: 'o4-mini', note: '빠른 reasoning' }
|
|
16605
16637
|
],
|
|
16606
|
-
|
|
16607
|
-
{ id: '
|
|
16608
|
-
{ id: '
|
|
16609
|
-
{ id: '
|
|
16638
|
+
agy: [
|
|
16639
|
+
{ id: 'antigravity-pro', note: 'Antigravity 최고급 (1M+ context)' },
|
|
16640
|
+
{ id: 'antigravity-flash', note: '빠른 응답' },
|
|
16641
|
+
{ id: 'antigravity-experimental', note: '실험적 (사용 가능 시)' }
|
|
16610
16642
|
],
|
|
16611
16643
|
copilot: [
|
|
16612
16644
|
{ id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
|
|
@@ -16620,7 +16652,7 @@ const _PROVIDER_MODEL_CATALOG = {
|
|
|
16620
16652
|
};
|
|
16621
16653
|
|
|
16622
16654
|
// 1.9.170: provider cycle 순서 (Tab) — 빌트인 5종. user provider는 동적으로 뒤에 추가.
|
|
16623
|
-
const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', '
|
|
16655
|
+
const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'copilot'];
|
|
16624
16656
|
|
|
16625
16657
|
// 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
|
|
16626
16658
|
const _AGENT_ROLE_PROMPTS = {
|
|
@@ -16679,7 +16711,9 @@ async function _agentRepl(root, opts) {
|
|
|
16679
16711
|
startedAt: new Date().toISOString(),
|
|
16680
16712
|
sessionId: 'sess-' + new Date().toISOString().replace(/[:.]/g, '-'),
|
|
16681
16713
|
// 1.9.170: 실시간 스트리밍 모드 (사용자 명시 — 추론중/diff/thinking 실시간 표시)
|
|
16682
|
-
streamMode: opts.stream !== false // default ON (env 로 끄려면 LEERNESS_REPL_STREAM=0)
|
|
16714
|
+
streamMode: opts.stream !== false, // default ON (env 로 끄려면 LEERNESS_REPL_STREAM=0)
|
|
16715
|
+
// 1.9.247 (UR-0016 2단계): multi-provider auto-fallback — default OFF (opt-in)
|
|
16716
|
+
autoFallback: opts.autoFallback === true || process.env.LEERNESS_REPL_AUTO_FALLBACK === '1'
|
|
16683
16717
|
};
|
|
16684
16718
|
if (process.env.LEERNESS_REPL_STREAM === '0') state.streamMode = false;
|
|
16685
16719
|
const sessionPath = () => path.join(absRoot(root), '.harness', 'agent-sessions', `${state.sessionId}.jsonl`);
|
|
@@ -16798,7 +16832,7 @@ async function _agentRepl(root, opts) {
|
|
|
16798
16832
|
// prompt 에 세부 모델 표시 (사용자 명시): agent[claude · claude-opus-4-7 / actor / ▶]>
|
|
16799
16833
|
const prompt = () => {
|
|
16800
16834
|
if (!isTty) return 'agent> ';
|
|
16801
|
-
const modelShort = (state.model || '').replace(/^(claude|gpt|gemini)-/, '').slice(0, 18);
|
|
16835
|
+
const modelShort = (state.model || '').replace(/^(claude|gpt|gemini|antigravity)-/, '').slice(0, 18);
|
|
16802
16836
|
const modelTag = modelShort ? C.dim(' · ') + C.mag(modelShort) : '';
|
|
16803
16837
|
return C.cy(`agent[${state.provider}${modelTag}${C.cy('/' + state.role)}${state.streamMode ? C.cy('/▶') : ''}${C.cy(']>')} `);
|
|
16804
16838
|
};
|
|
@@ -16818,7 +16852,7 @@ async function _agentRepl(root, opts) {
|
|
|
16818
16852
|
if (m.includes('gpt-4')) return 128000;
|
|
16819
16853
|
return 128000;
|
|
16820
16854
|
}
|
|
16821
|
-
if (provider === '
|
|
16855
|
+
if (provider === 'agy') return 1000000; // 2M까지 가능하지만 보수적으로 1M
|
|
16822
16856
|
if (provider === 'copilot') return 64000;
|
|
16823
16857
|
if (provider === 'ollama') return 8000; // 모델별 다르나 default 8k
|
|
16824
16858
|
return 100000;
|
|
@@ -16851,7 +16885,7 @@ async function _agentRepl(root, opts) {
|
|
|
16851
16885
|
.map(a => ({ id: a.id, st: _checkAgent(a).status }))
|
|
16852
16886
|
.filter(x => x.st === 'ready');
|
|
16853
16887
|
} catch {}
|
|
16854
|
-
const allAgents = ['claude', 'codex', '
|
|
16888
|
+
const allAgents = ['claude', 'codex', 'agy', 'copilot'];
|
|
16855
16889
|
const subBar = allAgents.map(id => {
|
|
16856
16890
|
if (id === state.provider) return C.bold(C.green(`*${id}`));
|
|
16857
16891
|
const isReady = ready.find(r => r.id === id);
|
|
@@ -16994,7 +17028,7 @@ async function _agentRepl(root, opts) {
|
|
|
16994
17028
|
['/quit', 'REPL 종료 (세션 자동 저장)'],
|
|
16995
17029
|
['/clear', '대화 히스토리 초기화'],
|
|
16996
17030
|
['/status', '현재 provider / model / role / perms 상태'],
|
|
16997
|
-
['/provider <id>', 'provider 전환 (ollama, claude, codex,
|
|
17031
|
+
['/provider <id>', 'provider 전환 (ollama, claude, codex, agy, copilot)'],
|
|
16998
17032
|
['/model <id>', '현재 provider의 모델 전환'],
|
|
16999
17033
|
['/role <id>', '역할 전환 (actor / planner / reviewer)'],
|
|
17000
17034
|
['/stream on|off', '실시간 스트리밍 토글 (default ON)']
|
|
@@ -17046,7 +17080,7 @@ async function _agentRepl(root, opts) {
|
|
|
17046
17080
|
log(' :model <name> — 모델 변경 (1.9.155 모든 provider 지원, 예: :model claude-opus-4-7)');
|
|
17047
17081
|
log(' :models — provider 별 모델 목록 (ollama 실시간 / 그 외 추천 카탈로그)');
|
|
17048
17082
|
log(' :role <r> — 역할 변경 (planner / reviewer / actor)');
|
|
17049
|
-
log(' :provider <p> — provider 변경 (ollama / claude / codex /
|
|
17083
|
+
log(' :provider <p> — provider 변경 (ollama / claude / codex / agy / copilot — ready 검증)');
|
|
17050
17084
|
log(' :stream on|off — 🆕 1.9.170 실시간 스트리밍 토글 (추론중/diff/thinking 실시간 표시)');
|
|
17051
17085
|
log(' :status — 현재 세션 상태 자세히 (1.9.155)');
|
|
17052
17086
|
log(' :clear — 화면 클리어 + history 유지');
|
|
@@ -17057,7 +17091,7 @@ async function _agentRepl(root, opts) {
|
|
|
17057
17091
|
log(' :perm [mode] — :permissions 단축 alias');
|
|
17058
17092
|
log(' :quit / :exit / :q — 종료 (자동 저장)');
|
|
17059
17093
|
log(C.bold('\n 🆕 1.9.170 키보드 단축키:'));
|
|
17060
|
-
log(' Tab — 다음 provider 로 cycle (ollama → claude → codex →
|
|
17094
|
+
log(' Tab — 다음 provider 로 cycle (ollama → claude → codex → agy → copilot)');
|
|
17061
17095
|
log(' Shift+Tab — 현재 provider 의 다음 model 로 cycle (catalog 기준)');
|
|
17062
17096
|
log(C.bold('\n Slash 명령 (1.9.150) — leerness 내부 명령 직접 호출:'));
|
|
17063
17097
|
log(' :verify — leerness verify-code (테스트/타입/린트 자동 검수)');
|
|
@@ -17124,7 +17158,7 @@ async function _agentRepl(root, opts) {
|
|
|
17124
17158
|
}
|
|
17125
17159
|
if (op === 'provider') {
|
|
17126
17160
|
const newProv = rest[0] || state.provider;
|
|
17127
|
-
const validProviders = ['ollama', 'claude', 'codex', '
|
|
17161
|
+
const validProviders = ['ollama', 'claude', 'codex', 'agy', 'copilot'];
|
|
17128
17162
|
if (!validProviders.includes(newProv)) {
|
|
17129
17163
|
log(C.yel(` ⚠ provider 는 ${validProviders.join(' / ')} (받음: ${newProv})`));
|
|
17130
17164
|
return false;
|
|
@@ -17167,6 +17201,20 @@ async function _agentRepl(root, opts) {
|
|
|
17167
17201
|
rl.setPrompt(prompt());
|
|
17168
17202
|
return false;
|
|
17169
17203
|
}
|
|
17204
|
+
// 1.9.247 (UR-0016 2단계): :fallback on|off — provider 실패 시 다음 ready agent 자동 전환 (opt-in)
|
|
17205
|
+
if (op === 'fallback') {
|
|
17206
|
+
const v = (rest[0] || '').toLowerCase();
|
|
17207
|
+
if (v === 'on' || v === '1' || v === 'true') {
|
|
17208
|
+
state.autoFallback = true;
|
|
17209
|
+
log(C.green(' ↪ auto-fallback: ON (1.9.247) — 현재 provider transient 실패 (timeout/exit=null) 시 다음 ready agent 자동 전환 + 재시도'));
|
|
17210
|
+
} else if (v === 'off' || v === '0' || v === 'false') {
|
|
17211
|
+
state.autoFallback = false;
|
|
17212
|
+
log(C.dim(' ↪ auto-fallback: OFF — 실패 시 사용자가 직접 :provider 전환'));
|
|
17213
|
+
} else {
|
|
17214
|
+
log(C.dim(` 현재 auto-fallback: ${state.autoFallback ? 'ON ↪' : 'OFF'} — 사용: :fallback on|off`));
|
|
17215
|
+
}
|
|
17216
|
+
return false;
|
|
17217
|
+
}
|
|
17170
17218
|
if (op === 'clear') { process.stdout.write('\x1b[2J\x1b[H'); return false; }
|
|
17171
17219
|
if (op === 'reset') { state.history = []; log(C.dim(' history 초기화됨')); return false; }
|
|
17172
17220
|
if (op === 'history') {
|
|
@@ -17347,12 +17395,12 @@ async function _agentRepl(root, opts) {
|
|
|
17347
17395
|
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}:`;
|
|
17348
17396
|
const t0 = Date.now();
|
|
17349
17397
|
let result;
|
|
17350
|
-
// 1.9.153: multi-provider REPL — ollama 외 claude/codex/
|
|
17398
|
+
// 1.9.153: multi-provider REPL — ollama 외 claude/codex/agy/copilot 도 세션 관리 (사용자 명시)
|
|
17351
17399
|
// 1.9.170: streamMode === true 이면 _cliChatStream 사용 (사용자 명시 — 추론중/diff 실시간 표시)
|
|
17352
17400
|
if (state.provider === 'ollama') {
|
|
17353
17401
|
log(C.dim(` → ollama${state.model ? ' (' + state.model + ')' : ''} 호출 중...`));
|
|
17354
17402
|
result = await _ollamaChat(finalPrompt, state.model);
|
|
17355
|
-
} else if (['claude', 'codex', '
|
|
17403
|
+
} else if (['claude', 'codex', 'agy', 'copilot'].includes(state.provider)) {
|
|
17356
17404
|
if (state.streamMode) {
|
|
17357
17405
|
log(C.dim(` → ${state.provider} CLI stream 호출 중... (Ctrl+C 로 중단)`));
|
|
17358
17406
|
result = await _cliChatStream(root, state.provider, finalPrompt, { timeout: 120000 });
|
|
@@ -17361,7 +17409,7 @@ async function _agentRepl(root, opts) {
|
|
|
17361
17409
|
result = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
|
|
17362
17410
|
}
|
|
17363
17411
|
} else {
|
|
17364
|
-
log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|
|
|
17412
|
+
log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|agy|copilot`));
|
|
17365
17413
|
promptWithStatus(); return;
|
|
17366
17414
|
}
|
|
17367
17415
|
const dt = Date.now() - t0;
|
|
@@ -17370,7 +17418,7 @@ async function _agentRepl(root, opts) {
|
|
|
17370
17418
|
state.history.push({ role: 'assistant', content: result.response });
|
|
17371
17419
|
// 1.9.170: stream 모드에서는 이미 실시간으로 출력됐으므로 헤더만 표시 (응답 중복 방지)
|
|
17372
17420
|
// 1.9.246: 정상 완료 작업은 초록색 ✓ 강조 (사용자 명시 UR-0016)
|
|
17373
|
-
if (state.streamMode && ['claude', 'codex', '
|
|
17421
|
+
if (state.streamMode && ['claude', 'codex', 'agy', 'copilot'].includes(state.provider)) {
|
|
17374
17422
|
log(C.green(` ✓ [assistant: ${state.provider}/${state.model || 'default'}, role=${state.role}, ${dt}ms · ${result.response.length}자]`));
|
|
17375
17423
|
} else {
|
|
17376
17424
|
log('');
|
|
@@ -17387,12 +17435,53 @@ async function _agentRepl(root, opts) {
|
|
|
17387
17435
|
// 실패 원인 분류
|
|
17388
17436
|
if (/exit=null/.test(errMsg) || /timeout/i.test(errMsg)) {
|
|
17389
17437
|
log(C.dim(` ↳ 가능 원인: (1) ${state.provider} CLI 응답 시간 초과 (모델 로딩/큰 응답 대기 중)`));
|
|
17390
|
-
log(C.dim(` (2) network/auth 문제 (특히 codex/
|
|
17391
|
-
log(C.dim(` ↳ 직접 검증: ${state.provider} --print "ping" ${state.provider === '
|
|
17438
|
+
log(C.dim(` (2) network/auth 문제 (특히 codex/agy 는 인터넷 + 토큰 필요)`));
|
|
17439
|
+
log(C.dim(` ↳ 직접 검증: ${state.provider} --print "ping" ${state.provider === 'agy' ? '--yolo' : ''}`));
|
|
17392
17440
|
} else if (/exit=1/.test(errMsg) || /unauth/i.test(errMsg) || /login/i.test(errMsg)) {
|
|
17393
17441
|
log(C.dim(` ↳ 가능 원인: 인증 누락 (login 필요)`));
|
|
17394
17442
|
log(C.dim(` ↳ 해결: ${state.provider} login ${state.provider === 'copilot' ? '(gh auth login)' : ''}`));
|
|
17395
17443
|
}
|
|
17444
|
+
// 1.9.247 (UR-0016 2단계): 자동 fallback — 사용자 명시 (`:fallback on` 또는 LEERNESS_REPL_AUTO_FALLBACK=1) 시
|
|
17445
|
+
// 현재 provider 가 transient 실패 (timeout/exit=null/network) 시 다음 ready agent 로 자동 전환 + 1회 재시도.
|
|
17446
|
+
// 사용자 명시 (이전 codex CLI exit=null 보고): "멀티 provider auto-fallback 도 검토".
|
|
17447
|
+
const isTransient = /exit=null|timeout/i.test(errMsg);
|
|
17448
|
+
if (state.autoFallback && isTransient) {
|
|
17449
|
+
try {
|
|
17450
|
+
const others = EXTERNAL_AGENTS.filter(a => a.id !== state.provider)
|
|
17451
|
+
.map(a => ({ def: a, status: _checkAgent(a) }))
|
|
17452
|
+
.filter(x => x.status.status === 'ready');
|
|
17453
|
+
if (others.length > 0) {
|
|
17454
|
+
const next = others[0].def.id;
|
|
17455
|
+
log(C.green(` ↪ auto-fallback (1.9.247): ${state.provider} 실패 → ${next} 자동 전환 + 재시도 중...`));
|
|
17456
|
+
state.provider = next;
|
|
17457
|
+
state.model = null;
|
|
17458
|
+
const cat = _PROVIDER_MODEL_CATALOG[state.provider];
|
|
17459
|
+
if (cat && cat.length) state.model = cat[0].id;
|
|
17460
|
+
rl.setPrompt(prompt());
|
|
17461
|
+
const t1 = Date.now();
|
|
17462
|
+
let retry;
|
|
17463
|
+
if (state.streamMode) retry = await _cliChatStream(root, state.provider, finalPrompt, { timeout: 120000 });
|
|
17464
|
+
else retry = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
|
|
17465
|
+
const dt1 = Date.now() - t1;
|
|
17466
|
+
_recordRun(root, { kind: 'agent_repl_turn_fallback', provider: state.provider, model: state.model, role: state.role, durationMs: dt1, ok: retry.ok, error: retry.error, promptChars: finalPrompt.length, responseChars: (retry.response || '').length });
|
|
17467
|
+
if (retry.ok) {
|
|
17468
|
+
state.history.push({ role: 'assistant', content: retry.response });
|
|
17469
|
+
log(C.green(` ✓ fallback 성공 [${state.provider}/${state.model || 'default'}, ${dt1}ms · ${retry.response.length}자]`));
|
|
17470
|
+
if (!state.streamMode) log(retry.response);
|
|
17471
|
+
_printInputDivider();
|
|
17472
|
+
if (state.history.length % 6 === 0) saveSession();
|
|
17473
|
+
promptWithStatus();
|
|
17474
|
+
return;
|
|
17475
|
+
} else {
|
|
17476
|
+
log(C.yel(` ⚠ fallback 도 실패 (${next}): ${retry.error || 'unknown'}`));
|
|
17477
|
+
}
|
|
17478
|
+
} else {
|
|
17479
|
+
log(C.dim(` (auto-fallback: 다른 ready provider 없음)`));
|
|
17480
|
+
}
|
|
17481
|
+
} catch (e) {
|
|
17482
|
+
log(C.dim(` (auto-fallback 오류: ${e.message})`));
|
|
17483
|
+
}
|
|
17484
|
+
}
|
|
17396
17485
|
// 전환 가능 provider 안내
|
|
17397
17486
|
try {
|
|
17398
17487
|
const others = EXTERNAL_AGENTS.filter(a => a.id !== state.provider)
|
|
@@ -17400,8 +17489,9 @@ async function _agentRepl(root, opts) {
|
|
|
17400
17489
|
.filter(x => x.status.status === 'ready');
|
|
17401
17490
|
if (others.length) {
|
|
17402
17491
|
log(C.dim(` 💡 즉시 전환: ${others.map(x => `:provider ${x.def.id}`).join(' · ')} 또는 Tab 키`));
|
|
17492
|
+
if (!state.autoFallback) log(C.dim(` 💡 다음 실패 시 자동 전환: \`:fallback on\` (1.9.247 UR-0016 2단계)`));
|
|
17403
17493
|
} else {
|
|
17404
|
-
log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|
|
|
17494
|
+
log(C.dim(` 💡 다른 provider 활성화: .env 에서 LEERNESS_ENABLE_<CLAUDE|CODEX|AGY|COPILOT>=1`));
|
|
17405
17495
|
}
|
|
17406
17496
|
} catch {}
|
|
17407
17497
|
}
|
|
@@ -17434,12 +17524,12 @@ async function agentCmd(root, taskArg) {
|
|
|
17434
17524
|
log(' leerness agent "<task>" # 1회 위임 (actor 역할 기본)');
|
|
17435
17525
|
log(' leerness agent "<task>" --role planner # 계획만 (1.9.148)');
|
|
17436
17526
|
log(' leerness agent "<task>" --role reviewer # 비판적 검토 (1.9.148)');
|
|
17437
|
-
log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/
|
|
17527
|
+
log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/agy/copilot)');
|
|
17438
17528
|
log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + Ollama 모델 선택');
|
|
17439
17529
|
log('');
|
|
17440
17530
|
log('REPL 메타 명령: :help / :model / :role / :provider / :history / :save / :quit');
|
|
17441
17531
|
log('REPL Slash 명령 (1.9.150): :verify / :audit / :handoff / :health (sandboxed runCommandSafe)');
|
|
17442
|
-
log('REPL Multi-provider (1.9.153): ollama / claude / codex /
|
|
17532
|
+
log('REPL Multi-provider (1.9.153): ollama / claude / codex / agy / copilot — 활성 CLI 자동 감지');
|
|
17443
17533
|
log('');
|
|
17444
17534
|
log('현재 활성 provider: ' + (_activeCliAgents().join(', ') || '(없음) — .env에서 LEERNESS_ENABLE_* 활성화'));
|
|
17445
17535
|
log('권한 모드: ' + (_readPermissions(root).mode || 'basic'));
|
|
@@ -17470,17 +17560,17 @@ async function agentCmd(root, taskArg) {
|
|
|
17470
17560
|
// 1.9.148: role prompt 자동 prepend (모든 provider 공통)
|
|
17471
17561
|
const finalPrompt = `${rolePrompt}\n\nTask: ${task}`;
|
|
17472
17562
|
const t0 = Date.now();
|
|
17473
|
-
// 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/
|
|
17563
|
+
// 1.9.154: 1-shot 모드도 multi-provider — Ollama 외 claude/codex/agy/copilot 직접 호출 (1.9.153 _cliChat 재사용)
|
|
17474
17564
|
let r;
|
|
17475
17565
|
if (provider === 'ollama') {
|
|
17476
17566
|
log('\n[ollama 호출 중...]');
|
|
17477
17567
|
r = await _ollamaChat(finalPrompt);
|
|
17478
|
-
} else if (['claude', 'codex', '
|
|
17568
|
+
} else if (['claude', 'codex', 'agy', 'copilot'].includes(provider)) {
|
|
17479
17569
|
log(`\n[${provider} CLI 호출 중...]`);
|
|
17480
17570
|
r = await _cliChat(root, provider, finalPrompt, { timeout: 90000 });
|
|
17481
17571
|
if (r.ok && !r.model) r.model = provider; // _cliChat 결과 보강
|
|
17482
17572
|
} else {
|
|
17483
|
-
fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/
|
|
17573
|
+
fail(`알 수 없는 provider: ${provider} (ollama/claude/codex/agy/copilot)`);
|
|
17484
17574
|
process.exitCode = 1;
|
|
17485
17575
|
return;
|
|
17486
17576
|
}
|
|
@@ -18049,7 +18139,7 @@ function healthCmd(root) {
|
|
|
18049
18139
|
const hasRepl = /async function _agentRepl/.test(harnessSrc);
|
|
18050
18140
|
const hasCliChat = /async function _cliChat/.test(harnessSrc);
|
|
18051
18141
|
cap.replMultiProvider = (hasRepl && hasCliChat)
|
|
18052
|
-
? { score: 90, status: '✓', evidence: 'ollama/claude/codex/
|
|
18142
|
+
? { score: 90, status: '✓', evidence: 'ollama/claude/codex/agy/copilot 5종 (1.9.149+1.9.153)' }
|
|
18053
18143
|
: { score: 30, status: '⚠', evidence: 'REPL 미완성' };
|
|
18054
18144
|
// (5) MCP 도구 — tools array 카운트
|
|
18055
18145
|
const toolsMatch = harnessSrc.match(/{ name: 'leerness_/g);
|
|
@@ -19258,7 +19348,7 @@ function whichCmd() {
|
|
|
19258
19348
|
}
|
|
19259
19349
|
|
|
19260
19350
|
function help() {
|
|
19261
|
-
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/
|
|
19351
|
+
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
|
|
19262
19352
|
leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
|
|
19263
19353
|
leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
|
|
19264
19354
|
leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
|
package/package.json
CHANGED
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', '
|
|
609
|
-
const okReg = r.status === 0 && /등록됨 \(in-progress\) by
|
|
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.*
|
|
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',
|
|
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
|
-
&& /\|
|
|
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',
|
|
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/
|
|
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',
|
|
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
|
-
&& /\|
|
|
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 시
|
|
1756
|
-
const env = { ...process.env,
|
|
1757
|
-
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '코드 분석해서 요약', '--to', '
|
|
1758
|
-
//
|
|
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:
|
|
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,
|
|
1781
|
-
const r = cp.spawnSync(process.execPath, [CLI, 'agents', 'dispatch', '번역해줘 한국어를 영어로', '--to', '
|
|
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',
|
|
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 거부 실패`);
|