leerness 1.13.0 → 1.15.0

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,103 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.15.0 — 2026-06-09 — 🛡️ [안정화/Stable] Karpathy 가이드라인 정렬 3부작 안정 minor
4
+
5
+ **🛡️ 안정화(Stable) minor.** Andrej Karpathy 코딩 가이드라인(생각하고 코딩 / 단순성 / 외과적 변경 / 목표 주도) 대비 외부 에이전트 검토에서 도출한 정렬 작업(1.14.1~1.14.3)을 검증·통합해 npm 공개. R-0011 정책의 6번째 minor. 영상은 HyperFrames 파이프라인 제작.
6
+
7
+ ### 이번 minor 통합 (1.14.1~1.14.3) — Karpathy 4원칙 정렬
8
+ - **원칙1·2 (생각하고 코딩 / 단순성)**: `review-request` 가 요청의 **범위 과대 신호**(전체·모두·리팩토링·재구성·rewrite/refactor everything)와 **투기적 신호**(나중에·확장 가능·유연하게·범용화·추상화·future-proof)를 탐지해 "더 작게 쪼갤 수 있나? 요청 범위만" 환기(advisory). `--json simplicitySignals`.
9
+ - **원칙4 (목표 주도 — 검증가능한 완료)**: `plan add --done-when "<조건>"` 로 milestone 에 성공 기준을 1급 필드(`Done-When`)로 저장 · `plan list` 표시 + `--json doneWhen` · 미정 시 환기. (부수: `--done-when` 값이 제목에 흡수되던 잠복 버그 수정.)
10
+ - **원칙2 (자기적용)**: 정적 분석으로 호출 0·동적참조 0 인 **죽은 함수 5개 제거**(무행위변경). 함수 476→471.
11
+
12
+ ### 검증 (회귀 0)
13
+ - **selftest 213 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump. 각 기능 행위 재현(신호 탐지/오탐0, done-when 저장·표시, dead 제거 후 재스캔 0).
14
+
15
+ ### 안정화 표시 (R-0006)
16
+ CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
17
+
18
+ ## 1.14.3 — 2026-06-09 — Karpathy 정렬④(완결): 자체 단순화 — 죽은 코드 제거 (원칙2)
19
+
20
+ **🧹 leerness 가 자신의 원칙2(단순성)를 자신에게 적용.** 정적 분석으로 "정의됐으나 호출 0 + 동적(문자열) 참조 0" 인 함수만 골라 제거 — 무행위변경(검증가능) 단순화.
21
+
22
+ ### 변경 (UR-0033)
23
+ - **죽은 함수 5개 제거**: `_isAutoLoopActive` · `_invalidateSkillsCache` · `_currentLang` · `_typewrite` · `_writeDomainCatalog` (전부 호출처 0, 과거 리팩토링/대체로 고립된 잔재). 함수 476→471, bin 19561줄.
24
+ - 제거 후 **재스캔 = 새 고아 0** (각 함수가 쓰던 헬퍼는 다른 곳에서도 사용 중 — 연쇄 dead 없음).
25
+ - 막연한 "큰 파일 줄이기"가 아닌, **검증가능·무행위변경** 범위로 한정(Karpathy 원칙3 외과적 변경 준수).
26
+
27
+ ### 검증 (회귀 0)
28
+ - **selftest 213 PASS** · **E2E 365/365 PASS** (행위 동일 — dead 코드라 출력/동작 불변). 제거 전 각 후보의 전체 참조(bin+lib+scripts+test)=정의 1줄만 직접 확인(맹신 X).
29
+ - patch(1.14.3) — npm 미배포(R-0011, GitHub). **Karpathy 백로그(UR-0031/0032/0033) 완결.**
30
+
31
+ ## 1.14.2 — 2026-06-09 — Karpathy 정렬③: plan --done-when 검증가능 완료조건 (원칙4)
32
+
33
+ **🎯 milestone 에 "성공 기준"을 1급 필드로.** Karpathy 원칙4(목표 주도 실행 — 검증가능한 완료 정의)의 빠진 절반. plan 에 done-criteria 개념이 없어 "언제 끝인지" 가 모호했던 것을 보강.
34
+
35
+ ### 변경 (UR-0032)
36
+ - **plan add `--done-when "<조건>"`**: milestone 에 `Done-When:` 라인으로 검증가능 완료조건 저장. 미지정 시 `(미정)`.
37
+ - **plan list 표시 + `--json doneWhen`**: 각 milestone 의 완료기준 노출. 누락(legacy/미정) 시 `⚠ 미정 — --done-when 권장 (Karpathy 원칙4)` 환기.
38
+ - **🐛 잠복 버그 수정(맹신 X)**: `nonFlagArgs()` 의 value-flag 집합(`withValue`)에 `--done-when` 이 없어, 값이 positional 로 누출돼 **milestone 제목에 흡수**(예: "결제 연동" → "결제 연동 Stripe e2e…")되던 것 차단. 행위 재현으로 발견·수정.
39
+
40
+ ### 검증 (회귀 0)
41
+ - **selftest 212→213**, 행위: `plan add "결제 연동" --done-when "Stripe e2e 통과"` → 제목="결제 연동"(흡수 없음)·Done-When 분리 저장·plan list/json 노출; 미지정 → (미정)+환기.
42
+ - patch(1.14.2) — npm 미배포(R-0011, GitHub). 잔여 Karpathy: UR-0033(자체 단순화).
43
+
44
+ ## 1.14.1 — 2026-06-09 — Karpathy 정렬②: review-request 범위과대/투기적 신호 (원칙1+2)
45
+
46
+ **🤔 사전 검토 게이트에 "생각하고 코딩" + "단순성 우선" 신호 추가.** Karpathy 리뷰가 가장 약한 원칙으로 꼽은 1(트레이드오프 표면화)·2(단순성)를, 가장 많이 쓰는 `review-request`(작업 전 자동 호출)에서 보강.
47
+
48
+ ### 변경 (UR-0031)
49
+ - **review-request 단순성/범위 신호**: 요청 텍스트에서 **범위 과대 동사**(전체·모두·리팩토링·재구성·rewrite/refactor everything 등)와 **투기적 신호**(나중에·확장 가능·유연하게·범용화·추상화·future-proof 등)를 탐지해 `efficiencyHints` + `--json simplicitySignals` 로 표면화. "더 작게 쪼갤 수 있나? 요청 범위만" 환기. advisory(차단 X — 표면화만, Karpathy 원칙1).
50
+ - 신규 명령 0 — 기존 명령 확장(leerness 자신의 원칙2 준수).
51
+
52
+ ### 검증 (회귀 0)
53
+ - **selftest 211→212**, 행위 재현: "전체 코드베이스 리팩토링 + 나중에 유연하게 확장 가능하게 추상화" → broad=[전체,리팩토링] spec=[나중에,유연하게,확장 가능,추상화]; 단순 요청 → 0(오탐 없음).
54
+ - patch(1.14.1) — R-0011 정책상 npm 미배포(GitHub). 잔여 Karpathy 백로그: UR-0032(plan --done-when 성공기준), UR-0033(자체 단순화).
55
+
56
+ ## 1.14.0 — 2026-06-09 — 🛡️ [안정화/Stable] 블라인드 리뷰 수정 + Karpathy 정렬 안정 minor
57
+
58
+ **🛡️ 안정화(Stable) minor.** 블라인드 3-모델 리뷰(codex/Sonnet/Opus) 수정 + Karpathy 가이드라인 정렬(1.13.1~1.13.2)을 검증·통합해 npm 공개. R-0011 정책의 5번째 minor. (이 릴리스의 소개 영상부터 **HyperFrames 파이프라인**으로 자동 제작됩니다.)
59
+
60
+ ### 이번 minor 통합 (1.13.1~1.13.2)
61
+ - **🔴 memory search 5종 표면 복구** (블라인드 Sonnet P1): `memory search` 가 lessons.md/rules.md 를 누락해 저장한 교훈·룰을 못 찾던 문제 → 추가.
62
+ - **🔴 옵션-only 무명령의 묵시적 init 쓰기 차단** (블라인드 codex P1): `leerness --json` 처럼 명령 없이 옵션만 주면 cwd 에 `.harness` 가 생성되던 부작용 → help 처리(명시 init 만 쓰기).
63
+ - **📖 README 블라인드 재구성**: 3모델이 코드/행위만으로 파악한 정체성 반영(거짓완료 차단 전면화·다언어·경로규칙·한국어 우선).
64
+ - **🔬 verify-claim scope-creep 표면화** (Karpathy 원칙3 "외과적 변경"): git 에 변경됐으나 evidence/주장에 없는 파일(요청 범위 밖 변경)을 advisory 로 노출(`--json scopeCreep`).
65
+ - doctor 문구 + selftest README exists 가드 (블라인드 P3).
66
+
67
+ ### 검증 (회귀 0)
68
+ - **selftest 211 PASS** · **E2E 365/365 PASS** · npm gate=minor_bump. scope-creep 행위(claim A + change A,B→[B]), memory search 5종 검색, --json-무명령 비쓰기 재현.
69
+
70
+ ### 안정화 표시 (R-0006)
71
+ CHANGELOG [안정화/Stable] · git tag annotation (Stable) · GitHub release (Stable) · npm dist-tag `stable` 시도.
72
+
73
+ ## 1.13.2 — 2026-06-09 — Karpathy 가이드라인 정렬: verify-claim scope-creep 표면화 (외과적 변경)
74
+
75
+ **🔬 외부 에이전트(Sonnet/Opus) 가 leerness 를 Andrej Karpathy 4대 코딩 가이드라인 대비 검토.** 두 리뷰가 수렴: leerness 는 원칙4(목표주도/검증루프)는 최강이나, **원칙3(외과적 변경)이 최대 갭** — 변경 파일이 요청 범위 내인지 검사가 없었음. 최고가치·최저노력 항목을 즉시 구현(신규 명령 0 — leerness 자신의 원칙2 위반 악화 방지).
76
+
77
+ ### 변경 (UR-0030, Karpathy 원칙3)
78
+ - **verify-claim 역방향 git 교차검증**: 기존엔 "주장한 파일이 git 변경에 있는가"(한 방향)만 봤음. 이제 **"git 에 변경됐으나 evidence/주장에 없는 파일"(scope-creep / 요청 범위 밖 변경 신호)** 도 표면화 — `--json scopeCreep` + 사람용 advisory(`🔬 외과적 변경 점검`). 하네스 자체 기록(.harness/.git/node_modules 등) 제외, 오탐 방지 위해 advisory(기본 FAIL 아님). 이미 계산하던 `gitChanged` Set 재사용 — 최소 변경.
79
+
80
+ ### 검증 (회귀 0)
81
+ - **selftest 210→211**, 행위 재현: claim a.js + change a.js,b.js → `scopeCreep={count:1,files:["b.js"]}` (.harness 정확 제외).
82
+ - 백로그(기존 명령 확장 권장): review-request scope/단순성 신호(원칙1+2, UR-0031) · plan `--done-when` 성공기준(원칙4, UR-0032) · leerness 자체 단순화 검토(원칙2, UR-0033).
83
+ - patch(1.13.2) — R-0011 정책상 npm 미배포(GitHub).
84
+
85
+ ## 1.13.1 — 2026-06-09 — 블라인드 3-모델 리뷰 수정 + README 재구성 (codex gpt-5.5 · Sonnet 4.8 · Opus 4.8)
86
+
87
+ **🔍 블라인드 멀티모델 리뷰**: 3개 모델(codex gpt-5.5 외부 CLI + Claude Sonnet/Opus 4.8 서브에이전트)이 **leerness 소개 없이, README 를 물리적으로 제거(숨김)한 클린룸**에서 npm 최신본(1.13.0)을 코드·CLI·행위만으로 분석. 발견을 맹신 X로 직접 재현·검증해 수정하고, 그 리뷰 내용으로 README 를 재구성.
88
+
89
+ ### 버그 수정 (전부 재현 검증)
90
+ - **🔴 memory search 가 lessons/rules 누락** (Sonnet P1): `memory search` 가 5종 메모리 표면을 표방하나 `lessons.md`/`rules.md` 를 검색 못 해(저장한 교훈·룰이 'no matches') 모순감지 핵심 용도가 훼손됐음 → 두 파일 추가. (검증: lesson/rule/decision 3종 모두 검색됨)
91
+ - **🔴 옵션-only 무명령의 묵시적 init 쓰기** (codex P1): `leerness --json` 처럼 명령 없이 옵션만 주면 cwd 에 `.harness` 가 의도치 않게 생성되던 쓰기 부작용 → 무명령+옵션만은 help 로 처리(명시 `init` 만 쓰기). bare `leerness` 온보딩은 유지.
92
+ - **doctor 문구 + selftest README 의존** (Sonnet/codex/Opus P3): doctor 가 pass 수에 '실패' 를 붙여 "209/210 실패"(=209건 실패로 오독)되던 것 → "통과 N/M (K건 실패)". selftest 의 README 배너 케이스에 `exists` 가드 추가 → 문서가 없는(pruned) 설치에서도 코어 무결성 진단 통과.
93
+
94
+ ### README 재구성 (블라인드 리뷰 기반)
95
+ 3모델이 코드/행위만으로 파악한 정체성("AI 코딩 에이전트 운영/신뢰성 하네스, 코드를 대신 쓰지 않음")을 반영 — **거짓 완료 차단(핵심 차별점) 섹션 신설**(verify-claim 다언어 인식 포함), selftest 카운트 정정(→210), 경로 규칙(메모리 명령은 `--path`/`./dir`)·한국어-우선 출력 명시.
96
+
97
+ ### 검증 / 잔여
98
+ - **selftest 210 PASS** · E2E 365/365. 백로그: bare relative dir cwd-fallback(codex P2, README 경로규칙으로 문서화 + pure 함수 계약 변경은 후속).
99
+ - patch(1.13.1, 같은 minor) — R-0011 정책상 npm 미배포(GitHub). Opus 가 "버그처럼 보였으나 자기 테스트 데이터 오류"로 정직하게 철회한 항목은 수정 안 함(과수정 회피).
100
+
3
101
  ## 1.13.0 — 2026-06-09 — 🛡️ [안정화/Stable] verify-claim 다언어 + 정직성·자원·보안 안정화
