leerness 1.36.127 → 1.36.128

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/README.md CHANGED
@@ -122,7 +122,7 @@ MIT
122
122
  <!-- leerness:project-readme:start -->
123
123
  ## Leerness Project Harness
124
124
 
125
- 이 프로젝트는 Leerness v1.36.127 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.128 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
126
126
 
127
127
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
128
128
 
@@ -176,7 +176,7 @@ leerness memory restore decision <date|title>
176
176
 
177
177
  ### MCP server (외부 AI 통합)
178
178
 
179
- Leerness v1.36.127는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
179
+ Leerness v1.36.128는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **89개 도구**를 노출:
180
180
 
181
181
  ```jsonc
182
182
  // 카테고리별
@@ -197,7 +197,7 @@ Leerness v1.36.127는 stdio JSON-RPC MCP server를 내장합니다 — Claude Co
197
197
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
198
198
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
199
199
 
200
- 현재 누적: **v1.9.x → 1.36.127 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.128 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
201
201
 
202
202
  ### 성능 가이드
203
203
 
@@ -235,5 +235,5 @@ leerness release pack --close --auto-main-push
235
235
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
236
236
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
237
237
 
238
- Last synced by Leerness v1.36.127: 2026-08-17
238
+ Last synced by Leerness v1.36.128: 2026-08-18
239
239
  <!-- leerness:project-readme:end -->
package/bin/leerness.js CHANGED
@@ -34,7 +34,7 @@ const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE
34
34
  const { tokenizeForRank: _tokenizeForRank, expandQuery: _expandQuery, scoreHits: _scoreHits, suggestTerms: _suggestTerms } = require('../lib/search-core'); // 1.36.23: memory search 랭킹 코어(순수·0-deps)
35
35
  const { findCorruptedStateJson: _findCorruptedStateJson } = require('../lib/state-integrity'); // 1.36.1 (클린룸 리뷰 FN): .harness/*.json 상태 무결성 (audit/health/check 공유)
36
36
 
37
- const VERSION = '1.36.127';
37
+ const VERSION = '1.36.128';
38
38
 
39
39
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
40
40
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -6693,6 +6693,34 @@ function _selfTestCases() {
6693
6693
  } finally { if (saved !== undefined) process.env.LEERNESS_MCP_TIMEOUT_MS = saved; else delete process.env.LEERNESS_MCP_TIMEOUT_MS; }
6694
6694
  } },
6695
6695
  // 1.36.125: 측정 통제 자체의 가드 — 목록이 비거나 배선이 끊기면 selftest 는 다시 사용자 설정에 휘둘린다.
6696
+ // 1.36.128 (실측, T-0104): `adapter <tool> [+.mcp.json]` 은 **그 도구가 그 파일을 읽는다**는 외부 제품에 대한 주장이다.
6697
+ // 이 머신에 설치된 CLI 로 직접 재 보니 5종 중 2종이 틀렸다:
6698
+ // codex — `.mcp.json` 을 둔 디렉터리에서 `codex mcp list` 가 대조군과 동일, `codex mcp get` 은 not found (거짓)
6699
+ // opencode — 같은 파일을 두면 `opencode mcp list` 에 `connected` 로 등장(서버 7 vs 6) (참)
6700
+ // 판정을 바꾸는 것이 아니라 **주장과 사실을 맞춘다**: 안 읽는 도구엔 파일을 쓰지 않고 되는 명령을 안내한다.
6701
+ { name: '1.36.128 (T-0104): 어댑터 MCP 주장 — 안 읽는 도구는 mcp:false + 근거·대안 필수 · 읽는 도구는 파일 목록 필수 (카탈로그 계약)', run: () => {
6702
+ const c = require('../lib/catalogs');
6703
+ const A = c.ADAPTERS;
6704
+ if (A !== ADAPTERS) return false;
6705
+ // ① 실측으로 거짓이 확인된 두 종은 주장하지 않는다 + 사용자가 막히지 않게 대안을 준다
6706
+ for (const id of ['codex', 'goose']) {
6707
+ const a = A[id];
6708
+ if (!a || a.mcp !== false) return false;
6709
+ if (!a.mcpSetup || !a.mcpWhy) return false;
6710
+ if (!/^(measured|measured-partial)$/.test(String(a.mcpEvidence || ''))) return false;
6711
+ }
6712
+ // ② 실측으로 참인 opencode 는 근거를 measured 로 남긴다(다음 사람이 또 추측하지 않게)
6713
+ if (A.opencode.mcp !== true || A.opencode.mcpEvidence !== 'measured') return false;
6714
+ // ③ `mcp:true` 인 모든 어댑터는 **어떤 파일**을 쓰는지 밝힌다(빈 배열 공허참 차단)
6715
+ const trues = Object.entries(A).filter(([, a]) => a.mcp === true);
6716
+ if (trues.length < 2) return false;
6717
+ if (!trues.every(([, a]) => Array.isArray(a.mcpFiles) && a.mcpFiles.length > 0 && a.mcpFiles.every(f => /\.json$/.test(f)))) return false;
6718
+ // ④ 대조군 — MCP 를 아예 안 다루는 어댑터는 대안 안내도 없어야 한다(잡음 금지)
6719
+ if (A.copilot.mcp !== false || A.copilot.mcpSetup) return false;
6720
+ // ⑤ 배선(카탈로그만 고치고 쓰기가 남으면 무의미)은 **e2e X 블록이 CLI 행위로** 잰다 —
6721
+ // 여기서 소스를 읽으면 자기소스 검사 동결(1.36.84 메타가드)에 걸리고, 실제로 걸렸다.
6722
+ return true;
6723
+ } },
6696
6724
  // 1.36.127 (T-0105/T-0106 도그푸딩): 고아 가드 탐지기가 **자기 저장소에서 눈이 멀어 있었다** —
6697
6725
  // CI 가 `bin/leerness.js` 를 실행하므로 그 파일이 러너로 잡히고, 그 안의 selftest 단언 문자열
6698
6726
  // (`'test:core'` 등)이 호출로 읽혀 진짜 고아 2건이 0건으로 보고됐다(실측).
@@ -24232,8 +24260,12 @@ function adapterCmd(root, tool, opts = {}) {
24232
24260
  log(` init 전체 대신 특정 도구의 지침/연결 파일만 생성합니다. (권장: leerness init . --minimal 후 adapter)`);
24233
24261
  log('');
24234
24262
  for (const [id, a] of Object.entries(ADAPTERS)) {
24235
- log(` ${id.padEnd(9)} ${a.label}${a.mcp ? ' [+.mcp.json]' : ''}`);
24263
+ // 1.36.128 (실측): `[+.mcp.json]` **그 도구가 그 파일을 읽는다**는 주장이다. 실제로 두 종(codex·goose)
24264
+ // 읽지 않았다(codex: `mcp get` 이 not found · goose: 실효 설정에 미등장). 안 읽는 도구엔 파일 대신 **되는 명령**을 준다.
24265
+ log(` ${id.padEnd(9)} ${a.label}${a.mcp ? ` [+${(a.mcpFiles || ['.mcp.json']).join(' +')}]` : ''}`);
24236
24266
  log(` ${a.keys.join(', ')}`);
24267
+ if (!a.mcp && a.mcpSetup) log(` MCP: ${a.mcpWhy}
24268
+ → ${a.mcpSetup}`);
24237
24269
  }
24238
24270
  log(`\n 사용: leerness adapter <tool> [--dry-run]`);
24239
24271
  return;
@@ -24303,7 +24335,10 @@ function adapterCmd(root, tool, opts = {}) {
24303
24335
  const files = coreFiles(root, lang === 'en' ? 'en' : 'ko', [], { mode: _instMode || undefined });
24304
24336
  const managedOverwrite = new Set(['AGENTS.md', 'CLAUDE.md', '.cursor/rules/leerness.mdc', '.github/copilot-instructions.md']);
24305
24337
  const planned = a.keys.filter(k => files[k] != null);
24306
- if (a.mcp) planned.push('.mcp.json');
24338
+ // 1.36.128 (검수 P1, 실측 재현): dry-run 계획이 `.mcp.json` 만 하드코딩해, cursor 는 실제 실행이
24339
+ // **계획에 없던 `.cursor/mcp.json`(사용자 설정)** 까지 병합했다 — dry-run 이 보여주지 않은 파일을 건드리면
24340
+ // 그건 조용한 변경이다. 카탈로그의 `mcpFiles` 를 계획 생성에 그대로 태운다(단일 출처).
24341
+ if (a.mcp) planned.push(...(a.mcpFiles && a.mcpFiles.length ? a.mcpFiles : ['.mcp.json']));
24307
24342
  if (dry) {
24308
24343
  if (json) { log(JSON.stringify({ adapter: tool, dryRun: true, files: planned }, null, 2)); return; }
24309
24344
  log(`# leerness adapter ${tool} [dry-run] — 생성/갱신 예정 ${planned.length}개 (실제 변경 0)`);
