leerness 1.36.41 → 1.36.42

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,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.36.42 — 2026-07-16 — 판정 정직화 배치 C — codex 5차 헌트 10건 전건 처분 완결 (#4~#10)
4
+
5
+ - **#4 agent-mode**: 3개 자식 전부 실패해도 "자율 라운드 진행 준비" 선언 + exit 0 — 전부 실패=exit 1, 부분 실패=⚠ 명시, tick/stop 자식 실패 전파, 미지 하위명령 exit 1.
6
+ - **#5 orchestrate**: `--agents NaN/0/-1` 이 조용히 1로 화하고 전원 실패에도 exit 0 → 정수 1~256 검증(`invalid_agents`) + 성공 0=exit 1.
7
+ - **#6 release bump/note**: "banana" 를 0.0.1 로 덮어쓰기 → semver 검증 후 거부(`invalid_version`, 원본 보존) · prerelease 는 npm 의미론(1.2.3-beta.2→1.2.3 안정화) · 멀티라인 노트가 CHANGELOG 에 가짜 버전 헤더를 만들던 것 → 한 리스트 항목으로 정규화.
8
+ - **#7 consistency merge-design-guide**: `--dry-run` 실기록 + 재실행마다 중복 append → dry-run 무기록 + 소스별 멱등(이미 병합 skip 표시).
9
+ - **#8 whats-new `--to`**: value-flag 미등록으로 값이 positional 경로로 흡수돼 **엉뚱한(패키지 내장) CHANGELOG 를 읽던 것** → 등록. 실측: --to 지정 시에도 로컬 CHANGELOG.
10
+ - **#9 lens 커스텀**: 손상 quality-lenses.json 무언 무시 → 경고 노출(advisory 유지) · `constructor` 도메인명이 프로토타입 상속 키로 오인돼 크래시 → 자기 소유 키 검사.
11
+ - **#10 register-pending `--json`**: 사람용 3줄이 JSON 앞에 섞여 파서 실패 → 단일 JSON 문서. (review/persona/guide 의 JSON 계약은 별도 라운드 — 출력 구조 재설계 필요라 이연 명기.)
12
+ - **검증**: selftest 312/312, 13케이스 일괄 실증 스크립트 전부 ✓, exit 전파형 게이트 e2e, 클린룸. **codex 5차 10건 전건 처분(수정 10, guide-json 만 부분 이연).**
13
+
3
14
  ## 1.36.41 — 2026-07-16 — 데이터 보존 3종 2차 — .mcp.json 손상 중단 · register-pending 락 · glossary 마커 보존 (codex 5차 헌트 #1/#2/#3 High)
4
15
 
5
16
  5차 헌트(adapter/ci/orchestrate/guide/doctor/glossary/consistency/release/lens 표면)의 High 3건 — 전부 기존 결함 클래스의 새 발화점:
package/README.md CHANGED
@@ -121,7 +121,7 @@ MIT
121
121
  <!-- leerness:project-readme:start -->
122
122
  ## Leerness Project Harness
123
123
 
124
- 이 프로젝트는 Leerness v1.36.41 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
124
+ 이 프로젝트는 Leerness v1.36.42 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
125
 
126
126
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
127
127
 
@@ -175,7 +175,7 @@ leerness memory restore decision <date|title>
175
175
 
176
176
  ### MCP server (외부 AI 통합)
177
177
 
178
- Leerness v1.36.41는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
178
+ Leerness v1.36.42는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
179
 
180
180
  ```jsonc
181
181
  // 카테고리별
@@ -196,7 +196,7 @@ Leerness v1.36.41는 stdio JSON-RPC MCP server를 내장합니다 — Claude Cod
196
196
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
197
197
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) 회귀 테스트 갱신 → 4) 전체 e2e 스위트 통과 → 5) npm publish + git tag → 6) main push → 7) session close → 8) 다음 라운드 예약.
198
198
 
199
- 현재 누적: **v1.9.x → 1.36.41 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
199
+ 현재 누적: **v1.9.x → 1.36.42 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
200
 
201
201
  ### 성능 가이드
202
202
 
@@ -234,6 +234,6 @@ leerness release pack --close --auto-main-push
234
234
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
235
235
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
236
236
 
237
- Last synced by Leerness v1.36.41: 2026-07-16
237
+ Last synced by Leerness v1.36.42: 2026-07-16
238
238
  <!-- leerness:project-readme:end -->
239
239
 
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.41';
37
+ const VERSION = '1.36.42';
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') 시 호스트 프로세스 오염.
@@ -218,7 +218,7 @@ function _resolveRoot(positional) {
218
218
  }
219
219
  function nonFlagArgs() {
220
220
  const out = [];
221
- const withValue = new Set(['--language','--skills','--path','--status','--progress','--goal','--reason','--next','--target','--token-env','--package','--out','--from','--repo','--id','--note','--evidence','--query','--limit','--action','--agent','--tool','--doc','--command','--capability','--before','--after','--display','--threshold','--trigger','--check','--set','--min-score','--include','--days','--gh-pages-src','--roadmap','--since','--agents','--model','--timeout','--retry-on-fail','--label','--score','--tokens','--alternatives','--impact','--tag','--surface','--depends-on','--affects','--co-changes-with','--files','--branch','--remote','--task-add','--next-action','--role','--provider','--env-var','--deploy','--token-lifetime-hours','--port','--secret','--keep','--shell','--ps-version','--done-when','--test-cmd']); // 1.14.2 (UR-0032): --done-when 값이 positional 로 누출돼 milestone 제목에 흡수되던 것 차단. 1.17.2 (UR-0045): --test-cmd 동일 원칙(신규 value-flag 는 반드시 여기 등록)
221
+ const withValue = new Set(['--language','--skills','--path','--status','--progress','--goal','--reason','--next','--target','--token-env','--package','--out','--from','--to','--repo','--id','--note','--evidence','--query','--limit','--action','--agent','--tool','--doc','--command','--capability','--before','--after','--display','--threshold','--trigger','--check','--set','--min-score','--include','--days','--gh-pages-src','--roadmap','--since','--agents','--model','--timeout','--retry-on-fail','--label','--score','--tokens','--alternatives','--impact','--tag','--surface','--depends-on','--affects','--co-changes-with','--files','--branch','--remote','--task-add','--next-action','--role','--provider','--env-var','--deploy','--token-lifetime-hours','--port','--secret','--keep','--shell','--ps-version','--done-when','--test-cmd']); // 1.14.2 (UR-0032): --done-when 값이 positional 로 누출돼 milestone 제목에 흡수되던 것 차단. 1.17.2 (UR-0045): --test-cmd 동일 원칙(신규 value-flag 는 반드시 여기 등록)
222
222
  const a = process.argv.slice(2);
223
223
  for (let i = 0; i < a.length; i++) {
224
224
  const x = a[i];
@@ -2396,7 +2396,11 @@ function agentModeCmd(root, sub) {
2396
2396
  const r3 = spawnChild(['handoff', root]);
2397
2397
  if (r3.status === 0) log(gr(` ✓ handoff 출력 완료 (위 참조)`)); else log(yl(` ⚠ handoff 종료 ${r3.status}`));
2398
2398
  log('');
2399
- log(gr(`✓ agent-mode start 완료자율 라운드 진행 준비`));
2399
+ // 1.36.42 (codex 5차 #4): 세 자식 전부 실패(status null 포함)여도 "준비" 를 선언하던 허위 성공 실패 기준 정직 판정.
2400
+ const _fails = [r1, r2, r3].filter(r => r.status !== 0).length;
2401
+ if (_fails === 3) { fail(`agent-mode start 실패 — 3개 단계 전부 실패 (경로/설치 확인: ${root})`); process.exitCode = 1; return; }
2402
+ if (_fails > 0) { log(yl(`⚠ agent-mode start 부분 완료 — ${_fails}개 단계 실패 (위 ⚠ 참조)`)); }
2403
+ else log(gr(`✓ agent-mode start 완료 — 자율 라운드 진행 준비`));
2400
2404
  return;
2401
2405
  }
2402
2406
  if (sub === 'tick') {
@@ -2404,6 +2408,7 @@ function agentModeCmd(root, sub) {
2404
2408
  log(cy(`# leerness agent-mode tick (1.9.239) — 가벼운 상태 확인`));
2405
2409
  const r = spawnChild(['pulse', '--path', root]);
2406
2410
  process.stdout.write(r.stdout || '');
2411
+ if (r.status !== 0) process.exitCode = 1; // 1.36.42 (#4): 자식 실패 전파
2407
2412
  return;
2408
2413
  }
