leerness 1.36.59 → 1.36.60

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,14 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.60 — 2026-07-21 — 언어 전환 현지화 병합 (F-05 2회차, 전 검수 High 해소) — R-0001 검수 5회전
4
+
5
+ KO→EN(역방향 포함) 전환 시 지시 레이어가 새 언어로 재생성되고 사용자 커스텀만 정확히 1회 이월된다.
6
+
7
+ - **차감 병합**: _managedMerge 에 altTemplate — 구 언어 canonical 라인은 커스텀이 아니므로 미이월 (종전: KO 템플릿 통째 Preserved 이월). session-workflow.md managed 편입(전환 시에도 낡은 언어로 방치되던 것). 보존 래퍼 문구 현지화(en 프로젝트에 한글 래퍼가 새 한글원이 되던 것).
8
+ - **검수 2차 High 반영 — 전환-감지 게이트 + 출현-횟수 차감**: 무조건 차감은 동일-언어 재init 에서 "반대-언어 템플릿과 우연히 일치하는 정당한 커스텀"을 유실시켰다(실측) — 이전 저장 언어를 포착해 실제 전환일 때만 차감 + Set 대신 출현 1회분만 소비(복제 커스텀 생존).
9
+ - (검수 #2) adapter 경로에 래퍼 언어 전달(전환-감지 없는 경로라 altTemplate 은 의도적 미적용). (검수 #3) 구버전 workflow 의 자동생성 이력 로그(MCP 도구-카운트/마일스톤 불릿)는 좁은 시그니처로 필터 — 낡은 모순 잔재 방지, 사용자 산문 보존.
10
+ - 실측: 동일-언어 커스텀 생존 / 전환 차감 유지(잔존 한글=커스텀 1건) / 이력 필터+커스텀 보존. selftest 332(+1), 검수 통과 프로브(migrate/update 동일 경로·EN 래퍼 혼재 무중복·5종 바이트 멱등).
11
+
3
12
  ## 1.36.59 — 2026-07-21 — en 지시-레이어 완전 번역 (외부감사 F-05 시리즈 1회차) — R-0001 검수 4회전
4
13
 
5
14
  실측 베이스라인: en init 산출물 61개 중 한글 잔존 53개. 1회차 = en 에이전트가 "지시"로 읽는 최고 영향 5종.
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.59 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.60 하네스를 사용합니다. 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.59는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
+ Leerness v1.36.60는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
180
180
 
181
181
  ```jsonc
182
182
  // 카테고리별
@@ -197,7 +197,7 @@ Leerness v1.36.59는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
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.59 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.60 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
201
201
 
202
202
  ### 성능 가이드
203
203
 
@@ -235,6 +235,6 @@ 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.59: 2026-07-22
238
+ Last synced by Leerness v1.36.60: 2026-07-22
239
239
  <!-- leerness:project-readme:end -->
240
240
 
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.59';
37
+ const VERSION = '1.36.60';
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') 시 호스트 프로세스 오염.
@@ -738,9 +738,9 @@ function createBackup(root, reason, files, dry = false) {
738
738
  }
739
739
 
740
740
  // 1.9.1 P2 / 1.9.368 (UR-0025): MERGE_OVERWRITE_FILES → lib/catalogs, managedMerge 순수 코어 → lib/pure-utils. 얇은 래퍼.
741
- function managedMerge(file, next, previous, archiveDir) {
741
+ function managedMerge(file, next, previous, archiveDir, mergeOpts = {}) {
742
742
  const archiveRel = archiveDir ? path.relative(process.cwd(), archiveDir).replace(/\\/g, '/') : '.harness/archive';
743
- return _managedMerge(file, next, previous, archiveRel, MERGE_OVERWRITE_FILES);
743
+ return _managedMerge(file, next, previous, archiveRel, MERGE_OVERWRITE_FILES, mergeOpts); // 1.36.60: altTemplate/lang 전달
744
744
  }
745
745
 
746
746
  function writeIfSafe(root, file, content, opts = {}) {
@@ -751,7 +751,7 @@ function writeIfSafe(root, file, content, opts = {}) {
751
751
  // 병합을 우회해 CLAUDE.md 커스텀 지시가 archive 에만 남았다(1.36.28 보존 재설계의 force 구멍).
752
752
  if (already && opts.mergeManaged) {
753
753
  const prev = read(p);
754
- writeUtf8(p, managedMerge(file, content, prev, opts.archiveDir));
754
+ writeUtf8(p, managedMerge(file, content, prev, opts.archiveDir, { altTemplate: opts.altTemplate, lang: opts.lang }));
755
755
  return { action: 'merged', file };
756
756
  }
757
757
  if (already && !opts.force) return { action: 'preserved', file };
@@ -988,6 +988,17 @@ async function install(root, opts = {}) {
988
988
  // 1.9.10: 스킬 카탈로그 출처 안내
989
989
  // 1.9.184 (사용자 명시): leerness-skillpack 미사용 정책 — 안내 메시지 제거. builtin catalog 만 사용.
990
990
  const files = coreFiles(root, lang, skills, { minimal: !!opts.minimal });
991
+ // 1.36.60 (F-05 2회차, 검수 #1 High): 반대-언어 canonical 템플릿 — 언어 전환 재init/마이그레이션 시
992
+ // 구 언어 템플릿 라인을 "사용자 커스텀"으로 오인해 통째로 Preserved 에 이월하던 것을 차감 병합으로 해소.
993
+ // (검수 2차 High): 차감은 "실제 언어 전환일 때만" — 동일-언어 재init 에 무조건 적용하면 반대-언어 템플릿과
994
+ // 우연히 일치하는 정당한 커스텀이 유실된다. 이전 저장 언어를 쓰기 전에 포착해 전환 여부로 게이트.
995
+ let _prevLang = null;
996
+ try { _prevLang = read(path.join(root, '.harness', 'LANGUAGE')).trim().toLowerCase() || null; } catch {}
997
+ if (!_prevLang) { try { _prevLang = String(JSON.parse(read(path.join(root, '.harness', 'manifest.json'))).language || '').toLowerCase() || null; } catch {} }
998
+ const _langSwitched = !!(_prevLang && (_prevLang === 'ko' || _prevLang === 'en') && _prevLang !== lang);
999
+ const _altLang = lang === 'en' ? 'ko' : 'en';
1000
+ let _altFiles = {};
1001
+ if (_langSwitched) { try { _altFiles = coreFiles(root, _altLang, skills, { minimal: !!opts.minimal }); } catch {} }
991
1002
  const backup = createBackup(root, opts.force ? 'force' : (opts.migration ? 'migration' : 'init'), files, opts.dry);
992
1003
  if (opts.dry) {
993
1004
  log(`Backup target: ${backup.archiveDir}`);
@@ -998,6 +1009,7 @@ async function install(root, opts = {}) {
998
1009
  }
999
1010
  const managedOverwrite = new Set([
1000
1011
  'AGENTS.md','CLAUDE.md','.cursor/rules/leerness.mdc','.github/copilot-instructions.md',
1012
+ '.harness/session-workflow.md', // 1.36.60 (검수 #1): managed 편입 — 종전엔 재init 시 구 언어/구 버전 그대로 방치
1001
1013
  '.harness/HARNESS_VERSION','.harness/manifest.json','.harness/LANGUAGE','.harness/skills-lock.json',
1002
1014
  '.harness/context-routing.md','.harness/writeback-policy.md','.harness/task-type-map.md',
1003
1015
  '.harness/leerness-maintenance.md','.harness/protected-files.md','.harness/AX_MIGRATION_GUIDE.md',
@@ -1032,7 +1044,7 @@ async function install(root, opts = {}) {
1032
1044
  actions.push({ file:f, action });
1033
1045
  continue;
1034
1046
  }
1035
- const r = writeIfSafe(root, f, c, { force: effForce, mergeManaged, archiveDir: backup.archiveDir });
1047
+ const r = writeIfSafe(root, f, c, { force: effForce, mergeManaged, archiveDir: backup.archiveDir, altTemplate: _altFiles[f], lang });
1036
1048
  actions.push(r);
1037
1049
  _done++;
1038
1050
  drawProgress(_done, f);
@@ -5203,6 +5215,25 @@ function _selfTestCases() {
5203
5215
  const s = read(__filename);
5204
5216
  const wired = s.includes("_profile === 'core' ? _ALL_TOOLS.filter") && s.includes("Unknown tool: ${name} (core profile");
5205
5217
  return allExist && loop && cleanSer && wired;
5218
+ } },
5219
+ { name: '언어 전환 병합 (1.36.60, F-05 2회차 검수 High): altTemplate 차감 — 구 언어 템플릿을 커스텀 오인 이월 차단 + 커스텀 보존 + en 래퍼 — 순수 행위검사', run: () => {
5220
+ const p = require('../lib/pure-utils');
5221
+ const koT = '# 지시\n한국어 규칙 1\n한국어 규칙 2\n';
5222
+ const enT = '# Instructions\nEnglish rule 1\nEnglish rule 2\n';
5223
+ const prev = koT + 'MY CUSTOM LINE\n';
5224
+ // 전환: KO(prev) → EN(next), altTemplate=koT — 한국어 템플릿 라인은 이월 안 되고 커스텀만
5225
+ const m = p._managedMerge('AGENTS.md', enT, prev, '.harness/archive', null, { altTemplate: koT, lang: 'en' });
5226
+ const noKoTemplate = !m.includes('한국어 규칙 1') && !m.includes('한국어 규칙 2');
5227
+ const customKept = (m.match(/MY CUSTOM LINE/g) || []).length === 1;
5228
+ const enWrapper = m.includes('carried over from the previous version') && !/[가-힣]/.test(m.replace('MY CUSTOM LINE', ''));
5229
+ // altTemplate 없이(구 동작): 커스텀 오인 이월 — 회귀 방지용 대조군 (구 언어 라인이 이월됨을 확인)
5230
+ const m2 = p._managedMerge('AGENTS.md', enT, prev, '.harness/archive', null, {});
5231
+ const control = m2.includes('한국어 규칙 1');
5232
+ // 멱등: 전환 결과에 같은 병합 재적용 시 커스텀 중복 없음
5233
+ const m3 = p._managedMerge('AGENTS.md', enT, m, '.harness/archive', null, { altTemplate: koT, lang: 'en' });
5234
+ const idem = (m3.match(/MY CUSTOM LINE/g) || []).length === 1;
5235
+ const wired = read(__filename).includes('altTemplate: _altFiles[f]') && read(__filename).includes("'.harness/session-workflow.md', // 1.36.60");
5236
+ return noKoTemplate && customKept && enWrapper && control && idem && wired;
5206
5237
  } }
5207
5238
  ];
5208
5239
  }
@@ -20367,7 +20398,7 @@ function adapterCmd(root, tool, opts = {}) {
20367
20398
  const results = [];
20368
20399
  for (const k of a.keys) {
20369
20400
  if (files[k] == null) continue;
20370
- results.push(writeIfSafe(root, k, files[k], { mergeManaged: managedOverwrite.has(k) }));
20401
+ results.push(writeIfSafe(root, k, files[k], { mergeManaged: managedOverwrite.has(k), lang: lang === 'en' ? 'en' : 'ko' })); // 1.36.60 (검수 2차 #2): 래퍼 언어 전달 (altTemplate 은 전환-감지 없는 이 경로에선 미적용 — High 회귀 방지)
20371
20402
  }
20372
20403
  if (a.mcp) results.push(_mergeMcpJson(root));
20373
20404
  if (json) { log(JSON.stringify({ adapter: tool, files: results }, null, 2)); return; }
package/lib/pure-utils.js CHANGED
@@ -765,10 +765,16 @@ function _mergeReadmeSection(existing, block, START, END) {
765
765
  }
766
766
  // 1.9.368 (UR-0025): 관리 파일 마이그레이션 머지 순수 코어 — 이전 내용을 migration-preserved 블록으로 보존(데이터/인덱스 파일은 overwrite).
767
767
  // archiveRel(사전 계산된 표시 경로) + overwriteSet 을 인자로 주입 → path/process/상수 비결합(순수).
768
- function _managedMerge(file, next, previous, archiveRel, overwriteSet) {
768
+ function _managedMerge(file, next, previous, archiveRel, overwriteSet, opts = {}) {
769
769
  if (!previous || previous.trim() === next.trim()) return next;
770
770
  const tag = '<!-- leerness:migration-preserved -->';
771
771
  if (overwriteSet && overwriteSet.has(String(file).replace(/\\/g, '/'))) return next;
772
+ // 1.36.60 (F-05 2회차, 검수 High): 언어 전환 시 구 언어 canonical 템플릿(altTemplate)의 라인도 차감 —
773
+ // 종전엔 KO→EN 재init 에서 KO 템플릿 전체가 "커스텀"으로 오인돼 Preserved 에 통째 이월됐다.
774
+ // (검수 2차 High): 차감은 "출현 횟수" 기반 — canonical 1회분만 소비해, 사용자가 의도적으로 템플릿 라인을
775
+ // 복제한 커스텀(2번째 출현)은 생존한다. Set 차감은 그 복제본까지 지웠다.
776
+ const altCounts = new Map();
777
+ for (const l of String(opts.altTemplate || '').split('\n').map(x => x.trim()).filter(Boolean)) altCounts.set(l, (altCounts.get(l) || 0) + 1);
772
778
  // 1.36.28 (사용자 보고 — 반복 마이그레이션 데이터 유실): 종전 <details> 중첩 설계는 previous 에 preserved 태그가
773
779
  // 있으면 통째로 next 만 반환 → 2번째 마이그레이션부터 사용자가 CLAUDE/AGENTS 에 직접 쓴 커스텀 지시가 조용히
774
780
  // 사라졌다(매 버전 AI 마이그레이션 흐름에서 상시 발화). 재설계: 라인-집합 diff 로 "새 템플릿에 없는 라인"만
@@ -780,17 +786,26 @@ function _managedMerge(file, next, previous, archiveRel, overwriteSet) {
780
786
  for (const raw of previous.split('\n')) {
781
787
  const l = raw.trim();
782
788
  if (!l || nextLines.has(l)) continue;
783
- // preserved 래퍼 자체의 보일러플레이트는 이월 대상 아님 (구 <details> 형식 + 신 평문 형식 모두)
789
+ const _ac = altCounts.get(l);
790
+ if (_ac > 0) { altCounts.set(l, _ac - 1); continue; } // canonical 출현분만 소비 (복제 커스텀 생존)
791
+ // 1.36.60 (검수 2차 #3): 구버전 workflow 의 자동생성 이력 로그(도구-카운트/마일스톤 불릿)는 커스텀이 아니라
792
+ // 릴리스마다 낡는 생성물 — 좁은 시그니처만 필터 (사용자 산문이 이 패턴과 겹칠 확률은 무시 가능)
793
+ if (String(file).includes('session-workflow') && (/^- .*MCP \*\*\d+\s*도구/.test(l) || /^- MCP \*\*\d+/.test(l) || /^- 🏆 마일스톤/.test(l) || /^- 🎉 \*\*\d+ 라운드/.test(l))) continue;
794
+ // preserved 래퍼 자체의 보일러플레이트는 이월 대상 아님 (구 <details> 형식 + 신 평문 형식 + en 래퍼 모두)
784
795
  if (l === tag || l === '---' || l === '<details>' || l === '</details>') continue;
785
796
  if (/^```/.test(l) || /^<summary>/.test(l)) continue;
