leerness 1.9.279 → 1.9.280

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,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.9.280 — 2026-06-03 — UR-0033: leerness adapter <tool> — 도구별 선택 설치 + .mcp.json
4
+
5
+ **🔌 GPT-5.5 로드맵 2단계 "어댑터" — init 전체 대신 특정 도구의 지침/연결 파일만 생성. `--minimal` + `adapter` 로 침투성↓·범용성↑.**
6
+
7
+ ### 배경
8
+ GPT-5.5: "leerness adapter claude/cursor/codex/goose/gemini — 각 도구별로 필요한 지침 파일만 선택 생성." 범용 하네스는 모든 에이전트 환경을 한꺼번에 심지 말고 사용자가 쓰는 도구만 연결해야 함.
9
+
10
+ ### 구현
11
+ 1. **`ADAPTERS` 레지스트리 9종** — claude/cursor/copilot/codex/goose/gemini/opencode/aider/qwen. 각 도구가 소유하는 파일 키(coreFiles 재사용 — 단일 출처) + MCP 지원 여부.
12
+ 2. **`leerness adapter <tool> [path] [--dry-run]`** — 해당 도구의 지침 파일만 `writeIfSafe`(비파괴 mergeManaged) 로 생성. `--dry-run` 미리보기(0 변경). `adapter list` 레지스트리 표.
13
+ 3. **`.mcp.json` 자동 등록** — MCP 지원 도구(claude/cursor/codex/goose/opencode)는 `.mcp.json` 에 `leerness mcp serve` 를 병합 등록(기존 mcpServers 보존) → 그 도구가 **UR-0031 상태 verb(state_show/start/record/verify/handoff)를 즉시 호출**.
14
+ 4. **권장 흐름** — `leerness init . --minimal --no-env` → `leerness adapter <내 도구>` (최소 설치 + 내 에이전트만 연결).
15
+ 5. selftest 28→29 + e2e 226→227.
16
+
17
+ ### 검증
18
+ - **selftest 29/29 PASS** · **E2E 227/227 PASS** (회귀 0).
19
+ - 디버그: adapter 가 `--path` 만 읽어 위치 인자 path 무시 → CWD 오염 버그 발견·수정(위치 args[2] 우선) + stray 정리. 실측: `adapter cursor` → .cursor + .mcp.json, list 9종, dry-run 0파일.
20
+
3
21
  ## 1.9.279 — 2026-06-03 — UR-0031: 상태 substrate MCP 시맨틱 verb 노출 (모든 에이전트 공통 호출 표면)
4
22
 
5
23
  **🔌 1.9.278 의 `.leerness/` 상태 substrate 를 MCP 도구 5종으로 노출 — Claude Code/Goose/Codex/Cursor 가 동일한 verb 로 작업 상태를 읽고 쓴다 (GPT-5.5 범용 하네스 핵심).**
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.279-green)]() [![tests](https://img.shields.io/badge/e2e-226%2F226-success)]() [![selftest](https://img.shields.io/badge/selftest-24%2F24-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.280-green)]() [![tests](https://img.shields.io/badge/e2e-227%2F227-success)]() [![selftest](https://img.shields.io/badge/selftest-24%2F24-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
  ╔══════════════════════════════════════════════════════════════╗
@@ -196,6 +196,15 @@ leerness init . --minimal # 핵심 파일만 (에디터 통합/가이드/roa
196
196
  leerness init . --no-env # .env/.env.example 자동 생성만 생략
197
197
  ```
198
198
 
199
+ **도구별 어댑터 (1.9.280) — 내 에이전트만 연결:**
200
+
201
+ ```bash
202
+ leerness init . --minimal --no-env # 1) 최소 설치 / minimal install
203
+ leerness adapter cursor # 2) 내 도구만 (.cursor + .mcp.json) / only my tool
204
+ leerness adapter list # 가능 어댑터 (claude/cursor/codex/goose/opencode/aider/qwen/...)
205
+ # MCP 지원 도구는 .mcp.json 에 leerness 등록 → 상태 verb(state_*) 직접 호출
206
+ ```
207
+
199
208
  ### 릴리스 채널 (안정 vs 실험)
200
209
 
201
210
  leerness는 활발히 개발됩니다(잦은 1.9.x). 채널을 골라 안정성을 제어하세요:
@@ -440,7 +449,7 @@ MIT — © leerness contributors
440
449
  <!-- leerness:project-readme:start -->
441
450
  ## Leerness Project Harness
442
451
 
443
- 이 프로젝트는 Leerness v1.9.279 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
452
+ 이 프로젝트는 Leerness v1.9.280 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
444
453
 
445
454
  ### Core Commands
446
455
 
@@ -482,7 +491,7 @@ leerness memory restore decision <date|title>
482
491
 
483
492
  ### MCP server (외부 AI 통합)
484
493
 
485
- Leerness v1.9.279는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
494
+ Leerness v1.9.280는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **42개 도구**를 노출:
486
495
 
487
496
  ```jsonc
488
497
  // 카테고리별
@@ -503,7 +512,7 @@ Leerness v1.9.279는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
503
512
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
504
513
  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) 다음 라운드 예약.
505
514
 
506
- 현재 누적: **70 라운드 (1.9.40 → 1.9.279)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
515
+ 현재 누적: **70 라운드 (1.9.40 → 1.9.280)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
507
516
 
508
517
  ### 성능 가이드 (1.9.140 측정)
509
518
 
@@ -541,6 +550,6 @@ leerness release pack --close --auto-main-push
541
550
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
542
551
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
543
552
 
544
- Last synced by Leerness v1.9.279: 2026-06-03
553
+ Last synced by Leerness v1.9.280: 2026-06-03
545
554
  <!-- leerness:project-readme:end -->
546
555
 
package/bin/harness.js CHANGED
@@ -9,7 +9,7 @@ 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 } = require('../lib/pure-utils');
11
11
 
