leerness 1.36.56 → 1.36.57

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.57 — 2026-07-21 — integrity check --repair: managed 문서 무결성 복구 (외부감사 F-04) — R-0001 검수 2회전
4
+
5
+ drift(신선도)와 별개로 "파일 자체가 잘리거나 사라진" 손상을 감지/복구. R-0001 codex 검수가 High 2 포함 5건을 지적 — 전건 재현·보정 후 재검(minimal 오판·repair 실패 exit 0 는 검수 없이는 출하됐을 결함).
6
+
7
+ - **`leerness integrity check [--repair] [--force] [--json]`**: 정책-문서 12종(사용자 데이터 표면 제외) — 부재/H1 전무/템플릿 40% 미만 절단 감지. --repair 는 원본 archive 대피(pid+seq 접미, 검수 #4) 후 현재 버전 템플릿 재생성.
8
+ - (검수 #1 High) **minimal 설치 존중**: manifest.minimal 반영 — minimal 이 뺀 6종을 '부재 손상' 오판/재생성하지 않음(6종만 검사).
9
+ - (검수 #2 High) **repair 실패 = exit 1**: 종전엔 복구 실패(EEXIST 등)에도 exit 0.
10
+ - (검수 #3) **오탐 완화**: H1 판정을 "H1 전무"로(제목 번역/개명 무탐), 길이-단독 신호(truncated)는 advisory — --force 없인 복구/exit 비관여(합리적 축약 보호).
11
+ - (검수 #5) unknown 하위명령 JSON 오류 + templateBytes 실제 바이트.
12
+ - 실측: 감사 시나리오(context-routing 절단) 감지→복구, 사용자 추가 편집 무탐, en/ko 라운드트립 클린. selftest 330(+1).
13
+
3
14
  ## 1.36.56 — 2026-07-21 — 시크릿 스캐너 F-06 (외부감사 P1) + 매 라운드 codex 검수 룰 (R-0001, 사용자 지시)
4
15
 
5
16
  사용자 지시로 영구 룰 R-0001(every-round: 출하 전 codex 교차 검수) 등록 — 이 릴리스가 첫 적용: 1차 구현 → codex 검수가 4건 지적 → 전건 재현 확인 후 보정 → 재검.
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.56 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
125
+ 이 프로젝트는 Leerness v1.36.57 하네스를 사용합니다. 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.56는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **86개 도구**를 노출:
179
+ Leerness v1.36.57는 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.56는 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.56 릴리스 태그 이력** (수백 라운드) · _reports/는 비공개 보존.
200
+ 현재 누적: **v1.9.x → 1.36.57 릴리스 태그 이력** (수백 라운드) · _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.56: 2026-07-21
238
+ Last synced by Leerness v1.36.57: 2026-07-21
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.56';
37
+ const VERSION = '1.36.57';
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') 시 호스트 프로세스 오염.
@@ -5153,6 +5153,34 @@ function _selfTestCases() {
5153
5153
  const srcOk = read(__filename).includes('_seenAt.has(_valKey)') && read(__filename).includes('_giMatch(fileRel)');
5154
5154
  return unknownOk && sameTokenOnce && distinctKept && extlessGuard && srcOk;
5155
5155
  } finally { try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
5156
+ } },
5157
+ { name: 'integrity check/repair (1.36.57, 외부감사 F-04): 절단/부재 감지 → archive 대피 후 템플릿 재생성 · 사용자 추가편집 무탐 — 행위검사', run: () => {
5158
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), '__leerness_int_'));
5159
+ const _w = process.stdout.write; const saveArgv = process.argv; const saveExit = process.exitCode;
5160
+ try {
5161
+ process.stdout.write = () => true;
5162
+ fs.mkdirSync(path.join(tmp, '.harness'), { recursive: true });
5163
+ const tf = coreFiles(tmp, 'ko', [], {});
5164
+ for (const k of _INTEGRITY_DOCS) fs.mkdirSync(path.dirname(path.join(tmp, k)), { recursive: true }), fs.writeFileSync(path.join(tmp, k), tf[k]);
5165
+ fs.writeFileSync(path.join(tmp, '.harness', 'manifest.json'), '{"language":"ko"}');
5166
+ // 손상 주입: 절단 + 삭제 / 사용자 추가 편집(무탐 대상)
5167
+ fs.writeFileSync(path.join(tmp, '.harness', 'context-routing.md'), 'x');
5168
+ fs.rmSync(path.join(tmp, '.harness', 'guardrails.md'));
5169
+ fs.appendFileSync(path.join(tmp, '.harness', 'testing-strategy.md'), '\n## 커스텀 절차\n- 내 규칙\n');
5170
+ process.argv = ['node', 'x', 'integrity', 'check'];
5171
+ process.exitCode = 0; integrityCmd(tmp);
5172
+ const detected = process.exitCode === 1;
5173
+ process.argv = ['node', 'x', 'integrity', 'check', '--repair'];
5174
+ process.exitCode = 0; integrityCmd(tmp);
5175
+ const routing = fs.readFileSync(path.join(tmp, '.harness', 'context-routing.md'), 'utf8');
5176
+ const repaired = routing.includes('# Context Routing') && fs.existsSync(path.join(tmp, '.harness', 'guardrails.md'));
5177
+ const backed = fs.readdirSync(path.join(tmp, '.harness', 'archive')).some(n => n.startsWith('integrity-context-routing'));
5178
+ const customKept = fs.readFileSync(path.join(tmp, '.harness', 'testing-strategy.md'), 'utf8').includes('커스텀 절차');
5179
+ process.argv = ['node', 'x', 'integrity', 'check'];
5180
+ process.exitCode = 0; integrityCmd(tmp);
5181
+ const cleanAfter = process.exitCode !== 1;
5182
+ return detected && repaired && backed && customKept && cleanAfter;
5183
+ } finally { process.stdout.write = _w; process.argv = saveArgv; process.exitCode = saveExit; try { fs.rmSync(tmp, { recursive: true, force: true }); } catch {} }
5156
5184
  } }
5157
5185
  ];
