leerness 1.36.69 → 1.36.70

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,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.70 — 2026-07-24 — 성장 한계 클래스 2: migrate 보고 상한 (88KB → 13.5KB) — R-0001 검수 13회전
4
+
5
+ 1.36.69 whats-new 상한과 동일 규율을 migrate 보고서의 "AI must re-read" 섹션에 적용. 큰 버전 점프(602 릴리스)에서 보고서가 ~88KB 로 폭주해 AI 컨텍스트를 태우던 것.
6
+
7
+ - **헤드라인 최신 30 + 명령/플래그/파일 목록 40 캡**, 각 헤더에 실제 총수 표기 + 정직한 "… 외 N개" 오버플로 라인 + `whats-new --from X --all/--limit N` 탈출구. 작은 점프에선 상한 비가시(전량 출력).
8
+ - (검수 #1 Medium) **광고된 `--limit N` 이 텍스트 모드에서 조용히 무시되던 것 수정** — 명시적 `--limit` 존중 + 오버플로 라인(기본 텍스트 출력은 전체 유지, 계약 무변경). 종전엔 보고서가 권하는 명령이 602줄을 그대로 뱉었다.
9
+ - (검수 #2) e2e 픽스처 `--no-stale-check`(빈 캐시 npm 조회 8초 지연 차단). (검수 #3) e2e 단언을 헤딩 파싱 기반 정확 일치(shown===30·행수·오버플로 수치)로 강화.
10
+ - 검수 실측(codex): 30/602 헤드라인 + 외 572 · 명령 40/170+130 · 플래그 40/133+93 · 파일 40/43+3 — 전 수치 독립 파싱과 일치. 소점프 무영향, 마크다운 정합, fromV 엣지(동일/쓰레기/미래) 전부 통과.
11
+ - 검증: selftest 334, e2e +1.
12
+
3
13
  ## 1.36.69 — 2026-07-22 — 성장 한계 클래스: whats-new --json 응답 상한 (804KB → 30KB) — R-0001 검수 12회전
4
14
 
5
15
  직전 게이트가 우연히 드러낸 누적 성장 문제를 픽스처 우회가 아니라 제품에서 해결. CHANGELOG 1MB 돌파로 `--json` 이 804KB 를 뱉어 AI 소비자 컨텍스트를 폭파시키고 기본 stdout 버퍼에서 절단됐다.
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.69 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.70 하네스를 사용합니다. 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.69는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
+ Leerness v1.36.70는 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.69는 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.69 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.70 릴리스 태그 이력** (수백 라운드) · _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.69: 2026-07-23
238
+ Last synced by Leerness v1.36.70: 2026-07-24
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.69';
37
+ const VERSION = '1.36.70';
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') 시 호스트 프로세스 오염.
@@ -929,17 +929,22 @@ function writeMigrationReport(root, backup, actions, opts = {}) {
929
929
  v.newFiles.forEach(f => allFiles.add(f));
930
930
  }
931
931
  if (diff.length) {
932
+ // 1.36.70 (성장 한계 클래스, whats-new 1.36.69 와 동일 규율): 큰 버전 점프(601+ 릴리스)에서
933
+ // 보고서가 ~88KB 로 폭주해 AI 컨텍스트를 태우던 것 — 목록별 상한 + 정직한 "외 N개" 표기.
934
+ const _capList = (set, cap, fmt) => { const a = [...set]; return a.slice(0, cap).map(fmt).join('\n') + (a.length > cap ? `\n- … 외 ${a.length - cap}개 (전체: \`leerness whats-new --from ${fromV} --all\`)` : ''); };
932
935
  aiReadBlock = `\n## 🤖 AI must re-read (1.9.41 차분 안내)\n\n`;
933
- aiReadBlock += `이 migrate는 ${fromV} → ${VERSION} 점프입니다. 메인 AI 에이전트는 다음을 인지하고 우선 활용:\n\n`;
934
- if (allCommands.size) aiReadBlock += `**📌 신규 명령** (이전엔 없던 ):\n${[...allCommands].map(c => `- \`leerness ${c}\``).join('\n')}\n\n`;
935
- if (allFlags.size) aiReadBlock += `**🚩 신규 플래그**:\n${[...allFlags].map(f => `- \`${f}\``).join('\n')}\n\n`;
936
- if (allFiles.size) aiReadBlock += `**📄 신규/변경 파일** (반드시 재독):\n${[...allFiles].map(f => `- \`${f}\``).join('\n')}\n\n`;
937
- aiReadBlock += `**버전별 헤드라인**:\n`;
938
- for (const v of diff) {
936
+ aiReadBlock += `이 migrate는 ${fromV} → ${VERSION} 점프입니다 (${diff.length}개 릴리스). 메인 AI 에이전트는 다음을 인지하고 우선 활용:\n\n`;
937
+ if (allCommands.size) aiReadBlock += `**📌 신규 명령** (이전엔 없던 것, ${allCommands.size}개):\n${_capList(allCommands, 40, c => `- \`leerness ${c}\``)}\n\n`;
938
+ if (allFlags.size) aiReadBlock += `**🚩 신규 플래그** (${allFlags.size}개):\n${_capList(allFlags, 40, f => `- \`${f}\``)}\n\n`;
939
+ if (allFiles.size) aiReadBlock += `**📄 신규/변경 파일** (반드시 재독, ${allFiles.size}개):\n${_capList(allFiles, 40, f => `- \`${f}\``)}\n\n`;
940
+ const _hlCap = 30;
941
+ aiReadBlock += `**버전별 헤드라인** (최신 ${Math.min(_hlCap, diff.length)}/${diff.length}):\n`;
942
+ for (const v of diff.slice(0, _hlCap)) {
939
943
  const firstLine = (v.body.match(/^\*\*([^*]+)\*\*/) || [])[1]
940
944
  || (v.body.split('\n').find(l => l.trim() && !l.startsWith('##')) || '').trim().slice(0, 120);
941
945
  aiReadBlock += `- ${v.version} — ${firstLine || '(no headline)'}\n`;
942
946
  }
947
+ if (diff.length > _hlCap) aiReadBlock += `- … 외 ${diff.length - _hlCap}개 버전 — \`leerness whats-new --from ${fromV} --limit N\` 으로 조회\n`;
943
948
  aiReadBlock += `\n**권장 행동**:\n1. 위 신규 명령을 \`--help\`로 확인\n2. \`AGENTS.md\` / \`CLAUDE.md\` / \`.harness/session-workflow.md\` 재독 (다음 \`leerness handoff\` 호출 시 자동 안내)\n3. 이전 청크의 기억 무효 — 새 도구 우선 시도\n4. 상세: \`leerness whats-new --from ${fromV}\`\n`;
944
949
  }