12
- const VERSION = '1.9.279';
12
+ const VERSION = '1.9.280';
13
13
 
14
14
  // 1.9.184: DEP0190 (child_process shell: true) deprecation warning 억제 (사용자 명시).
15
15
  // leerness 는 cross-platform PATH resolution 을 위해 shell: true 를 의도적으로 사용 (claude.cmd / ollama.cmd 등 Windows .cmd 처리).
@@ -2960,6 +2960,7 @@ function _selfTestCases() {
2960
2960
  { name: '_pickModel: top/code/fast 등급 선택 (1.9.270)', run: () => { return _pickModel('codex', 'code') === 'gpt-5-codex' && _pickModel('claude', 'top') === 'claude-opus-4-7' && /haiku/.test(_pickModel('claude', 'fast')); } },
2961
2961
  { name: 'CAPABILITY_SURFACE: 6 영역 + risk/optOut + 주의명령 (1.9.272)', run: () => { const ks = Object.keys(CAPABILITY_SURFACE); return ks.length === 6 && ks.includes('automationBridges') && Object.values(CAPABILITY_SURFACE).every(v => ['low','medium','high'].includes(v.risk) && v.optOut && v.desc) && POWERFUL_COMMANDS.length >= 5; } },
2962
2962
  { name: '_resolveNpmTag: latest 기본 + next 허용 + 잘못된 형식 폴백 (1.9.275)', run: () => _resolveNpmTag(null, {}) === 'latest' && _resolveNpmTag('next', {}) === 'next' && _resolveNpmTag(null, { LEERNESS_NPM_TAG: 'next' }) === 'next' && _resolveNpmTag('bad tag!', {}) === 'latest' && _resolveNpmTag('Beta', {}) === 'beta' },
2963
+ { name: 'ADAPTERS + _mcpJsonContent: 도구 매핑 + .mcp.json 등록 (1.9.280)', run: () => { const ids = Object.keys(ADAPTERS); const okMap = ['claude','cursor','codex','goose','opencode'].every(id => ADAPTERS[id] && Array.isArray(ADAPTERS[id].keys) && ADAPTERS[id].keys.length); const m = JSON.parse(_mcpJsonContent()); return ids.length >= 9 && okMap && ADAPTERS.claude.mcp === true && ADAPTERS.copilot.mcp === false && m.mcpServers.leerness.command === 'npx' && m.mcpServers.leerness.args.join(' ') === 'leerness mcp serve'; } },
2963
2964
  { name: '_newRunRecord: GPT-5.5 권고 스키마 14필드 + 기본값 (1.9.278)', run: () => { const r = _newRunRecord({ run_id: 'run-0001', goal: 'g', started_at: '2026-06-03T00:00:00Z' }); return r.schemaVersion === 1 && r.run_id === 'run-0001' && r.started_at === '2026-06-03T00:00:00Z' && Array.isArray(r.files_changed) && Array.isArray(r.commands_run) && Array.isArray(r.tests_run) && Array.isArray(r.decisions) && r.verification_result === null && r.status === 'in-progress' && 'handoff_summary' in r && 'model_name' in r && 'task_id' in r; } },
2964
2965
  { name: 'coreFiles --minimal: 핵심 유지 + 비핵심 제외 + verify 필수 보존 (1.9.276)', run: () => { const full = coreFiles('.', 'ko', []); const min = coreFiles('.', 'ko', [], { minimal: true }); const keep = ['.harness/plan.md','.harness/progress-tracker.md','.harness/session-handoff.md','AGENTS.md','CLAUDE.md','.harness/consistency-policy.md','.harness/reuse-map.md','.harness/encoding-policy.md','.harness/secret-policy.md']; const drop = ['.cursor/rules/leerness.mdc','.harness/skill-index.md','.harness/architecture.md']; const verifyReq = ['.harness/design-system.md','.harness/protected-files.md','.harness/current-state.md']; if (!verifyReq.every(k => min[k])) return false; return Object.keys(min).length < Object.keys(full).length && keep.every(k => min[k]) && drop.every(k => !min[k]); } },
2965
2966
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
@@ -3620,6 +3621,7 @@ function commandsCmd(root) {
3620
3621
  { cmd: 'roles list|set|unset|catalog|suggest|verify', desc: '모델별 역할 부여 (코딩/검수/지휘/디자인/디버그/설계/분배) — 1.9.270' },
3621
3622
  { cmd: 'capabilities [--json]', desc: '권한·보안 표면 공개 (무엇을 하는지 + opt-out + 주의 명령) — 1.9.272' },
3622
3623
  { cmd: 'state show|start|record|verify|handoff', desc: '.leerness/ JSON 상태 substrate (에이전트 간 인수인계 표준) — 1.9.278' },
3624
+ { cmd: 'adapter <tool>|list [--dry-run]', desc: '도구별 지침/.mcp.json 선택 생성 (claude/cursor/codex/goose/...) — 1.9.280' },
3623
3625
  { cmd: 'release channel [--json]', desc: '릴리스 채널 정책 (latest 안정 / next 실험 / 버전 고정) — 1.9.275' },
3624
3626
  { cmd: 'slash-commands [agent] [--json --record --detect --refresh]', desc: 'CLI 에이전트 슬래시 명령 레지스트리 + --help probe 자동 갱신 (1.9.265~267, UR-0021)' },
3625
3627
  { cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
@@ -18192,6 +18194,78 @@ function stateCmd(root, sub, ...args) {
18192
18194
  return;
18193
18195
  }
18194
18196
 
18197
+ // ===== 1.9.280 (UR-0033, GPT-5.5 범용 하네스): leerness adapter <tool> =====
18198
+ // 목적: 도구별로 "필요한 지침/연결 파일만" 선택 생성 (전체 init 대신). --minimal + adapter 조합으로 침투성↓·범용성↑.
18199
+ // MCP 지원 도구는 .mcp.json (leerness mcp serve 등록) 도 생성 → UR-0031 상태 verb 를 즉시 호출 가능.
18200
+ // 파일 템플릿은 coreFiles() 재사용(단일 출처). 비파괴(writeIfSafe + mergeManaged).
18201
+ const ADAPTERS = {
18202
+ 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 },
18203
+ cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
18204
+ copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
18205
+ codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
18206
+ goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
18207
+ gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
18208
+ opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
18209
+ aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
18210
+ qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
18211
+ };
18212
+ function _mcpJsonContent() {
18213
+ return JSON.stringify({ mcpServers: { leerness: { command: 'npx', args: ['leerness', 'mcp', 'serve'] } } }, null, 2) + '\n';
18214
+ }
18215
+ // .mcp.json 병합 — 기존 mcpServers 보존하고 leerness 항목만 추가/갱신.
18216
+ function _mergeMcpJson(root) {
18217
+ const f = path.join(absRoot(root), '.mcp.json');
18218
+ let obj = {};
18219
+ if (exists(f)) { try { obj = JSON.parse(read(f)); } catch { obj = {}; } }
18220
+ obj.mcpServers = obj.mcpServers || {};
18221
+ const already = !!obj.mcpServers.leerness;
18222
+ obj.mcpServers.leerness = { command: 'npx', args: ['leerness', 'mcp', 'serve'] };
18223
+ writeUtf8(f, JSON.stringify(obj, null, 2) + '\n');
18224
+ return { file: '.mcp.json', action: already ? 'updated' : 'created' };
18225
+ }
18226
+ function adapterCmd(root, tool, opts = {}) {
18227
+ root = absRoot(root || process.cwd());
18228
+ const json = has('--json');
18229
+ const dry = has('--dry-run') || opts.dryRun;
18230
+ if (!tool || tool === 'list') {
18231
+ if (json) { log(JSON.stringify({ adapters: Object.fromEntries(Object.entries(ADAPTERS).map(([k, v]) => [k, { label: v.label, files: v.keys, mcp: v.mcp }])) }, null, 2)); return; }
18232
+ log(`# leerness adapter (1.9.280, UR-0033) — 도구별 선택 설치`);
18233
+ log(` init 전체 대신 특정 도구의 지침/연결 파일만 생성합니다. (권장: leerness init . --minimal 후 adapter)`);
18234
+ log('');
18235
+ for (const [id, a] of Object.entries(ADAPTERS)) {
18236
+ log(` ${id.padEnd(9)} ${a.label}${a.mcp ? ' [+.mcp.json]' : ''}`);
18237
+ log(` ${a.keys.join(', ')}`);
18238
+ }
18239
+ log(`\n 사용: leerness adapter <tool> [--dry-run]`);
18240
+ return;
18241
+ }
18242
+ const a = ADAPTERS[tool];
18243
+ if (!a) return fail(`알 수 없는 adapter: ${tool} (가능: ${Object.keys(ADAPTERS).join(', ')})`);
18244
+ const lang = exists(path.join(root, '.harness/LANGUAGE')) ? read(path.join(root, '.harness/LANGUAGE')).trim() : 'ko';
18245
+ const files = coreFiles(root, lang === 'en' ? 'en' : 'ko', []);
18246
+ const managedOverwrite = new Set(['AGENTS.md', 'CLAUDE.md', '.cursor/rules/leerness.mdc', '.github/copilot-instructions.md']);
18247
+ const planned = a.keys.filter(k => files[k] != null);
18248
+ if (a.mcp) planned.push('.mcp.json');
18249
+ if (dry) {
18250
+ if (json) { log(JSON.stringify({ adapter: tool, dryRun: true, files: planned }, null, 2)); return; }
18251
+ log(`# leerness adapter ${tool} [dry-run] — 생성/갱신 예정 ${planned.length}개 (실제 변경 0)`);
18252
+ planned.forEach(f => log(` - ${f}`));
18253
+ log(` → 실제 적용: leerness adapter ${tool}`);
18254
+ return;
18255
+ }
18256
+ const results = [];
18257
+ for (const k of a.keys) {
18258
+ if (files[k] == null) continue;
18259
+ results.push(writeIfSafe(root, k, files[k], { mergeManaged: managedOverwrite.has(k) }));
18260
+ }
18261
+ if (a.mcp) results.push(_mergeMcpJson(root));
18262
+ if (json) { log(JSON.stringify({ adapter: tool, files: results }, null, 2)); return; }
18263
+ ok(`adapter ${tool} (${a.label}) 적용 — ${results.length}개 파일`);
18264
+ for (const r of results) log(` ${r.action}: ${r.file}`);
18265
+ if (a.mcp) log(` ℹ .mcp.json 등록 — 이 도구가 leerness MCP verb(state_show/start/record/verify/handoff)를 직접 호출 가능`);
18266
+ return;
18267
+ }
18268
+
18195
18269
  // 1.9.149+1.9.153: REPL 모드 — leerness 자율 AI 에이전트 (multi-provider 세션)
18196
18270
  async function _agentRepl(root, opts) {
18197
18271
  // 1.9.153: .env 자동 로드 (REPL 진입 직전) — install 직후 LEERNESS_ENABLE_* 즉시 반영
@@ -21120,6 +21194,12 @@ async function main() {
21120
21194
  if (cmd === 'capabilities' || cmd === 'security-surface') return capabilitiesCmd(arg('--path', process.cwd()), { json: has('--json') });
21121
21195
  // 1.9.278 (UR-0032): leerness state <show|start|record|verify|handoff> — .leerness/ 구조화 상태 substrate
21122
21196
  if (cmd === 'state') return stateCmd(arg('--path', process.cwd()), args[1], ...args.slice(2));
21197
+ // 1.9.280 (UR-0033): leerness adapter <tool> [path] — 도구별 지침/.mcp.json 선택 생성 (path: 위치 args[2] 또는 --path)
21198
+ if (cmd === 'adapter') {
21199
+ const _aTool = args[1] && !args[1].startsWith('-') ? args[1] : 'list';
21200
+ const _aRoot = (args[2] && !args[2].startsWith('-')) ? args[2] : arg('--path', process.cwd());
21201
+ return adapterCmd(_aRoot, _aTool);
21202
+ }
21123
21203
  // 1.9.245: API skill cache — 공식 문서·관련링크 자동 정리 (사용자 명시 UR-0015)
21124
21204
  if (cmd === 'api-skill') return apiSkillCmd(arg('--path', process.cwd()), args[1] || 'help');
21125
21205
  // 1.9.208: leerness constraints <list|check|add> — 플랫폼/API 제약 사전 체크 (사용자 명시)
@@ -21302,5 +21382,7 @@ module.exports = {
21302
21382
  // 1.9.276: init --minimal 파일 필터 (GPT-5.5 2차 리뷰) — 단위 테스트
21303
21383
  MINIMAL_SKIP_KEYS, coreFiles,
21304
21384
  // 1.9.278: .leerness/ 상태 스키마 (UR-0032, GPT-5.5 범용 하네스) — 단위 테스트
21305
- _newRunRecord, _leernessStateDir, _loadLeernessState, _saveLeernessState, _loadRun, _saveRun, stateCmd
21385
+ _newRunRecord, _leernessStateDir, _loadLeernessState, _saveLeernessState, _loadRun, _saveRun, stateCmd,
21386
+ // 1.9.280: adapter (UR-0033) — 단위 테스트
21387
+ ADAPTERS, _mcpJsonContent, _mergeMcpJson, adapterCmd
21306
21388
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.9.279",
3
+ "version": "1.9.280",
4
4
  "description": "Leerness: 비파괴 마이그레이션, 자동 버전 감지·업데이트, 계획/진행/핸드오프 자동화, 게으름·시크릿·인코딩 자동 가드, Claude Code 슬래시 통합을 갖춘 한국어 우선 AI 개발 하네스.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -2897,5 +2897,28 @@ total++;
2897
2897
  if (!ok) failed++;
2898
2898
  }
2899
2899
 
2900
+ // 1.9.280 회귀 (UR-0033): leerness adapter — 도구별 선택 생성 + .mcp.json
2901
+ total++;
2902
+ {
2903
+ const aDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-adapter-'));
2904
+ cp.spawnSync(process.execPath, [CLI, 'init', aDir, '--minimal', '--no-env', '--yes'], { encoding: 'utf8', timeout: 30000 });
2905
+ // adapter cursor: .cursor + .mcp.json 생성, .claude 미생성(minimal이라 commands는 있으나 cursor adapter는 안 건드림)
2906
+ const rCur = cp.spawnSync(process.execPath, [CLI, 'adapter', 'cursor', aDir], { encoding: 'utf8', timeout: 15000 });
2907
+ const cursorOk = rCur.status === 0 && fs.existsSync(path.join(aDir, '.cursor', 'rules', 'leerness.mdc'));
2908
+ let mcpOk = false;
2909
+ try { const m = JSON.parse(fs.readFileSync(path.join(aDir, '.mcp.json'), 'utf8')); mcpOk = m.mcpServers && m.mcpServers.leerness && m.mcpServers.leerness.args.join(' ') === 'leerness mcp serve'; } catch {}
2910
+ // adapter list --json: 9종
2911
+ const rList = cp.spawnSync(process.execPath, [CLI, 'adapter', 'list', '--json', '--path', aDir], { encoding: 'utf8', timeout: 15000 });
2912
+ let listOk = false;
2913
+ try { const j = JSON.parse(rList.stdout); listOk = Object.keys(j.adapters).length >= 9 && j.adapters.claude.mcp === true && j.adapters.copilot.mcp === false; } catch {}
2914
+ // adapter --dry-run: 파일 미생성
2915
+ const dDir = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-adapterdry-'));
2916
+ const rDry = cp.spawnSync(process.execPath, [CLI, 'adapter', 'claude', '--dry-run', '--path', dDir], { encoding: 'utf8', timeout: 15000 });
2917
+ const dryOk = /\[dry-run\]/.test(rDry.stdout) && fs.readdirSync(dDir).length === 0;
2918
+ const ok = cursorOk && mcpOk && listOk && dryOk;
2919
+ console.log(ok ? '✓ B(1.9.280) adapter: cursor(.cursor+.mcp.json)/list 9종/dry-run(0파일)' : `✗ adapter 실패 (cursor=${cursorOk} mcp=${mcpOk} list=${listOk} dry=${dryOk})`);
2920
+ if (!ok) { failed++; console.log((rCur.stdout || '').slice(0, 200)); }
2921
+ }
2922
+
2900
2923
  console.log(`\nE2E result: ${total - failed}/${total} passed`);
2901
2924
  if (failed > 0) process.exit(1);