2409
2414
  if (sub === 'stop') {
@@ -2411,10 +2416,12 @@ function agentModeCmd(root, sub) {
2411
2416
  log(dm(` → session close --auto-apply-delivered --auto-cleanup-branches --auto-fix-encoding`));
2412
2417
  const r = spawnChild(['session', 'close', root, '--auto-apply-delivered', '--auto-cleanup-branches', '--auto-fix-encoding']);
2413
2418
  process.stdout.write(r.stdout || '');
2419
+ if (r.status !== 0) process.exitCode = 1; // 1.36.42 (#4): 자식 실패 전파
2414
2420
  return;
2415
2421
  }
2416
- log(yl(`unknown subcommand: ${sub}`));
2422
+ fail(`unknown subcommand: ${sub}`); // 1.36.42 (#4): 미지 하위명령 exit 1 (종전 exit 0)
2417
2423
  log(dm(` → leerness agent-mode help`));
2424
+ process.exitCode = 1;
2418
2425
  }
2419
2426
 
2420
2427
  // 1.9.234: recentChanges — 최근 N 라운드의 핵심 변경 요약 (git tag commit subject)
@@ -3291,6 +3298,22 @@ function _selfTestCases() {
3291
3298
  } catch {} finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
3292
3299
  return mcpGuard && rpLock && gOk;
