leerness 1.9.294 → 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,27 @@
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
+
3
25
  ## 1.9.294 — 2026-06-04 — UR-0025 (3단계): 역할/모델 카탈로그 → lib/role-catalog.js 모듈 분리
4
26
 
5
27
  **🧩 모놀리스 분리 3단계 — 역할/모델 레지스트리(4개 카탈로그)를 비파괴 데이터 모듈로 분리. 1.9.274(pure-utils)/1.9.291(agent-registry) 패턴 계승.**
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.294-green)]() [![tests](https://img.shields.io/badge/e2e-239%2F239-success)]() [![selftest](https://img.shields.io/badge/selftest-42-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.294 하네스를 사용합니다. 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.294는 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.294는 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.294)** · 매 라운드 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.294: 2026-06-04
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.294';
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') 시 호스트 프로세스 오염.
@@ -3001,6 +3003,7 @@ function _selfTestCases() {
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; } },
3003
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)); } },
3004
3007
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3005
3008
  ];
3006
3009
  }
@@ -17981,23 +17984,7 @@ function rolesCmd(root, sub, ...args) {
17981
17984
  // ===== 1.9.272: Capability / Security Surface 공개 (GPT-5.5 외부 리뷰 반영) =====
17982
17985
  // leerness 는 권한이 큰 CLI 하네스(child_process/git/외부 CLI/automation 브리지/hook 설치)이므로,
17983
17986
  // 무엇을 할 수 있고 어떻게 끄는지를 명시적으로 공개해 신뢰도를 높인다. SECURITY.md 와 동일 출처.
17984
- const CAPABILITY_SURFACE = {
17985
- filesystem: { risk: 'low', desc: '.harness/ 메타파일 생성·갱신, 변경 전 .harness/archive/ 자동 백업. 소스코드는 직접 수정 안 함.', optOut: '핵심 동작 (백업으로 보호)' },
17986
- network: { risk: 'low', desc: 'npm 최신 버전 비교(update --check)만. 그 외 외부 URL 자동 fetch 안 함.', optOut: 'LEERNESS_OFFLINE=1' },
17987
- childProcess: { risk: 'medium', desc: 'git(명시 명령 시 status/commit/push), npm test(verify-code), 외부 CLI --version 감지. 셸 spawn.', optOut: 'verify 계열 한정 · 외부 CLI 는 opt-in' },
17988
- externalAgents: { risk: 'medium', desc: 'agents dispatch/multi — 외부 AI CLI(claude/codex/agy/grok/copilot) 호출. 기본은 명령 텍스트만 생성, multi --execute 시 실제 spawn.', optOut: 'LEERNESS_ENABLE_* 미설정 시 비활성 (기본 off)' },
17989
- automationBridges: { risk: 'high', desc: 'web(playwright)/pc(robotjs)/lsp(typescript) 브리지 — opt-in 의존성. pc 는 마우스/키보드 제어(full 권한).', optOut: '의존성 미설치 시 비활성 (기본 off, 명시 설치 필요)' },
17990
- claudeHook: { risk: 'low', desc: 'init 시 .claude/settings.local.json 에 SessionStart hook(update --check) 설치.', optOut: 'leerness init . --no-auto-update' }
17991
- };
17992
- const POWERFUL_COMMANDS = [
17993
- { cmd: 'init', note: '.harness/ 50+ 파일 + .claude hook 생성 (변경 전 백업)' },
17994
- { cmd: 'update --yes', note: '자동 마이그레이션 — 메타파일 갱신' },
17995
- { cmd: 'agents multi --execute', note: '외부 AI CLI 실제 spawn (병렬 실행)' },
17996
- { cmd: 'release publish / sync-main', note: 'git push + npm publish + GitHub release' },
17997
- { cmd: 'pc <click|type|...>', note: '마우스/키보드 제어 (robotjs, full 권한)' },
17998
- { cmd: 'web <...>', note: '헤드리스 브라우저 자동화 (playwright)' },
17999
- { cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도' }
18000
- ];
17987
+ // CAPABILITY_SURFACE / POWERFUL_COMMANDS → lib/catalogs.js (1.9.295 UR-0025 4단계)
18001
17988
  function capabilitiesCmd(root, opts = {}) {
18002
17989
  if (opts.json) {
18003
17990
  log(JSON.stringify({ version: VERSION, surface: CAPABILITY_SURFACE, powerfulCommands: POWERFUL_COMMANDS,
@@ -18284,17 +18271,7 @@ function stateCmd(root, sub, ...args) {
18284
18271
  // 목적: 도구별로 "필요한 지침/연결 파일만" 선택 생성 (전체 init 대신). --minimal + adapter 조합으로 침투성↓·범용성↑.
18285
18272
  // MCP 지원 도구는 .mcp.json (leerness mcp serve 등록) 도 생성 → UR-0031 상태 verb 를 즉시 호출 가능.
18286
18273
  // 파일 템플릿은 coreFiles() 재사용(단일 출처). 비파괴(writeIfSafe + mergeManaged).
18287
- const ADAPTERS = {
18288
- 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 },
18289
- cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
18290
- copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
18291
- codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
18292
- goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
18293
- gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
18294
- opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
18295
- aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
18296
- qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
18297
- };
18274
+ // ADAPTERS lib/catalogs.js (1.9.295 UR-0025 4단계)
18298
18275
  // _mcpJsonContent → lib/pure-utils.js 로 이동 (1.9.283 UR-0025 2단계)
18299
18276
  // .mcp.json 병합 — 기존 mcpServers 보존하고 leerness 항목만 추가/갱신.
18300
18277
  function _mergeMcpJson(root) {
@@ -18354,35 +18331,12 @@ function adapterCmd(root, tool, opts = {}) {
18354
18331
  // 기능 계획 시 "이미 검증된 OSS 가 있는데 새로 만드는가?" 를 묻는 오프라인 구조적 게이트.
18355
18332
  // 네트워크 크롤러가 아님(offline-first 유지) — 호스트 AI(메인)가 실제 OSS 탐색을 하되, 이 게이트가
18356
18333
  // 카테고리 후보 + 적합성 체크리스트를 제공해 "재사용 검토 생략"을 방지. 기존 `reuse`(내부 자원)와 구분.
18357
- const REUSE_CATEGORIES = [
18358
- { key: 'auth', kw: ['auth', 'login', 'oauth', 'jwt', 'session', '인증', '로그인', '토큰'], candidates: 'auth.js(NextAuth), lucia, passport, jose(JWT)' },
18359
- { key: 'http', kw: ['http client', 'fetch', 'api client', 'request', 'rest client', 'axios'], candidates: 'axios, ky, got, undici(내장 fetch)' },
18360
- { key: 'date', kw: ['date', 'time', 'datetime', 'timezone', '날짜', '시간', '달력'], candidates: 'date-fns, dayjs, luxon, Temporal(표준)' },
18361
- { key: 'validation', kw: ['validation', 'schema', 'validate', 'parse input', '검증', '유효성', '스키마'], candidates: 'zod, valibot, yup, joi' },
18362
- { key: 'state', kw: ['state management', 'store', 'global state', '상태 관리', '스토어'], candidates: 'zustand, redux-toolkit, jotai, nanostores' },
18363
- { key: 'ui', kw: ['ui component', 'design system', 'button', 'modal', 'component library', '컴포넌트', '디자인 시스템'], candidates: 'shadcn/ui, radix, MUI, Ark UI' },
18364
- { key: 'markdown', kw: ['markdown', 'md parser', 'mdx', '마크다운'], candidates: 'marked, markdown-it, remark/unified' },
18365
- { key: 'cli', kw: ['cli', 'command line', 'argv', 'arg parser', '명령행', '인자 파싱'], candidates: 'commander, yargs, clipanion, citty' },
18366
- { key: 'db', kw: ['orm', 'database', 'sql', 'query builder', 'migration', 'db', '데이터베이스', '쿼리'], candidates: 'prisma, drizzle, kysely' },
18367
- { key: 'test', kw: ['test', 'unit test', 'e2e', 'mock', '테스트', '목'], candidates: 'vitest, jest, playwright, node:test' },
18368
- { key: 'pdf', kw: ['pdf', 'generate pdf', 'pdf 생성'], candidates: 'pdf-lib, pdfkit, puppeteer(렌더)' },
18369
- { key: 'csv', kw: ['csv', 'excel', 'xlsx', 'spreadsheet', '스프레드시트'], candidates: 'papaparse, csv-parse, exceljs' },
18370
- { key: 'queue', kw: ['queue', 'job', 'worker', 'cron', 'scheduler', '큐', '작업 스케줄'], candidates: 'bullmq, p-queue, node-cron, croner' },
18371
- { key: 'i18n', kw: ['i18n', 'translation', 'localization', '국제화', '다국어'], candidates: 'i18next, lingui, format.js' },
18372
- { key: 'logging', kw: ['log', 'logger', 'logging', '로깅'], candidates: 'pino, winston, consola' }
18373
- ];
18334
+ // REUSE_CATEGORIES lib/catalogs.js (1.9.295 UR-0025 4단계)
18374
18335
  function _reuseDetect(feature) {
18375
18336
  const t = String(feature || '').toLowerCase();
18376
18337
  return REUSE_CATEGORIES.filter(c => c.kw.some(k => t.includes(k)));
18377
18338
  }
18378
- const REUSE_CHECKLIST = [
18379
- '라이선스: 프로젝트와 호환되는가 (MIT/Apache vs GPL 등)',
18380
- '유지보수: 최근 커밋/릴리스가 활발한가, 이슈 응답이 있는가',
18381
- '보안: 알려진 취약점이 없는가 (npm audit / advisory)',
18382
- '적합성: 요구사항의 80%+ 를 충족하는가 (과한 의존성/기능 과잉 아닌가)',
18383
- '통합 비용: 학습+통합 비용 < 직접 구현 비용인가',
18384
- '제어: 핵심 로직이면 외부 의존 리스크를 감수할 가치가 있는가'
18385
- ];
18339
+ // REUSE_CHECKLIST lib/catalogs.js (1.9.295 UR-0025 4단계)
18386
18340
  function reuseCheckCmd(root, feature, opts = {}) {
18387
18341
  const json = has('--json');
18388
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 };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.294",
3
+ "version": "1.9.295",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -3202,5 +3202,28 @@ total++;
3202
3202
  if (!ok) failed++;
3203
3203
  }
3204
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
+
3205
3228
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
3206
3229
  if (failed > 0) process.exit(1);