4
102
 
5
103
  **🛡️ 안정화(Stable) minor. 헤드라인 = verify-claim 다언어 지원(비-JS 개발자 핵심 회귀 수정).** 15번째 멀티에이전트 버그헌트 성과(1.12.2~1.12.5)를 검증·통합해 npm 공개. R-0011 정책의 4번째 minor.
package/README.md CHANGED
@@ -67,6 +67,22 @@ CLAUDE.md / AGENTS.md 에 `세션 시작 시 leerness handoff .`, `종료 전 le
67
67
 
68
68
  ---
69
69
 
70
+ ## 거짓 완료 차단 — 핵심 차별점
71
+
72
+ 세 모델의 블라인드 리뷰가 공통으로 꼽은 leerness의 핵심 가치입니다. AI 에이전트가 가장 자주 하는 거짓말 "다 했어요"를, leerness는 **완료 주장을 실제 코드와 대조**해 검증합니다.
73
+
74
+ ```bash
75
+ # 에이전트가 "결제 API 연동 완료"라고 주장하지만 코드엔 호출 흔적이 없으면:
76
+ leerness verify-claim T-0001 --require-evidence
77
+ # ⚠ FAIL (낙관 1) · [Payment] 결제: evidence에 주장 있으나 코드에 호출 흔적 없음 → exit 1
78
+ ```
79
+
80
+ - `optimism-check` · `verify-claim` — evidence의 도메인 주장(API·DB·결제·이메일·큐·캐시·알림·스토리지 등 10종)을 실제 소스 호출과 대조. **JavaScript뿐 아니라 Python·Ruby·Go·C#·Java·PHP·Rust 구현도 인식**합니다(1.13).
81
+ - `lazy detect` — 증거 없는 done, 빈 handoff, 테스트 미실행, 미추적 TODO를 탐지.
82
+ - 정직한 완료는 통과하고 가짜 완료만 exit 1로 차단 — `gate` 또는 CI에 그대로 연결됩니다.
83
+
84
+ ---
85
+
70
86
  ## 핵심 개념 — 5계층
71
87
 
72
88
  - **기억(Memory)** — `task`/`plan`/`decision`/`lesson`/`rule`/`feature`(그래프). canonical JSON 을 단일 진실소스로 저장하고 마크다운은 projection. archive/restore 지원.
@@ -90,6 +106,8 @@ CLAUDE.md / AGENTS.md 에 `세션 시작 시 leerness handoff .`, `종료 전 le
90
106
 