@@ -24343,6 +24378,12 @@ function adapterCmd(root, tool, opts = {}) {
24343
24378
  // 주장은 **파일 단위**로 한다 — 우리가 쓴 파일만 이름을 대고 말한다.
24344
24379
  _wrote.forEach(r => log(` ℹ ${r.file} 에 MCP 등록 — 이 파일을 읽는 도구는 leerness MCP verb(state_show/start/record/verify/handoff)를 직접 호출 가능`));
24345
24380
  _kept.forEach(r => log(` ℹ ${r.file} 의 기존 leerness 항목을 그대로 두었습니다 — 그 항목이 leerness MCP 를 띄우는지는 확인하지 않았습니다`));
24381
+ // 1.36.128 (실측): 이 도구가 프로젝트 MCP 파일을 **안 읽는** 경우, 파일을 안 쓰는 것으로 끝내면
24382
+ // 사용자는 "그럼 어떻게 등록하나" 를 모른 채 남는다. 못 하는 이유와 **되는 명령**을 함께 준다.
24383
+ if (!a.mcp && a.mcpSetup) {
24384
+ log(` ℹ MCP: ${a.mcpWhy}`);
24385
+ log(` → ${a.mcpSetup}`);
24386
+ }
24346
24387
  // 스킬을 까는 어댑터면 구 평면 파일도 같이 알린다(1.36.123: `init` 에만 있어 이 경로는 조용했다)
24347
24388
  if (a.keys.some(k => String(k).includes('.claude/skills/'))) _warnLegacySkillFile(root);
24348
24389
  // 보존은 안전하지만 **막다른 길이면 안 된다** — 우리 것이 망가졌을 때 되돌릴 방법을 알려 준다(자체 헌트).
package/lib/catalogs.js CHANGED
@@ -22,13 +22,26 @@ const POWERFUL_COMMANDS = [
22
22
  { cmd: 'setup-agents', note: '외부 CLI 활성화 + 자동 설치 시도', noteEn: 'enables external CLIs + attempts auto-install' }
23
23
  ];
24
24
  const ADAPTERS = {
25
- 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/SKILL.md'], mcp: true },
26
- cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true },
25
+ 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/SKILL.md'], mcp: true, mcpFiles: ['.mcp.json'], mcpEvidence: 'documented' },
26
+ cursor: { label: 'Cursor', keys: ['.cursor/rules/leerness.mdc'], mcp: true, mcpFiles: ['.cursor/mcp.json', '.mcp.json'], mcpEvidence: 'documented' },
27
27
  copilot: { label: 'GitHub Copilot', keys: ['.github/copilot-instructions.md'], mcp: false },
28
- codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: true },
29
- goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: true },
28
+ // 1.36.128 (실측): `.mcp.json` 디렉터리에서 `codex mcp list` 출력이 대조군과 **완전히 동일**했고
29
+ // `codex mcp get` "No MCP server named ... found" 를 냈다 — codex 는 프로젝트 `.mcp.json` 읽지 않는다.
30
+ // ⚠ (검수 P2) 처음엔 "~/.codex/config.toml **만** 쓴다" 고 적었는데 그건 측정보다 강한 말이다 —
31
+ // codex 는 프로젝트 범위 `.codex/config.toml` 도 문서화하고 있다(신뢰 게이트). 내 프로브(임시 디렉터리)에서는
32
+ // `mcp list/get` 에 안 잡혔지만, 그 명령이 프로젝트 범위를 비추지 않을 수도 있다. **잰 것만** 말한다.
33
+ // 파일을 써 놓고 '등록됨' 처럼 보이게 하는 대신, **실제로 되는 명령**을 안내한다.
34
+ codex: { label: 'OpenAI Codex CLI', keys: ['AGENTS.md'], mcp: false,
35
+ mcpEvidence: 'measured', mcpWhy: 'codex 는 프로젝트 .mcp.json 을 읽지 않는다 (설정은 codex 자신의 config.toml)',
36
+ mcpSetup: 'codex mcp add leerness -- npx -y leerness mcp serve (전역 등록 · 프로젝트 범위는 .codex/config.toml 의 [mcp_servers.leerness])' },
37
+ // 1.36.128 (실측): `goose info -v` 가 찍는 실효 설정(extensions)에 프로젝트 `.mcp.json` 의 항목이 없다.
38
+ // 설정 경로도 전역 config.yaml 뿐이다. (provider 미설정이라 세션 기동까지는 확인하지 못함 — 근거 수준을 함께 적는다)
39
+ goose: { label: 'Goose (Block)', keys: ['AGENTS.md'], mcp: false,
40
+ mcpEvidence: 'measured-partial', mcpWhy: 'goose 는 전역 config.yaml 로 extension 을 관리한다 — 프로젝트 .mcp.json 미인식',
41
+ mcpSetup: 'goose session --with-extension "npx -y leerness mcp serve" (영구 등록: goose configure)' },
30
42
  gemini: { label: 'Gemini CLI / Antigravity', keys: ['AGENTS.md'], mcp: false },
