leerness 1.36.72 → 1.36.73

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,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.73 — 2026-07-25 — 8차 헌트 완전 종결: F12 skills-lock 정합 + F9 설계 결정
4
+
5
+ 8차 홀리스틱 헌트(16건)의 마지막 설계 판단 2건을 결론 — 이로써 16/16 전건 처리 완료.
6
+
7
+ - **F12**: skills-lock 은 "요청 집합"이 아니라 **디스크 실재**를 기록 — `--skills all` 후 `recommended` 재init 시 9개 디렉토리가 남는데 락은 5개만 주장하던 불일치를 합집합 재생성으로 해소(실측 dirs 9 = lock 9). 스킬 제거는 하지 않음(비파괴 원칙).
8
+ - **F9 (설계 결정)**: 초고령 KO canonical 이 en 프로젝트에 커스텀으로 오인 이월되는 것 — **자동 차감은 하지 않는다**(구판 템플릿 라인과 사용자 노트를 확신으로 구분 불가, false-DROP 이 버그·false-PRESERVE 는 무해). 대신 한글 비중 높은 이월(≥10줄·>50%) 시 Preserved 섹션에 **검토·정리 안내**를 명시해 조용한 동결을 없앤다. 안내는 멱등(재이월 안 됨), 소수 한글 커스텀·ko 프로젝트엔 무경고.
9
+ - 검증: F12 재현 시나리오 실측, F9 순수함수 5프로브(안내/멱등/무손실/소수 무경고/ko 무경고), selftest 334, e2e +1.
10
+
3
11
  ## 1.36.72 — 2026-07-24 — 성장 한계 클래스 4(task list) + F16 lint 거짓 PASS 차단 — R-0001 검수 15회전
4
12
 
5
13
  - **task list --json 상한**: 기본 최신 100행 + `shown/truncated` 정직 표기(`total` 은 필터 후 전량), `--all/--limit N` 옵트인. MCP `leerness_task_list` 계약 동반 갱신(limit/all 노출+전달). 텍스트 모드 무변경(codex: HEAD 와 바이트 동일 확인).
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.72 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.73 하네스를 사용합니다. 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.72는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
+ Leerness v1.36.73는 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.72는 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.72 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.73 릴리스 태그 이력** (수백 라운드) · _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.72: 2026-07-25
238
+ Last synced by Leerness v1.36.73: 2026-07-25
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.72';
37
+ const VERSION = '1.36.73';
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') 시 호스트 프로세스 오염.
@@ -525,9 +525,21 @@ function mergeReadmeSection(existing, block) {
525
525
  return _mergeReadmeSection(existing, block, README_START, README_END);
526
526
  }
527
527
 