91
107
  전체 명령은 `leerness commands` 또는 `leerness --help` 로 확인하세요.
92
108
 
109
+ > **경로 규칙** — 대부분 명령은 `[path]` 위치 인자를 받습니다. `task`/`decision`/`lesson` 등 메모리 명령에 다른 폴더를 지정할 땐 `--path <dir>` 또는 `./dir` 형태를 쓰세요(맨 폴더 이름만 주면 현재 폴더로 처리됩니다). 출력은 한국어가 기본이며 `--language en` 으로 영어를 늘릴 수 있습니다(일부 메시지는 한국어 유지).
110
+
93
111
  ---
94
112
 
95
113
  ## 대표 워크플로
@@ -130,7 +148,7 @@ leerness mcp serve # JSON-RPC over stdio, 85 도구
130
148
  - **원자적 UTF-8 쓰기** — temp + rename 으로 부분쓰기 손상 방지, BOM 자동 strip.
131
149
  - **shell 미경유 MCP** — `mcp serve` 의 도구 호출은 셸을 거치지 않고 인자를 직접 전달(명령 주입 차단).
132
150
  - **순수 `--json` / 일관 exit code** — 성공·실패·미존재 경로 모두 파싱 가능한 JSON(`{ok,error,code}`) + 실패 시 exit 1. 자동화·CI 친화.