31
- opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true },
43
+ // 1.36.128 (실측): `.mcp.json` 두면 `opencode mcp list` `✓ connected` 로 나타난다(서버 7 vs 6, 대조군 0건).
44
+ opencode: { label: 'opencode', keys: ['AGENTS.md'], mcp: true, mcpFiles: ['.mcp.json'], mcpEvidence: 'measured' },
32
45
  aider: { label: 'Aider', keys: ['AGENTS.md'], mcp: false },
33
46
  qwen: { label: 'Qwen Code', keys: ['AGENTS.md'], mcp: false }
34
47
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.127",
3
+ "version": "1.36.128",
4
4
  "description": "The AI-coding operations layer that makes \"done\" require evidence — persistent memory, evidence-gated completion checks, and clean handoffs for any AI agent (Claude Code, Codex, Cursor). State lives as plain files in your repo. CLI + MCP, 0 runtime dependencies.",
5
5
  "keywords": [
6
6
  "leerness",
package/scripts/e2e.js CHANGED
@@ -12708,6 +12708,86 @@ total++;
12708
12708
  if (!ok) failed++;
12709
12709
  }
12710
12710
 
12711
+ // 1.36.128 (T-0104 실측): `adapter <tool> [+.mcp.json]` 은 **외부 제품이 그 파일을 읽는다**는 주장이다.
12712
+ // 이 머신의 실제 CLI 로 재 보니 codex 는 읽지 않았고(`codex mcp get` → not found) opencode 는 읽었다(connected).
12713
+ // 그래서 안 읽는 도구에는 파일을 **쓰지 않고** 되는 명령을 안내한다. 그 계약을 CLI 행위로 고정한다.
12714
+ total++;
12715
+ {
12716
+ let ok = false; const bad = []; const dbg = {};
12717
+ const sb = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-adp128-'));
12718
+ const envA = Object.assign({}, process.env, { TMPDIR: sb, TEMP: sb, TMP: sb, LEERNESS_OFFLINE: '1', LEERNESS_NO_PROMPT: '1' });
12719
+ for (const k of ['LEERNESS_WORKSPACE_DIR', 'LEERNESS_LANG', 'LEERNESS_MCP_PROFILE', 'LEERNESS_MCP_TIMEOUT_MS']) delete envA[k];
12720
+ const mkA = (name) => {
12721
+ const d = path.join(sb, name); fs.mkdirSync(d, { recursive: true });
12722
+ fs.writeFileSync(path.join(d, 'package.json'), '{"name":"a","version":"0.1.0"}');
12723
+ const r = cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--minimal'], { cwd: d, encoding: 'utf8', timeout: 600000, env: envA });
12724
+ if (r.status !== 0) bad.push(`${name}:init실패(${r.status})`);
12725
+ try { fs.unlinkSync(path.join(d, '.mcp.json')); } catch {}
12726
+ return d;
12727
+ };
12728
+ const RA = (d, args) => cp.spawnSync(process.execPath, [CLI, ...args], { cwd: d, encoding: 'utf8', timeout: 600000, env: envA, maxBuffer: 32 * 1024 * 1024 });
12729
+ try {
12730
+ const dC = mkA('codexp');
12731
+ const rC = RA(dC, ['adapter', 'codex', '--path', dC]);
12732
+ const oC = String(rC.stdout || '');
12733
+ // (검수 P3) 안내만 찍고 exit 1 로 죽어도 통과하던 자리 — 적용 자체가 성공했는지 본다
12734
+ if (rC.status !== 0) bad.push(`①codex adapter exit=${rC.status}`);
12735
+ if (fs.existsSync(path.join(dC, '.mcp.json'))) bad.push('①codex에 무동작 .mcp.json 생성');
12736
+ if (!/읽지 않는다/.test(oC)) bad.push('①이유 미고지');
12737
+ if (!/codex mcp add leerness/.test(oC)) bad.push('①되는 명령 미고지');
12738
+ dbg.codex = { wroteMcp: fs.existsSync(path.join(dC, '.mcp.json')) };
12739
+ // (검수 P3) goose 도 `mcp:false` + 대안 안내 경로다 — 카탈로그만 고치고 경로를 안 태우면 무의미하다.
12740
+ const dG = mkA('goosep');
12741
+ const rG = RA(dG, ['adapter', 'goose', '--path', dG]);
12742
+ const oG = String(rG.stdout || '');
12743
+ if (rG.status !== 0) bad.push(`①goose adapter exit=${rG.status}`);
12744
+ if (fs.existsSync(path.join(dG, '.mcp.json'))) bad.push('①goose에 무동작 .mcp.json 생성');
12745
+ if (!/goose session --with-extension "npx -y leerness mcp serve"/.test(oG)) bad.push('①goose 안내 명령 형태 불일치');
12746
+ dbg.goose = { wroteMcp: fs.existsSync(path.join(dG, '.mcp.json')), exit: rG.status };
12747
+
12748
+ const dL = mkA('claudep');
12749
+ const rL = RA(dL, ['adapter', 'claude', '--path', dL]);
12750
+ const oL = String(rL.stdout || '');
12751
+ if (rL.status !== 0) bad.push(`②claude adapter exit=${rL.status}`);
12752
+ if (!fs.existsSync(path.join(dL, '.mcp.json'))) bad.push('②claude에 .mcp.json 미생성');
12753
+ if (/codex mcp add|goose session --with-extension/.test(oL)) bad.push('②남의 대안 안내 누출');
12754
+ if (!/\.mcp\.json 에 MCP 등록/.test(oL)) bad.push('②등록 문구 없음');
12755
+ dbg.claude = { wroteMcp: fs.existsSync(path.join(dL, '.mcp.json')) };
12756
+ // (검수 P1) dry-run 이 안 보여준 사용자 설정 파일을 실제 실행이 건드리면 조용한 변경이다.
12757
+ // cursor 는 MCP 파일이 **둘**이라 이 어긋남이 실제로 있었다 — 계획 ⊇ 실제를 단언한다.
12758
+ {
12759
+ const dR = mkA('cursorp');
12760
+ let pj = null;
12761
+ try { pj = JSON.parse(String(RA(dR, ['adapter', 'cursor', '--path', dR, '--dry-run', '--json']).stdout || '')); } catch { bad.push('②bdryJSON아님'); }
12762
+ const planned = (pj && pj.files) || [];
12763
+ if (!planned.includes('.cursor/mcp.json')) bad.push(`②b계획에 .cursor/mcp.json 없음(${JSON.stringify(planned)})`);
12764
+ const rR = RA(dR, ['adapter', 'cursor', '--path', dR]);
12765
+ if (rR.status !== 0) bad.push(`②bcursor adapter exit=${rR.status}`);
12766
+ for (const f of ['.cursor/mcp.json', '.mcp.json']) {
12767
+ if (fs.existsSync(path.join(dR, f)) && !planned.includes(f)) bad.push(`②b계획에 없던 파일을 생성: ${f}`);
12768
+ }
12769
+ dbg.cursor = { planned };
12770
+ }
12771
+ const listR = RA(sb, ['adapter']);
12772
+ if (listR.status !== 0) bad.push(`③adapter list exit=${listR.status}`);
12773
+ const list = String(listR.stdout || '');
12774
+ const codexBracket = /codex\s+OpenAI Codex CLI\s+\[\+/.test(list);
12775
+ if (codexBracket) bad.push('③목록이 codex 에 여전히 대괄호 표기');
12776
+ if (!/opencode\s+opencode\s+\[\+\.mcp\.json\]/.test(list)) bad.push('③opencode 표기 사라짐');
12777
+ let lj = null; try { lj = JSON.parse(String(RA(sb, ['adapter', 'list', '--json']).stdout || '')); } catch { bad.push('③listJSON아님'); }
12778
+ if (lj && lj.adapters) {
12779
+ if (lj.adapters.codex.mcp !== false) bad.push('③json:codex.mcp가 false 아님');
12780
+ if (lj.adapters.claude.mcp !== true) bad.push('③json:claude.mcp가 true 아님');
12781
+ }
12782
+ dbg.list = { codexBracket };
12783
+ ok = bad.length === 0;
12784
+ } catch (e) { dbg.err = String(e && e.message).slice(0, 200); }
12785
+ finally { try { fs.rmSync(sb, { recursive: true, force: true }); } catch {} }
12786
+ console.log(ok ? `✓ X(1.36.128/T-0104) 어댑터 MCP 주장: 안 읽는 도구(codex)엔 무동작 파일 미생성 + 이유·되는 명령 고지 · 읽는 도구(claude)는 그대로 등록하고 남의 안내 누출 0 · 목록/JSON 표기 일치 ${JSON.stringify(dbg)}`
12787
+ : '✗ 1.36.128 어댑터 MCP 주장 실패 ' + JSON.stringify({ bad: bad.slice(0, 8), dbg }));
12788
+ if (!ok) failed++;
12789
+ }
12790
+
12711
12791
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
12712
12792
  if (failed > 0) process.exit(1);
12713
12793