945
950
  }
@@ -204,12 +204,17 @@ function whatsNewCmd(root, deps = {}) {
204
204
  if (allFiles.size) log(t(` 📄 신규 파일: ${[...allFiles].join(', ')}`, ` 📄 new files: ${[...allFiles].join(', ')}`));
205
205
  log('');
206
206
  log(t(`## 📜 버전별 헤드라인`, `## 📜 per-version headlines`));
207
- for (const v of diff) {
207
+ // 1.36.70 (검수 #1): migrate 보고서가 광고하는 `--limit N` 이 텍스트 모드에서 조용히 무시되던 것 —
208
+ // 명시적 --limit 만 존중(기본 텍스트 출력은 전체 유지, 계약 무변경).
209
+ const _txtRawLim = parseInt(arg('--limit', ''), 10);
210
+ const _txtLim = (has('--limit') && Number.isFinite(_txtRawLim) && _txtRawLim > 0) ? _txtRawLim : diff.length;
211
+ for (const v of diff.slice(0, _txtLim)) {
208
212
  // body 첫 줄(또는 strong header) 추출
209
213
  const firstLine = (v.body.match(/^\*\*([^*]+)\*\*/) || [])[1]
210
214
  || (v.body.split('\n').find(l => l.trim() && !l.startsWith('##')) || '').trim().slice(0, 120);
211
215
  log(` • ${v.version}${v.date ? ` (${v.date})` : ''} — ${firstLine || '(no headline)'}`);
212
216
  }
217
+ if (_txtLim < diff.length) log(t(` … 외 ${diff.length - _txtLim}개 버전 (전체: --all 또는 --limit 생략)`, ` … ${diff.length - _txtLim} more version(s) (drop --limit or use --all for everything)`));
213
218
  log('');
214
219
  log(t(`## 💡 권장 행동`, `## 💡 recommended actions`));
215
220
  log(t(` 1. 위 신규 명령들을 시도해 보세요 (예: \`leerness <명령> --help\`)`, ` 1. Try the new commands above (e.g. \`leerness <command> --help\`)`));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.69",
3
+ "version": "1.36.70",
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
@@ -7207,5 +7207,36 @@ total++;
7207
7207
  if (!ok) failed++;
7208
7208
  }
7209
7209
 
7210
+ // 1.36.70 (성장 한계 클래스): migrate 보고서 상한 — 큰 버전 점프에서 ~88KB 폭주하던 AI must re-read 를 목록별 캡 + "외 N개" 정직 표기
7211
+ total++;
7212
+ {
7213
+ let ok = false;
7214
+ const _d = [];
7215
+ try {
7216
+ const d = fs.mkdtempSync(path.join(os.tmpdir(), 'leerness-migcap-')); _d.push(d);
7217
+ // (검수 #2) --no-stale-check: 빈 캐시에서 npm 조회로 최대 8초 지연되던 것 차단
7218
+ cp.spawnSync(process.execPath, [CLI, 'init', d, '--yes', '--no-env', '--no-stale-check'], { encoding: 'utf8', timeout: 40000 });
7219
+ fs.writeFileSync(path.join(d, '.harness', 'HARNESS_VERSION'), '1.9.33');
7220
+ cp.spawnSync(process.execPath, [CLI, 'migrate', d], { encoding: 'utf8', timeout: 60000, maxBuffer: 32 * 1024 * 1024 });
7221
+ const rep = fs.readFileSync(path.join(d, '.harness', 'migration-report.md'), 'utf8');
7222
+ const sizeOk = rep.length < 30 * 1024;
7223
+ // (검수 #3) 헤딩을 파싱해 shown/total 정합 + 헤드라인 행수 + 오버플로 수치 정확 일치로 단언 강화
7224
+ const hm = rep.match(/최신 (\d+)\/(\d+)/);
7225
+ const shown = hm ? parseInt(hm[1], 10) : 0, totalV = hm ? parseInt(hm[2], 10) : 0;
7226
+ const hlRows = (rep.match(/^- 1\.\d+\.\d+ — /gm) || []).length;
7227
+ const om = rep.match(/외 (\d+)개 버전/);
7228
+ const capOk = shown === 30 && totalV > 30 && hlRows === 30 && !!om && parseInt(om[1], 10) === totalV - 30;
7229
+ const escapeOk = rep.includes('whats-new --from 1.9.33'); // 전체가 필요한 소비자를 위한 탈출구
7230
+ // (검수 #1) 광고된 --limit 이 텍스트 모드에서도 실제 동작
7231
+ const limTxt = cp.spawnSync(process.execPath, [CLI, 'whats-new', '--from', '1.9.33', '--limit', '5'], { encoding: 'utf8', timeout: 20000, maxBuffer: 32 * 1024 * 1024 });
7232
+ const limRows = (limTxt.stdout.match(/^ {2}• 1\.\d+\.\d+/gm) || []).length;
7233
+ const limOk = limRows === 5 && /외 \d+개 버전|more version/.test(limTxt.stdout);
7234
+ ok = sizeOk && capOk && escapeOk && limOk;
7235
+ if (!ok) console.log(` [migcap 디버그] size=${sizeOk}(${Math.round(rep.length / 1024)}KB) cap=${capOk}(${shown}/${totalV},rows=${hlRows}) escape=${escapeOk} lim=${limOk}(${limRows})`);
7236
+ } catch (e) {} finally { _d.forEach(x => { try { fs.rmSync(x, { recursive: true, force: true }); } catch {} }); }
7237
+ console.log(ok ? '✓ B(1.36.70) migrate 보고 상한: 88KB→<30KB + 외N개 정직 표기 + whats-new 탈출구' : '✗ migrate 보고 상한 실패');
7238
+ if (!ok) failed++;
7239
+ }
7240
+
7210
7241
  console.log(`\nE2E result: ${total - failed}/${total} passed · ${((Date.now() - _e2eStart) / 1000).toFixed(0)}s`);
7211
7242
  if (failed > 0) process.exit(1);