3293
3300
  } },
3301
+ { name: '판정 정직화 배치C (1.36.42, codex 5차 #4~#10): agent-mode/orchestrate exit + release semver/note + consistency dry-run·멱등 + whats-new --to + lens 커스텀 + JSON 순수 (소스가드+행위)', run: () => {
3302
+ const s = read(__filename);
3303
+ const amOk = s.includes('if (_fails === 3) { fail(`agent-mode start 실패') && s.includes('미지 하위명령 exit 1');
3304
+ const orOk = s.includes("'invalid_agents'") && s.includes('if (!ok.length) process.exitCode = 1;');
3305
+ const relOk = s.includes("'invalid_version'") && s.includes('멀티라인은 한 리스트 항목의 연속 들여쓰기로 정규화');
3306
+ const toOk = s.includes("'--from','--to','--repo'");
3307
+ const conOk = s.includes('이미 병합됨(skip)') && /mergeDesign[\s\S]{0,600}has\('--dry-run'\)/.test(s);
3308
+ const lensOk = s.includes('function _loadProjectLensesEx') && s.includes('Object.prototype.hasOwnProperty.call(out, k)');
3309
+ const rpJsonOk = /registerPendingCmd[\s\S]{0,1600}if \(has\('--json'\)\) \{ log\(JSON\.stringify\(\{ ok: true, id, \.\.\.row \}, null, 2\)\); return; \}/.test(s);
3310
+ // 행위: prerelease 안정화 + 프로토타입 키 무크래시
3311
+ const p = require('../lib/pure-utils');
3312
+ const preOk = p.compareVer('1.2.3', '1.2.3-beta.2') === 1; // bump 결과가 상위인지 간접 확인
3313
+ let protoOk = false;
3314
+ try { const m = _mergeLensCatalog(LENS_CATALOG, { constructor: { title: 'C', persona: 'r', questions: ['q1'] } }); protoOk = !!(m.constructor && Array.isArray(m.constructor.questions) && m.constructor.questions[0] === 'q1'); } catch {}
3315
+ return amOk && orOk && relOk && toOk && conOk && lensOk && rpJsonOk && preOk && protoOk;
3316
+ } },
3294
3317
  { name: 'debug 렌즈 (1.36.27, obra/superpowers systematic-debugging): 자기질문 6문항 ko/en 락스텝 + affects 유효 + route bugfix 힌트 + 파일매핑 미확장 — 행위검사', run: () => {
3295
3318
  const d = LENS_CATALOG.debug;
3296
3319
  if (!d) return false;
@@ -5579,12 +5602,18 @@ const LENS_CATALOG = {
5579
5602
  };
5580
5603
  // 1.19.3 (UR-0003 렌즈 완전판 v3): 프로젝트별 커스텀 렌즈 — .harness/quality-lenses.json 읽기-병합(쓰기 명령 없음, AI/사용자가 편집).
5581
5604
  // 포맷: { "domains": { "code": { "questions": ["추가 질문"] }, "a11y": { "title":"접근성", "persona":"스크린리더 사용자", "questions":[...], "affects":["design"] } } }
5582
- function _loadProjectLenses(root) {
5605
+ // 1.36.42 (codex 5차 #9): 손상 파일을 무언 무시하면 사용자는 커스텀 렌즈가 적용된 줄 안다 — 에러를 함께 반환(Ex), 기존 시그니처는 래퍼.
5606
+ function _loadProjectLensesEx(root) {
5607
+ const p = path.join(absRoot(root || process.cwd()), '.harness', 'quality-lenses.json');
5608
+ if (!exists(p)) return { domains: {}, error: null };
5583
5609
  try {
5584
- const p = path.join(absRoot(root || process.cwd()), '.harness', 'quality-lenses.json');
5585
- if (!exists(p)) return {};
5586
5610
  const j = JSON.parse(read(p));
5587
- return (j && typeof j === 'object' && j.domains && typeof j.domains === 'object') ? j.domains : {};
5611
+ return { domains: (j && typeof j === 'object' && j.domains && typeof j.domains === 'object') ? j.domains : {}, error: null };
5612
+ } catch (e) { return { domains: {}, error: `quality-lenses.json 파싱 실패(커스텀 렌즈 미적용): ${e.message}` }; }
5613
+ }
5614
+ function _loadProjectLenses(root) {
5615
+ try {
5616
+ return _loadProjectLensesEx(root).domains;
5588
5617
  } catch { return {}; }
5589
5618
  }
5590
5619
  // 내장 + 프로젝트 커스텀 병합. 기존 도메인 → 질문 추가(dedup, 최대 16 — 내장 최대치(database 12) + 커스텀 여유; 낮으면 내장이 잘림); 신규 도메인 → 기본값으로 추가. _custom 플래그로 표시.
@@ -5594,7 +5623,9 @@ function _mergeLensCatalog(builtin, custom) {
5594
5623
  for (const [k, c] of Object.entries(custom || {})) {
5595
5624
  if (!c || typeof c !== 'object') continue;
5596
5625
  const cq = Array.isArray(c.questions) ? c.questions.filter(q => typeof q === 'string' && q.trim()) : [];
5597
- if (out[k]) {
5626
+ // 1.36.42 (codex 5차 #9b): truthy 검사는 'constructor' 같은 프로토타입 키에서 상속 함수를 기존 도메인으로
5627
+ // 오인해 .questions.push 크래시 — 자기 소유 키만 병합 대상으로.
5628
+ if (Object.prototype.hasOwnProperty.call(out, k)) {
5598
5629
  const seen = new Set(out[k].questions);
5599
5630
  for (const q of cq) if (!seen.has(q)) { out[k].questions.push(q); seen.add(q); }
5600
5631
  const before = out[k].questions.length;
@@ -5678,6 +5709,8 @@ function lensCmd(domain, opts = {}) {
5678
5709
  if (domain != null) domain = String(domain).trim().toLowerCase();
5679
5710
  // 1.19.3: 내장 + 프로젝트 커스텀(.harness/quality-lenses.json) 병합 catalog.
5680
5711
  const root = opts.root || arg('--path', process.cwd());
5712
+ // 1.36.42 (#9): 손상 커스텀 렌즈 파일은 무언 무시 대신 경고 (advisory — lens 자체는 계속 동작)
5713
+ try { const _lx = _loadProjectLensesEx(root); if (_lx.error && !jsonMode) warn(_lx.error); } catch {}
5681
5714
  // 1.36.30: 토글 준수 (⚙ 탭 / leerness toggle set lens off)
5682
5715
  if (!_tgl.toggleOn(root, 'lens')) {
5683
5716
  if (jsonMode) { log(JSON.stringify({ ok: true, skipped: true, reason: 'toggle_off', hint: 'leerness toggle set lens on' })); return; }
@@ -12393,7 +12426,11 @@ async function orchestrateCmd(root, goalParts) {
12393
12426
  return process.exit(1);
12394
12427
  }
12395
12428
 
12396
- const agentCount = Math.max(1, Math.min(256, parseInt(arg('--agents', '4'), 10)));
12429
+ // 1.36.42 (codex 5차 #5): NaN/0/-1 조용히 1(또는 NaN 루프) 화하던 것 — 정수 1..256 검증.
12430
+ const _agRaw = arg('--agents', '4');
12431
+ const _agN = Number(_agRaw);
12432
+ if (!Number.isInteger(_agN) || _agN < 1 || _agN > 256) { failJson(has('--json'), 'invalid_agents', `--agents 는 1~256 정수여야 합니다 (받음: ${_agRaw})`); return; }
12433
+ const agentCount = _agN;
12397
12434
  const model = arg('--model', process.env.LEERNESS_OLLAMA_MODEL || 'qwen2.5:7b-instruct');
12398
12435
  const timeoutMs = parseInt(arg('--timeout', '300000'), 10);
12399
12436
  const retryOnFail = parseInt(arg('--retry-on-fail', '0'), 10); // 1.9.22 후보 2 통합
@@ -12428,6 +12465,7 @@ async function orchestrateCmd(root, goalParts) {
12428
12465
  if (failures.length) {
12429
12466
  for (const f of failures.slice(0, 3)) log(` · agent ${f.agent}: ${f.error}`);
12430
12467
  }
12468
+ if (!ok.length) process.exitCode = 1; // 1.36.42 (#5): 전원 실패인데 exit 0 이던 허위 성공 — 성공 0 = exit 1
12431
12469
 
12432
12470
  if (ok.length) {
12433
12471
  const totalPromptTokens = ok.reduce((a, b) => a + b.promptTokens, 0);
@@ -14014,10 +14052,11 @@ function registerPendingCmd(root, requestParts) {
14014
14052
  return rec;
14015
14053
  });
14016
14054
  const id = row.id;
14055
+ // 1.36.42 (codex 5차 #10): --json 인데 사람용 3줄이 앞에 섞여 파서가 깨짐 — JSON 모드는 단일 JSON 문서만.
14056
+ if (has('--json')) { log(JSON.stringify({ ok: true, id, ...row }, null, 2)); return; }
14017
14057
  log(`✓ ${id} 등록됨 (in-progress) by ${agent}`);
14018
14058
  log(` request: ${row.request}`);
14019
14059
  log(` 💡 작업 완료 후: leerness task update ${id} --status done --evidence "..."`);
14020
- if (has('--json')) log(JSON.stringify({ ok: true, id, ...row }, null, 2));
14021
14060
  }
14022
14061
 
14023
14062
  // 1.9.22 후보 4: llm-bench record + retro 통합
@@ -14060,10 +14099,20 @@ function mergeDesign(root) {
14060
14099
  root = absRoot(root);
14061
14100
  const canonical = path.join(root,'.harness/design-system.md');
14062
14101
  const cands = ['designguide.md','design-guide.md','.harness/designguide.md','docs/designguide.md','docs/design-guide.md'];
14102
+ // 1.36.42 (codex 5차 #7): (a) --dry-run 인데 실기록, (b) 재실행마다 같은 내용 중복 append(비멱등) — 둘 다 교정.
14103
+ const dry = has('--dry-run');
14104
+ const already = exists(canonical) ? read(canonical) : '';
14063
14105
  let merged = '';
14064
- for (const f of cands) { const p = path.join(root,f); if (exists(p)) merged += `\n\n## Merged from ${f}\n\n` + read(p); }
14106
+ const skipped = [];
14107
+ for (const f of cands) {
14108
+ const p = path.join(root,f);
14109
+ if (!exists(p)) continue;
14110
+ if (already.includes(`## Merged from ${f}`)) { skipped.push(f); continue; } // 멱등: 같은 소스 재병합 방지
14111
+ merged += `\n\n## Merged from ${f}\n\n` + read(p);
14112
+ }
14113
+ if (dry) { ok(`[dry-run] ${merged ? '병합 예정' : '병합 대상 없음'}${skipped.length ? ` · 이미 병합됨(skip): ${skipped.join(', ')}` : ''} — 실제 변경 0`); return; }
14065
14114
  if (merged) append(canonical, merged);
14066
- ok(merged ? 'design guides merged into .harness/design-system.md' : 'nothing to merge');
14115
+ ok(merged ? `design guides merged into .harness/design-system.md${skipped.length ? ` (이미 병합 skip: ${skipped.join(', ')})` : ''}` : (skipped.length ? `nothing to merge (이미 전부 병합됨: ${skipped.join(', ')})` : 'nothing to merge'));
14067
14116
  }
14068
14117
 
14069
14118
  // 1.9.2: self check를 update --check의 thin wrapper로 통합 (단일 출처).
@@ -15526,12 +15575,16 @@ function releaseBump(root) {
15526
15575
  if (!exists(pkgFile)) return fail('package.json 없음');
15527
15576
  let pkg; try { pkg = JSON.parse(read(pkgFile)); } catch (e) { return fail('package.json 파싱 실패: ' + e.message); }
15528
15577
  const cur = String(pkg.version || '0.0.0');
15529
- const parts = cur.split('.').map(n => parseInt(n, 10) || 0);
15530
- const [maj, min, pat] = [parts[0]||0, parts[1]||0, parts[2]||0];
15578
+ // 1.36.42 (codex 5차 #6): (a) semver("banana") 0.0.0 으로 오인해 0.0.1 로 덮어쓰던 것 → 거부.
15579
+ // (b) prerelease patch bump npm 의미론(1.2.3-beta.2 → 1.2.3: pre 태그 제거=안정화)을 따름.
15580
+ const _vm = cur.match(/^(\d+)\.(\d+)\.(\d+)(-[0-9A-Za-z.-]+)?$/);
15581
+ if (!_vm) { failJson(has('--json'), 'invalid_version', `package.json version 이 semver 가 아님: "${cur}" — bump 거부(수동 교정 필요)`); return; }
15582
+ const [maj, min, pat] = [parseInt(_vm[1], 10), parseInt(_vm[2], 10), parseInt(_vm[3], 10)];
15583
+ const isPre = !!_vm[4];
15531
15584
  let next;
15532
- if (kind === 'major') next = `${maj + 1}.0.0`;
15533
- else if (kind === 'minor') next = `${maj}.${min + 1}.0`;
15534
- else next = `${maj}.${min}.${pat + 1}`;
15585
+ if (kind === 'major') next = isPre && min === 0 && pat === 0 ? `${maj}.0.0` : `${maj + 1}.0.0`;
15586
+ else if (kind === 'minor') next = isPre && pat === 0 ? `${maj}.${min}.0` : `${maj}.${min + 1}.0`;
15587
+ else next = isPre ? `${maj}.${min}.${pat}` : `${maj}.${min}.${pat + 1}`;
15535
15588
  pkg.version = next;
15536
15589
  writeUtf8(pkgFile, JSON.stringify(pkg, null, 2) + '\n');
15537
15590
  const hv = path.join(root, '.harness/HARNESS_VERSION');
@@ -15542,6 +15595,9 @@ function releaseBump(root) {
15542
15595
  function releaseNote(root, text) {
15543
15596
  root = absRoot(root);
15544
15597
  if (!text) return fail('note text required (e.g., release note "내용")');
15598
+ // 1.36.42 (codex 5차 #6b): 개행 포함 노트가 CHANGELOG 에 가짜 버전 헤더("## 9.9.9 …")를 만들 수 있었음 —
15599
+ // 멀티라인은 한 리스트 항목의 연속 들여쓰기로 정규화(헤더 문자로 시작하는 줄도 텍스트로 유지).
15600
+ text = String(text).replace(/\r\n?/g, '\n').split('\n').map((l, i) => i === 0 ? l : ' ' + l.replace(/^#+\s*/, '')).join('\n');
15545
15601
  const pkgFile = path.join(root, 'package.json');
15546
15602
  let version = 'unknown';
15547
15603
  if (exists(pkgFile)) { try { version = JSON.parse(read(pkgFile)).version || 'unknown'; } catch {} }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.41",
3
+ "version": "1.36.42",
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",