leerness 1.9.151 → 1.9.153
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 +78 -0
- package/README.md +2 -2
- package/bin/harness.js +192 -18
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,83 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 1.9.153 — 2026-05-20
|
|
4
|
+
|
|
5
|
+
**`.env` 직접 생성/마이그레이션 + REPL 배너 leerness 고유 문구 + multi-provider REPL (사용자 명시 3종).**
|
|
6
|
+
|
|
7
|
+
자율 모드 83 라운드.
|
|
8
|
+
|
|
9
|
+
### Added — install 흐름에서 `.env` 직접 생성/마이그레이션 (사용자 명시)
|
|
10
|
+
- 기존 `.env.example` 만 작성 → **이제 `.env` 도 직접 생성** (보안 = 빈 키만)
|
|
11
|
+
- `mergeEnvFile()` — KEY 기준 처리:
|
|
12
|
+
- 기존 키 (사용자가 채운 값 포함) **절대 덮어쓰지 않음**
|
|
13
|
+
- 누락된 키만 빈 값으로 추가
|
|
14
|
+
- 주석/빈 줄은 substring 미포함 시만 append
|
|
15
|
+
- `.gitignore` 에 `.env` 자동 등록 (1.9.71/75 audit 검증과 통합)
|
|
16
|
+
- `.env.example` 은 계속 생성 (참조 템플릿)
|
|
17
|
+
|
|
18
|
+
### Changed — REPL 배너 leerness 고유 문구 (사용자 명시)
|
|
19
|
+
- 기존: `Hermes / OpenClaw / OpenCode 스타일 + Sandbox`
|
|
20
|
+
- 변경: `검수·기억·샌드박스 통합 자율 AI 에이전트`
|
|
21
|
+
- agent 사용법 (non-TTY) 헤더도 동일 — leerness 자체 정체성 강화
|
|
22
|
+
|
|
23
|
+
### Added — REPL multi-provider 세션 관리 (사용자 명시)
|
|
24
|
+
- 기존: Ollama 전용 채팅
|
|
25
|
+
- 변경: **ollama / claude / codex / gemini / copilot** 5종 세션 관리
|
|
26
|
+
- `_cliChat(root, provider, prompt, opts)` — 외부 CLI 호출 헬퍼
|
|
27
|
+
- 각 CLI 별 비-인터랙티브 호출 인자 자동 매핑
|
|
28
|
+
- `runCommandSafe` 경유 (env scrub + permissions + observability 자동)
|
|
29
|
+
- 활성 (`_checkAgent` ready) 확인 후 실행 — 비활성 시 friendly 에러
|
|
30
|
+
- REPL 진입 시:
|
|
31
|
+
- `.env` 자동 로드 (LEERNESS_ENABLE_* 즉시 반영)
|
|
32
|
+
- 활성 CLI 단일 → 자동 선택 / 복수 → 사용자 번호 선택 / 0개 → Ollama fallback
|
|
33
|
+
- `:provider <p>` 메타 명령으로 세션 중 전환 가능
|
|
34
|
+
- `:role <r>` 와 조합하여 planner=claude / actor=codex 같은 multi-CLI 워크플로 가능
|
|
35
|
+
|
|
36
|
+
### Security
|
|
37
|
+
- `.env` 가 `.gitignore` 에 등록 (실제 시크릿 누출 방지)
|
|
38
|
+
- `_cliChat` 가 `runCommandSafe` 경유 → env scrub 화이트리스트만 자식 프로세스에 전파
|
|
39
|
+
- `.harness/runs/run-*.jsonl` 에 `kind: 'agent_repl_cli'` 로 모든 외부 CLI 호출 기록
|
|
40
|
+
|
|
41
|
+
### Verified
|
|
42
|
+
- e2e 217/217 ✓
|
|
43
|
+
- stress-v98: 23/23 (env 생성 7종 + REPL 배너 3종 + multi-provider 6종 + 누적 회귀 7종)
|
|
44
|
+
- VERSION = 1.9.153 / autonomous-rounds = 83
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## 1.9.152 — 2026-05-20
|
|
49
|
+
|
|
50
|
+
**`agents multi` — 활성 N개 에이전트 일괄 dispatch + handoff 헤드라인 활성 에이전트 카운트 (1.9.151 복수 선택 후속).**
|
|
51
|
+
|
|
52
|
+
자율 모드 82 라운드. 1.9.151 install 복수 선택의 자연스러운 후속 — 실제 사용 시점에 선택된 에이전트들을 동시에 활용.
|
|
53
|
+
|
|
54
|
+
### Added — `leerness agents multi "<task>"` 신규 명령
|
|
55
|
+
- 활성 (ready) 에이전트들에 **일괄 dispatch 명령** 자동 생성 (claude/codex/gemini/copilot/ollama)
|
|
56
|
+
- `--only c1,c2` — 활성 중에서 추가 필터링
|
|
57
|
+
- `--write` — 파일 수정 권한 플래그 (각 CLI 별 적절한 옵션 자동 적용)
|
|
58
|
+
- `--json` — 구조화 출력 (`{ task, count, agents, commands }`)
|
|
59
|
+
- 메인 에이전트가 N개 sub-agent로 spawn 후 결과 합의/투표 → 가장 안정적인 답 선택
|
|
60
|
+
|
|
61
|
+
### Added — `agents dispatch --multi` / `--to all` alias routing
|
|
62
|
+
- 기존 `dispatch --to <id>` 와 동일한 인터페이스로 multi 모드 진입 가능
|
|
63
|
+
- `--to all` 또는 `--to *` 또는 `--multi` 플래그로 자동 routing
|
|
64
|
+
|
|
65
|
+
### Added — `_dispatchCommand(agentId, task, writeMode)` 공유 헬퍼
|
|
66
|
+
- single dispatch + multi dispatch 가 동일한 명령 빌더 사용 — 일관성 보장
|
|
67
|
+
- 5종 CLI 모두 지원 (claude/codex/gemini/copilot/ollama)
|
|
68
|
+
|
|
69
|
+
### Added — handoff 헤드라인 8번째 항목 — 활성 에이전트 카운트
|
|
70
|
+
- `🤖 agents N (claude,codex,...)` — 메인 에이전트가 매 세션 즉시 sub-agent 분배 가능성 인지
|
|
71
|
+
- 활성 0개면 표시 안 함 (노이즈 최소화)
|
|
72
|
+
- 1.9.151 복수 선택 결과가 .env 활성화 → 즉시 헤드라인 반영
|
|
73
|
+
|
|
74
|
+
### Verified
|
|
75
|
+
- e2e 217/217 ✓
|
|
76
|
+
- stress-v97: 18/18 (agents multi 6종 + handoff 헤드라인 3종 + help/fail-fast 2종 + 누적 회귀 7종)
|
|
77
|
+
- VERSION = 1.9.152 / autonomous-rounds = 82
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
3
81
|
## 1.9.151 — 2026-05-20
|
|
4
82
|
|
|
5
83
|
**install 흐름 — CLI 에이전트 복수 선택 + REPL 자동 시작 prompt + viewwork 제거 + help 검증 (사용자 명시 3종).**
|
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,7 +12,7 @@
|
|
|
12
12
|
║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
|
|
13
13
|
║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
|
|
14
14
|
║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
|
|
15
|
-
║ v1.9.
|
|
15
|
+
║ v1.9.153 AI Agent Reliability Harness + Sandbox ║
|
|
16
16
|
║ verify · remember · orchestrate · audit · sandbox · drift ║
|
|
17
17
|
╚══════════════════════════════════════════════════════════════╝
|
|
18
18
|
```
|
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.
|
|
9
|
+
const VERSION = '1.9.153';
|
|
10
10
|
const MARK = '<!-- leerness:managed -->';
|
|
11
11
|
const README_START = '<!-- leerness:project-readme:start -->';
|
|
12
12
|
const README_END = '<!-- leerness:project-readme:end -->';
|
|
@@ -579,6 +579,31 @@ function mergeLinesFile(p, lines) {
|
|
|
579
579
|
writeUtf8(p, next);
|
|
580
580
|
}
|
|
581
581
|
|
|
582
|
+
// 1.9.153: env 파일 전용 key-aware merge — KEY=VALUE 줄을 키 기준 처리 (기존 값 보존, 빈 키만 추가)
|
|
583
|
+
// 사용자가 .env 의 LEERNESS_NPM_TOKEN=abc123 처럼 직접 편집한 값을 절대 덮어쓰지 않음.
|
|
584
|
+
// 주석 / 빈 줄은 substring includes 로 중복 방지 (mergeLinesFile 와 동일).
|
|
585
|
+
function mergeEnvFile(p, lines) {
|
|
586
|
+
const current = exists(p) ? read(p) : '';
|
|
587
|
+
const existingKeys = new Set();
|
|
588
|
+
for (const ln of current.split(/\r?\n/)) {
|
|
589
|
+
const m = ln.match(/^\s*([A-Z][A-Z0-9_]+)\s*=/);
|
|
590
|
+
if (m) existingKeys.add(m[1]);
|
|
591
|
+
}
|
|
592
|
+
let next = current;
|
|
593
|
+
for (const line of lines) {
|
|
594
|
+
const km = line.match(/^\s*([A-Z][A-Z0-9_]+)\s*=/);
|
|
595
|
+
if (km) {
|
|
596
|
+
if (existingKeys.has(km[1])) continue; // 기존 키 값 보존 (덮어쓰기 X)
|
|
597
|
+
next += (next.endsWith('\n') || !next ? '' : '\n') + line + '\n';
|
|
598
|
+
existingKeys.add(km[1]);
|
|
599
|
+
} else {
|
|
600
|
+
// 주석 또는 빈 줄 — substring 미포함 시만 append
|
|
601
|
+
if (!next.includes(line)) next += (next.endsWith('\n') || !next ? '' : '\n') + line + '\n';
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
writeUtf8(p, next);
|
|
605
|
+
}
|
|
606
|
+
|
|
582
607
|
function writeMigrationReport(root, backup, actions, opts = {}) {
|
|
583
608
|
const p = path.join(root, '.harness/migration-report.md');
|
|
584
609
|
const rows = actions.map(a => `| ${a.file} | ${a.action} |`).join('\n');
|
|
@@ -820,7 +845,10 @@ async function install(root, opts = {}) {
|
|
|
820
845
|
}
|
|
821
846
|
if (!opts.dry) {
|
|
822
847
|
mergeLinesFile(path.join(root, '.gitignore'), [
|
|
823
|
-
|
|
848
|
+
// 1.9.153: .env 직접 생성 + 사용자 글로벌 룰 SECRET_PATTERNS 6종 일괄 ignore (audit 통합)
|
|
849
|
+
// audit 가 검사하는 6 패턴: .env / .env.local / .env.production / .env.*.local / *.pem / credentials.json
|
|
850
|
+
'.env', '.env.local', '.env.production', '.env.*.local', '*.pem', 'credentials.json',
|
|
851
|
+
'.harness/skill-publish.local.json','.harness/**/*.local.json',
|
|
824
852
|
'.harness/archive/','.harness/migration-report.md','.harness/cache/',
|
|
825
853
|
// 1.9.147: 자동 유지보수 — 자격증명 + incident 페이로드 비공개 (보안)
|
|
826
854
|
'.harness/credentials.local.json','.harness/incidents/',
|
|
@@ -836,8 +864,12 @@ async function install(root, opts = {}) {
|
|
|
836
864
|
return new Set([a]); // back-compat: 단일 문자열
|
|
837
865
|
})();
|
|
838
866
|
const enable = (cli) => enabledSet.has(cli);
|
|
839
|
-
|
|
840
|
-
|
|
867
|
+
// 1.9.153: .env.example 은 템플릿 (배포 가능, 실제 시크릿 값 없음)
|
|
868
|
+
// .env 는 실 사용 파일 — 사용자가 토큰 채워 넣음. 보안 정책: 토큰 값은 절대 자동 채우지 않음 (키만).
|
|
869
|
+
// .gitignore 에 .env 가 들어가 있어야 함 (audit 가 자동 검증). mergeLinesFile 은 기존 키 유지 + 신규 추가.
|
|
870
|
+
const envLines = [
|
|
871
|
+
'# Leerness — environment variable names only. Do not commit real secrets (this file is in .gitignore).',
|
|
872
|
+
`# Generated/migrated by leerness v${VERSION} at ${new Date().toISOString().slice(0, 10)}.`,
|
|
841
873
|
'LEERNESS_NPM_TOKEN=','LEERNESS_GITHUB_TOKEN=',
|
|
842
874
|
'# 1.9.22 — orchestrate opt-in. URL이 설정되면 leerness가 Ollama를 사용 가능. 미설정 시 LLM 호출 자동 시작 금지.',
|
|
843
875
|
`LEERNESS_OLLAMA_BASE_URL=${enable('ollama') ? 'http://localhost:11434' : ''}`,
|
|
@@ -850,11 +882,22 @@ async function install(root, opts = {}) {
|
|
|
850
882
|
`LEERNESS_ENABLE_COPILOT=${enable('copilot') ? 1 : 0}`,
|
|
851
883
|
`LEERNESS_ENABLE_OLLAMA=${enable('ollama') ? 1 : 0}`,
|
|
852
884
|
'# 1.9.42 — agentskills.io 공개 표준 스킬 자동 탐색 (opt-in). URL 설정 시 `leerness skill discover` 사용 가능.',
|
|
853
|
-
'#
|
|
885
|
+
'# 예시 URL: https://agentskills.io/llms.txt',
|
|
854
886
|
'LEERNESS_SKILL_DISCOVER_URL=',
|
|
855
887
|
'# (선택) 사용자 요청 분석 시 자동 매칭 스킬 추천. 1=활성, 0/미설정=비활성.',
|
|
856
888
|
'LEERNESS_SKILL_AUTO_DISCOVER=0'
|
|
857
|
-
]
|
|
889
|
+
];
|
|
890
|
+
mergeLinesFile(path.join(root, '.env.example'), envLines);
|
|
891
|
+
// 1.9.153: .env 직접 생성/마이그레이션 (사용자 명시 요청). 보안 = 빈 값만 — 사용자가 직접 토큰 채움.
|
|
892
|
+
// 기존 .env 가 있으면 mergeEnvFile 이 KEY 기준 처리:
|
|
893
|
+
// - 기존 키 (사용자가 채운 값 포함) 는 절대 덮어쓰지 않음
|
|
894
|
+
// - 누락된 키만 빈 값으로 추가
|
|
895
|
+
// .env 가 .gitignore 에 등록되어 있는지 audit 가 검증 (1.9.75+).
|
|
896
|
+
try {
|
|
897
|
+
mergeEnvFile(path.join(root, '.env'), envLines);
|
|
898
|
+
} catch (e) {
|
|
899
|
+
warn(`.env 생성/마이그레이션 실패 (계속 진행): ${e.message}`);
|
|
900
|
+
}
|
|
858
901
|
// 1.9.146: agent 권한 파일 자동 생성 (사용자 명시 요청 #5)
|
|
859
902
|
if (resolved.permissionMode) {
|
|
860
903
|
try { _writePermissionsPreset(root, resolved.permissionMode); } catch (e) { warn('permissions 생성 실패: ' + e.message); }
|
|
@@ -2914,10 +2957,18 @@ function handoff(root) {
|
|
|
2914
2957
|
const lessons = exists(lessonsPath(root)) ? (read(lessonsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0;
|
|
2915
2958
|
parts.push(`🧠 mem T${inProgressTasks}/D${decisions}/R${rulesActive}/P${planMilestones}/L${lessons}`);
|
|
2916
2959
|
} catch {}
|
|
2960
|
+
// 8) 1.9.152: 활성 외부 AI CLI 카운트 (1.9.151 복수 선택 결과 반영) — 메인 에이전트가 sub-agent 분배 가능성 즉시 인지
|
|
2961
|
+
try {
|
|
2962
|
+
_loadEnvFile(root); // .env 자동 로드 (handoff 컨텍스트)
|
|
2963
|
+
const ready = EXTERNAL_AGENTS.map(a => _checkAgent(a)).filter(c => c.status === 'ready');
|
|
2964
|
+
if (ready.length > 0) {
|
|
2965
|
+
parts.push(`🤖 agents ${ready.length} (${ready.map(c => c.id).join(',')})`);
|
|
2966
|
+
}
|
|
2967
|
+
} catch {}
|
|
2917
2968
|
if (parts.length) {
|
|
2918
2969
|
const isTty = process.stdout && process.stdout.isTTY;
|
|
2919
2970
|
const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
|
|
2920
|
-
log(cy(`📊 헤드라인 (1.9.81/93/113): ${parts.join(' · ')}`));
|
|
2971
|
+
log(cy(`📊 헤드라인 (1.9.81/93/113/152): ${parts.join(' · ')}`));
|
|
2921
2972
|
}
|
|
2922
2973
|
} catch {}
|
|
2923
2974
|
}
|
|
@@ -5016,6 +5067,17 @@ async function setupAgentsCmd(root, opts = {}) {
|
|
|
5016
5067
|
log(' 다음: leerness agents list / leerness agents quota');
|
|
5017
5068
|
}
|
|
5018
5069
|
|
|
5070
|
+
// 1.9.152: 단일 agent dispatch 명령 빌더 — agents dispatch (단일) + agents multi (복수) 가 공유
|
|
5071
|
+
function _dispatchCommand(agentId, task, writeMode) {
|
|
5072
|
+
const q = String(task || '').replace(/"/g, '\\"');
|
|
5073
|
+
if (agentId === 'claude') return `claude ${writeMode ? '--print --dangerously-skip-permissions' : '--print'} "${q}"`;
|
|
5074
|
+
if (agentId === 'codex') return `codex ${writeMode ? 'exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox' : 'exec --skip-git-repo-check'} "${q}"`;
|
|
5075
|
+
if (agentId === 'gemini') return `gemini ${writeMode ? '-p --yolo' : '-p'} "${q}"`;
|
|
5076
|
+
if (agentId === 'copilot') return `gh copilot suggest "${q}"`;
|
|
5077
|
+
if (agentId === 'ollama') return `# ollama — leerness agent "${q}" --provider ollama 로 직접 호출 (REPL: leerness agent)`;
|
|
5078
|
+
return `# ${agentId}: 명령 빌더 미정의`;
|
|
5079
|
+
}
|
|
5080
|
+
|
|
5019
5081
|
function agentsCmd(root, sub, ...args) {
|
|
5020
5082
|
root = absRoot(root || process.cwd());
|
|
5021
5083
|
// .env 자동 로드 (1.9.22)
|
|
@@ -5059,11 +5121,61 @@ function agentsCmd(root, sub, ...args) {
|
|
|
5059
5121
|
return agentsCmd(root, 'list'); // 비-JSON은 list와 동일
|
|
5060
5122
|
}
|
|
5061
5123
|
|
|
5124
|
+
// 1.9.152: agents multi — 1.9.151 install 복수 선택된 ready 에이전트들에 일괄 dispatch 명령 생성
|
|
5125
|
+
// 단일 task → 활성 N개 에이전트 동시 dispatch 명령들. 사용자가 한 번에 복사 실행하거나 메인 에이전트가 spawn.
|
|
5126
|
+
if (sub === 'multi') {
|
|
5127
|
+
const task = args.filter(x => !x.startsWith('-')).join(' ').trim() || arg('--task', null);
|
|
5128
|
+
if (!task) { fail('multi "<task>" 또는 --task 필요'); return process.exit(1); }
|
|
5129
|
+
const onlyArg = arg('--only', null); // 'claude,codex' 처럼 콤마 구분 — 활성 중에서 추가 필터
|
|
5130
|
+
const writeMode = has('--write');
|
|
5131
|
+
const checks = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }));
|
|
5132
|
+
let ready = checks.filter(x => x.status.status === 'ready');
|
|
5133
|
+
if (onlyArg) {
|
|
5134
|
+
const wanted = new Set(onlyArg.split(/[,\s]+/).filter(Boolean));
|
|
5135
|
+
ready = ready.filter(x => wanted.has(x.def.id));
|
|
5136
|
+
}
|
|
5137
|
+
if (!ready.length) {
|
|
5138
|
+
fail('활성 (ready) 에이전트 없음 — `leerness agents list` 로 확인. 1.9.151 install 흐름에서 복수 선택 후 .env 활성화 필요.');
|
|
5139
|
+
return process.exit(1);
|
|
5140
|
+
}
|
|
5141
|
+
if (has('--json')) {
|
|
5142
|
+
log(JSON.stringify({
|
|
5143
|
+
task, count: ready.length,
|
|
5144
|
+
agents: ready.map(x => ({ id: x.def.id, version: x.status.version })),
|
|
5145
|
+
commands: ready.map(x => _dispatchCommand(x.def.id, task, writeMode))
|
|
5146
|
+
}, null, 2));
|
|
5147
|
+
return;
|
|
5148
|
+
}
|
|
5149
|
+
log(`# leerness agents multi (1.9.152) — ${ready.length}개 활성 에이전트 일괄 dispatch`);
|
|
5150
|
+
log(`task: ${task.slice(0, 120)}${task.length > 120 ? '…' : ''}`);
|
|
5151
|
+
log(`mode: ${writeMode ? '✏ write (파일 수정 가능)' : '🔒 read-only (분석 전용, 안전)'}`);
|
|
5152
|
+
log(`대상: ${ready.map(x => x.def.id).join(', ')}`);
|
|
5153
|
+
log('');
|
|
5154
|
+
log('## 각 에이전트 실행 명령 (사용자가 병렬 실행 또는 메인 에이전트가 spawn)');
|
|
5155
|
+
log('');
|
|
5156
|
+
for (const { def, status } of ready) {
|
|
5157
|
+
log(`### [${def.id}] (v${status.version || '?'})`);
|
|
5158
|
+
log('```sh');
|
|
5159
|
+
log(_dispatchCommand(def.id, task, writeMode));
|
|
5160
|
+
log('```');
|
|
5161
|
+
log('');
|
|
5162
|
+
}
|
|
5163
|
+
log('## 정책 (1.9.152)');
|
|
5164
|
+
log(` - leerness는 외부 CLI를 자동 호출하지 않음 (사용자/메인 에이전트가 명시적으로 실행)`);
|
|
5165
|
+
log(` - 메인 에이전트(Claude)가 위 ${ready.length}개 명령을 보고 ${ready.length}개 sub-agent로 spawn — 결과 합의/투표로 가장 안정적인 답 선택`);
|
|
5166
|
+
log(` - 활성 에이전트 변경: \`.env\`에서 LEERNESS_ENABLE_<CLI>=1/0 또는 \`leerness setup-agents\` 재실행`);
|
|
5167
|
+
log(` - quota 체크: \`leerness agents quota\``);
|
|
5168
|
+
return;
|
|
5169
|
+
}
|
|
5062
5170
|
if (sub === 'dispatch') {
|
|
5063
5171
|
const task = args.filter(x => !x.startsWith('-')).join(' ').trim() || arg('--task', null);
|
|
5064
5172
|
const target = arg('--to', null);
|
|
5065
5173
|
if (!task) { fail('dispatch "<task>" 또는 --task 필요'); return process.exit(1); }
|
|
5066
|
-
|
|
5174
|
+
// 1.9.152: --multi 또는 --to=all 또는 --to 없음 + 활성 ≥2 → multi 모드로 routing
|
|
5175
|
+
if (has('--multi') || target === 'all' || target === '*') {
|
|
5176
|
+
return agentsCmd(root, 'multi', ...args);
|
|
5177
|
+
}
|
|
5178
|
+
if (!target) { fail('--to <agent_id> 필요 (claude/codex/gemini/copilot) — 활성 전체에 일괄 분배는 `leerness agents multi`'); return process.exit(1); }
|
|
5067
5179
|
const agentDef = EXTERNAL_AGENTS.find(a => a.id === target);
|
|
5068
5180
|
if (!agentDef) { fail(`알 수 없는 agent: ${target}`); return process.exit(1); }
|
|
5069
5181
|
// 1.9.36: 작업 유형 키워드 분석 → 최적 CLI 추천 (ready 체크 전에 출력 — 비활성이어도 추천)
|
|
@@ -10004,6 +10116,41 @@ async function _ollamaListModels() {
|
|
|
10004
10116
|
});
|
|
10005
10117
|
}
|
|
10006
10118
|
|
|
10119
|
+
// 1.9.153: 외부 CLI 채팅 호출 (multi-provider REPL — 사용자 명시 요청)
|
|
10120
|
+
// claude/codex/gemini/copilot 를 child_process 로 호출 후 stdout 캡처.
|
|
10121
|
+
// runCommandSafe 경유 — env scrub + permissions + observability 자동 적용.
|
|
10122
|
+
async function _cliChat(root, provider, prompt, opts) {
|
|
10123
|
+
opts = opts || {};
|
|
10124
|
+
const agent = EXTERNAL_AGENTS.find(a => a.id === provider);
|
|
10125
|
+
if (!agent) return { ok: false, error: `unknown provider: ${provider}`, provider };
|
|
10126
|
+
const status = _checkAgent(agent);
|
|
10127
|
+
if (status.status !== 'ready') {
|
|
10128
|
+
return { ok: false, error: `${provider} 비활성 (${status.status}) — .env 에서 ${agent.envFlag}=1 + CLI 설치 필요`, provider };
|
|
10129
|
+
}
|
|
10130
|
+
// CLI 별 비-인터랙티브 호출 인자 매핑 (read-only 모드 — REPL 안에서 파일 수정 X)
|
|
10131
|
+
let cmd, args;
|
|
10132
|
+
if (provider === 'claude') { cmd = 'claude'; args = ['--print', prompt]; }
|
|
10133
|
+
else if (provider === 'codex') { cmd = 'codex'; args = ['exec', '--skip-git-repo-check', prompt]; }
|
|
10134
|
+
else if (provider === 'gemini') { cmd = 'gemini'; args = ['-p', prompt]; }
|
|
10135
|
+
else if (provider === 'copilot') { cmd = 'gh'; args = ['copilot', 'suggest', prompt]; }
|
|
10136
|
+
else return { ok: false, error: `provider ${provider} 미지원`, provider };
|
|
10137
|
+
// runCommandSafe — env scrub + observability 자동
|
|
10138
|
+
const r = runCommandSafe(cmd, args, {
|
|
10139
|
+
cwd: process.cwd(), root,
|
|
10140
|
+
timeout: opts.timeout || 60000,
|
|
10141
|
+
allowOutsideCwd: true, // CLI 가 cwd 밖에서 실행될 수 있음
|
|
10142
|
+
kind: 'agent_repl_cli', label: `repl-${provider}`
|
|
10143
|
+
});
|
|
10144
|
+
if (r.status === 0) {
|
|
10145
|
+
return { ok: true, response: (r.stdout || '').trim(), provider, model: provider };
|
|
10146
|
+
}
|
|
10147
|
+
return {
|
|
10148
|
+
ok: false,
|
|
10149
|
+
error: `exit=${r.status} ${(r.stderr || r.stdout || '').slice(0, 200)}`,
|
|
10150
|
+
provider
|
|
10151
|
+
};
|
|
10152
|
+
}
|
|
10153
|
+
|
|
10007
10154
|
// 1.9.149: observability lite — 모든 agent 호출의 traceId + duration + exit + failureCause 기록
|
|
10008
10155
|
function _runsDir(root) { return path.join(absRoot(root), '.harness', 'runs'); }
|
|
10009
10156
|
function _recordRun(root, entry) {
|
|
@@ -10159,8 +10306,10 @@ const _AGENT_ROLE_PROMPTS = {
|
|
|
10159
10306
|
reviewer: '역할: reviewer. planner 의 계획 또는 actor 의 결과를 비판적으로 검토. 누락된 검증, 잠재 cascade, 오류 가능성 지적. 동의/수정 결론 명시.',
|
|
10160
10307
|
actor: '역할: actor. 계획에 따라 정확한 명령/코드만 실행. evidence(파일 경로 + 테스트 결과) 함께 기록. 새 계획 생성 금지.'
|
|
10161
10308
|
};
|
|
10162
|
-
// 1.9.149: REPL 모드 —
|
|
10309
|
+
// 1.9.149+1.9.153: REPL 모드 — leerness 자율 AI 에이전트 (multi-provider 세션)
|
|
10163
10310
|
async function _agentRepl(root, opts) {
|
|
10311
|
+
// 1.9.153: .env 자동 로드 (REPL 진입 직전) — install 직후 LEERNESS_ENABLE_* 즉시 반영
|
|
10312
|
+
try { _loadEnvFile(root); } catch {}
|
|
10164
10313
|
const readline = require('readline');
|
|
10165
10314
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
10166
10315
|
const isTty = process.stdout.isTTY;
|
|
@@ -10169,9 +10318,28 @@ async function _agentRepl(root, opts) {
|
|
|
10169
10318
|
bold: s => `\x1b[1m${s}\x1b[0m`, green: s => `\x1b[32m${s}\x1b[0m`,
|
|
10170
10319
|
yel: s => `\x1b[33m${s}\x1b[0m`, mag: s => `\x1b[35m${s}\x1b[0m`
|
|
10171
10320
|
} : { cy:s=>s, dim:s=>s, bold:s=>s, green:s=>s, yel:s=>s, mag:s=>s };
|
|
10321
|
+
// 1.9.153: provider 자동 선택 — opts.provider 명시 안 됨 + 활성 CLI 가 있으면 사용자에게 선택지 표시
|
|
10322
|
+
let initialProvider = opts.provider;
|
|
10323
|
+
if (!initialProvider) {
|
|
10324
|
+
const ready = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }))
|
|
10325
|
+
.filter(x => x.status.status === 'ready');
|
|
10326
|
+
if (ready.length === 1) {
|
|
10327
|
+
initialProvider = ready[0].def.id; // 단일 활성 → 자동 선택
|
|
10328
|
+
} else if (ready.length > 1 && isTty) {
|
|
10329
|
+
// 복수 활성 → 사용자에게 선택지 (Ollama 우선이 아닌, 활성된 CLI 중 선택)
|
|
10330
|
+
console.log('');
|
|
10331
|
+
console.log(` 사용 가능한 CLI 에이전트 ${ready.length}개:`);
|
|
10332
|
+
ready.forEach((x, i) => console.log(` ${i + 1}) ${x.def.id}${x.status.version ? ' (v' + x.status.version + ')' : ''}`));
|
|
10333
|
+
const choice = await new Promise(res => rl.question(`\n provider 선택 (Enter=1): `, res));
|
|
10334
|
+
const idx = parseInt(choice, 10) - 1;
|
|
10335
|
+
initialProvider = (idx >= 0 && idx < ready.length) ? ready[idx].def.id : ready[0].def.id;
|
|
10336
|
+
} else {
|
|
10337
|
+
initialProvider = 'ollama'; // 활성 0개 → fallback (사용 시 friendly 경고)
|
|
10338
|
+
}
|
|
10339
|
+
}
|
|
10172
10340
|
// 세션 state
|
|
10173
10341
|
let state = {
|
|
10174
|
-
provider:
|
|
10342
|
+
provider: initialProvider,
|
|
10175
10343
|
model: opts.model || process.env.LEERNESS_OLLAMA_MODEL || null,
|
|
10176
10344
|
role: opts.role || 'actor',
|
|
10177
10345
|
history: [], // [{role: 'user'|'assistant', content: ''}]
|
|
@@ -10189,8 +10357,8 @@ async function _agentRepl(root, opts) {
|
|
|
10189
10357
|
// 환영 메시지 + 모델 선택
|
|
10190
10358
|
log('');
|
|
10191
10359
|
log(C.bold(C.cy(' ╔════════════════════════════════════════════════════╗')));
|
|
10192
|
-
log(C.bold(C.cy(' ║ leerness agent — REPL mode
|
|
10193
|
-
log(C.bold(C.cy(' ║
|
|
10360
|
+
log(C.bold(C.cy(' ║ leerness agent — REPL mode ║')));
|
|
10361
|
+
log(C.bold(C.cy(' ║ 검수·기억·샌드박스 통합 자율 AI 에이전트 ║')));
|
|
10194
10362
|
log(C.bold(C.cy(' ╚════════════════════════════════════════════════════╝')));
|
|
10195
10363
|
log('');
|
|
10196
10364
|
// Ollama 모델 자동 감지 — model이 명시되지 않았으면 사용자에게 선택지 제공
|
|
@@ -10305,11 +10473,15 @@ async function _agentRepl(root, opts) {
|
|
|
10305
10473
|
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}:`;
|
|
10306
10474
|
const t0 = Date.now();
|
|
10307
10475
|
let result;
|
|
10476
|
+
// 1.9.153: multi-provider REPL — ollama 외 claude/codex/gemini/copilot 도 세션 관리 (사용자 명시)
|
|
10308
10477
|
if (state.provider === 'ollama') {
|
|
10309
|
-
log(C.dim(` → ollama
|
|
10478
|
+
log(C.dim(` → ollama${state.model ? ' (' + state.model + ')' : ''} 호출 중...`));
|
|
10310
10479
|
result = await _ollamaChat(finalPrompt, state.model);
|
|
10480
|
+
} else if (['claude', 'codex', 'gemini', 'copilot'].includes(state.provider)) {
|
|
10481
|
+
log(C.dim(` → ${state.provider} CLI 호출 중...`));
|
|
10482
|
+
result = await _cliChat(root, state.provider, finalPrompt, { timeout: 90000 });
|
|
10311
10483
|
} else {
|
|
10312
|
-
log(C.yel(` ⚠ ${state.provider}
|
|
10484
|
+
log(C.yel(` ⚠ ${state.provider} provider 미지원 — :provider ollama|claude|codex|gemini|copilot`));
|
|
10313
10485
|
rl.prompt(); return;
|
|
10314
10486
|
}
|
|
10315
10487
|
const dt = Date.now() - t0;
|
|
@@ -10346,17 +10518,19 @@ async function agentCmd(root, taskArg) {
|
|
|
10346
10518
|
return;
|
|
10347
10519
|
}
|
|
10348
10520
|
// non-TTY: 사용법만 출력
|
|
10349
|
-
log('# leerness agent
|
|
10521
|
+
log('# leerness agent — 검수·기억·샌드박스 통합 자율 AI 에이전트');
|
|
10350
10522
|
log('');
|
|
10351
10523
|
log('사용법:');
|
|
10352
|
-
log(' leerness agent #
|
|
10524
|
+
log(' leerness agent # REPL 모드 (provider 자동 선택 + 채팅)');
|
|
10353
10525
|
log(' leerness agent "<task>" # 1회 위임 (actor 역할 기본)');
|
|
10354
10526
|
log(' leerness agent "<task>" --role planner # 계획만 (1.9.148)');
|
|
10355
10527
|
log(' leerness agent "<task>" --role reviewer # 비판적 검토 (1.9.148)');
|
|
10356
|
-
log(' leerness agent --
|
|
10528
|
+
log(' leerness agent --provider claude # provider 명시 (ollama/claude/codex/gemini/copilot)');
|
|
10529
|
+
log(' leerness agent --interactive --model qwen2.5-coder # 명시적 REPL + Ollama 모델 선택');
|
|
10357
10530
|
log('');
|
|
10358
10531
|
log('REPL 메타 명령: :help / :model / :role / :provider / :history / :save / :quit');
|
|
10359
10532
|
log('REPL Slash 명령 (1.9.150): :verify / :audit / :handoff / :health (sandboxed runCommandSafe)');
|
|
10533
|
+
log('REPL Multi-provider (1.9.153): ollama / claude / codex / gemini / copilot — 활성 CLI 자동 감지');
|
|
10360
10534
|
log('');
|
|
10361
10535
|
log('현재 활성 provider: ' + (_activeCliAgents().join(', ') || '(없음) — .env에서 LEERNESS_ENABLE_* 활성화'));
|
|
10362
10536
|
log('권한 모드: ' + (_readPermissions(root).mode || 'basic'));
|
|
@@ -11151,7 +11325,7 @@ function reuseAutodetectCmd(root) {
|
|
|
11151
11325
|
}
|
|
11152
11326
|
|
|
11153
11327
|
function help() {
|
|
11154
|
-
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 init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\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
|
|
11328
|
+
log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] # 1.9.152 활성 N개 에이전트 일괄 dispatch 명령 (복수 선택 후속)\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 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
|
|
11155
11329
|
leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
|
|
11156
11330
|
leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
|
|
11157
11331
|
leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
|