leerness 1.9.150 → 1.9.152

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,76 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.152 — 2026-05-20
4
+
5
+ **`agents multi` — 활성 N개 에이전트 일괄 dispatch + handoff 헤드라인 활성 에이전트 카운트 (1.9.151 복수 선택 후속).**
6
+
7
+ 자율 모드 82 라운드. 1.9.151 install 복수 선택의 자연스러운 후속 — 실제 사용 시점에 선택된 에이전트들을 동시에 활용.
8
+
9
+ ### Added — `leerness agents multi "<task>"` 신규 명령
10
+ - 활성 (ready) 에이전트들에 **일괄 dispatch 명령** 자동 생성 (claude/codex/gemini/copilot/ollama)
11
+ - `--only c1,c2` — 활성 중에서 추가 필터링
12
+ - `--write` — 파일 수정 권한 플래그 (각 CLI 별 적절한 옵션 자동 적용)
13
+ - `--json` — 구조화 출력 (`{ task, count, agents, commands }`)
14
+ - 메인 에이전트가 N개 sub-agent로 spawn 후 결과 합의/투표 → 가장 안정적인 답 선택
15
+
16
+ ### Added — `agents dispatch --multi` / `--to all` alias routing
17
+ - 기존 `dispatch --to <id>` 와 동일한 인터페이스로 multi 모드 진입 가능
18
+ - `--to all` 또는 `--to *` 또는 `--multi` 플래그로 자동 routing
19
+
20
+ ### Added — `_dispatchCommand(agentId, task, writeMode)` 공유 헬퍼
21
+ - single dispatch + multi dispatch 가 동일한 명령 빌더 사용 — 일관성 보장
22
+ - 5종 CLI 모두 지원 (claude/codex/gemini/copilot/ollama)
23
+
24
+ ### Added — handoff 헤드라인 8번째 항목 — 활성 에이전트 카운트
25
+ - `🤖 agents N (claude,codex,...)` — 메인 에이전트가 매 세션 즉시 sub-agent 분배 가능성 인지
26
+ - 활성 0개면 표시 안 함 (노이즈 최소화)
27
+ - 1.9.151 복수 선택 결과가 .env 활성화 → 즉시 헤드라인 반영
28
+
29
+ ### Verified
30
+ - e2e 217/217 ✓
31
+ - stress-v97: 18/18 (agents multi 6종 + handoff 헤드라인 3종 + help/fail-fast 2종 + 누적 회귀 7종)
32
+ - VERSION = 1.9.152 / autonomous-rounds = 82
33
+
34
+ ---
35
+
36
+ ## 1.9.151 — 2026-05-20
37
+
38
+ **install 흐름 — CLI 에이전트 복수 선택 + REPL 자동 시작 prompt + viewwork 제거 + help 검증 (사용자 명시 3종).**
39
+
40
+ 자율 모드 81 라운드.
41
+
42
+ ### Added — install: CLI 에이전트 복수 선택 (사용자 명시)
43
+ - 기존 4지 단일 선택 (`none`/`claude`/`ollama`/`all`) → **5개 후보 다중 선택**
44
+ - Claude / Codex / Gemini / Copilot / Ollama
45
+ - Space 토글, `a` 전체, `n` 해제, Enter 확정
46
+ - non-TTY fallback: 콤마 구분 (예: `1,5` 또는 `claude,ollama` 또는 `all` 또는 `none`)
47
+ - `.env.example` LEERNESS_ENABLE_* 플래그가 선택된 에이전트별로 정확히 활성화 (이전엔 `all` 외에는 단일만)
48
+ - 배열/문자열/'all'/'none' 모두 처리하는 `enabledSet` Set 기반 헬퍼 (back-compat)
49
+
50
+ ### Added — install 종료 후 REPL 자동 시작 prompt (사용자 명시)
51
+ - 모든 문항 (언어/에이전트/권한) 끝난 후 — **에이전트가 선택된 경우만** REPL 활성화 여부 묻기
52
+ - "예" 선택 시 설치 완료 직후 1.9.149 `_agentRepl` 자동 진입 (Ollama provider / actor 역할)
53
+ - 기본값 "아니오" (안전) — 사용자가 토큰/모델 설정 후 별도로 `leerness agent` 실행
54
+
55
+ ### Removed — viewwork 관련 (사용자 명시 — leerness 와 무관)
56
+ - `viewworkEmit()` / `viewworkInstall()` 함수 정의 삭제
57
+ - 라우터 분기 `cmd === 'viewwork'` 2종 제거
58
+ - `session close` 의 자동 `viewworkEmit` 콜 제거
59
+ - help 텍스트에서 `leerness viewwork install/emit` 2줄 제거
60
+ - `scripts/e2e.js` 의 `viewwork install` + `viewwork emit` 테스트 라인 제거
61
+ - 기존 프로젝트의 `.viewwork/` 디렉토리는 leerness 가 삭제하지 않음 (사용자 책임)
62
+
63
+ ### Verified — help 모든 명령어 통과
64
+ - sub-agent 병렬 검증: **39/39 명령어 모두 exit 0 또는 1 (정상)** — TypeError / ReferenceError / unknown command 0건
65
+ - 검증 명령어: init / migrate / update / status / verify / debug / audit / check / scan / encoding / lazy / memory / handoff / orchestrate / deps / persona / agents / setup-agents / reuse-map / session / route / self / readme / consistency / plan / task / skill / gate / retro / insights / brainstorm / roadmap / verify-code / lessons / rule / release / health / runs / permissions / env / creds / decision / lesson / plan list / agent
66
+
67
+ ### Verified
68
+ - e2e 217/217 (viewwork 2건 삭제로 219→217)
69
+ - stress-v96: 26/26 (복수선택 4종 + REPL prompt 4종 + viewwork 제거 6종 + help 5종 + 누적 회귀 7종)
70
+ - VERSION = 1.9.151 / autonomous-rounds = 81
71
+
72
+ ---
73
+
3
74
  ## 1.9.150 — 2026-05-20