133
- - **모듈 분리(DI)** + **내장 자가검증** — `lib/` 순수 유틸/IO/카탈로그 분리, `selftest` 180+ 케이스로 설치 무결성 검증.
151
+ - **모듈 분리(DI)** + **내장 자가검증** — `lib/` 순수 유틸/IO/카탈로그 분리, `selftest` 210 케이스로 설치 무결성 검증(`doctor` 가 함께 실행). 문서(README) 부재에도 코어 무결성 진단은 통과.
134
152
 
135
153
  ---
136
154
 
@@ -168,7 +186,7 @@ MIT
168
186
  <!-- leerness:project-readme:start -->
169
187
  ## Leerness Project Harness
170
188
 
171
- 이 프로젝트는 Leerness v1.13.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
189
+ 이 프로젝트는 Leerness v1.15.0 하네스를 사용합니다. AI 에이전트는 작업 전 `leerness handoff`로 컨텍스트를 적재하고, 작업 후 `leerness check`/`leerness audit`/`leerness session close`를 수행해야 합니다.
172
190
 
173
191
  ### 정체성 — AI 에이전트 운영 레이어 (UR-0030)
174
192
 
@@ -222,7 +240,7 @@ leerness memory restore decision <date|title>
222
240
 
223
241
  ### MCP server (외부 AI 통합)
224
242
 
225
- Leerness v1.13.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
243
+ Leerness v1.15.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code · Cursor · Codex CLI 등 외부 AI에 **85개 도구**를 노출:
226
244
 