528
- function skillLock(skills) {
528
+ function skillLock(skills, root) {
529
+ // 1.36.73 (8차 헌트 F12): 락은 "요청 집합"이 아니라 디스크 실재를 기록 — `--skills all` 후 `recommended` 재init 시
530
+ // 9개 디렉토리가 남는데 락은 5개만 주장하던 불일치. 기존 설치 디렉토리(카탈로그 내장분)와 합집합.
531
+ // 스킬 제거는 하지 않음(비파괴 원칙 — 사용자가 쓰고 있을 수 있다).
532
+ const set = new Set(skills);
533
+ try {
534
+ if (root) {
535
+ const dir = path.join(root, '.harness', 'skills');
536
+ if (exists(dir)) for (const e of fs.readdirSync(dir, { withFileTypes: true })) {
537
+ if (e.isDirectory() && skillCatalog[e.name]) set.add(e.name);
538
+ }
539
+ }
540
+ } catch {}
529
541
  const data = { leernessVersion: VERSION, updatedAt: now(), installedSkills: {} };
530
- for (const s of skills) data.installedSkills[s] = skillCatalog[s] || { version: 'unknown' };
542
+ for (const s of [...set].sort()) data.installedSkills[s] = skillCatalog[s] || { version: 'unknown' }; // (검수 #4) 정렬 직렬화 — 동일 멤버십이면 바이트 동일(no-op 보존)
531
543
  return JSON.stringify(data, null, 2) + '\n';
532
544
  }
533
545
 
@@ -543,7 +555,7 @@ function coreFiles(root, lang = 'ko', selectedSkills = [], opts = {}) {
543
555
  '.harness/HARNESS_VERSION': VERSION + '\n',
544
556
  '.harness/LANGUAGE': lang + '\n',
545
557
  '.harness/manifest.json': JSON.stringify({ project, leernessVersion: VERSION, language: lang, installedAt: now(), minimal: !!opts.minimal }, null, 2) + '\n',
546
- '.harness/skills-lock.json': skillLock(selectedSkills),
558
+ '.harness/skills-lock.json': skillLock(selectedSkills, root),
547
559
  '.harness/project-brief.md': fm('project-brief', ['프로젝트 목적 확인','신규 기능 판단','계획 수립'], ['프로젝트 목적 변경','사용자/범위 변경'], `# Project Brief\n\n## Project\n${project}\n\n## Purpose\n- 이 프로젝트의 목적을 실제 내용으로 업데이트하세요.\n\n## Users\n-\n\n## Success Criteria\n-\n`),
548
560
  '.harness/plan.md': fm('plan', ['작업 시작 전','새 요청 접수','범위 변경','신규 프로젝트 감지'], ['계획 추가/수정/드랍','milestone 변경','목표 변경'], `# Plan\n\n## Goal\n- 사용자 목적을 기준으로 전체 계획을 유지합니다.\n\n## Scope\n- 포함 범위를 기록합니다.\n\n## Out of Scope / Dropped\n| ID | Item | Reason | Date |\n|---|---|---|---|\n\n## Milestones\n\n### M-0001. 프로젝트 계획 정리\nStatus: planned\nProgress: 0%\n\nTasks:\n- [ ] project-brief.md를 실제 프로젝트 목적에 맞게 작성\n- [ ] context-map.md를 실제 파일 구조에 맞게 작성\n`),
549
561
  '.harness/progress-tracker.md': _canonicalProgressHeader(lang) + '\n', // 1.9.293: _canonicalProgressHeader 단일 출처 · 1.36.63: lang 전달
package/lib/pure-utils.js CHANGED
@@ -804,7 +804,7 @@ function _managedMerge(file, next, previous, archiveRel, overwriteSet, opts = {}
804
804
  // preserved 래퍼 자체의 보일러플레이트는 이월 대상 아님 (구 <details> 형식 + 신 평문 형식 + en 래퍼 모두)
805
805
  if (l === tag || l === '---' || l === '<details>' || l === '</details>') continue;
806
806
  if (/^```/.test(l) || /^<summary>/.test(l)) continue;
807
- if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서 이어진|^> Custom user\/project content carried over/.test(l)) continue;
807
+ if (/^## Preserved previous content|^Previous content was backed up|^`[^`]*archive[^`]*`$|^> 이전 버전에서 이어진|^> Custom user\/project content carried over|^> ⚠ \d+ Korean lines were preserved below\. If this project never had Korean customizations.*with certainty\.$/.test(l)) continue; // (검수 #2) 생성 문장 전체 앵커 — 같은 접두의 사용자 커스텀 라인 오삭제 방지
808
808
  if (seen.has(l)) continue;
809
809
  seen.add(l);
810
810
  custom.push(raw.replace(/\\`\\`\\`/g, '```')); // 구 형식에서 이스케이프된 펜스 복원
@@ -812,9 +812,18 @@ function _managedMerge(file, next, previous, archiveRel, overwriteSet, opts = {}
812
812
  if (!custom.length) return next;
813
813
  const ar = archiveRel || '.harness/archive';
814
814
  // 1.36.60 (검수): 보존 래퍼 문구도 프로젝트 언어를 따른다 — en 프로젝트에 한글 래퍼가 새 한글원을 만들던 것 해소
815
- const note = opts.lang === 'en'
815
+ let note = opts.lang === 'en'
816
816
  ? `> Custom user/project content carried over from the previous version — re-carried automatically on every migration. Full original backup: \`${ar}\``
817
817
  : `> 이전 버전에서 이어진 사용자/프로젝트 커스텀 내용 — 마이그레이션마다 자동 이월됩니다. 전체 원본 백업: \`${ar}\``;
818
+ // 1.36.73 (8차 헌트 F9 설계 결정): 초고령 KO canonical(현행 altTemplate 에 없는 구판 템플릿)이 en 프로젝트에
819
+ // 커스텀으로 오인 이월될 수 있다. 자동 차감은 데이터 손실 위험(어느 라인이 템플릿인지 확신 불가 — false-DROP 이 버그)
820
+ // 이라 하지 않고, 한글 비중이 높으면 "검토·정리 안내"를 명시해 조용한 동결만 없앤다.
821
+ if (opts.lang === 'en') {
822
+ const hangulLines = custom.filter(l => /[가-힣]/.test(l)).length;
823
+ if (hangulLines >= 10 && hangulLines > custom.length * 0.5) {
824
+ note += `\n> ⚠ ${hangulLines} Korean lines were preserved below. If this project never had Korean customizations, most of these are likely template text from an old Korean install — review and prune manually (data is also in the backup above). leerness will not auto-delete them: it cannot distinguish old template lines from your own notes with certainty.`;
825
+ }
826
+ }
818
827
  return next.trimEnd() + `\n\n---\n${tag}\n## Preserved previous content\n\n${note}\n\n` + custom.join('\n') + '\n';
819
828
  }
820
829
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.72",
3
+ "version": "1.36.73",
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
@@ -7298,5 +7298,44 @@ total++;
7298
7298
  if (!ok) failed++;
7299
7299
  }
7300
7300
 
7301
+ // 1.36.73 (8차 헌트 F12/F9 종결): skills-lock = 디스크 실재 + KO-잔존 이월 안내
7302
+ total++;
7303
+ {
7304
+ let ok = false;
7305
+ const _d = [];
7306
+ try {
7307
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-f12-')); _d.push(d);
7308
+ // (검수 #3) exit 단언 + 정렬 집합 정확 비교 — 카운트만 비교하면 2차 init 실패를 1차 결과가 가렸다
7309
+ const i1 = cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'en', '--skills', 'all', '--no-stale-check', '--no-env'], { encoding: 'utf8', timeout: 40000 });
7310
+ const i2 = cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--language', 'en', '--skills', 'recommended', '--no-stale-check', '--no-env'], { encoding: 'utf8', timeout: 40000 });
7311
+ const dirSet = fs.readdirSync(path.join(d, '.harness', 'skills'), { withFileTypes: true }).filter(e => e.isDirectory()).map(e => e.name).sort();
7312
+ const lockSet = Object.keys(JSON.parse(fs.readFileSync(path.join(d, '.harness', 'skills-lock.json'), 'utf8')).installedSkills).sort();
7313
+ const f12Ok = i1.status === 0 && i2.status === 0 && dirSet.length > 5 && JSON.stringify(dirSet) === JSON.stringify(lockSet);
7314
+ // F9: 실 CLI migrate 경유 — 구판 KO canonical 을 en 프로젝트 CLAUDE.md 에 심고 migrate
7315
+ const d9 = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-f9-')); _d.push(d9);
7316
+ const i3 = cp.spawnSync(process.execPath, [CLI, 'init', d9, '--yes', '--language', 'en', '--no-stale-check', '--no-env'], { encoding: 'utf8', timeout: 40000 });
7317
+ const cl = path.join(d9, 'CLAUDE.md');
7318
+ const ko = Array.from({ length: 15 }, (_, i) => '한국어 구판 템플릿 라인 ' + i).join('\n');
7319
+ fs.appendFileSync(cl, '\n' + ko + '\nMY_CUSTOM_KEEP_42\n');
7320
+ fs.writeFileSync(path.join(d9, '.harness', 'HARNESS_VERSION'), '1.36.0');
7321
+ const mg = cp.spawnSync(process.execPath, [CLI, 'migrate', d9], { encoding: 'utf8', timeout: 60000, maxBuffer: 32 * 1024 * 1024 });
7322
+ const after = fs.readFileSync(cl, 'utf8');
7323
+ const mg2 = cp.spawnSync(process.execPath, [CLI, 'migrate', d9], { encoding: 'utf8', timeout: 60000, maxBuffer: 32 * 1024 * 1024 });
7324
+ const after2 = fs.readFileSync(cl, 'utf8');
7325
+ const f9Ok = i3.status === 0 && mg.status === 0 && mg2.status === 0
7326
+ && after.includes('Korean lines were preserved') // 실 파일에 안내
7327
+ && after.includes('MY_CUSTOM_KEEP_42') // 커스텀 무손실
7328
+ && (after2.match(/Korean lines were preserved/g) || []).length === 1; // 재migrate 멱등
7329
+ // (검수 #2 회귀) 같은 접두의 사용자 커스텀 라인은 생존해야
7330
+ const m = require(path.join(path.dirname(CLI), '..', 'lib', 'pure-utils.js'));
7331
+ const userLine = '> ⚠ 10 Korean lines were preserved for localization QA.';
7332
+ const surv = m._managedMerge('CLAUDE.md', '# N\n', '# O\n' + userLine, 'a', null, { lang: 'en' }).includes(userLine);
7333
+ ok = f12Ok && f9Ok && surv;
7334
+ if (!ok) console.log(` [f12/f9 디버그] f12=${f12Ok}(${dirSet.length}/${lockSet.length}) f9=${f9Ok}(mg=${mg.status}) surv=${surv}`);
7335
+ } catch (e) {} finally { _d.forEach(x => { try { fs.rmSync(x, { recursive: true, force: true }); } catch {} }); }
7336
+ console.log(ok ? '✓ B(1.36.73) F12 skills-lock=디스크 실재 + F9 KO-잔존 이월 안내(멱등·무손실·소수 무경고)' : '✗ F12/F9 실패');
7337
+ if (!ok) failed++;
7338
+ }
7339
+
7301
7340
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
7302
7341
  if (failed > 0) process.exit(1);