4
75
 
5
76
  **Sandboxing — `runCommandSafe()` wrapper + REPL slash-commands (3중 LLM 합의 #3 / Codex 권고).**
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.150-green)]() [![tests](https://img.shields.io/badge/e2e-219%2F219-success)]() [![stress](https://img.shields.io/badge/stress--v95-21%2F21-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-80-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![repl-agent](https://img.shields.io/badge/agent--REPL-Hermes%2FOpenClaw_style-success)]() [![sandbox](https://img.shields.io/badge/runCommandSafe-cwd_jail%2Benv_scrub-success)]() [![observability](https://img.shields.io/badge/runs--jsonl-traceId%2Fduration%2Fmodel-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.152-green)]() [![tests](https://img.shields.io/badge/e2e-217%2F217-success)]() [![stress](https://img.shields.io/badge/stress--v97-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-82-blueviolet)]() [![main-push](https://img.shields.io/badge/release--main--push-auto-success)]() [![agents-multi](https://img.shields.io/badge/agents_multi-N_parallel_dispatch-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.150 AI Agent Reliability Harness + Sandbox ║
15
+ ║ v1.9.152 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.150';
9
+ const VERSION = '1.9.152';
10
10
  const MARK = '<!-- leerness:managed -->';
11
11
  const README_START = '<!-- leerness:project-readme:start -->';
12
12
  const README_END = '<!-- leerness:project-readme:end -->';
@@ -688,23 +688,30 @@ async function resolveInstallOptions(root, opts = {}) {
688
688
  // 1.9.148: 스킬 prompt 제거 (사용자 명시 요청) — leerness가 자동으로 공식 표준 스킬 5종 설치.
689
689
  // 필요할 때 사용자가 leerness skill install <id> 로 추가 가능.
690
690
  if (!explicitSkills) skills = parseSkillsValue('recommended');
691
- // 1.9.146: CLI 에이전트 활성화 선택 (사용자 명시 요청 #3 — Ollama 추가)
692
- // 설치 마지막에 .env.example 활성화 옵트인 키만 기록 (실제 토큰 입력은 사용자가 직접).
693
- let agentsOptIn = null;
691
+ // 1.9.151: CLI 에이전트 활성화 — 복수 선택 (사용자 명시 요청)
692
+ // _selectMany Space 토글, a 전체, n 해제, Enter 확정. 선택된 에이전트들만 .env.example에 LEERNESS_ENABLE_* 활성화.
693
+ let agentsOptIn = null; // string[] (다중) 또는 'none' (선택 안함)
694
694
  if (shouldAsk && !opts._skipAgentsPrompt) {
695
695
  if (useInteractive) {
696
- const aOpt = await _selectOne('CLI 에이전트 활성화 (sub-agent 위임용, opt-in)', [
697
- { label: '활성화 안함 (나중에 .env에서 직접 설정)', description: '권장토큰/모델은 사용자가 직접 관리', id: 'none' },
698
- { label: 'Claude (LEERNESS_CLAUDE_ENABLED=1)', description: 'claude CLI 또는 ANTHROPIC_API_KEY', id: 'claude' },
699
- { label: 'Ollama (LEERNESS_OLLAMA_ENABLED=1) — 로컬 LLM', description: 'http://localhost:11434 (모델: llama3/qwen 등)', id: 'ollama' },
700
- { label: '여러 (claude+codex+gemini+copilot+ollama)', description: '전체 후보 활성화 (각각 별도 토큰 필요)', id: 'all' }
701
- ], { defaultIndex: 0 });
702
- agentsOptIn = aOpt ? aOpt.id : 'none';
696
+ const picked = await _selectMany('CLI 에이전트 활성화 (복수 선택, Space 토글) — sub-agent 위임용', [
697
+ { label: 'Claude (ANTHROPIC_API_KEY 또는 claude CLI)', description: '추론력 최고 코드 작성/리뷰 기본', id: 'claude' },
698
+ { label: 'Codex (OpenAI codex CLI)', description: 'OpenAI 코드 모델', id: 'codex' },
699
+ { label: 'Gemini (gemini CLI)', description: 'Google 멀티모달 모델', id: 'gemini' },
700
+ { label: 'Copilot (gh extension)', description: 'GitHub Copilot CLI', id: 'copilot' },
701
+ { label: 'Ollama (로컬 LLM — llama3/qwen 등)', description: 'http://localhost:11434 무료/오프라인', id: 'ollama' }
702
+ ], { defaults: [] });
703
+ agentsOptIn = picked.length ? picked.map(p => p.id) : 'none';
703
704
  } else {
704
- log('\nCLI 에이전트 활성화 (opt-in, 나중에 .env에서 변경 가능):');
705
- log('1) 활성화 안함 2) Claude 3) Ollama 4) 전체');
706
- const a = await ask('선택 [1]: ');
707
- agentsOptIn = a === '2' ? 'claude' : a === '3' ? 'ollama' : a === '4' ? 'all' : 'none';
705
+ log('\nCLI 에이전트 활성화 (복수 선택 콤마로 구분, opt-in):');
706
+ log(' 1) claude 2) codex 3) gemini 4) copilot 5) ollama (예: 1,5 또는 all 또는 none)');
707
+ const a = (await ask('선택 [none]: ')).trim().toLowerCase();
708
+ if (a === 'all') agentsOptIn = ['claude', 'codex', 'gemini', 'copilot', 'ollama'];
709
+ else if (!a || a === 'none' || a === '0') agentsOptIn = 'none';
710
+ else {
711
+ const map = { '1': 'claude', '2': 'codex', '3': 'gemini', '4': 'copilot', '5': 'ollama' };
712
+ const picks = a.split(/[,\s]+/).map(t => map[t] || (['claude','codex','gemini','copilot','ollama'].includes(t) ? t : null)).filter(Boolean);
713
+ agentsOptIn = picks.length ? picks : 'none';
714
+ }
708
715
  }
709
716
  }
710
717
  // 1.9.146: 권한 모드 (사용자 명시 요청 #5 — agent IDE 모드 사전 prompt)
@@ -726,7 +733,24 @@ async function resolveInstallOptions(root, opts = {}) {
726
733
  permissionMode = a === '2' ? 'extended' : a === '3' ? 'full' : 'basic';
727
734
  }
728
735
  }
729
- return { lang, skills, agentsOptIn, permissionMode };
736
+ // 1.9.151: 모든 문항 종료 — REPL 모드 즉시 활성화 여부 (사용자 명시 요청)
737
+ // 선택된 에이전트가 있을 때만 표시. 설치 완료 후 install() 가 처리.
738
+ let startRepl = false;
739
+ const hasAgents = Array.isArray(agentsOptIn) && agentsOptIn.length > 0;
740
+ if (shouldAsk && hasAgents && !opts._skipReplPrompt) {
741
+ if (useInteractive) {
742
+ const rOpt = await _selectOne('설치 완료 후 REPL agent 모드를 즉시 시작할까요?', [
743
+ { label: '아니오 — 설치만 완료 (나중에 `leerness agent` 로 실행)', description: '권장 — 토큰/모델 설정 후 사용', id: 'no' },
744
+ { label: '예 — 설치 직후 REPL 모드 진입 (Hermes/OpenClaw 스타일)', description: 'Ollama 우선 — 가능하면 자동 모델 선택', id: 'yes' }
745
+ ], { defaultIndex: 0 });
746
+ startRepl = rOpt && rOpt.id === 'yes';
747
+ } else {
748
+ log('\n설치 완료 후 REPL agent 모드를 즉시 시작할까요? (y/N)');
749
+ const a = (await ask('선택 [N]: ')).trim().toLowerCase();
750
+ startRepl = a === 'y' || a === 'yes';
751
+ }
752
+ }
753
+ return { lang, skills, agentsOptIn, permissionMode, startRepl };
730
754
  }
731
755
 
732
756
  async function install(root, opts = {}) {
@@ -753,7 +777,11 @@ async function install(root, opts = {}) {
753
777
  log(`Target: ${root}`);
754
778
  log(`Language: ${lang}`);
755
779
  log(`Skills: ${skills.length ? skills.join(', ') : 'none (건너뜀)'}`);
756
- if (resolved.agentsOptIn && resolved.agentsOptIn !== 'none') log(`Agents 활성화: ${resolved.agentsOptIn}`);
780
+ if (resolved.agentsOptIn && resolved.agentsOptIn !== 'none') {
781
+ const list = Array.isArray(resolved.agentsOptIn) ? resolved.agentsOptIn.join(', ') : String(resolved.agentsOptIn);
782
+ log(`Agents 활성화: ${list}`);
783
+ }
784
+ if (resolved.startRepl) log(`REPL 자동 시작: 예 (설치 완료 후 \`leerness agent\` 진입)`);
757
785
  if (resolved.permissionMode) log(`Agent 권한 모드: ${resolved.permissionMode}`);
758
786
  // 1.9.10: 스킬 카탈로그 출처 안내
759
787
  if (SKILLPACK_SOURCE === 'builtin') log(`Skill catalog source: builtin (leerness-skillpack 미설치 — \`npm i leerness-skillpack\`로 확장 가능)`);
@@ -799,9 +827,15 @@ async function install(root, opts = {}) {
799
827
  // 1.9.149: agent REPL 세션 + observability runs 비공개 (대화 내용 보호)
800
828
  '.harness/agent-sessions/','.harness/runs/'
801
829
  ]);
802
- // 1.9.146: agentsOptIn 선택에 따라 LEERNESS_ENABLE_* 플래그 자동 설정 (사용자 명시 요청 #3 Ollama 추가)
830
+ // 1.9.151: agentsOptIn 복수 선택 지원 배열 또는 'none' 또는 'all' (back-compat) 모두 처리
803
831
  const a = resolved.agentsOptIn || 'none';
804
- const enable = (cli) => a === 'all' || a === cli;
832
+ const enabledSet = (() => {
833
+ if (Array.isArray(a)) return new Set(a);
834
+ if (a === 'all') return new Set(['claude', 'codex', 'gemini', 'copilot', 'ollama']);
835
+ if (a === 'none' || !a) return new Set();
836
+ return new Set([a]); // back-compat: 단일 문자열
837
+ })();
838
+ const enable = (cli) => enabledSet.has(cli);
805
839
  mergeLinesFile(path.join(root, '.env.example'), [
806
840
  '# Leerness uses environment variable names only. Do not store real secrets here.',
807
841
  'LEERNESS_NPM_TOKEN=','LEERNESS_GITHUB_TOKEN=',
@@ -809,11 +843,11 @@ async function install(root, opts = {}) {
809
843
  `LEERNESS_OLLAMA_BASE_URL=${enable('ollama') ? 'http://localhost:11434' : ''}`,
810
844
  '# 선택. 기본 모델 (orchestrate --model 로 override 가능). 예: llama3 / qwen2.5-coder / gpt-oss',
811
845
  'LEERNESS_OLLAMA_MODEL=',
812
- '# 1.9.30+1.9.146 — 외부 AI CLI 활성화 플래그. 1=활성, 0/미설정=비활성. 메인 에이전트가 sub-agent 분배 시 활성 CLI들에 작업 위임 가능.',
846
+ '# 1.9.30+1.9.146+1.9.151 — 외부 AI CLI 활성화 플래그 (복수 선택). 1=활성, 0/미설정=비활성. 메인 에이전트가 sub-agent 분배 시 활성 CLI들에 작업 위임 가능.',
813
847
  `LEERNESS_ENABLE_CLAUDE=${enable('claude') ? 1 : 0}`,
814
- `LEERNESS_ENABLE_CODEX=${enable('all') ? 1 : 0}`,
815
- `LEERNESS_ENABLE_GEMINI=${enable('all') ? 1 : 0}`,
816
- `LEERNESS_ENABLE_COPILOT=${enable('all') ? 1 : 0}`,
848
+ `LEERNESS_ENABLE_CODEX=${enable('codex') ? 1 : 0}`,
849
+ `LEERNESS_ENABLE_GEMINI=${enable('gemini') ? 1 : 0}`,
850
+ `LEERNESS_ENABLE_COPILOT=${enable('copilot') ? 1 : 0}`,
817
851
  `LEERNESS_ENABLE_OLLAMA=${enable('ollama') ? 1 : 0}`,
818
852
  '# 1.9.42 — agentskills.io 공개 표준 스킬 자동 탐색 (opt-in). URL 설정 시 `leerness skill discover` 사용 가능.',
819
853
  '# 예: LEERNESS_SKILL_DISCOVER_URL=https://agentskills.io/llms.txt',
@@ -869,6 +903,15 @@ async function install(root, opts = {}) {
869
903
  // 1.9.148: 1.9.32 중복 prompt 제거 (사용자 명시 — CLI 에이전트 prompt 중복).
870
904
  // resolveInstallOptions (1.9.146) 가 이미 모든 prompt 모은 위치에 통합된 4지선다 prompt 있음.
871
905
  // 별도 setupAgents 명령은 사용자가 명시적으로 `leerness setup-agents` 호출 시에만.
906
+ // 1.9.151: 설치 완료 직후 — startRepl 선택 시 REPL agent 모드 즉시 진입 (사용자 명시 요청)
907
+ if (resolved.startRepl && !opts.migration && process.stdin.isTTY && process.env.LEERNESS_NO_PROMPT !== '1') {
908
+ log('');
909
+ log('🚀 설치 완료 — REPL agent 모드를 시작합니다 (1.9.149 Hermes/OpenClaw 스타일)...');
910
+ log('');
911
+ try {
912
+ await _agentRepl(root, { provider: 'ollama', role: 'actor' });
913
+ } catch (e) { warn('REPL 진입 실패: ' + e.message); }
914
+ }
872
915
  }
873
916
  }
874
917
 
@@ -2871,10 +2914,18 @@ function handoff(root) {
2871
2914
  const lessons = exists(lessonsPath(root)) ? (read(lessonsPath(root)).match(/^### \d{4}-\d{2}-\d{2}/gm) || []).length : 0;
2872
2915
  parts.push(`🧠 mem T${inProgressTasks}/D${decisions}/R${rulesActive}/P${planMilestones}/L${lessons}`);
2873
2916
  } catch {}
2917
+ // 8) 1.9.152: 활성 외부 AI CLI 카운트 (1.9.151 복수 선택 결과 반영) — 메인 에이전트가 sub-agent 분배 가능성 즉시 인지
2918
+ try {
2919
+ _loadEnvFile(root); // .env 자동 로드 (handoff 컨텍스트)
2920
+ const ready = EXTERNAL_AGENTS.map(a => _checkAgent(a)).filter(c => c.status === 'ready');
2921
+ if (ready.length > 0) {
2922
+ parts.push(`🤖 agents ${ready.length} (${ready.map(c => c.id).join(',')})`);
2923
+ }
2924
+ } catch {}
2874
2925
  if (parts.length) {
2875
2926
  const isTty = process.stdout && process.stdout.isTTY;
2876
2927
  const cy = s => isTty ? `\x1b[36m${s}\x1b[0m` : s;
2877
- log(cy(`📊 헤드라인 (1.9.81/93/113): ${parts.join(' · ')}`));
2928
+ log(cy(`📊 헤드라인 (1.9.81/93/113/152): ${parts.join(' · ')}`));
2878
2929
  }
2879
2930
  } catch {}
2880
2931
  }
@@ -4973,6 +5024,17 @@ async function setupAgentsCmd(root, opts = {}) {
4973
5024
  log(' 다음: leerness agents list / leerness agents quota');
4974
5025
  }
4975
5026
 
5027
+ // 1.9.152: 단일 agent dispatch 명령 빌더 — agents dispatch (단일) + agents multi (복수) 가 공유
5028
+ function _dispatchCommand(agentId, task, writeMode) {
5029
+ const q = String(task || '').replace(/"/g, '\\"');
5030
+ if (agentId === 'claude') return `claude ${writeMode ? '--print --dangerously-skip-permissions' : '--print'} "${q}"`;
5031
+ if (agentId === 'codex') return `codex ${writeMode ? 'exec --skip-git-repo-check --dangerously-bypass-approvals-and-sandbox' : 'exec --skip-git-repo-check'} "${q}"`;
5032
+ if (agentId === 'gemini') return `gemini ${writeMode ? '-p --yolo' : '-p'} "${q}"`;
5033
+ if (agentId === 'copilot') return `gh copilot suggest "${q}"`;
5034
+ if (agentId === 'ollama') return `# ollama — leerness agent "${q}" --provider ollama 로 직접 호출 (REPL: leerness agent)`;
5035
+ return `# ${agentId}: 명령 빌더 미정의`;
5036
+ }
5037
+
4976
5038
  function agentsCmd(root, sub, ...args) {
4977
5039
  root = absRoot(root || process.cwd());
4978
5040
  // .env 자동 로드 (1.9.22)
@@ -5016,11 +5078,61 @@ function agentsCmd(root, sub, ...args) {
5016
5078
  return agentsCmd(root, 'list'); // 비-JSON은 list와 동일
5017
5079
  }
5018
5080
 
5081
+ // 1.9.152: agents multi — 1.9.151 install 복수 선택된 ready 에이전트들에 일괄 dispatch 명령 생성
5082
+ // 단일 task → 활성 N개 에이전트 동시 dispatch 명령들. 사용자가 한 번에 복사 실행하거나 메인 에이전트가 spawn.
5083
+ if (sub === 'multi') {
5084
+ const task = args.filter(x => !x.startsWith('-')).join(' ').trim() || arg('--task', null);
5085
+ if (!task) { fail('multi "<task>" 또는 --task 필요'); return process.exit(1); }
5086
+ const onlyArg = arg('--only', null); // 'claude,codex' 처럼 콤마 구분 — 활성 중에서 추가 필터
5087
+ const writeMode = has('--write');
5088
+ const checks = EXTERNAL_AGENTS.map(a => ({ def: a, status: _checkAgent(a) }));
5089
+ let ready = checks.filter(x => x.status.status === 'ready');
5090
+ if (onlyArg) {
5091
+ const wanted = new Set(onlyArg.split(/[,\s]+/).filter(Boolean));
5092
+ ready = ready.filter(x => wanted.has(x.def.id));
5093
+ }
5094
+ if (!ready.length) {
5095
+ fail('활성 (ready) 에이전트 없음 — `leerness agents list` 로 확인. 1.9.151 install 흐름에서 복수 선택 후 .env 활성화 필요.');
5096
+ return process.exit(1);
5097
+ }
5098
+ if (has('--json')) {
5099
+ log(JSON.stringify({
5100
+ task, count: ready.length,
5101
+ agents: ready.map(x => ({ id: x.def.id, version: x.status.version })),
5102
+ commands: ready.map(x => _dispatchCommand(x.def.id, task, writeMode))
5103
+ }, null, 2));
5104
+ return;
5105
+ }
5106
+ log(`# leerness agents multi (1.9.152) — ${ready.length}개 활성 에이전트 일괄 dispatch`);
5107
+ log(`task: ${task.slice(0, 120)}${task.length > 120 ? '…' : ''}`);
5108
+ log(`mode: ${writeMode ? '✏ write (파일 수정 가능)' : '🔒 read-only (분석 전용, 안전)'}`);
5109
+ log(`대상: ${ready.map(x => x.def.id).join(', ')}`);
5110
+ log('');
5111
+ log('## 각 에이전트 실행 명령 (사용자가 병렬 실행 또는 메인 에이전트가 spawn)');
5112
+ log('');
5113
+ for (const { def, status } of ready) {
5114
+ log(`### [${def.id}] (v${status.version || '?'})`);
5115
+ log('```sh');
5116
+ log(_dispatchCommand(def.id, task, writeMode));
5117
+ log('```');
5118
+ log('');
5119
+ }
5120
+ log('## 정책 (1.9.152)');
5121
+ log(` - leerness는 외부 CLI를 자동 호출하지 않음 (사용자/메인 에이전트가 명시적으로 실행)`);
5122
+ log(` - 메인 에이전트(Claude)가 위 ${ready.length}개 명령을 보고 ${ready.length}개 sub-agent로 spawn — 결과 합의/투표로 가장 안정적인 답 선택`);
5123
+ log(` - 활성 에이전트 변경: \`.env\`에서 LEERNESS_ENABLE_<CLI>=1/0 또는 \`leerness setup-agents\` 재실행`);
5124
+ log(` - quota 체크: \`leerness agents quota\``);
5125
+ return;
5126
+ }
5019
5127
  if (sub === 'dispatch') {
5020
5128
  const task = args.filter(x => !x.startsWith('-')).join(' ').trim() || arg('--task', null);
5021
5129
  const target = arg('--to', null);
5022
5130
  if (!task) { fail('dispatch "<task>" 또는 --task 필요'); return process.exit(1); }
5023
- if (!target) { fail('--to <agent_id> 필요 (claude/codex/gemini/copilot)'); return process.exit(1); }
5131
+ // 1.9.152: --multi 또는 --to=all 또는 --to 없음 + 활성 ≥2 → multi 모드로 routing
5132
+ if (has('--multi') || target === 'all' || target === '*') {
5133
+ return agentsCmd(root, 'multi', ...args);
5134
+ }
5135
+ if (!target) { fail('--to <agent_id> 필요 (claude/codex/gemini/copilot) — 활성 전체에 일괄 분배는 `leerness agents multi`'); return process.exit(1); }
5024
5136
  const agentDef = EXTERNAL_AGENTS.find(a => a.id === target);
5025
5137
  if (!agentDef) { fail(`알 수 없는 agent: ${target}`); return process.exit(1); }
5026
5138
  // 1.9.36: 작업 유형 키워드 분석 → 최적 CLI 추천 (ready 체크 전에 출력 — 비활성이어도 추천)
@@ -8193,48 +8305,10 @@ function autoUpdateInstall(root) {
8193
8305
  ok('/update slash command added');
8194
8306
  }
8195
8307
 
8196
- // ===== ViewWork hook =====
8197
- function viewworkEmit(root, ev) {
8198
- root = absRoot(root);
8199
- const dir = path.join(root, '.viewwork');
8200
- if (!exists(dir)) return;
8201
- const file = path.join(dir, 'agent-events.jsonl');
8202
- const line = JSON.stringify({
8203
- at: Date.now(),
8204
- agent: ev.agent || 'leerness',
8205
- agentKind: ev.agentKind || 'system',
8206
- action: ev.action || 'task',
8207
- path: ev.path || '/.harness',
8208
- tool: ev.tool || 'leerness-cli',
8209
- toolKind: ev.toolKind || 'task',
8210
- note: ev.note || ''
8211
- }) + '\n';
8212
- try { fs.appendFileSync(file, line, 'utf8'); } catch {}
8213
- }
8214
-
8215
- function viewworkInstall(root) {
8216
- root = absRoot(root);
8217
- const dir = path.join(root, '.viewwork');
8218
- mkdirp(dir);
8219
- if (!exists(path.join(dir, 'agent-events.jsonl'))) writeUtf8(path.join(dir, 'agent-events.jsonl'), '');
8220
- if (!exists(path.join(dir, 'config.json'))) writeUtf8(path.join(dir, 'config.json'), JSON.stringify({ schemaVersion: 2 }, null, 2) + '\n');
8221
- if (!exists(path.join(dir, 'version'))) writeUtf8(path.join(dir, 'version'), '2\n');
8222
- const settingsDir = path.join(root, '.claude');
8223
- mkdirp(settingsDir);
8224
- const settingsFile = path.join(settingsDir, 'settings.local.json');
8225
- let settings = {};
8226
- if (exists(settingsFile)) { try { settings = JSON.parse(read(settingsFile)); } catch {} }
8227
- settings.hooks = settings.hooks || {};
8228
- if (!settings.hooks.Stop) settings.hooks.Stop = [];
8229
- if (!settings.hooks.Stop.some(h => h.command && h.command.includes('leerness viewwork'))) {
8230
- settings.hooks.Stop.push({ matcher: '*', command: 'leerness viewwork emit . --action task --note "claude session stop"' });
8231
- }
8232
- writeUtf8(settingsFile, JSON.stringify(settings, null, 2) + '\n');
8233
- writeUtf8(path.join(root, '.claude/commands/viewwork-ping.md'),
8234
- `# /viewwork-ping\n\nViewWork 이벤트를 수동으로 기록합니다.\n\n\`\`\`\n!leerness viewwork emit . --action note --note \"manual ping\"\n\`\`\`\n`);
8235
- ok('viewwork hook installed');
8236
- ok('claude .claude/settings.local.json updated (Stop hook adds a viewwork event)');
8237
- }
8308
+ // 1.9.151: ViewWork hook 제거 (사용자 명시 — leerness와 무관한 외부 도구)
8309
+ // 이전 1.9.0~1.9.150 에서 .viewwork/ 디렉토리에 hook 으로 이벤트 기록했으나, ViewWork는 leerness 의존 산출물이
8310
+ // 아닌 별도 도구임. 사용자가 직접 ViewWork 를 사용하지 않으면 leerness 가 이를 강제할 이유가 없음.
8311
+ // 기존 프로젝트의 .viewwork/ 폴더는 그대로 유지 (leerness 가 삭제하지 않음 — 사용자 책임).
8238
8312
 
8239
8313
  // 1.9.37: drift detection — 메타파일 staleness 측정으로 "leerness 점점 안 쓰는" 현상 감지
8240
8314
  function driftCheckCmd(root, opts = {}) {
@@ -11146,7 +11220,7 @@ function reuseAutodetectCmd(root) {
11146
11220
  }
11147
11221
 
11148
11222
  function help() {
11149
- 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 viewwork install [path]\n leerness viewwork emit [path] [--action a] [--note n] [--agent x] [--tool t]\n leerness route <task-type>\n leerness self check [path]\n leerness readme sync [path]\n leerness consistency check [path]\n leerness consistency merge-design-guide [path]\n leerness plan show|init|add|drop|progress|sync [args]\n leerness task list|add|update|drop|fix-evidence|relink [args]\n leerness skill list|info <name>\n leerness skill learn <id> --doc <url> --command "..." --capability "..." [--note ...]\n leerness skill use <id> [--note ...]\n leerness skill optimize <id> --before "..." --after "..." [--note ...]\n leerness skill remove <id>\n leerness skill consolidate [--threshold 0.3]\n leerness gate [path] # verify+audit+scan+encoding+lazy
11223
+ 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
11150
11224
  leerness retro [path] [--days 7] [--all-apps] [--include p1,p2] [--json] # 회고 (1.9.13~1.9.16)
11151
11225
  leerness insights [path] [--all-apps] [--include p1,p2] [--json] # 누적 통계 (1.9.13~1.9.16)
11152
11226
  leerness brainstorm "<주제>" [--all-apps] [--include p1,p2] [--json] # 브레인스토밍 (1.9.13~1.9.16)
@@ -11248,9 +11322,8 @@ async function main() {
11248
11322
  if (cmd === 'whats-new') return whatsNewCmd(args[1] || arg('--path', process.cwd()));
11249
11323
  if (cmd === 'reuse' && args[1] === 'autodetect') return reuseAutodetectCmd(args[2] || arg('--path', process.cwd()));
11250
11324
  if (cmd === 'setup-agents' || cmd === 'setup' && args[1] === 'agents') return await setupAgentsCmd(args[1] && args[1] !== 'agents' ? args[1] : (args[2] || process.cwd()));
11251
- if (cmd === 'session' && args[1] === 'close') { const r = sessionClose(args[2] || process.cwd(), { json: has('--json') }); viewworkEmit(args[2] || process.cwd(), { action: 'task', tool: 'session-close', note: 'session close' }); return r; }
11252
- if (cmd === 'viewwork' && args[1] === 'install') return viewworkInstall(args[2] || process.cwd());
11253
- if (cmd === 'viewwork' && args[1] === 'emit') return viewworkEmit(args[2] || process.cwd(), { action: arg('--action','task'), note: arg('--note',''), agent: arg('--agent','leerness'), tool: arg('--tool','leerness-cli') });
11325
+ if (cmd === 'session' && args[1] === 'close') return sessionClose(args[2] || process.cwd(), { json: has('--json') });
11326
+ // 1.9.151: viewwork 명령 제거 (사용자 명시 leerness 무관). session close viewworkEmit 콜도 함께 제거.
11254
11327
  if (cmd === 'route') return route(args[1] || 'planning');
11255
11328
  if (cmd === 'self' && args[1] === 'check') return await selfCheck(absRoot(args[2] || process.cwd()));
11256
11329
  if (cmd === 'self' && args[1] === 'migrate') return log('Run: npx --yes leerness@latest migrate . --dry-run, then migrate without --dry-run after review.');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.150",
3
+ "version": "1.9.152",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -145,8 +145,7 @@ total++;
145
145
  if (!ok) failed++;
146
146
  }
147
147
 
148
- run('viewwork install', ['viewwork', 'install', tmp]);
149
- run('viewwork emit', ['viewwork', 'emit', tmp, '--action', 'note', '--note', 'e2e ping']);
148
+ // 1.9.151: viewwork 명령 제거 (사용자 명시 — leerness 와 무관)
150
149
  run('route planning', ['route', 'planning']);
151
150
  run('route bugfix', ['route', 'bugfix']);
152
151
  run('skill list', ['skill', 'list']);