leerness 1.9.293 → 1.9.295

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,49 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.295 — 2026-06-04 — UR-0025 (4단계): 잔여 정적 카탈로그 → lib/catalogs.js — 데이터 추출 단계 완료 ✅
4
+
5
+ **🧩 모놀리스 분리 4단계 — 잔여 정적 데이터 카탈로그 5종을 비파괴 분리. 이로써 leerness 의 모든 정적 데이터 카탈로그가 lib/ 모듈로 외부화됨(데이터 추출 단계 완료).**
6
+
7
+ ### 배경
8
+ GPT-5.5 + Codex 공통 지적(단일 대형 `bin/harness.js`)을 점진 분리 중. 1단계(pure-utils 14함수)·2단계(agent-registry)·3단계(role-catalog)에 이어 4단계로 capability/adapter/reuse 정적 카탈로그를 분리.
9
+
10
+ ### 구현 (UR-0025 4단계)
11
+ 1. **`lib/catalogs.js` 신설** — 5개 순수 데이터 카탈로그 이동(53줄):
12
+ - `CAPABILITY_SURFACE`(6영역 위험/optOut 공개) + `POWERFUL_COMMANDS`(주의 명령 7종)
13
+ - `ADAPTERS`(도구별 어댑터 9종 — claude/cursor/codex/goose/opencode 등)
14
+ - `REUSE_CATEGORIES`(OSS 빌드 vs 재사용 게이트 15종) + `REUSE_CHECKLIST`(적합성 6항목)
15
+ 2. **비파괴 require-based 분리** — `capabilitiesCmd`/`adapterCmd`/`_reuseDetect`/`reuseCheckCmd` 소비처는 동일 바인딩. 런타임 변형 0(모두 읽기 전용 검증). 상단 단일 require + 원위치 마커 주석.
16
+ 3. **harness.js 21607→21560줄**. **lib 4모듈 체제 완성**: pure-utils(163)+agent-registry(147)+role-catalog(103)+catalogs(60) = 473줄 외부화.
17
+ 4. selftest 42→43 (5 카탈로그 동일참조 단일출처 + 인라인 제거 검증) · e2e 239→240 (모듈 standalone + `capabilities`/`reuse-check` 동작 회귀).
18
+
19
+ ### 검증
20
+ - **selftest 43/43 PASS** · **E2E 240/240 PASS** (회귀 0).
21
+ - 기존 selftest(CAPABILITY_SURFACE 6영역 + _reuseDetect 카테고리 + ADAPTERS .mcp.json)가 추출 정합성 즉시 검증.
22
+ - `capabilities --json` / `reuse-check "JWT 인증" --json` 정상 동작(모듈 분리 후 회귀 없음).
23
+ - **UR-0025 정적 데이터 카탈로그 추출 단계 완료** — 이후는 기능(command handler) 모듈화 검토 영역(별도 신중 진행).
24
+
25
+ ## 1.9.294 — 2026-06-04 — UR-0025 (3단계): 역할/모델 카탈로그 → lib/role-catalog.js 모듈 분리
26
+
27
+ **🧩 모놀리스 분리 3단계 — 역할/모델 레지스트리(4개 카탈로그)를 비파괴 데이터 모듈로 분리. 1.9.274(pure-utils)/1.9.291(agent-registry) 패턴 계승.**
28
+
29
+ ### 배경
30
+ GPT-5.5 + Codex 두 리뷰 공통 지적인 단일 대형 `bin/harness.js`(#1 유지보수 이슈)를 점진 분리 중. 1단계(pure-utils 14함수)·2단계(agent-registry)에 이어 3단계로 역할/모델 데이터 카탈로그를 분리.
31
+
32
+ ### 구현 (UR-0025 3단계)
33
+ 1. **`lib/role-catalog.js` 신설** — 4개 순수 데이터 카탈로그 이동(96줄):
34
+ - `_PROVIDER_MODEL_CATALOG`(provider×모델 10종, REPL Tab cycle)
35
+ - `_AGENT_ROLE_PROMPTS`(planner/reviewer/actor 역할 프롬프트)
36
+ - `ROLE_CATALOG`(7종 역할: commander/reviewer/coder/architect/designer/debugger/dispatcher)
37
+ - `_ROLE_ALIASES`(한국어 별칭 21종)
38
+ 2. **비파괴 require-based 분리** — `_normalizeRole`/`_pickModel`/REPL 소비처는 동일 바인딩 사용. 런타임 변형 0(모두 읽기 전용 검증).
39
+ 3. **harness.js 21700→21606줄** (94줄 감소). 누적: pure-utils(163)+agent-registry(147)+role-catalog(103) 3 모듈.
40
+ 4. selftest 41→42 (4개 카탈로그 동일참조 단일출처 + 인라인 제거 검증) · e2e 238→239 (모듈 standalone + harness 인라인 제거 + `roles list` 동작 회귀).
41
+
42
+ ### 검증
43
+ - **selftest 42/42 PASS** · **E2E 239/239 PASS** (회귀 0).
44
+ - 기존 selftest(ROLE_CATALOG 7종 + _normalizeRole 한국어별칭 + _pickModel top/code/fast)가 추출 정합성 즉시 검증.
45
+ - `roles list --json` 정상 동작 확인(모듈 분리 후 회귀 없음).
46
+
3
47
  ## 1.9.293 — 2026-06-04 — idempotency auto-fix + progress-tracker 복제 버그 근본 수정 🐛
4
48
 
5
49
  **🔁 idempotency 위반(task/요청 중복)을 자동 회복하는 `--auto-fix` 신설 + 중복이 매 세션 누적되던 근본 버그(헤더 유실 시 전체 복제) 수정. 라이브 프로젝트 progress-tracker.md 69행→6행 복구.**
package/README.md CHANGED
@@ -3,7 +3,7 @@
3
3
  > **AI 코딩 에이전트의 거짓 완료·중복·망각·충돌을 막아주는 검수·기억·협업 CLI 하네스.**
4
4
  > **A CLI harness that stops AI coding agents from faking completion, duplicating work, forgetting context, and colliding.**
5
5
 
6
- [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.293-green)]() [![tests](https://img.shields.io/badge/e2e-238%2F238-success)]() [![selftest](https://img.shields.io/badge/selftest-41-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-80-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
6
+ [![npm](https://img.shields.io/badge/npm-leerness-blue)](https://www.npmjs.com/package/leerness) [![version](https://img.shields.io/badge/version-1.9.295-green)]() [![tests](https://img.shields.io/badge/e2e-240%2F240-success)]() [![selftest](https://img.shields.io/badge/selftest-43-success)]() [![mcp](https://img.shields.io/badge/MCP--tools-80-brightgreen)]() [![providers](https://img.shields.io/badge/AI_providers-10-brightgreen)]() [![license](https://img.shields.io/badge/license-MIT-lightgrey)]()
7
7
 
8
8
  ```
9
9
  ╔══════════════════════════════════════════════════════════════╗
@@ -471,7 +471,7 @@ MIT — © leerness contributors
471
471
  <!-- leerness:project-readme:start -->
472
472
  ## Leerness Project Harness
473
473
 
474
- 이 프로젝트는 Leerness v1.9.293 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
474
+ 이 프로젝트는 Leerness v1.9.295 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
475
475
 
476
476
  ### Core Commands
477
477
 
@@ -513,7 +513,7 @@ leerness memory restore decision <date|title>
513
513
 
514
514
  ### MCP server (외부 AI 통합)
515
515
 
516
- Leerness v1.9.293는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **80개 도구**를 노출:
516
+ Leerness v1.9.295는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **80개 도구**를 노출:
517
517
 
518
518
  ```jsonc
519
519
  // 카테고리별
@@ -534,7 +534,7 @@ Leerness v1.9.293는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
534
534
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
535
535
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
536
536
 
537
- 현재 누적: **70 라운드 (1.9.40 → 1.9.293)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
537
+ 현재 누적: **70 라운드 (1.9.40 → 1.9.295)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
538
538
 
539
539
  ### 성능 가이드 (1.9.140 측정)
540
540
 
@@ -572,6 +572,6 @@ leerness release pack --close --auto-main-push
572
572
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
573
573
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
574
574
 
575
- Last synced by Leerness v1.9.293: 2026-06-03
575
+ Last synced by Leerness v1.9.295: 2026-06-04
576
576
  <!-- leerness:project-readme:end -->
577
577
 
package/bin/harness.js CHANGED
@@ -9,8 +9,10 @@ const readline = require('readline');
9
9
  // 1.9.274 (UR-0025 1단계): 순수 유틸 함수 모듈 분리 (require-based, 비파괴). selftest 7종이 동작 검증.
10
10
  const { _isSecretKey, compareVer, parseHarnessVersion, _classifyCJK, _riskLabel, _detectSystemLang, _parseSlashFromHelp,
11
11
  PERMISSION_TIERS, _tierRank, _requiredTier, _policyAllows, _resolveNpmTag, _mcpJsonContent, _newRunRecord } = require('../lib/pure-utils');
12
+ // 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
13
+ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST } = require('../lib/catalogs');
12
14
 
13
- const VERSION = '1.9.293';
15
+ const VERSION = '1.9.295';
14
16
 
15
17
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
16
18
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -3000,6 +3002,8 @@ function _selfTestCases() {
3000
3002
  { name: 'lib/agent-registry: EXTERNAL_AGENTS/AGENT_SLASH_COMMANDS 모듈 단일출처 분리 (UR-0025 1.9.291)', run: () => { const m = require('../lib/agent-registry'); return m.EXTERNAL_AGENTS === EXTERNAL_AGENTS && m.AGENT_SLASH_COMMANDS === AGENT_SLASH_COMMANDS && m.EXTERNAL_AGENTS.length === 10 && Object.keys(m.AGENT_SLASH_COMMANDS).length === 9 && !/const EXTERNAL_AGENTS = \[/.test(read(__filename)); } },
3001
3003
  { name: 'get_project_context: MCP 시맨틱 verb 등록 + CLI context 디스패치 (UR-0031 1.9.292)', run: () => { const src = read(__filename); const mcpDef = /name: 'leerness_get_project_context'/.test(src); const mcpCase = /case 'leerness_get_project_context':[\s\S]*?cliArgs = \['context'/.test(src); const cliDisp = /if \(cmd === 'context'\)\s+return contextCmd/.test(src); return typeof contextCmd === 'function' && mcpDef && mcpCase && cliDisp && _mcpToolCount() >= 80; } },
3002
3004
  { name: '_canonicalProgressHeader + idempotency auto-fix (근본 복제버그 fix 1.9.293)', run: () => { const h = _canonicalProgressHeader(); const headerOk = /leernessRole: progress-tracker/.test(h) && /\| ID \| Status \| Request \|/.test(h) && /\|---\|/.test(h); const src = read(__filename); const fnOk = typeof _autoFixIdempotency === 'function'; const noWholeTextFallback = /if \(idx < 0\) return _canonicalProgressHeader\(\);/.test(src); const driftWired = /_autoFixIdempotency\(root\)/.test(src) && /idempotency 중복/.test(src); return headerOk && fnOk && noWholeTextFallback && driftWired; } },
3005
+ { name: 'lib/role-catalog: ROLE/PROVIDER/ALIASES/PROMPTS 모듈 단일출처 분리 (UR-0025 1.9.294)', run: () => { const m = require('../lib/role-catalog'); return m.ROLE_CATALOG === ROLE_CATALOG && m._PROVIDER_MODEL_CATALOG === _PROVIDER_MODEL_CATALOG && m._ROLE_ALIASES === _ROLE_ALIASES && m._AGENT_ROLE_PROMPTS === _AGENT_ROLE_PROMPTS && Object.keys(m.ROLE_CATALOG).length === 7 && Object.keys(m._PROVIDER_MODEL_CATALOG).length === 10 && !/const ROLE_CATALOG = \{/.test(read(__filename)); } },
3006
+ { name: 'lib/catalogs: CAPABILITY/ADAPTERS/REUSE 모듈 단일출처 분리 (UR-0025 1.9.295)', run: () => { const m = require('../lib/catalogs'); return m.CAPABILITY_SURFACE === CAPABILITY_SURFACE && m.ADAPTERS === ADAPTERS && m.REUSE_CATEGORIES === REUSE_CATEGORIES && m.REUSE_CHECKLIST === REUSE_CHECKLIST && m.POWERFUL_COMMANDS === POWERFUL_COMMANDS && Object.keys(m.CAPABILITY_SURFACE).length === 6 && !/const CAPABILITY_SURFACE = \{/.test(read(__filename)); } },
3003
3007
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3004
3008
  ];
3005
3009
  }
@@ -17812,102 +17816,8 @@ function runsShowCmd(root, id) {
17812
17816
  if (!exists(fp)) return fail(`run 없음: ${id}`);
17813
17817
  log(read(fp));
17814
17818
  }
17815
- // 1.9.155: provider 추천 모델 카탈로그 REPL :models 명령에서 노출 (실제 가용성은 사용자 CLI 결정)
17816
- // 1.9.170: provider × 실제 모델 catalog 확장 (Tab cycle 지원 — 사용자 명시 요청)
17817
- // 각 provider 의 최신 실제 모델 ID 를 반영. Tab/Shift+Tab 키로 cycle.
17818
- const _PROVIDER_MODEL_CATALOG = {
17819
- claude: [
17820
- { id: 'claude-opus-4-7', note: '최신 1M context (Anthropic Opus 4.7)' },
17821
- { id: 'claude-opus-4-5', note: '안정 Opus 4.5' },
17822
- { id: 'claude-sonnet-4-7', note: '균형형 — Sonnet 4.7 (속도/품질)' },
17823
- { id: 'claude-sonnet-4-5', note: 'Sonnet 4.5 안정' },
17824
- { id: 'claude-haiku-4-5', note: '빠름 — Haiku 4.5' }
17825
- ],
17826
- codex: [
17827
- { id: 'gpt-5.5', note: 'OpenAI 최신 추론 모델' },
17828
- { id: 'gpt-5.4', note: 'OpenAI 안정 (이전 세대)' },
17829
- { id: 'gpt-5', note: 'OpenAI gpt-5 (base)' },
17830
- { id: 'gpt-5-codex', note: '코드 특화 (Codex)' },
17831
- { id: 'o4-mini', note: '빠른 reasoning' }
17832
- ],
17833
- agy: [
17834
- { id: 'antigravity-pro', note: 'Antigravity 최고급 (1M+ context)' },
17835
- { id: 'antigravity-flash', note: '빠른 응답' },
17836
- { id: 'antigravity-experimental', note: '실험적 (사용 가능 시)' }
17837
- ],
17838
- grok: [
17839
- { id: 'grok-beta', note: 'xAI 최신 (1.9.268 provider 승격)' },
17840
- { id: 'grok-2', note: 'xAI Grok 2' },
17841
- { id: 'grok-2-mini', note: '빠른 응답' }
17842
- ],
17843
- // 1.9.277: 신규 provider 4종 모델 (provider-agnostic 인 도구는 default + 설정 안내)
17844
- opencode: [
17845
- { id: 'default', note: 'opencode 설정 모델 (opencode auth/models 로 provider별 지정)' },
17846
- { id: 'anthropic/claude-sonnet', note: 'Anthropic 경유' },
17847
- { id: 'openai/gpt-5', note: 'OpenAI 경유' }
17848
- ],
17849
- qwen: [
17850
- { id: 'qwen3-coder-plus', note: '코드 특화 최신' },
17851
- { id: 'qwen-max', note: '최고 성능' },
17852
- { id: 'qwen-plus', note: '균형' },
17853
- { id: 'qwen-turbo', note: '빠름' }
17854
- ],
17855
- aider: [
17856
- { id: 'sonnet', note: 'Anthropic Sonnet (aider --model)' },
17857
- { id: 'gpt-5', note: 'OpenAI' },
17858
- { id: 'deepseek', note: 'DeepSeek (가성비)' },
17859
- { id: 'o1-mini', note: '빠른 reasoning' }
17860
- ],
17861
- goose: [
17862
- { id: 'default', note: 'goose configure 로 설정한 provider/model' },
17863
- { id: 'claude-sonnet', note: 'Anthropic 경유' },
17864
- { id: 'gpt-5', note: 'OpenAI 경유' }
17865
- ],
17866
- copilot: [
17867
- { id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
17868
- ],
17869
- ollama: [
17870
- { id: 'llama3', note: 'Meta — :models 로 실시간 조회 권장' },
17871
- { id: 'qwen2.5-coder', note: 'Alibaba — 코드 특화' },
17872
- { id: 'gpt-oss', note: 'OpenAI 오픈소스' },
17873
- { id: 'deepseek-coder-v2', note: 'DeepSeek 코드 모델' }
17874
- ]
17875
- };
17876
-
17877
- // 1.9.170: provider cycle 순서 (Tab) — 빌트인 6종(1.9.268 grok 추가). user provider는 동적으로 뒤에 추가.
17878
- const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'grok', 'opencode', 'qwen', 'aider', 'goose', 'copilot'];
17879
-
17880
- // 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
17881
- const _AGENT_ROLE_PROMPTS = {
17882
- planner: '역할: planner. task를 step 3-6개로 분해, 각 step의 입출력/검증 방법 명시. 코드 작성 금지, 계획만.',
17883
- reviewer: '역할: reviewer. planner 의 계획 또는 actor 의 결과를 비판적으로 검토. 누락된 검증, 잠재 cascade, 오류 가능성 지적. 동의/수정 결론 명시.',
17884
- actor: '역할: actor. 계획에 따라 정확한 명령/코드만 실행. evidence(파일 경로 + 테스트 결과) 함께 기록. 새 계획 생성 금지.'
17885
- };
17886
- // ===== 1.9.270: Agent Roles — 모델별 역할 부여 (사용자 명시) =====
17887
- // 여러 AI 에이전트 활성 시 역할(코딩/검수/지휘/디자인/디버그/설계/분배)을 provider+model 에 매핑.
17888
- // 사용자 설정: .harness/agent-roles.json { schemaVersion, roles: { <role>: { provider, model, persona } } }
17889
- // dispatch --role <role> → 역할 기반 provider+model 라우팅. roles suggest → 활성 에이전트 기반 최적 배치 판단.
17890
- // 방향성 판단(사용자 요청): 모델별 강점이 분명하고(추론/코드/멀티모달) 작업이 분해 가능할 때, 역할 특화는
17891
- // 품질·정확도를 올림 — 강추론 모델의 독립 검수는 자기승인 편향을 차단, 코드 특화 모델은 구현 정확도↑.
17892
- // 단 조율 오버헤드 < 이득이어야 하므로 opt-in + verify(비활성 provider 배정 경고)로 오설정을 방지.
17893
- const ROLE_CATALOG = {
17894
- commander: { ko: '지휘관', desc: '전체 계획 수립·작업 분해·최종 의사결정', prefer: ['claude', 'codex', 'grok'], modelKind: 'top', why: '최강 추론 모델이 전체 맥락을 쥐고 분해/결정 → 일관성↑' },
17895
- reviewer: { ko: '검수자', desc: '구현 결과 비판적 검수·버그/누락 적발', prefer: ['claude', 'codex', 'grok'], modelKind: 'top', why: '독립 강추론 모델의 적대적 검수가 자기승인 편향 차단 → 정확도↑' },
17896
- coder: { ko: '코딩 담당', desc: '구현·패치·리팩터', prefer: ['codex', 'agy', 'claude', 'grok'], modelKind: 'code', why: '코드 특화 모델이 구현 정확도/속도↑' },
17897
- architect: { ko: '설계 담당', desc: '아키텍처·데이터 모델·인터페이스 설계', prefer: ['codex', 'claude'], modelKind: 'top', why: '깊은 코드 추론 모델이 설계 트레이드오프 분석' },
17898
- designer: { ko: '디자인 담당', desc: 'UI/UX·시각 디자인·레이아웃', prefer: ['agy', 'claude', 'grok'], modelKind: 'top', why: '멀티모달/시각 역량 모델이 디자인 산출물↑' },
17899
- debugger: { ko: '디버그 담당', desc: '버그 진단·근본원인·재현', prefer: ['codex', 'claude', 'grok'], modelKind: 'top', why: '깊은 추론 모델의 가설-검증이 디버깅 효율↑' },
17900
- dispatcher: { ko: '하위 분배 담당', desc: '서브에이전트 업무 분배·병렬 조율', prefer: ['claude', 'grok', 'codex'], modelKind: 'fast', why: '빠른 응답 모델이 분배 오버헤드↓' }
17901
- };
17902
- const _ROLE_ALIASES = {
17903
- '코딩': 'coder', '코더': 'coder', '코딩담당': 'coder',
17904
- '검수': 'reviewer', '검수자': 'reviewer', '리뷰': 'reviewer', '리뷰어': 'reviewer',
17905
- '지휘': 'commander', '지휘관': 'commander', '사령관': 'commander',
17906
- '디자인': 'designer', '디자인담당': 'designer',
17907
- '디버그': 'debugger', '디버거': 'debugger', '디버깅': 'debugger',
17908
- '설계': 'architect', '설계담당': 'architect', '아키텍트': 'architect',
17909
- '분배': 'dispatcher', '분배담당': 'dispatcher', '오케스트레이터': 'dispatcher'
17910
- };
17819
+ // 1.9.294 (UR-0025 3단계): 역할/모델 카탈로그(_PROVIDER_MODEL_CATALOG + _AGENT_ROLE_PROMPTS + ROLE_CATALOG + _ROLE_ALIASES) 데이터 모듈 분리 (비파괴, require-based).
17820
+ const { _PROVIDER_MODEL_CATALOG, _AGENT_ROLE_PROMPTS, ROLE_CATALOG, _ROLE_ALIASES } = require('../lib/role-catalog');
17911
17821
  function _normalizeRole(name) {
17912
17822
  const raw = String(name || '').trim();
17913
17823
  if (_ROLE_ALIASES[raw]) return _ROLE_ALIASES[raw];
@@ -18074,23 +17984,7 @@ function rolesCmd(root, sub, ...args) {
18074
17984
  // ===== 1.9.272: Capability / Security Surface 공개 (GPT-5.5 외부 리뷰 반영) =====
18075
17985
  // leerness 는 권한이 큰 CLI 하네스(child_process/git/외부 CLI/automation 브리지/hook 설치)이므로,
18076
17986
  // 무엇을 할 수 있고 어떻게 끄는지를 명시적으로 공개해 신뢰도를 높인다. SECURITY.md 와 동일 출처.
18077
- const CAPABILITY_SURFACE = {
18078
- filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
18079
- network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
18080
- childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
18081
- externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
18082
- automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
18083
- claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
18084
- };
18085
- const POWERFUL_COMMANDS = [
18086
- { cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
18087
- { cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
18088
- { cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
18089
- { cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
18090
- { cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
18091
- { cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
18092
- { cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
18093
- ];
17987
+ // CAPABILITY_SURFACE / POWERFUL_COMMANDS → lib/catalogs.js (1.9.295 UR-0025 4단계)
18094
17988
  function capabilitiesCmd(root, opts = {}) {
18095
17989
  if (opts.json) {
18096
17990
  log(JSON.stringify({ version: VERSION, surface: CAPABILITY_SURFACE, powerfulCommands: POWERFUL_COMMANDS,
@@ -18377,17 +18271,7 @@ function stateCmd(root, sub, ...args) {
18377
18271
  // 목적: 도구별로 "필요한 지침/연결 파일만" 선택 생성 (전체 init 대신). --minimal + adapter 조합으로 침투성↓·범용성↑.
18378
18272
  // MCP 지원 도구는 .mcp.json (leerness mcp serve 등록) 도 생성 → UR-0031 상태 verb 를 즉시 호출 가능.
18379
18273
  // 파일 템플릿은 coreFiles() 재사용(단일 출처). 비파괴(writeIfSafe + mergeManaged).
18380
- const ADAPTERS = {
18381
- claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
18382
- cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
18383
- copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
18384
- codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
18385
- goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
18386
- gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
18387
- opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
18388
- aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
18389
- qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
18390
- };
18274
+ // ADAPTERS lib/catalogs.js (1.9.295 UR-0025 4단계)
18391
18275
  // _mcpJsonContent → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
18392
18276
  // .mcp.json 병합 — 기존 mcpServers 보존하고 leerness 항목만 추가/갱신.
18393
18277
  function _mergeMcpJson(root) {
@@ -18447,35 +18331,12 @@ function adapterCmd(root, tool, opts = {}) {
18447
18331
  // 기능 계획 시 "이미 검증된 OSS 가 있는데 새로 만드는가?" 를 묻는 오프라인 구조적 게이트.
18448
18332
  // 네트워크 크롤러가 아님(offline-first 유지) — 호스트 AI(메인)가 실제 OSS 탐색을 하되, 이 게이트가
18449
18333
  // 카테고리 후보 + 적합성 체크리스트를 제공해 "재사용 검토 생략"을 방지. 기존 `reuse`(내부 자원)와 구분.
18450
- const REUSE_CATEGORIES = [
18451
- { key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
18452
- { key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
18453
- { key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
18454
- { key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
18455
- { key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
18456
- { key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
18457
- { key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
18458
- { key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
18459
- { key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
18460
- { key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
18461
- { key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
18462
- { key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
18463
- { key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
18464
- { key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
18465
- { key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
18466
- ];
18334
+ // REUSE_CATEGORIES lib/catalogs.js (1.9.295 UR-0025 4단계)
18467
18335
  function _reuseDetect(feature) {
18468
18336
  const t = String(feature || '').toLowerCase();
18469
18337
  return REUSE_CATEGORIES.filter(c => c.kw.some(k => t.includes(k)));
18470
18338
  }
18471
- const REUSE_CHECKLIST = [
18472
- '라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
18473
- '유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
18474
- '보안: 알려진 취약점이 없는가 (npm audit / advisory)',
18475
- '적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
18476
- '통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
18477
- '제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
18478
- ];
18339
+ // REUSE_CHECKLIST lib/catalogs.js (1.9.295 UR-0025 4단계)
18479
18340
  function reuseCheckCmd(root, feature, opts = {}) {
18480
18341
  const json = has('--json');
18481
18342
  feature = String(feature || '').trim();
@@ -0,0 +1,60 @@
1
+ // lib/catalogs.js — capability/adapter/reuse 정적 데이터 카탈로그 (순수 데이터, 부작용 0)
2
+ // 1.9.295 (UR-0025 4단계): bin/harness.js 에서 비파괴 분리. selftest(CAPABILITY_SURFACE 6영역 + ADAPTERS + REUSE)가 동작 검증.
3
+ // 런타임 변형 없음 — capabilitiesCmd/adapterCmd/_reuseDetect/reuseCheckCmd 소비처 모두 읽기 전용.
4
+ 'use strict';
5
+
6
+ const CAPABILITY_SURFACE = {
7
+ filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
8
+ network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
9
+ childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
10
+ externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
11
+ automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
12
+ claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
13
+ };
14
+ const POWERFUL_COMMANDS = [
15
+ { cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
16
+ { cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
17
+ { cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
18
+ { cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
19
+ { cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
20
+ { cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
21
+ { cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
22
+ ];
23
+ const ADAPTERS = {
24
+ claude: { label: 'Anthropic Claude Code', keys: ['CLAUDE.md', '.claude/commands/handoff.md', '.claude/commands/session-close.md', '.claude/commands/audit.md', '.claude/commands/lazy-detect.md', '.claude/commands/update.md', '.claude/skills/leerness.md'], mcp: true },
25
+ cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
26
+ copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
27
+ codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
28
+ goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
29
+ gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
30
+ opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
31
+ aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
32
+ qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
33
+ };
34
+ const REUSE_CATEGORIES = [
35
+ { key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
36
+ { key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
37
+ { key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
38
+ { key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
39
+ { key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
40
+ { key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
41
+ { key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
42
+ { key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
43
+ { key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
44
+ { key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
45
+ { key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
46
+ { key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
47
+ { key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
48
+ { key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
49
+ { key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
50
+ ];
51
+ const REUSE_CHECKLIST = [
52
+ '라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
53
+ '유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
54
+ '보안: 알려진 취약점이 없는가 (npm audit / advisory)',
55
+ '적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
56
+ '통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
57
+ '제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
58
+ ];
59
+
60
+ module.exports = { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST };
@@ -0,0 +1,103 @@
1
+ // lib/role-catalog.js — 역할/모델 레지스트리 (순수 데이터, 부작용 0)
2
+ // 1.9.294 (UR-0025 3단계): bin/harness.js 에서 비파괴 분리. selftest(ROLE_CATALOG 7종 + _normalizeRole + _pickModel)가 동작 검증.
3
+ // 런타임 변형 없음 — _normalizeRole/_pickModel/_AGENT_ROLE_PROMPTS 소비처 모두 읽기 전용.
4
+ 'use strict';
5
+
6
+ // 1.9.155: provider 별 추천 모델 카탈로그 — REPL :models 명령에서 노출 (실제 가용성은 사용자 CLI 가 결정)
7
+ // 1.9.170: provider × 실제 모델 catalog 확장 (Tab cycle 지원 — 사용자 명시 요청)
8
+ // 각 provider 의 최신 실제 모델 ID 를 반영. Tab/Shift+Tab 키로 cycle.
9
+ const _PROVIDER_MODEL_CATALOG = {
10
+ claude: [
11
+ { id: 'claude-opus-4-7', note: '최신 1M context (Anthropic Opus 4.7)' },
12
+ { id: 'claude-opus-4-5', note: '안정 Opus 4.5' },
13
+ { id: 'claude-sonnet-4-7', note: '균형형 — Sonnet 4.7 (속도/품질)' },
14
+ { id: 'claude-sonnet-4-5', note: 'Sonnet 4.5 안정' },
15
+ { id: 'claude-haiku-4-5', note: '빠름 — Haiku 4.5' }
16
+ ],
17
+ codex: [
18
+ { id: 'gpt-5.5', note: 'OpenAI 최신 추론 모델' },
19
+ { id: 'gpt-5.4', note: 'OpenAI 안정 (이전 세대)' },
20
+ { id: 'gpt-5', note: 'OpenAI gpt-5 (base)' },
21
+ { id: 'gpt-5-codex', note: '코드 특화 (Codex)' },
22
+ { id: 'o4-mini', note: '빠른 reasoning' }
23
+ ],
24
+ agy: [
25
+ { id: 'antigravity-pro', note: 'Antigravity 최고급 (1M+ context)' },
26
+ { id: 'antigravity-flash', note: '빠른 응답' },
27
+ { id: 'antigravity-experimental', note: '실험적 (사용 가능 시)' }
28
+ ],
29
+ grok: [
30
+ { id: 'grok-beta', note: 'xAI 최신 (1.9.268 provider 승격)' },
31
+ { id: 'grok-2', note: 'xAI Grok 2' },
32
+ { id: 'grok-2-mini', note: '빠른 응답' }
33
+ ],
34
+ // 1.9.277: 신규 provider 4종 모델 (provider-agnostic 인 도구는 default + 설정 안내)
35
+ opencode: [
36
+ { id: 'default', note: 'opencode 설정 모델 (opencode auth/models 로 provider별 지정)' },
37
+ { id: 'anthropic/claude-sonnet', note: 'Anthropic 경유' },
38
+ { id: 'openai/gpt-5', note: 'OpenAI 경유' }
39
+ ],
40
+ qwen: [
41
+ { id: 'qwen3-coder-plus', note: '코드 특화 최신' },
42
+ { id: 'qwen-max', note: '최고 성능' },
43
+ { id: 'qwen-plus', note: '균형' },
44
+ { id: 'qwen-turbo', note: '빠름' }
45
+ ],
46
+ aider: [
47
+ { id: 'sonnet', note: 'Anthropic Sonnet (aider --model)' },
48
+ { id: 'gpt-5', note: 'OpenAI' },
49
+ { id: 'deepseek', note: 'DeepSeek (가성비)' },
50
+ { id: 'o1-mini', note: '빠른 reasoning' }
51
+ ],
52
+ goose: [
53
+ { id: 'default', note: 'goose configure 로 설정한 provider/model' },
54
+ { id: 'claude-sonnet', note: 'Anthropic 경유' },
55
+ { id: 'gpt-5', note: 'OpenAI 경유' }
56
+ ],
57
+ copilot: [
58
+ { id: 'default', note: 'gh copilot 기본 (모델 선택 불가)' }
59
+ ],
60
+ ollama: [
61
+ { id: 'llama3', note: 'Meta — :models 로 실시간 조회 권장' },
62
+ { id: 'qwen2.5-coder', note: 'Alibaba — 코드 특화' },
63
+ { id: 'gpt-oss', note: 'OpenAI 오픈소스' },
64
+ { id: 'deepseek-coder-v2', note: 'DeepSeek 코드 모델' }
65
+ ]
66
+ };
67
+
68
+ // 1.9.170: provider cycle 순서 (Tab) — 빌트인 6종(1.9.268 grok 추가). user provider는 동적으로 뒤에 추가.
69
+ const _PROVIDER_CYCLE_ORDER = ['ollama', 'claude', 'codex', 'agy', 'grok', 'opencode', 'qwen', 'aider', 'goose', 'copilot'];
70
+
71
+ // 1.9.148: planner/reviewer/actor 역할 시스템 프롬프트 (Gemini 권고 — 자기-승인 편향 방지)
72
+ const _AGENT_ROLE_PROMPTS = {
73
+ planner: '역할: planner. task를 step 3-6개로 분해, 각 step의 입출력/검증 방법 명시. 코드 작성 금지, 계획만.',
74
+ reviewer: '역할: reviewer. planner 의 계획 또는 actor 의 결과를 비판적으로 검토. 누락된 검증, 잠재 cascade, 오류 가능성 지적. 동의/수정 결론 명시.',
75
+ actor: '역할: actor. 계획에 따라 정확한 명령/코드만 실행. evidence(파일 경로 + 테스트 결과) 함께 기록. 새 계획 생성 금지.'
76
+ };
77
+ // ===== 1.9.270: Agent Roles — 모델별 역할 부여 (사용자 명시) =====
78
+ // 여러 AI 에이전트 활성 시 역할(코딩/검수/지휘/디자인/디버그/설계/분배)을 provider+model 에 매핑.
79
+ // 사용자 설정: .harness/agent-roles.json { schemaVersion, roles: { <role>: { provider, model, persona } } }
80
+ // dispatch --role <role> → 역할 기반 provider+model 라우팅. roles suggest → 활성 에이전트 기반 최적 배치 판단.
81
+ // 방향성 판단(사용자 요청): 모델별 강점이 분명하고(추론/코드/멀티모달) 작업이 분해 가능할 때, 역할 특화는
82
+ // 품질·정확도를 올림 — 강추론 모델의 독립 검수는 자기승인 편향을 차단, 코드 특화 모델은 구현 정확도↑.
83
+ // 단 조율 오버헤드 < 이득이어야 하므로 opt-in + verify(비활성 provider 배정 경고)로 오설정을 방지.
84
+ const ROLE_CATALOG = {
85
+ commander: { ko: '지휘관', desc: '전체 계획 수립·작업 분해·최종 의사결정', prefer: ['claude', 'codex', 'grok'], modelKind: 'top', why: '최강 추론 모델이 전체 맥락을 쥐고 분해/결정 → 일관성↑' },
86
+ reviewer: { ko: '검수자', desc: '구현 결과 비판적 검수·버그/누락 적발', prefer: ['claude', 'codex', 'grok'], modelKind: 'top', why: '독립 강추론 모델의 적대적 검수가 자기승인 편향 차단 → 정확도↑' },
87
+ coder: { ko: '코딩 담당', desc: '구현·패치·리팩터', prefer: ['codex', 'agy', 'claude', 'grok'], modelKind: 'code', why: '코드 특화 모델이 구현 정확도/속도↑' },
88
+ architect: { ko: '설계 담당', desc: '아키텍처·데이터 모델·인터페이스 설계', prefer: ['codex', 'claude'], modelKind: 'top', why: '깊은 코드 추론 모델이 설계 트레이드오프 분석' },
89
+ designer: { ko: '디자인 담당', desc: 'UI/UX·시각 디자인·레이아웃', prefer: ['agy', 'claude', 'grok'], modelKind: 'top', why: '멀티모달/시각 역량 모델이 디자인 산출물↑' },
90
+ debugger: { ko: '디버그 담당', desc: '버그 진단·근본원인·재현', prefer: ['codex', 'claude', 'grok'], modelKind: 'top', why: '깊은 추론 모델의 가설-검증이 디버깅 효율↑' },
91
+ dispatcher: { ko: '하위 분배 담당', desc: '서브에이전트 업무 분배·병렬 조율', prefer: ['claude', 'grok', 'codex'], modelKind: 'fast', why: '빠른 응답 모델이 분배 오버헤드↓' }
92
+ };
93
+ const _ROLE_ALIASES = {
94
+ '코딩': 'coder', '코더': 'coder', '코딩담당': 'coder',
95
+ '검수': 'reviewer', '검수자': 'reviewer', '리뷰': 'reviewer', '리뷰어': 'reviewer',
96
+ '지휘': 'commander', '지휘관': 'commander', '사령관': 'commander',
97
+ '디자인': 'designer', '디자인담당': 'designer',
98
+ '디버그': 'debugger', '디버거': 'debugger', '디버깅': 'debugger',
99
+ '설계': 'architect', '설계담당': 'architect', '아키텍트': 'architect',
100
+ '분배': 'dispatcher', '분배담당': 'dispatcher', '오케스트레이터': 'dispatcher'
101
+ };
102
+
103
+ module.exports = { _PROVIDER_MODEL_CATALOG, _AGENT_ROLE_PROMPTS, ROLE_CATALOG, _ROLE_ALIASES };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.293",
3
+ "version": "1.9.295",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -3181,5 +3181,49 @@ total++;
3181
3181
  if (!ok) failed++;
3182
3182
  }
3183
3183
 
3184
+ // 1.9.294 회귀 (UR-0025 3단계): lib/role-catalog.js 모듈 분리 — 단일출처 + 부작용 0
3185
+ total++;
3186
+ {
3187
+ let ok = false;
3188
+ try {
3189
+ const reg = require(path.resolve(__dirname, '..', 'lib', 'role-catalog.js'));
3190
+ const dataOk = reg.ROLE_CATALOG && Object.keys(reg.ROLE_CATALOG).length === 7 &&
3191
+ reg._PROVIDER_MODEL_CATALOG && Object.keys(reg._PROVIDER_MODEL_CATALOG).length === 10 &&
3192
+ reg._ROLE_ALIASES && Object.keys(reg._ROLE_ALIASES).length >= 14 &&
3193
+ reg._AGENT_ROLE_PROMPTS && reg._AGENT_ROLE_PROMPTS.actor;
3194
+ const harnessSrc = fs.readFileSync(path.resolve(__dirname, '..', 'bin', 'harness.js'), 'utf8');
3195
+ const movedOut = !/const ROLE_CATALOG = \{/.test(harnessSrc) && /require\('\.\.\/lib\/role-catalog'\)/.test(harnessSrc);
3196
+ // roles 명령이 여전히 동작 (모듈 require 후) — 회귀 방지
3197
+ const rr = cp.spawnSync(process.execPath, [CLI, 'roles', 'list', '--path', tmp, '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
3198
+ const rolesOk = rr.status === 0;
3199
+ ok = dataOk && movedOut && rolesOk;
3200
+ } catch {}
3201
+ console.log(ok ? '✓ B(1.9.294) lib/role-catalog 모듈 분리: 단일출처 + 인라인 제거 + roles 동작 (UR-0025)' : '✗ role-catalog 모듈 분리 실패');
3202
+ if (!ok) failed++;
3203
+ }
3204
+
3205
+ // 1.9.295 회귀 (UR-0025 4단계): lib/catalogs.js 모듈 분리 — 단일출처 + 부작용 0 + 소비 명령 회귀
3206
+ total++;
3207
+ {
3208
+ let ok = false;
3209
+ try {
3210
+ const reg = require(path.resolve(__dirname, '..', 'lib', 'catalogs.js'));
3211
+ const dataOk = reg.CAPABILITY_SURFACE && Object.keys(reg.CAPABILITY_SURFACE).length === 6 &&
3212
+ Array.isArray(reg.POWERFUL_COMMANDS) && reg.POWERFUL_COMMANDS.length === 7 &&
3213
+ reg.ADAPTERS && Object.keys(reg.ADAPTERS).length === 9 &&
3214
+ Array.isArray(reg.REUSE_CATEGORIES) && reg.REUSE_CATEGORIES.length === 15 &&
3215
+ Array.isArray(reg.REUSE_CHECKLIST) && reg.REUSE_CHECKLIST.length === 6;
3216
+ const harnessSrc = fs.readFileSync(path.resolve(__dirname, '..', 'bin', 'harness.js'), 'utf8');
3217
+ const movedOut = !/const CAPABILITY_SURFACE = \{/.test(harnessSrc) && !/const ADAPTERS = \{/.test(harnessSrc) && /require\('\.\.\/lib\/catalogs'\)/.test(harnessSrc);
3218
+ // 소비 명령 회귀: capabilities + reuse-check (카탈로그 require 후 동작)
3219
+ const cap = cp.spawnSync(process.execPath, [CLI, 'capabilities', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
3220
+ const reuse = cp.spawnSync(process.execPath, [CLI, 'reuse-check', 'JWT 인증', '--json'], { cwd: tmp, encoding: 'utf8', timeout: 20000 });
3221
+ const cmdsOk = cap.status === 0 && reuse.status === 0 && /auth/.test(reuse.stdout);
3222
+ ok = dataOk && movedOut && cmdsOk;
3223
+ } catch {}
3224
+ console.log(ok ? '✓ B(1.9.295) lib/catalogs 모듈 분리: 단일출처 + 인라인 제거 + capabilities/reuse-check 동작 (UR-0025)' : '✗ catalogs 모듈 분리 실패');
3225
+ if (!ok) failed++;
3226
+ }
3227
+
3184
3228
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
3185
3229
  if (failed > 0) process.exit(1);