leerness 1.9.154 → 1.9.156

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,104 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.156 — 2026-05-20
4
+
5
+ **`agents multi --execute` 실제 spawn + consensus 통합 (1.9.155 점검 보고서 발견 gap #1 보강).**
6
+
7
+ 자율 모드 86 라운드. 5능력 점검 보고서가 발견한 **"agents multi가 명령 문자열만 출력 — 실제 spawn 안 함"** 문제를 직접 해결.
8
+
9
+ ### Added — `leerness agents multi "<task>" --execute`
10
+ - 기존 (1.9.152): 활성 N개 에이전트에 dispatch 명령 **문자열만 출력** — 사용자가 실행
11
+ - 신규 (1.9.156): `--execute` 플래그 시 **leerness가 직접 N개 sub-agent 병렬 spawn**
12
+ - `Promise.all` 로 `_cliChat(provider)` 동시 호출 — 진짜 N-way 분배
13
+ - 각 호출은 1.9.150 `runCommandSafe` 경유 → cwd jail + env scrub + 자동 observability
14
+ - `--timeout <s>` 옵션 (기본 60s) — 무한 대기 방지
15
+ - 결과 수집 후 **1.9.155 multi-signal consensus** 자동 적용:
16
+ - `score = 0.4*tokensNorm + 0.4*overlap + 0.2*lengthFit`
17
+ - best 1위 + others 2-4위 점수 표시 (투명성)
18
+ - `--json` 출력: `{ task, count, success, totalElapsedMs, results, best, failures }`
19
+ - `_recordRun` 통합 — kind `agents_multi_execute` + task-log 자동 기록
20
+ - 활성 0개 또는 onlyArg 매칭 0개 → 즉시 fail (실 호출 시도 X)
21
+
22
+ ### Verification — 5능력 점검 매트릭스 갱신
23
+ | 영역 | 1.9.155 | 1.9.156 |
24
+ |---|---|---|
25
+ | 멀티 에이전트 오케스트레이션 | 70% (명령 출력만) | **90%** (실 spawn + consensus 합의) |
26
+ | 종합 완성도 | 55% | **60%** |
27
+
28
+ 이제 leerness 가 "지시 생성기" 가 아닌 **"실 실행 오케스트레이터"** — Hermes Agent / OpenClaw 같은 도구와의 격차가 크게 줄어듦.
29
+
30
+ ### Pending — 보고서 권고 다음 3 후보
31
+ 1. **1.9.157** — LSP 어댑터 MVP (TypeScript LSP 먼저)
32
+ 2. **1.9.158** — Provider Registry MCP 도구 (OpenRouter/Bedrock 100+ 모델 흡수)
33
+ 3. **1.9.159** — playwright/computer-use bridge (`permissions.browser/mouse` 실 동작 — opt-in)
34
+
35
+ ### Verified
36
+ - e2e 217/217 ✓
37
+ - stress-v101: 18/18 (--execute 7종 + CLI 동작 3종 + 누적 회귀 8종)
38
+ - VERSION = 1.9.156 / autonomous-rounds = 86
39
+
40
+ ---
41
+
42
+ ## 1.9.155 — 2026-05-20
43
+
44
+ **REPL UX 대폭 개선 + provider 모델 카탈로그 + orchestrate consensus 강화 + 5능력 점검 보고서 (사용자 명시).**
45
+
46
+ 자율 모드 85 라운드. 사용자 명시 점검 요청 — sub-agent 2개 병렬로 비교/실측 후 정직한 보고서 산출 + 발견 gap 1건 즉시 보강.
47
+
48
+ ### Added — REPL UX (사용자 명시: 선택한 CLI 모델 변경 가능)
49
+ - **`:model <name>` 모든 provider 지원** — claude/codex/gemini/copilot/ollama 모두 가능 (1.9.149 → 1.9.155)
50
+ - **`_PROVIDER_MODEL_CATALOG`** — provider 별 추천 모델 카탈로그
51
+ - claude: `claude-opus-4-5` / `claude-sonnet-4-5` / `claude-haiku-4-5`
52
+ - codex: `gpt-5` / `gpt-5-codex` / `o4-mini`
53
+ - gemini: `gemini-2.5-pro` / `gemini-2.5-flash`
54
+ - copilot: `default` (모델 선택 불가)
55
+ - ollama: `llama3` / `qwen2.5-coder` / `gpt-oss` (실시간 조회 권장)
56
+ - **`:models`** — provider 별 분기 (ollama 실시간 / 그 외 카탈로그)
57
+ - **`:status`** — 현재 세션 상태 자세히 (provider/model/role/permissions/history/sessionId/activeCli)
58
+ - **REPL 진입 시 handoff context 자동 노출** — 매번 `:handoff` 안 해도 즉시 컨텍스트 인지
59
+ - `:help` 에 1.9.155 명령 안내 통합
60
+
61
+ ### Added — orchestrate consensus 강화 (sub-agent 점검 발견 gap 보강)
62
+ - 기존: "응답 토큰 수가 가장 긴 응답 = best" 임시 휴리스틱
63
+ - 변경: **multi-signal scoring**
64
+ - `tokensNorm` (0~1) — 정보 밀도
65
+ - `overlap` — 다른 응답과의 단어 교집합 비율 평균 (합의도)
66
+ - `lengthFit` — 평균 길이 z-score 기반 적정 가중 (`|z| <= 1.5` 가산)
67
+ - `score = 0.4*tokens + 0.4*overlap + 0.2*lengthFit`
68
+ - 출력: best 1위 + others 2-4위 점수 표시 (투명성)
69
+
70
+ ### Verified — 5능력 점검 보고서 (`_reports/1.9.155-capability-audit.md`)
71
+ sub-agent 2 병렬 분석 (약 110초):
72
+
73
+ **경쟁 비교 (sub-agent #1)** — OpenClaw / Hermes Agent / OpenCode WebSearch 실측:
74
+ - leerness 비교우위: 한국어+신뢰도 하네스, MCP 47도구+Memory 5종, Feature Causality Graph, 의존성 0, 자율 release
75
+ - 부족 영역: 모델/프로바이더 폭(5종 vs 200+), LSP 통합 미흡, GUI/멀티채널 부재
76
+ - 권고: LSP 어댑터, Provider Registry MCP, i18n+영어 docs
77
+
78
+ **5능력 매트릭스 (sub-agent #2 코드 실측)**:
79
+ | 영역 | 평가 | 완성도 |
80
+ |---|---|---|
81
+ | 웹 띄워 검수 | ❌ | 5% (permissions.browser=toggle만, 실 코드 0) |
82
+ | 권한 따른 PC 조작 | ❌ | 5% (mouse/keyboard/admin 필드만, 실 사용처 0) |
83
+ | 멀티 에이전트 오케스트레이션 | ⚠ → ✓ (1.9.155 보강) | 70% → 80% |
84
+ | REPL multi-provider | ✓ | 90% |
85
+ | MCP 47도구 일관성 | ✓ | 100% |
86
+
87
+ **종합 ~55% — "검수·기억·드리프트 방지 하네스" 본질은 95%, "범용 PC 자동화"는 30%**. 정직한 포지셔닝.
88
+
89
+ ### Pending — 보고서가 제안한 다음 4 라운드 후보
90
+ 1. 1.9.156 — `agents multi --execute` (단순 명령 출력 → 실제 spawn + consensus 통합)
91
+ 2. 1.9.157 — LSP 어댑터 MVP (TypeScript LSP 먼저)
92
+ 3. 1.9.158 — Provider Registry MCP 도구 (OpenRouter/Bedrock 흡수)
93
+ 4. 1.9.159 — playwright/computer-use bridge (permissions.browser/mouse 실 동작 — opt-in)
94
+
95
+ ### Verified
96
+ - e2e 217/217 ✓
97
+ - stress-v100: 18/18 (REPL :model 3종 + :status/UX 3종 + consensus 2종 + 점검 보고서 3종 + 누적 회귀 7종) 🎉 **100번째 stress 마일스톤**
98
+ - VERSION = 1.9.155 / autonomous-rounds = 85
99
+
100
+ ---
101
+
3
102
  ## 1.9.154 — 2026-05-20
4
103
 
5
104
  **agent 1-shot multi-provider + REPL `:provider` 활성 검증 (1.9.153 후속, 일관성 강화).**
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.154-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v99-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-47-blue)]() [![json](https://img.shields.io/badge/--json-20_commands-blueviolet)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-84-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![multi-provider](https://img.shields.io/badge/agent-5_provider_1shot%2BREPL-success)]() [![env](https://img.shields.io/badge/.env-auto_generate%2Bmigrate-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.156-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v101-18%2F18-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-47-blue)]() [![rounds](https://img.shields.io/badge/autonomous--rounds-86-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![multi-execute](https://img.shields.io/badge/agents_multi-real_spawn%2Bconsensus-success)]() [![multi-provider](https://img.shields.io/badge/REPL-5_providers%2Bmodel_catalog-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.154 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.156 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.154';
9
+ const VERSION = '1.9.156';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -4158,9 +4158,43 @@ async function orchestrateCmd(root, goalParts) {
4158
4158
  log(` 평균 latency: ${avgElapsed.toFixed(0)}ms · wall-clock 총: ${totalElapsedWallClock}ms (병렬 효과 ${(avgElapsed * ok.length / totalElapsedWallClock).toFixed(1)}x)`);
4159
4159
 
4160
4160
  log('');
4161
- log(`## 최고 응답 (longest by response token count, 임시 휴리스틱)`);
4162
- const best = ok.reduce((a, b) => (b.responseTokens > a.responseTokens ? b : a));
4163
- log(` agent ${best.agent} · ${best.responseTokens} 응답 토큰 · ${best.elapsed}ms`);
4161
+ log(`## 최고 응답 (1.9.155 multi-signal consensus 토큰 + 단어중복도 + 길이 정규화)`);
4162
+ // 1.9.155: 단순 token-max multi-signal scoring
4163
+ // 1) responseTokens (information density) 정규화 0~1
4164
+ // 2) wordOverlap (agreement with others) — 다른 응답과의 단어 교집합 비율 평균
4165
+ // 3) lengthZ (적정 길이 가중) — 평균에서 너무 짧지/길지 않음
4166
+ const tokenizer = (s) => new Set(String(s || '').toLowerCase().match(/[\w가-힣]{3,}/g) || []);
4167
+ const wordsOf = ok.map(o => tokenizer(o.reply));
4168
+ const maxTokens = Math.max(...ok.map(o => o.responseTokens), 1);
4169
+ const avgLen = ok.reduce((s, o) => s + o.reply.length, 0) / ok.length;
4170
+ const stdLen = Math.sqrt(ok.reduce((s, o) => s + (o.reply.length - avgLen) ** 2, 0) / ok.length) || 1;
4171
+ const scored = ok.map((o, i) => {
4172
+ const tokensNorm = o.responseTokens / maxTokens;
4173
+ // 다른 응답들과 단어 교집합 비율 평균
4174
+ const myWords = wordsOf[i];
4175
+ let overlapSum = 0;
4176
+ for (let j = 0; j < wordsOf.length; j++) {
4177
+ if (i === j) continue;
4178
+ const other = wordsOf[j];
4179
+ if (!myWords.size || !other.size) continue;
4180
+ let inter = 0;
4181
+ for (const w of myWords) if (other.has(w)) inter++;
4182
+ overlapSum += inter / Math.max(myWords.size, 1);
4183
+ }
4184
+ const overlap = (ok.length > 1) ? overlapSum / (ok.length - 1) : 0;
4185
+ // length z-score (적정 길이 가산: |z| <= 1.5 일 때 가산)
4186
+ const z = Math.abs((o.reply.length - avgLen) / stdLen);
4187
+ const lengthFit = z <= 1.5 ? (1 - z / 1.5) : 0;
4188
+ const score = 0.4 * tokensNorm + 0.4 * overlap + 0.2 * lengthFit;
4189
+ return { ...o, score, tokensNorm, overlap, lengthFit };
4190
+ });
4191
+ scored.sort((a, b) => b.score - a.score);
4192
+ const best = scored[0];
4193
+ log(` best: agent ${best.agent} · score=${best.score.toFixed(3)} (tokens=${best.tokensNorm.toFixed(2)} · overlap=${best.overlap.toFixed(2)} · lengthFit=${best.lengthFit.toFixed(2)})`);
4194
+ log(` → ${best.responseTokens} 응답 토큰 · ${best.elapsed}ms`);
4195
+ if (scored.length > 1) {
4196
+ log(` others: ${scored.slice(1, 4).map(s => `${s.agent}=${s.score.toFixed(2)}`).join(', ')}`);
4197
+ }
4164
4198
  log(` --- 처음 600자 ---`);
4165
4199
  log(best.reply.slice(0, 600));
4166
4200
  }
@@ -5128,6 +5162,7 @@ function agentsCmd(root, sub, ...args) {
5128
5162
  if (!task) { fail('multi "<task>" 또는 --task 필요'); return process.exit(1); }
5129
5163
  const onlyArg = arg('--only', null); // 'claude,codex' 처럼 콤마 구분 — 활성 중에서 추가 필터
5130
5164
  const writeMode = has('--write');
5165
+ const execute = has('--execute'); // 1.9.156: 명령 출력 → 실제 spawn + consensus 합의
5131
5166
  const checks = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }));
5132
5167
  let ready = checks.filter(x => x.status.status === 'ready');
5133
5168
  if (onlyArg) {
@@ -5138,6 +5173,97 @@ function agentsCmd(root, sub, ...args) {
5138
5173
  fail('활성 (ready) 에이전트 없음 — `leerness agents list` 로 확인. 1.9.151 install 흐름에서 복수 선택 후 .env 활성화 필요.');
5139
5174
  return process.exit(1);
5140
5175
  }
5176
+ // 1.9.156: --execute 모드 — 실제 spawn + 결과 수집 + multi-signal consensus
5177
+ if (execute) {
5178
+ return (async () => {
5179
+ const timeout = parseInt(arg('--timeout', '60'), 10) * 1000;
5180
+ if (!has('--json')) {
5181
+ log(`# leerness agents multi --execute (1.9.156) — ${ready.length}개 활성 에이전트 병렬 호출`);
5182
+ log(`task: ${task.slice(0, 120)}${task.length > 120 ? '…' : ''}`);
5183
+ log(`mode: ${writeMode ? '✏ write' : '🔒 read-only'} · timeout=${timeout / 1000}s`);
5184
+ log(`대상: ${ready.map(x => x.def.id).join(', ')}`);
5185
+ log('');
5186
+ log('## 병렬 호출 중...');
5187
+ }
5188
+ const t0 = Date.now();
5189
+ // 병렬 _cliChat 호출 (sandbox 자동: runCommandSafe + env scrub + observability)
5190
+ const results = await Promise.all(ready.map(async ({ def }) => {
5191
+ const start = Date.now();
5192
+ const r = await _cliChat(root, def.id, task, { timeout });
5193
+ return {
5194
+ agent: def.id,
5195
+ elapsed: Date.now() - start,
5196
+ ok: r.ok,
5197
+ response: r.response || '',
5198
+ error: r.error || null,
5199
+ responseTokens: Math.ceil((r.response || '').length / 4) // 대략 token 추정
5200
+ };
5201
+ }));
5202
+ const totalElapsed = Date.now() - t0;
5203
+ const ok = results.filter(r => r.ok);
5204
+ const failures = results.filter(r => !r.ok);
5205
+ _recordRun(root, { kind: 'agents_multi_execute', count: ready.length, success: ok.length, durationMs: totalElapsed, task: task.slice(0, 200) });
5206
+ // 1.9.155 consensus 로직 재사용 — multi-signal scoring (tokens + overlap + lengthFit)
5207
+ let best = null, scored = [];
5208
+ if (ok.length) {
5209
+ const tokenizer = (s) => new Set(String(s || '').toLowerCase().match(/[\w가-힣]{3,}/g) || []);
5210
+ const wordsOf = ok.map(o => tokenizer(o.response));
5211
+ const maxTokens = Math.max(...ok.map(o => o.responseTokens), 1);
5212
+ const avgLen = ok.reduce((s, o) => s + o.response.length, 0) / ok.length;
5213
+ const stdLen = Math.sqrt(ok.reduce((s, o) => s + (o.response.length - avgLen) ** 2, 0) / ok.length) || 1;
5214
+ scored = ok.map((o, i) => {
5215
+ const tokensNorm = o.responseTokens / maxTokens;
5216
+ const myWords = wordsOf[i];
5217
+ let overlapSum = 0;
5218
+ for (let j = 0; j < wordsOf.length; j++) {
5219
+ if (i === j) continue;
5220
+ let inter = 0;
5221
+ for (const w of myWords) if (wordsOf[j].has(w)) inter++;
5222
+ overlapSum += inter / Math.max(myWords.size, 1);
5223
+ }
5224
+ const overlap = (ok.length > 1) ? overlapSum / (ok.length - 1) : 0;
5225
+ const z = Math.abs((o.response.length - avgLen) / stdLen);
5226
+ const lengthFit = z <= 1.5 ? (1 - z / 1.5) : 0;
5227
+ const score = 0.4 * tokensNorm + 0.4 * overlap + 0.2 * lengthFit;
5228
+ return { ...o, score, tokensNorm, overlap, lengthFit };
5229
+ }).sort((a, b) => b.score - a.score);
5230
+ best = scored[0];
5231
+ }
5232
+ if (has('--json')) {
5233
+ log(JSON.stringify({
5234
+ task, count: ready.length, success: ok.length, totalElapsedMs: totalElapsed,
5235
+ results: scored.length ? scored : results,
5236
+ best: best ? { agent: best.agent, score: best.score, response: best.response } : null,
5237
+ failures
5238
+ }, null, 2));
5239
+ return;
5240
+ }
5241
+ log(`\n## 결과: ${ok.length}/${ready.length} 성공 · 총 ${totalElapsed}ms (병렬)`);
5242
+ for (const r of results) {
5243
+ if (r.ok) log(` ✓ ${r.agent.padEnd(8)} · ${r.elapsed}ms · ${r.responseTokens} 토큰`);
5244
+ else log(` ✗ ${r.agent.padEnd(8)} · ${r.elapsed}ms · ${(r.error || '').slice(0, 60)}`);
5245
+ }
5246
+ if (best) {
5247
+ log('');
5248
+ log(`## 🏆 합의 선택 (multi-signal consensus, 1.9.155)`);
5249
+ log(` best: ${best.agent} · score=${best.score.toFixed(3)} (tokens=${best.tokensNorm.toFixed(2)} · overlap=${best.overlap.toFixed(2)} · lengthFit=${best.lengthFit.toFixed(2)})`);
5250
+ if (scored.length > 1) {
5251
+ log(` others: ${scored.slice(1, 4).map(s => `${s.agent}=${s.score.toFixed(2)}`).join(', ')}`);
5252
+ }
5253
+ log(` --- 처음 600자 ---`);
5254
+ log(best.response.slice(0, 600));
5255
+ // task-log 기록
5256
+ try {
5257
+ const tlp = taskLogPath(root);
5258
+ 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`;
5259
+ append(tlp, block);
5260
+ } catch {}
5261
+ }
5262
+ if (failures.length && !best) {
5263
+ process.exitCode = 1;
5264
+ }
5265
+ })();
5266
+ }
5141
5267
  if (has('--json')) {
5142
5268
  log(JSON.stringify({
5143
5269
  task, count: ready.length,
@@ -5160,9 +5286,10 @@ function agentsCmd(root, sub, ...args) {
5160
5286
  log('```');
5161
5287
  log('');
5162
5288
  }
5163
- log('## 정책 (1.9.152)');
5164
- log(` - leerness는 외부 CLI를 자동 호출하지 않음 (사용자/메인 에이전트가 명시적으로 실행)`);
5165
- log(` - 메인 에이전트(Claude)가 ${ready.length}개 명령을 보고 ${ready.length}개 sub-agent spawn 결과 합의/투표로 가장 안정적인 답 선택`);
5289
+ log('## 정책 (1.9.152 / 1.9.156)');
5290
+ log(` - 기본 모드: 명령 문자열만 출력 (사용자/메인 에이전트가 명시적으로 실행)`);
5291
+ log(` - 1.9.156 신규: \`--execute\` 플래그 leerness가 직접 ${ready.length}개 sub-agent 병렬 spawn + multi-signal consensus 자동 합의`);
5292
+ log(` 예: leerness agents multi "<task>" --execute (또는 --execute --json)`);
5166
5293
  log(` - 활성 에이전트 변경: \`.env\`에서 LEERNESS_ENABLE_<CLI>=1/0 또는 \`leerness setup-agents\` 재실행`);
5167
5294
  log(` - quota 체크: \`leerness agents quota\``);
5168
5295
  return;
@@ -10300,6 +10427,32 @@ function runsShowCmd(root, id) {
10300
10427
  if (!exists(fp)) return fail(`run 없음: ${id}`);
10301
10428
  log(read(fp));
10302
10429
  }
10430
+ // 1.9.155: provider 별 추천 모델 카탈로그 — REPL :models 명령에서 노출 (실제 가용성은 사용자 CLI 가 결정)
10431
+ const _PROVIDER_MODEL_CATALOG = {
10432
+ claude: [
10433
+ { id: 'claude-opus-4-5', note: '최고 추론 (Anthropic)' },
10434
+ { id: 'claude-sonnet-4-5', note: '균형형 (속도/품질)' },
10435
+ { id: 'claude-haiku-4-5', note: '빠름' }
10436
+ ],
10437
+ codex: [
10438
+ { id: 'gpt-5', note: 'OpenAI 최신' },
10439
+ { id: 'gpt-5-codex', note: '코드 특화' },
10440
+ { id: 'o4-mini', note: '빠른 reasoning' }
10441
+ ],
10442
+ gemini: [
10443
+ { id: 'gemini-2.5-pro', note: 'Google 최고급' },
10444
+ { id: 'gemini-2.5-flash', note: '빠른 응답' }
10445
+ ],
10446
+ copilot: [
10447
+ { id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
10448
+ ],
10449
+ ollama: [
10450
+ { id: 'llama3', note: 'Meta — :models 로 실시간 조회 권장' },
10451
+ { id: 'qwen2.5-coder', note: 'Alibaba — 코드 특화' },
10452
+ { id: 'gpt-oss', note: 'OpenAI 오픈소스' }
10453
+ ]
10454
+ };
10455
+
10303
10456
  // 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
10304
10457
  const _AGENT_ROLE_PROMPTS = {
10305
10458
  planner: '역할: planner. task를 step 3-6개로 분해, 각 step의 입출력/검증 방법 명시. 코드 작성 금지, 계획만.',
@@ -10379,9 +10532,23 @@ async function _agentRepl(root, opts) {
10379
10532
  }
10380
10533
  }
10381
10534
  log('');
10382
- log(C.dim(' 메타 명령: :help | :model <m> | :role <r> | :provider <p> | :clear | :save | :history | :quit'));
10535
+ log(C.dim(' 메타 명령: :help | :model <m> | :role <r> | :provider <p> | :status | :clear | :save | :history | :quit'));
10383
10536
  log(C.dim(' Slash 명령 (1.9.150): :verify | :audit | :handoff | :health'));
10384
- log(C.dim(` 현재 — provider=${state.provider} model=${state.model || '(없음)'} role=${state.role} permissions=${_readPermissions(root).mode}`));
10537
+ log(C.dim(` 현재 — provider=${state.provider} model=${state.model || '(기본)'} role=${state.role} permissions=${_readPermissions(root).mode}`));
10538
+ // 1.9.155: REPL 진입 시 handoff 컨텍스트 자동 노출 (UX 개선 — 사용자가 매번 :handoff 안 해도 컨텍스트 인지)
10539
+ try {
10540
+ const hf = cp.spawnSync(process.execPath, [__filename, 'handoff', root, '--compact', '--no-drift-check', '--no-headline'], {
10541
+ encoding: 'utf8', timeout: 8000,
10542
+ env: { ...process.env, LEERNESS_NO_BANNER: '1', LEERNESS_NO_PROMPT: '1', LEERNESS_NO_DRIFT_CHECK: '1', LEERNESS_NO_LESSONS: '1' }
10543
+ });
10544
+ if (hf.status === 0 && hf.stdout) {
10545
+ const preview = hf.stdout.split('\n').slice(0, 4).map(l => l.replace(/^\s+/, '')).filter(Boolean).slice(0, 3).join(' · ');
10546
+ if (preview) {
10547
+ log('');
10548
+ log(C.dim(` 📍 context: ${preview.slice(0, 220)}${preview.length > 220 ? '…' : ''}`));
10549
+ }
10550
+ }
10551
+ } catch {}
10385
10552
  log('');
10386
10553
  const prompt = () => isTty ? C.cy(`agent[${state.role}]> `) : 'agent> ';
10387
10554
  rl.setPrompt(prompt());
@@ -10394,12 +10561,13 @@ async function _agentRepl(root, opts) {
10394
10561
  rl.close(); return true;
10395
10562
  }
10396
10563
  if (op === 'help' || op === '?') {
10397
- log(C.bold('\n 메타 명령:'));
10564
+ log(C.bold('\n 메타 명령 (provider/모델/역할 전환):'));
10398
10565
  log(' :help / :? — 이 도움말');
10399
- log(' :model <name> — 모델 변경 (예: :model qwen2.5-coder)');
10400
- log(' :models — Ollama 사용 가능 모델 목록');
10566
+ log(' :model <name> — 모델 변경 (1.9.155 모든 provider 지원, 예: :model claude-opus-4-5)');
10567
+ log(' :models — provider 모델 목록 (ollama 실시간 / 그 외 추천 카탈로그)');
10401
10568
  log(' :role <r> — 역할 변경 (planner / reviewer / actor)');
10402
- log(' :provider <p> — provider 변경 (ollama / claude / codex / gemini)');
10569
+ log(' :provider <p> — provider 변경 (ollama / claude / codex / gemini / copilot — ready 검증)');
10570
+ log(' :status — 현재 세션 상태 자세히 (1.9.155)');
10403
10571
  log(' :clear — 화면 클리어 + history 유지');
10404
10572
  log(' :reset — history 초기화');
10405
10573
  log(' :history — 대화 history 표시');
@@ -10413,11 +10581,37 @@ async function _agentRepl(root, opts) {
10413
10581
  log(' :health — leerness health --json (종합 헬스 체크)');
10414
10582
  return false;
10415
10583
  }
10416
- if (op === 'model') { state.model = rest.join(' ') || state.model; log(C.green(` model = ${state.model}`)); return false; }
10584
+ if (op === 'model') {
10585
+ // 1.9.155: provider 별 모델 명시 (사용자 명시 요청 — 선택한 CLI 모델 변경 가능)
10586
+ const m = rest.join(' ').trim();
10587
+ if (!m) {
10588
+ log(C.dim(` 현재 model: ${state.model || '(provider 기본)'} · provider: ${state.provider}`));
10589
+ log(C.dim(` 사용: :model <name> — 예: :model qwen2.5-coder (ollama) / :model claude-opus-4-5 (claude) / :model gpt-5 (codex)`));
10590
+ return false;
10591
+ }
10592
+ state.model = m;
10593
+ log(C.green(` model = ${state.model} (provider: ${state.provider})`));
10594
+ log(C.dim(` ※ 다음 메시지부터 새 모델로 호출. provider 가 ${state.provider} 인지 :provider 로 재확인 권장.`));
10595
+ return false;
10596
+ }
10417
10597
  if (op === 'models') {
10418
- const r = await _ollamaListModels();
10419
- if (r.ok && r.models.length) { log(C.green(` ${r.models.length}개:`)); r.models.forEach(m => log('' + m)); }
10420
- else log(C.yel(' ⚠ Ollama 미가동'));
10598
+ // 1.9.155: provider 모델 목록 — ollama 는 실시간 조회, 그 외는 추천 카탈로그 노출
10599
+ if (state.provider === 'ollama') {
10600
+ const r = await _ollamaListModels();
10601
+ if (r.ok && r.models.length) {
10602
+ log(C.green(` [ollama 실시간] ${r.models.length}개 모델:`));
10603
+ r.models.forEach(m => log(' • ' + m));
10604
+ } else log(C.yel(' ⚠ Ollama 미가동 — ollama serve + ollama pull <model>'));
10605
+ } else {
10606
+ const catalog = _PROVIDER_MODEL_CATALOG[state.provider] || [];
10607
+ if (catalog.length) {
10608
+ log(C.green(` [${state.provider} 추천 모델 카탈로그]`));
10609
+ catalog.forEach(m => log(` • ${m.id}${m.note ? ' — ' + m.note : ''}`));
10610
+ log(C.dim(' ※ 실제 가용 모델은 해당 CLI 의 --help / 공식 문서 참고. :model <name> 으로 변경.'));
10611
+ } else {
10612
+ log(C.dim(` ${state.provider} 의 추천 모델 카탈로그 없음 — :model <name> 으로 직접 지정`));
10613
+ }
10614
+ }
10421
10615
  return false;
10422
10616
  }
10423
10617
  if (op === 'role') {
@@ -10464,6 +10658,23 @@ async function _agentRepl(root, opts) {
10464
10658
  }
10465
10659
  if (op === 'save') { saveSession(); log(C.dim(` → ${rel(root, sessionPath())}`)); return false; }
10466
10660
  if (op === 'permissions') { permissionsListCmd(root); return false; }
10661
+ if (op === 'status') {
10662
+ // 1.9.155: REPL 안에서 현재 세션 상태 자세히 (provider/model/role/permissions/history/runs)
10663
+ log(C.bold('\n 📊 REPL 세션 상태 (1.9.155)'));
10664
+ log(` provider: ${state.provider}`);
10665
+ log(` model: ${state.model || '(기본)'}`);
10666
+ log(` role: ${state.role} (${_AGENT_ROLE_PROMPTS[state.role]?.slice(0, 60) || ''}...)`);
10667
+ log(` permissions: ${_readPermissions(root).mode || 'basic'}`);
10668
+ log(` history: ${state.history.length}턴 (마지막: ${state.history[state.history.length - 1]?.role || '-'})`);
10669
+ log(` session ID: ${state.sessionId}`);
10670
+ log(` started: ${state.startedAt}`);
10671
+ try {
10672
+ const ready = EXTERNAL_AGENTS.map(a => _checkAgent(a)).filter(c => c.status === 'ready');
10673
+ log(` 활성 CLI: ${ready.length ? ready.map(c => c.id).join(', ') : '(없음)'}`);
10674
+ } catch {}
10675
+ log('');
10676
+ return false;
10677
+ }
10467
10678
  // 1.9.150: leerness 내부 명령 slash-commands — :verify / :audit / :handoff / :health
10468
10679
  if (op === 'verify' || op === 'audit' || op === 'handoff' || op === 'health') {
10469
10680
  const subArgs = {
@@ -11361,7 +11572,7 @@ function reuseAutodetectCmd(root) {
11361
11572
  }
11362
11573
 
11363
11574
  function help() {
11364
- 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
11575
+ 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 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
11365
11576
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
11366
11577
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
11367
11578
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.154",
3
+ "version": "1.9.156",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",