5158
5186
  }
@@ -5526,6 +5554,79 @@ function anchorsCmd(root, sub) {
5526
5554
  fail(`알 수 없는 하위명령: ${sub} (가능: status, draft [--apply])`);
5527
5555
  }
5528
5556
 
5557
+ // 1.36.57 (외부 GPT 감사 F-04): managed 정책-문서 무결성 점검/복구 — drift(신선도)와 별개로 "파일 자체가
5558
+ // 잘리거나 사라진" 손상을 감지한다. 대상은 사용자가 편집하지 않는 순수 템플릿 문서군만(사용자 데이터 표면 제외 —
5559
+ // plan/progress/brief/decisions 등은 절대 건드리지 않음). 판정은 보수적: 부재 / H1 상실 / 템플릿 대비 40% 미만 크기.
5560
+ // --repair 는 손상 원본을 .harness/archive/ 로 대피 후 현재 버전 템플릿으로 재생성.
5561
+ const _INTEGRITY_DOCS = [
5562
+ '.harness/session-workflow.md', '.harness/context-routing.md', '.harness/writeback-policy.md',
5563
+ '.harness/task-type-map.md', '.harness/protected-files.md', '.harness/guardrails.md',
5564
+ '.harness/consistency-policy.md', '.harness/testing-strategy.md', '.harness/review-checklist.md',
5565
+ '.harness/release-checklist.md', '.harness/session-close-policy.md', '.harness/plan-progress-boundary.md',
5566
+ ];
5567
+ function integrityCmd(root) {
5568
+ root = absRoot(root);
5569
+ const json = has('--json');
5570
+ const repair = has('--repair');
5571
+ if (!exists(path.join(root, '.harness'))) { failJson(json, 'harness_missing', `leerness 미설치: ${root} — 먼저 leerness init`); return; }
5572
+ let lang = 'ko', minimal = false;
5573
+ try { const mf = JSON.parse(read(path.join(root, '.harness', 'manifest.json'))); lang = mf.language || 'ko'; minimal = !!mf.minimal; } catch {}
5574
+ // (검수 #1) minimal 설치 존중 — minimal 이 의도적으로 뺀 문서를 '부재 손상'으로 오판/재생성하지 않는다
5575
+ const templates = coreFiles(root, lang, [], { minimal });
5576
+ const docs = _INTEGRITY_DOCS.filter(k => typeof templates[k] === 'string');
5577
+ const findings = []; const repaired = []; let repairFailed = false;
5578
+ const force = has('--force');
5579
+ for (const key of docs) {
5580
+ const tmpl = templates[key];
5581
+ const fp = path.join(root, key);
5582
+ let kind = null;
5583
+ if (!exists(fp)) kind = 'missing';
5584
+ else {
5585
+ let cur = ''; try { cur = read(fp); } catch { kind = 'unreadable'; }
5586
+ if (!kind) {
5587
+ // (검수 #3) H1 상실 = "H1 이 하나도 없음"(정확 일치 아님 — 사용자가 제목을 번역/개명해도 무탐)
5588
+ if (!cur.split('\n').some(l => /^# \S/.test(l.trim()))) kind = 'h1_lost';
5589
+ else if (cur.length < tmpl.length * 0.4) kind = 'truncated';
5590
+ }
5591
+ }
5592
+ if (!kind) continue;
5593
+ // (검수 #3) 길이-단독 신호(truncated)는 advisory — --force 없인 복구도 exit 1 도 아님 (합리적 축약 오판 방지)
5594
+ const advisory = kind === 'truncated' && !force;
5595
+ const f = { file: key, kind, advisory, templateBytes: Buffer.byteLength(tmpl, 'utf8') };
5596
+ if (repair && !advisory) {
5597
+ try {
5598
+ if (exists(fp)) {
5599
+ const bakDir = path.join(root, '.harness', 'archive');
5600
+ mkdirp(bakDir);
5601
+ // (검수 #4) 같은 ms 백업 충돌 방지 — pid+시퀀스 접미
5602
+ fs.copyFileSync(fp, path.join(bakDir, `integrity-${path.basename(key)}-${Date.now()}-${process.pid}-${++_integSeq}.bak`));
5603
+ f.backedUp = true;
5604
+ }
5605
+ writeUtf8(fp, tmpl);
5606
+ f.repaired = true; repaired.push(key);
5607
+ } catch (e) { f.repaired = false; f.error = String(e.message || e).slice(0, 120); repairFailed = true; }
5608
+ }
5609
+ findings.push(f);
5610
+ }
5611
+ // (검수 #2) 실패한 repair 는 성공이 아니다 — hard 손상 잔존 또는 복구 실패 시 exit 1 (advisory 는 exit 무영향)
5612
+ const hardLeft = findings.filter(f => !f.advisory && !f.repaired);
5613
+ if (json) {
5614
+ log(JSON.stringify({ ok: !repairFailed && hardLeft.length === 0, checked: docs.length, minimal, findings, repaired }, null, 2));
5615
+ if (repairFailed || hardLeft.length) process.exitCode = 1;
5616
+ return;
5617
+ }
5618
+ log(`# leerness integrity — managed 정책-문서 무결성 (${docs.length}종 검사${minimal ? ' · minimal' : ''})`);
5619
+ if (!findings.length) { ok(' 손상 없음 — 전 문서 구조 정상'); return; }
5620
+ for (const f of findings) {
5621
+ const icon = f.repaired ? '🔧 복구' : (f.advisory ? '⚠ 의심' : '✗ 손상');
5622
+ log(` ${icon} ${f.file} (${f.kind})${f.repaired && f.backedUp ? ' — 원본은 .harness/archive/ 대피' : ''}${f.advisory ? ' — 길이-단독 신호(advisory): 복구하려면 --repair --force' : ''}${f.error ? ` — 복구 실패: ${f.error}` : ''}`);
5623
+ }
5624
+ if (hardLeft.length && !repair) warn(` 복구: leerness integrity check --repair (손상 원본은 archive 로 대피 후 템플릿 재생성)`);
5625
+ if (repairFailed || hardLeft.length) process.exitCode = 1;
5626
+ return;
5627
+ }
5628
+ let _integSeq = 0;
5629
+
5529
5630
  // 1.36.28 (codex 미검토표면 헌트 #3 — 데이터 유실 클래스): 손상된 JSON 스토어를 "빈 값"으로 오인해 덮어쓰는 fail-open 방지.
5530
5631
  // 파일이 "없음"과 "있으나 손상"을 구분 — 후자는 파싱 실패를 던져 변경(저장)을 거부하고 원본을 보존한다.
5531
5632
  // 설계: LOAD 가 아니라 SAVE/변경 진입점에서만 던진다(handoff 등 비변경 읽기 경로는 resilient 유지). state 는
@@ -6293,6 +6394,7 @@ function commandsCmd(root) {
6293
6394
  { cmd: 'review-request "<request>"', desc: '사용자 요청 사전 검토 (1.9.176)' },
6294
6395
  { cmd: 'clarify "<사용자 요청>" [--json]', desc: '요청 모호성 신호 감지 → 사용자에게 물을 질문 생성 (추측 구현 방지) — 1.36.51 UR-0061' },
6295
6396
  { cmd: 'tech [--json]', desc: '기술 프로필 — 개발 언어·연결 서비스 자동 감지 + 마이그레이션/언어전환 이력, 그래프 🛠 탭 표시 — 1.36.53 UR-0062' },
6397
+ { cmd: 'integrity check [--repair] [--json]', desc: 'managed 정책-문서 12종 무결성(부재/H1 상실/절단) 점검 — --repair: archive 대피 후 템플릿 재생성 — 1.36.57 감사 F-04' },
6296
6398
  { cmd: 'preview add|list|show|approve|revise', desc: '신규 기능 미리보기 승인 워크플로 — approve 전 코드 작성 금지 계약 — 1.36.51 UR-0061' },
6297
6399
  { cmd: 'review <file> --persona <ids>', desc: '페르소나 리뷰 (1.9.29)' },
6298
6400
  { cmd: 'brainstorm "<topic>" [--include-code]', desc: '워크스페이스 회수 + 코드 grep' }
@@ -22658,6 +22760,10 @@ async function main() {
22658
22760
  if (cmd === 'anchors') return anchorsCmd(arg('--path', null) || _taskPositionalPath(args, 1) || process.cwd(), args[1] && !args[1].startsWith('-') ? args[1] : null); // 1.36.36: 정체성앵커 초안
22659
22761
  if (cmd === 'toggle') return _tgl.toggleCmd(arg('--path', process.cwd()), args[1], args[2], args[3], { has, VERSION }); // 1.36.30: 기능 토글 (그래프 ⚙ 탭 연동)
22660
22762
  if (cmd === 'tech') return _tech.techCmd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd(), args[1], { has }); // 1.36.53 (UR-0062): 기술 프로필
22763
+ if (cmd === 'integrity') { // 1.36.57 (감사 F-04): managed 문서 무결성 점검/복구
22764
+ if (args[1] && args[1] !== 'check') { failJson(has('--json'), 'unknown_subcommand', `알 수 없는 integrity 하위명령: ${args[1]} (가능: check [--repair] [--force])`); return process.exit(process.exitCode || 1); }
22765
+ return integrityCmd(arg('--path', null) || _taskPositionalPath(args, 2) || process.cwd());
22766
+ }
22661
22767
  // 1.36.51 (사용자 요청 UR-0061): 모호성 질문 + 미리보기 승인 워크플로
22662
22768
  if (cmd === 'clarify') return _clar.clarifyCmd(arg('--path', process.cwd()), args.slice(1).filter(x => !x.startsWith('-')).join(' '), { has });
22663
22769
  if (cmd === 'preview') {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leerness",
3
- "version": "1.36.56",
3
+ "version": "1.36.57",
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",