786
- if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서 이어진/.test(l)) continue;
797
+ if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서 이어진|^> Custom user\/project content carried over/.test(l)) continue;
787
798
  if (seen.has(l)) continue;
788
799
  seen.add(l);
789
800
  custom.push(raw.replace(/\\`\\`\\`/g, '```')); // 구 형식에서 이스케이프된 펜스 복원
790
801
  }
791
802
  if (!custom.length) return next;
792
803
  const ar = archiveRel || '.harness/archive';
793
- return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\n> 이전 버전에서 이어진 사용자/프로젝트 커스텀 내용 마이그레이션마다 자동 이월됩니다. 전체 원본 백업: \`${ar}\`\n\n` + custom.join('\n') + '\n';
804
+ // 1.36.60 (검수): 보존 래퍼 문구도 프로젝트 언어를 따른다en 프로젝트에 한글 래퍼가 한글원을 만들던 해소
805
+ const note = opts.lang === 'en'
806
+ ? `> Custom user/project content carried over from the previous version — re-carried automatically on every migration. Full original backup: \`${ar}\``
807
+ : `> 이전 버전에서 이어진 사용자/프로젝트 커스텀 내용 — 마이그레이션마다 자동 이월됩니다. 전체 원본 백업: \`${ar}\``;
808
+ return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\n${note}\n\n` + custom.join('\n') + '\n';
794
809
  }
795
810
 
796
811
  // 1.9.369 (UR-0025): --skills 값 파싱 순수 코어 — catalog 주입(harness skillCatalog 비결합). all/recommended/csv 처리 + catalog 필터.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.59",
3
+ "version": "1.36.60",
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",