227
245
  ```jsonc
228
246
  // 카테고리별
@@ -243,7 +261,7 @@ Leerness v1.13.0는 stdio JSON-RPC MCP server를 내장합니다 — Claude Code
243
261
  `<<autonomous-loop-dynamic>>` 신호만 보내면 AI가:
244
262
  1) 다음 라운드 후보 선정 → 2) 코드 변경 → 3) stress-v* 신규 작성 + 누적 회귀 → 4) e2e 219/219 → 5) npm pack + git tag + GitHub release → 6) main 자동 push (1.9.140+) → 7) session close → 8) 다음 라운드 예약.
245
263
 
246
- 현재 누적: **70 라운드 (1.9.40 → 1.13.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
264
+ 현재 누적: **70 라운드 (1.9.40 → 1.15.0)** · 매 라운드 GitHub release/태그 생성 · _reports/는 비공개 보존.
247
265
 
248
266
  ### 성능 가이드 (1.9.140 측정)
249
267
 
@@ -281,6 +299,6 @@ leerness release pack --close --auto-main-push
281
299
  - `.harness/session-handoff.md`: 다음 세션 인수인계 (자동 작성)
282
300
  - `.harness/lessons.md` / `decisions.md` / `rules.md`: 영구 메모리 (5 surface)
283
301
 
284
- Last synced by Leerness v1.13.0: 2026-06-09
302
+ Last synced by Leerness v1.15.0: 2026-06-09
285
303
  <!-- leerness:project-readme:end -->
286
304
 
package/bin/leerness.js CHANGED
@@ -32,7 +32,7 @@ const { _evidenceQuality, _parseEvidenceStats, _shellGuardAnalyze, _claimFileInG
32
32
  // 1.9.295 (UR-0025 4단계): 정적 데이터 카탈로그 모듈 분리 (비파괴, require-based).
33
33
  const { CAPABILITY_SURFACE, POWERFUL_COMMANDS, ADAPTERS, REUSE_CATEGORIES, REUSE_CHECKLIST, _DEFAULT_PLATFORM_CONSTRAINTS, _DEFAULT_DOMAIN_CATALOG, _TOOL_CATALOG, _LSP_LANG_PATTERNS, OPTIMISM_PATTERNS, BUILT_IN_PERSONAS, STRINGS, BUILTIN_CATALOG, ROADMAP_STATUS_LABEL, ROADMAP_STATUS_COLOR, SECRET_PATTERNS, MERGE_OVERWRITE_FILES, MINIMAL_SKIP_KEYS, REQUIRED_WORKSPACE_FILES, KEYWORD_STOPWORDS, SKILL_CATALOG_PRESETS } = require('../lib/catalogs'); // 1.9.344/368/369 (UR-0025): catalog 분리 · 1.11.4 (UR-0007): _TOOL_CATALOG
34
34
 
35
- const VERSION = '1.13.0';
35
+ const VERSION = '1.15.0';
36
36
 
37
37
  // 1.9.290 (UR-0037, Codex gpt-5.5 #4 수렴): CLI 전용 부작용은 require 시 실행하지 않는다.
38
38
  // 이전: warning listener 제거 / NODE_OPTIONS 변경 / chcp IIFE 가 top-level 즉시 실행 → require('harness') 시 호스트 프로세스 오염.
@@ -190,12 +190,6 @@ function _withLock(targetPath, fn, opts = {}) {
190
190
  }
191
191
  // 1.9.327 (UR-0025): _getLocalTz / _formatLocal → lib/pure-utils.js 로 이동 (순수 TZ/날짜 포맷, require 사용).
192
192
  // 자동 모드 활성 여부 (R-XXXX every-round 룰 존재 시 true)
193
- function _isAutoLoopActive(root) {
194
- try {
195
- const rules = readRules(root);
196
- return rules.some(r => r.status === 'active' && /every-round|every-session/i.test(r.trigger || ''));
197
- } catch { return false; }
198
- }
199
193
  function _getAutoLoopRule(root) {
200
194
  try {
201
195
  return readRules(root).find(r => r.status === 'active' && /every-round/i.test(r.trigger || '')) || null;
@@ -222,7 +216,7 @@ function _resolveRoot(positional) {
222
216
  }
223
217
  function nonFlagArgs() {
224
218
  const out = [];
225
- 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']);
219
+ 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']); // 1.14.2 (UR-0032): --done-when 값이 positional 로 누출돼 milestone 제목에 흡수되던 것 차단
226
220
  const a = process.argv.slice(2);
227
221
  for (let i = 0; i < a.length; i++) {
228
222
  const x = a[i];
@@ -1257,10 +1251,6 @@ function _buildAllSkills(root) {
1257
1251
  return out;
1258
1252
  }
1259
1253
  // 1.9.66: skill 추가/제거 시 캐시 invalidate (외부 helper)
1260
- function _invalidateSkillsCache(root) {
1261
- try { _SKILLS_LIST_CACHE.delete(absRoot(root)); } catch {}
1262
- }
1263
-
1264
1254
  function skillList(root) {
1265
1255
  const all = listAllSkills(root);
1266
1256
  // 1.9.84: --json 옵션 (MCP 통합용)
@@ -1862,42 +1852,11 @@ async function skillAutoCacheCmd(root, sub) {
1862
1852
  // lang 결정: explicit > .harness/LANGUAGE > LEERNESS_LANG env > 'ko' (default)
1863
1853
  // 1.9.338 (UR-0025 심층): STRINGS (i18n ko/en catalog) 는 lib/catalogs.js 로 이전 (import). _t 는 _translate(STRINGS,..) 박막.
1864
1854
  // 현재 사용 언어 결정 (env > config > 'ko')
1865
- function _currentLang(root) {
1866
- if (process.env.LEERNESS_LANG) return process.env.LEERNESS_LANG === 'en' ? 'en' : 'ko';
1867
- try {
1868
- if (root) {
1869
- const fp = path.join(root, '.harness', 'LANGUAGE');
1870
- if (exists(fp)) {
1871
- const v = read(fp).trim().toLowerCase();
1872
- if (v === 'en' || v === 'english') return 'en';
1873
- if (v === 'ko' || v === 'korean' || v === 'kr') return 'ko';
1874
- }
1875
- }
1876
- } catch {}
1877
- return 'ko'; // default
1878
- }
1879
1855
  // 1.9.338 (UR-0025 심층): 순수 _translate(STRINGS, key, lang) (lib/pure-utils) 박막 — STRINGS catalog 주입.
1880
1856
  function _t(key, lang) {
1881
1857
  return _translate(STRINGS, key, lang);
1882
1858
  }
1883
1859
 
1884
- // 1.9.206: UI/UX 개선 — typewriter / fade-in 효과 (opt-in via LEERNESS_TYPEWRITER=1)
1885
- function _typewrite(text, delayMs) {
1886
- delayMs = delayMs || 15;
1887
- if (process.env.LEERNESS_TYPEWRITER !== '1' || !process.stdout.isTTY) {
1888
- process.stdout.write(text);
1889
- return Promise.resolve();
1890
- }
1891
- return new Promise((resolve) => {
1892
- let i = 0;
1893
- const step = () => {
1894
- if (i >= text.length) return resolve();
1895
- process.stdout.write(text[i++]);
1896
- setTimeout(step, delayMs);
1897
- };
1898
- step();
1899
- });
1900
- }
1901
1860
  // 색상 helper (TTY 시 ANSI, 비-TTY 시 plain)
1902
1861
  const _ui = {
1903
1862
  bold: s => process.stdout.isTTY ? `\x1b[1m${s}\x1b[0m` : s,
@@ -3313,9 +3272,11 @@ function _selfTestCases() {
3313
3272
  return wired && behav;
3314
3273
  } },
3315
3274
  { name: 'README ASCII 배너 표시 + CLI _banner 와 동일 아트 (1.9.441)', run: () => {
3316
- const readme = read(path.join(path.dirname(__filename), '..', 'README.md'));
3275
+ // 1.13.1 (15th 블라인드 리뷰 P3, Opus): README 부재 시 read() throw 로 selftest 가 깨끗한 실패 대신 예외 → exists 가드. README 는 코어 무결성 파일이 아니므로(문서 pruning 가능) 없으면 통과 처리, 있으면 CLI _banner 와 일치 검사.
3276
+ const readmePath = path.join(path.dirname(__filename), '..', 'README.md');
3317
3277
  const bannerLine = '███████╗███████╗██╗ ██╗'.slice(0, 0) + '██║ █████╗ █████╗ ██████╔╝'; // LEERNESS 배너 고유 라인(자기참조 회피 split)
3318
- return readme.includes(bannerLine) && read(__filename).includes(bannerLine); // README CLI _banner 동일 아트
3278
+ if (!exists(readmePath)) return read(__filename).includes(bannerLine); // 문서 없으면 CLI 아트만 확인
3279
+ return read(readmePath).includes(bannerLine) && read(__filename).includes(bannerLine); // README ↔ CLI _banner 동일 아트
3319
3280
  } },
3320
3281
  { name: '12th 외부평가 Sonnet P1 (UR-0141): task 계열 positional path 지원 (cwd 오염 차단) (1.9.442)', run: () => {
3321
3282
  const m = require('../lib/pure-utils');
@@ -3560,6 +3521,24 @@ function _selfTestCases() {
3560
3521
  const reqDirectives = reqs.length === 1 && reqs[0] === 'requests';
3561
3522
  return apiCrlf && statBeforeRead && nestedSkip && shellNoSpace && reqDirectives;
3562
3523
  } },
3524
+ { name: 'Karpathy 가이드라인3 "외과적 변경" (UR-0030): verify-claim 역방향 git 교차검증 scope-creep 표면화 (1.13.2)', run: () => {
3525
+ const src = read(__filename);
3526
+ return src.includes('const changedNotClaimed = gitApplicable') && src.includes('files.some(f => _claimFileInGit(f, new Set([g])))') && src.includes('scopeCreep:') && src.includes('외과적 변경 점검');
3527
+ } },
3528
+ { name: 'Karpathy 가이드라인1+2 (UR-0031): review-request 범위과대/투기적 신호 표면화 (1.14.1)', run: () => {
3529
+ const m = require('../lib/review-request');
3530
+ const rr = read(path.join(path.dirname(__filename), '..', 'lib', 'review-request.js'));
3531
+ const wired = rr.includes('const simplicitySignals = { broad: broadHits, speculative: specHits }') && rr.includes('범위 과대 신호') && rr.includes('투기적 신호') && rr.includes('simplicitySignals,');
3532
+ return typeof m.reviewRequestCmd === 'function' && wired;
3533
+ } },
3534
+ { name: 'Karpathy 가이드라인4 (UR-0032): plan --done-when 검증가능 완료조건 저장/파싱/표시 (1.14.2)', run: () => {
3535
+ const src = read(__filename);
3536
+ const wired = src.includes("const doneWhen = _lineSafe(arg('--done-when', '') || '(미정)')") && src.includes('Done-When: ${doneWhen}') && src.includes("const doneWhenMatch = b.match(/^Done-When:") && src.includes('doneWhen: doneWhenMatch ? doneWhenMatch[1].trim() : null')
3537
+ && src.includes("'--ps-version','--done-when'"); // 잠복버그 회귀가드: nonFlagArgs withValue 에 --done-when (제목 흡수 차단)
3538
+ const b = '### M-0001. 로그인\nStatus: planned\nProgress: 0%\nDone-When: 로그인 e2e 테스트 통과\n\nTasks:\n- [ ] x\n';
3539
+ const dw = (b.match(/^Done-When:\s*(.+)$/m) || [])[1];
3540
+ return wired && dw === '로그인 e2e 테스트 통과';
3541
+ } },
3563
3542
  { name: 'VERSION 형식 (x.y.z)', run: () => /^\d+\.\d+\.\d+$/.test(VERSION) }
3564
3543
  ];
3565
3544
  }
@@ -4804,13 +4783,6 @@ function _loadDomainCatalog(root) {
4804
4783
  return merged;
4805
4784
  } catch { return _DEFAULT_DOMAIN_CATALOG; }
4806
4785
  }
4807
- function _writeDomainCatalog(root, catalog) {
4808
- try {
4809
- mkdirp(path.join(root, '.harness'));
4810
- writeUtf8(_domainCatalogPath(root), JSON.stringify({ ...catalog, updatedAt: new Date().toISOString() }, null, 2));
4811
- return true;
4812
- } catch { return false; }
4813
- }
4814
4786
  // 1.9.325 (UR-0025): _classifyIntent → lib/pure-utils.js 로 이동 (순수 intent 분류, require 사용).
4815
4787
  function _detectDomain(text, root) {
4816
4788
  return _matchDomain(_loadDomainCatalog(root), text);
@@ -6461,6 +6433,7 @@ function planListCmd(root, opts = {}) {
6461
6433
  const title = headerMatch[2].trim();
6462
6434
  const statusMatch = b.match(/^Status:\s*(.+)$/m);
6463
6435
  const progressMatch = b.match(/^Progress:\s*(.+)$/m);
6436
+ const doneWhenMatch = b.match(/^Done-When:\s*(.+)$/m); // 1.14.2 (Karpathy 원칙4, UR-0032): 검증가능 완료조건
6464
6437
  // Tasks 블록 (- [ ] 또는 - [x])
6465
6438
  const tasks = [];
6466
6439
  const tasksSection = b.match(/Tasks:\s*\n([\s\S]+?)(?=\n###|\n## |$)/);
@@ -6475,6 +6448,7 @@ function planListCmd(root, opts = {}) {
6475
6448
  title,
6476
6449
  status: statusMatch ? statusMatch[1].trim() : null,
6477
6450
  progress: progressMatch ? progressMatch[1].trim() : null,
6451
+ doneWhen: doneWhenMatch ? doneWhenMatch[1].trim() : null,
6478
6452
  tasks,
6479
6453
  });
6480
6454
  }
@@ -6489,6 +6463,7 @@ function planListCmd(root, opts = {}) {
6489
6463
  log(`\n[${m.id}] ${m.title}`);
6490
6464
  if (m.status) log(` Status: ${m.status}`);
6491
6465
  if (m.progress) log(` Progress: ${m.progress}`);
6466
+ log(` 완료기준(Done-When): ${m.doneWhen || '⚠ 미정 — plan add ... --done-when "<검증가능 조건>" 권장 (Karpathy 원칙4)'}`);
6492
6467
  if (m.tasks.length) log(` Tasks: ${m.tasks.length}개 (${m.tasks.filter(t => t.done).length} 완료)`);
6493
6468
  }
6494
6469
  }
@@ -6497,10 +6472,12 @@ function planAdd(root, text) {
6497
6472
  if (!_requireInit(root, 'plan add')) return; // 1.9.311 (UR-0047): init 가드
6498
6473
  if (!_validateChoice(arg('--status', null), TASK_STATUSES, 'plan status')) { process.exitCode = 1; return; } // 1.9.310 (UR-0046)
6499
6474
  const status = arg('--status','planned'), progress = arg('--progress','0'), nextAction = arg('--next', '다음 액션 작성');
6475
+ // 1.14.2 (Karpathy 원칙4 "성공기준 정의", UR-0032): --done-when 으로 검증가능 완료조건을 milestone 에 기록. 미지정 시 (미정) — plan show/audit 가 환기.
6476
+ const doneWhen = _lineSafe(arg('--done-when', '') || '(미정)');
6500
6477
  // 1.9.303 (UR-0043): M-id append + T-id upsert 를 하나의 락으로 — 동시 plan add ID 충돌 방지.
6501
6478
  const { id, tid } = _withLock(progressPath(root), () => {
6502
6479
  const id = nextId(root, 'M');
6503
- append(planPath(root), `\n### ${id}. ${text}\nStatus: ${status}\nProgress: ${progress}%\n\nTasks:\n- [ ] ${text}\n`);
6480
+ append(planPath(root), `\n### ${id}. ${text}\nStatus: ${status}\nProgress: ${progress}%\nDone-When: ${doneWhen}\n\nTasks:\n- [ ] ${text}\n`);
6504
6481
  const tid = nextId(root, 'T');
6505
6482
  upsertProgress(root, { id: tid, status, request: text, evidence: `plan:${id}`, nextAction });
6506
6483
  return { id, tid };
@@ -7653,7 +7630,8 @@ function preCheck(root) {
7653
7630
  function memorySearch(root, query) {
7654
7631
  root = absRoot(root);
7655
7632
  if (!query) { fail('query required (e.g., memory search "키워드")'); return; }
7656
- const files = ['.harness/decisions.md','.harness/task-log.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/plan.md','.harness/review-evidence.md','.harness/architecture.md'];
7633
+ // 1.13.1 (15th 블라인드 리뷰 P1, Sonnet): lessons.md + rules.md 누락 수정 — memory search 가 5종 메모리 표면을 표방하나 lesson/rule 을 검색 못 해(lesson add/rule add 로 저장한 교훈·룰이 'no matches') 모순감지 핵심 용도가 훼손됐음.
7634
+ const files = ['.harness/decisions.md','.harness/lessons.md','.harness/rules.md','.harness/task-log.md','.harness/session-handoff.md','.harness/progress-tracker.md','.harness/plan.md','.harness/review-evidence.md','.harness/architecture.md'];
7657
7635
  const re = new RegExp(query.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&'), 'i');
7658
7636
  let total = 0;
7659
7637
  for (const f of files) {
@@ -9627,6 +9605,9 @@ function verifyClaimCmd(root, taskId) {
9627
9605
  const gitApplicable = !!gitChanged && gitChanged.size > 0 && files.length > 0;
9628
9606
  const claimedInGit = gitApplicable ? files.filter(f => _claimFileInGit(f, gitChanged)) : [];
9629
9607
  const claimedNotInGit = gitApplicable ? files.filter(f => !_claimFileInGit(f, gitChanged)) : [];
9608
+ // 1.13.2 (Karpathy 가이드라인 3 "외과적 변경", UR-0030): 역방향 교차검증 — git 에 변경됐으나 evidence/주장에 없는 파일(scope-creep / 요청 범위 밖 변경 신호). 하네스 자체 기록(.harness 등)은 제외. advisory(오탐 방지 — 기본 FAIL 아님, 표면화만).
9609
+ const _SCOPE_SKIP = /^(\.harness[\\/]|\.git[\\/]|node_modules[\\/]|\.claude[\\/]|dist[\\/]|build[\\/])/;
9610
+ const changedNotClaimed = gitApplicable ? [...gitChanged].filter(g => !_SCOPE_SKIP.test(g) && !files.some(f => _claimFileInGit(f, new Set([g])))) : [];
9630
9611
  // 테스트 카운트: tests/test.js의 check( 또는 it( 또는 test( 개수
9631
9612
  let actualTestCount = null;
9632
9613
  const candidateTestFiles = ['tests/test.js', 'test/test.js', 'tests/index.js'];
@@ -9728,7 +9709,8 @@ function verifyClaimCmd(root, taskId) {
9728
9709
  },
9729
9710
  evidence: { required: mustHaveEvidence, ...evq },
9730
9711
  claims: !claimsChecked ? null : { ok: strictOk, optimism: optimismSuspects.map(s => ({ kind: s.kind, label: s.label })), honesty: honestyFindings.map(f => ({ dim: f.dim, label: f.label })) },
9731
- git: gitChanged === null ? { applicable: false, reason: 'not-a-git-repo' } : (!gitApplicable ? { applicable: false, reason: 'no-working-changes-or-no-claimed-files' } : { applicable: true, claimedInGit: claimedInGit.length, claimedNotInGit, strongMismatch: gitStrongMismatch })
9712
+ git: gitChanged === null ? { applicable: false, reason: 'not-a-git-repo' } : (!gitApplicable ? { applicable: false, reason: 'no-working-changes-or-no-claimed-files' } : { applicable: true, claimedInGit: claimedInGit.length, claimedNotInGit, strongMismatch: gitStrongMismatch, changedNotClaimed }),
9713
+ scopeCreep: !gitApplicable ? null : { count: changedNotClaimed.length, files: changedNotClaimed.slice(0, 10) } // 1.13.2 (Karpathy 외과적 변경): 요청/주장 밖 변경 파일(advisory)
9732
9714
  };
9733
9715
  if (runResult) {
9734
9716
  out.run = runResult;
@@ -9811,6 +9793,8 @@ function verifyClaimCmd(root, taskId) {
9811
9793
  } else {
9812
9794
  log(` - git diff 교차검증: ${gitStrongMismatch ? '⚠ 불일치' : '✓'} 주장 ${files.length}개 중 실제 변경 ${claimedInGit.length}개${claimedNotInGit.length ? ` · git 변경에 없음: ${claimedNotInGit.slice(0, 5).join(', ')}` : ''}`);
9813
9795
  if (gitStrongMismatch) log(` · 주장한 파일이 working tree/직전커밋 변경에 전무 — 변경이 더 오래전 커밋이거나, 실제로 변경 안 됐을 수 있음(허위완료 의심)${has('--strict-claims') ? ' → FAIL' : ' (advisory — 커밋 후 검증 시 정상일 수 있음)'}`);
9796
+ // 1.13.2 (Karpathy 가이드라인 3 "외과적 변경", UR-0030): scope-creep 표면화 — git 변경됐으나 evidence/주장에 없는 파일.
9797
+ if (changedNotClaimed.length) log(` · 🔬 외과적 변경 점검: git 에 변경됐으나 evidence/주장에 없는 파일 ${changedNotClaimed.length}건: ${changedNotClaimed.slice(0, 5).join(', ')}${changedNotClaimed.length > 5 ? ' …' : ''} — 요청 범위 밖 변경(scope creep)인지 확인 (advisory)`);
9814
9798
  }
9815
9799
  // 1.9.309 (UR-0048): done 주장 evidence 완전성 — 기본 강제(상단 pre-compute). --lenient 로 opt-out.
9816
9800
  if (mustHaveEvidence) {
@@ -19107,6 +19091,8 @@ async function main() {
19107
19091
  return log(VERSION);
19108
19092
  }
19109
19093
  if (has('--help') || has('-h')) return help();
19094
+ // 1.13.1 (15th 블라인드 리뷰 P1, codex gpt-5.5): 무명령 + 옵션만(예: leerness --json) 은 묵시적 init 쓰기를 하지 않음 — cwd 에 .harness 가 의도치 않게 생성되던 부작용 차단. 명시 명령 없이 옵션만이면 help (bare leerness 온보딩 init 은 유지).
19095
+ if (!args[0] && process.argv.slice(2).some(a => a.startsWith('-'))) { help(); return; }
19110
19096
  // 1.9.38 (B): 사용 통계 카운터 — usage stats 명령 자체와 비차단 경로는 제외
19111
19097
  // 1.9.317 (UR-0051, 설치리뷰): 내부 auto-call(LEERNESS_INTERNAL=1) 은 usage 집계 제외 — 텔레메트리 오염(거짓 skill 추천) 방지.
19112
19098
  if (process.env.LEERNESS_INTERNAL !== '1' && cmd !== 'usage' && cmd !== 'init' && cmd !== 'migrate' && cmd !== '--version' && cmd !== '--help') {