leerness 1.9.155 → 1.9.157

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,96 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.157 — 2026-05-20
4
+
5
+ **Provider Registry CLI MVP — 사용자 정의 provider 동적 추가 (점검 보고서 권고 #3 — Provider Registry MCP의 CLI 단계).**
6
+
7
+ 자율 모드 87 라운드. 1.9.155 점검 보고서가 발견한 "모델/프로바이더 폭 5종 vs 200+" gap 보강의 첫 단계.
8
+
9
+ ### Added — `leerness provider list|add|remove` 신규 명령
10
+ - 빌트인 5종 (claude/codex/gemini/copilot/ollama) + **사용자 정의 provider 동적 추가**
11
+ - `.harness/providers.json` — 사용자 정의 저장 (schemaVersion 1)
12
+ - `_allProviders(root)` — 빌트인 + 사용자 정의 merge
13
+ - 같은 id 의 user override 적용 → 빌트인 설정 변경 가능
14
+ - 빌트인에 없는 user-only provider 추가
15
+
16
+ ### CLI
17
+ ```bash
18
+ leerness provider list # 빌트인 + 사용자 정의 통합 표시
19
+ leerness provider list --json # 구조화 출력 (total/builtin/user/providers)
20
+ leerness provider add openrouter --bin openrouter-cli --desc "OpenRouter aggregator"
21
+ leerness provider add bedrock --bin aws-bedrock-cli --env-flag LEERNESS_ENABLE_BEDROCK
22
+ leerness provider remove openrouter # 사용자 정의만 제거 가능 (빌트인 거부)
23
+ ```
24
+
25
+ ### Changed — `agents list/check` Provider Registry 통합
26
+ - 표 출력에 `source` 컬럼 추가 (builtin / user / user(override))
27
+ - JSON 응답에 `source` 필드 추가
28
+ - 활성 없을 때 "1.9.157: 빌트인 외 CLI 추가" hint 표시
29
+ - 헤더 `(1.9.30)` 유지 — 기존 e2e regex 호환
30
+
31
+ ### Use Cases
32
+ - **OpenRouter 200+ 모델 흡수**: `provider add openrouter --bin openrouter-cli` 후 `LEERNESS_ENABLE_OPENROUTER=1` 설정
33
+ - **AWS Bedrock 통합**: `provider add bedrock --bin aws-bedrock-cli`
34
+ - **Groq / Hugging Face / 자체 LLM CLI**: 모두 동일 패턴
35
+ - **자동 발견**: 1.9.158 에서 OpenRouter llms.txt 자동 동기화 예정
36
+
37
+ ### Verified — 5능력 매트릭스 갱신
38
+ | 영역 | 1.9.156 | 1.9.157 |
39
+ |---|---|---|
40
+ | Provider 폭 | 5종 고정 | **무제한 (동적 등록)** |
41
+ | 종합 완성도 | 60% | **62%** |
42
+
43
+ ### Pending — 보고서 권고 남은 후보
44
+ - **1.9.158** — `leerness provider sync` (OpenRouter llms.txt 자동 동기화) + MCP `leerness_provider_list` (48번째 도구)
45
+ - **1.9.159** — LSP 어댑터 MVP (TypeScript LSP)
46
+ - **1.9.160** — playwright/computer-use bridge (`permissions.browser/mouse` 실 동작)
47
+
48
+ ### Verified
49
+ - e2e 217/217 ✓
50
+ - stress-v102: 19/19 (Provider Registry 함수 3종 + list 2종 + add/remove 6종 + agents 통합 2종 + 누적 회귀 6종)
51
+ - VERSION = 1.9.157 / autonomous-rounds = 87
52
+
53
+ ---
54
+
55
+ ## 1.9.156 — 2026-05-20
56
+
57
+ **`agents multi --execute` 실제 spawn + consensus 통합 (1.9.155 점검 보고서 발견 gap #1 보강).**
58
+
59
+ 자율 모드 86 라운드. 5능력 점검 보고서가 발견한 **"agents multi가 명령 문자열만 출력 — 실제 spawn 안 함"** 문제를 직접 해결.
60
+
61
+ ### Added — `leerness agents multi "<task>" --execute`
62
+ - 기존 (1.9.152): 활성 N개 에이전트에 dispatch 명령 **문자열만 출력** — 사용자가 실행
63
+ - 신규 (1.9.156): `--execute` 플래그 시 **leerness가 직접 N개 sub-agent 병렬 spawn**
64
+ - `Promise.all` 로 `_cliChat(provider)` 동시 호출 — 진짜 N-way 분배
65
+ - 각 호출은 1.9.150 `runCommandSafe` 경유 → cwd jail + env scrub + 자동 observability
66
+ - `--timeout <s>` 옵션 (기본 60s) — 무한 대기 방지
67
+ - 결과 수집 후 **1.9.155 multi-signal consensus** 자동 적용:
68
+ - `score = 0.4*tokensNorm + 0.4*overlap + 0.2*lengthFit`
69
+ - best 1위 + others 2-4위 점수 표시 (투명성)
70
+ - `--json` 출력: `{ task, count, success, totalElapsedMs, results, best, failures }`
71
+ - `_recordRun` 통합 — kind `agents_multi_execute` + task-log 자동 기록
72
+ - 활성 0개 또는 onlyArg 매칭 0개 → 즉시 fail (실 호출 시도 X)
73
+
74
+ ### Verification — 5능력 점검 매트릭스 갱신
75
+ | 영역 | 1.9.155 | 1.9.156 |
76
+ |---|---|---|
77
+ | 멀티 에이전트 오케스트레이션 | 70% (명령 출력만) | **90%** (실 spawn + consensus 합의) |
78
+ | 종합 완성도 | 55% | **60%** |
79
+
80
+ 이제 leerness 가 "지시 생성기" 가 아닌 **"실 실행 오케스트레이터"** — Hermes Agent / OpenClaw 같은 도구와의 격차가 크게 줄어듦.
81
+
82
+ ### Pending — 보고서 권고 다음 3 후보
83
+ 1. **1.9.157** — LSP 어댑터 MVP (TypeScript LSP 먼저)
84
+ 2. **1.9.158** — Provider Registry MCP 도구 (OpenRouter/Bedrock 100+ 모델 흡수)
85
+ 3. **1.9.159** — playwright/computer-use bridge (`permissions.browser/mouse` 실 동작 — opt-in)
86
+
87
+ ### Verified
88
+ - e2e 217/217 ✓
89
+ - stress-v101: 18/18 (--execute 7종 + CLI 동작 3종 + 누적 회귀 8종)
90
+ - VERSION = 1.9.156 / autonomous-rounds = 86
91
+
92
+ ---
93
+
3
94
  ## 1.9.155 — 2026-05-20
4
95
 
5
96
  **REPL UX 대폭 개선 + provider 모델 카탈로그 + orchestrate consensus 강화 + 5능력 점검 보고서 (사용자 명시).**
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  > **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
4
4
 
5
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.155-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v100-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-47-blue)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-85-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![multi-provider](https://img.shields.io/badge/REPL-5_providers%2Bmodel_catalog-success)]() [![consensus](https://img.shields.io/badge/orchestrate-multi--signal_consensus-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
5
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.157-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v102-19%2F19-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-47-blue)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-87-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![provider-registry](https://img.shields.io/badge/provider_registry-dynamic_add-success)]() [![multi-execute](https://img.shields.io/badge/agents_multi-real_spawn%2Bconsensus-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
6
 
7
7
  ```
8
8
  ╔══════════════════════════════════════════════════════════════╗
@@ -12,7 +12,7 @@
12
12
  ║ ██║ ██╔══╝ ██╔══╝ ██╔══██╗██║╚██╗██║██╔══╝ ╚════██║ ║
13
13
  ║ ███████╗███████╗███████╗██║ ██║██║ ╚████║███████╗███████║ ║
14
14
  ║ ╚══════╝╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝╚══════╝ ║
15
- ║ v1.9.155 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.157 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.155';
9
+ const VERSION = '1.9.157';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -4584,6 +4584,119 @@ const EXTERNAL_AGENTS = [
4584
4584
  installCmd: 'curl -fsSL https://ollama.com/install.sh | sh (또는 https://ollama.com/download)', installHint: 'ollama serve 실행 + ollama pull <model>' }
4585
4585
  ];
4586
4586
 
4587
+ // 1.9.157: Provider Registry — 사용자 정의 provider 동적 추가 (.harness/providers.json)
4588
+ // 빌트인 5종 (EXTERNAL_AGENTS) + 사용자 정의를 merge. OpenRouter / Bedrock / Groq 등 새 CLI 즉시 흡수 가능.
4589
+ // 파일 형식: { "schemaVersion": 1, "providers": [{ id, bin, envFlag, versionArgs, desc, installHint }] }
4590
+ function _providersFile(root) { return path.join(absRoot(root), '.harness', 'providers.json'); }
4591
+ function _readUserProviders(root) {
4592
+ const p = _providersFile(root);
4593
+ if (!exists(p)) return [];
4594
+ try {
4595
+ const j = JSON.parse(read(p));
4596
+ return Array.isArray(j.providers) ? j.providers : [];
4597
+ } catch { return []; }
4598
+ }
4599
+ function _writeUserProviders(root, providers) {
4600
+ const p = _providersFile(root);
4601
+ mkdirp(path.dirname(p));
4602
+ writeUtf8(p, JSON.stringify({ schemaVersion: 1, providers }, null, 2) + '\n');
4603
+ }
4604
+ // 빌트인 + 사용자 정의 merge — 같은 id 가 있으면 user 가 빌트인 override
4605
+ function _allProviders(root) {
4606
+ try {
4607
+ const userList = _readUserProviders(root);
4608
+ const builtinIds = new Set(EXTERNAL_AGENTS.map(a => a.id));
4609
+ const userOverrides = new Map();
4610
+ for (const u of userList) {
4611
+ if (!u || !u.id) continue;
4612
+ // 정상화 — 누락 필드는 빌트인에서 fallback
4613
+ userOverrides.set(u.id, {
4614
+ id: u.id,
4615
+ bin: u.bin || u.id,
4616
+ envFlag: u.envFlag || `LEERNESS_ENABLE_${String(u.id).toUpperCase()}`,
4617
+ versionArgs: Array.isArray(u.versionArgs) ? u.versionArgs : ['--version'],
4618
+ desc: u.desc || `(user) ${u.id}`,
4619
+ installHint: u.installHint || '',
4620
+ installCmd: u.installCmd || ''
4621
+ });
4622
+ }
4623
+ // 빌트인 먼저, user override 적용
4624
+ const merged = EXTERNAL_AGENTS.map(a => userOverrides.has(a.id) ? userOverrides.get(a.id) : a);
4625
+ // 빌트인에 없는 user-only 추가
4626
+ for (const u of userOverrides.values()) {
4627
+ if (!builtinIds.has(u.id)) merged.push(u);
4628
+ }
4629
+ return merged;
4630
+ } catch { return EXTERNAL_AGENTS.slice(); }
4631
+ }
4632
+ function providerCmd(root, sub, ...args) {
4633
+ root = absRoot(root || process.cwd());
4634
+ _loadEnvFile(root);
4635
+ if (!sub || sub === 'list') {
4636
+ const all = _allProviders(root);
4637
+ const userList = _readUserProviders(root);
4638
+ const userIds = new Set(userList.map(u => u.id));
4639
+ if (has('--json')) {
4640
+ log(JSON.stringify({
4641
+ total: all.length,
4642
+ builtin: EXTERNAL_AGENTS.length,
4643
+ user: userList.length,
4644
+ providers: all.map(p => ({ id: p.id, bin: p.bin, envFlag: p.envFlag, source: userIds.has(p.id) ? 'user' : 'builtin', desc: p.desc }))
4645
+ }, null, 2));
4646
+ return;
4647
+ }
4648
+ log(`# leerness provider list (1.9.157)`);
4649
+ log(`총 ${all.length}개 (빌트인 ${EXTERNAL_AGENTS.length} + 사용자 ${userList.length})`);
4650
+ log('');
4651
+ log(`| id | source | bin | envFlag |`);
4652
+ log(`|---|---|---|---|`);
4653
+ for (const p of all) {
4654
+ const src = userIds.has(p.id) ? (EXTERNAL_AGENTS.some(b => b.id === p.id) ? 'user(override)' : 'user') : 'builtin';
4655
+ log(`| ${p.id} | ${src} | ${p.bin} | ${p.envFlag} |`);
4656
+ }
4657
+ if (!userList.length) {
4658
+ log('');
4659
+ log(`💡 사용자 정의 provider 추가: leerness provider add <id> --bin <cmd> [--env-flag F] [--version-args ARGS] [--desc D]`);
4660
+ }
4661
+ return;
4662
+ }
4663
+ if (sub === 'add') {
4664
+ const id = (args[0] || arg('--id', '')).trim();
4665
+ if (!id) return fail('provider add <id> 필요 (예: openrouter)');
4666
+ if (!/^[a-z][a-z0-9_-]*$/i.test(id)) return fail(`잘못된 id: ${id} (영문자/숫자/_- 만 허용)`);
4667
+ const bin = arg('--bin', id);
4668
+ const envFlag = arg('--env-flag', `LEERNESS_ENABLE_${id.toUpperCase()}`);
4669
+ const versionArgs = (arg('--version-args', '--version') || '--version').split(/\s+/).filter(Boolean);
4670
+ const desc = arg('--desc', `(user) ${id}`);
4671
+ const installHint = arg('--install-hint', '');
4672
+ const userList = _readUserProviders(root);
4673
+ // 중복 id 처리: 빌트인 override 또는 user 갱신
4674
+ const existingIdx = userList.findIndex(u => u.id === id);
4675
+ const entry = { id, bin, envFlag, versionArgs, desc, installHint };
4676
+ if (existingIdx >= 0) userList[existingIdx] = entry;
4677
+ else userList.push(entry);
4678
+ _writeUserProviders(root, userList);
4679
+ ok(`provider 등록: ${id} (bin=${bin}, envFlag=${envFlag})`);
4680
+ log(` → 활성화: .env에 ${envFlag}=1 설정 후 \`leerness agents list\` 로 확인`);
4681
+ return;
4682
+ }
4683
+ if (sub === 'remove') {
4684
+ const id = (args[0] || arg('--id', '')).trim();
4685
+ if (!id) return fail('provider remove <id> 필요');
4686
+ if (EXTERNAL_AGENTS.some(b => b.id === id) && !_readUserProviders(root).some(u => u.id === id)) {
4687
+ return fail(`${id} 는 빌트인 — 제거 불가 (override 만 제거 가능)`);
4688
+ }
4689
+ const userList = _readUserProviders(root);
4690
+ const before = userList.length;
4691
+ const filtered = userList.filter(u => u.id !== id);
4692
+ if (filtered.length === before) return fail(`사용자 정의 provider ${id} 없음`);
4693
+ _writeUserProviders(root, filtered);
4694
+ ok(`provider 제거: ${id}`);
4695
+ return;
4696
+ }
4697
+ fail(`알 수 없는 sub: ${sub} (list / add / remove)`);
4698
+ }
4699
+
4587
4700
  // 1.9.36: 작업 키워드 분석으로 최적 CLI 추천
4588
4701
  // \b는 ASCII word boundary만 인식 → 한글 키워드는 단순 substring 검사 사용.
4589
4702
  function _recommendAgent(task) {
@@ -5119,17 +5232,20 @@ function agentsCmd(root, sub, ...args) {
5119
5232
  _loadEnvFile(path.join(root, '..'));
5120
5233
 
5121
5234
  if (!sub || sub === 'list') {
5122
- const checks = EXTERNAL_AGENTS.map(a => _checkAgent(a));
5235
+ // 1.9.157: Provider Registry 통합 — 빌트인 5종 + 사용자 정의 provider 포함
5236
+ const providers = _allProviders(root);
5237
+ const userIds = new Set(_readUserProviders(root).map(u => u.id));
5238
+ const checks = providers.map(a => ({ ...(_checkAgent(a)), source: userIds.has(a.id) ? 'user' : 'builtin' }));
5123
5239
  if (has('--json')) { log(JSON.stringify({ agents: checks }, null, 2)); return; }
5124
5240
  log(`# 외부 AI CLI 오케스트레이션 (1.9.30)`);
5125
5241
  log('');
5126
- log(`| Agent | env (${'env=1 활성'}) | 설치 | 버전 | 상태 |`);
5127
- log(`|---|---|---|---|---|`);
5242
+ log(`| Agent | source | env (${'env=1 활성'}) | 설치 | 버전 | 상태 |`);
5243
+ log(`|---|---|---|---|---|---|`);
5128
5244
  for (const c of checks) {
5129
5245
  const envMark = c.enabled ? '✓' : '✗';
5130
5246
  const instMark = c.installed ? '✓' : '✗';
5131
5247
  const statusEmoji = c.status === 'ready' ? '🟢 ready' : c.status === 'not-installed' ? '⚪ 미설치' : c.status === 'disabled' ? '🟡 비활성' : '❓';
5132
- log(`| ${c.id} | ${envMark} ${c.envFlag} | ${instMark} | ${c.version || '-'} | ${statusEmoji} |`);
5248
+ log(`| ${c.id} | ${c.source} | ${envMark} ${c.envFlag} | ${instMark} | ${c.version || '-'} | ${statusEmoji} |`);
5133
5249
  }
5134
5250
  const ready = checks.filter(c => c.status === 'ready');
5135
5251
  log('');
@@ -5140,6 +5256,7 @@ function agentsCmd(root, sub, ...args) {
5140
5256
  log(` 1) CLI 설치 (예: \`npm i -g @openai/codex-cli\`, \`npm i -g @google/gemini-cli\`)`);
5141
5257
  log(` 2) .env 또는 환경변수: LEERNESS_ENABLE_CODEX=1, LEERNESS_ENABLE_GEMINI=1`);
5142
5258
  log(` 3) \`leerness agents check\`로 재확인`);
5259
+ log(` 💡 1.9.157: 빌트인 외 CLI 추가: \`leerness provider add <id> --bin <cmd>\``);
5143
5260
  } else {
5144
5261
  log('');
5145
5262
  log(`💡 메인 에이전트가 sub-agent 분배 시 위 ${ready.length}개 CLI 활용 가능:`);
@@ -5150,7 +5267,10 @@ function agentsCmd(root, sub, ...args) {
5150
5267
 
5151
5268
  if (sub === 'check') {
5152
5269
  // list의 alias, 단 명시적 재확인 (JSON 출력 기본)
5153
- const checks = EXTERNAL_AGENTS.map(a => _checkAgent(a));
5270
+ // 1.9.157: Provider Registry 통합
5271
+ const providers = _allProviders(root);
5272
+ const userIds = new Set(_readUserProviders(root).map(u => u.id));
5273
+ const checks = providers.map(a => ({ ...(_checkAgent(a)), source: userIds.has(a.id) ? 'user' : 'builtin' }));
5154
5274
  if (has('--json')) { log(JSON.stringify({ agents: checks, ready: checks.filter(c => c.status === 'ready').map(c => c.id) }, null, 2)); return; }
5155
5275
  return agentsCmd(root, 'list'); // 비-JSON은 list와 동일
5156
5276
  }
@@ -5162,6 +5282,7 @@ function agentsCmd(root, sub, ...args) {
5162
5282
  if (!task) { fail('multi "<task>" 또는 --task 필요'); return process.exit(1); }
5163
5283
  const onlyArg = arg('--only', null); // 'claude,codex' 처럼 콤마 구분 — 활성 중에서 추가 필터
5164
5284
  const writeMode = has('--write');
5285
+ const execute = has('--execute'); // 1.9.156: 명령 출력 → 실제 spawn + consensus 합의
5165
5286
  const checks = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }));
5166
5287
  let ready = checks.filter(x => x.status.status === 'ready');
5167
5288
  if (onlyArg) {
@@ -5172,6 +5293,97 @@ function agentsCmd(root, sub, ...args) {
5172
5293
  fail('활성 (ready) 에이전트 없음 — `leerness agents list` 로 확인. 1.9.151 install 흐름에서 복수 선택 후 .env 활성화 필요.');
5173
5294
  return process.exit(1);
5174
5295
  }
5296
+ // 1.9.156: --execute 모드 — 실제 spawn + 결과 수집 + multi-signal consensus
5297
+ if (execute) {
5298
+ return (async () => {
5299
+ const timeout = parseInt(arg('--timeout', '60'), 10) * 1000;
5300
+ if (!has('--json')) {
5301
+ log(`# leerness agents multi --execute (1.9.156) — ${ready.length}개 활성 에이전트 병렬 호출`);
5302
+ log(`task: ${task.slice(0, 120)}${task.length > 120 ? '…' : ''}`);
5303
+ log(`mode: ${writeMode ? '✏ write' : '🔒 read-only'} · timeout=${timeout / 1000}s`);
5304
+ log(`대상: ${ready.map(x => x.def.id).join(', ')}`);
5305
+ log('');
5306
+ log('## 병렬 호출 중...');
5307
+ }
5308
+ const t0 = Date.now();
5309
+ // 병렬 _cliChat 호출 (sandbox 자동: runCommandSafe + env scrub + observability)
5310
+ const results = await Promise.all(ready.map(async ({ def }) => {
5311
+ const start = Date.now();
5312
+ const r = await _cliChat(root, def.id, task, { timeout });
5313
+ return {
5314
+ agent: def.id,
5315
+ elapsed: Date.now() - start,
5316
+ ok: r.ok,
5317
+ response: r.response || '',
5318
+ error: r.error || null,
5319
+ responseTokens: Math.ceil((r.response || '').length / 4) // 대략 token 추정
5320
+ };
5321
+ }));
5322
+ const totalElapsed = Date.now() - t0;
5323
+ const ok = results.filter(r => r.ok);
5324
+ const failures = results.filter(r => !r.ok);
5325
+ _recordRun(root, { kind: 'agents_multi_execute', count: ready.length, success: ok.length, durationMs: totalElapsed, task: task.slice(0, 200) });
5326
+ // 1.9.155 consensus 로직 재사용 — multi-signal scoring (tokens + overlap + lengthFit)
5327
+ let best = null, scored = [];
5328
+ if (ok.length) {
5329
+ const tokenizer = (s) => new Set(String(s || '').toLowerCase().match(/[\w가-힣]{3,}/g) || []);
5330
+ const wordsOf = ok.map(o => tokenizer(o.response));
5331
+ const maxTokens = Math.max(...ok.map(o => o.responseTokens), 1);
5332
+ const avgLen = ok.reduce((s, o) => s + o.response.length, 0) / ok.length;
5333
+ const stdLen = Math.sqrt(ok.reduce((s, o) => s + (o.response.length - avgLen) ** 2, 0) / ok.length) || 1;
5334
+ scored = ok.map((o, i) => {
5335
+ const tokensNorm = o.responseTokens / maxTokens;
5336
+ const myWords = wordsOf[i];
5337
+ let overlapSum = 0;
5338
+ for (let j = 0; j < wordsOf.length; j++) {
5339
+ if (i === j) continue;
5340
+ let inter = 0;
5341
+ for (const w of myWords) if (wordsOf[j].has(w)) inter++;
5342
+ overlapSum += inter / Math.max(myWords.size, 1);
5343
+ }
5344
+ const overlap = (ok.length > 1) ? overlapSum / (ok.length - 1) : 0;
5345
+ const z = Math.abs((o.response.length - avgLen) / stdLen);
5346
+ const lengthFit = z <= 1.5 ? (1 - z / 1.5) : 0;
5347
+ const score = 0.4 * tokensNorm + 0.4 * overlap + 0.2 * lengthFit;
5348
+ return { ...o, score, tokensNorm, overlap, lengthFit };
5349
+ }).sort((a, b) => b.score - a.score);
5350
+ best = scored[0];
5351
+ }
5352
+ if (has('--json')) {
5353
+ log(JSON.stringify({
5354
+ task, count: ready.length, success: ok.length, totalElapsedMs: totalElapsed,
5355
+ results: scored.length ? scored : results,
5356
+ best: best ? { agent: best.agent, score: best.score, response: best.response } : null,
5357
+ failures
5358
+ }, null, 2));
5359
+ return;
5360
+ }
5361
+ log(`\n## 결과: ${ok.length}/${ready.length} 성공 · 총 ${totalElapsed}ms (병렬)`);
5362
+ for (const r of results) {
5363
+ if (r.ok) log(` ✓ ${r.agent.padEnd(8)} · ${r.elapsed}ms · ${r.responseTokens} 토큰`);
5364
+ else log(` ✗ ${r.agent.padEnd(8)} · ${r.elapsed}ms · ${(r.error || '').slice(0, 60)}`);
5365
+ }
5366
+ if (best) {
5367
+ log('');
5368
+ log(`## 🏆 합의 선택 (multi-signal consensus, 1.9.155)`);
5369
+ log(` best: ${best.agent} · score=${best.score.toFixed(3)} (tokens=${best.tokensNorm.toFixed(2)} · overlap=${best.overlap.toFixed(2)} · lengthFit=${best.lengthFit.toFixed(2)})`);
5370
+ if (scored.length > 1) {
5371
+ log(` others: ${scored.slice(1, 4).map(s => `${s.agent}=${s.score.toFixed(2)}`).join(', ')}`);
5372
+ }
5373
+ log(` --- 처음 600자 ---`);
5374
+ log(best.response.slice(0, 600));
5375
+ // task-log 기록
5376
+ try {
5377
+ const tlp = taskLogPath(root);
5378
+ const block = `\n## ${today()} agents multi --execute (1.9.156)\n- task: ${task.slice(0, 200)}\n- agents: ${ready.map(x => x.def.id).join(', ')}\n- success: ${ok.length}/${ready.length}\n- best: ${best.agent} (score=${best.score.toFixed(3)})\n`;
5379
+ append(tlp, block);
5380
+ } catch {}
5381
+ }
5382
+ if (failures.length && !best) {
5383
+ process.exitCode = 1;
5384
+ }
5385
+ })();
5386
+ }
5175
5387
  if (has('--json')) {
5176
5388
  log(JSON.stringify({
5177
5389
  task, count: ready.length,
@@ -5194,9 +5406,10 @@ function agentsCmd(root, sub, ...args) {
5194
5406
  log('```');
5195
5407
  log('');
5196
5408
  }
5197
- log('## 정책 (1.9.152)');
5198
- log(` - leerness는 외부 CLI를 자동 호출하지 않음 (사용자/메인 에이전트가 명시적으로 실행)`);
5199
- log(` - 메인 에이전트(Claude)가 ${ready.length}개 명령을 보고 ${ready.length}개 sub-agent spawn 결과 합의/투표로 가장 안정적인 답 선택`);
5409
+ log('## 정책 (1.9.152 / 1.9.156)');
5410
+ log(` - 기본 모드: 명령 문자열만 출력 (사용자/메인 에이전트가 명시적으로 실행)`);
5411
+ log(` - 1.9.156 신규: \`--execute\` 플래그 leerness가 직접 ${ready.length}개 sub-agent 병렬 spawn + multi-signal consensus 자동 합의`);
5412
+ log(` 예: leerness agents multi "<task>" --execute (또는 --execute --json)`);
5200
5413
  log(` - 활성 에이전트 변경: \`.env\`에서 LEERNESS_ENABLE_<CLI>=1/0 또는 \`leerness setup-agents\` 재실행`);
5201
5414
  log(` - quota 체크: \`leerness agents quota\``);
5202
5415
  return;
@@ -11479,7 +11692,7 @@ function reuseAutodetectCmd(root) {
11479
11692
  }
11480
11693
 
11481
11694
  function help() {
11482
- 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
11695
+ log(`Leerness v${VERSION}\n\nUsage:\n leerness init [path] [--language auto|ko|en] [--skills recommended|all|a,b]\n leerness migrate [path] [--dry-run] [--force]\n leerness update [path] [--check|--yes|--force|--from <tarball>]\n leerness auto-update install [path]\n leerness status [path]\n leerness verify [path]\n leerness debug [path]\n leerness audit [path]\n leerness check [path]\n leerness scan secrets [path]\n leerness encoding check [path]\n leerness lazy detect [path]\n leerness memory search "query" [--limit 5]\n leerness handoff [path] [--all-apps] [--include p1,p2] [--since 24h|3d] [--compact] [--json] # 1.9.17-22 워크스페이스 (--compact: LLM 시스템 프롬프트용 1줄 요약)\n leerness orchestrate "<목표>" [--agents N] [--model qwen2.5:7b-instruct] [--retry-on-fail K] # 1.9.22 Ollama opt-in (LEERNESS_OLLAMA_BASE_URL 필요)\n leerness llm-bench record --score N --model X [--label L] [--tokens T] # 1.9.22 LLM 벤치 히스토리 누적\n leerness deps <capability> [--run-tests] [--json] # 1.9.24 depends-on 역방향 추적 + 자동 회귀 sweep\n leerness memory search "키" [--include-code] # 1.9.25 소스 코드 본문도 검색 (모순 감지 핵심)\n leerness brainstorm "주제" [--include-code] # 1.9.25 코드 본문 hits 포함\n leerness register-pending "<요청>" [--agent X] [--note Y] # 1.9.25 다중 세션 in-progress 즉시 등록\n leerness optimism-check <T-ID> [--json] # 1.9.26/27 낙관적 표시 감지 (1.9.27: 10 카테고리 + URL/메서드 매핑 + 신뢰도 점수)\n leerness persona list|show <id>|add <id> # 1.9.29 페르소나 카탈로그 (보안/성능/UX/testing/docs 5종 내장)\n leerness review <file> --persona <id1,id2,...> # 1.9.29 도메인 페르소나 리뷰 프롬프트 자동 생성\n leerness agents list|check|quota # 1.9.30/31 외부 AI CLI 가용성 + quota 추정 (claude/codex/gemini/copilot)\n leerness agents dispatch "<task>" --to <id> # 1.9.30 활성 CLI 대상 실행 명령 생성 (실 호출 X, 사용자 실행)\n leerness agents multi "<task>" [--only c1,c2] [--write] [--execute] [--timeout 60] # 1.9.152/156 활성 N개 일괄 dispatch (--execute: spawn + consensus)\n leerness provider list|add|remove [args] # 1.9.157 Provider Registry — 사용자 정의 CLI provider 동적 추가 (OpenRouter/Bedrock 흡수)\n leerness agents dispatch "<task>" --multi # 1.9.152 multi 모드 alias (또는 --to all)\n leerness setup-agents [path] [--yes|--no-setup-agents] # 1.9.32 sub-agent CLI 인터랙티브 설정 (.env + 미설치 자동 설치)\n leerness init [path] [--no-stale-check] # 1.9.33 npx 캐시 함정 — 옛 버전 자동 경고 (끄려면 --no-stale-check)\n leerness 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
11483
11696
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
11484
11697
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
11485
11698
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -11551,6 +11764,8 @@ async function main() {
11551
11764
  if (cmd === 'persona') return personaCmd(arg('--path', process.cwd()), args[1], args[2]);
11552
11765
  if (cmd === 'review') return reviewCmd(arg('--path', process.cwd()), args[1]);
11553
11766
  if (cmd === 'agents') return agentsCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
11767
+ // 1.9.157: Provider Registry — 사용자 정의 provider 동적 추가
11768
+ if (cmd === 'provider') return providerCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
11554
11769
  if (cmd === 'contract' && args[1] === 'verify') return contractVerifyCmd(args[2], args[3]);
11555
11770
  if (cmd === 'drift' && (args[1] === 'check' || !args[1])) return driftCheckCmd(args[2] || arg('--path', process.cwd()));
11556
11771
  if (cmd === 'usage' && (args[1] === 'stats' || !args[1])) return usageStatsCmd(args[2] || arg('--path', process.cwd()));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.155",
3
+ "version": "1.9.157",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",